|
|
|
|
|
|
#include "UnityStandardBRDF.cginc" |
|
|
|
#include "UnityStandardUtils.cginc" |
|
|
|
|
|
|
|
#define DEBUG_CASCADES 0 |
|
|
|
#define MAX_SHADOW_CASCADES 4 |
|
|
|
#define MAX_LIGHTS 8 |
|
|
|
|
|
|
|
|
|
|
half4 globalLightColor[MAX_LIGHTS]; |
|
|
|
float4 globalLightPos[MAX_LIGHTS]; |
|
|
|
float4 globalLightSpotDir[MAX_LIGHTS]; |
|
|
|
float4 globalLightAtten[MAX_LIGHTS]; |
|
|
|
half4 globalLightSpotDir[MAX_LIGHTS]; |
|
|
|
half4 globalLightAtten[MAX_LIGHTS]; |
|
|
|
|
|
|
|
int4 globalLightCount; // x: pixelLightCount, y = totalLightCount (pixel + vert) |
|
|
|
|
|
|
|
|
|
|
sampler2D _MainTex; float4 _MainTex_ST; |
|
|
|
sampler2D _MainTex; half4 _MainTex_ST; |
|
|
|
float _Metallic; |
|
|
|
float _Glossiness; |
|
|
|
half _Metallic; |
|
|
|
half _Glossiness; |
|
|
|
float4x4 _WorldToShadow[MAX_SHADOW_CASCADES]; |
|
|
|
float4 _PSSMDistances; |
|
|
|
half4x4 _WorldToShadow[MAX_SHADOW_CASCADES]; |
|
|
|
half4 _PSSMDistances; |
|
|
|
|
|
|
|
struct LightInput |
|
|
|
{ |
|
|
|
|
|
|
half4 spotDir; |
|
|
|
}; |
|
|
|
|
|
|
|
inline half3 EvaluateOneLight(LightInput lightInput, half3 diffuseColor, half3 specularColor, float3 normal, float3 posWorld, half3 viewDir) |
|
|
|
inline int ComputeCascadeIndex(half eyeZ) |
|
|
|
{ |
|
|
|
// PSSMDistance is set to infinity for non active cascades. This way the comparison for unavailable cascades will always be zero. |
|
|
|
half3 cascadeCompare = step(_PSSMDistances, half3(eyeZ, eyeZ, eyeZ)); |
|
|
|
return dot(cascadeCompare, cascadeCompare); |
|
|
|
} |
|
|
|
|
|
|
|
inline half3 EvaluateOneLight(LightInput lightInput, half3 diffuseColor, half3 specularColor, half3 normal, float3 posWorld, half3 viewDir) |
|
|
|
{ |
|
|
|
float3 posToLight = lightInput.pos.xyz; |
|
|
|
posToLight -= posWorld * lightInput.pos.w; |
|
|
|
|
|
|
return diffuse + specular; |
|
|
|
} |
|
|
|
|
|
|
|
inline half3 EvaluateOneLightAndShadow(LightInput lightInput, half3 diffuseColor, half3 specularColor, float3 normal, float3 posWorld, half3 viewDir, half3 shadowCoord) |
|
|
|
inline half3 EvaluateOneLightAndShadow(LightInput lightInput, half3 diffuseColor, half3 specularColor, half3 normal, float4 posWorld, half3 viewDir) |
|
|
|
int cascadeIndex = ComputeCascadeIndex(posWorld.w); |
|
|
|
float3 shadowCoord = mul(_WorldToShadow[cascadeIndex], float4(posWorld.xyz, 1.0)); |
|
|
|
|
|
|
|
// TODO: Apply proper bias considering NdotL |
|
|
|
half bias = 0.001; |
|
|
|
half shadowDepth = tex2D(g_tShadowBuffer, shadowCoord.xy).r; |
|
|
|
|
|
|
return color * shadowAttenuation; |
|
|
|
} |
|
|
|
|
|
|
|
inline int ComputeCascadeIndex(float eyeZ) |
|
|
|
{ |
|
|
|
// PSSMDistance is set to infinity for non active cascades. This way the comparison for unavailable cascades will always be zero. |
|
|
|
half3 cascadeCompare = step(_PSSMDistances, half3(eyeZ, eyeZ, eyeZ)); |
|
|
|
return dot(cascadeCompare, cascadeCompare); |
|
|
|
} |
|
|
|
|
|
|
|
struct VertexInput |
|
|
|
{ |
|
|
|
float4 vertex : POSITION; |
|
|
|
|
|
|
|
|
|
|
// Vertex shader |
|
|
|
float3 positionWS : TEXCOORD1; |
|
|
|
float3 normalWS : TEXCOORD2; |
|
|
|
float3 color : TEXCOORD3; |
|
|
|
float3 shadowCoord : TEXCOORD4; |
|
|
|
float4 posWSEyeZ : TEXCOORD1; // xyz: posWorld, w: eyeZ |
|
|
|
half3 normalWS : TEXCOORD2; |
|
|
|
half3 color : TEXCOORD3; |
|
|
|
float4 hpos : SV_POSITION; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex); |
|
|
|
o.hpos = UnityObjectToClipPos(v.vertex); |
|
|
|
o.positionWS = mul(unity_ObjectToWorld, v.vertex).xyz; |
|
|
|
o.posWSEyeZ.xyz = mul(unity_ObjectToWorld, v.vertex).xyz; |
|
|
|
o.posWSEyeZ.w = -UnityObjectToViewPos(v.vertex).z; |
|
|
|
|
|
|
|
half eyePosZ = -UnityObjectToViewPos(v.vertex).z; |
|
|
|
half3 viewDir = normalize(_WorldSpaceCameraPos - o.positionWS); |
|
|
|
int cascadeIndex = ComputeCascadeIndex(eyePosZ); |
|
|
|
o.shadowCoord = mul(_WorldToShadow[cascadeIndex], float4(o.positionWS, 1.0)); |
|
|
|
half3 viewDir = normalize(_WorldSpaceCameraPos - o.posWSEyeZ); |
|
|
|
|
|
|
|
for (int lightIndex = globalLightCount.x; lightIndex < globalLightCount.y; ++lightIndex) |
|
|
|
{ |
|
|
|
|
|
|
lightInput.atten = globalLightAtten[lightIndex]; |
|
|
|
lightInput.spotDir = globalLightSpotDir[lightIndex]; |
|
|
|
o.color += EvaluateOneLight(lightInput, diffuseAndSpecularColor, diffuseAndSpecularColor, o.normalWS, o.positionWS, viewDir); |
|
|
|
o.color += EvaluateOneLight(lightInput, diffuseAndSpecularColor, diffuseAndSpecularColor, o.normalWS, o.posWSEyeZ.xyz, viewDir); |
|
|
|
#if DEBUG_CASCADES |
|
|
|
half4 cascadeColors[MAX_SHADOW_CASCADES] = { half4(0.5, 0, 0, 1) , half4(0, 0.5, 0, 1) , half4(0, 0.0, 0.5, 1) , half4(0.5, 0, 0.5, 1) }; |
|
|
|
o.color = cascadeColors[cascadeIndex]; |
|
|
|
#endif |
|
|
|
return o; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
half3 viewDir = normalize(_WorldSpaceCameraPos - i.positionWS); |
|
|
|
float3 posWorld = i.posWSEyeZ.xyz; |
|
|
|
half3 viewDir = normalize(_WorldSpaceCameraPos - posWorld); |
|
|
|
|
|
|
|
half4 diffuseAlbedo = tex2D(_MainTex, i.uv); |
|
|
|
half2 metalSmooth; |
|
|
|
|
|
|
|
|
|
|
half3 color = i.color * diffuseAlbedo.rgb; |
|
|
|
|
|
|
|
#if !DEBUG_CASCADES |
|
|
|
for (int lightIndex = 0; lightIndex < globalLightCount.x; ++lightIndex) |
|
|
|
{ |
|
|
|
LightInput lightInput; |
|
|
|
|
|
|
lightInput.spotDir = globalLightSpotDir[lightIndex]; |
|
|
|
|
|
|
|
if (lightIndex == 0) |
|
|
|
color += EvaluateOneLightAndShadow(lightInput, diffuse, specColor, i.normalWS, i.positionWS, viewDir, i.shadowCoord); |
|
|
|
color += EvaluateOneLightAndShadow(lightInput, diffuse, specColor, i.normalWS, i.posWSEyeZ, viewDir); |
|
|
|
color += EvaluateOneLight(lightInput, diffuse, specColor, i.normalWS, i.positionWS, viewDir); |
|
|
|
color += EvaluateOneLight(lightInput, diffuse, specColor, i.normalWS, posWorld, viewDir); |
|
|
|
#endif |
|
|
|
|
|
|
|
return half4(color, diffuseAlbedo.a); |
|
|
|
} |
|
|
|
ENDCG |
|
|
|