PBR實現


我們的目標:UnityStandard

我一直作Unity開發,所以環境也就選擇了Unity。目標也就是盡可能接近UnityStandard的效果,不過主要為了學習PBR,所以就只實現基本的PBR和法線。也就是使用Albedo,Matellic,Normal三個貼圖。遮蔽,自發光,反射和ImageBasedLighting這種就先不管了

PBR原理

PBR的原理務必要看Trace的這篇【PBR】基於物理渲染的基礎理論1.0版

簡單的解釋
1. 分開處理反射面的絕緣體特性和金屬特性,最后光照應該是Diffuse+Specular
2. 純金屬沒有Diffuse,非金屬主要是Diffuse,有一點反射
3. 反光部分的主要有三個東西影響:微表面的法線分布(NDF),微表面的入射和反射遮擋(Geometry Function),反射率和入射角的關系(Fresnel反射)

猜測Unity的方案

我沒去找Standard的源碼,照着WORKING WITH PHYSICALLY-BASED SHADING: A PRACTICAL APPROACH猜測一下他的方案。

Unity Metallic方式

1. Albedo:基礎色,和以前的Diffuse圖不一樣,不能畫出光的效
2. Metallic:Metallic圖的r通道,描述金屬性質。應該是描述反射率的參數,用這個參數將Diffuse部分和Specular部分分開
3. Smoothness:光滑度,Metallic圖的a通道。用來描述光滑程度(廢話),或者說,描述微表面理論中微表面法線和整體法線的一致程度(NDF),以及微表面遮擋的程度(Geometry Function)
4. Normal:法線,沒啥說的

所以看起來差不多應該是這樣的意思:
Diffuse*(1-Metallic)+f(l,v)*Metallic

f(l,v)就是PBR的核心內容,BRDF公式:

//       D(h) F(v,h) G(l,v,h)
//f(l,v) = ---------------------------
//       4(n·l)(n·v)

 

BRDF公式的選擇與說明

公式這東西誰看誰暈,所以先強推這個文章
如何看懂這些"該死的"圖形學公式
我能繼續搞下去全靠這個了。

到這里問題就很簡單了,選擇合適的公式就對了。翻找了一下SIGGRAPH的文章,全是干貨真是好地方!照着人家的干貨做了如下選擇:

1. D(h):GGX

//	  alpha^2
//D(m) = -----------------------------------
//	  pi*((n·m)^2 *(alpha^2-1)+1)^2

Q:alpha是什么
A:alpha = roughness * roughness,roughness是粗糙度,roughness= 1-smoothness

Q:m是什么?h是什么?
A:m是微表面的法線, h是入射光和反射光的半角向量,根據微表面原理,只有法線和半角向量一致的微表面參與反射,所以m和h是相等的

2. G(l,v,h):Smith-Schlick,在Smith近似下G(l,v,h) = g(l)*g(v),講道理入射和反射的遮蔽應該是相同的。

 

//	  n·v
//g(v) = -----------------
//     (n·v) *(1-k) +k

  

Q:k是什么?
A:k是Schlick公式的參數,具體的要去看Schlick的論文

Q:k應該是多少?
A:我看了幾個地方,k的選擇都不太一樣,這里我們本着找NB的抄的態度,用了UE4的 k =(a^2 +1) * (a^2 +1)/8;

3. F(v,h):UE4對Schlick的一個近似。

//Schlick
//F(v,h) = F0 +(1-F0)*(1-(v·h))^5
//
//UE4 approximation
//
//F(v,h) = F0+(1-F0)2^((-5.55473(v·h)-6.98316)*v·h)

  

Q:F0是什么?
A:F0,入射角0時的反射率。這個數值我用了Metallic的值,應該還有跟roughness相關的計算,誰清除求告知

然后合成一個!

//       alpha^2 * (F0+(1-F0)*pow(2,(-5.55473(v·h)-6.98316)*v·h))
//f(l,v) = ---------------------------------------------------------------------------
//       4*pi*((n·h)^2 *(alpha^2-1)+1)^2*((n·v) *(1-k) +k)*((n·l) *(1-k) +k)

  

寫Shader

這就沒啥說的了,就是把公式寫上去。
效果圖:右邊是Standard,左邊是Custom,有一點點不同,反射更強烈一點

在球上的對比效果

 

