[URP]PBR光照模型Shader(持续优化ing)

环境

下面代码的运行环境

  • Unity : 2022.3.3f1
  • Pipeline : URP

 

---------------------------------------------------------------------------------------------------------------------------------

常规做法:兰伯特(半兰伯特)+ 反射(phong和Blinn-Phong)

halfLambert = dot(L,N)*0.5+0.5;

phong=dot( r , v );其中:r=reflect( -L , v );

Blinn-Phong = dot(N,H);其中H=(L+V)*0.5 ,近似省性能,H=normalize(L + V);

PBR的公式:

从美术角度去理解,大致分为两个,直接光照模型(直接漫反射和高光) + 间接光照模型(间接漫反射和高光) + 环境阴影;

PBR遵循三大原则:①能量守恒;②基于物理的BRDF;③微表面模型 ;

实际上做效果的时候,可以根据项目情况适当修改,以视觉效果为准“只要看起来是对的那他就是对的”;

BRDF = K*(F * G * D) / (4 * dot(N, V) * dot(N, L))

  • F:菲涅尔项(Fresnel term),描述了材质在不同入射角下的反射比例。可以根据菲涅尔方程计算得到。
  • G:几何项(Geometry term),考虑了观察者、光源和材质微表面之间的遮挡关系。可以使用Schlick's approximation或其他几何函数进行计算。
  • D:法线分布项(Normal distribution term),描述了材质微表面的法线分布情况。常用的法线分布函数有Beckmann distribution、GGX distribution等。
  • N:表面法线向量。
  • V:视线方向向量。
  • L:光照方向向量。

写代码的时候列出所需参数,代入公式,Debug输出值观察所需颜色结果;

我这里加入了阴影自定义颜色 和细节Mask(Diffuse,normal,Spec),参考资料见References,感谢前辈们的分享经验;

代码如下(注释):

