在URP中的BRDF计算公式问题

在URP中的BRDF计算公式问题

1)在URP中的BRDF计算公式问题
​2)Job System占用主线程时间问题
3)在Profiler中定位TempBuffer问题
4)Unity 2019使用HDRP的摄像机的GL画线问题
5)Unity WebCamTexture获取到的相机图片被旋转


这是第248篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题,建议阅读时间10分钟,认真读完必有收获。

UWA 问答社区:answer.uwa4d.com
UWA QQ群2:793972859(原群已满员)

Shader

Q:最近在看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=roughness^2/( NoH^2*(roughness^2-1)+1)^2*(LoH^2*(roughness+0.5)*4.0)

然后看Unity 2015年的论文,也就是代码里写的那个地址:
https://community.arm.com/events/1155

代码里的roughness实际上是图里roughness的平方,为了防止混淆,下面用roughness©和roughness(n)来表示:
roughness©=roughness(n)^2

看到这里我产生了疑问,论文里roughness(n)+0.5这一步,对应到代码里,应该是roughness©^0.5+0.5才对,而URP代码里直接用了roughness©+0.5。我想问下是我理解的不对,还是URP的Shader写错了?

A:讲一下不太成熟的看法。我这边的结论是:

  • 用平方(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的材质绘制在同一个屏幕上如下,两者几乎没有差别:

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

感谢Prin@UWA问答社区提供了回答,欢迎大家转至社区交流:
https://answer.uwa4d.com/question/607fdebf6bb31032f97913ba


Script

Q:如图,为什么放到Job System去执行还会占用主线程的时间?

A:等待子线程执行完毕。

感谢Walker-523103@UWA问答社区提供了回答,欢迎大家转至社区交流:
https://answer.uwa4d.com/question/607ebad56bb31032f97913b0


Rendering

Q:如图所示,在一个场景里两个TempBuffer,不是从C#脚本上创建产生的。场景没有使用后效,没有使用Grab Pass的Shader,场景摄像机都关掉了MSAA和HDR,仍然有这两个TempBuffer。请问如何完全去掉?

好像和这两个东西有关:

A1:设置一下这个试试:Camera.forceIntoRenderTexture = false;
这个选项会强制将相机渲染到TempBuffer上。

在不使用后效时手动设置为false,开启后效会自动设为true。所以如果使用了后效,则TempBuffer是不可避免的了。

感谢范世青@UWA问答社区提供了回答

A2:屏蔽掉所有MonoBehavior的OnRenderImage(RenderTexture source, RenderTexture destination)函数,一般来说每个OnRenderImage(RenderTexture source, RenderTexture destination)函数都会产生一个TempBuffer。

感谢李星@UWA问答社区提供了回答

A3:Grab RenderTexture一般是Depth最小的相机的Clear Flags设置为Depth Only的时候会出现。

感谢Xuan@UWA问答社区提供了回答,欢迎大家转至社区交流:
https://answer.uwa4d.com/question/6079c4c26bb31032f9791362


Rendering

Q:在Unity 2019.3.0f6使用GL画线在Game视图下摄像机找不到,项目是属于HDRP工程。

A1:把你GL绘制的放到GLDraws()中,我在Unity 2020.2.2f1c1中,HDRP10.2.2环境下已经成功绘制出来效果:

protected void OnEnable()

{

if (GraphicsSettings.renderPipelineAsset != null)

RenderPipelineManager.endFrameRendering += OnCameraRender;

}
protected void OnDisable()
{
    if (GraphicsSettings.renderPipelineAsset != null)
        RenderPipelineManager.endFrameRendering -= OnCameraRender;
}

protected void OnCameraRender(ScriptableRenderContext context, Camera[] cameraList)
{
    foreach(var camera in cameraList)
    {
        if (CheckFilter(camera))
            GLDraws();
    }
}

感谢pp@UWA问答社区提供了回答,欢迎大家转至社区交流:
https://answer.uwa4d.com/question/5f9296c15d9aea5aa97197d1


Script

Q:从Unity WebCamTexture获取到的相机图片被旋转了,我看微信的扫一扫,画面都是对的,而且手机旋转的时候,画面也一直是正的,没有看到图片有旋转的痕迹。手机竖屏的时候是正常的,旋转到横屏,画面就不对了。

A:可以在屏幕旋转的时候按下面这样的方式调整一下用于显示的RawImage的旋转:

感谢羽飞@UWA问答社区提供了回答,欢迎大家转至社区交流:
https://answer.uwa4d.com/question/608268846bb31032f979141c

封面图来源:Unity Lightcookie Caustic
https://lab.uwa4d.com/lab/5dcdc2858bab6aaf023555a2


今天的分享就到这里。当然,生有涯而知无涯。在漫漫的开发周期中,您看到的这些问题也许都只是冰山一角,我们早已在UWA问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入,也许你的方法恰能解别人的燃眉之急;而他山之“石”,也能攻你之“玉”。

官网:www.uwa4d.com
官方技术博客:blog.uwa4d.com
官方问答社区:answer.uwa4d.com
UWA学堂:edu.uwa4d.com
官方技术QQ群:793972859(原群已满员)