Shader "Stein/CustomPBR"
{
    Properties
    {
        _Matel("Matel",2D) = "white"{}
        _Albedo("Albedo", 2D) = "white" {}
          _Normal ("Normal", 2D) = "bump"{}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

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

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog
            #pragma multi_compile_fwdbase_fullshadows

            #include "UnityCG.cginc"  
            #include "AutoLight.cginc"
            #define PI 3.14159265359

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal:NORMAL;
                 float4 tangent : TANGENT;
            };

            struct VertexOutput
            {
                float4 pos : SV_POSITION;
                float2 uv0 : TEXCOORD0;
                float4 posWorld : TEXCOORD1;
                float3 normalDir : TEXCOORD2;
                float3 tangentDir : TEXCOORD3;
                float3 bitangentDir : TEXCOORD4;
                LIGHTING_COORDS(5,6)
                UNITY_FOG_COORDS(7)
            };

            uniform float4 _LightColor0;

            sampler2D _Albedo;
            float4 _Albedo_ST;
            sampler2D _Matel;
            float4 _Matel_ST;
            uniform sampler2D _Normal;
            uniform float4 _Normal_ST;


            VertexOutput vert (appdata v)
            {
                 VertexOutput o = (VertexOutput)0;

                o.pos = mul(UNITY_MATRIX_MVP, v.vertex );
                o.uv0 = v.uv;
                o.posWorld = mul(_Object2World, v.vertex);

                //世界坐標下的幾個向量值,參考ShaderForge
                o.normalDir = UnityObjectToWorldNormal(v.normal);
                o.tangentDir = normalize( mul( _Object2World, float4( v.tangent.xyz, 0.0 ) ).xyz );
                o.bitangentDir = normalize(cross(o.normalDir, o.tangentDir) * v.tangent.w);

                UNITY_TRANSFER_FOG(o,o.pos);
                TRANSFER_VERTEX_TO_FRAGMENT(o)
                return o;
            }

            fixed4 frag (VertexOutput i) : SV_Target
            {
                 i.normalDir = normalize(i.normalDir);

                float3 lightDirection = normalize(_WorldSpaceLightPos0.xyz);
                float3 viewDirection = normalize(_WorldSpaceCameraPos.xyz - i.posWorld.xyz);

                //法線左邊轉換
                float3x3 tangentTransform = float3x3( i.tangentDir, i.bitangentDir, i.normalDir);//法線的TBN旋轉矩陣
                float4 _Normal_var = tex2D(_Normal,TRANSFORM_TEX(i.uv0, _Normal));
                float3 normalLocal =_Normal_var.rgb*2-1;//之前的問題是沒有Unpack,整個坐標是偏了的,參考UnityCG.cginc
                float3 normalDirection = normalize(mul( normalLocal, tangentTransform )); // 最終的法線

                //從matellic圖上取數據
                fixed4 matelTex = tex2D(_Matel,TRANSFORM_TEX(i.uv0,_Matel));
                float matellic = matelTex.r;//unity matellic 值,是一個grayscale value ,存在 r 通道
                float roughness = 1-matelTex.a;//unity 用的是smoothness,在matellic map的alpha 通道,這里轉換一下
                float f0 = matelTex.r;//HACK 這個就是先這樣用……

                //預先計算一些常量
                float3 h =normalize( lightDirection+viewDirection);//h,l和v的半角向量
                float a = roughness*roughness;//alpha
                float a2 = a*a;//alpha^2

                float NoL =saturate( dot(normalDirection,lightDirection));
                float NoV =saturate(dot(normalDirection,viewDirection));
                float NoH =saturate(dot(normalDirection,h));
                float VoH =saturate(dot(viewDirection,h));

                //light & light color
                float3 attenColor = LIGHT_ATTENUATION(i) * _LightColor0.xyz;

                // sample the _Albedo texture
                fixed4 albedo = tex2D(_Albedo, i.uv0);

                //diffuse part
                float3 directDiffuse =dot( normalDirection, lightDirection ) * attenColor;
                float3 indirectDiffuse = float3(0,0,0);
                indirectDiffuse += UNITY_LIGHTMODEL_AMBIENT.rgb; // Ambient Light
                float3 diffuse = (directDiffuse + indirectDiffuse) * albedo*(1-matellic);

                //specular part
                //微表面BRDF公式
                //                D(h) F(v,h) G(l,v,h)
                //f(l,v) = ---------------------------
                //                4(n·l)(n·v)

                //這個是GGX
                //                alpha^2
                //D(m) = -----------------------------------
                //                pi*((n·m)^2 *(alpha^2-1)+1)^2

                //簡化 D(h)*PI/4
                float sqrtD = rcp(NoH*NoH*(a2-1)+1);
//                float D = a2*sqrtD*sqrtD/rcp(PI*4);
                float D = a2*sqrtD*sqrtD/4;//在 direct specular時,BRDF好像要乘PI,這里就直接約去。Naty Hoffman的那個文沒太看懂

                //in smith model G(l,v,h) = g(l)*g(v),這個公式是Schlick的趨近公式,參數各有不同
                //                n·v
                //G(v) = -----------------
                //                (n·v) *(1-k) +k

//                float k = a2*sqrt(2/PI);             //Schlick-Beckmann
//                float k = a2/2;                        //Schlick-GGX
                float k =(a2+1)*(a2+1)/8;        //UE4,咱們就挑NB的抄

                //簡化G(l,v,h)/(n·l)(n·v)
                float GV=(NoV *(1-k) +k);
                float GL =(NoL *(1-k) +k);

                //F(v,h)
                float f = f0 +(1-f0)*pow(2,(-5.55473*VoH-6.98316)*VoH);//參數是從UE4那里抄來的,應該是Schlick公式的趨近

                fixed3 specularTerm = D*f *rcp(GV*GL);

                fixed3 specular = albedo*attenColor*(1/PI+ specularTerm)*NoL*matellic;//albedo/PI是BRDF公式的diffuse部分,沒有就會偏黑
                fixed4 finalcolor = (fixed4)0;
                finalcolor.rgb =diffuse +specular;
                finalcolor.a = albedo.a;

                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, finalcolor);
                return finalcolor;
            }
            ENDCG
        }

        Pass
        {
            Name "FORWARD_DELTA"
            Tags {
                "LightMode"="ForwardAdd"
            }
             Blend One One

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog
            #pragma multi_compile_fwdbase_fullshadows

            #include "UnityCG.cginc"  
            #include "AutoLight.cginc"
            #define PI 3.14159265359

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal:NORMAL;
                 float4 tangent : TANGENT;
            };

            struct VertexOutput
            {
                float4 pos : SV_POSITION;
                float2 uv0 : TEXCOORD0;
                float4 posWorld : TEXCOORD1;
                float3 normalDir : TEXCOORD2;
                float3 tangentDir : TEXCOORD3;
                float3 bitangentDir : TEXCOORD4;
                LIGHTING_COORDS(5,6)
                UNITY_FOG_COORDS(7)
            };

            uniform float4 _LightColor0;

            sampler2D _Albedo;
            float4 _Albedo_ST;
            sampler2D _Matel;
            float4 _Matel_ST;
            uniform sampler2D _Normal;
            uniform float4 _Normal_ST;


            VertexOutput vert (appdata v)
            {
                 VertexOutput o = (VertexOutput)0;

                o.pos = mul(UNITY_MATRIX_MVP, v.vertex );
                o.uv0 = v.uv;
                o.posWorld = mul(_Object2World, v.vertex);

                //世界坐標下的幾個向量值,參考ShaderForge
                o.normalDir = UnityObjectToWorldNormal(v.normal);
                o.tangentDir = normalize( mul( _Object2World, float4( v.tangent.xyz, 0.0 ) ).xyz );
                o.bitangentDir = normalize(cross(o.normalDir, o.tangentDir) * v.tangent.w);

                UNITY_TRANSFER_FOG(o,o.pos);
                TRANSFER_VERTEX_TO_FRAGMENT(o)
                return o;
            }

            fixed4 frag (VertexOutput i) : SV_Target
            {
                 i.normalDir = normalize(i.normalDir);


                //light dir & light color
                 float3 lightDirection  =(float3)0;
                 float3 attenColor = (float3)0;

                 if(_WorldSpaceLightPos0.w==0)
                 {
                    lightDirection = normalize(_WorldSpaceLightPos0.xyz);
                    attenColor = LIGHT_ATTENUATION(i) * _LightColor0.xyz;
                 }
                 else
                 {
                     lightDirection =_WorldSpaceLightPos0.xyz- i.posWorld;
                     attenColor =_LightColor0.xyz /(1+length(lightDirection));
                     lightDirection = normalize(lightDirection);
                 }

//                float3 attenColor = LIGHT_ATTENUATION(i) * _LightColor0.xyz;

                float3 viewDirection = normalize(_WorldSpaceCameraPos.xyz - i.posWorld.xyz);

                //法線左邊轉換
                float3x3 tangentTransform = float3x3( i.tangentDir, i.bitangentDir, i.normalDir);//法線的TBN旋轉矩陣
                float4 _Normal_var = tex2D(_Normal,TRANSFORM_TEX(i.uv0, _Normal));
                float3 normalLocal =_Normal_var.rgb*2-1;//之前的問題是沒有Unpack,整個坐標是偏了的,參考UnityCG.cginc
                float3 normalDirection = normalize(mul( normalLocal, tangentTransform )); // 最終的法線

                //從matellic圖上取數據
                fixed4 matelTex = tex2D(_Matel,TRANSFORM_TEX(i.uv0,_Matel));
                float matellic = matelTex.r;//unity matellic 值,是一個grayscale value ,存在 r 通道
                float roughness = 1-matelTex.a;//unity 用的是smoothness,在matellic map的alpha 通道,這里轉換一下
                float f0 = matelTex.r;//HACK 這個就是先這樣用……

                //預先計算一些常量
                float3 h =normalize( lightDirection+viewDirection);//h,l和v的半角向量
                float a = roughness*roughness;//alpha
                float a2 = a*a;//alpha^2

                float NoL =saturate( dot(normalDirection,lightDirection));
                float NoV =saturate(dot(normalDirection,viewDirection));
                float NoH =saturate(dot(normalDirection,h));
                float VoH =saturate(dot(viewDirection,h));


                // sample the _Albedo texture
                fixed4 albedo = tex2D(_Albedo, i.uv0);

                //diffuse part
                float3 directDiffuse =dot( normalDirection, lightDirection ) * attenColor;
                float3 indirectDiffuse = float3(0,0,0);
                indirectDiffuse += UNITY_LIGHTMODEL_AMBIENT.rgb; // Ambient Light
                float3 diffuse = (directDiffuse + indirectDiffuse) * albedo*(1-matellic);

                //specular part
                //微表面BRDF公式
                //                D(h) F(v,h) G(l,v,h)
                //f(l,v) = ---------------------------
                //                4(n·l)(n·v)

                //這個是GGX
                //                alpha^2
                //D(m) = -----------------------------------
                //                pi*((n·m)^2 *(alpha^2-1)+1)^2

                //簡化 D(h)*PI/4
                float sqrtD = rcp(NoH*NoH*(a2-1)+1);
//                float D = a2*sqrtD*sqrtD/rcp(PI*4);
                float D = a2*sqrtD*sqrtD/4;//在 direct specular時,BRDF好像要乘PI,這里就直接約去。Naty Hoffman的那個文沒太看懂

                //in smith model G(l,v,h) = g(l)*g(v),這個公式是Schlick的趨近公式,參數各有不同
                //                n·v
                //G(v) = -----------------
                //                (n·v) *(1-k) +k

//                float k = a2*sqrt(2/PI);             //Schlick-Beckmann
//                float k = a2/2;                        //Schlick-GGX
                float k =(a2+1)*(a2+1)/8;        //UE4,咱們就挑NB的抄

                //簡化G(l,v,h)/(n·l)(n·v)
                float GV=(NoV *(1-k) +k);
                float GL =(NoL *(1-k) +k);

                //F(v,h)
                float f = f0 +(1-f0)*pow(2,(-5.55473*VoH-6.98316)*VoH);//參數是從UE4那里抄來的,應該是Schlick公式的趨近

                fixed3 specularTerm = D*f *rcp(GV*GL);

                fixed3 specular = albedo* attenColor*(1/PI+ specularTerm)*NoL*matellic;//albedo/PI是BRDF公式的diffuse部分,沒有就會偏黑
                fixed4 finalcolor = (fixed4)0;
                finalcolor.rgb =diffuse +specular;
                finalcolor.a = 0;

                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, finalcolor);
                return finalcolor;
            }
            ENDCG
        }
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM