---
title: "URP的BRDF计算的疑惑，与论文中的公式不一样？"
author: "石心.Yong"
published_by: "Perrin Yong"
canonical: https://www.pystone.net/notes/uwa-answer-urp-brdf-formula-difference/
type: note
content_role: source
source_type: "source"
visibility: public
id_stability: rename-stable
source_path: "10-计算机、信息技术与工程/05-游戏图形与运行时/我的UWA问答/2021-04-23 - URP的BRDF计算的疑惑，与论文中的公式不一样？.md"
content_hash: c831b2e4fea93d3987fd8bec169afbd1b67663352de757ca1d800022af3259fa
knowledge_version: 224c990773de.5fa8af6e39fa
site_commit: 224c990773de166d23a886306577dd90379529ce
notes_commit: 5fa8af6e39fa3891d1b9b4832bfa6c4e0ecaaf0a
---
# 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

![BRDF.png](/media/e2462ead63c8f7948a3c.png)

代码里的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函数如下：

![image.png](/media/d999f6d659a7dd8ae6de.png)

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

![image.png](/media/127b94ebb48cbf77d19f.png)

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

![image.png](/media/61b63131c32322bf3e20.png)

![image.png](/media/bcb0b95a61ac0d56af70.png)

两个图像差别不大，而从走势上看，取平方似乎更最接近最初的 V*F (Modified KSK and Schlick Fresnel depend on L*H)

![image.png](/media/3c568ca67b1cf0415d78.png)

笔者修改了Lighting.hlsl的代码，使用一次方： outBRDFData.normalizationTerm = outBRDFData.perceptualRoughness* 4.0h + 2.0h;

将使用roughness(n) + 0.5 和使用roughness(n)^2 + 0.5 的材质绘制在同一个屏幕上如下，两者几乎没有差别：

![image.png](/media/da39e0794065b525a9ad.png)

根据图形学第一定律：**如果它看起来是对的，那么它就是对的**，所以理解成两个都对应该也可以。那么，是不是在差距不大的情况下就不必深究了。

- 回答时间：2021-04-23 15:15:26
- 最后更新：2021-04-23 15:24:43
- 采纳状态：已采纳

## 来源与关系

- 上位集合：[我的 UWA 问答](https://www.pystone.net/notes/my-uwa-answers/)
- UWA 标签：终端平台、渲染模块
- 原始问题：[URP的BRDF计算的疑惑，与论文中的公式不一样？](https://answer.uwa4d.com/question/607fdebf6bb31032f97913ba)
- 源页最后更新：2021-04-23 07:15:26
