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
| Shader "Custom/URP/TerrainCustom" { Properties { // Splatmap [HideInInspector] _Control ("Splatmap 控制图", 2D) = "red" {}
// 4 层地形纹理(与 Unity Terrain 约定的名称一致) [HideInInspector] _Splat0 ("草地", 2D) = "white" {} [HideInInspector] _Splat1 ("岩石", 2D) = "white" {} [HideInInspector] _Splat2 ("泥土", 2D) = "white" {} [HideInInspector] _Splat3 ("沙地", 2D) = "white" {}
// 法线贴图 [HideInInspector] _Normal0 ("草地法线", 2D) = "bump" {} [HideInInspector] _Normal1 ("岩石法线", 2D) = "bump" {} [HideInInspector] _Normal2 ("泥土法线", 2D) = "bump" {} [HideInInspector] _Normal3 ("沙地法线", 2D) = "bump" {}
// 雪覆盖效果 _SnowColor ("雪的颜色", Color) = (0.9, 0.95, 1.0, 1.0) _SnowThreshold ("雪覆盖阈值(法线Y分量)", Range(0.5, 1.0)) = 0.8 _SnowBlend ("雪混合过渡", Range(0, 0.5)) = 0.1 _SnowHeight ("雪线高度(世界坐标Y)", Float) = 50.0 _SnowHeightBlend ("雪线过渡范围", Float) = 10.0
// 纹理缩放 _Scale0 ("草地缩放", Float) = 20.0 _Scale1 ("岩石缩放", Float) = 15.0 _Scale2 ("泥土缩放", Float) = 25.0 _Scale3 ("沙地缩放", Float) = 18.0 }
SubShader { Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" // 地形专用 Tag:告诉 Unity 这是地形 Shader "TerrainCompatible" = "True" }
Pass { Tags { "LightMode" = "UniversalForward" }
HLSLPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE #pragma multi_compile _ _SHADOWS_SOFT
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
TEXTURE2D(_Control); SAMPLER(sampler_Control); TEXTURE2D(_Splat0); SAMPLER(sampler_Splat0); TEXTURE2D(_Splat1); SAMPLER(sampler_Splat1); TEXTURE2D(_Splat2); SAMPLER(sampler_Splat2); TEXTURE2D(_Splat3); SAMPLER(sampler_Splat3); TEXTURE2D(_Normal0); SAMPLER(sampler_Normal0); TEXTURE2D(_Normal1); SAMPLER(sampler_Normal1); TEXTURE2D(_Normal2); SAMPLER(sampler_Normal2); TEXTURE2D(_Normal3); SAMPLER(sampler_Normal3);
CBUFFER_START(UnityPerMaterial) float4 _Control_ST; float4 _SnowColor; float _SnowThreshold, _SnowBlend; float _SnowHeight, _SnowHeightBlend; float _Scale0, _Scale1, _Scale2, _Scale3; CBUFFER_END
struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; float4 tangentOS : TANGENT; float2 uv : TEXCOORD0; // Splatmap UV };
struct Varyings { float4 positionCS : SV_POSITION; float3 positionWS : TEXCOORD0; float3 normalWS : TEXCOORD1; float3 tangentWS : TEXCOORD2; float3 bitangentWS : TEXCOORD3; float2 controlUV : TEXCOORD4; float2 worldXZ : TEXCOORD5; // 世界坐标 XZ(用于纹理平铺) };
Varyings vert(Attributes IN) { Varyings OUT; OUT.positionCS = TransformObjectToHClip(IN.positionOS.xyz); OUT.positionWS = TransformObjectToWorld(IN.positionOS.xyz); OUT.normalWS = TransformObjectToWorldNormal(IN.normalOS); OUT.tangentWS = TransformObjectToWorldDir(IN.tangentOS.xyz); OUT.bitangentWS = cross(OUT.normalWS, OUT.tangentWS) * IN.tangentOS.w; OUT.controlUV = IN.uv; // Splatmap UV 直接使用 OUT.worldXZ = OUT.positionWS.xz; // 世界坐标用于纹理平铺 return OUT; }
// 从法线贴图解码法线到世界空间 float3 SampleNormalTS(TEXTURE2D_PARAM(normalTex, sampler_normalTex), float2 uv) { float4 packedNormal = SAMPLE_TEXTURE2D(normalTex, sampler_normalTex, uv); // Unity 法线贴图格式:GA 通道存储 XY,Z 由 sqrt(1-x²-y²) 重建 float3 normalTS; normalTS.xy = packedNormal.wy * 2.0 - 1.0; normalTS.z = sqrt(max(0, 1.0 - dot(normalTS.xy, normalTS.xy))); return normalTS; }
half4 frag(Varyings IN) : SV_Target { // ===== 读取 Splatmap 权重 ===== half4 splat = SAMPLE_TEXTURE2D(_Control, sampler_Control, IN.controlUV); // 归一化权重(确保总和为 1,避免过亮/过暗) float totalWeight = splat.r + splat.g + splat.b + splat.a; splat /= max(totalWeight, 0.001);
// ===== 按缩放值计算各层 UV ===== float2 uv0 = IN.worldXZ / _Scale0; float2 uv1 = IN.worldXZ / _Scale1; float2 uv2 = IN.worldXZ / _Scale2; float2 uv3 = IN.worldXZ / _Scale3;
// ===== 采样各层纹理 ===== half4 albedo0 = SAMPLE_TEXTURE2D(_Splat0, sampler_Splat0, uv0); half4 albedo1 = SAMPLE_TEXTURE2D(_Splat1, sampler_Splat1, uv1); half4 albedo2 = SAMPLE_TEXTURE2D(_Splat2, sampler_Splat2, uv2); half4 albedo3 = SAMPLE_TEXTURE2D(_Splat3, sampler_Splat3, uv3);
// ===== Splatmap 加权混合 ===== half4 mixedAlbedo = albedo0 * splat.r + albedo1 * splat.g + albedo2 * splat.b + albedo3 * splat.a;
// ===== 法线混合 ===== float3 normal0 = SampleNormalTS(TEXTURE2D_ARGS(_Normal0, sampler_Normal0), uv0); float3 normal1 = SampleNormalTS(TEXTURE2D_ARGS(_Normal1, sampler_Normal1), uv1); float3 normal2 = SampleNormalTS(TEXTURE2D_ARGS(_Normal2, sampler_Normal2), uv2); float3 normal3 = SampleNormalTS(TEXTURE2D_ARGS(_Normal3, sampler_Normal3), uv3);
float3 mixedNormalTS = normalize( normal0 * splat.r + normal1 * splat.g + normal2 * splat.b + normal3 * splat.a );
// 切线空间法线转世界空间 float3x3 TBN = float3x3( normalize(IN.tangentWS), normalize(IN.bitangentWS), normalize(IN.normalWS) ); float3 normalWS = normalize(mul(mixedNormalTS, TBN));
// ===== 雪覆盖效果 ===== // 高度因子:超过雪线才有雪 float heightFactor = saturate( (IN.positionWS.y - _SnowHeight) / _SnowHeightBlend ); // 坡度因子:法线 Y 分量越大(越水平)越容易积雪 float slopeFactor = smoothstep( _SnowThreshold - _SnowBlend, _SnowThreshold + _SnowBlend, normalWS.y ); float snowAmount = heightFactor * slopeFactor;
half3 finalAlbedo = lerp(mixedAlbedo.rgb, _SnowColor.rgb, snowAmount); // 积雪后法线变平(雪会填平细节) normalWS = lerp(normalWS, IN.normalWS, snowAmount * 0.7);
// ===== PBR 光照 ===== float4 shadowCoord = TransformWorldToShadowCoord(IN.positionWS); Light mainLight = GetMainLight(shadowCoord);
float NdotL = saturate(dot(normalWS, mainLight.direction)); half3 ambient = SampleSH(normalWS); // 球谐函数环境光 half3 diffuse = mainLight.color * NdotL * mainLight.shadowAttenuation;
// 积雪区域 Smoothness 更高(雪面更光滑) float smoothness = lerp(0.3, 0.8, snowAmount); float3 viewDir = normalize(_WorldSpaceCameraPos - IN.positionWS); float3 halfDir = normalize(mainLight.direction + viewDir); float NdotH = saturate(dot(normalWS, halfDir)); half3 specular = mainLight.color * pow(NdotH, smoothness * 128.0) * 0.1 * mainLight.shadowAttenuation;
half3 finalColor = finalAlbedo * (ambient + diffuse) + specular;
return half4(finalColor, 1.0); } ENDHLSL }
// 阴影投射 Pass(地形必须有) Pass { Name "ShadowCaster" Tags { "LightMode" = "ShadowCaster" } ZWrite On ZTest LEqual Cull Back
HLSLPROGRAM #pragma vertex ShadowVert #pragma fragment ShadowFrag #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl"
struct ShadowAttribs { float4 posOS : POSITION; float3 normalOS : NORMAL; }; struct ShadowVaryings { float4 posCS : SV_POSITION; };
ShadowVaryings ShadowVert(ShadowAttribs IN) { ShadowVaryings OUT; float3 posWS = TransformObjectToWorld(IN.posOS.xyz); float3 normalWS = TransformObjectToWorldNormal(IN.normalOS); OUT.posCS = TransformWorldToHClip(ApplyShadowBias(posWS, normalWS, _LightDirection)); return OUT; } half4 ShadowFrag(ShadowVaryings IN) : SV_Target { return 0; } ENDHLSL } } }
|