Unreal Forward下伪Subsurface

Unreal Forward下伪Subsurface

因为一些原因切换到了虚幻的Forward渲染管线,结果场景里面的皮肤那是全部都失去了次表面散射的效果,后来去官网社区搜索了半天才知道Forward目前不支持次表面散射,后续更新支持遥遥无望。做为暂时解决方案,索性自己试着在虚幻里面用材质做了一个次表面散射。想到可以也有童鞋和我一样遇到这种问题,便吧填坑的经验分享一下。既然要做次表面散射那就得选择用那种方式,目前有TSD和Preintegrated以及ScreenSpace。综合下来还是选择了ScreenSpace,因为是ScreenSpace所以我们的伪造材质是Post材质(这里的Post材质设置得切换到BeforeTonemap,不然会导致次表面散射的结果不能被Tonemapp处理效果会很奇怪)

首先建立一个Post材质,直接代入Jorge Jimenez的开源代码即可(这里要注意的是因为ScreenSpace次表面使用了Stencil来优化处理区域,而虚幻并没有直接暴露Stencil的使用所以我们得把输入参数的Depth和Stencil换成CustomDepth和Stencil)。而且直接应用在PostProcessVolume上,再把需要次表面处理的模型勾上CustomDepth的选项便可以实现材质伪造的Subsurface效果了(因为这里是XY两个方向处理所以我们得使用两个Custom模拟双Pass分别对XY两个方向处理,最后叠加在一起即可,皮肤材质使用普通的DefualtLight制作就好)

因为是用了Forward那就干脆采样ShadowMap吧透射(Transmittance)也一起做了,于是乎采样了DICE的透射模型,具体可以看:zhuanlan.zhihu.com/p/33

这里我封装了一个Transmittance的函数,因为Forward关系可以直接在材质里面采样到ShadowMap,通过float ShadowAtten = Square(Texture2DSampleLevel(LightAttenuationTexture, LightAttenuationTextureSampler, UV, 0)); 可以获得阴影贴图(UV为ScreenPosition)

最后把这个函数直接连接在皮肤材质的自发光即可。

得注意的是这个伪造的透射依赖于ShadowMap来判断阴影面是否透光,虚幻的渲染管线没有暴露各种光源的LightVector,所以场景里面只能用AtmosphereLightVector这一个LightVector和其ShadowMap。可以试试结合UnlitLightingSystem做MultiLight Transmission。


这里插入一个第一个图片的CustomNode使用的代码;

float4 kernel[] = {
float4(0.1631199, 0.5303371, 0.8999063, 0),
float4(0.001416983, 6.00041e-06, 7.560949e-23, -3),
float4(0.005145977, 5.495354e-05, 5.589978e-17, -2.520833),
float4(0.00807552, 0.0001670088, 9.126089e-13, -2.083333),
float4(0.01167975, 0.0004200887, 1.166694e-09, -1.6875),
float4(0.01601504, 0.001182777, 1.885376e-07, -1.333333),
float4(0.02186621, 0.003401761, 5.904319e-06, -1.020833),
float4(0.03074967, 0.007605369, 5.265377e-05, -0.75),
float4(0.04495762, 0.01359771, 0.0002632917, -0.5208333),
float4(0.07157286, 0.02296654, 0.001545455, -0.3333333),
float4(0.09067899, 0.04222976, 0.004814911, -0.1875),
float4(0.07607266, 0.08207474, 0.01207155, -0.08333334),
float4(0.0402088, 0.06112473, 0.03129287, -0.02083333),
float4(0.0402088, 0.06112473, 0.03129287, 0.02083333),
float4(0.07607266, 0.08207474, 0.01207155, 0.08333334),
float4(0.09067899, 0.04222976, 0.004814911, 0.1875),
float4(0.07157286, 0.02296654, 0.001545455, 0.3333333),
float4(0.04495762, 0.01359771, 0.0002632917, 0.5208333),
float4(0.03074967, 0.007605369, 5.265377E-05, 0.75),
float4(0.02186621, 0.003401761, 5.904319E-06, 1.020833),
float4(0.01601504, 0.001182777, 1.885376E-07, 1.333333),
float4(0.01167975, 0.0004200887, 1.166694E-09, 1.6875),
float4(0.00807552, 0.0001670088, 9.126089E-13, 2.083333),
float4(0.005145977, 5.495354E-05, 5.589978E-17, 2.520833),
float4(0.001416983, 6.00041E-06, 7.560949E-23, 3),
};

float4 ColorTex = SceneTextureLookup(UV, 14, false);
float DeptnTex = -SceneTextureLookup(UV, 13, false);

float distanceToProjectionWindow = 1.0 / tan(0.5 * radians(20));
float scale = distanceToProjectionWindow / DeptnTex;

float2 Step = -SSSPower * scale * BlurDir;
Step *= 1;
Step *= 1.0 / 3.0;

float4 FinalColor = ColorTex ;
FinalColor.rgb *= kernel[0].rgb;

float2 BlurUVOffse = 0;
float4 BlurColor = 0;
float BlurDepth = 0;
float s = 0;
for (int i = 1; i < 25; i++){
BlurUVOffse = UV + kernel[i].a * Step;
BlurColor = SceneTextureLookup(BlurUVOffse, 14, false);
BlurDepth = -SceneTextureLookup(BlurUVOffse, 13, false);

s = saturate(300 * distanceToProjectionWindow * -SSSPower * abs(DeptnTex - BlurDepth));
BlurColor.rgb = lerp(BlurColor.rgb, ColorTex .rgb, s);
FinalColor.rgb += kernel[i].rgb * BlurColor.rgb;
}
return FinalColor * 0.5;


以上内容为Forward渲染管线下不能使用Subsurface的暂时伪造办法,因为我场景要求不高所以没有继续改善,大家如果有更深需要可以自己改造(比如蓝图计算Kernel数组传递到RT给Post材质读取SubsurfaceColor用)。后续虚幻更新应该会在Forward下支持Subsurface,毕竟都DigitalMike了手动滑稽。

编辑于 2022-05-19 02:52