From adb6fe4659dd01db94f128c4411034c1128b6e4a Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Mon, 13 Apr 2026 03:14:06 +0800 Subject: [PATCH] rendering: improve main light shadow receiver filtering --- .../assets/builtin/shaders/forward-lit.shader | 66 +- project/Assets/Shaders/Toon.shader | 617 ++++++++++++++++++ project/Assets/Shaders/Toon.shader.meta | 5 + 3 files changed, 669 insertions(+), 19 deletions(-) create mode 100644 project/Assets/Shaders/Toon.shader create mode 100644 project/Assets/Shaders/Toon.shader.meta diff --git a/engine/assets/builtin/shaders/forward-lit.shader b/engine/assets/builtin/shaders/forward-lit.shader index 0375418c..3ea4564a 100644 --- a/engine/assets/builtin/shaders/forward-lit.shader +++ b/engine/assets/builtin/shaders/forward-lit.shader @@ -93,6 +93,49 @@ Shader "Builtin Forward Lit" 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 @@ -136,25 +179,10 @@ Shader "Builtin Forward Lit" const float receiverDepth = shadowNdc.z - gShadowSampling.receiverDepthBias; #endif const float2 shadowTexelSize = gShadowMapMetrics.inverseMapSize; - 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); + const float visibility = ComputeShadowPcfVisibility( + shadowUv, + receiverDepth, + shadowTexelSize); const float shadowStrength = saturate(gShadowSampling.shadowStrength); return lerp(1.0f - shadowStrength, 1.0f, visibility); #endif diff --git a/project/Assets/Shaders/Toon.shader b/project/Assets/Shaders/Toon.shader new file mode 100644 index 00000000..9e38dbea --- /dev/null +++ b/project/Assets/Shaders/Toon.shader @@ -0,0 +1,617 @@ +Shader "Toon" +{ + Properties + { + _BaseMap ("Base Map", 2D) = "white" [Semantic(BaseColorTexture)] + _BaseColor ("Base Color", Color) = (1,1,1,1) [Semantic(BaseColor)] + _Cutoff ("Alpha Cutoff", Range) = 0.5 [Semantic(AlphaCutoff)] + _IsDay ("Is Day", Float) = 1 + _DoubleSided ("Double Sided", Float) = 0 + _LightMap ("Light Map", 2D) = "white" + _LightDirectionMultiplier ("Light Direction Multiplier", Vector) = (1,0.5,1,0) + _ShadowOffset ("Shadow Offset", Float) = 0.1 + _ShadowSmoothness ("Shadow Smoothness", Float) = 0.4 + _ShadowColor ("Shadow Color", Color) = (1.1,1.1,1.1,1) + _ShadowRamp ("Shadow Ramp", 2D) = "white" + _UseCustomMaterialType ("Use Custom Material Type", Float) = 0 + _CustomMaterialType ("Custom Material Type", Float) = 1 + _UseEmission ("Use Emission", Float) = 0 + _EmissionIntensity ("Emission Intensity", Float) = 0.2 + _UseNormalMap ("Use Normal Map", Float) = 0 + _NormalMap ("Normal Map", 2D) = "bump" + _IsFace ("Is Face", Float) = 0 + _FaceDirection ("Face Direction", Vector) = (0,0,1,0) + _FaceShadowOffset ("Face Shadow Offset", Float) = 0 + _FaceBlushColor ("Face Blush Color", Color) = (1,0.72156864,0.69803923,1) + _FaceBlushStrength ("Face Blush Strength", Float) = 0 + _FaceLightMap ("Face Light Map", 2D) = "white" + _FaceShadow ("Face Shadow", 2D) = "white" + _UseSpecular ("Use Specular", Float) = 0 + _SpecularSmoothness ("Specular Smoothness", Float) = 5 + _NonmetallicIntensity ("Nonmetallic Intensity", Float) = 0.3 + _MetallicIntensity ("Metallic Intensity", Float) = 8 + _MetalMap ("Metal Map", 2D) = "white" + _UseRim ("Use Rim", Float) = 0 + _RimOffset ("Rim Offset", Float) = 5 + _RimThreshold ("Rim Threshold", Float) = 0.5 + _RimIntensity ("Rim Intensity", Float) = 0.5 + _UseSmoothNormal ("Use Smooth Normal", Float) = 0 + _OutlineWidth ("Outline Width", Float) = 1.6 + _OutlineWidthParams ("Outline Width Params", Vector) = (0,6,0.1,0.6) + _OutlineZOffset ("Outline Z Offset", Float) = 0.1 + _ScreenOffset ("Screen Offset", Vector) = (0,0,0,0) + _OutlineColor ("Outline Color", Color) = (0.5176471,0.35686275,0.34117648,1) + _OutlineColor2 ("Outline Color 2", Color) = (0.3529412,0.3529412,0.3529412,1) + _OutlineColor3 ("Outline Color 3", Color) = (0.47058824,0.47058824,0.5647059,1) + _OutlineColor4 ("Outline Color 4", Color) = (0.5176471,0.35686275,0.34117648,1) + _OutlineColor5 ("Outline Color 5", Color) = (0.35,0.35,0.35,1) + } + 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 _BaseColor; + float4 _Cutoff; + float4 _IsDay; + float4 _DoubleSided; + float4 _LightDirectionMultiplier; + float4 _ShadowOffset; + float4 _ShadowSmoothness; + float4 _ShadowColor; + float4 _UseCustomMaterialType; + float4 _CustomMaterialType; + float4 _UseEmission; + float4 _EmissionIntensity; + float4 _UseNormalMap; + float4 _IsFace; + float4 _FaceDirection; + float4 _FaceShadowOffset; + float4 _FaceBlushColor; + float4 _FaceBlushStrength; + float4 _UseSpecular; + float4 _SpecularSmoothness; + float4 _NonmetallicIntensity; + float4 _MetallicIntensity; + float4 _UseRim; + float4 _RimOffset; + float4 _RimThreshold; + float4 _RimIntensity; + float4 _UseSmoothNormal; + float4 _OutlineWidth; + float4 _OutlineWidthParams; + float4 _OutlineZOffset; + float4 _ScreenOffset; + float4 _OutlineColor; + float4 _OutlineColor2; + float4 _OutlineColor3; + float4 _OutlineColor4; + float4 _OutlineColor5; + }; + + cbuffer ShadowReceiverConstants + { + float4x4 gWorldToShadowMatrix; + ShadowMapMetrics gShadowMapMetrics; + ShadowSamplingData gShadowSampling; + }; + + Texture2D BaseColorTexture; + Texture2D _LightMap; + Texture2D _ShadowRamp; + Texture2D _NormalMap; + Texture2D _FaceLightMap; + Texture2D _FaceShadow; + Texture2D _MetalMap; + SamplerState LinearClampSampler; + Texture2D ShadowMapTexture; + SamplerState ShadowMapSampler; + + struct VSInput + { + float3 position : POSITION; + float3 normal : NORMAL; + float4 color : COLOR; + float2 texcoord : TEXCOORD0; + float2 backTexcoord : TEXCOORD1; + float3 tangent : TEXCOORD2; + float3 bitangent : TEXCOORD3; + }; + + struct PSInput + { + float4 position : SV_POSITION; + float2 texcoord : TEXCOORD0; + float2 backTexcoord : TEXCOORD1; + float3 positionWS : TEXCOORD2; + float3 positionVS : TEXCOORD3; + float3 normalWS : TEXCOORD4; + float3 tangentWS : TEXCOORD5; + float3 bitangentWS : TEXCOORD6; + float4 color : COLOR; + }; + + float3 NormalizeSafe(float3 value, float3 fallbackValue) + { + const float lengthSq = dot(value, value); + if (lengthSq <= 1e-6f) { + return fallbackValue; + } + + return value * rsqrt(lengthSq); + } + + float3 UnpackNormalRG(float4 packedNormal) + { + const float2 normalXY = packedNormal.xy * 2.0f - 1.0f; + const float normalZSq = saturate(1.0f - dot(normalXY, normalXY)); + return float3(normalXY, sqrt(normalZSq)); + } + + bool UseEmissionFeature() + { + #ifdef _EMISSION + return true; + #else + return _UseEmission.x > 0.5f; + #endif + } + + bool UseNormalMapFeature() + { + #ifdef _NORMAL_MAP + return true; + #else + return _UseNormalMap.x > 0.5f; + #endif + } + + bool UseFaceFeature() + { + #ifdef _IS_FACE + return true; + #else + return _IsFace.x > 0.5f; + #endif + } + + bool UseSpecularFeature() + { + #ifdef _SPECULAR + return true; + #else + return _UseSpecular.x > 0.5f; + #endif + } + + bool UseRimFeature() + { + #ifdef _RIM + return true; + #else + return _UseRim.x > 0.5f; + #endif + } + + bool UseDoubleSidedFeature() + { + #ifdef _DOUBLE_SIDED + return true; + #else + return _DoubleSided.x > 0.5f; + #endif + } + + float2 ResolveMaterialTexcoord(PSInput input, bool isFrontFace) + { + if (!UseDoubleSidedFeature()) { + return input.texcoord; + } + + return lerp(input.texcoord, input.backTexcoord, isFrontFace ? 0.0f : 1.0f); + } + + 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.texcoord = input.texcoord; + output.backTexcoord = input.backTexcoord; + output.positionWS = positionWS.xyz; + output.positionVS = positionVS.xyz; + output.normalWS = normalize(mul((float3x3)gNormalMatrix, input.normal)); + output.tangentWS = normalize(mul((float3x3)gNormalMatrix, input.tangent)); + output.bitangentWS = normalize(mul((float3x3)gNormalMatrix, input.bitangent)); + output.color = input.color; + 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 float3 resolvedNormalWS = NormalizeSafe(normalWS, float3(0.0f, 1.0f, 0.0f)); + const float3 resolvedLightDirectionWS = + NormalizeSafe(lightDirectionWS, float3(0.0f, -1.0f, 0.0f)); + const float nDotL = saturate(dot(resolvedNormalWS, resolvedLightDirectionWS)); + const float normalBiasWorld = + gShadowMapMetrics.worldTexelSize * gShadowSampling.normalBiasScale * (1.0f - nDotL); + const float3 shadowPositionWS = positionWS + resolvedNormalWS * 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 = NormalizeSafe(light.directionAndType.xyz, float3(0.0f, -1.0f, 0.0f)); + 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 = NormalizeSafe(light.directionAndType.xyz, float3(0.0f, -1.0f, 0.0f)); + } else { + const float3 lightVectorWS = light.positionAndRange.xyz - positionWS; + const float distanceSq = dot(lightVectorWS, lightVectorWS); + if (distanceSq <= 1e-6f) { + return float3(0.0f, 0.0f, 0.0f); + } + + directionToLightWS = lightVectorWS * rsqrt(distanceSq); + attenuation = ComputeRangeAttenuation(distanceSq, light.positionAndRange.w); + if (attenuation <= 0.0f) { + return float3(0.0f, 0.0f, 0.0f); + } + + if (lightType > 1.5f) { + attenuation *= ComputeSpotAttenuation(light, directionToLightWS); + if (attenuation <= 0.0f) { + return float3(0.0f, 0.0f, 0.0f); + } + } + } + + const float diffuse = saturate(dot(normalWS, directionToLightWS)); + if (diffuse <= 0.0f) { + return float3(0.0f, 0.0f, 0.0f); + } + + return lightColor * (diffuse * lightIntensity * attenuation); + } + + float3 ComputeSurfaceNormalWS(PSInput input, float2 texcoord) + { + float3 normalWS = NormalizeSafe(input.normalWS, float3(0.0f, 1.0f, 0.0f)); + if (!UseNormalMapFeature()) { + return normalWS; + } + + const float3 tangentWS = NormalizeSafe(input.tangentWS, float3(1.0f, 0.0f, 0.0f)); + const float3 bitangentWS = NormalizeSafe(input.bitangentWS, float3(0.0f, 0.0f, 1.0f)); + const float3 normalTS = UnpackNormalRG(_NormalMap.Sample(LinearClampSampler, texcoord)); + const float3 mappedNormalWS = + tangentWS * normalTS.x + + bitangentWS * normalTS.y + + normalWS * normalTS.z; + return NormalizeSafe(mappedNormalWS, normalWS); + } + + float3 GetAdjustedMainLightDirection() + { + const float3 scaledDirection = + gMainLightDirectionAndIntensity.xyz * _LightDirectionMultiplier.xyz; + return NormalizeSafe( + scaledDirection, + NormalizeSafe(gMainLightDirectionAndIntensity.xyz, float3(0.0f, -1.0f, 0.0f))); + } + + float ResolveMaterialSelector(float lightMapAlpha) + { + const float useCustomMaterialType = _UseCustomMaterialType.x > 0.5f ? 1.0f : 0.0f; + return lerp(lightMapAlpha, _CustomMaterialType.x, useCustomMaterialType); + } + + float GetGenericShadow(float3 normalWS, float3 lightDirectionWS, float aoFactor) + { + const float ndotl = dot(normalWS, lightDirectionWS); + const float halfLambert = ndotl * 0.5f + 0.5f; + const float shadow = saturate(2.0f * halfLambert * aoFactor); + return lerp(shadow, 1.0f, step(0.9f, aoFactor)); + } + + float GetFaceShadow(PSInput input, float2 texcoord, float3 lightDirectionWS) + { + const float3 faceDirection = NormalizeSafe( + float3(_FaceDirection.x, 0.0f, _FaceDirection.z), + float3(0.0f, 0.0f, 1.0f)); + const float3 flatLightDirection = NormalizeSafe( + float3(lightDirectionWS.x, 0.0f, lightDirectionWS.z), + float3(0.0f, 0.0f, 1.0f)); + const float faceDotLight = dot(faceDirection, flatLightDirection); + const float faceCrossLight = cross(faceDirection, flatLightDirection).y; + + float2 shadowUv = texcoord; + shadowUv.x = lerp(shadowUv.x, 1.0f - shadowUv.x, step(0.0f, faceCrossLight)); + + const float faceShadowMap = _FaceLightMap.Sample(LinearClampSampler, shadowUv).r; + const float faceShadow = step(-0.5f * faceDotLight + 0.5f + _FaceShadowOffset.x, faceShadowMap); + const float faceMask = _FaceShadow.Sample(LinearClampSampler, texcoord).a; + return lerp(faceShadow, 1.0f, faceMask); + } + + float3 GetShadowColor(float shadow, float materialSelector) + { + float rampIndex = 4.0f; + rampIndex = lerp(rampIndex, 1.0f, step(0.2f, materialSelector)); + rampIndex = lerp(rampIndex, 2.0f, step(0.4f, materialSelector)); + rampIndex = lerp(rampIndex, 0.0f, step(0.6f, materialSelector)); + rampIndex = lerp(rampIndex, 3.0f, step(0.8f, materialSelector)); + + const float rangeMin = 0.5f + _ShadowOffset.x - _ShadowSmoothness.x; + const float rangeMax = 0.5f + _ShadowOffset.x; + const float2 rampUv = float2( + smoothstep(rangeMin, rangeMax, shadow), + rampIndex / 10.0f + 0.5f * saturate(_IsDay.x) + 0.05f); + const float3 shadowRamp = _ShadowRamp.Sample(LinearClampSampler, rampUv).rgb; + + float3 shadowColor = + shadowRamp * lerp(_ShadowColor.rgb, float3(1.0f, 1.0f, 1.0f), smoothstep(0.9f, 1.0f, rampUv.x)); + shadowColor = lerp(shadowColor, float3(1.0f, 1.0f, 1.0f), step(rangeMax, shadow)); + return shadowColor; + } + + float3 GetSpecular( + PSInput input, + float3 normalWS, + float3 lightDirectionWS, + float3 albedo, + float3 lightMap) + { + const float3 viewDirectionVS = NormalizeSafe(-input.positionVS, float3(0.0f, 0.0f, 1.0f)); + const float3 lightDirectionVS = NormalizeSafe( + mul((float3x3)gViewMatrix, lightDirectionWS), + float3(0.0f, 0.0f, 1.0f)); + const float3 normalVS = NormalizeSafe(mul((float3x3)gViewMatrix, normalWS), float3(0.0f, 0.0f, 1.0f)); + const float3 halfDirectionVS = NormalizeSafe(lightDirectionVS + viewDirectionVS, lightDirectionVS); + const float nDotH = dot(normalVS, halfDirectionVS); + const float blinnPhong = pow(saturate(nDotH), max(_SpecularSmoothness.x, 1.0f)); + + const float2 matcapUv = normalVS.xy * 0.5f + 0.5f; + const float3 metalMap = _MetalMap.Sample(LinearClampSampler, matcapUv).rgb; + + const float3 nonMetallic = + step(1.1f, lightMap.b + blinnPhong) * lightMap.r * _NonmetallicIntensity.x; + const float3 metallic = + blinnPhong * lightMap.b * albedo * metalMap * _MetallicIntensity.x; + return lerp(nonMetallic, metallic, step(0.9f, lightMap.r)); + } + + float GetRim(PSInput input, float3 normalWS) + { + const float3 viewDirectionVS = NormalizeSafe(-input.positionVS, float3(0.0f, 0.0f, 1.0f)); + const float3 normalVS = NormalizeSafe(mul((float3x3)gViewMatrix, normalWS), float3(0.0f, 0.0f, 1.0f)); + const float nDotV = dot(normalVS, viewDirectionVS); + const float fresnel = pow(saturate(1.0f - nDotV), max(_RimOffset.x, 0.001f)); + return smoothstep(0.0f, max(_RimThreshold.x, 0.001f), fresnel) * _RimIntensity.x; + } + + float4 MainPS(PSInput input, bool isFrontFace : SV_IsFrontFace) : SV_TARGET + { + const float2 texcoord = ResolveMaterialTexcoord(input, isFrontFace); + float4 baseMap = BaseColorTexture.Sample(LinearClampSampler, texcoord); + float3 albedo = baseMap.rgb * _BaseColor.rgb; + const float alphaMask = baseMap.a; + + if (UseFaceFeature()) { + albedo = lerp(albedo, _FaceBlushColor.rgb, _FaceBlushStrength.x * alphaMask); + } + + const float3 normalWS = ComputeSurfaceNormalWS(input, texcoord); + + float3 color = albedo; + if (gMainLightColorAndFlags.a >= 0.5f) { + const float3 lightDirectionWS = GetAdjustedMainLightDirection(); + const float4 lightMap = _LightMap.Sample(LinearClampSampler, texcoord); + const float materialSelector = ResolveMaterialSelector(lightMap.a); + float shadow = UseFaceFeature() + ? GetFaceShadow(input, texcoord, lightDirectionWS) + : GetGenericShadow(normalWS, lightDirectionWS, lightMap.g * input.color.x); + + const float3 shadowColor = GetShadowColor(shadow, materialSelector); + + float3 specular = float3(0.0f, 0.0f, 0.0f); + + float3 emission = float3(0.0f, 0.0f, 0.0f); + if (UseEmissionFeature()) { + emission = albedo * _EmissionIntensity.x * alphaMask; + } + + float3 rim = float3(0.0f, 0.0f, 0.0f); + if (UseRimFeature()) { + rim = albedo * GetRim(input, normalWS); + } + + color = albedo * shadowColor + specular + emission + rim; + } + + return float4(saturate(color), 1.0f); + } + 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 + #pragma shader_feature_local _ _EMISSION + #pragma shader_feature_local _ _NORMAL_MAP + #pragma shader_feature_local _ _IS_FACE + #pragma shader_feature_local _ _SPECULAR + #pragma shader_feature_local _ _RIM + ENDHLSL + } + UsePass "Builtin Depth Only/DepthOnly" + UsePass "Builtin Shadow Caster/ShadowCaster" + } +} diff --git a/project/Assets/Shaders/Toon.shader.meta b/project/Assets/Shaders/Toon.shader.meta new file mode 100644 index 00000000..1c68e09b --- /dev/null +++ b/project/Assets/Shaders/Toon.shader.meta @@ -0,0 +1,5 @@ +fileFormatVersion: 1 +guid: d8469e07cc3799e13f1d0b254dc0b4d1 +folderAsset: false +importer: ShaderImporter +importerVersion: 8