2026-04-04 16:42:31 +08:00
|
|
|
Shader "Builtin Forward Lit"
|
2026-04-02 23:04:59 +08:00
|
|
|
{
|
2026-04-04 16:42:31 +08:00
|
|
|
Properties
|
2026-04-03 00:01:31 +08:00
|
|
|
{
|
2026-04-04 16:42:31 +08:00
|
|
|
_BaseColor ("Base Color", Color) = (1,1,1,1) [Semantic(BaseColor)]
|
2026-04-06 21:05:50 +08:00
|
|
|
_Cutoff ("Alpha Cutoff", Range) = 0.5 [Semantic(AlphaCutoff)]
|
2026-04-04 16:42:31 +08:00
|
|
|
_MainTex ("Base Map", 2D) = "white" [Semantic(BaseColorTexture)]
|
2026-04-03 00:01:31 +08:00
|
|
|
}
|
2026-04-07 03:35:06 +08:00
|
|
|
HLSLINCLUDE
|
2026-04-07 10:34:20 +08:00
|
|
|
cbuffer PerObjectConstants
|
2026-04-07 03:35:06 +08:00
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-07 10:34:20 +08:00
|
|
|
cbuffer LightingConstants
|
2026-04-07 03:35:06 +08:00
|
|
|
{
|
|
|
|
|
float4 gMainLightDirectionAndIntensity;
|
|
|
|
|
float4 gMainLightColorAndFlags;
|
|
|
|
|
float4 gLightingParams;
|
|
|
|
|
AdditionalLightData gAdditionalLights[XC_MAX_ADDITIONAL_LIGHTS];
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-07 10:34:20 +08:00
|
|
|
cbuffer MaterialConstants
|
2026-04-07 03:35:06 +08:00
|
|
|
{
|
|
|
|
|
float4 gBaseColorFactor;
|
|
|
|
|
float4 gAlphaCutoffParams;
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-07 10:34:20 +08:00
|
|
|
cbuffer ShadowReceiverConstants
|
2026-04-07 03:35:06 +08:00
|
|
|
{
|
|
|
|
|
float4x4 gWorldToShadowMatrix;
|
|
|
|
|
float4 gShadowBiasAndTexelSize;
|
|
|
|
|
float4 gShadowOptions;
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-07 10:34:20 +08:00
|
|
|
Texture2D BaseColorTexture;
|
|
|
|
|
SamplerState LinearClampSampler;
|
|
|
|
|
Texture2D ShadowMapTexture;
|
|
|
|
|
SamplerState ShadowMapSampler;
|
2026-04-07 03:35:06 +08:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 22:14:02 +08:00
|
|
|
float ComputeShadowAttenuation(float3 positionWS, float3 normalWS, float3 lightDirectionWS)
|
2026-04-07 03:35:06 +08:00
|
|
|
{
|
|
|
|
|
#ifndef XC_MAIN_LIGHT_SHADOWS
|
|
|
|
|
return 1.0f;
|
|
|
|
|
#else
|
|
|
|
|
if (gShadowOptions.x < 0.5f) {
|
|
|
|
|
return 1.0f;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 22:14:02 +08:00
|
|
|
const float nDotL = saturate(dot(normalize(normalWS), normalize(lightDirectionWS)));
|
|
|
|
|
const float normalBiasWorld = gShadowOptions.y * gShadowOptions.z * (1.0f - nDotL);
|
|
|
|
|
const float3 shadowPositionWS = positionWS + normalize(normalWS) * normalBiasWorld;
|
|
|
|
|
const float4 shadowClip = mul(gWorldToShadowMatrix, float4(shadowPositionWS, 1.0f));
|
2026-04-07 03:35:06 +08:00
|
|
|
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 - gShadowBiasAndTexelSize.x;
|
|
|
|
|
#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 - gShadowBiasAndTexelSize.x;
|
|
|
|
|
#endif
|
2026-04-11 22:14:02 +08:00
|
|
|
const float2 shadowTexelSize = gShadowBiasAndTexelSize.yz;
|
|
|
|
|
float visibility = 0.0f;
|
|
|
|
|
[unroll]
|
|
|
|
|
for (int offsetY = -1; offsetY <= 1; ++offsetY) {
|
|
|
|
|
[unroll]
|
|
|
|
|
for (int offsetX = -1; offsetX <= 1; ++offsetX) {
|
|
|
|
|
const float2 sampleUv =
|
|
|
|
|
shadowUv + float2((float)offsetX, (float)offsetY) * shadowTexelSize;
|
|
|
|
|
if (sampleUv.x < 0.0f || sampleUv.x > 1.0f ||
|
|
|
|
|
sampleUv.y < 0.0f || sampleUv.y > 1.0f) {
|
|
|
|
|
visibility += 1.0f;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const float shadowDepth = ShadowMapTexture.Sample(ShadowMapSampler, sampleUv).r;
|
|
|
|
|
visibility += receiverDepth <= shadowDepth ? 1.0f : 0.0f;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
visibility *= (1.0f / 9.0f);
|
2026-04-07 03:35:06 +08:00
|
|
|
const float shadowStrength = saturate(gShadowBiasAndTexelSize.w);
|
2026-04-11 22:14:02 +08:00
|
|
|
return lerp(1.0f - shadowStrength, 1.0f, visibility);
|
2026-04-07 03:35:06 +08:00
|
|
|
#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 =
|
2026-04-11 22:14:02 +08:00
|
|
|
diffuse > 0.0f ? ComputeShadowAttenuation(input.positionWS, normalWS, directionToLightWS) : 1.0f;
|
2026-04-07 03:35:06 +08:00
|
|
|
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
|
2026-04-04 16:42:31 +08:00
|
|
|
SubShader
|
2026-04-02 23:04:59 +08:00
|
|
|
{
|
2026-04-07 03:35:06 +08:00
|
|
|
Cull Back
|
|
|
|
|
ZWrite On
|
|
|
|
|
ZTest LEqual
|
2026-04-04 16:42:31 +08:00
|
|
|
Pass
|
|
|
|
|
{
|
|
|
|
|
Name "ForwardLit"
|
2026-04-08 13:13:42 +08:00
|
|
|
Tags { "LightMode" = "ForwardLit" }
|
2026-04-04 16:42:31 +08:00
|
|
|
HLSLPROGRAM
|
2026-04-07 03:35:06 +08:00
|
|
|
#pragma target 4.5
|
2026-04-04 16:42:31 +08:00
|
|
|
#pragma vertex MainVS
|
|
|
|
|
#pragma fragment MainPS
|
2026-04-06 20:30:25 +08:00
|
|
|
#pragma multi_compile _ XC_MAIN_LIGHT_SHADOWS
|
2026-04-06 21:05:50 +08:00
|
|
|
#pragma shader_feature_local _ XC_ALPHA_TEST
|
2026-04-04 16:42:31 +08:00
|
|
|
ENDHLSL
|
2026-04-02 23:04:59 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|