源码 / Ch17/Flag.shader
Ch17/Flag.shader
// 旗帜飘动:在细分平面(物体空间 XZ 平面,法线 +Y)上沿 Y 方向位移。 // 位移 = 沿旗面传播的正弦波 + 噪声扰动,振幅从旗杆处的 0 逐渐增大; // 法线用有限差分重新计算(对位移函数在 x、z 方向各取一个邻点)。 Shader "Cookbook/Ch17/Flag" { Properties { [MainTexture] _BaseMap ("Flag Texture", 2D) = "white" {} [MainColor] _BaseColor ("Tint", Color) = (1, 1, 1, 1) [NoScaleOffset] _NoiseTex ("Noise", 2D) = "gray" {} _Amplitude ("Amplitude", Range(0, 0.5)) = 0.18 _WaveLength ("Wave Length", Range(0.2, 4)) = 1.3 _Speed ("Speed", Range(0, 5)) = 2.2 _NoiseStrength ("Noise Strength", Range(0, 1)) = 0.5 _Phase ("Phase (for stills)", Float) = 0 _FlagWidth ("Flag Width (object space X)", Float) = 2 [Toggle] _RecalcNormal ("Recalculate Normal", Float) = 1 [Toggle] _DeformShadow ("Deform In Shadow Pass", Float) = 1 } SubShader { Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" "Queue"="Geometry" } HLSLINCLUDE #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" CBUFFER_START(UnityPerMaterial) float4 _BaseMap_ST; half4 _BaseColor; float _Amplitude, _WaveLength, _Speed, _NoiseStrength, _Phase, _FlagWidth, _RecalcNormal, _DeformShadow; CBUFFER_END TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap); TEXTURE2D(_NoiseTex); SAMPLER(sampler_NoiseTex); #include "Assets/Cookbook/Ch17/VertexAnimCommon.hlsl" // 位移只依赖旗面上的 (x, z) 坐标和时间:这是能用有限差分求法线的前提 float FlagOffset(float2 xz) { float u = xz.x / _FlagWidth + 0.5; // 0 = 旗杆一侧,1 = 自由端 float t = _Time.y * _Speed + _Phase; // 运行时 _Time 驱动;静帧用 _Phase float k = TWO_PI / _WaveLength; float pin = smoothstep(0.0, 0.25, u) * u; // 旗杆处钉死,越往外摆得越大 // 主波沿 +x 传播,次波斜向传播,让波峰不那么整齐 float w = sin(k * xz.x - t) + 0.35 * sin(k * 1.7 * (xz.x * 0.6 + xz.y) - t * 1.3); // 噪声:随时间平移,制造“阵风”造成的不规则起伏 float2 nuv = xz * 0.12 + float2(-t * 0.05, t * 0.02); // 低频采样:只要大尺度的起伏 float n = SAMPLE_TEXTURE2D_LOD(_NoiseTex, sampler_NoiseTex, nuv, 0).r * 2.0 - 1.0; return (w + n * _NoiseStrength * 2.0) * _Amplitude * pin; } void Deform(inout VAAttributes v) { float3 p = v.positionOS.xyz; float h = FlagOffset(p.xz); if (_RecalcNormal > 0.5) { // 有限差分:在 x、z 方向各挪一小步,得到两个“切线”,叉乘就是新法线 const float e = 0.04; // 步长太小会放大噪声贴图的双线性折痕 float hx = FlagOffset(p.xz + float2(e, 0)); float hz = FlagOffset(p.xz + float2(0, e)); float3 tangent = float3(e, hx - h, 0); float3 bitangent = float3(0, hz - h, e); v.normalOS = normalize(cross(bitangent, tangent)); } v.positionOS.y += h; } // 阴影 Pass 专用:_DeformShadow = 0 时不变形,用来演示“影子不动”的错误 void DeformForShadow(inout VAAttributes v) { if (_DeformShadow > 0.5) Deform(v); } #define VA_DEFORM_SHADOW DeformForShadow #include "Assets/Cookbook/Ch17/VertexAnimPasses.hlsl" ENDHLSL Pass { Name "ForwardLit" Tags { "LightMode"="UniversalForwardOnly" } Cull Off HLSLPROGRAM #pragma vertex VAForwardVert #pragma fragment frag #pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE _MAIN_LIGHT_SHADOWS_SCREEN #pragma multi_compile_fragment _ _SHADOWS_SOFT _SHADOWS_SOFT_LOW _SHADOWS_SOFT_MEDIUM _SHADOWS_SOFT_HIGH half4 frag(VAVaryings i, FRONT_FACE_TYPE face : FRONT_FACE_SEMANTIC) : SV_Target { float3 N = normalize(i.normalWS) * IS_FRONT_VFACE(face, 1.0, -1.0); // 旗子两面都要正确受光 half3 albedo = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, TRANSFORM_TEX(i.uv, _BaseMap)).rgb * _BaseColor.rgb; return half4(VALighting(albedo, N, i.positionWS, 0.25h, 24.0h), 1); } ENDHLSL } Pass { Name "ShadowCaster" Tags { "LightMode"="ShadowCaster" } ZWrite On ZTest LEqual ColorMask 0 Cull Off HLSLPROGRAM #pragma vertex VAShadowVert #pragma fragment VANullFrag ENDHLSL } Pass { Name "DepthOnly" Tags { "LightMode"="DepthOnly" } ZWrite On ColorMask R Cull Off HLSLPROGRAM #pragma vertex VADepthVert #pragma fragment VANullFrag ENDHLSL } } }