虚幻引擎4中的真实着色
翻译:王成林(麦克斯韦的麦斯威尔 ) 审校:黄秀美(厚德载物)
作者Brian Karis,Epic Games
图1:UE4:《渗入者》演示
导言
大约在一年以前,我决定投入一些时间来改进我们的着色模型,采取一个更加基于物理的材质制作流程。我这样做一部分原因是想要渲染更多逼真的图片,另外我们也想看看采用一个更加基于物理的方法制作材质和使用材质分层能够实现什么效果。美术人员认为这样会大大提升工作流程和质量,而我在另外一家工作室亲眼看到过这些成效,当时我们采取了离线合成材质层的方法。Epic这里的一位技术美工尝试在着色器中进行分层,得到了出色的结果,这甚至成为了一项额外要求。
为了坚持这个改进方向,我们知道材质分层需要简单高效。恰巧在这个时候,迪士尼展示[2]了他们在《无敌破坏王》中使用的基于物理的着色和材质模型。Brent Burley展示了离线特征电影级渲染即使只有少量几个参数也可能会足够复杂。他还指出一个相对实用的着色模型可以适用于大多数的采样材质。他们得出的结论成为我们工作的灵感和基础,另外仿照他们的“原则” ,我们决定为我们的系统确定目标:
实时性能
· 首先也是最重要的一点,它应高效地同时使用多个可见光。
降低复杂度
· 参数应尽可能的少。参数过多会导致决策瘫痪,反复试错,或者会产生一些互相关联的属性,从而为了实现一个效果需要改变很多数值。
· 我们应该可以交替使用基于图片的光照以及分析性光源,所以参数必须在所有光照类型中都表现一致。
接口要直观
· 我们更喜欢易于理解的数值,而不是诸如折射率之类的物理术语。
感觉上是线性的
· 我们希望支持通过蒙版(mask)进行分层,但是每个像素我们只能渲染一次。这意味着根据参数进行混合的着色必须尽量符合着色结果的混合。
易于掌握
· 我们希望人们无需对电介质和导体具备过多理论上的知识,另外要把制作那些基础的基于物理真实的材质所需的努力降到最低。
稳定
· 应该很难错误地制作出基于物理但不真实的材质
· 参数之间的所有组合得到的结果都应该尽量真实无误
富于表达性
· 延迟着色限制了我们可以拥有的着色模型数量,所以我们的基础着色模型需要具有足够强的描述性以涵盖真实世界中99%的材质。
· 所有可以分层的材质需要共用相同的参数集以便在它们之间进行混合。
灵活性
· 其它的项目以及项目制作人也许不想实现照片级真实效果,所以要能够灵活地开启非照片级真实渲染。
着色模型
漫反射BRDF
我们研究了Burley的漫反射模型,但是发现它和Lambertian漫反射(等式1)只有少数不同之处,所以我们无法解释额外的开销。另外,任何更加复杂的漫反射模型很难有效地用在基于图片的光照或者球谐光照(Spherical harmonic lighting)中。因此,我们没在其它选项上花费过多精力。
其中cdiff是材质的漫反射系数。
微平面(microfacet)高光BRDF
普通的Cook-Torrance[5,6]微平面高光着色模型是:
查看该课程中的[9]以了解更多细节。
我们从迪士尼的模型入手,通过对比更加高效的模型来衡量每一项的重要性。这比听上去的要更困难;发布的公式中每一项使用的输入参数不一定相同,而这对于比较结果正确与否至关重要。
高光D项
对于正态分布函数(NDF),我们发现迪士尼采用的GGX/Trowbridge-Reitz值得它的成本。使用Blinn-Phong所需的额外开销相对很小,且更长的“尾部”所产生的独特的、自然的外表也吸引了我们的美工。我们还采用了迪士尼的重新参数化。
高光G项
关于高光几何衰减项,我们进行了比其他项更多的评估。最终,我们选择使用Schlick模型[19],但是我们使用k=α/2以便更好地适合GGX[21]的Smith模型。通过这个改动,Schlick模型在α=1时完全对应Smith模型,而且在[0,1]范围内(见图2)也是相当接近的近似了。我们还选择使用迪士尼的调整,在平方之前先将粗糙度映射为以降低“热度”。重点注意一下,该调整仅用于分析性光源;如果应用于基于图片的光照,那么掠射角的结果会过于黑暗。
高光F项
对于菲涅尔,我们选择使用传统的Schlick近似[19],但是做了一个小改动:我们使用球面高斯近似[10]来代替指数。这样计算的效率稍微高一些,且几乎感觉不到它们之间的差别。公式为:
其中F0是沿法线入射时的高光反射值。
图2:k=α/2的Schlick和Smith非常接近
基于图片的光照
为了在基于图片的光照中使用这个着色模型,我们需要解出辐射率(radiance)积分,常用的方法是使用重要性采样。以下表达式描述了该数值积分:
以下HLSL代码展示了如何使用我们的着色模型进行采样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | float3 ImportanceSampleGGX( float2 Xi, float Roughness, float3 N ) { float a = Roughness * Roughness; float Phi = 2 * PI * Xi.x; float CosTheta = sqrt( (1 - Xi.y) / ( 1 (a*a - 1) * Xi.y ) ); float SinTheta = sqrt( 1 - CosTheta * CosTheta ); float3 H; H.x = SinTheta * cos( Phi ); H.y = SinTheta * sin( Phi ); H.z = CosTheta; float3 UpVector = abs(N.z) < 0.999 ? float3(0,0,1) : float3(1,0,0); float3 TangentX = normalize( cross( UpVector, N ) ); float3 TangentY = cross( N, TangentX ); // Tangent to world space return TangentX * H.x TangentY * H.y N * H.z; } float3 SpecularIBL( float3 SpecularColor , float Roughness, float3 N, float3 V ) { float3 SpecularLighting = 0; const uint NumSamples = 1024; for ( uint i = 0; i < NumSamples; i ) { float2 Xi = Hammersley( i, NumSamples ); float3 H = ImportanceSampleGGX( Xi, Roughness, N ); float3 L = 2 * dot( V, H ) * H - V; float NoV = saturate( dot( N, V ) ); float NoL = saturate( dot( N, L ) ); float NoH = saturate( dot( N, H ) ); float VoH = saturate( dot( V, H ) ); if ( NoL > 0 ) { float3 SampleColor = EnvMap.SampleLevel( EnvMapSampler , L, 0 ).rgb; float G = G_Smith( Roughness, NoV, NoL ); float Fc = pow( 1 - VoH, 5 ); float3 F = (1 - Fc) * SpecularColor Fc; // Incident light = SampleColor * NoL // Microfacet specular = D*G*F / (4*NoL*NoV) // pdf = D * NoH / (4 * VoH) SpecularLighting = SampleColor * F * G * VoH / (NoH * NoV); } } return SpecularLighting / NumSamples; } |
即使使用重要性采样,我们仍然需要采集很多样本。使用mip map[3]可以极大地减少样本数量,但是总数仍需大于16以保证质量足够高。由于我们为了本地反射在每个像素上混合了许多环境贴图,我们实际上只能为每个像素提供一个单一样本。
分开求和近似
为了实现这一点,我们将上述求和分成两个求和项进行近似。然后我们可以分别预计算每个求和项。该近似对于一个恒定的Li(l)非常准确,对于普通环境相对准确。
预先过滤的环境贴图
我们使用了不同粗糙度预先计算了第一个求和项,并将结果存储在立方图的mip-map级别中。大多数的游戏公司采用了这种方法[1,9]。一个细微的区别是,我们使用着色模型的GGX分布对环境贴图进行卷积。由于它是一个微平面模型,分布的形状会根据到平面的视角变化而变化,所以我们假设该角度为0,即n=v=r。该各向同性的假设是第二次近似,不幸的是,它意味着我们在掠射角不会得到长反射。和分开求和的近似相比,该近似对于IBL结果造成的误差更大。如上面代码所展示的,我们发现使用cosθlk加权能实现更佳的结果[1]。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | float3 PrefilterEnvMap( float Roughness, float3 R ) { float3 N = R; float3 V = R; float3 PrefilteredColor = 0; const uint NumSamples = 1024; for ( uint i = 0; i < NumSamples; i ) { float2 Xi = Hammersley( i, NumSamples ); float3 H = ImportanceSampleGGX( Xi, Roughness, N ); float3 L = 2 * dot( V, H ) * H - V; float NoL = saturate( dot( N, L ) ); if ( NoL > 0 ) { PrefilteredColor = EnvMap.SampleLevel( EnvMapSampler , L, 0 ).rgb * NoL; TotalWeight = NoL; } } return PrefilteredColor / TotalWeight; } |
环境BRDF
第二个求和包含了其余所有内容。这和在纯白环境下对高光BRDF做积分是一样的,即Li(lk)=1。通过在Schlick的Fresnel中进行代换:F (v, h) =F0 (1-F0)(1-v.h)5,我们发现可以将F0 提到积分外面。
这样我们有了两个输入参数(粗糙度和cos)和两个输出参数(缩放大小和到F0的偏移值),所有这些参数的范围都在[0,1]中,方便我们计算。我们预先计算该函数的结果,将它存在一张2D查找纹理[2](LUT)中。
图3:2D查找纹理
在完成这项工作后,我们发现所有现存的研究都得到了和我们几乎一样的结果。Gotanda使用了一个3D LUT [8] ,而Drobot将其优化为一个2D LUT [7] ,和我们的做法几乎相同。另外,作为这门课程的一部分,Lazarov更进一步,展示了一个相似的积分[3]的一些分析性近似。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | float2 IntegrateBRDF( float Roughness, float NoV ) { float3 V; V.x = sqrt( 1.0f - NoV * NoV ); // sin V.y = 0; V.z = NoV; // cos float A = 0; float B = 0; const uint NumSamples = 1024; for ( uint i = 0; i < NumSamples; i ) { float2 Xi = Hammersley( i, NumSamples ); float3 H = ImportanceSampleGGX( Xi, Roughness, N ); float3 L = 2 * dot( V, H ) * H - V; float NoL = saturate( L.z ); float NoH = saturate( H.z ); float VoH = saturate( dot( V, H ) ); if ( NoL > 0 ) { float G = G_Smith( Roughness, NoV, NoL ); float G_Vis = G * VoH / (NoH * NoV); float Fc = pow( 1 - VoH, 5 ); A = (1 - Fc) * G_Vis; B = Fc * G_Vis; } } return float2( A, B ) / NumSamples; } |
最后,为了对使用重要性进行采样的参考作近似,我们将两个预先计算的和相乘:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | float3 ApproximateSpecularIBL( float3 SpecularColor , float Roughness, float3 N, float3 V ) { float NoV = saturate( dot( N, V ) ); float3 R = 2 * dot( V, N ) * N - V; float3 PrefilteredColor = PrefilterEnvMap( Roughness, R ); float2 EnvBRDF = IntegrateBRDF( Roughness, NoV ); return PrefilteredColor * ( SpecularColor * EnvBRDF.x EnvBRDF.y ); } |
图4:顶部为参考,中间为分开求和近似,底部为包含n=v假设的完全近似。径向对称假设引入了最大的误差,但是综合近似仍和参考很像。
图5:和图4相同的对照,但使用的是绝缘体
材质模型
我们的材质模型是迪士尼模型的一个简化,侧重于实时渲染的效率。限制参数数量对于优化G-缓冲器空间,减少纹理的存储和使用,以及最小化在像素着色器中混合材质层的开销极为重要。
以下是我们的基础材质模型:
BaseColor(基础颜色) 单一颜色。很容易理解的概念。
Metallic(金属性) 无需理解绝缘体和导体反射率,减少出错的空间
Roughness(粗糙度) 它的意思很清楚,但是如果使用光滑度就需要一番解释了
Cavity(腔洞) 用于小规模的遮挡
基础颜色,金属性与粗糙度和迪士尼模型中的一样,但是腔洞参数在迪士尼模型中没有出现,所以有必要解释一下。腔洞被用来指定来自几何体的遮蔽,该几何体小于我们的运行时遮挡系统能处理的几何体,通常由于该几何体只出现在法线贴图中。例如地板之间的缝隙,以及衣服上的缝合处。
最值得注意的一点是我们省略了高光参数。事实上我们一直使用该参数,直到完成《渗入者》演示才停止使用,因为我们不喜欢它了。首先,我们认为“高光”是一个糟糕的参数名称,它造成了诸多误解,且在某种程度上不利于美工从控制高光强度过渡到控制粗糙度。美工和图像程序员都容易忘记它的范围,认为它的默认值为1,而它的实际默认值为Burley的0.5(对应4%的反射率)。高光被有效使用的情况几乎都是为了小规模的遮蔽。我们发现折射指数变量(IOR)对于非金属相对不那么重要,所以我们最近使用更加易于理解的腔洞参数代替高光。非金属的F0现在为常数0.04。
我们决定在我们的基础材质模型中不使用以下迪士尼模型中的参数,而是将它们作为特例:
Subsurface(亚表面) 使用不同的方法对阴影贴图采样
Anisotropy(各向异性) 需要很多IBL样本
Clearcoat(透明涂层) 需要双倍IBL样本
Sheen(光泽度) Burley的论文中没有很好的定义
我们还没有在产品中使用过这些特殊情况,除了《元素》演示中的冰块用到了亚表面。此外,我们有一个专门用于皮肤的着色模型。未来我们打算采用一个混合了延迟和前向着色器的方法,以便更好地支持更多有专门用途的着色器。目前我们采取纯延迟着色方法,使用一个id存储在G缓冲器中的着色模型的一个动态分支来处理不同的着色模型。
经历
到目前为止有一种情况我已经遇到多次了。我会告诉开始过渡的美工要使用不同的粗糙度,“要像你过去使用高光颜色那样使用粗糙度”,不久后我听到了他们激动的声音“起作用了!”但是随后一条有趣的评论说道:“感觉粗糙度弄反了。”结果发现美工希望看到他们制作的纹理中更亮的纹素对应更亮的高光。如果图片存储了粗糙度,那么亮就对应更粗糙,会导致高光强度降低。
有一个问题人们问了我无数遍:“金属性是二进制的吗?”,对此我一开始耐心地解释混合/分层材质的诸多细节。后来我学到了最好就是说一句“是的!”原因是美工一开始不愿意将参数设置到最大或最小,通常我发现他们将金属的金属性设置为0.8。接下来要讨论的材质层,在99%的情况下,金属性不仅仅是0或者是1.
从控制高光到控制粗糙度的过渡中我们遇到了一些问题,其中包括不能复制材质。最重要的几个问题来自于《堡垒之夜》,这是一款Epic目前正在制作的游戏。《堡垒之夜》的美术方向被定位为非照片级,它明确地使用了互补色表现漫反射和高光反射,从物理上看不是那么真实,且不能用我们的新材质模型表示。在一番讨论后,我们决定继续支持旧的漫射色/高光色,将其作为引擎中的一个选项以保持《堡垒之夜》的游戏质量,因为这个游戏已经开发很久了。但是,我们不认为新模型排除了非照片级渲染,迪士尼在《无敌破坏王》中的使用证明了这一点,所以我们打算未来所有的项目都使用新模型。
材质分层
在之前的方法中我们使用对应不同模型的纹理的数值单独指定材质参数。混合来源于同一个库的材质层相较我们之前的方法提供了更多的好处:
· 重新使用了多个资源的成果
· 减少了单一资源的复杂度
· 统一并集中了定义游戏外观的材质,使美术和技术方向更加简单。
为了彻底接受这个新工作流程,我们需要重新思考我们的工具。虚幻引擎自从第三代早期开始就加入了基于节点图表的材质编辑器这一特色功能。该节点图表明确了输入(如纹理,常量),运算和输出,这些都被编译到了着色器代码中。
尽管该功能的主要目的是为了实现材质分层,令人惊讶的是我们几乎不用加入任何工具就可以支持材质层的制作和混合。UE4的材质编辑器中的节点图表部分可以整合函数并使用多个材质。该功能可用于实现一个材质层。将材质层保留在基于节点的编辑器中而不是作为一个高高在上的固定函数系统,使材质层可以以一种可编程的方式被映射和合并。
为了简化工作流程,我们加入了一个新的数据类型:材质属性(Material Attribute),它存储了材质所有的输出数据。和其它类型一样,这个新的类型可以作为单独的针脚被传入或传出材质函数,沿着连线传递,或者被直接输出。有了这些变化,材质层可以作为输入被直接拖拽进来,可以采用和处理纹理时同样的方法合并,调整和输出。实际上,大多数的材质图表更加易于使用了,因为将采用分层作为一个特定材质的主要变动映射并混合了不同材质层。这比过去使用参数进行调整要简单多了。
由于有几个感觉上为线性的材质参数,事实上完全在着色器中混合不同材质层会实用一些。我们认为和一个完全离线合成的系统相比,这会使画质有一个实质性的提升。由于能够在不同频率下映射数据,纹理数据的可视分辨率(Apparent Resolution)可能会非常高:每个顶点或者低频纹理数据都是不同的,材质层混合蒙版,法线贴图和腔洞贴图在每个网格物体上都有被指定,材质层会沿着网格物体的表面进行覆盖。更高级的例子甚至会用到更多频率。虽然
图6:UE4材质编辑器中的简单材质层
由于着色器的开销我们只能使用有限数量的材质层,但是我们的美工还没发现该限制会引起任何问题。
有一点值得注意的是在一些情况中美工为了应付着色器中的分层限制,他们将网格物体分为多个部分,导致需要更多的绘图调用(draw call)。虽然由于UE4在CPU方面作了代码优化,我们期望绘图调用的数量会降低,但是这好像在未来会成为一个问题。还有一点我们没调查的,即在那些完全被一个材质层覆盖的区域中使用动态分支以减少着色器损耗。
到目前为止,我们对于材质层的使用经历非常愉快。我们发现我们的工作效率得到提升,且画面质量也有进步。我们希望改进美工用来处理材质层库文件的接口,使美工更容易找到这些接口并能够预览各材质层。我们还打算在当前运行时系统之外研究一个可以离线合成/烘焙的系统,以支持更多数量的材质层和提供更佳的可调控性。
图7:不同材质层和锈迹相混合
图8:使用不同层次细节的材质分层结果
光照模型
对于着色,我们希望使其更加基于物理从而改进我们的光照模型。我们着重考虑的两点是光线的衰减以及不确定的自发光源——即通常人们所说的区域光。
改进光线衰减相对直接:我们采用在物理上非常精确的平方反比衰减,并使用光度测量单位流明。不过,我们必须要处理的一个小麻烦是这种类型的衰减函数在接近零点时距离为无穷。但是为了效率——在实时计算和离线计算中——我们仍然需要人为地限制光源的影响范围。有很多方法可以实现这点[4],但是我们选择平方反比函数,使得光源大部分的影响范围相对不受影响,同时仍然柔和地过渡到零。它的优势在于修改光照的半径不会改变它的有效亮度,当光源被美工固定时这一点很重要,但是由于性能的原因我们仍需调整光照范围。
分母中的1是为了防止函数在靠近光源的位置激增。在不要求物理正确性的情况下,可以将其暴露为一个可供美工调控的参数。
这个简单的变化带来的画质区别,尤其在那些有很多本地光源的场景中,可能会节省巨大的开支。
图9:使用平方反比衰减得到更自然的结果
区域光
区域光源不仅仅会生成更真实的图片。当我们使用基于物理的材质时,区域光也非常重要。我们发现,没有它们美工会凭直觉不使用低粗糙值,这样会产生无限小的高光,看上去不自然。他们其实在尝试着重现从确定光源射出的区域光的效果[4]。
不幸的是,这会导致着色和光照之间具有相关性,违反了基于物理渲染的一条核心原则:当材质被用在和其制作环境不同的光照环境中时,不应该需要修改它的材质。
区域光是一块活跃的研究领域。在离线渲染中,通常的方法是使用均匀采样法(Uniform Sampling)或者重要性采样法获取光源表面多个发光点[12][20]。这对于实时渲染根本不现实。在探讨可行的方案前,先给出我们的要求:
· 连贯的材质外表
- 使用漫反射BRDF和高光BRDF计算得到的能量总数不能相差太多。
· 当立体角(SolidAngle)趋近于0时该模型会近似于点光源模型
- 我们不希望牺牲我们着色模型的任何特征以实现这一点
· 足够快,可以用在任何地方
- 否则,我们不能解决之前提到的“粗糙度偏移”问题。
公告牌反射(Billboard Reflection)
公告牌反射[13]是一种可被用作离散光源的IBL。一张存储了自发光光源的2D图片被映射到3D空间中的一块矩形区域。和环境贴图预过滤相似,图片也使用不同大小的高光分布圆锥进行预过滤。从图片计算高光着色可以被认为是一种圆锥跟踪,这里的圆锥接近高光NDF。圆锥中心的射线和公告牌所在平面相交。然后图片空间中的相交点会被用作纹理坐标,相交处的圆锥半径被用来推导出一个预先过滤的mip等级。糟糕的是,虽然图片可以直接表示复杂的区域光源,但是公告板反射由于多个原因不能满足我们的第二个要求:
· 在平面上图片被预先过滤,因此图片空间能表示的立体角有限。
· 当光线和平面不相交时没有数据
· 光照向量l是未知的,或者使用反射向量代替
圆锥截面
圆锥跟踪不需要预过滤;它可以使用分析的方式完成。我们实验的一个版本使用Oat的圆锥-圆锥相交公式[15]朝着一个球体跟踪了一个圆锥体,但是它的成本太高无法实际使用。另一种方法是由Drobot[7]最近提出的,他使用一个面向着色点的圆盘去截圆锥体。然后对一个近似于NDF的多项式在相交区域内做分段积分。
根据Drobot的最新进展,这貌似是一个有趣的研究方向,但是以它当前的形式,它还没有满足我们的要求。由于使用了一个圆锥,高光分布必须是径向对称的。这一点就排除了高光延伸的可能性,而后者是微平面高光模型的一个重要特征。另外,和公告牌反射相似,这里没有着色模型需要的光照向量。
高光D项的修改
我们去年展示的一个方法[14]是基于光源的立体角修改高光分布。这背后的理论是将光源的分布视作对应圆锥角的D(h)。一个分布对另外一个分布的卷积可以通过将两个圆锥体的角度相加以得到一个新圆锥体得到近似。为了实现这点,将公式3的α转换为一个有效圆锥角,加上光源的角度,然后转换回来。现在我们使用α’代替α。我们使用以下近似进行处理:
尽管效率很高,但是该方法很不幸地没有满足我们的第一个要求,因为当受大面积区域光照射时,非常光滑的材质看上去很粗糙。也许听起来是显而易见的,但是如果高光NDF非常紧凑——例如Blinn Phong——因此更加符合光源的分布的话,那么该方法会有更明显的效果。对于我们选择的着色模型(基于GGX),它并不十分可行。
图10:左图为参考图,右图对高光D进行了调整。它的近似效果很差,因为图中的球形位于掠射角时会消失,另外光滑的材质,比如打磨过的黄铜,看上去很粗糙。
代表点(Representative Point)
如果对于一个特定的着色点,我们可以将所有来自同一片区域的光视作来自于光源平面的一个代表点,那么我们就可以直接使用我们的着色模型了。一个合理的选择是选择拥有最大贡献的点。对于Phong分布,这个点就是光源上和反射光线角度最小的点。
这个方法在之前被发表过[16][22],但是人们从未提到过能量守恒。通过移动自发光光源的原点,我们实际增加了光源的立体角,但是没有弥补额外的能量。改正它要比直接除以立体角要稍微复杂一些,因为能量差依赖于高光分布。举个例子,改变一个粗糙材质的入射光方向对能量几乎不会造成任何变化,但是对于一个光滑的材质能量的变化可能是巨大的。
球形光
如果球形光在水平面以上[18],那么它的辐射照度(Irradiance)等价于一个点光源。尽管有悖于直觉,这意味着如果我们接受球体在水平面以下所造成的误差,那么我们只需要处理高光光源。我们通过寻找和光线距离最短的点来近似得到和反射光线之间角度最小的点。对于一个球体很明确:
这里,L是一个从着色点到光照中心的向量,sourceRadius是球形光的半径,r是反射向量。在光线和球面相交的情况中,计算得到的点即为光线上距离球心最近的点。当标准化以后,它是统一的。
通过将自发光光源的起点移动到球面上,我们有效地将高光分布拓展了圆周角那么大。虽然它不是一个亚表面分布,使用标准化的Phong分布可以对此进行最优的解释:
这里φr为r和L之间的角度,φs为球面的圆周角的一半。Ipoint是标准化的,意味着它在半球范围内的积分结果为1。Isphere显然不是标准化的,根据指数p的大小,它的积分可能会非常大。
图11:上图显示了公式13解释的拓宽效果
为了近似该能量增量,我们使用之前修改高光D项所采用的方法,在那个方法中我们基于光的立体角拓宽分布。我们为那个更宽的分布使用标准化因子,然后替换之前的标准化因子。对于GGX,标准化因子为。为了推导出用于代表点运算的标准化我们将原始的标准化因子除以新拓宽的标准化因子:
代表点方法的结果符合我们所有的要求。通过正确处理能量守恒,无论光源大小材质的表现都是相同的。光滑的材质仍会在边缘产生清晰的高光,且由于它只修改了BRDF的输入值,我们的着色模型不受影响。最后,美工们可以很有效率地根据他们的喜好进行使用。
管形光(Tube Light)
球形光被用来表示灯泡,而管形光(光外有一个胶囊)可以用来表示现实世界中常见的日光灯。首先,我们对一个半径长度不为零的管形光,即线性光求解。只要线段在水平面以上,它的辐射照度可以通过分析进行积分[16,17]:
其中L0和L1是从着色点到线段端点的向量。
图12:左图为参考图,右图使用了代表点方法。尽管能量没有完全守恒,但是我们的近似已经非常接近参考图了。
为了防止负辐射照度、分母为零等错误出现,以及为了在长度为零时符合点光源的衰减,我们修改了该公式:
对于线性高光我们需要解下列方程组以得到t的值:
Picott[16]求得了和r之间角度最小的情况下的t值:
和球形光情况相似,我们对最小角度做了近似然后改为求最短距离情况下的t值:
以上方法对于个别情况处理得不是很好,不能总得到最近的点,但是它计算所需开销稍低一些,且得到的结果看上去和等式18的结果一样合理。
要注意一点:由于等式18和19将r作为一条直线而不是射线,两个解决方案都没有正确地处理指向远离线段方向的射线。这会造成从一个端点到另一个端点突然的变化,即使在完美的平面上也会如此。这个问题发生在反射光线从指向光照的方向过渡到远离光照的方向的时候。我们可以通过在计算点和每个端点之间进行选择来解决这个问题,但是这样做成本太高。目前我们只有简单地接受这个方法。
为了使能量守恒,我们使用之前用于球形光的概念。高光分布被光照的圆周角拓宽了,但是这一次只在一维中所以我们使用各向异性版本的GGX[2]。各向异性的GGX的标准化因子为,而在各向同性的情况中。于是我们得到:
因为我们只改变了光的原点以及加入了一个能量守恒项,这些计算可以累加起来。对于一条线段和一个球面这样做的话得到了不同形状的卷积的近似值,且相当符合管形光的行为。管形光的结果显示在图13中。
图13:使用加入能量守恒的代表点方法的管形光
我们发现加入能量守恒的代表点方法对于简单形状效果很好,希望在未来将它应用到除球形和管形以外的其它形状上。尤其我们希望将它应用在贴有纹理的四边形上以表示更加复杂、颜色更加丰富的光源。
结论
在着色、材质和光照领域中,我们转向更加基于物理的实现所做的工作已经证明是非常成功的。它为我们最新的《渗透者》项目的画质做出了极大贡献,我们计划在未来所有项目中都使用这些改进。事实上,这些改进已经应用于《堡垒之夜》,一个在着色模型改进之前已经开发了很久的游戏。我们打算在这些领域继续改进,目标是拥有更强的灵活性和可伸缩性,使得各种场景以及各级别的硬件都能利用基于物理的方法。
鸣谢
我想要感谢Epic Games,尤其渲染团队的成员,他们帮助我完成这篇报告,以及各位美工,他们为我指明方向,反馈建议,以及使用引擎制作出了精美的作品。我想要特别感谢Sebastien Lagarde,他在我的重要性取样数学计算中发现了一处错误,改正后我最终求得了环境BRDF的解。永远不要低估全世界高水平开发者对你代码进行检查的重要性!最后,我想要感谢StephanHill和Stephan McAuley给出的珍贵的反馈建议。
【版权声明】
原文作者未做权利声明,视为共享知识产权进入公共领域,自动获得授权。
[1] 该加权没有出现在等式7中,等式7是一个简单形式。
[2] 我们使用R16G16格式,因为我们发现精确度很重要。
[3] 他们的着色模型使用了不同的D和G函数
[4] 这和其它开发者的观察相吻合[7,8]。