Ilingis 发表于 2022-6-20 13:07

unity urp 12 PBR理论和简单实现

一、PBR理论

1.1基础理论

PBR模型是基于物理渲染的模型,因为其公式是基于对真实世界的模拟,所以PBR模型对材质的表现能力很强,可以模拟几乎所以现实中存在的材质,在游戏中能很有效的提高材质的区分程度。
目前主流的PBR模型有两种,一种是PBR-SG(specular-glossiness)模型,一种是PBR-MR(metallic-roughness)模型,这两种工作流如下所示


PBR-SG模型因为参数过于灵活,经常会调节出不理想的效果,所以目前大多采用PBR-MR工作流。

1.2公式

PBR的核心公式如下,其由两部分组成:漫反射部分和高光部分,整个式子就是对各个方向的光照产生的这两部分效果做积分。



BRDF方程

其含义如下


其中ks=F,KD = (1-KS)*(1-metallic)。
1.2.1 直接光

直接光的漫反射部分就是兰伯特模型,只不过多除以了pi,这是为了保证能量守恒
直接光的镜面反射部分的核心就是DGF这三个项的计算。
1.D项是Normal distribution function,他描述了微表面的法线n和half vector之间的趋同性,公式如下


随着roughness的改变,其效果如下


2.G项Geometry function,如下所示,其用来表示微表面对于反射光线的遮挡比例。表面越粗糙,微表面对镜面反射的遮挡比例也就会越高。


其公式如下


其中k的计算再直接光照和间接光找中不同


随着粗糙度的变化,效果会有如下的变化


3.F项即Fresnel function,用来模拟现实中的菲涅尔现象。
其公式如下所示



Fresnel-Schlick公式,是对Fresnel的近似公式

它的效果如下所示


其中F0表示从0角度观看物体表面时的反射率


1.2.2间接光

1.间接光的漫反射
间接光的漫反射就是对光照探针进行采样。
2.间接光的高光反射
间接光的高光反射基于IBL,较为复杂。
这两个直接使用雪峰的代码,以后再深究。。。
二、代码

