URP-Lit-BRDF简析

前述

之前理清了lit包含的各个文件之间的联系,现在再回过头仔细看看到底它用的BRDF是什么。

最终颜色分为两类
实时光与间接光

实时光

指平行光或者点光源等

反射率方程

先来看主光源的反射率方程

half NdotL = saturate(dot(normalWS, lightDirectionWS));
计算cosTheta
half3 radiance = lightColor * (lightAttenuation * NdotL);
因为是平行光,仅计算一个方向,所以就简单的相乘即可
half3 brdf = brdfData.diffuse;
brdf += brdfData.specular * DirectBRDFSpecular(brdfData, normalWS, lightDirectionWS, viewDirectionWS);
然后就是BRDF了,分为两部分,漫反射与高光。

实时光漫反射

回过头看看漫反射怎么取的
InitializeBRDFData(surfaceData.albedo, surfaceData.metallic, surfaceData.smoothness, brdfData);
输入仅有取自表面的反照率,金属,光滑

half oneMinusReflectivity = OneMinusReflectivityMetallic(metallic);

这里用到了一个魔法数 标准介电反射率系数 (= 4%)
OneMinusReflectivityMetallic 可以理解为吸收率? 对应与learnopengl的PBR那篇文章中的 被折射 的比例kd。
一减去反射率 = 1-reflectivity = 1-lerp(dielectricSpec, 1, metallic) = lerp(1-dielectricSpec, 0, metallic)。
理解为金属的反射率为1,非金属的反射率为0.04

half reflectivity = half(1.0) - oneMinusReflectivity;

取反射率

half3 brdfDiffuse = albedo * oneMinusReflectivity;

从贴图的albedo乘以吸收率得到Diffuse,即最终漫反射的颜色。 这里就是Lambertian漫反射的公式了。又被称为完全漫反射即漫反射的方向与角度无关。

实时光高光

half3 brdfSpecular = lerp(kDieletricSpec.rgb, albedo, metallic);
这里就是高光的颜色了,指即使是非金属,也会有0.04的反射高光,而金属的反射颜色就取自albedo。

outBRDFData.perceptualRoughness = PerceptualSmoothnessToPerceptualRoughness(smoothness);
outBRDFData.roughness           = max(PerceptualRoughnessToRoughness(outBRDFData.perceptualRoughness), HALF_MIN_SQRT);
outBRDFData.roughness2          = max(outBRDFData.roughness * outBRDFData.roughness, HALF_MIN);
outBRDFData.grazingTerm         = saturate(smoothness + reflectivity);
outBRDFData.normalizationTerm   = outBRDFData.roughness * half(4.0) + half(2.0);
outBRDFData.roughness2MinusOne  = outBRDFData.roughness2 - half(1.0);

最后这里是为了之后的计算方便,提前计算了很多变量。
逐行看
perceptualRoughness 计算感知粗糙度(这个名字很奇怪) 1-smoothness。
然后是真正使用的粗糙度

real PerceptualRoughnessToRoughness(real perceptualRoughness)
{
    return perceptualRoughness * perceptualRoughness;
}

并不理解为什么这里要刻意降低粗糙度。

下面都是没有具体含义的仅为了优化计算的项了。

回到反射率方程

可以看到漫反射是贴图的albedo乘以吸收率得到Diffuse,吸收率仅与金属度相关。所以光滑度不影响漫反射。

然后brdfSpecular高光颜色近似理解为albedo * metallic。

BRDF

然后终于到了熟悉的Cook-Torrance BRDF的镜面反射部分了。即D,F与G函数。

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

而具体的DFG函数采用哪一种,不同的引擎和管线有不同的选择。看看Untiy的URPLit用的是哪一种吧
Unity的注射中将F函数命名为V即可见性。

D采用的是GGX。即D = roughness^2 / ( NoH^2 * (roughness^2 - 1) + 1 )^2。

但是F与G就采用了一种近似的优化方程。
V * F = 1.0 / ( LoH^2 * (roughness + 0.5) )
来源https://community.arm.com/events/1155

间接光

实时光看完了
再来看看环境光相关。

间接光漫反射

环境光照的漫反射直接取Lightmap或者SH 也是用的完全漫反射
half3 indirectDiffuse = bakedGI;

间接光镜面反射

环境光照的镜面反射颜色要根据反射向量,粗糙度,屏幕UV计算
half3 indirectSpecular = GlossyEnvironmentReflection(reflectVector, positionWS, brdfData.perceptualRoughness, 1.0h, normalizedScreenSpaceUV);

half mip = PerceptualRoughnessToMipmapLevel(perceptualRoughness);
从粗糙的映射出一个mip等级

half4 encodedIrradiance = half4(SAMPLE_TEXTURECUBE_LOD(unity_SpecCube0, samplerunity_SpecCube0, reflectVector, mip));

irradiance = DecodeHDREnvironment(encodedIrradiance, unity_SpecCube0_HDR);
然后用反射向量采用采样

return irradiance * occlusion;
最后还要计算遮蔽

half3 c = indirectDiffuse * brdfData.diffuse;

这里看到indirectDiffuse仅包含了光照的强度
    half3 EnvironmentBRDFSpecular(BRDFData brdfData, half fresnelTerm)
{
    float surfaceReduction = 1.0 / (brdfData.roughness2 + 1.0);
    return half3(surfaceReduction * lerp(brdfData.specular, brdfData.grazingTerm, fresnelTerm));
}

依据粗糙度和菲涅尔调整高光。

总结

URP的lit为了性能做了很多优化,BRDF也是用的简化的版本。看来有必要去看看HDRP中的Lit的实现了。


URP-Lit-BRDF简析
https://www.kuanmi.top/2023/06/26/URP-Lit-BRDF/
作者
KuanMi
发布于
2023年6月27日
许可协议