Unity Shader - 搬砖日志 - URP PBR (抄作业篇,持续更新~)

文章目录

  • 目的
  • 环境
  • PBR 主要渲染方程
    • D 项
      • GGB(desmos)
      • D_Term 完整 Shader
    • G 项
      • GGB
      • G_Term 完整 Shader
    • F 项
      • GGB
      • F_Term 完整 Shader
    • D, G, F 带入公式
      • PBR_Test_DGF.hlsl
      • DGF_Term
  • 应用到具体 PBR 素材上
    • 完整 Shader - 只有 PBR + SH(Reflection Probe) + Emissive
      • PBR__Emissive_Lib.hlsl
      • URP_PBR_Emissive.shader
  • References


目的

这篇文章的目的:只为了备忘、和便于回顾、复习

只适合本人自己查看


环境

下面代码的运行环境

  • Unity : 2019.3.11f1
  • Pipeline : URP

这篇文章,在 2021/12 - 2022/2 初,断断续续的时间片抄完的笔记

参考资料具体查看:Reference

下面多数是抄出来的作业,少部分有自己的理解、调整的东西

这篇 PBR 中,很多都是经过各个 optimization 之后的分支代码


PBR 主要渲染方程

L o ( p , w o ) = ∫ Ω ( k d c π + k s D G F 4 ( w o ⋅ n ) ( w i ⋅ n ) ) L i ( p , w i ) ( w i ⋅ n ) d w i L_o(p,w_o)=\int_{\Omega}(k_d \frac{c}{\pi} + k_s \frac {D G F}{4(w_o \cdot n)(w_i \cdot n)}) L_i(p,w_i)(w_i \cdot n) dw_i Lo(p,wo)=Ω(kdπc+ks4(won)(win)DGF)Li(p,wi)(win)dwi

  • D - 法线分布函数:描述微表面法线 N 和 半角 H 同向性的比重,光滑度越高,也就是粗糙度越低(越光滑),那么N,H 的同向性越大(反射越清晰)
  • G - 后面补上,看下面抄的作业中的描述,也时可以,但是还不够详细
  • F - 同上

D 项

法线分布函数GGX(Normal Distribution Function),即核心方程的D项

D - 法线分布函数:描述微表面法线 N 和 半角 H 同向性的比重,光滑度越高,也就是粗糙度越低(越光滑),那么N,H 的同向性越大(反射越清晰)

D = a 2 π ( ( n ⋅ h ) 2 ( a 2 − 1 ) + 1 ) 2 D = \frac {a^2} {\pi ((n \cdot h)^2 (a^2-1)+1)^2} D=π((nh)2(a21)+1)2a2

  • a a a - 粗糙度
  • n n n - 法线
  • h h h - normalize(L + V),是方向与光源方向的半角向量,这里的光源方向指 计算点到光源的方向,不是光源照射的方向
  • n ⋅ h n \cdot h nh - NdotH - 法线与半角向量的点乘

核心函数

// #define PI 3.1415926
// D 项 法线微表面分布函数
float D_Function(float NdotH, float roughness)
{
    float a2 = roughness * roughness;
    float NdotH2 = NdotH * NdotH;
    float d = (NdotH2 * (a2 - 1) + 1);
    d = d * d;// * PI;
    return a2 / d;
}

GGB(desmos)

这次使用的是 desmos 不是 GGM,也是也差不多,可以看到 D_Term 只是一个拟合曲线(只要横向在 0-1 的部分曲线),来达到近视对应 物理仪器采集的数值对应的模型,下面是可以看到 NdotH 0 - 1 的变化:下压 到 上拱
Unity Shader - 搬砖日志 - URP PBR (抄作业篇,持续更新~)_第1张图片

D_Term 完整 Shader

// jave.lin 2022/01/18
// PBR GGX D Term

