一、前言

        又是鸽了好久的更新,之前写过一份基础光照,就关联度而言,这次来理理基础PBR光照。在《入门精要》第18章中,介绍了Unity自己的实现原理,由于PBR计算涉及多个组成部分,每个部分都有不同的算法实现,这次我会围绕自己的PBR实现,以金属-粗糙度工作流为主,结合书中原理进行整理。

二、基础概念

1、概述

        PBR(Physically Based Rendering,基于物理的渲染)指的是一套符合真实物理光学规律的渲染理论,并非某一个特定的算法,相较于Lambert、Phong等经验模型,PBR旨在实现光照无关、全局统一、物理可信的渲染效果。

        PBR渲染需要遵循以下核心原则:能量守恒、微表面理论、菲涅尔效应和基于物理的BRDF(双向反射分布函数)。

2、能量守恒

        能量守恒是PBR最根本的原则,即:入射光的总能量 ≥ 出射光的总能量,具体表现为以下几点:

a. 反射光与折射光(漫反射)之和不能超过入射光,光线照射到物体表面后,一部分被反射,其余部分折射进入物体内部;

b. 折射光要么被吸收,要么经历多次散射后从表面其他位置漫反射出来;

c. 物体的反射率越高,漫反射越弱。

3、微平面理论

        微平面理论提出,任何光滑表面在微观尺度上都由大量朝向各异的微小平面组成,主要表现为以下两点:

a. 粗糙度:金属-粗糙度工作流的核心参数之一,定义了微平面法线的统计分布。粗糙表面,微平面方向混乱,反射光分散,形成模糊高光;光滑表面,微平面朝向统一,反射光集中,形成清晰高光;

b. 遮蔽和阴影:粗糙表面的微平面会相互遮挡,从而影响最终光照的强度。

4、菲涅尔效应

        菲涅尔效应描述了视线与表面法线夹角(入射角)对反射率的影响,主要表现为以下两点:

a. 当视线几乎平行于表面(掠射角,即入射角接近90度)时,几乎所有材质(包括非金属)的反射率会趋近100%;当视线垂直于表面时,反射率最低;

b. 对于金属,菲涅尔反射率高且反射颜色为金属本身颜色,对于非金属(电介质),菲涅尔反射率低且反射无色。

5、基于物理的BRDF(双向反射分布函数)

        基于物理的BRDF是对以上三个原则的数学描述,通常由高光反射项和漫反射项两个部分组成,在《入门精要》中给出了以下两种理解方式:

a. 当给定入射角度后,BRDF可以给出所有出射方向上的反射和散射光线的相对分布情况;

b. 当给定观察方向后,BRDF可以给出所有入射方向到该出射方向的光线分布。

或者更直观来说,当一束光沿入射方向到达表面某点时,BRDF表示有多少部分的能量反射到了观察方向上。

三、高光反射项

        高光反射项用于描述表面反射,这部分计算中,Cook-Torrance BRDF是最常用的BRDF,在我自己的实现中,也采用了这个BRDF,主要包含以下三个核心项:法线分布函数(NDF)、几何遮蔽函数(G)和菲涅尔项(F)。

1、法线分布函数(NDF)

        法线分布函数基于微平面理论,用于描述微平面法线朝向分布,计算了多少比例的微平面将光线从入射方向反射到观察方向上,由粗糙度参数进行控制。在我的实现中,NDF采用了GGX模型(与Unity相同),GGX模型的数学表达式如下:

D = \frac{\alpha^2}{\pi((\alpha^2-1)(n\cdot h)^2+1)^2}

        其中,\alpha = roughness^2,需要注意的是roughness为线性粗糙度,通常情况下粗糙度/光滑度贴图中获得的是感知粗糙度/光滑度(感知粗糙度 = 1 - 感知光滑度),线性粗糙度为感知粗糙度的平方。

        与Beckmann、Phong等NDF模型相比,GGX拥有以下几点优势:

a. 更长的尾部:在掠射角或粗糙表面附近,GGX 的高光衰减非常缓慢,产生一个从高光中心向外逐渐扩散的大范围“光晕”或“光尘”效果,更符合真实世界的测量数据

b. 更自然的高光过渡:高光形状不再是简单的椭圆形,而是朝边缘拉伸,并且在粗糙金属或塑料上表现出更加柔和、真实的焦散样外观

c. 能量保持较好:配合合适的几何函数(如 Smith-GGX),能很好地满足能量守恒

