PBR实现2.0

时间:2023-03-08 21:46:46
PBR实现2.0

之前的错误和欠缺

1. 过于简单的划分diffuse和specular,非常光滑的非金属材料也是很能反光的
2. 费奈尔效应的处理,F0的选取也比较随意
3. 没有GI,更不支持AO

正确划分diffuse和spcular

之前我的PBR实现非金属无论怎样光滑都是没有反光的,这显然很不对。完全忽略了非金属的反射

金属性越高,反射率越高,分给Specular计算的Albedo越多。1.0的金属会反射所有的光,也就没有diffuse。但是非金属不太一样,0.0的非金属也仍旧又一定的反射能力,这个就是电介质反射率需要参与计算的地方。

在UnityStandardUtils.cginc中可以找到下面的分离方法,实际上是将Matellic的数值remap到[DielectricSpec,1]的范围。

inline half OneMinusReflectivityFromMetallic(half metallic)
{
  // We'll need oneMinusReflectivity, so
  // 1-reflectivity = 1-lerp(dielectricSpec, 1, metallic) = lerp(1-dielectricSpec, 0, metallic)
  // store (1-dielectricSpec) in unity_ColorSpaceDielectricSpec.a, then
  // 1-reflectivity = lerp(alpha, 0, metallic) = alpha + metallic*(0 - alpha) =
  // = alpha - metallic * alpha
  half oneMinusDielectricSpec = unity_ColorSpaceDielectricSpec.a;
  return oneMinusDielectricSpec - metallic * oneMinusDielectricSpec;
} inline half3 DiffuseAndSpecularFromMetallic (half3 albedo, half metallic, out half3 specColor, out half oneMinusReflectivity)
{
  specColor = lerp (unity_ColorSpaceDielectricSpec.rgb, albedo, metallic);
  oneMinusReflectivity = OneMinusReflectivityFromMetallic(metallic);
  return albedo * oneMinusReflectivity;
}

unity_ColorSpaceDielectricSpec里面存的是Unity选用的电介质反射率,alpha通道是1-dielectricSpec

Matellic:0 Gloss:0.5 的效果
PBR实现2.0)
从左至右:PBR1.0,Standard, PBR2.0, Standard

F0的选取

F0是入射角为0时的反射光,也就是分配给Specular计算的Albedo。之前的PBR实现实现会会整体偏亮就是因为Matellic肯定比正常的F0要大的多。

Matellic:0 Gloss:0.5 的效果

PBR实现2.0
从左至右:PBR1.0,Standard, PBR2.0, Standard

过亮的问题看起来解决了,但是整体感觉更粗糙一些。

为什么我的shader颜色偏黑

这是Gloss:0,Matellic:0~1的对比图。关闭了场景的环境光。第一行是CustomPBR,第二行是Standard

PBR实现2.0

最早是发现经过之前两个调整之后,整体颜色偏暗。经过几个对比测试之后确定是Specular部分偏低。也就是上图Matellic0.0和Matellic1.0对比在Diffuse为主的非金属上颜色基本正常,只有Specular的金属上色彩偏差最大。

这个问题让我非常迷惑,在一些对比测试中将公式改为和Unity一致也没有解决这个问题。进一步看到在Unity中对GammaSpace的Specular进行了一次开方修正。
同样的在CustomPBR中进行处理之后效果就正常了。

PBR实现2.0

对Gamma这块我还不是很明白,搜了一下Gamma这个修正坑很深。以后再研究,要是哪位有相关的资料分享那就太好了。

没有GI怎么行

没有GI的情况下物体在场景里面实在是太突兀,先将Unity 的GI整合进来,学习一下Unity的GI。

UnityGI的处理

环境光照也是光照,也分为diffuse和specular。无论计算过程多么复杂,最后都是要得到环境光照的这两个分量,然后和基本光照叠加。

Unity的叠加方式。这里是简化说明,本体在UnityStandardBRDF.cginc中

//indirectDiffuse 就是GI的diffuse
diffuse = (indirectDiffuse + directDiffuse) * diffuesColor; //directF:计算了Fresnel的specularColor
//indirectSpecular:GI的specular部分,包括各种Probe
//indirectF:计算了GI的Fresnel的SpecularColor
specular = lightColor * directSpecularTerm * directF + indirectSpecular * indirectSpecularTerm * indirectF;

整合UnityGI

会用到的struct在UnityLightingCommon.cginc里。
Unity的GI运算在UnityGlobalIllumination.cginc里。
我们要用的方法是这个

inline UnityGI UnityGlobalIllumination (UnityGIInput data, half occlusion, half3 normalWorld, Unity_GlossyEnvironmentData glossIn)

我们所需要的间接diffuse 和间接specular就在UnityGI.indirect

需要作的也就是给这个方法凑参数,构造一个UnityGIInput。这个结构体的大部分都很直观。

i_ambientOrLightmapUV
有lightmap的时候没有ambientColor,没有lightmap的时候只有ambientColor。这两个肯定是互斥的。

box和probe
在Unity中会有两个SpecCube生效,unity_SpecCube0,场景的反射源。unity_SpecCube1,当前的reflectionProbe,一起提供了IBL所需要的数据(其实应该是IBL的结果吧……待研究)

整合效果

Matellic:1.0 Gloss:0.7 Ambient:Red 在红色方块附近有一个ReflectionProbe

PBR实现2.0

弓箭效果

PBR实现2.0

下一步

1. 研究GI,看看能不能自己实现一个。现在GI方面有很多不明
2. Gamma修正,这个对最终画面影响很大,不能不看