Shader "Test/URP_PBR_D_Term"
{
    Properties
    {
        _Roughness ("Roughness", Range(0, 1)) = 0.5
    }
    SubShader
    {
        Tags
        {
            "RenderPipeline" = "UniversalRenderPipeline"
        }
        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
            struct appdata
            {
                float4 positionOS : POSITION;
                half3 normalOS : NORMAL;
            };
            struct v2f
            {
                float4 positionCS : SV_POSITION;
                half3 normalWS : TEXCOORD0;
                float3 positionWS : TEXCOORD1;
            };
            half _Roughness;
            // #define PI 3.1415926
            // D 项 法线微表面分布函数
            float D_Function(float NdotH, float roughness)
            {
                float a2 = roughness * roughness;
                float NdotH2 = NdotH * NdotH;
                float d = (NdotH2 * (a2 - 1) + 1);
                d = d * d;// * PI;
                return a2 / d;
            }
            v2f vert (appdata v)
            {
                v2f o;
                o.positionCS = TransformObjectToHClip(v.positionOS.xyz);
                o.positionWS = TransformObjectToWorld(v.positionOS.xyz);
                o.normalWS = TransformObjectToWorldNormal(v.normalOS.xyz);
                return o;
            }
            half4 frag (v2f i) : SV_Target
            {
                half3 N = normalize(i.normalWS);
                // return half4(N, 1);
                Light mainLight = GetMainLight();
                half3 L = normalize(mainLight.direction);
                // return half4(L, 1);
                half3 V = SafeNormalize(_WorldSpaceCameraPos - i.positionWS);
                // return half4(V, 1);
                half3 H = normalize(L + V);
                // return half4(H, 1);
                half HdotN = max(dot(H, N), 1e-5);
                return D_Function(HdotN, _Roughness);
            }
            ENDHLSL
        }
    }
}

Unity Shader - 搬砖日志 - URP PBR (抄作业篇,持续更新~)_第2张图片


G 项

几何函数G,(Geometry function),即核心方程的G项,它是描述入射射线(即光照方向)和出射射线(视线方向)被自己的微观几何形状遮挡的比重。

G = n ⋅ l l e r p ( n ⋅ l , 1 , k ) × n ⋅ v l e r p ( n ⋅ v , 1 , k ) G = \frac {n \cdot l} {lerp(n \cdot l, 1, k)} \times \frac {n \cdot v} {lerp(n \cdot v, 1, k)} G=lerp(nl,1,k)nl×lerp(nv,1,k)nv

  • n n n - 法线
  • l l l - 灯光方向(不是入射方向,即:入射反向,从计算点到光源的方向)
  • k k k - NdotL 的过去系数,这里使用拟合曲线:float k = pow(1 + roughness, 2) * 0.5;,下面的 GGB 有图形化该曲线样式

G项是由2个几乎一样的子项相乘得来,故我们可以先定义子项的函数

// G项子项
inline float G_subSection(float dot, float k)
{
	return dot / lerp(dot, 1, k);
}

再去定义真正的G项,把 d o t ( N , L ) dot(N, L) dot(N,L) d o t ( N , V ) dot(N, V) dot(N,V)代入子项,相乘即可

// G项
 float G_Function(float NdotL, float NdotV, float roughness)
 {
     // method1-k:
     // // float k = pow(1 + roughness, 2) / 8.0;

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

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

GGB

k 曲线,变量 roughness 在 0~1 的范围,值域为:0.5~2.0
Unity Shader - 搬砖日志 - URP PBR (抄作业篇,持续更新~)_第3张图片

G_Term 完整 Shader

// jave.lin 2022/01/19
// PBR G Term

Shader "Test/URP_PBR_G_Term"
{
    Properties
    {
        _Roughness ("Roughness", Range(0, 1)) = 0.5
    }
    SubShader
    {
        Tags
        {
            "RenderPipeline" = "UniversalRenderPipeline"
        }
        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
            struct appdata
            {
                float4 positionOS : POSITION;
                half3 normalOS : NORMAL;
            };
            struct v2f
            {
                float4 positionCS : SV_POSITION;
                half3 normalWS : TEXCOORD0;
                float3 positionWS : TEXCOORD1;
            };
            half _Roughness;
            // G项子项
            inline float G_subSection(float dot, float k)
            {
                return dot / lerp(dot, 1, k);
            }
            // G项
            float G_Function(float NdotL, float NdotV, float roughness)
            {
                // method1-k:
                // // float k = pow(1 + roughness, 2) / 8.0;

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

                // method3-k:
                float k = pow(1 + roughness, 2) * 0.5;
                return G_subSection(NdotL, k) * G_subSection(NdotV, k);
            }
            v2f vert (appdata v)
            {
                v2f o;
                o.positionCS = TransformObjectToHClip(v.positionOS.xyz);
                o.positionWS = TransformObjectToWorld(v.positionOS.xyz);
                o.normalWS = TransformObjectToWorldNormal(v.normalOS.xyz);
                return o;
            }
            half4 frag (v2f i) : SV_Target
            {
                half3 N = normalize(i.normalWS);
                // return half4(N, 1);
                Light mainLight = GetMainLight();
                half3 L = normalize(mainLight.direction);
                // return half4(L, 1);
                half3 V = SafeNormalize(_WorldSpaceCameraPos - i.positionWS);
                // return half4(V, 1);
                // half3 H = normalize(L + V);
                // return half4(H, 1);
                half NdotL = max(dot(N, L), 1e-5);
                half NdotV = max(dot(N, V), 1e-5);
                return G_Function(NdotL, NdotV, _Roughness);
            }
            ENDHLSL
        }
    }
}