MyURP.shader
Shader "Custom/10_MyURP"
{
    Properties
    {
      _BaseColor ("BaseColor", Color) = (1,1,1,1) //颜色
      _BaseMap ("Albedo", 2D) = "white" {} //基础颜色(反照率)
      _MetallicMap("MetallicMap", 2D) = "white" {} //金属图,r通道存储金属度,a通道存储光滑度
      _MetallicStrength ("MetallicStrength", Range(0,1)) = 0 //金属强度Metallic strength
      _Smoothness("Smoothness",Range(0,1)) = 0.5 //光滑度
      _NormalMap("NormalMap", 2D) = "white"{} //法线贴图
      _OcclusionMap("OcclusionMap",2D) = "white"{} //环境光遮蔽贴图, g通道
    }
    SubShader
    {
      Tags { "RenderType"="Opaque" }
      
      HLSLINCLUDE
      #include"Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
      #include"Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
      
      sampler2D _BaseMap, _MetallicMap, _NormalMap, _OcclusionMap;
      
      CBUFFER_START(UnityPerMaterial)
      float4 _BaseColor;
      float4 _BaseMap_ST, _MetallicMap_ST, _NormalMap_ST, _OcclusionMap_ST;
      float _Smoothness, _MetallicStrength;
      CBUFFER_END
      
      struct Attributes
      {
            float3 positionOS: POSITION;
            half3 normalOS: NORMAL;
            half4 tangentOS: TANGENT;
            float2 texcoord: TEXCOORD0;
      };
      
      struct Varyings
      {
            float2 uv: TEXCOORD0;
            float3 positionWS: TEXCOORD1;
            half3 normalWS: TEXCOORD2;
            half3 tangentWS: TEXCOORD3;
            half3 bitangentWS: TEXCOORD4;
            float4 positionCS: SV_POSITION;
      };   
      ENDHLSL
      
      Pass
      {
            Tags
            {
                "LightMode"="UniversalForward"
            }
            HLSLPROGRAM
            #include "MyURPFunction.hlsl"
            
            #pragma vertex vert
            #pragma fragment frag
            
            Varyings vert(Attributes input)
            {
                Varyings output;
                VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS);
                VertexNormalInputs vertexNormalInputs = GetVertexNormalInputs(input.normalOS, input.tangentOS);
                output.uv = TRANSFORM_TEX(input.texcoord, _BaseMap);
                output.positionCS = vertexInput.positionCS;
                output.positionWS = vertexInput.positionWS;
                output.normalWS = vertexNormalInputs.normalWS;
                output.tangentWS = vertexNormalInputs.tangentWS;
                output.bitangentWS = vertexNormalInputs.bitangentWS;
                return output;
            }
            
            half4 frag(Varyings input): SV_TARGET
            {
                half4 output = (1,1,1,1);
               
                //properties
                half3 albedo = tex2D(_BaseMap,input.uv).rgb * _BaseColor.rgb;
                half2 metallicGloss = tex2D(_MetallicMap,input.uv).ra;
                half metallic = metallicGloss.x * _MetallicStrength;
                //half roughness = 1 - metallicGloss.y * _Smoothness;
                half smoothness = metallicGloss.y;
                half roughness = 1-smoothness;
                roughness = roughness*roughness;
               
                half occlusion = tex2D(_OcclusionMap, input.uv).g;
               
                //get normal map in WS
                float4 normalTex = tex2D(_NormalMap, input.uv);
                float3 normalTS =UnpackNormalScale(normalTex,1); //tangent space normal
                normalTS.z = sqrt(1-dot(normalTS.xy, normalTS.xy));
                float3x3 T2W = {input.tangentWS, input.bitangentWS, normalize(input.normalWS)};
                T2W = transpose(T2W);
                float3 normalWS = NormalizeNormalPerPixel(mul(T2W, normalTS));
                //light
                Light mainLight = GetMainLight();
               
                //get input
                //float3 normalWS = normalize(input.normalWS);
                float3 positionWS = input.positionWS;
                float3 viewDirWS = SafeNormalize(GetCameraPositionWS()-positionWS);
                float3 halfDir = normalize(viewDirWS+mainLight.direction);
                float NdotH = max(saturate(dot(normalWS, halfDir)),0.000001);
                float NdotL = max(saturate(dot(normalWS, mainLight.direction)),0.000001);
                float NdotV = max(saturate(dot(normalWS, viewDirWS)),0.000001);
                float HdotL = max(saturate(dot(halfDir, mainLight.direction)),0.000001);
                float3 F0 = lerp(0.04, albedo, metallic);
               
                ///////////////////////////
                //    direct light       //
                //////////////////////////
                //specular section
                float D = D_Function(NdotH, roughness);
                //return D;
                float G = G_Function(NdotL, NdotV, roughness);
                //return G;
                float3 F = F_Function(HdotL, F0);
                //return float4(F,1);
               
                float3 BRDFSpeSection = (D*G*F)/(4*NdotL*NdotV);
                float3 DirectSpeColor = BRDFSpeSection*mainLight.color*NdotL*PI;
                //return float4(DirectSpeColor, 1);
               
                //diffuse section
                float3 KS = F;
                float3 KD = (1-KS)*(1-metallic);
                float3 directDiffColor = KD*albedo*mainLight.color*NdotL;
                float3 directColor = DirectSpeColor+directDiffColor;
                              
                ///////////////////////////
                //    indirect light       //
                //////////////////////////

                //indirect diffuse
                float3 SHcolor = SH_IndirectionDiff(normalWS)*occlusion;               
                float3 IndirKS=IndirF_Function(NdotV,F0,roughness);
                float3 IndirKD = (1-IndirKS)*(1-metallic);
                float3 IndirDiffColor=SHcolor*IndirKD*albedo;
                //return float4(IndirDiffColor,1);

                //indirect specular
                float3 IndirSpeCubeColor = IndirSpeCube(normalWS,viewDirWS,roughness,occlusion);
                //return float4(IndirSpeCubeColor,1);
                float3 IndirSpeCubeFactor = IndirSpeFactor(roughness,smoothness,BRDFSpeSection,F0,NdotV);
                float3 IndirSpeColor = IndirSpeCubeColor*IndirSpeCubeFactor;
                //return float4(IndirSpeColor,1);
                float3 IndirColor = IndirSpeColor+IndirDiffColor;
                //return float4(IndirColor,1);
                float3 color = IndirColor+directColor;
                return float4(color, 1);
            }
            
            ENDHLSL
      }
    }
}
MyURPFunction.hlsl
#pragma once

float D_Function(float NdotH, float roughness)
{
    float a = roughness;
    float a2 = a * a;
    float NdotH2=NdotH*NdotH;
   
    float nominator = a2;
    float denominator = NdotH2*(a2-1)+1;
    denominator = denominator*denominator*PI;
    return nominator/denominator;
}

