造一个PBR轮子


PBR理论

完全理解了PBR之后就感觉发明出来这个东西的人真的是天才,之前广泛应用的Blinn-Phone仅仅只是经验总结的光照模型,并不是物理上的准确。PBR的思路就是完全按照渲染方程来进行光照的计算,然后用贴图对BRDF的DFG项进行控制,即通过漫反射、法线、金属度等多种贴图计算出实时的阴影,实时的高光,实时的反射,实时的菲涅尔效应,只要按照测量得到的BRDF数据,就可以得到正确的物理上正确的光照结果。

这个流程对美术也是友好的,只需提供合理的贴图,贴图提供的参数合理就可以。一般情况下会直接使用测量出来的BRDF值。

这里就基于自己的理解总结一下,然后仿照standard shader的源码仿造一个pbr轮子,基于金属度-粗糙度工作流。

主要就是要实现这个式子:

PBR的效果可以分为直接光照和间接光照,直接光照和间接光照都能再分为漫反射和镜面反射两个部分。

直接光照

直接光照分为镜面反射和漫反射两个部分

对于漫反射部分,可以使用Lambert模型,或者整体上更亮一些的Disney漫反射模型。镜面反射的部分可以用另外三个式子决定:微平面分布函数、几何遮蔽函数、菲涅尔函数。

镜面高光在光滑的物体表面出现,当物体越粗糙时,镜面高光的颜色就更加平均,越光滑时,就越倾向于变成一个高光点。几何遮蔽在运算中会和微平面函数相乘,它的作用是限制高光光点出现的位置。

菲涅尔由金属度参数控制,金属度越强,反射越强烈,而且反射的光线会带有金属的本身的颜色。当金属度越低时,反射越弱,反射的颜色就越倾向于环境本身的颜色。区分金属和非金属特性的一个重要的视觉差异就是看反光是否带有物体本身的颜色,金属的反光会带有金属本身的颜色,而非金属物体的反光并不会带有物体颜色。

对于分母的4 * vn * ln,试了一下它的作用就是做一个柔和化处理,让非常亮的部分变暗一点,然后让非常暗的部分变亮一点。

总结:

在金属粗糙度这个流程的直接光照部分,粗糙度的作用是控制高光的范围,金属度的作用是控制高光是否带有材质本身的颜色,非金属的物体反射的高光是白色的,而金属物体的高光点会带有物体本身的颜色。

间接光照

间接光照也分成直接漫反射和镜面反射两个部分。

间接光漫反射本质上是对光照探针进行采样,光照探针里存了环境的光照信息,unity内部就是用球谐函数去进行计算。球谐光照的的作用是为非金属的材质添加间接光照,它只受到金属度这一个参数的影响,当物体是金属时,不受到漫反射间接光照(球谐光照)的影响。

间接光照的镜面反射是采样了一张立方体贴图,这张立方体贴图把周边的环境记录下来,然后再作为反射信息显示到物体的表面。因为实时渲染中如果真的去求间接光照的积分,耗能实在是太高了。

镜面反射的显示收到两个参数的影响:Metallic和Roughness 。

Metallic 数值越高,镜面反射会带有物体本身的颜色。金属物体反射的光线会带有金属本身的颜色,而非金属物体不论本身是什么颜色,它们反射的光线都是光线原本的颜色。Roughness则控制CubeMap的Mip等级,Mip等级越高,贴图就越模糊。

总结:

金属度的作用:1.控制漫反射和镜面反射的比例;2.控制反射颜色和材质本身颜色相乘;

粗糙度的作用:1.控制直接光照高光点的扩散;2.控制CubeMap的模糊程度;3.控制镜面反射颜色的强度;

PBR实现

直接光照

计算镜面反射项和漫反射项的相关函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//Geometry function
half SmithJointGGX(half nl, half nv, half roughness)
{//在我的理解里,镜面反射的分母(n·l)(n·v)就是对可见性影响做的一个补充,直接把它归类在几何遮蔽函数里一起算了
half ag = roughness * roughness;
half lambdaV = nl * (nv * (1 - ag) + ag);
half lambdaL = nv * (nl * (1 - ag) + ag);

return 0.25f/(lambdaV + lambdaL + 1e-5f);
}

//Normal Distribution Function
half NormalDistribution(half nh,half roughness)
{
half a = roughness * roughness;
half a2 = a * a;
half d = (a2 - 1.0f) * nh * nh + 1.0f;
return a2 * UNITY_INV_PI / (d * d + 1e-5f);
}

//Fresnel effect
half3 ComputeFresnelTerm(half3 F0,half cosA)
{
return F0 + (1 - F0) * pow(1 - cosA, 5);
}

half3 ComputeDisneyDiffuseTerm(half nv,half nl,half lh,half roughness,half3 baseColor)
{
half Fd90 = 0.5f + 2 * roughness * lh * lh;
return baseColor * UNITY_INV_PI * (1 + (Fd90 - 1) * pow(1-nl,5)) * (1 + (Fd90 - 1) * pow(1-nv,5));
}

计算镜面反射和漫反射的比例:

1
2
3
half3 specColor = lerp(unity_ColorSpaceDielectricSpec.rgb,albedo,metallic);
half oneMinusReflectivity = (1- metallic) * unity_ColorSpaceDielectricSpec.a;
half3 diffColor = albedo * oneMinusReflectivity;

然后直接计算好各项加起来就行:

1
2
3
4
5
6
7
8
9
10
half G = SmithJointGGX(nl,nv,roughness);
half D = NormalDistribution(nh,roughness);
half3 F = ComputeFresnelTerm(specColor,vh);
half3 specularTerm = F * D * G;

half3 diffuseTerm = ComputeDisneyDiffuseTerm(nv,nl,lh,roughness,diffColor);

half3 color = UNITY_PI * (diffuseTerm + specularTerm) * _LightColor0.rgb * nl;

return half4(color, 1.0);

下面是没有添加间接光照的效果:

间接光照

首先在顶点数据中新增两个texcoord,用于计算动态和静态光照贴图。

在计算间接光漫反射时,要判断物体是否是动态物体,如果是动态物体,则计算非重要的光源(包含顶点光照和球谐光照,光照探头存储的光照数据在球谐光照中),对于静态物体,则是对其进行计算采样静态和动态光照贴图的采样坐标,然后在片元函数中进行采样。


Author: cdc
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source cdc !
  TOC