Unity Shader - 搬砖日志 - URP PBR (抄作业篇,持续更新~)_第4张图片


F 项

菲涅尔函数F(Fresnel equation),即核心方程的F项

菲涅尔反射(Fresnel Reflectance)。光线以不同角度入射会有不同的反射率。相同的入射角度,不同的物质也会有不同的反射率。万物皆有菲涅尔反射。F0是即 0 度角入射的菲涅尔反射值。大多数非金属F0范围是 0.02 - 0.04,大多数金属F0范围是 0.7 - 1.0

如下图(UE4 PBR文档的图片):
Unity Shader - 搬砖日志 - URP PBR (抄作业篇,持续更新~)_第5张图片

下面是 Schlick 的模型的公式对应的代码:

F = l e r p ( ( 1 − ( n ⋅ v ) ) 5 , 1 , F 0 ) F = lerp((1-(n \cdot v))^5, 1, F_0) F=lerp((1(nv))5,1,F0)

但是由于我们需要的法线方向并不是模型本身的宏观法线n,而是经过D项筛选通过的微观法线H,故需把N改成H,再将 Schlick 调整为如下数学公式(为何我觉得上面的公司更为直观,-_-,大概是因为更代码化)

F S c h l i c k ( h , v , F 0 ) = F 0 + ( 1 − F 0 ) ( 1 − ( h ⋅ v ) ) 5 F_{Schlick} (h, v, F_0) = F_0 + (1 - F_0)(1 - (h \cdot v))^5 FSchlick(h,v,F0)=F0+(1F0)(1(hv))5

后来unity对它进行了优化(Optimizing GGX Shaders with dot(L,H)),视线方向V换成了L,如下所示,这是我们所使用的函数

F ( l , h ) = F 0 + ( 1 − F 0 ) ( 1 − l ⋅ h ) 5 F(l, h) = F_0 + (1 - F_0) (1 - l \cdot h)^5 F(l,h)=F0+(1F0)(1lh)5

其中的5次方计算量比较大,把它变成自然对数函数进行计算可以节省计算量,后续文章里所有的5次方计算都可以换算成对数计算

x y = e y   l n   x x^y = e^{y \space ln \space x} xy=ey ln x

故最终,我们的菲涅尔函数如下。

// method1:
// F项

inline half3 F0_Function(half3 albedo, half metallic)
{
    // jave.lin :
    // 非金属一般都是纯灰度:0.02 ~ 0.04
    // 金属一般都是纯颜色:0.7 ~ 1.0 之间的,这里我们简单的使用一个 lerp 来模拟
    // 后续其他版本的 pbr 可以调整为更加效果的
    return lerp(0.04, albedo, metallic);
}

half3 F_Function(float HdotL, half3 F)
{
    // jave.lin : (1-x)^5 转为性能更高的来模拟:2^((-5.55473 * x- 6.98316) * x)
    half Fre = exp2((-5.55473 * HdotL - 6.98316) * HdotL);
    return lerp(Fre, 1, F);
}

// // method2:
// // F项
// half3 F_Function(float HdotL, half3 F)
// {
//     half Fre = pow(1 - HdotL, 5);
//     return lerp(Fre, 1, F);
// }

