Shader "Builtin Forward Lit" { Properties { _BaseColor ("Base Color", Color) = (1,1,1,1) [Semantic(BaseColor)] _Cutoff ("Alpha Cutoff", Range) = 0.5 [Semantic(AlphaCutoff)] _MainTex ("Base Map", 2D) = "white" [Semantic(BaseColorTexture)] } HLSLINCLUDE cbuffer PerObjectConstants { float4x4 gProjectionMatrix; float4x4 gViewMatrix; float4x4 gModelMatrix; float4x4 gNormalMatrix; }; static const int XC_MAX_ADDITIONAL_LIGHTS = 8; struct AdditionalLightData { float4 colorAndIntensity; float4 positionAndRange; float4 directionAndType; float4 spotAnglesAndFlags; }; struct ShadowMapMetrics { float2 inverseMapSize; float worldTexelSize; float padding; }; struct ShadowSamplingData { float enabled; float receiverDepthBias; float normalBiasScale; float shadowStrength; }; cbuffer LightingConstants { float4 gMainLightDirectionAndIntensity; float4 gMainLightColorAndFlags; float4 gLightingParams; AdditionalLightData gAdditionalLights[XC_MAX_ADDITIONAL_LIGHTS]; }; cbuffer MaterialConstants { float4 gBaseColorFactor; float4 gAlphaCutoffParams; }; cbuffer ShadowReceiverConstants { float4x4 gWorldToShadowMatrix; ShadowMapMetrics gShadowMapMetrics; ShadowSamplingData gShadowSampling; }; Texture2D BaseColorTexture; SamplerState LinearClampSampler; Texture2D ShadowMapTexture; SamplerState ShadowMapSampler; struct VSInput { float3 position : POSITION; float3 normal : NORMAL; float2 texcoord : TEXCOORD0; }; struct PSInput { float4 position : SV_POSITION; float3 normalWS : TEXCOORD0; float2 texcoord : TEXCOORD1; float3 positionWS : TEXCOORD2; }; PSInput MainVS(VSInput input) { PSInput output; const float4 positionWS = mul(gModelMatrix, float4(input.position, 1.0f)); const float4 positionVS = mul(gViewMatrix, positionWS); output.position = mul(gProjectionMatrix, positionVS); output.normalWS = mul((float3x3)gNormalMatrix, input.normal); output.texcoord = input.texcoord; output.positionWS = positionWS.xyz; return output; } float SampleShadowVisibility(float2 sampleUv, float receiverDepth) { if (sampleUv.x < 0.0f || sampleUv.x > 1.0f || sampleUv.y < 0.0f || sampleUv.y > 1.0f) { return 1.0f; } const float shadowDepth = ShadowMapTexture.Sample(ShadowMapSampler, sampleUv).r; return receiverDepth <= shadowDepth ? 1.0f : 0.0f; } float ResolveShadowPcfAxisWeight(int offset) { const int absoluteOffset = abs(offset); if (absoluteOffset == 0) { return 6.0f; } if (absoluteOffset == 1) { return 4.0f; } return 1.0f; } float ComputeShadowPcfVisibility(float2 shadowUv, float receiverDepth, float2 shadowTexelSize) { float weightedVisibility = 0.0f; float weightSum = 0.0f; [unroll] for (int offsetY = -2; offsetY <= 2; ++offsetY) { const float weightY = ResolveShadowPcfAxisWeight(offsetY); [unroll] for (int offsetX = -2; offsetX <= 2; ++offsetX) { const float weight = weightY * ResolveShadowPcfAxisWeight(offsetX); const float2 sampleUv = shadowUv + float2((float)offsetX, (float)offsetY) * shadowTexelSize; weightedVisibility += SampleShadowVisibility(sampleUv, receiverDepth) * weight; weightSum += weight; } } return weightSum > 0.0f ? weightedVisibility / weightSum : 1.0f; } float ComputeShadowAttenuation(float3 positionWS, float3 normalWS, float3 lightDirectionWS) { #ifndef XC_MAIN_LIGHT_SHADOWS return 1.0f; #else if (gShadowSampling.enabled < 0.5f) { return 1.0f; } const float nDotL = saturate(dot(normalize(normalWS), normalize(lightDirectionWS))); const float normalBiasWorld = gShadowMapMetrics.worldTexelSize * gShadowSampling.normalBiasScale * (1.0f - nDotL); const float3 shadowPositionWS = positionWS + normalize(normalWS) * normalBiasWorld; const float4 shadowClip = mul(gWorldToShadowMatrix, float4(shadowPositionWS, 1.0f)); if (shadowClip.w <= 0.0f) { return 1.0f; } const float3 shadowNdc = shadowClip.xyz / shadowClip.w; #if UNITY_UV_STARTS_AT_TOP const float shadowUvY = shadowNdc.y * -0.5f + 0.5f; #else const float shadowUvY = shadowNdc.y * 0.5f + 0.5f; #endif const float2 shadowUv = float2(shadowNdc.x * 0.5f + 0.5f, shadowUvY); #if UNITY_NEAR_CLIP_VALUE < 0 if (shadowUv.x < 0.0f || shadowUv.x > 1.0f || shadowUv.y < 0.0f || shadowUv.y > 1.0f || shadowNdc.z < -1.0f || shadowNdc.z > 1.0f) { return 1.0f; } const float receiverDepth = shadowNdc.z * 0.5f + 0.5f - gShadowSampling.receiverDepthBias; #else if (shadowUv.x < 0.0f || shadowUv.x > 1.0f || shadowUv.y < 0.0f || shadowUv.y > 1.0f || shadowNdc.z < 0.0f || shadowNdc.z > 1.0f) { return 1.0f; } const float receiverDepth = shadowNdc.z - gShadowSampling.receiverDepthBias; #endif const float2 shadowTexelSize = gShadowMapMetrics.inverseMapSize; const float visibility = ComputeShadowPcfVisibility( shadowUv, receiverDepth, shadowTexelSize); const float shadowStrength = saturate(gShadowSampling.shadowStrength); return lerp(1.0f - shadowStrength, 1.0f, visibility); #endif } float ComputeRangeAttenuation(float distanceSq, float range) { if (range <= 0.0f) { return 0.0f; } const float clampedRange = max(range, 0.0001f); const float rangeSq = clampedRange * clampedRange; if (distanceSq >= rangeSq) { return 0.0f; } const float distance = sqrt(max(distanceSq, 0.0f)); const float normalized = saturate(1.0f - distance / clampedRange); return normalized * normalized; } float ComputeSpotAttenuation(AdditionalLightData light, float3 directionToLightWS) { const float cosOuter = light.spotAnglesAndFlags.x; const float cosInner = light.spotAnglesAndFlags.y; const float3 spotAxisToLightWS = normalize(light.directionAndType.xyz); const float cosTheta = dot(spotAxisToLightWS, directionToLightWS); return saturate((cosTheta - cosOuter) / max(cosInner - cosOuter, 1e-4f)); } float3 EvaluateAdditionalLight(AdditionalLightData light, float3 normalWS, float3 positionWS) { const float lightType = light.directionAndType.w; const float3 lightColor = light.colorAndIntensity.rgb; const float lightIntensity = light.colorAndIntensity.w; float3 directionToLightWS = float3(0.0f, 0.0f, 0.0f); float attenuation = 1.0f; if (lightType < 0.5f) { directionToLightWS = normalize(light.directionAndType.xyz); } else { const float3 lightVectorWS = light.positionAndRange.xyz - positionWS; const float distanceSq = dot(lightVectorWS, lightVectorWS); if (distanceSq <= 1e-6f) { return 0.0f.xxx; } directionToLightWS = lightVectorWS * rsqrt(distanceSq); attenuation = ComputeRangeAttenuation(distanceSq, light.positionAndRange.w); if (attenuation <= 0.0f) { return 0.0f.xxx; } if (lightType > 1.5f) { attenuation *= ComputeSpotAttenuation(light, directionToLightWS); if (attenuation <= 0.0f) { return 0.0f.xxx; } } } const float diffuse = saturate(dot(normalWS, directionToLightWS)); if (diffuse <= 0.0f) { return 0.0f.xxx; } return lightColor * (diffuse * lightIntensity * attenuation); } float4 MainPS(PSInput input) : SV_TARGET { float4 baseColor = BaseColorTexture.Sample(LinearClampSampler, input.texcoord) * gBaseColorFactor; #ifdef XC_ALPHA_TEST clip(baseColor.a - gAlphaCutoffParams.x); #endif const int additionalLightCount = min((int)gLightingParams.x, XC_MAX_ADDITIONAL_LIGHTS); if (gMainLightColorAndFlags.a < 0.5f && additionalLightCount == 0) { return baseColor; } const float3 normalWS = normalize(input.normalWS); float3 lighting = gLightingParams.yyy; if (gMainLightColorAndFlags.a >= 0.5f) { const float3 directionToLightWS = normalize(gMainLightDirectionAndIntensity.xyz); const float diffuse = saturate(dot(normalWS, directionToLightWS)); const float shadowAttenuation = diffuse > 0.0f ? ComputeShadowAttenuation(input.positionWS, normalWS, directionToLightWS) : 1.0f; lighting += gMainLightColorAndFlags.rgb * (diffuse * gMainLightDirectionAndIntensity.w * shadowAttenuation); } [unroll] for (int lightIndex = 0; lightIndex < XC_MAX_ADDITIONAL_LIGHTS; ++lightIndex) { if (lightIndex >= additionalLightCount) { break; } lighting += EvaluateAdditionalLight(gAdditionalLights[lightIndex], normalWS, input.positionWS); } return float4(baseColor.rgb * lighting, baseColor.a); } ENDHLSL SubShader { Cull Back ZWrite On ZTest LEqual Pass { Name "ForwardLit" Tags { "LightMode" = "ForwardLit" } HLSLPROGRAM #pragma target 4.5 #pragma vertex MainVS #pragma fragment MainPS #pragma multi_compile _ XC_MAIN_LIGHT_SHADOWS #pragma shader_feature_local _ XC_ALPHA_TEST ENDHLSL } } }