Lv.3 Unity主线:一个简单的PBRShader

使用资产

通过网盘分享的文件:PBR测试内容 链接: https://pan.baidu.com/s/1MpDbI4TXPY2lKaz2uZC3_w?pwd=2psd 提取码: 2psd

完整代码

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
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
Shader "Unlit/PBRShader"
{
    properties
    {
        _MainTex("MainTex", 2D) = "white"{}

        _NormalMap("NormalMap", 2D) = "bump"{}
        _NormalMapScale("NormalMapScale", Range(0,5)) = 1

        _RoughnessTex("RoughnessTex", 2D)= "white"{}
        _MetallicTex("MetallicTex", 2D)= "white"{}
        _HeightTex("HeightTex", 2D)= "white"{}
        _AOTex("AOTex", 2D)= "white"{}
        _CubeMap("CubeMap", cube)= "white"{}

        _SpecularColor("SpecularColor",color) = (1,1,1,1)
        _SpecularIntensity("SpecularIntensity", Range(0,50)) = 1
        _SpecularShininess("SpecularShininess", Range(0,10)) = 2

        [Header(Roughness Adjustment)]
        [Space]
        _RoughnessIntensity ("RoughnessIntensity", Range(0, 2)) = 1
        [Space]
        _RoughnessBias ("Bias", Range(-1.0, 1.0)) = 0
        [Space]
        _RoughnessRemapMin ("Remap Min", Range(0, 1)) = 0
        _RoughnessRemapMax ("Remap Max", Range(0, 1)) = 1
        [Header(ParallaxMapping Basic)]
        _ParallaxScale ("Parallax Scale 未使用", Range(0, 10)) = 0.1
        [Header(ParallaxOcclusionMapping)]
        _HeightScale ("Height Scale", Range(0, 0.2)) = 0.05
        _MinLayers("Parallax Min Layers", Range(1, 128)) = 10
        _MaxLayers("Parallax Max Layers", Range(1, 128)) = 50
        _HeightReferencePlane ("Height Reference Plane", Range(0, 1)) = 0.5  // 解决凹凸问题
        //_POMFadeDistance ("POM Near Fade Distance", Range(0, 10)) = 2.0  
        _ShadowSoftness("POM Shadow Strength" ,Range(0, 10)) = 1.0
        _ShadowSteps("POM Shadow Steps" ,Range(0, 16)) = 8

        [Header(EnvBRDFLUT)]
        [Toggle]_UseCustomLUT("Custom BRDF LUT", float) = 1.0
        _CustomBRDFLUT("Custom BRDF LUT", 2D) = "white" {}

    }

    Subshader
    {
        Tags
        {
            "RenderType"="Opaque"
            "Queue"="Geometry"
        }

        Pass
        {
            Name"FORWARD"
            Tags{"LightMode"="ForwardBase"}

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdbase

            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "AutoLight.cginc"
             
            struct appdata
            {
                float4 vertex   : POSITION;
                float3 normal   : NORMAL;
                float4 tangent  : TANGENT;
                float2 uv       : TEXCOORD0;
                
            };

            struct v2f
            {
                float4 pos          : SV_POSITION;
                float4 positionWS   : TEXCOORD0;
                float3 normalWS     : TEXCOORD1;
                float3 tangentWS    : TEXCOORD2;
                float3 bitangentWS  : TEXCOORD3;
                float2 texcoord     : TEXCOORD4;
                float3 viewDirTS    : TEXCOORD5;
                float3 lightDirTS   : TEXCOORD6;
                UNITY_SHADOW_COORDS(7)
            };

            sampler2D _MainTex;
            half4 _MainTex_ST;
            sampler2D _NormalMap;
            sampler2D _RoughnessTex;
            sampler2D _MetallicTex;
            sampler2D _HeightTex;
            sampler2D _AOTex;
            samplerCUBE _CubeMap;
            float4 _CubeMap_HDR; //此参数声明后,Unity会自动配置
            half _NormalMapScale;
            half4 _SpecularColor;
            half _SpecularIntensity;
            half _SpecularShininess;
           

            half _RoughnessIntensity;
            half _RoughnessBias;
            half _RoughnessRemapMin;
            half _RoughnessRemapMax;
            half _RoughnessBoost;

            half _ParallaxScale;

            half _HeightScale;
            half _MinLayers;
            half _MaxLayers;
            half _HeightReferencePlane;
            half _POMFadeDistance;

            half _ShadowSoftness;
            half _ShadowSteps;

            half _UseCustomLUT;
            sampler2D _CustomBRDFLUT;

            float3 ParallaxOcclusionMapping(float2 uv, float3 viewDirTS, float3 lightDirTS)
            {
                
                // === 保存原始UV导数 ===
                float2 dx = ddx(uv);
                float2 dy = ddy(uv);
                
                // 这个系数能保证在极低角度时视差平滑消失
                //float angleFade = saturate(viewDirTS.z * 5.0);
                float angleFade = saturate(pow(viewDirTS.z, 0.5));
                //float angleFade = smoothstep(0.15, 0.4, viewDirTS.z);

                // 截断淡出,效果较差
                // float2 rawOffset = viewDirTS.xy / max(viewDirTS.z, 0.0001) * _HeightScale;
                // float maxLen = _HeightScale * 2.0; 
                // float currentLen = length(rawOffset);
                // float2 maxParallaxOffset = rawOffset * (min(currentLen, maxLen) / max(currentLen, 0.001));

                // === 1. 动态调整采样层数 ===
                float numLayers = lerp(_MaxLayers, _MinLayers, abs(viewDirTS.z));
                // === 2. 计算步进参数 ===
                float layerStepDistance = 1.0 / numLayers;              // 步进距离
                float2 maxParallaxOffset = viewDirTS.xy / max(viewDirTS.z, 0.001) * _HeightScale * angleFade; //最大步进距离
                float2 deltaUV = maxParallaxOffset / numLayers; //步进UV
                // === 3. 初始化 第一层数据===
                float currentLayerDistance = 0.0;         //起始层基准
                float2 currentUV = uv + maxParallaxOffset * (1.0 - _HeightReferencePlane);   // 当前UV坐标
                //float currentHeight = 1 - tex2D(_HeightTex, currentUV).r; // 初始表面高度
                float currentHeight = 1.0 - tex2Dgrad(_HeightTex, currentUV, dx, dy).r;

                // 缓存上一步的数据,避免循环结束后重复采样
                float prevHeight = currentHeight;
                float prevLayerDistance = 0.0;

                //[unroll(50)] // 展开循环优化(数字要≥_MaxLayers)
                [loop] //标记循环
                for(int i = 0; i < numLayers; i++)
                {
                    // 1.检查是否当前位置数值是否大于当前高度值
                    if(currentLayerDistance >= currentHeight)
                        break;
                    
                    // 2.记录旧状态
                    prevHeight = currentHeight;
                    prevLayerDistance = currentLayerDistance;

                    // 3.步进操作
                    // 沿视线方向步进,下一层的数据,这里虽然命名是current,但是数据已经是下一层的了
                    currentUV -= deltaUV;                               //UV步进1层
                    currentLayerDistance +=  layerStepDistance;         //当前位置步进1层 

                    // 4.采样新高度
                    //currentHeight = 1 - tex2Dlod(_HeightTex, float4(currentUV, 0, 0)).r; //深度值随UV步进1层
                    currentHeight = 1.0 - tex2Dgrad(_HeightTex, currentUV, dx, dy).r;
                }
                // 步进操作前,也就是当前UV
                float2 prevUV = currentUV + deltaUV;

                //当前步进距离对高度值影响              
                float beforeDistance = prevHeight - prevLayerDistance;
                 
                //下一层,总步进距离对高度值影响
                float afterDistance = currentHeight - currentLayerDistance;  
    
                // 插值权重,两个端点之间的线性插值
                float weight = afterDistance / (afterDistance - beforeDistance);

                // 最终UV = 加权平均
                // 对每一次步进的UV插值
                float2 finalUV = lerp(currentUV, prevUV, weight);
                //float finalHeight = lerp(currentLayerDistance, prevLayerDistance, weight);

                // === 自阴影循环 (Soft Shadows) ===
                float shadow = 1.0;
                
                //float lightAngleFade = smoothstep(0.05, 0.25, lightDirTS.z);
                float lightAngleFade = saturate(pow(lightDirTS.z, 0.5));
                
                if(_ShadowSoftness > 0.0 && angleFade > 0.01 && lightDirTS.z > 0.0)
                {
                    float hitDepth = 1.0 - tex2Dgrad(_HeightTex, finalUV, dx, dy).r;
                    
                    if(hitDepth > 0.001)
                    {
                        // 计算完整的UV偏移方向(从交点到光源方向在UV上的投影)
                        float2 lightDirUV = lightDirTS.xy / max(lightDirTS.z, 0.001) * _HeightScale;
                        
                        // 限制最大追踪距离,防止低角度时偏移过大
                        float maxOffset = _HeightScale * 2.0;
                        float offsetLen = length(lightDirUV);
                        if(offsetLen > maxOffset)
                        {
                            lightDirUV = lightDirUV / offsetLen * maxOffset;
                        }
                        
                        // 按深度比例的总UV偏移
                        float2 totalUV = lightDirUV * hitDepth;
                        
                        // 均匀分配到每一步
                        float2 stepUV = totalUV / _ShadowSteps;
                        float stepDepth = hitDepth / _ShadowSteps;
                        
                        float currentDepth = hitDepth;
                        float2 currentUV = finalUV;
                        
                        [loop]
                        for(int j = 0; j < _ShadowSteps; j++)
                        {
                            currentDepth -= stepDepth;
                            currentUV += stepUV;
                            
                            if(currentDepth <= 0.0)
                                break;
                            
                            float terrainDepth = 1.0 - tex2Dgrad(_HeightTex, currentUV, dx, dy).r;
                            
                            if(terrainDepth < currentDepth)
                            {
                                float occlusion = currentDepth - terrainDepth;
                                shadow = min(shadow, 1.0 - saturate(occlusion * _ShadowSoftness));
                                
                                if(shadow < 0.01)
                                {
                                    shadow = 0.0;
                                    break;
                                }
                            }
                        }
                    }
                }
                // 根据淡出系数混合阴影
                shadow = lerp(1.0, shadow, angleFade * lightAngleFade);
                
                // 根据淡出系数混合阴影(让阴影也随视角淡出)
                //shadow = lerp(1.0, shadow, angleFade);

                //return finalUV;
                return float3(finalUV,shadow);
            }

            float2 EnvBRDFApprox(float roughness, float NoV)
            {
                // Epic Games通过拟合大量数据得出的魔法系数
                const float4 c0 = float4(-1.0, -0.0275, -0.572, 0.022);
                const float4 c1 = float4(1.0, 0.0425, 1.04, -0.04);

                float4 r = roughness * c0 + c1;
                float a004 = min(r.x * r.x, exp2(-9.28 * NoV)) * r.x + r.y;
                float2 AB = float2(-1.04, 1.04) * a004 + r.zw;

                return AB;  // AB.x = scale, AB.y = bias
            }

            v2f vert(appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.positionWS = mul(unity_ObjectToWorld, v.vertex);

                o.normalWS = UnityObjectToWorldNormal(v.normal);
                o.tangentWS = UnityObjectToWorldDir(v.tangent.xyz);
                //* v.tangent.w处理平台差异
                o.bitangentWS = cross(o.normalWS, o.tangentWS) * v.tangent.w;
                o.texcoord = TRANSFORM_TEX(v.uv,_MainTex);

                //ParallaxMapping_Basic start
                float3x3 TBN = float3x3(o.tangentWS, o.bitangentWS, o.normalWS);
                float3 worldViewDir = normalize(_WorldSpaceCameraPos - o.positionWS);       
                o.viewDirTS = mul(TBN, worldViewDir); 

                half3 lightWS = normalize(_WorldSpaceLightPos0.xyz);
                o.lightDirTS= mul(TBN, lightWS); 

                //ParallaxMapping_Basic end
                TRANSFER_SHADOW(o);
                return o;
            }
            

            float4 frag(v2f i) : SV_Target
            {
                //vectors
                float3 positionWS = i.positionWS; 
                float3 normalWS = normalize(i.normalWS);
                float3 tangentWS = normalize(i.tangentWS);
                float3 bitangentWS = normalize(i.bitangentWS);
                float3x3 TBN = float3x3(tangentWS, bitangentWS, normalWS);
                half3 lightWS = normalize(_WorldSpaceLightPos0.xyz);
                float3 viewDir = normalize(_WorldSpaceCameraPos - positionWS);

                half3 lightDirTS = i.lightDirTS; 
                //************tex*************
                //ParallaxMapping_Basic start
                //float height = 1.0 - tex2D(_HeightTex, i.texcoord).r;
                //float2 offset = i.viewDirTS.xy / i.viewDirTS.z * height * _ParallaxScale * 0.01;
                //float2 texcoord = i.texcoord - offset;
                //ParallaxMapping_Basic end

                float3 POMData = ParallaxOcclusionMapping(i.texcoord, i.viewDirTS,  lightDirTS);
                float2 texcoord = POMData.xy;
                float  POMShadow = POMData.z;

                float3 lightColor = _LightColor0.xyz;
                float4 baseCol = tex2D(_MainTex, texcoord);
                float3 normalMapData = UnpackNormal(tex2D(_NormalMap, texcoord)).xyz;
                normalMapData.xy *= _NormalMapScale;
                float metallic = tex2D(_MetallicTex, texcoord).b;
                float roughness = tex2D(_RoughnessTex, texcoord).r + _RoughnessBoost;

                //halfLambet
                float3 pixelNormal = normalize(mul(normalMapData, TBN));
                float nol = max(0, dot(pixelNormal, lightWS));
                float halfLambet = nol*0.5 + 0.5;
                float3 diffuseColor = baseCol * nol * lightColor;
                //Specular
                //phong
                half3 reflectDir = reflect(-lightWS, pixelNormal);
                half phongItem = max(0, dot(reflectDir, viewDir));
               
                //Blinn-phong
                half3 halfDir = normalize(lightWS + viewDir);
                half blinnPhongItem = max(0, dot(pixelNormal, halfDir));       

               
              
                //整体漫反射剔除金属的的漫反射(错误写法,能量不守恒)
                //float3 pbrDiffuseColor = lerp(0.96 * diffuseColor, 0, metallic);
                
                roughness = roughness * _RoughnessIntensity + _RoughnessBias;
                roughness = clamp(roughness, _RoughnessRemapMin, _RoughnessRemapMax);

                //GGXSpecular
                float3 f0 = lerp(float3(0.04,0.04,0.04), baseCol, metallic);
                float roughness2 = roughness * roughness;
                float d = roughness2*roughness2/(3.14159*(blinnPhongItem * blinnPhongItem * (roughness2 * roughness2 - 1) + 1)*(blinnPhongItem * blinnPhongItem * (roughness2 * roughness2 - 1) + 1));

                float k = (roughness + 1) * (roughness + 1) / 8;
                float nov = max(0, dot(pixelNormal, viewDir));
                float gv = nov / (nov * (1 - k) + k);
                float gl = nol / (nol * (1 - k) + k);
                float g = gv * gl; 
                float3 f = f0 + (1-f0) * pow(1 - max(0,(dot(viewDir, halfDir))), 5);
                float3 KD_GGX =  (1 - f)* (1 - metallic);
                float3 ggxBRDF = d * f * g / (4 * nol * nov  + 0.001);
                float3 pbrSpecularColor = ggxBRDF * _SpecularColor * lightColor * nol;
                float3 pbrDiffuseColor =  baseCol /3.14159 * KD_GGX * nol * lightColor;


                //******环境光处理**********

                /******************漫反射环境光*********************/
                //EnvironmentColor
                //1.环境光(Unity 自动处理)
                fixed3 envColor = UNITY_LIGHTMODEL_AMBIENT.rgb * baseCol.rgb;
                //               ^^^^^^^^^^^^^^^^^^^^^^^ Unity 内置的环境光颜色
                //2.SH环境光(支持 Skybox/Gradient/Color)
                fixed3 envColorSH = ShadeSH9(half4(normalWS, 1.0)) * baseCol.rgb;
                //               ^^^^^^^ Unity 的球谐函数,根据法线方向采样环境光
                /******************镜面反射环境光*********************/
                //1.采样反射探针(Reflection Probe)
                half4 skyData = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, reflectDir);
                half3 skyColor = DecodeHDR(skyData, unity_SpecCube0_HDR);
                    
                // 计算反射方向
                float3 reflviewDir = reflect(-viewDir, normalize(pixelNormal));
               
                //环境光漫反射
                float3 f_indirect = f0 + (1-f0) * pow(1 - max(0,(dot(viewDir, pixelNormal))), 5);
                float3 kD_indirect  = (1.0 - f_indirect) * (1.0 - metallic);
                float3 indirectDiffuse  = kD_indirect * envColorSH;
                
                //环境光镜面漫反射
                float mipLevel = roughness* 7.0;
                float4 envSpecularRaw = texCUBElod(_CubeMap, float4(reflviewDir, mipLevel));
                half3 envSpecular = DecodeHDR(envSpecularRaw, _CubeMap_HDR); 

                //1.拟合BRDF(未使用)
                float2 envBRDF = EnvBRDFApprox(roughness, nov);
                float3 envSpecularEasy = envSpecular * f_indirect;
                //2.采样BRDFLUT
                float2 brdfLUT = tex2D(unity_NHxRoughness, float2(roughness, nov)).rg;
                float2 CustombrdfLUT = tex2D(_CustomBRDFLUT, float2(roughness, nov)).rg;
                
                if(_UseCustomLUT > 0.5)
                {
                    brdfLUT = CustombrdfLUT;
                }
                envSpecular =  envSpecular * (f0 * brdfLUT.x + brdfLUT.y);
                
                //AO
                float AO = tex2D(_AOTex, texcoord).r;
                //float fakeAO = saturate(dot(normalWS, float3(0,1,0))) * 0.6 + 0.4;



                //Shadow
                float shadow = SHADOW_ATTENUATION(i);
 
                //output
                float3 finalColor = (pbrDiffuseColor + pbrSpecularColor) * shadow + (indirectDiffuse + envSpecular )* AO * POMShadow ;


                return float4(finalColor, 1); 
            }
            ENDCG
        }
        
    }FallBack"Diffuse"

}

测试大致效果




    Enjoy Reading This Article?

    Here are some more articles you might like to read next:

  • URP - RendererFeature :ScreenSpaceOutline
  • 平滑法线处理 - 八面体映射
  • 理论支线:直接光漫反射与GGX高光的混合问题
  • 理论支线:PBR - 基于图像的照明( image based lighting-IBL)
  • Lv.3 Unity主线:视差(Parallax Mapping)

  • # #