GGB

Unity Shader - 搬砖日志 - URP PBR (抄作业篇,持续更新~)_第6张图片

Unity Shader - 搬砖日志 - URP PBR (抄作业篇,持续更新~)_第7张图片

F_Term 完整 Shader

// jave.lin 2022/01/19
// PBR F Term

Shader "Test/URP_PBR_F_Term"
{
    Properties
    {
        _Color ("Color", Color) = (1, 1, 1, 1)
        _Metallic ("_Metallic", Range(0, 1)) = 0.5
    }
    SubShader
    {
        Tags
        {
            "RenderPipeline" = "UniversalRenderPipeline"
        }
        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
            struct appdata
            {
                float4 positionOS : POSITION;
                half3 normalOS : NORMAL;
            };
            struct v2f
            {
                float4 positionCS : SV_POSITION;
                half3 normalWS : TEXCOORD0;
                float3 positionWS : TEXCOORD1;
            };
            half4 _Color;
            half _Metallic;

            // method1:
            // F项

            inline half3 F0_Function(half3 albedo, half metallic)
            {
                // jave.lin :
                // 非金属一般都是纯灰度:0.02 ~ 0.04
                // 金属一般都是纯颜色:0.7 ~ 1.0 之间的,这里我们简单的使用一个 lerp 来模拟
                // 后续其他版本的 pbr 可以调整为更加效果的
                return lerp(0.04, albedo, metallic);
            }

            half3 F_Function(float HdotL, half3 F)
            {
                // jave.lin : (1-x)^5 转为性能更高的来模拟:2^((-5.55473 * x- 6.98316) * x)
                half Fre = exp2((-5.55473 * HdotL - 6.98316) * HdotL);
                return lerp(Fre, 1, F);
            }

            // // method2:
            // // F项
            // half3 F_Function(float HdotL, half3 F)
            // {
            //     half Fre = pow(1 - HdotL, 5);
            //     return lerp(Fre, 1, F);
            // }
            
            v2f vert (appdata v)
            {
                v2f o;
                o.positionCS = TransformObjectToHClip(v.positionOS.xyz);
                o.positionWS = TransformObjectToWorld(v.positionOS.xyz);
                o.normalWS = TransformObjectToWorldNormal(v.normalOS.xyz);
                return o;
            }
            half4 frag (v2f i) : SV_Target
            {
                // half3 N = normalize(i.normalWS);
                // return half4(N, 1);
                Light mainLight = GetMainLight();
                half3 L = normalize(mainLight.direction);
                // return half4(L, 1);
                half3 V = SafeNormalize(_WorldSpaceCameraPos - i.positionWS);
                // return half4(V, 1);
                half3 H = normalize(L + V);
                // return half4(H, 1);
                // half3 F = lerp(0.04, _Color.rgb, _Metallic);
                half HdotL = max(dot(H, L), 1e-5);
                half3 F = F0_Function(_Color.rgb, _Metallic);
                return half4(F_Function(HdotL, F), 1);
            }
            ENDHLSL
        }
    }
}

Unity Shader - 搬砖日志 - URP PBR (抄作业篇,持续更新~)_第8张图片


D, G, F 带入公式

直接光的高光部分:我们把D,G,F代入公式,先计算出高光部分。注意这里经过半球积分后会乘PI,不要丢了;注意这里并未再次乘KS,因为KS=F,已经计算过了一次不需要重复计算。

halfHdotN = max(dot(H, N), 1e-5);
half NdotL = max(dot(N, L), 1e-5);
half NdotV = max(dot(N, V), 1e-5);
half HdotL = max(dot(H, L), 1e-5);

// ======= Direct START ======

half3 F = F0_Function(_Color.rgb, _Metallic);
half Direct_D = D_Function(HdotN, _Roughness);
half Direct_G = G_Function(NdotL, NdotV, _Roughness);
half3 Direct_F = F_Function(HdotL, F);
half3 BRDFSpecSection = (Direct_D * Direct_G) * Direct_F / (4 * NdotL * NdotV);
half3 DirectSpeColor = BRDFSpecSection * mainLight.color * NdotL * PI;
return half4(DirectSpeColor, 1);
// ======= Direct END ======

