URP的BRDF计算的疑惑,与论文中的公式不一样?
本文目录 3 个章节
URP的BRDF计算的疑惑,与论文中的公式不一样?
note · 我的 UWA 问答 本文收录我在 UWA 问答发表的公开回答。问题内容归原提问者;回答保留当时语境,技术结论可能受 Unity 版本、平台和项目条件限制。
问题
最近在看URP shader的时候发现个问题,不知道是不是我理解的不对
首先贴代码
先是URP的Lighting.hlsl里初始化BRDF的部分
inline void InitializeBRDFData(half3 albedo, half metallic, half3 specular, half smoothness, half alpha, out BRDFData outBRDFData)
{
#ifdef _SPECULAR_SETUP
half reflectivity = ReflectivitySpecular(specular);
half oneMinusReflectivity = 1.0 - reflectivity;
outBRDFData.diffuse = albedo * (half3(1.0h, 1.0h, 1.0h) - specular);
outBRDFData.specular = specular;
#else
half oneMinusReflectivity = OneMinusReflectivityMetallic(metallic);
half reflectivity = 1.0 - oneMinusReflectivity;
outBRDFData.diffuse = albedo * oneMinusReflectivity;
outBRDFData.specular = lerp(kDieletricSpec.rgb, albedo, metallic);
#endif
outBRDFData.grazingTerm = saturate(smoothness + reflectivity);
outBRDFData.perceptualRoughness = PerceptualSmoothnessToPerceptualRoughness(smoothness);
outBRDFData.roughness = max(PerceptualRoughnessToRoughness(outBRDFData.perceptualRoughness), HALF_MIN);
outBRDFData.roughness2 = outBRDFData.roughness * outBRDFData.roughness;
outBRDFData.normalizationTerm = outBRDFData.roughness * 4.0h + 2.0h;
outBRDFData.roughness2MinusOne = outBRDFData.roughness2 - 1.0h;
#ifdef _ALPHAPREMULTIPLY_ON
outBRDFData.diffuse *= alpha;
alpha = alpha * oneMinusReflectivity + reflectivity;
#endif
}
按照我的理解
outBRDFData.perceptualRoughness 是我们说的粗糙度
outBRDFData.roughness 是粗糙度的平方
outBRDFData.roughness2 是粗糙度的4次方
再看URP的Lighting.hlsl里计算BRDF的部分
// Based on Minimalist CookTorrance BRDF
// Implementation is slightly different from original derivation: http://www.thetenthplanet.de/archives/255
//
// * NDF [Modified] GGX
// * Modified Kelemen and Szirmay-Kalos for Visibility term
// * Fresnel approximated with 1/LdotH
half3 DirectBDRF(BRDFData brdfData, half3 normalWS, half3 lightDirectionWS, half3 viewDirectionWS)
{
#ifndef _SPECULARHIGHLIGHTS_OFF
float3 halfDir = SafeNormalize(float3(lightDirectionWS) + float3(viewDirectionWS));
float NoH = saturate(dot(normalWS, halfDir));
half LoH = saturate(dot(lightDirectionWS, halfDir));
// GGX Distribution multiplied by combined approximation of Visibility and Fresnel
// BRDFspec = (D * V * F) / 4.0
// D = roughness^2 / ( NoH^2 * (roughness^2 - 1) + 1 )^2
// V * F = 1.0 / ( LoH^2 * (roughness + 0.5) )
// See "Optimizing PBR for Mobile" from Siggraph 2015 moving mobile graphics course
// https://community.arm.com/events/1155
// Final BRDFspec = roughness^2 / ( NoH^2 * (roughness^2 - 1) + 1 )^2 * (LoH^2 * (roughness + 0.5) * 4.0)
// We further optimize a few light invariant terms
// brdfData.normalizationTerm = (roughness + 0.5) * 4.0 rewritten as roughness * 4.0 + 2.0 to a fit a MAD.
float d = NoH * NoH * brdfData.roughness2MinusOne + 1.00001f;
half LoH2 = LoH * LoH;
half specularTerm = brdfData.roughness2 / ((d * d) * max(0.1h, LoH2) * brdfData.normalizationTerm);
// On platforms where half actually means something, the denominator has a risk of overflow
// clamp below was added specifically to "fix" that, but dx compiler (we convert bytecode to metal/gles)
// sees that specularTerm have only non-negative terms, so it skips max(0,..) in clamp (leaving only min(100,...))
#if defined (SHADER_API_MOBILE) || defined (SHADER_API_SWITCH)
specularTerm = specularTerm - HALF_MIN;
specularTerm = clamp(specularTerm, 0.0, 100.0); // Prevent FP16 overflow on mobiles
#endif
half3 color = specularTerm * brdfData.specular + brdfData.diffuse;
return color;
#else
return brdfData.diffuse;
#endif
}
这里最终的公式是BRDFspec = roughness2 / ( NoH2 *(roughness**2 - 1) + 1 )*2 (LoH^2 (roughness + 0.5) 4.0)
然后看unity 2015年的论文,也就是代码里写的那个地址https://community.arm.com/events/1155

代码里的roughness实际上是图里roughness的平方,为了防止混淆,下面用roughness(c)和roughness(n)来表示
roughness(c)=roughness(n)^2
看到这里我产生了疑问,论文里roughness(n) + 0.5这一步,对应到代码里,应该是roughness(c)^0.5+0.5才对,而URP代码里直接用了roughness(c)+0.5。
我想问下是我理解的不对,还是URP的shader写错了。
- 提问者:金喆
- 提问时间:2021-04-22 00:13:51
我的回答
讲一下不太成熟的看法哈。我这边的结论是:
- 用平方(roughness(n)^2)加0.5是合理的;
- 用一次方或者是平方差别不大。
SIGGRAPH 报告中最初V*F函数如下:

实际使用的V*F函数是对上述函数的一个近似,使用图像相近的函数进行优化:

笔者对取一次放和取平方的函数图像进行了绘制如下:


两个图像差别不大,而从走势上看,取平方似乎更最接近最初的 VF (Modified KSK and Schlick Fresnel depend on LH)

笔者修改了Lighting.hlsl的代码,使用一次方: outBRDFData.normalizationTerm = outBRDFData.perceptualRoughness* 4.0h + 2.0h;
将使用roughness(n) + 0.5 和使用roughness(n)^2 + 0.5 的材质绘制在同一个屏幕上如下,两者几乎没有差别:

根据图形学第一定律:如果它看起来是对的,那么它就是对的,所以理解成两个都对应该也可以。那么,是不是在差距不大的情况下就不必深究了。
- 回答时间:2021-04-23 15:15:26
- 最后更新:2021-04-23 15:24:43
- 采纳状态:已采纳
来源与关系
- 上位集合:我的 UWA 问答
- UWA 标签:终端平台、渲染模块
- 原始问题:URP的BRDF计算的疑惑,与论文中的公式不一样?
- 源页最后更新:2021-04-23 07:15:26