本文主要内容来自于Epic Games Brian Karis在SIGGRAPH 2013上面的分享,详情请浏览原文。
1. Introduction
这里介绍了UE4尝试将管线升级成PBR的动机,同时由于整个升级工作都是受Disney在《无敌破坏王》应用的PBR材质系统[2]的启发,因此也是以此为基础开展的,在开始之前,先为UE4的PBR设定了一系列的目标:
- Real-Time Performance
- Reduced Complexity:参数要够简单易用,且由于场景中天光与直接光照的应用都很广泛,因此这些参数应该能够同时支持这两种光照
- Intuitive Interface:界面直观
- Perceptually Linear,对参数的插值的结果应该等同于对参数结果的插值
- Easy to Master:不需要技术知识也能理解
- Robust:足够稳定
- Expressive:1.基础shading model要能覆盖现实世界99%的材质;2.所有可分层材质(layerable material)都需要共享同一套参数,方便插值
- Flexible:能够支持non-photorealistic rendering,方便覆盖不同风格的游戏
2. Shading Model
光照计算公式可以表示成如下形式:
拆解一下就变成了:
2.1 Diffuse BRDF
这里对比了Burley的漫反射模型跟Lambertian的漫反射模型(公式1),发现其实二者的表现区别比较小,没有必要为了这轻微的提升而浪费因此带来的计算消耗,且考虑到复杂的漫反射模型在IBL或者SHL(球谐光照)中的可能无法实现高效渲染,因此这里的漫反射模型就直接取用了最基本的Lambertian Diffuse模型了:
指的是像素的基色albedo color。
2.1.1 Microfacet Specular BRDF
经典的Cook-Torrance微表面高光着色BRDF模型[4,5]公式给出如下:
h指的是入射光方l与观察方向v的中间向量(v = normalize(l+v)),详情细节可以参考[9]。
UE4这边以Disney的离线渲染模型作为基础渲染公式,之后尝试为公式中的每一项寻找到高效的替代计算方法来进行优化,不过由于每一项内容的输入参数不一致,使得效果对比变得困难,下面对每一项进行分开叙述。
Specular D
D表示的是表面法线分布函数(Normal Distribution Function,NDF),Disney使用的是GGX/Trowbridge-Reitz公式,经过测试发现,这个公式的性价比非常高,且如果后面使用Blinn-Phong模型的话,额外的消耗基本上可以忽略。此外,UE4这边尝试用粗糙度的平方来取代Disney公式中的α值:
Specular G
G表示的是几何表面的微观细节对于高光的阻尼作用,这一项,UE4取用的是Schlick模型,且为了更好的契合GGX的Smith模型[21],这里做了一个小改动k = α/2,这样一来,对于Schlick模型在α = 1 的时候就跟Smith模型完全一样了,而[0,1]范围内,这两者的区别也比较小(参考Figure 2)。另外,这个地方还参考了Disney为了降低“hotness”而做的简化处理:将α 用(Roughness + 1) / 2替代(相当于对于D跟G而言,α 的值是不一样的。需要注意的是,这个简化调整只会用在分析光源(analytic light source,用公式表达的光源,IBL等不属于此类)上,如果用在IBL上的话,如果用平行于表面的视线渲染的话得到的结果会过暗:
Specular F
F表示的是菲涅尔效应的对于观察角度的影响,这一项用的是经典的Schlick的逼近算法[19],其中用了SH逼近[10]算法来取代Schlick算法中的指数power运算,这个修正可以大大降低运算的消耗,同时引起的质量下降则可以忽略不计,整个公式给出如下:
其中指的是光照垂直于平面入射时的反射高光强度。
Image-Based Lighting
要对IBL应用上述的Shading Model,就需要进行辐射度的积分运算,积分通常都是通过蒙特卡洛算法来模拟:
Shader代码给出如下:
//根据概率分布函数计算采样点
//https://agraphicsguy.wordpress.com/2015/11/01/sampling-microfacet-brdf/
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;
}
而即使通过蒙特卡洛算法来模拟积分运算,依然需要大量的采样点才能得到较高的显示质量,通过mipmap[3]可以在一定程度上降低采样点所需的数目,不过即使如此对于单个像素而言,也需要16个以上的采样点。而由于为了实现局部反射,UE4需要对很多环境贴图的采样结果进行blend,因此这里留给每个像素的预算只剩下一个采样点了,16到1,这个差距还很明显。
Split Sum Approximation
为了进一步降低消耗,这边做了一个简化处理,那就是将公式6中的累加运算拆分成两个累加操作,而拆分后的这两个累加都可以通过预处理的方式计算得到,这样在运行时,只需要一次采样,即可得到对应的计算结果。至于这样的拆分是否合理呢,实际上,在L_i(l)是一个常量的时候,这个拆分是无损等价拆分,在其他情况下,其计算结果也非常的接近,因此这种拆分是可以说得过去的:
Pre-Filtered Environment Map
具体推导可以参考Specular IBL。
第一个累加项表示的是各个方向的光照输入的情况。这里将第一个累加公式通过预处理的方式计算各个不同粗糙度输入下的输出,并将结果存储到一张mipmap cubemap中,这是游戏行业的标准做法[1,9],与之不同的是,UE4这里存储的是Shading Model的GGX分布与环境贴图的卷积结果,这个卷积也是通过蒙特卡洛算法计算得到。由于这里表述的是微表面模型,因此shading model的GGX的分布与观察角度是有关系的,为了简化计算,这里假设视线与法线的夹角为0,即n = v = r。而这个假设会导致以平行于表面的视线来查看场景时,将无法得到清晰的镜面反射(lengthy reflection),跟前面Split Sum Approximation的拆分简化处理不同,这个地方的简化算法会有比较大的损害,这也是为什么UE4的IBL输出表现误差大的主要原因。下面给出具体的实现代码,需要注意的是,里面的权重计算公式用的是cos(θlk),因为实际测试中发现,这个权重计算公式得到的效果更好,这个数值在上面的公式中没有体现出来。
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;
}
Environment BRDF
第二项是除了光照之外的其他数据,也可以看成是高光BRDF在白光(L_i(lk) = 1)输入下的输出,将Schlick的菲涅尔公式带入其中的话:F(v, h) = F0 + (1 − F0)(1 − v · h)^5前面的积分可以展开成一个积分运算:
这个公式可以表示成两个输入下的两个输出,其中两个输入指的是粗糙度Roughness(这个对应的是f(l,v)?)以及cosθv(这个对应的是v*h),两个输出对应的是等式右边的F0的scale因子A与bias因子B,F0 * A + B。两输入两输出可以编码成一个2D的LUT:
这个LUT使用的像素格式为R16G16(保证有足够的精度,不然表现会有比较大的误差)。
做完上述的处理之后,跟同行们的实现进行对比发现,其实大家给出的解决方案最终都殊途同归——使用LUT来进行简化——只有Lazarov走得更远了一步[11],他提出了一种对于积分公式的简化模拟公式。
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;
}
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 );
}
Figure 4: 第一排是参考, 中间一排是split sum approximation, 下面一排是完整的模拟 n = v 。虽然因为径向对称的假设引入了较多的误差,不过加上其他部分整合后的输出结果跟参考输出其实就没有太多的差别了。
Figure 5: 这是一组绝缘体的输出对比上面这里的Split-sum Approximation是天光Specular部分,天光Diffuse部分同样需要进行预处理
积分部分为当前采样点的法线的上半球的采样结果积分,输入为法线与environment cubemap,可以通过预计算完成,这个预计算是一个convolution过程,预计算结果我们称之为irradiance map(详情可参考Diffuse irradiance):
Material Model
UE4所使用的材质模型,实际上是在Disney材质模型的基础上以实时渲染为目标经过一系列的优化处理后而来的。通过限定材质的参数以优化G-Buffer的空间消耗,降低贴图存储空间消耗以及缩小多层材质的混合消耗。
基础的材质模型主要包含以下输入贴图:
- 基色贴图
- 金属度贴图,整合导体跟绝缘体
- 粗糙度贴图
- 孔洞贴图Cavity,用于模拟微表面的阴影表现
前三张贴图都跟Disney的材质模型一致,只有孔洞贴图是UE4引入的,这个贴图主要用于模拟模型几何表面的微观凹凸感,比如衣服上的褶皱,水泥砖上的孔洞等,这些表面细节通常用法线贴图来表征,但是法线贴图不会产生对应的阴影,因此需要增加一张额外的贴图处理这个问题。
这里需要注意的是,UE4的材质贴图中没有了高光贴图Specular,这是因为,Karis觉得高光贴图这个名字很容易让人误会,而且也不方便实现美术同学从高光强度intensity控制到粗糙度roughness控制的转变。此外,其默认值应该是0.5,而美术同学以及引擎程序们却经常好像忘了这一点,都会以1作为其默认值。而因为高光贴图应用的场景大多是为了计算小尺寸阴影,且Karis发现折射系数(index of refraction, IOR)变量对于非金属而言其实没有什么作用,因此决定将高光贴图替换成更为通俗易懂的孔洞贴图。非金属材质的F0目前给的是一个0.04的常量。
Disney的材质模型中还有一些参数并没有被纳入UE4的基础材质模型,这是因为这些参数其实只有在特殊场合中才会用到,因此只需要通过一些特殊的方式来处理即可:
- 次表面贴图:用于对Shadow map进行区别采样
- 各向异性贴图:需要采样较多的IBL sample
- Clearcoat贴图:需要对IBL进行双重采样
- Sheen:光泽贴图
在UE4中,除了次表面贴图在《Elemental》Demo中用于进行冰面模拟之外,其他的参数到目前为止还没用过。目前UE4使用的是单纯的延迟渲染管线,不同的shading model在G-Buffer中对应的是不同的Shading Model ID,以用于进行不同的Shader分支处理,未来如果支持了前向渲染、延迟渲染混合管线之后,再考虑支持更多特殊的shading model。
Experiences
在实际使用的时候,由于高光贴图已经被替换成了孔洞贴图,因此美术同学需要调整粗糙度贴图来实现对高光效果的调整,不过需要注意的是,高光贴图中,数值越大,表明颜色的高亮程度越高,但是对于粗糙度贴图而言,数值越大就表示粗糙度越高,其对应的高光程度反而越低,这是需要注意的。
金属度贴图中存储的金属度并不是0/1二值的,而是[0,1]的浮点范围,而且在使用中注意不要设置为0或者1,因为自然界中没有完全反射也没有完全不反射的物质。
在从非PBR到PBR转型的过程中,UE4还遭遇了一些问题,比如说在开发《堡垒之夜》的时候遇到的一个问题:《堡垒》是一个比较风格化的游戏,整个游戏的风格比较偏动画,non-photorealistic,为了实现这种效果,在开发之初就为diffuse/specular反射设定了一套互补的颜色数据,而这种设定是物理不正确的,因此无法使用上面提到的PBR材质模型,且由于开发日久,已经不太适合推倒重来,因此又专门为堡垒保留了老式的diffuse/specular材质模型,不过这并不意味着新的材质模型不支持动画风格的游戏,实际上Disney的《无敌破坏王》就是使用的PBR实现的动画电影。
Material Layering
分层材质系统的通过对多个source材质进行混合来实现不同的材质效果,而source材质则是来源于一个共享的材质库。UE以前的材质系统需要为每一个模型指定其专有的材质,而这个专有的材质的参数又是通过专有的贴图来指定,相对于以前的材质系统,UE4的新的分层材质系统有很多便利的地方:
- 可以在多个asset中共享材质来减少重复劳动
- 对于单个asset而言,其材质的实现与指定也更为方便简单
- 为整个游戏实现一套归一的材质标准
为了适应分层贴图系统,UE4这边的工具系统就需要做相应的改进,因此在UE3早期的时候就开发了蓝图节点编辑系统,通过蓝图编辑的材质最终都会转换成shader代码。
为了将分层材质集成到现有的工作流上,特地引入了一个新的数据结构:材质attributes,通过这个数据类型,材质layer可以用作节点编辑的输入进行layer之间的组合与运算,事实证明,这种做法简化了材质的编辑。
由于材质参数只有寥寥几个,因此材质的混合可以直接在一个shader中完成,不过这种实现方式对于那些将一个模型拆分成多个部分进行渲染来说,可能会存在问题。
分层材质系统上线之后,到目前为止收到的反馈都是正面的,既提高了质量也提高了效率,未来可能会考虑开发离线的材质系统以支持更大范围的材质层数以及更好的可扩展性。
Figure 8: Material layering results exploiting multiple frequencies of detailLighting Model
提升光照模型的表现的两个核心点:光照衰减(light falloff)以及面光源(non-punctual light source of emission)。
光照衰减要想表现正确就要遵循物理定律,实现起来就需要满足两个条件:
- 光照强度与到光源的距离的平方成反比(能量守恒)
- 光照强度使用真实的物理光照强度(现实世界测度计所测量得到的数值)
在这里有一个问题是,按照第一个条件来实施的话,任何距离上的点受到的光照都不会降低至0,从而导致整个场景中无穷远处的位置也将受到光照的影响,十分影响实施效率,这个问题有很多解决办法[4],UE4采用的是一种窗口方法,即在光源的主要影响范围的光照强度的计算公式不受影响,继续按照半径的平方的反比来,而超出这个范围之外的光照强度则是采用一个软过渡的方式来降低至0.这种方法的优点在于,修改了光源的半径,并不会改变其有效的亮度——保证有效的亮度不改变对于被美术同学锁定的情况而言是非常重要的,但是出于对性能的考虑就需要修正光照的范围。
分母中的1是为了避免distance为0的时候的无穷大,对于那些不需要物理正确的渲染风格的游戏而言,这个数值可以暴露给美术同学进行控制。加了上面这个公式控制之后,对于那些拥有大量局部光源的场景而言,其表现与期望的点光表现效果基本上也没什么区别了。
通过对UE源码的分析,我们发现,实际上UE在使用的时候是直接移除上述公式中的分母项,而仅仅保留了分子项,下面给出falloff公式与InverseSquare公式的表现:
Green:InverseSquare, Blue:Falloff上述截图中,虚线表示的是纯分子表现,实线表示的是添加了二次分母的表现,从效果对比来看,两者区别不大,可能这也是为什么UE只保留了分母的原因吧?
Area Lights
面光源的重要性不仅仅在于能够生成更为真实的场景画面,更在于能够让美术同学能够无拘无束的使用PBR材质。如果不能实现正确的面光源效果,美术同学都不敢使用过于低粗糙度的材质,反射的作用使得低粗糙度的表面表现得就像一个面光源,在面光源渲染不正确的情况下会使得画面表现非常的不自然,而如果需要为不同的光源单独设置不同的材质又违背了PBR在任何光源环境下都应该表现正确的原则。
光源面光源的实现,目前有很多人在研究。在离线渲染中,最通用的做法就是将面光源表达成一个点光源阵列[12,20]。而这种做法对于实时渲染来说是不现实的,下面先给出UE4对于面光源实施效果的一些期望:
- Consistent material appearance,使用光滑的材质,经过面光源照射后的结果应该也是光滑的
- Approaches point light model as the solid angle approaches zero,将面光源缩小到一点时,得到的效果应该跟点光源的效果一致
- Fast enough to use everywhere,高效
Billboard Reflections
公告板反射[13]其实是一种可以当成离散光源的IBL,一个存储了自发光数据的2D贴图可以映射到3D场景中的一个矩形上。跟环境贴图的预处理一样,这个自发光贴图也会对不同尺寸的specular distribution cone进行预处理。而计算这个自发光贴图输出的高光,在cone的形状接近于高光NDF的时候,可以看成是某种形式的cone tracing。
cone tracing的简单介绍如下:
首先,在每个表面的每个点上,将传统做GI计算时的半球积分空间给分割成多个独立的Cones,用这些Cones组合得到的空间(中间会有重叠或裂隙)来近似原始的半球空间,并在其上做Irridiance的采集。
之后,对于每个独立的Cone,又使用下述方法再进行近似:
也即是在每个Cone的内部又将其用多个密布排列的Cube来进行近似,使用Cube的方法是其会使得OCTree的Tracing变得很方便。 每个Cube大小的计算就可以根据具体Cone的属性(比如夹角,最大长度等)来进行计算,一般来说从每个Cone内部分割出来的Cube个数不会太多.
对于每个Cube在OCTree中的Tracing,使用的方法也比较简单:直接计算出的Cube的Size,然后根据此Size找出与其最适配的那层Mipmap,这里的原则就是Cube的Size要尽可能地与Mipmap层中的结点Size接近。最后,直接使用此Cube的位置信息来采样Mipmap中的相应位置上的结点值,即可完成对此Cube的Tracing。
对Cone中的每个Cube完成Tracing之后,当前Cone方向上的Irridiance累积结果就可以认为是Cone中所有Cube采样结果的叠加。这个看起来虽然有些不太合理,但是视觉效果上的近似已经很不错了。此外,作者也对该近似方法进行了数学上的分析(step by step pre-integration):将每个Cube认为是Transparent属性,然后Irridiance会在其中进行不断的传递。具体可以见这论文里边的详细内容。
原文链接:https://blog.csdn.net/bugrunner/article/details/8962535
cone(从受光点发出)中心点的射线将会与公告板所在的平面存在一个交点,而这个点在屏幕空间中的位置信息则被用作贴图的坐标,交点处对应的cone圆面的半径则被用于推导对应的specular cone预处理的mip层级。不幸的是,虽然这种方法能够用一种非常直观的方法来实现对复杂面光源的模拟,但是却无法满足前面剔除的第二个条件(固体角接近0的时候其效果要与点光源保持一致):
- 由于自发光贴图的预处理过程是在一个平面上完成的,因此其能够表达的固体角是受限的(无法表示背面的固体角?)
- 在cone中心的射线与平面无交点的时候,没有输出数据
- 这个模型中的光照向量l是未知的或者说只能用反射光线方向来近似。
Cone Intersection
cone tracing的结果可以通过代数运算的方式得到,不需要提前预处理。cone跟sphere的相交检测可以通过Oat的cone-cone相交检测算法[15]求得,不过这种方法消耗过高,不适合用于实时渲染,这个方法的一个替代算法是Drobot[7]提出的,这种方法可以用一个朝向shading point的圆盘(disk)来实现跟cone的相交检测。之后对于相交区域,再用一个能够近似高光NDF的多项式逐段逐段进行积分即可。
如果使用Drobot算法,cone跟disk的相交区域就会是一个径向对称的区域,这种对称区域对于渲染那些拉伸后的高光(stretched highlights)就无能为力了,而这种拉伸高光是微表面高光模型中的一个重要特征。另外跟公告板反射一样,用这种算法得到的shading model也没有对应的light vector。
Specular D Modification
UE4在2012年提出的一种实现方法[14]就是根据光源的固体角来修正高光的distribution。其实现原理就是将光源的distribution看成光源固体角的D(h)项(Specular Normal Distribution Function),在这个理论的基础下,两个Specular Distribution的卷积就可以用将这二者对应的cone的夹角相加得到的新的cone所对应的Specular Distribution来近似。
具体而言,就是将公式3中的α 值转换成一个cone angle,将这个角度跟光源对应的固体角相加,再转换回公式3中的α 值,这个过程可以用如下的公式来模拟:
虽然这个实现算法非常高效,但是其不符合前面对于面光源的三点要求中的第一点(Consistent material appearance),因为对于光滑的材质在使用大的面光源照射的时候,得到的效果却是粗糙的(从上面的公式可以看出,当sourceRadius很大的时候,不管α 是否为0,新计算的粗糙度都会比较大)。虽然存在瑕疵,但是这个模型在高光的NDF比较紧凑(啥意思?能量比较集中?)的时候(比如说Blinn-Phong高光模型)表现非常好,不过对于UE4所选用的GGX Shading Model,这种算法的效果就不是很好了。
Figure 10:左图参考, 右图specular D modification方法效果,可以看到对于高光的模拟做得不是很好Representative Point
如果我们能够直接以面光源上的一个点的输出来代替面光源的整个输出的话,那么UE4之前的点光Shading Model就可以直接应用了,面光源上的这个点就被称为representative point,这个点的选取通常是依照最大贡献值来确定,比如说,对于Phong distribution而言,通常会选取距离反射光线最近的那一点作为representative point。
这个方法曾经在文献[16][22]中被提到,不过其效果并不符合能量守恒定律,虽然通过移动光源的原点(origin of emitted light),可以增加光源的固体角从而减轻问题但无法完全消除。想要完全的修正这个问题会比较复杂,因为输出能量的差异对于不同的材质是不一样的,比如说,对于一个粗糙的模型而言,调整输入光的方向对于输出能量来说可能影响不大,而对于一个光滑的模型而言,其输出的能量的改变就非常的明显了。
下面看下不同的光源应用这种方法的具体实施方案。
Sphere Lights
球面光源的辐射照度(Irradiance)在光源球体高于水平面(horizon)的时候等同于点光源的辐射照度[18],虽然这个结论并不直观,但是大大减少了我们的计算量:如果我们能够接受光源球体沉入到水平线以下位置的误差的话,那么我们在实际计算中就只需要着重考虑高光部分即可。
查找与反射向量(结合下文来看,这个地方的反射向量应该指的是View Dir的反射向量)具有最小夹角的点(这里查找的点是否就是可以用于替代整个球面光源的点光源的点?)就等同于查找到这个反射向量的距离最小的点,其公式给出如下:
其中L指的是从当前着色点到光源中心的向量,而sourceRadius就是光源的半径,r指的是反射向量,centerToRay指的是从球心向着r引出的垂线的方向,注意上面的saturate是为了应对反射光线与球体相交的情况,这个时候的closestPoint就不是球面上的点,而是光源中心到反射光线上的最近一点了。
通过将光源的原点从球心移动到球面,可以有效的拓宽高光的distribution范围,虽然这个地方处理的并不是微表面distribution,但还是可以借用Phong Specular Distribution公式来解释:
其中ϕr 是r跟L之间的夹角(在Phong高光公式中,这个夹角应该是R跟V之间的夹角),而是ϕs 球面包角(目测是从当前的着色点向着球体引两条切线,这两条切线的夹角,就是包角)的一半,公式12是经过归一化处理的,即对于球面积分得到的结果应该等于1,而公式13则是非归一化的了,而且由于指数p的存在,其积分的结果可能比1大很多。
为了保证能量守恒,即公式13的积分结果等于1,就需要对公式进行一个处理,整个实现过程类似于前面Specular D中为了应对distribution范围增大而做的处理一样,即为公式13重新设计一个归一化的因子。对于GGX而言,这个归一化因子是
因此这里归一化的做法就是将原有的归一化因子除以新的distribution范围的归一化因子,得到的归一化因子等于如下公式:
从结果上来看,球面代表点方法符合前面给出的所有条件:能够满足能量守恒定律,不管光源类型是什么,都能够得到正确的一致的表现。光滑的材质渲染的结果也是光滑的,而且由于这个做法只会改动到BRDF的输入数据,整个Shading Model是不受影响的,而且这种算法的实现非常的高效。(这个算法只适用于球面光源)
Figure 12: 左边参考, 右边是Representative point方法的结果,虽然能量守恒上并不完美,但是跟参考的结果已经相差不远了Tube Lights
球面光源比较适合模拟那些灯泡类型的光源,而现实世界中最常见的荧光灯则比较适合使用胶囊状的灯管光源(tube light)来模拟。为了简化处理,最开始这里先处理一下只有长度而没有半径的线光源(radius = 0)。高于水平面horizon的线光源的辐射照度Irradiance可以通过公式积分得到[16,17]:
其中,跟指的是从着色点到线光源的两个端点的向量。
为了避免得到负数irradiance,或者分母为0的情况,同时考虑到当线光源的长度过渡到0的时候irradiance也应该衰减到0的合理推断,这里需要对上面的公式做一下修正:[图片上传失败...(image-329acb-1594972357212)]
对于线光源的高光部分,就需要从下面的公式中求出 t 来:
Picott[16]给出的与反射向量r的夹角最小的点所对应的t值计算公式如下所示:
跟球面光源一样,这里通过用最近距离来模拟最小角度:
公式19的计算消耗更低,且其计算结果跟公式18的计算结果也非常逼近,不过这个公式由于有一些边界情况处理得不到位,导致并不是所有时候都能找到对应的最近点。
这里需要注意的是,由于公式18跟19都是将r当成一条直线而非一条射线来对待(直线没有端点,射线有一个端点),因此都没有考虑射线指向远离光源方向的情况,从而导致在射线方向从指向光源向着远离光源角度过渡的时候,出现结果从某个端点跳变到另一个端点的情况,这个问题虽然可以通过将计算结果与两个端点进行比对来修复,不过这种修复方式性价比太低,因此最终选择接受这个瑕疵的存在。
为了保证能量守恒,这里也采用跟球面光源一样的处理方式,不过由于这个地方只有一个维度,因此这里取用的是各向异性的GGX[2],各向异性GGX的归一化因子是
(对于各向同性GGX有,αx = αy = α),得到新的归一化因子:
因为上述的操作只包括对光源原点进行移动,并在这个基础上增加一个能量守恒的处理,所以可以通过累加来进行tube light的模拟。通过将线光源跟球面光源进行卷积处理,能够很好的模拟tube light的效果,其结果图13所示:
Figure 13: Tube light using the representative point method with energy conservation经过验证发现,使用Representative Point方法加上能量守恒修正能够实现对简单形状的面光源的高效模拟,以后可能会将这个方法推广到更多更复杂光源的实现上,尤其是textured quad光源,以实现复杂的多种颜色的光源。
Conclusion
经过上面的一些处理后,UE4在Shading效果,材质系统以及光源模拟上得到了很好的效果。
Bibliography
[1] AMD, CubeMapGen: Cubemap Filtering and Mipchain Generation Tool.
[7] Drobot, Micha l, “Lighting Killzone: Shadow Fall”, Digital Dragons, April 2013.
[15] Oat, Chris, “Ambient Aperture Lighting”, SIGGRAPH 2006.
[18] Quilez, Inigo, “Spherical ambient occlusion”, 2006.
[22] Wang, Lifeng, Zhouchen Lin, Wenle Wang, and Kai Fu, “One-Shot Approximate Local Shading” 2006.
网友评论