PBR_Test_DGF.hlsl

#ifndef __PBR_TEST_DGF_H__
#define __PBR_TEST_DGF_H__

// jave.lin : 2022/01/19

half4 _Color;
half _Metallic;
half _Roughness; // 后续改到 ubo / cbuffer 中

// D 项 法线微表面分布函数
float D_Function(float NdotH, float roughness)
{
    float a2 = roughness * roughness;
    float NdotH2 = NdotH * NdotH;
    float d = (NdotH2 * (a2 - 1) + 1);
    d = d * d;// * PI;
    return a2 / d;
}

// G项子项
inline float G_subSection(float dot, float k)
{
    return dot / lerp(dot, 1, k);
}
// G项
float G_Function(float NdotL, float NdotV, float roughness)
{
    // method1-k:
    // // float k = pow(1 + roughness, 2) / 8.0;

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

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

// method1:
// F项
half3 F_Function(float HdotL, half3 F0)
{
    // jave.lin : (1-x)^5 转为性能更高的来模拟:2^((-5.55473 * x- 6.98316) * x)
    half Fre = exp2((-5.55473 * HdotL - 6.98316) * HdotL);
    return lerp(Fre, 1, F0);
}

// // method2:
// // F项
// half F_Function(float HdotL, half F0)
// {
//     half Fre = pow(1 - HdotL, 5);
//     return lerp(Fre, 1, F0);
// }

inline half3 F0_Function(half3 albedo, half metallic)
{
    // jave.lin :
    // 非金属一般都是纯灰度:0.02 ~ 0.04
    // 金属一般都是纯颜色:0.7 ~ 1.0 之间的,这里我们简单的使用一个 lerp 来模拟
    // 后续其他版本的 pbr 可以调整为更加效果的
    return lerp(0.04, albedo, metallic);
}

#endif

DGF_Term

// jave.lin 2022/01/19
// PBR DGF Term

Shader "Test/URP_PBR_DGF_Term"
{
    Properties
    {
        _Color ("Color", Color) = (1, 1, 1, 1)
        _Metallic ("Metallic", Range(0, 1)) = 0.5
        _Roughness ("Roughness", Range(0, 1)) = 0.5
    }
    SubShader
    {
        Tags
        {
            "RenderPipeline" = "UniversalRenderPipeline"
        }
        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
            #include "Includes/PBR_Test_DGF.hlsl"
            struct appdata
            {
                float4 positionOS : POSITION;
                half3 normalOS : NORMAL;
            };
            struct v2f
            {
                float4 positionCS : SV_POSITION;
                half3 normalWS : TEXCOORD0;
                float3 positionWS : TEXCOORD1;
            };
            v2f vert (appdata v)
            {
                v2f o;
                o.positionCS = TransformObjectToHClip(v.positionOS.xyz);
                o.positionWS = TransformObjectToWorld(v.positionOS.xyz);
                o.normalWS = TransformObjectToWorldNormal(v.normalOS.xyz);
                return o;
            }
            half4 frag (v2f i) : SV_Target
            {
                half3 N = normalize(i.normalWS);
                // return half4(N, 1);
                Light mainLight = GetMainLight();
                half3 L = normalize(mainLight.direction);
                // return half4(L, 1);
                half3 V = SafeNormalize(_WorldSpaceCameraPos - i.positionWS);
                // return half4(V, 1);
                half3 H = normalize(L + V);
                // return half4(H, 1);
                half HdotN = max(dot(H, N), 1e-5);
                half NdotL = max(dot(N, L), 1e-5);
                half NdotV = max(dot(N, V), 1e-5);
                half HdotL = max(dot(H, L), 1e-5);

                // ======= Direct START ======

                half3 F = F0_Function(_Color.rgb, _Metallic);
                half Direct_D = D_Function(HdotN, _Roughness);
                half Direct_G = G_Function(NdotL, NdotV, _Roughness);
                half3 Direct_F = F_Function(HdotL, F);
                half3 BRDFSpecSection = (Direct_D * Direct_G) * Direct_F / (4 * NdotL * NdotV);
                half3 DirectSpeColor = BRDFSpecSection * mainLight.color * NdotL * PI;
                return half4(DirectSpeColor, 1);
                // ======= Direct END ======

            }
            ENDHLSL
        }
    }
}


