PBR理论

【基于物理的渲染(PBR)白皮书】(一) 开篇:PBR核心知识体系总结与概览

如何在Unity中造一个PBR Shader轮子

【Free Bird/URP教学】10.PBR渲染

学习PRB路程(一、核心理论)

LearnOpenGL-PBR

BRDF

Cook-Torrance BRDF的镜面反射部分包含三个函数,此外分母部分还有一个标准化因子 。三个函数分别为法线分布函数(Normal Distribution Function)菲涅尔方程(Fresnel Rquation)几何函数(Geometry Function)

  • 法线分布函数:估算在受到表面粗糙度的影响下,取向方向与中间向量一致的微平面的数量。这是用来估算微平面的主要函数。
  • 几何函数:描述了微平面自成阴影的属性。当一个平面相对比较粗糙的时候,平面表面上的微平面有可能挡住其他的微平面从而减少表面所反射的光线。
  • 菲涅尔方程:菲涅尔方程描述的是在不同的表面角下表面所反射的光线所占的比率。

法线分布函数

h表示半视角向量normalize(V + L)n表示法线向量,α表示粗糙度。

不同粗糙度的视觉效果
float D_GGX_TR(float3 N, float3 H,float a) 
{
    float a2 = a * a;
    float NdotH  = max(dot(N, H), 0.0);
    float NdotH2 = NdotH * NdotH;

    float nom = a2;
    float denom  = (NdotH2 * (a2 - 1.0) + 1.0);
    denom = PI * denom * denom;

    return nom / denom;
}

几何函数

为了有效的估算几何部分,需要将观察方向(几何遮蔽(Geometry Obstruction))和光线方向向量(几何阴影(Geometry Shadowing))都考虑进去。我们可以使用史密斯法(Smith’s method)来把两者都纳入其中:

不同粗糙度的视觉效果
float GeometrySchlickGGX(float NdotV, float k)
{
    float nom = NdotV;
    float denom = NdotV * (1.0 - k) + k;
    return nom / denom;
}

float GeometrySmith(float3 N, flaot3 V, float3 L, float k)
{
    float NdotV = max(dot(N, V), 0.0);
    float NdotL = max(dot(N, L), 0.0);
    float ggx1 = GeometrySchlickGGX(NdotV);
    float ggx2 = GeometrySchlickGGX(NdotL);
    return ggx1 * ggx2;
}

菲涅尔方程

直接光照

float3 FresnelSchick(float cosTheta, float3 F0)
{
    //return Pow4(1 - NdotV);
    //return exp2((-5.55473 * NdotV - 6.98316) * NdotV);
    return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}
 half3 F0 = half3(1.00, 0.71, 0.29);
 F0 = lerp(F0,_MainColor,_Metalness);
 half3 F = FresnelSchick(NdotH,F0);

F0表示平面的基础反射率,_Metalness表示光泽度。、

间接光照

间接光通过采样SH的系数,主要是反映了环境贴图的结果,接着和直接光计算方法相一致,但是在粗糙度方面为了匹配mipmap,进行了修饰。同时在直接光的ks是菲涅尔项,但是在间接光需要单独算出间接光的ks。

float3 IndirFresnelSchlick(float cosTheta, float3 F0, float roughness)
{
   return F0 + (max(float3(1, 1, 1) * (1 - roughness), F0) - F0) * pow(1.0 - cosTheta, 5.0);
}

直接光

高光

half3 directSpecular = D*G*F/(4.0*NdotL*NdotV);

漫反射

half3 kS = F;
half3 kD = (1-kS)*(1-Metallic);
half3 directDiffuse = kD*Albedo*lightColor*NdotL;

间接光

高光

我们先计算反射射线的方向对cube进行采样。我们还需要根据不同的粗糙度进行采样不同的模糊度,粗糙度需要修改一下曲线去拟合,它并非线性;模糊度从0-6总共7个模糊等级。

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

我们还需要计算一个间接高光的影响因子,一般来说是按照一张现成的LUT图像进行采样,采样UV的U方向为NdotV,V方向为粗糙度来得到我们需要的影响因子。

但是采样贴图的过程非常耗,unity使用的曲线拟合去得到结果。

//间接高光 曲线拟合 放弃LUT采样而使用曲线拟合
half3 IndirSpecularFactor(half roughness,half smoothness,half3 BRDFSpecular,half3 F0,half NdotV)
{
    #ifdef UNITY_COLORSPACE_GAMMA
    half surReduction = 1 - 0.28 * roughness * roughness;
    #else
    half surReduction = 1 / (roughness * roughness + 1);
    #endif
    //Lighting.hlsl 274行
    #if defined(SHADER_API_GLES)
    half reflectivity = BRDFSpecular.r;
    #else
    half reflectivity = max(max(BRDFSpecular.r, BRDFSpecular.g), BRDFSpecular.b);
    #endif
    half grazingTSection = saturate(reflectivity + smoothness);
    //lighting.hlsl第512行
    half fresnel = Pow4(1 - NdotV);
    return lerp(F0, grazingTSection, fresnel) * surReduction;
}
//间接光高光
half3 indirSpecularCubeColor = IndirSpecularCube(N,V,Roughness,Ao);
half3 indirSpecularCubeFactor = IndirSpecularFactor(Roughness,Smoothness,directSpecular,F0,NdotV);
half3 indirSpecularColor = indirSpecularCubeColor * indirSpecularCubeFactor;

漫反射

基于图像的光照(Image based lighting, IBL)是一类光照技术的集合。将周围环境整体视为一个大光源。IBL 通常使用(取自现实世界或从3D场景生成的)环境立方体贴图 (Cubemap) ,我们可以将立方体贴图的每个像素视为光源,在渲染方程中直接使用它。这种方式可以有效地捕捉环境的全局光照和氛围,使物体更好地融入其环境。

将每个方向上贴图进行采样卷积得到图片右边的效果,Unity已经帮我们把cubemap的数据处理好存起来了。

// SH lighting environment
half4 unity_SHAr;
half4 unity_SHAg;
half4 unity_SHAb;
half4 unity_SHBr;
half4 unity_SHBg;
half4 unity_SHBb;
half4 unity_SHC;
float3 SH_IndirectionDiffuse(float3 normalWS)
{
    float4 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;
    float3 color=SampleSH9(SHCoefficients,normalWS);
    return max(0,color);
}
half3 shColor = SH_IndirectionDiffuse(N)*Ao;
half3 indirKS = IndirFresnelSchlick(NdotV,F0,Roughness);
half3 indirKD = (1-indirKS)*(1-Metallic);
half3 indirDiffColor = shColor*indirKD*Albedo;

代码Github:

[github repo="acgloby/LearnURP"]

最后更新于 2024-01-13