Shader "URP/PBR_2.0"
{
    Properties
    {
		_Color("基础颜色", Color) = (1,1,1,1)
        [MainTexture]_MainTex ("主贴图", 2D) = "white" {}
        _Spc("基础 Metalness(R) Smoothness(A)", 2D) = "black" {}
        _Metallic("金属度",Range(0,1))=1
        _Roughness ("粗糙度", Range(0, 1)) = 1
		[Normal]_NormalTex("法线贴图", 2D) = "bump" {}
        _NormalScale("法线强度", Float) = 1.0
        _AO("基础 AO", 2D)= "white" {}
        _layer1Tex ("Layer1 Albedo (RGB) Smoothness (A)", 2D) = "white" {}
        _layer1Metal ("Layer1 Metalness", Range(0,1)) = 0
        _layer1Norm("Layer 1 Normal", 2D) = "bump" {}
        _layer1Breakup ("Layer1 Breakup (R)", 2D) = "white" {}
        _layer1BreakupAmnt ("Layer1 Breakup Amount", Range(0,1)) = 0.5
        _layer1Tiling("Layer1 Tiling", float) = 10
        _Power ("Layer1 Blend Amount", float ) = 1
        _Shift("Layer1 Blend Height", float) = 1 
        [Normal]_DetailNormal ("Detail Normal", 2D) = "bump" {} 
        _DetailInt ("DetailNormal Intensity", Range(0,1)) = 0.4
        _DetailTiling("DetailNormal Tiling", float) = 2 
        [Toggle(_KALOS_G_FACTOR_ON)] _Kalos_G_Factor ("Optimize with Kalos G Factor", Int) = 1
        _ShadowMainColor("ShadowMainColor", color) = (0,0,0,1)
        [Toggle(_AdditionalLights)] _AddLights ("AddLights", Float) = 1

    }
        SubShader
        {
           Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalRenderPipeline"}
            LOD 100 

		 Pass
        {
		Tags { "LightMode" = "UniversalForward" }

            HLSLPROGRAM

			#pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fog

            // URP 软阴影
            #pragma multi_compile_fragment _ _SHADOWS_SOFT
            // URP 主光阴影、联机阴影、屏幕空间阴影//
            #pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE _MAIN_LIGHT_SHADOWS_SCREEN

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
			#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

            //奇技淫巧全在里面
            #include "PBRMath.hlsl"
            //光照贴图
            #pragma multi_compile _ LIGHTMAP_ON


			CBUFFER_START(UnityPerMaterial)
            float4 _Color,_ShadowMainColor; 
            float _NormalScale,_Power, _DetailInt, _DetailTiling, _layer1Tiling, _layer1Metal, _layer1BreakupAmnt, _Shift,_Metallic,_Roughness;
            float4 _MainTex_ST,_NormalTex_ST,_Spc_ST,_AO_ST,_DetailBump_ST,_layer1Tex_ST,_layer1Norm_ST,_layer1Breakup_ST;
            CBUFFER_END
			
            TEXTURE2D(_MainTex);        TEXTURE2D(_Spc);       TEXTURE2D(_AO);       TEXTURE2D(_DetailNormal);      TEXTURE2D(_NormalTex); 
            SAMPLER(sampler_MainTex);   SAMPLER(sampler_Spc);  SAMPLER(sampler_AO);  SAMPLER(sampler_DetailNormal); SAMPLER(sampler_NormalTex);

            TEXTURE2D(_layer1Tex);      TEXTURE2D(_layer1Norm);                      TEXTURE2D(_layer1Breakup);
            SAMPLER(sampler_layer1Tex); SAMPLER(sampler_layer1Norm);                 SAMPLER(sampler_layer1Breakup);


            struct VertexInput          
            {
                float4 positionOS : POSITION; 
                float2 uv : TEXCOORD0;
				float4 normalOS  : NORMAL;
				float4 tangentOS  : TANGENT;
                float2 uvLM : TEXCOORD1;
            };

            struct VertexOutput 
            {
                float4 position : SV_POSITION; 
                float4 uv : TEXCOORD0;              //xy是_MainTex的uv,zw是_NormlTex的UV
				float3 positionWS :  TEXCOORD1;
				float3 normalWS : TEXCOORD2;
				float3 tangentWS : TEXCOORD3;
				float3 bitangentWS : TEXCOORD4;
                float2 uvLM : TEXCOORD5;
                float3 vertexSH   : TEXCOORD6;
                half   fogCoord : TEXCOORD7;     //单个fog可以写half
                float3 worldPos : TEXCOORD8;
                //float4 shadowCoord : TEXCOORD8;
            };

                VertexOutput vert(VertexInput v)
                {
                    VertexOutput o;

					VertexPositionInputs positionInputs = GetVertexPositionInputs(v.positionOS.xyz);
                    o.position = positionInputs.positionCS;
					o.positionWS = positionInputs.positionWS;
                    //o.shadowCoord = GetShadowCoord(positionInputs);
                    //o.shadowCoord = TransformWorldToShadowCoord(o.positionWS);

					VertexNormalInputs normalInputs = GetVertexNormalInputs(v.normalOS.xyz,v.tangentOS);
					o.normalWS = normalInputs.normalWS;
					o.tangentWS = normalInputs.tangentWS;
					o.bitangentWS = normalInputs.bitangentWS;
    
                    o.worldPos = TransformObjectToWorld(v.positionOS.xyz);
                    o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);
                    o.uv.zw = TRANSFORM_TEX(v.uv, _NormalTex);

                    o.fogCoord = ComputeFogFactor(o.position.z);

                    OUTPUT_LIGHTMAP_UV(v.uvLM, unity_LightmapST, o.uvLM);
                    return o;
                }

                float4 frag(VertexOutput i): SV_Target 
                {
                //阴影
				float4 SHADOW_COORDS = TransformWorldToShadowCoord(i.worldPos.xyz);
				Light  lightDirectional     = GetMainLight(SHADOW_COORDS);
				half   shadow        = lightDirectional.shadowAttenuation;
                //贴图采样
                float4 MainTex      = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv.xy);
                half4 spec          = SAMPLE_TEXTURE2D(_Spc,sampler_Spc,i.uv.xy);
                half4 ao            = SAMPLE_TEXTURE2D(_AO,sampler_AO,i.uv.xy);

                half4 layer1        = SAMPLE_TEXTURE2D(_layer1Tex,sampler_layer1Tex,i.uv.xy* _layer1Tiling);
                half3 layer1norm    = UnpackNormal(SAMPLE_TEXTURE2D(_layer1Norm,sampler_layer1Norm,i.uv.xy* _layer1Tiling));
                half  layer1Breakup = SAMPLE_TEXTURE2D (_layer1Breakup,sampler_layer1Breakup, i.uv.xy* _layer1Tiling).r;

                half3 detnorm       = UnpackNormal(SAMPLE_TEXTURE2D (_DetailNormal,sampler_DetailNormal,i.uv.xy * _DetailTiling));
                //主法线处理
				float4 normalTXS = SAMPLE_TEXTURE2D(_NormalTex, sampler_NormalTex, i.uv.zw);
				float3 normalTS  = UnpackNormalScale(normalTXS,_NormalScale);
                    //-------法线混合//
                    half3 modNormal     = normalTS + half3(layer1norm.r * 0.6, layer1norm.g * 0.6, 0);
                    //-------TBN矩阵换算等参数准备//
                    real3x3 TBN      = real3x3(i.tangentWS, i.bitangentWS, i.normalWS);
                    half3  normalWS  = TransformTangentToWorld(modNormal,TBN);
                    detnorm          = normalize(mul(detnorm,TBN));
                    normalTS         = normalize(mul(normalTS,TBN));
                    layer1norm       = normalize(mul(layer1norm,TBN));
                    //准备混合遮罩///万能的遮罩,后面给反射这些也要混合使用它!!!
                    half3 layer1direction = half3(0,1,0);
                    half blend            = dot(normalWS, layer1direction);
                    half blend2           = (blend * _Power + _Shift) * lerp(1, layer1Breakup, _layer1BreakupAmnt);
                    blend2 = saturate(pow(blend2, 3));
                    //合并法线:合并法线操作之前必须在世界空间(即纹理解包+TBN之后才能使用) 
                    half3 blendedNormal = lerp(normalTS, layer1norm, blend2); 
                    blendedNormal = blendedNormal + (detnorm * half3(_DetailInt,_DetailInt,0)); //恭喜你获得了这个关键的法线!!!                                                 

                //向量准备
                half3 L = normalize(lightDirectional.direction);
                half3 V = SafeNormalize(_WorldSpaceCameraPos - i.positionWS);
                half3 H = normalize(L + V); 
                half3 N = blendedNormal.rgb; 
                //向量点积
                half HdotL = max(dot(H, L), 1e-5); //1e-5代表10的负5次方,也就是0.00001 
                //half NdotL = max(dot(N, L), 1e-5); 
                half NdotL = dot(N, L)*0.5+0.5; 
                     NdotL = lerp(NdotL,max(dot(N, L), 1e-5),0.75);  //这个是我个人喜好!
                half NdotV = max(dot(N, V), 1e-5); 
                half HdotN = max(dot(H, N), 1e-5);
                //合并反射、AO等贴图(使用上面同样的那个Mask)
                half3 blendedAlbedo      = lerp(MainTex.rgb, layer1.rgb, blend2);
                half  blendedSmoothness  = lerp(spec.a, layer1.a, blend2);
                half3 blendedMetallic    = lerp(spec.r, _layer1Metal, blend2);
                //PBR参数准备
                half  metallic   = blendedMetallic.r * _Metallic;
                half  smoothness = blendedSmoothness;
                half  roughness  = (1 - smoothness) * _Roughness;
                half3 F0         = Direct_F0_Function(blendedAlbedo, metallic);             //这里面定义的方法使用了奇技淫巧,去包含文件去看
                half3 Direct_F   = Direct_F_Function(HdotL, F0);
                //----// 直线光漫反射
                half3 KS = Direct_F;
                half3 KD = (1 - KS) * (1 - metallic);
                half3 DirectDiffColor = KD * blendedAlbedo * lightDirectional.color * NdotL;
                // 镜面反射
                half Direct_D = Direct_D_Function(HdotN, roughness);
                //----// BRDF
                #if defined(_KALOS_G_FACTOR_ON)
                        half Direct_G = Direct_G_Function_Kalos(HdotL, roughness);
                #else
                        half Direct_G = Direct_G_Function(NdotL, NdotV, roughness);
                #endif
                #if defined(_KALOS_G_FACTOR_ON)
                        half3 BRDFSpecSection = (Direct_D * Direct_G) * Direct_F / (4 * HdotL);
                #else
                        half3 BRDFSpecSection = (Direct_D * Direct_G) * Direct_F / (4 * NdotL * NdotV);
                #endif

                half3 DirectSpeColor = BRDFSpecSection * lightDirectional.color * (NdotL * PI * ao.rgb);
                // 第一部分(直线光照结果)
                half3 DirectColor = DirectDiffColor + DirectSpeColor;

                // 第二部分:间接漫反射
                half3 shColor = SH_IndirectionDiff(N) * ao.r;
                half3 Indirect_KS = Indirect_F_Function(NdotV, F0, roughness);
                half3 Indirect_KD = (1 - Indirect_KS) * (1 - metallic);
                half3 IndirectDiffColor = shColor * Indirect_KD * blendedAlbedo;
                // return half4(IndirectDiffColor, 1); // jave.lin : 添加一个 反射探针 即可看到效果:reflection probe

                // 间接反射
                //----// 反射探针的间接光
                half3 IndirectSpeCubeColor = IndirectSpeCube(N, V, roughness, ao.r);
                half3 IndirectSpeCubeFactor = IndirectSpeFactor(roughness, smoothness, BRDFSpecSection, F0, NdotV);
                half3 IndirectSpeColor = IndirectSpeCubeColor * IndirectSpeCubeFactor;
                half3 IndirectColor = IndirectDiffColor + IndirectSpeColor;

                //half3 ambient = _GlossyEnvironmentColor.rgb;
                //1,2部分光照结果(总)
                half3 finalCol = DirectColor + IndirectColor;
                finalCol.rgb = MixFog(finalCol.rgb,i.fogCoord);

                //光照烘焙颜色
                half3  bakedGI = SAMPLE_GI(i.uvLM, i.vertexSH, normalWS);

                //float shadow = MainLightRealtimeShadow(i.shadowCoord);      // shadow
                
                finalCol.rgb = lerp(_ShadowMainColor.rgb * finalCol.rgb, finalCol.rgb, shadow);
                finalCol.rgb =finalCol.rgb+ bakedGI.rgb*finalCol.rgb;

				clip(_Color.a-0.5);

                return  float4(finalCol.rgb,1) ;
                }

                ENDHLSL
            
        }

		UsePass "Universal Render Pipeline/Lit/ShadowCaster"
        UsePass "Universal Render Pipeline/Lit/DepthOnly"         //深度绘制Pass防止消失
    }
    FallBack "Packages/com.unity.render-pipelines.universal/FallbackError"
}
//奇技淫巧
#ifndef PBRMath
    #define PBRMath

            // 直接光照 D项 法线微表面分布函数
            half Direct_D_Function(half NdotH, half roughness)
            {
                half a2 = Pow4(roughness);
                half d = (NdotH * NdotH * (a2 - 1.0) + 1.0);
                d = d * d;// *PI;
                return saturate(a2 / d);
            }

            // method1:
            // 直接光照 F项
            half3 Direct_F_Function(half HdotL, half3 F0)
            {
                half Fre = exp2((-5.55473 * HdotL - 6.98316) * HdotL);
                return lerp(Fre, 1, F0);
            }

            // // method2:
            // // 直接光照 F项
            // half3 Direct_F_Function(half HdotL, half3 F0)
            // {
                //     half Fre = pow(1 - HdotL, 5);
                //     return lerp(Fre, 1, F0);
            // }

            inline half3 Direct_F0_Function(half3 albedo, half metallic)
            {
                return lerp(0.04, albedo, metallic);
            }

            // 直接光照 G项子项
            inline real Direct_G_subSection(half dot, half k)
            {
                return dot / lerp(dot, 1, k);
            }
            // 直接光照 G项
            half Direct_G_Function(half NdotL, half NdotV, half roughness)
            {
                // method1-k:
                // // half k = pow(1 + roughness, 2) / 8.0;

                // // method2-k:
                // const half d = 1.0 / 8.0;
                // half k = pow(1 + roughness, 2) * d;

                // method3-k:
                half k = pow(1 + roughness, 2) * 0.5;
                return Direct_G_subSection(NdotL, k) * Direct_G_subSection(NdotV, k);
            }

            // 模拟 G项:Kelemen-Szirmay-Kalos Geometry Factor
            inline half Direct_G_Function_Kalos(half LdotH, half roughness)
            {
                half k = pow(1 + roughness, 2) * 0.5;
                return Direct_G_subSection(LdotH, k);
            }
            //间接漫反射函数
            real3 SH_IndirectionDiff(real3 normalWS)
            {
                real4 SHCoefficients[7];
                SHCoefficients[0] = unity_SHAr;
                SHCoefficients[1] = unity_SHAg;
                SHCoefficients[2] = unity_SHAb;
                SHCoefficients[3] = unity_SHBr;
                SHCoefficients[4] = unity_SHBg;
                SHCoefficients[5] = unity_SHBb;
                SHCoefficients[6] = unity_SHC;
                real3 color = SampleSH9(SHCoefficients, normalWS);
                return max(0, color);
            }
            //间接镜面反射函数
            half3 Indirect_F_Function(half NdotV, half3 F0, half roughness)
            {
                half fre = exp2((-5.55473 * NdotV - 6.98316) * NdotV);
                return F0 + fre * saturate(1 - roughness - F0);
            }

            half3 IndirectSpeCube(half3 normalWS, half3 viewWS, float roughness, half AO)
            {
                half3 reflectDirWS = reflect(-viewWS, normalWS);
                roughness = roughness * (1.7 - 0.7 * roughness); // unity 内部不是线性 调整下 拟合曲线求近似,可以再 GGB 可视化曲线
                half mipmapLevel = roughness * 6; // 把粗糙度 remap 到 0~6 的 7个阶段,然后进行 texture lod 采样
                half4 specularColor = SAMPLE_TEXTURECUBE_LOD(unity_SpecCube0, samplerunity_SpecCube0, reflectDirWS, mipmapLevel); // 根据不同的 mipmap level 等级进行采样
                #if !defined(UNITY_USE_NATIVE_HDR)
                // 用 DecodeHDREnvironment 将解码 HDR 颜色值。
                // 可以看到采样出的 RGBM 是一个 4 通道的值
                // 最后的一个 M 存的是一个参数
                // 解码时将前三个通道表示的颜色乘上 x*(M^y)
                // x y 都是有环境贴图定义的系数
                // 存储在 unity_SpecCube0_HDR 这个结构中
                return DecodeHDREnvironment(specularColor, unity_SpecCube0_HDR) * AO;
                #else
                return specularColor.rgb * AO;
                #endif
            }

                half3 IndirectSpeFactor(half roughness, half smoothness, half3 BRDFspe, half3 F0, half NdotV)
                {
                    #ifdef UNITY_COLORSPACE_GAMMA
                    half SurReduction = 1 - 0.28 * roughness * roughness;
                    #else
                    half SurReduction = 1 / (roughness * roughness + 1);
                    #endif
                    #if defined(SHADER_API_GLES) // Lighting.hlsl 261 行
                    half Reflectivity = BRDFspe.x;
                    #else
                    half Reflectivity = max(max(BRDFspe.x, BRDFspe.y), BRDFspe.z);
                    #endif
                    half GrazingTSection = saturate(Reflectivity + smoothness);
                    half fre = Pow4(1 - NdotV); // Lighting.hlsl 第 501 行
                    // half fre = exp2((-5.55473 * NdotV - 6.98316) * NdotV); // Lighting.hlsl 第 501 行,他是 4 次方,我们是 5 次方
                    return lerp(F0, GrazingTSection, fre) * SurReduction;
                }



#endif

References:

感谢两位前辈的经验分享:

Unity Shader - 搬砖日志 - URP PBR (抄作业篇,持续更新~)_unity urp pbr_Jave.Lin的博客-CSDN博客

Unity Shader 学习笔记(4)URP渲染管线带阴影PBR-Shader模板 -- 新增可自定义阴影颜色_unity urp设置不接收阴影_嘿皮土豆的博客-CSDN博客

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不吃斋的和尚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值