应用到具体 PBR 素材上

Vela 提供的模型 - 已备份到百度网盘,但不公开(700MB+)

在 substance 官方也可以直接下载到:Vela Template

左边是:SP,右边是 Unity URP 的 Custom PBR Shader(法线是 F0 的模拟效果不太理想,后续可以将 F0 修改为一些一些色阶图来返回 F0 的纯灰度 或是 纯金属的反射颜色才比较好的效果)

天空盒 和 灯光方向、颜色、等参数都不太一样,所以效果差异就更大了
Unity Shader - 搬砖日志 - URP PBR (抄作业篇,持续更新~)_第9张图片


完整 Shader - 只有 PBR + SH(Reflection Probe) + Emissive

阴影还没添加,后续再添加了


PBR__Emissive_Lib.hlsl


#ifndef __PBR_Emissive_LIB_H__
    #define __PBR_Emissive_LIB_H__

    // #ifdef HAS_HALF
    // #undef HAS_HALF
    // #define HAS_HALF 1
    // #endif
    // jave.lin : 2022/02/06
    #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
    #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
            

    struct a2v
    {
        float4 positionOS : POSITION;
        float2 uv : TEXCOORD0;
        half3 normalOS : NORMAL;
        half4 tangentOS : TANGENT;
        float2 lightmapUV : TEXCOORD1;
    };
    struct v2f
    {
        float4 positionCS : SV_POSITION;
        float4 uv : TEXCOORD0; // xy: base map uv, zw: lightmap uv
        float4 normalWS : TEXCOORD1; // w: posWS.x
        float4 tangentWS : TEXCOORD2; // w: posWS.y
        float4 bitangentWS : TEXCOORD3; // w: posWS.z
    };

    TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap);
    TEXTURE2D(_MaskMap); SAMPLER(sampler_MaskMap);
    TEXTURE2D(_NormalMap); SAMPLER(sampler_NormalMap);
    TEXTURE2D(_EmissiveMap); SAMPLER(sampler_EmissiveMap);

    CBUFFER_START(UnityPerMaterial)
    float4 _BaseMap_ST;
    half4 _BaseColor;
    half _Metallic;
    half _AO;
    half _Roughness;
    half _NormalScale;
    half _Emissive;
    half _EmissiveTwinkleFrequence;
    CBUFFER_END

    // ========== Direct START =============
    // 直接光照部分

    // ========== 直接光照 D项 START =============
    // 直接光照 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);
    }
    // ========== 直接光照 D项 END =============

    // ========== 直接光照 G项 START =============
    // 直接光照 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
    // http://renderwonk.com/publications/s2010-shading-course/hoffman/s2010_physically_based_shading_hoffman_b.pdf
    // 搜索: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);
    }
    // ========== 直接光照 G项 END =============

    // ========== 直接光照 F项 START =============
    // 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);
    // }
    // ========== 直接光照 F项 END =============

    // ========== 直接光照 F0 START =============
    inline half3 Direct_F0_Function(half3 albedo, half metallic)
    {
        return lerp(0.04, albedo, metallic);
    }
    // ========== 直接光照 F0 END =============


    // ========== Direct END =============

    // ========== Indirect START =============

    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;
    }

    // ========== Indirect END =============

    // ========== vert START ===========
    v2f vert(a2v i)
    {
        v2f o = (v2f)0;
        float3 positionWS = TransformObjectToWorld(i.positionOS.xyz);
        o.positionCS = TransformWorldToHClip(positionWS);
        o.uv.xy = TRANSFORM_TEX(i.uv, _BaseMap);
        o.uv.zw = i.lightmapUV;
        o.normalWS.xyz = normalize(TransformObjectToWorldNormal(i.normalOS.xyz));
        o.tangentWS.xyz = normalize(TransformObjectToWorldDir(i.tangentOS.xyz));
        o.bitangentWS.xyz = cross(o.normalWS.xyz, o.tangentWS.xyz) * i.tangentOS.w * unity_WorldTransformParams.w;
        o.normalWS.w = positionWS.x;
        o.tangentWS.w = positionWS.y;
        o.bitangentWS.w = positionWS.z;
        return o;
    }
    // ========== vert END ===========

    // ========== frag START ===========
    half4 frag(v2f i) : SV_Target
    {
        half4 normalTex = SAMPLE_TEXTURE2D(_NormalMap, sampler_NormalMap, i.uv.xy);
        half3 normalTS = UnpackNormalScale(normalTex, _NormalScale);
        normalTS.z = sqrt(1 - saturate(dot(normalTS.xy, normalTS.xy)));
        float3x3 T2W = { i.tangentWS.xyz, i.bitangentWS.xyz, i.normalWS.xyz };
        T2W = transpose(T2W);
        half3 N = NormalizeNormalPerPixel(mul(T2W, normalTS));
        // return half4(N, 1);
        Light mainLight = GetMainLight();
        half3 L = normalize(mainLight.direction);
        // return half4(L, 1);
        float3 positionWS = float3(i.normalWS.w, i.tangentWS.w, i.bitangentWS.w);
        half3 V = SafeNormalize(_WorldSpaceCameraPos - positionWS);
        // return half4(V, 1);
        half3 H = normalize(L + V);
        // return half4(H, 1);
        half HdotN = max(dot(H, N), 1e-5);
        half NdotL = max(dot(N, L), 1e-5);
        half NdotV = max(dot(N, V), 1e-5);
        half HdotL = max(dot(H, L), 1e-5);

        // ======= Direct START ======

        half3 albedoTex = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, i.uv.xy).rgb * _BaseColor.rgb;
        half3 emissiveTex = SAMPLE_TEXTURE2D(_EmissiveMap, sampler_EmissiveMap, i.uv.xy).rgb;
        half3 maskTex = SAMPLE_TEXTURE2D(_MaskMap, sampler_MaskMap, i.uv.xy).rgb;
        half metallic = maskTex.r * _Metallic;
        half AO = lerp(1, maskTex.g, _AO);
        half smoothness = maskTex.b;
        half roughness = (1 - smoothness) * _Roughness;

        // direct specular
        half Direct_D = Direct_D_Function(HdotN, roughness);
         //return Direct_D;

        // jave.lin : 使用 Kelemen-Szirmay-Kalos Geometry Factor 优化(可以减少一个 sub G func)
        // 参考:http://renderwonk.com/publications/s2010-shading-course/hoffman/s2010_physically_based_shading_hoffman_b.pdf - 搜索:Kelemen-Szirmay-Kalos Geometry Factor