d. 计算相对高效:公式简洁,无超越函数,适合实时渲染

2、几何遮蔽项(G)

        几何遮蔽项也基于微平面理论,描述微平面间的相互遮蔽,计算了那些满足能将入射光反射到观察方向的微平面中有多少比例不会被遮蔽,由粗糙度参数控制。由于NDF中采用了GGX模型,这里选择采用GGX模型衍生的Smith-Schlick模型进行计算,数学表达式如下:

GGX_v=\frac{n\cdot v}{(n\cdot v)*(1-k)+k}

GGX_l=\frac{n\cdot l}{(n\cdot l) *(1-k)+k}

G = GGX_v*GGX_l

其中,在使用IBL光照(基于图像的照明)时,通常使用 k=\frac{roughness^2}{2};而在直接光照下,通常会使用 k=\frac{roughness^2+1}{8} 进行计算。

        Smith-Schlick模型是对Smith-GGX模型模型的Schlick近似,物理准确性稍逊于完整Smith模型,但性能高效。

3、菲涅尔项(F)

        菲涅尔项用于描述菲涅尔效应,由金属度和基础反射率(F0,即垂直入射的反射率)控制,这里选择采用Schlick菲涅尔近似等式进行计算,数学表达式如下:

F_0=lerp(0.04, albedo, metallic)

F=F_0+(1-F_0)*(1-v\cdot h)^5

        其中对于非金属,F_0 为标量灰度值,通常取0.04;对于金属,F_0 则为金属本身颜色,因此采用金属度对0.04和物体本身颜色进行插值计算。 

        Schlick近似物理准确性稍低于基于折射率的菲涅尔方程,但性能更高效且算式表达统一。

4、Cook-Torrance BRDF

        根据上文计算得到的三个核心项,我们对其进行统合,得到Cook-Torrance的高光反射项, 其数学表达式如下:

BRDF_{specular} =\frac{D\cdot F\cdot G}{4(n\cdot l)(n\cdot v)}

        至此,PBR中的高光反射项计算完毕。

四、漫反射项

        漫反射项用于描述次表面散射,这部分计算中,Cook-Torrance原始算法中采用了简单的Lambert模型,但更加常见的会选择使用Disney模型,Unity就采用了该模型作为漫反射项。

1、Lambert模型

        Lambert模型算式非常简单,其数学表达式如下:

BRDF_{diffuse} =\frac{albedo}{\pi}

        该式子实际是一个定值,公式中需要除以\pi,是因为我们假设漫反射在所有方向上的强度都是相同的,而BRDF要求在半球内的积分值为1。

2、Disney模型

        Disney模型相较于Lambert模型,在以下两方面修正了Lambert漫反射不随粗糙度和角度变化的缺陷:

a. 引入双向菲涅尔因子:同时考虑光照入射角和视线出射角,模拟掠角时光线折射减少导致的漫反射变化;

b. 粗糙度参与漫反射计算:对于光滑材质,掠射角漫反射减弱;对于粗糙材质,掠射角漫反射增强,微平面朝向随机,边缘不会发黑。

        Disney模型的数学表达式如下:

F_{D90} = 0.5+2roughness(h\cdot l)^2BRDF_{diffuse} = \frac{albedo}{\pi}(1+(F_{D90}-1)(1-n\cdot l)^5)(1+(F_{D90}-1)(1-n\cdot v)^5)

        其中,F_{D90} 为90度掠射角处的漫反射菲涅尔系数,和粗糙度强绑定。

3、改进型Disney模型

        在寒霜引擎的PBR实践这篇文章中,作者对寒霜引擎发表的论文进行了讲解,其中就提到了寒霜引擎对于Disney模型的改进,文章中通过分析其半球方向上的反射数据发现Disney模型的最终反射值高于1,也就是说无法遵循能量守恒,于是寒霜对其进行了简单的修改,使其保留原有反射下对结果进行归一化,使反射能量接近1,我的实现中就采用了这套改进后的模型。修正后的数学表达式如下:

E_{b} = lerp(0, 0.5, roughness)

E_{f} = lerp(1,\frac{1}{1.51},roughness)

F_{D90} = E_{b}+2roughness(h\cdot l)^2

BRDF_{diffuse} = E_{f}(1+(F_{D90}-1)(1-n\cdot l)^5)(1+(F_{D90}-1)(1-n\cdot v)^5)

        其中,E_b 是基于粗糙度对90度掠射角漫反射的能量偏置,降低光滑表面的掠射角漫反射,E_f 是对反射能量的归一化,降低粗糙表面的散射,1.51是基于测量数据的经验值,整体上就是将高粗糙度下的Disney反射能量拉回能量守恒范围。