float G_Function(float NdotL, float NdotV, float roughness)
{
    //float k=roughness*roughness/2; //for indirect light(IBL)
    float k = pow(1 + roughness, 2)/8; //for direct light
    //float denominator = (NdotL*(1-k)+k)*(NdotV*(1-k)+k);
    float denominator = (NdotL/lerp(NdotL,1,k))*(NdotV/lerp(NdotV,1,k));
    return denominator;
}

real3 F_Function(float HdotL, float3 F0)
{
    float Fre = exp2((-5.55473*HdotL-6.98316)*HdotL);

    return lerp(Fre,1,F0);
}

real3 IndirF_Function(float NdotV,float3 F0,float roughness)
{
   float Fre=exp2((-5.55473*NdotV-6.98316)*NdotV);
   return F0+Fre*saturate(1-roughness-F0);
}

//间接光漫反射 球谐函数 光照探针
real3 SH_IndirectionDiff(float3 normalWS)
{
   real4 SHCoefficients;
   SHCoefficients=unity_SHAr;
   SHCoefficients=unity_SHAg;
   SHCoefficients=unity_SHAb;
   SHCoefficients=unity_SHBr;
   SHCoefficients=unity_SHBg;
   SHCoefficients=unity_SHBb;
   SHCoefficients=unity_SHC;
   float3 Color=SampleSH9(SHCoefficients,normalWS);
   return max(0,Color);
}

//间接光高光 反射探针
real3 IndirSpeCube(float3 normalWS,float3 viewWS,float roughness,float AO)
{
   float3 reflectDirWS=reflect(-viewWS,normalWS);
   roughness=roughness*(1.7-0.7*roughness);//Unity内部不是线性 调整下拟合曲线求近似
   float MidLevel=roughness*6;//把粗糙度remap到0-6 7个阶级 然后进行lod采样
   float4 speColor=SAMPLE_TEXTURECUBE_LOD(unity_SpecCube0, samplerunity_SpecCube0,reflectDirWS,MidLevel);//根据不同的等级进行采样
   #if !defined(UNITY_USE_NATIVE_HDR)
   return DecodeHDREnvironment(speColor,unity_SpecCube0_HDR)*AO;//用DecodeHDREnvironment将颜色从HDR编码下解码。可以看到采样出的rgbm是一个4通道的值,最后一个m存的是一个参数,解码时将前三个通道表示的颜色乘上xM^y,x和y都是由环境贴图定义的系数,存储在unity_SpecCube0_HDR这个结构中。
   #else
   return speColor.xyz*AO;
   #endif
}

//间接高光 曲线拟合 放弃LUT采样而使用曲线拟合
real3 IndirSpeFactor(float roughness,float smoothness,float3 BRDFspe,float3 F0,float NdotV)
{
   #ifdef UNITY_COLORSPACE_GAMMA
   float SurReduction=1-0.28*roughness,roughness;
   #else
   float SurReduction=1/(roughness*roughness+1);
   #endif
   #if defined(SHADER_API_GLES)//Lighting.hlsl 261行
   float Reflectivity=BRDFspe.x;
   #else
   float Reflectivity=max(max(BRDFspe.x,BRDFspe.y),BRDFspe.z);
   #endif
   half GrazingTSection=saturate(Reflectivity+smoothness);
   float Fre=Pow4(1-NdotV);//lighting.hlsl第501行
   //float Fre=exp2((-5.55473*NdotV-6.98316)*NdotV);//lighting.hlsl第501行 它是4次方 我是5次方
   return lerp(F0,GrazingTSection,Fre)*SurReduction;
}三、结果



左边是自己实现的PBR效果,右边是unity urp的lit shader的效果,可以发现自己实现的PBR效果材质明显比unity系统中自带的要”光滑“,具体的原因寻找了很久也没找到。所以以后如果基于PBR的效果实现,最好使用unity urp封装好的BRDF函数,这样子效果会更好一些。
另外,如果把lit.shader的smoothness设置为1,调整下角度可以发现效果很相近,所以猜测是要把smoothness参数与metallic map的alpha通道值结合起来,不过自己并没找到理想的结合方法。


五、主要参考

宋开心:如何在Unity中造一个PBR Shader轮子
URP管线的自学HLSL之路 第三十七篇 造一个PBR的轮子
https://learnopengl.com/PBR/Theory
The PBR Guide - Part 1 on Substance 3D Tutorials
页: [1]
查看完整版本: unity urp 12 PBR理论和简单实现