#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
         //return Direct_G;

        half3 F0 = Direct_F0_Function(albedoTex, metallic);
        //return half4(F0, 1);
        half3 Direct_F = Direct_F_Function(HdotL, F0);
         //return half4(Direct_F, 1);

#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
        //return half4(BRDFSpecSection, 1);

        half3 DirectSpeColor = BRDFSpecSection * mainLight.color * (NdotL * PI * AO);
         //return half4(DirectSpeColor, 1);

        // direct diffuse
        half3 KS = Direct_F;
        half3 KD = (1 - KS) * (1 - metallic);
        // return half4(KD, 1);

        half3 emissiveColor = emissiveTex * pow(2, _Emissive);
#if defined(_EMISSIVE_TWINKLE_ON)
        emissiveColor *= sin(_Time.y * _EmissiveTwinkleFrequence * 10) * 0.5 + 0.5;
#endif
        half3 DirectDiffColor = KD * albedoTex * mainLight.color * NdotL + emissiveColor;
        // return half4(DirectDiffColor, 1);

        // direct lights
        half3 DirectColor = DirectDiffColor + DirectSpeColor;
        // return half4(DirectColor, 1);

        // ======= Direct END ======

        // ======= Indirect START ======
        
        // indirect diffuse
        half3 shColor = SH_IndirectionDiff(N) * AO;
        half3 Indirect_KS = Indirect_F_Function(NdotV, F0, roughness);
        half3 Indirect_KD = (1 - Indirect_KS) * (1 - metallic);
        half3 IndirectDiffColor = shColor * Indirect_KD * albedoTex;
        // return half4(IndirectDiffColor, 1); // jave.lin : 添加一个 反射探针 即可看到效果:reflection probe

        // indirect specular

        // 反射探针的间接光
        half3 IndirectSpeCubeColor = IndirectSpeCube(N, V, roughness, AO);
        // return half4(IndirectSpeCubeColor, 1);

        half3 IndirectSpeCubeFactor = IndirectSpeFactor(roughness, smoothness, BRDFSpecSection, F0, NdotV);
         //return half4(IndirectSpeCubeFactor, 1);

        half3 IndirectSpeColor = IndirectSpeCubeColor * IndirectSpeCubeFactor;
        // return half4(IndirectSpeColor, 1);

        half3 IndirectColor = IndirectDiffColor + IndirectSpeColor;
        // return half4(IndirectColor, 1);

        // ======= Indirect END ======

        half3 finalCol = DirectColor + IndirectColor;
        return half4(finalCol, 1);
    }
    // ========== frag END ===========


