寻找着色模型入口
我们首先找到UE4的Subsurface Scattering是在哪里实现的,打开RenderDoc,发现是在StandardDeferredLighting
这一步和延迟渲染的光照一同实现的。
于是我们在Source/Runtime/Renderer
路径下搜索StandardDeferredLighting
,定位到LightRendering.cpp。
在文件中查找IMPLEMENT_GLOBAL_SHADER
,找到所用到的shader类型为FDeferredLightPS
,shader文件为/Engine/Private/DeferredLightPixelShaders.usf
,入口函数为DeferredLightPixelMain
:
IMPLEMENT_GLOBAL_SHADER(FDeferredLightPS, "/Engine/Private/DeferredLightPixelShaders.usf", "DeferredLightPixelMain", SF_Pixel); |
打开DeferredLightPixelShaders.usf
中的DeferredLightPixelMain
函数,发现输出为名为OutColor
的变量,顺着OutColor
逐个排查,找到GetDynamicLighting
函数,跳到DeferredLightingCommon.ush
文件,进一步找到GetDynamicLightingSplit
函数,计算光照的函数应该是IntegrateBxDF
:
Lighting = IntegrateBxDF( GBuffer, N, V, Capsule, Shadow, LightData.bInverseSquared ); |
随着IntegrateBxDF
跳入文件ShadingModels.ush
,发现如下代码:
FDirectLighting IntegrateBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow ) |
终于找到了所有着色模型的分叉点,这样以后研究着色模型就可以从这里作为入口了。
深入SubsurfaceBxDF
FDirectLighting SubsurfaceBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow ) |
可以看出,SubsurfaceBxDF
中首先调用了一遍默认的延迟光照DefaultLitBxDF
,然后从GBuffer
中提取出SubsurfaceColor
和Opacity
进行进一步的次表面散射计算。
我们可以将lerp(BackScatter, 1, InScatter)
这一项展开,在极坐标中画出其Transmission
分布图像,其中$α$为入射角弧度,$o$为不透明度Opacity
,下图为入射角约为$\frac\pi4$时的Transmission
分布:
可以看出,其能量大部分分布在透射方向,小部分分布在明暗分界线方向,且不透明度$o$越小,透射能量越大。
ShadowTerm&LightAttenuation
计算完BxDF之后,在光照累加时还用到了Shadow
,这也是影响SSS的一个关键。
LightAccumulator_AddSplit( LightAccumulator, Lighting.Diffuse, Lighting.Specular, Lighting.Diffuse, LightColor * LightMask * Shadow.SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation ); |
我们看一下Shadow
的定义:
struct FShadowTerms |
从DeferredLightingCommon.ush下的GetShadowTerms
中可以找到shadow
来源于LightAttenuation
:
// Remapping the light attenuation buffer (see ShadowRendering.cpp) |
而LightAttenuation
是从ScreenShadowMaskTexture
中采样得到,我们可以用RenderDoc抓取ScreenShadowMaskTexture
查看。
当前场景的ScreenShadowMaskTexture
:
当前场景的ScreenShadowMaskTexture
的B
和A
通道均为1
:
R
通道记录了阴影信息:
G
通道记录了次表面阴影信息:
将RG
通道叠加,可以看到球上隐约绿色的部分即为次表面散射照亮区域:
换一个场景更加明显,其中立方体使用的是另一套次表面散射着色模型SubsurfaceProfile
:
ScreenShadowMaskTexture写入
从RenderDoc中可以找到ScreenShadowMaskTexture
写入的地方
在ShadowRendering.cpp的FSceneRenderer::RenderShadowProjections()
中:
// Normal deferred shadows render to the shadow mask |
该函数在LightRendering.cpp中的FDeferredShadingSceneRenderer::RenderLights
函数中被每个光源调用一次:
在ShadowRendering.cpp的注释中还可以找到不同通道的含义:
// Light Attenuation channel assignment: |
继续沿着RenderShadowMask()
找到FProjectedShadowInfo::RenderProjection()
,其中调用了函数BindShadowProjectionShaders()
,从名称可以看出在这里绑定了shader,进一步找到绑定的shader的类为TShadowProjectionPS
,在下面的宏定义中可以找到所用的shader文件为ShadowProjectionPixelShader.usf:
|
进入shader文件,找到OutColor
的来源:
float FadedSSSShadow = lerp(1.0f, Square(SSSTransmission), ShadowFadeFraction * PerObjectDistanceFadeFraction); |
接下来找到SHADINGMODELID_SUBSURFACE
分支下次表面分量的来源为ManualPCF
采样函数:
SSSTransmission = ManualPCF(ShadowPosition.xy, Settings); |
采样的数据来源于CalculateOcclusion
函数计算出的Occlusion
,其中计算方式为:
float4 Thickness = max(Settings.SceneDepth - ShadowmapDepth, 0); |
其中Settings.DensityMulConstant
的计算方式为:
float Opacity = GBufferData.CustomData.a; |
于是可以画出Occlusion
项的函数图像:
可以看出当Opacity
从1下降为0,Density
随着从无穷大下降至0;而随着Thickness * Density
的增大,Occlusion
从1下降至趋近于0。当Occlusion
为1时表示没有遮蔽,为0时完全在阴影中,这与常识相符。
以上就是光栅管线中SSS相关的部分,接下来会有一篇解决RayTracing管线SSS错误问题的相关文档。