1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
| Shader "Custom/URP/GrassWave" { Properties { _BaseColor ("Base Color", Color) = (0.3, 0.7, 0.2, 1.0) _TipColor ("Tip Color", Color) = (0.6, 0.9, 0.3, 1.0) _MainTex ("Albedo Texture", 2D) = "white" {} // 顶点动画参数 _WindDirection("Wind Direction", Vector) = (1.0, 0.0, 0.5, 0.0) _WindSpeed ("Wind Speed", Range(0.1, 5.0)) = 1.5 _WindStrength ("Wind Strength", Range(0.0, 0.5)) = 0.15 _WindFrequency("Wind Frequency", Range(0.5, 10.0)) = 3.0 // 草叶顶部影响权重(通过 UV.y 控制,底部固定,顶部飘动) _BendFactor ("Bend Factor", Range(0.0, 1.0)) = 1.0 // 双面渲染(草叶需要) [Toggle] _DoubleSided ("Double Sided", Float) = 1 }
SubShader { Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" "Queue" = "Geometry" }
// 双面渲染(草叶正背两面都可见) Cull Off
Pass { Name "ForwardLit" Tags { "LightMode" = "UniversalForward" }
HLSLPROGRAM #pragma vertex vert #pragma fragment frag
// URP 光照关键字 #pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE #pragma multi_compile _ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS #pragma multi_compile_fog
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
TEXTURE2D(_MainTex); SAMPLER(sampler_MainTex);
CBUFFER_START(UnityPerMaterial) float4 _MainTex_ST; float4 _BaseColor; float4 _TipColor; float4 _WindDirection; // xyz: 方向,w 未使用 float _WindSpeed; float _WindStrength; float _WindFrequency; float _BendFactor; CBUFFER_END
struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; float4 tangentOS : TANGENT; float2 uv : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID };
struct Varyings { float4 positionHCS : SV_POSITION; float2 uv : TEXCOORD0; float3 positionWS : TEXCOORD1; // 世界空间位置(用于光照) float3 normalWS : TEXCOORD2; // 世界空间法线 float heightFactor : TEXCOORD3; // 草叶高度因子(来自 UV.y) float4 shadowCoord : TEXCOORD4; // 阴影坐标 float fogFactor : TEXCOORD5; UNITY_VERTEX_OUTPUT_STEREO };
// ======== 顶点动画:正弦波草地飘动 ========
// 计算草叶在世界空间中的位移 // posWS: 世界空间位置 // heightFactor: UV.y(0=根部固定,1=顶端最大偏移) float3 computeWindOffset(float3 posWS, float heightFactor) { float3 windDir = normalize(_WindDirection.xyz); float time = _Time.y * _WindSpeed;
// 主波:基于世界坐标 XZ 相位的正弦波 float phase = dot(posWS.xz, windDir.xz) * _WindFrequency; float wave = sin(time + phase);
// 次波:添加第二频率增加自然感(频率 ×2.7,振幅 ×0.3) float wave2 = sin(time * 2.7 + phase * 1.3) * 0.3;
// 合并波形,乘以高度因子(底部不动) float totalWave = (wave + wave2) * _WindStrength * heightFactor;
return windDir * totalWave; }
Varyings vert(Attributes IN) { Varyings OUT; UNITY_SETUP_INSTANCE_ID(IN); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
// === 坐标变换链演示 ===
// 1. 对象空间 → 世界空间 float3 positionWS = TransformObjectToWorld(IN.positionOS.xyz);
// 2. 顶点动画:在世界空间中添加风力偏移 // UV.y 作为高度因子:底部(UV.y=0)固定,顶端(UV.y=1)最大偏移 float heightFactor = IN.uv.y * _BendFactor; positionWS += computeWindOffset(positionWS, heightFactor);
// 3. 世界空间 → 裁剪空间 OUT.positionHCS = TransformWorldToHClip(positionWS); OUT.positionWS = positionWS;
// 4. 法线变换(对象空间 → 世界空间) // 注意:法线需要用逆转置矩阵,URP 函数内部已处理 OUT.normalWS = TransformObjectToWorldNormal(IN.normalOS);
// 5. 双面法线修正(背面渲染时翻转法线) // 在 Fragment Shader 中用 VFACE 语义处理
OUT.uv = TRANSFORM_TEX(IN.uv, _MainTex); OUT.heightFactor = heightFactor;
// 6. 阴影坐标计算(用于采样 Shadow Map) VertexPositionInputs vertexInput = GetVertexPositionInputs(IN.positionOS.xyz); OUT.shadowCoord = GetShadowCoord(vertexInput);
// 7. 雾效因子 OUT.fogFactor = ComputeFogFactor(OUT.positionHCS.z);
return OUT; }
half4 frag(Varyings IN, bool isFrontFace : SV_IsFrontFace) : SV_Target { // 双面法线:背面渲染时翻转法线方向 float3 normalWS = normalize(IN.normalWS); normalWS = isFrontFace ? normalWS : -normalWS;
// 采样贴图 half4 texColor = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, IN.uv);
// 根据草叶高度混合底色和顶色 half4 grassColor = lerp(_BaseColor, _TipColor, IN.heightFactor) * texColor;
// ===== URP 光照计算 =====
// 获取主光源(方向、颜色、阴影衰减) Light mainLight = GetMainLight(IN.shadowCoord);
// Lambert 漫反射 float NdotL = saturate(dot(normalWS, mainLight.direction)); float3 diffuse = mainLight.color * NdotL * mainLight.distanceAttenuation * mainLight.shadowAttenuation;
// 半球环境光(Half-Lambert,避免背光面纯黑) float halfLambert = 0.5 * dot(normalWS, mainLight.direction) + 0.5; float3 ambient = grassColor.rgb * halfLambert * 0.3;
// 合并光照 float3 finalColor = grassColor.rgb * (diffuse + ambient);
// 应用雾效 finalColor = MixFog(finalColor, IN.fogFactor);
return half4(finalColor, grassColor.a); } ENDHLSL }
// ShadowCaster Pass(让草地正确投射阴影) Pass { Name "ShadowCaster" Tags { "LightMode" = "ShadowCaster" }
ZWrite On ZTest LEqual ColorMask 0 // 阴影 Pass 只写深度,不需要颜色输出 Cull Off
HLSLPROGRAM #pragma vertex vertShadow #pragma fragment fragShadow #pragma multi_compile_instancing
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/SurfaceInput.hlsl" #include "Packages/com.unity.render-pipelines.universal/Shaders/ShadowCasterPass.hlsl" // 注意:使用 URP 内置 ShadowCasterPass.hlsl 时,顶点着色器名字必须是 ShadowPassVertex
CBUFFER_START(UnityPerMaterial) float4 _MainTex_ST; float4 _BaseColor; float4 _TipColor; float4 _WindDirection; float _WindSpeed; float _WindStrength; float _WindFrequency; float _BendFactor; CBUFFER_END
struct ShadowAttributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; float2 uv : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID };
struct ShadowVaryings { float4 positionHCS : SV_POSITION; UNITY_VERTEX_OUTPUT_STEREO };
float3 computeWindOffset(float3 posWS, float heightFactor) { float3 windDir = normalize(_WindDirection.xyz); float time = _Time.y * _WindSpeed; float phase = dot(posWS.xz, windDir.xz) * _WindFrequency; float wave = sin(time + phase) + sin(time * 2.7 + phase * 1.3) * 0.3; return windDir * wave * _WindStrength * heightFactor; }
ShadowVaryings vertShadow(ShadowAttributes IN) { ShadowVaryings OUT; UNITY_SETUP_INSTANCE_ID(IN); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
float3 posWS = TransformObjectToWorld(IN.positionOS.xyz); // 阴影 Pass 也需要顶点动画,否则阴影形状与草地不匹配 posWS += computeWindOffset(posWS, IN.uv.y * _BendFactor);
// URP 阴影偏移(防止 shadow acne) float3 normalWS = TransformObjectToWorldNormal(IN.normalOS); float4 posHCS = TransformWorldToHClip(ApplyShadowBias(posWS, normalWS, _LightDirection)); // DX 平台裁剪 Z 到 [0,1] #if UNITY_REVERSED_Z posHCS.z = min(posHCS.z, posHCS.w * UNITY_NEAR_CLIP_VALUE); #else posHCS.z = max(posHCS.z, posHCS.w * UNITY_NEAR_CLIP_VALUE); #endif
OUT.positionHCS = posHCS; return OUT; }
half4 fragShadow(ShadowVaryings IN) : SV_Target { return 0; // 阴影 Pass 片元着色器只需返回 0 } ENDHLSL } } }
|