#endif

URP_PBR_Emissive.shader

// jave.lin 2022/02/06
// URP PBR Emissive

Shader "Test/URP_PBR_Emissive"
{
    Properties
    {
        _BaseColor ("Color", Color) = (1, 1, 1, 1)
        _BaseMap ("Albedo", 2D) = "white" {}
        [NoScaleOffset] _MaskMap ("Mask: Metalic(R), AO(G), Smoothness(B)", 2D) = "black" {}
        [NoScaleOffset] [Normal] _NormalMap ("Normal (RGB)", 2D) = "bump" {}
        [NoScaleOffset] _EmissiveMap ("Emissive (RGB)", 2D) = "black" {}
        _Metallic ("Metallic", Range(0, 1)) = 1
        _AO ("AO", Range(0, 1)) = 1
        _Roughness ("Roughness", Range(0, 1)) = 1
        _NormalScale("NormalScale", Range(0, 1)) = 1
        _Emissive ("Emissive", Range(0, 100)) = 1
        [Toggle(_EMISSIVE_TWINKLE_ON)] _Emissve_Twinkle("Emissive Twinkle", Int) = 1
        _EmissiveTwinkleFrequence ("Emissive Twinkle Frequence", Range(0, 1)) = 1
        [Toggle(_KALOS_G_FACTOR_ON)] _Kalos_G_Factor ("Optimize with Kalos G Factor", Int) = 1
    }
    SubShader
    {
        Tags
        {
            "RenderPipeline" = "UniversalRenderPipeline"
        }
        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma shader_feature _ _EMISSIVE_TWINKLE_ON
            #pragma shader_feature _ _KALOS_G_FACTOR_ON
            #include "Includes/PBR__Emissive_Lib.hlsl"
            ENDHLSL
        }
    }
}


jave.lin : 另外说明一下

PBR 也是经验模型,是基于物理观察后的数据做的数据模型来模拟的

所以比一般非物理观测模型的更加逼真一些

因此才叫:PBR(基于物理(观察后)渲染(数学模型))

上面我们提到的公式,都是经过再优化(在原来的 PBR 的某个分支的模型基础上再次简化计算的模型)


References

  • 毛神的 PBR 总结 - (毛神一路走好)
    • 【基于物理的渲染(PBR)白皮书】(一) 开篇:PBR核心知识体系总结与概览
    • 【基于物理的渲染(PBR)白皮书】(二) PBR核心理论与渲染光学原理总结
    • 【基于物理的渲染(PBR)白皮书】(三)迪士尼原则的BRDF与BSDF相关总结
    • 【基于物理的渲染(PBR)白皮书】(四)法线分布函数相关总结
    • 【基于物理的渲染(PBR)白皮书】(五)几何函数相关总结
  • URP管线的自学HLSL之路 第三十七篇 造一个PBR的轮子
  • Optimizing GGX Shaders with dot(L,H)
  • GAMES202 笔记 -Real-Time Physically-Based Materials

最后附上近期自己用圆珠笔话的一张画,耗时大概 40 分钟

自从学习 shader 得基础光照着色后,对画画时得细节了解也会增加

(希望自己不要潜得太久,就算不太顺利,也要往前看~,给自己加油!!!)

你可能感兴趣的:(unity,unity-shader,图形,unity,PBR,PBR优化)