跳转到主要内容
Computer Graphics Jun 26, 2022 2 tags

对PBR的理解

对PBR的理解

cover

PBR是什么

字面意思基于物理的渲染

什么叫基于物理?

• 基于物理的光照(Lighting) • 基于物理适配的摄像机(Camera)

PBR基础理念的概括

使用PBR有以下几个优点:

  1. 用PBR方法创建材质更容易,创建材质属性时不再需要猜测,而是根据真实数据设置;
  2. 材质在所有光照条件下看起来都是正确的;
  3. PBR提供了一种稳定的美术工作流程。

PBR有两种主要的工作流(指数据以何种形式输入到引擎中)

一种是Metallic/Roughness(金属值/粗糙度);另一种是Specular/Glossiness(镜面反射/光泽度)

两种工作流的不同点

金属工作流的名字来源于它定义了材质表面的金属值(是金属类型还是非金属类型);

高光反射工作流的名字来源于它可以直接指定表面的高光反射颜色(有很强的高光反射还是很弱的高光反射)等,而这在金属工作流中这个颜色需要由漫反射颜色和金属值衍生而来。

Metallic

  • Base Color = 漫反射+金属反射
  • Metal贴图作为一个蒙版,用来标记金属和非金属
  • 非金属F0固定为4% 金属的反射率值,与绝缘体的反射颜色一起存储在固有色贴图中
  1. 固有色贴图中的非金属区域表示反照颜色,金属区域表示的是反射率数值。
  2. 固有色不应当含有光照信息,微观遮蔽(micro-occlusion)信息除外。
  3. 暗部数值不应低于30 sRGB(宽松范围) - 50 sRGB(严格范围)。
  4. 亮部数值不应高于240 sRGB。
  5. 金属的反射率较高,有70-100%之间的镜面反射,映射到 sRGB 为180-255

Specular

  • 可以随意控制非金属F0
  • Diffuse(Albedo)只代表漫反射

接下来我们看一个在实际工作中经常会遇到的问题,就是为什么Substance中的预览效果与导入引擎实际渲染效果差别很大?

这跟显示设备的校色、匹配场景环境、理解线性和非线性颜色空间的区别有关系。人眼感知是非线性的,即使今天的显示器是数字的,它们仍然采用伽马编码信号作为输入。伽玛空间的公认标准称为sRGB,该标准定义了一个伽马空间映射到线性空间。

UnityShader PBR Code