五、HLSL实现

1、高光反射项

a. 法线分布函数

//GGX法线分布函数
float NDF_GGX(float NdotH, float linearRoughness)
{
    float a = linearRoughness * linearRoughness;
    float a2 = a * a;
    float NdotH2 = NdotH * NdotH;
    float denom = NdotH2 * (a2 - 1) + 1;
    denom = PI * denom * denom;
    
    return a2 / denom;
}

b. Smith-Schlick几何遮蔽项

//Smith-Schlick几何遮蔽
float Geometry_SchlickGGX(float NdotX, float k)
{
    float nom = NdotX;
    float denom = NdotX * (1 - k) + k;
    
    return nom / max(denom, EPSILON);
}

float Geometry_Smith(float NdotV, float NdotL, float k)
{
    float ggx_v = Geometry_SchlickGGX(NdotV, k);
    float ggx_l = Geometry_SchlickGGX(NdotL, k);
    
    return ggx_v * ggx_l;
}

c. 菲涅尔项

//辅助函数
float Pow5(float a)
{
    float a2 = a * a;
    return a2 * a2 * a;
}

//菲涅尔项
float3 Fresnel_Schlick(float3 F0, float VdotH)
{
    return F0 + (1 - F0) * Pow5(1 - VdotH);
}

d. Cook-Torrance BRDF

//本方法只是对Shader中的计算进行统括,存在上下文问题,需根据实际情况修改
float3 CookTorranceSpecularBRDF()
{
    //菲涅尔项
    float3 F = Fresnel_SchlickFrostbite(F0, VdotH, linearRoughness);

    //法线分布函数
    float D = NDF_GGX(NdotH, roughness);

    //几何遮蔽项
    float k = (1 + roughness) * (1 + roughness) / 8;//直接光k项
    float G = Geometry_Smith(NdotV, NdotL, k);

    //Cook-Torrance BRDF
    float3 nominator = F * D * G;
    float denominator = 4 * NdotV * NdotL + 0.001; //0.001防止分母为0
    float3 specularBRDF = nominator / denominator;

    return specularBRDF;
}

2、漫反射项

a. Frostbite-Disney漫反射

float DisneyDiffuseFrostbite(float NdotV, float NdotL, float LdotH, float roughness)
{
    float energyBias = lerp(0, 0.5, roughness);
    float energyFactor = lerp(1, 1 / 1.51, roughness);
    
    float fd90 = energyBias + 2 * LdotH * LdotH * roughness;
    
    float lightScatter = 1 + (fd90 - 1) * Pow5(1 - NdotL);
    float viewScatter = 1 + (fd90 - 1) * Pow5(1 - NdotV);
    
    return lightScatter * viewScatter * energyFactor;
}

b. 漫反射BRDF

//本方法只是对Shader中的计算进行统括,存在上下文问题,需根据实际情况修改
float3 DiffuseBRDF()
{
    float diffuseFactor = DisneyDiffuseFrostbite(NdotV, NdotL, LdotH, roughness);

    //菲涅尔越强,漫反射越弱;金属度越高,漫反射越弱
    //F为高光反射中的菲涅尔项
    float3 kD = (1 - F) * (1 - metallic) * diffuseFactor;

    //物体自身颜色影响、半球积分归一化
    float3 diffuseBRDF = kD * albedo / PI;
}

3、最终颜色

//本方法只是对Shader中的计算进行统括,存在上下文问题,需根据实际情况修改
float3 FinalColor()
{
    float3 diffuseBRDF = DiffuseBRDF();
    float3 specularBRDF = CookTorranceSpecularBRDF();

    float3 lightInfluence = light.color * NdotL;
    
    float3 finalColor = (diffuseBRDF + specularBRDF)*lightInfluence;

    return finalColor;
}

4、效果图

*不知道为什么图片传不上来,后面再更新试试……

六、总结

        本文对PBR高光反射项和漫反射项中常用的BRDF模型进行整理和介绍。当然,仅仅靠本文内容是不够的,还是无法在Unity中完成PBR渲染,毕竟目前还没有加上环境光和自发光,后面会找时间再开一篇文章完整把PBR的渲染流程贴上来。如果文章中有什么错误,还请各位大佬多多指出。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