Shader "MyShader/PBR_Custom"
{
    Properties
    {
        _MainTex("Albedo", 2D) = "white" {}

        _Emissive("Emissive", 2D) = "white" {}
        [HDR]_EmissiveColor("EmissiveColor",Color)=(1, 1, 1, 1)

        //[Gamma]_Roughness("Roughness", Range(0.0, 1.0)) = 0.0
        _Roughness("Roughness", 2D) = "white" {}
        _RoughnessFactor("RoughnessFactor", Range(0.2, 2)) = 1.0
        _MetallicGlossMap("Metallic", 2D) = "white" {}

        _AO("AO", 2D) = "white" {}

        _BumpScale("Scale", Float) = 1.0
        _BumpMap("Normal Map", 2D) = "bump" {}

        [NoScaleOffset]_LUT("LUT", 2D) = "white" {}

        _Color("Color",Color)=(1, 1, 1, 1)
    }
    SubShader
    {
        Tags{"RenderType" = "Opaque"}
        LOD 300

        CGINCLUDE
        #include "UnityCG.cginc"
        #include "Lighting.cginc"

        //D 法线分布函数GGX
        float D(float NdotH, float Roughness)
        {
            float alpha_2 = pow(lerp(0.002, 1, Roughness), 2);
            float NdotH_2 = pow(saturate(NdotH), 2);
            float deno = UNITY_PI * pow((NdotH_2 * (alpha_2 - 1) + 1), 2);
            
            return  alpha_2 / deno;
        }

        //F 菲涅尔近似方程 Fresnel-Schlick
        float F(float VdotH, float3 F0)
        {
            half t = Pow5 (1 - VdotH);
            return lerp(t, 1, F0);
        }

        //G 几何函数 GeometrySchlickGGX
        float G(float NdotV, float NdotL, float Roughness)
        {
            //float k = pow(Roughness+1, 2) / 8;
            float k = pow(Roughness, 2) / 2;
            float g_sub1 = NdotV / lerp(NdotV,1,k);
            float g_sub2 = NdotL / lerp(NdotL,1,k);
            return g_sub1 * g_sub2;
        }

        //高光反射
        float3 Specular(float D, float F, float G, float NdotV, float NdotL)
        {
            return (D * F * G) / (4 * NdotL * NdotV + 0.000001);//防除0
        }

        //Lambert漫反射
        float3 Diffuse(float NdotH, float3 Albedo, float Tint)
        {
            return Albedo * Tint * NdotH ;
        }

        //菲涅尔近似方程 Fresnel-Schlick
        float3 fresnelSchlickRoughness(float cosTheta, float3 F0, float roughness)
        {
            return F0 + (max(float3(1.0 - roughness, 1.0 - roughness, 1.0 - roughness), F0) - F0) * pow(1.0 - cosTheta, 5.0);
        }

        //漫反射系数
        float3 Kd(float3 Flast, float Metallic)
        {
            return (1 - Flast) * (1 - Metallic);
        }

        //间接光漫反射
        float3 IBL_Diffse(float Albedo, float3 Ndir, float Kd)
        {
            //球谐
            half3 ambient_contrib = ShadeSH9(float4(Ndir, 1));
            float3 ambient = 0.03 * Albedo;
            float3 iblDiffuse = max(half3(0, 0, 0), ambient.rgb + ambient_contrib);

            return  iblDiffuse * Albedo * Kd;
        }
        
        ENDCG

        Pass
        {
            Tags {"LightMode" = "ForwardBase"}
            CGPROGRAM

            #pragma target 3.0
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityStandardBRDF.cginc" 

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

            struct v2f
            {
                float4 vertex       : SV_POSITION;
                float2 uv           : TEXCOORD0;               
                float3 lightDir     : TEXCOORD1;
                float3 viewDir      : TEXCOORD2;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            
            sampler2D _Emissive;
            sampler2D _Emissive_ST;
            float4 _EmissiveColor;

            sampler2D _Roughness;
            sampler2D _Roughness_ST;
            float _RoughnessFactor;
            sampler2D _MetallicGlossMap;
            sampler2D _MetallicGlossMap_ST;
            sampler2D _AO;
            sampler2D _AO_ST;
            
            float _BumpScale;
            sampler2D _BumpMap;
            float4 _BumpMap_ST;

            sampler2D _LUT;
            fixed4 _Color;
            
            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);

                TANGENT_SPACE_ROTATION;//省写TBN真好用(
                o.lightDir=mul(rotation,ObjSpaceLightDir(v.vertex).xyz);
                o.viewDir=mul(rotation,ObjSpaceViewDir(v.vertex).xyz);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                //准备数据
                float4 albedo = tex2D(_MainTex,i.uv) * _Color;                                  //颜色
                float4 packNormal = tex2D(_BumpMap,i.uv);                                       //法线
                float metallic = tex2D(_MetallicGlossMap,i.uv).r;                               //金属
                float roughness = tex2D(_Roughness,i.uv).r * _RoughnessFactor;                  //粗糙
                float ao = tex2D(_AO,i.uv).r;                                                   //环境光遮蔽
                float4 emissive = tex2D(_Emissive,i.uv).r;                                        //自发光
                
                float3 nDir = UnpackNormal(packNormal);
                nDir.xy *= _BumpScale;
                nDir.z = sqrt(1 - saturate(dot(nDir.xy,nDir.xy)));                              //法线转切线空间
                float3 lDir = normalize(i.lightDir);
                float3 vDir = normalize(i.viewDir);
                float3 hDir = normalize(lDir+vDir);    

                //准备中间数据1
                float NdotH = saturate(dot(nDir,hDir));
                float VdotH = saturate(dot(vDir,hDir));
                float NdotV = saturate(dot(nDir,vDir));
                float NdotL = saturate(dot(nDir,lDir));
                //准备中间数据2
                float3 F0 = float3(0.04, 0.04, 0.04);
                F0=lerp(F0, albedo.rgb, metallic);
                float3 Flast_VH = fresnelSchlickRoughness(max(VdotH, 0.0), F0, roughness);     //微观
                float3 Flast_NV = fresnelSchlickRoughness(max(NdotV, 0.0), F0, roughness);     //宏观
                float kd_VH = Kd(Flast_VH, metallic);
                float kd_NV = Kd(Flast_VH, metallic);
                float d = D(NdotH, roughness);
                float f = F(VdotH, F0);
                float g = G(NdotV, NdotL, roughness);
                
                float2 LUT_lerp = float2( lerp(0, 0.99, NdotV), lerp(0, 0.99, roughness) );
                float2 envBDRF = tex2D(_LUT,  LUT_lerp).rg; // LUT采样
                //计算
                float3 diffuse = Diffuse(NdotH, albedo.rgb, _Color);
                float3 specular = Specular(d, f, g, NdotV, NdotL);
                diffuse = diffuse * kd_VH;
                specular = specular * _Color * NdotL * UNITY_PI;
                float3 DirectLight = diffuse + specular;

                float3 ibl_Diffse = IBL_Diffse(albedo, nDir, kd_NV);
                
                //这部分为ibl_specular-----------------------
                float mip_roughness = roughness * (1.7 - 0.7 * roughness);
                float3 reflectVec = reflect(-vDir, nDir);
                half mip = mip_roughness * 6;
                half4 rgbm = UNITY_SAMPLE_TEXCUBE_LOD(unity_SpecCube0, reflectVec, mip);
                float3 ibl_Specular = DecodeHDR(rgbm, unity_SpecCube0_HDR) * (Flast_NV * envBDRF.r + envBDRF.g);
                //--------------------------------------------

                float3 inDirectLight = (ibl_Diffse + ibl_Specular) * ao;
                
                return float4(DirectLight + inDirectLight , 1);
                //return float4(DirectLight + inDirectLight + emissive * _EmissiveColor, 1);
            }
            ENDCG
        }
    }
}

Related Articles

继续阅读