本篇文章的理论基础主要来自SIGGRAPH2011的一篇PPT(见专栏导言附录7),主要使用了预积分(LUT)的方案。
以及部分参考了GDC动视分享(见专栏导言附录4),这也是目前UE在用的Separable SSS的方案(当然UE里也有预积分的方案)。

随着模型越来越精细,如果我们渲染能力不提升上去的话,会不可避免的陷入“恐怖谷”。所以有必要对人体材质进行更高级的渲染。而且除此之外,我们还会对皮肤进行更“精美”的定制(),以符合人的审美。(这个不难理解,毕竟现在是个手机相机都附带类似美颜的功能了233)。
皮肤渲染技术发展史

渲染分析

除了上面PPT说的部分,一个完整的皮肤渲染包含了很多,比如细节纹理等。本篇我们准备的素材来自《底特律变人》中的Kara小姐姐,下图是游戏中的一段CG:

SSS(次表面散射)
关于次表面散射的理论部分,可以参考我单独写的文章,本篇采用的是Pre-Integrated Skin的方案。
(1)计算曲率
看完上面的文章,你应该看完了曲率公式的推导,那么曲率怎么在shader里求呢?还是以下面的这篇siggraph为例:

我们需要知道模型的normal以及pos,然后利用fwidth函数即可。
float deltaNormalWS = length(fwidth(N));
float deltaPosWS = length(fwidth(posWS));
//引入Bias和Scale进行调节
Curvature = saturate(CurvatureBias + CurvatureScale * 0.001 * deltaNormalWS/deltaPosWS);

不过,需要注意的是在低端机下(不支持OpenGLES2.0 +),无法使用fwidth这个API。代替的方式是我们可以预计算曲率,生成一张曲率图。
(2)Bent Normal

为了处理皮肤细小表面的细节,siggraph中提到对光照的不同通道使用不同的法线贴图(4种),但是这种做法过于耗费内存,所以提出了Bent Normal的做法。注意,这里的Bent Normal和计算AO时提到的Bent Normal并不完全相等(都是修改法线的技术)。这里是模拟皮肤表面的表面细节,是用模糊的方法(采样法线图的mipmap)做的,也可以叫做pre-integrated normal maps,AO里提到的Bent Normal则是从一个点到其他点的未被遮蔽的所有法线的平均法线方向。
float4 bentNormal = SAMPLE_TEXTURE2D_LOD(_NormalMap,sampler_NormalMap,input.uv,_BentNormalMipLevel);
bentNormal = UnpackNormalScale(bentNormal,_Normal); //和Normal共用一套sampler
需要注意的是上面求得的是tangent space的bentnormal,用TBN矩阵转换一下。
在Unity中对比下使用BentNormal前后:


(3)SSS效果总览:



Specular Reflections
动视PPT的分享中介绍到这部分,使用了Two Lobes Rendering(specular lobes,也可以叫做Dual Specular(GGX))+ Microgeometry Render(Noise)的技术。
Two Lobes Rendering 这部分其实就是用不同的Roughness叠加了两层高光,需要注意的是要遵循能量守恒。
而Microgeometry Render实际上是就是多用了个Detail Map。
Specular Reflections效果总览:




GI(Faking)
在StandardLit中我已经讲过SH+IBL的实现,不同的是specularAO的处理。在专栏导言附录4的PPT中,作者同时采用了多种AO结合。
如何在URP中拿到SSAO的结果在SimpleLit中也讲了,不同的是在StandardLit中我们是通过GetSpecularOcclusionFromAmbientOcclusion函数得到的Specular Occlusion,在PPT中作者提出了一种基于经验的获取面部皮肤Specular Occlusion的方式。

float3 specularOcclusion = lerp(pow(occlusion,_SpecularPow),1.0,saturate(NoV * NoV - 0.3));
以及作者提到了一种trick方式:Color-Bleed AO
下面逐个演示效果(min with SSAO):




GI总览:



透光
见PPT 164-191
谈到了三种技术:(单采样,多重采样和球谐)
- Single-Sample Transmittanc
- Multi-Sample Transmittance
- Spherical Harmonics Transmittance


三种方案优点和缺点:

作者最后提到以上三种方案都是针对procedural light,而对于light probes的散射则可以忽略不计(影响很小)。
文中还提到了对multipole diffusion profile进行简化。
本篇我只实现简单的透光,而且transmission直接给了个定值,一些不该透射的区域也受到了影响(因为是游戏里扒的素材,游戏内并没有实现透光效果,所以没thicknessMap,我也懒得在SD里做一份了),这里主要侧重于如何实现。从thicknessMap中计算或者复杂些按照上诉方法都可以,效果肯定比下面gif里实现的更好,更细腻。


后记
本篇+理论篇的博客撰写花费了大约两周的时间,然而敲代码的时间加起来不到一天= =果然只敲代码的话还是爽(本篇纯代码,不含SG或者ASE),但是要整理成图文真的很费时费力。
对于Skin的实现,还有Disney SSS,目前UE4和Unity HDRP都有相应的实现方式,改天也试着实现一下。
换了个模型查看效果(gif录制有些地方压缩后变色了没办法):




3.21 后记
今天看了下URP官方实现的skinlit(因为要写hairLit顺便看了下),整理了一下差异,有时间再讲讲一些细节:


官方Demo 视频:https://www.bilibili.com/video/BV1g8411p78X
官方Demo github:https://github.com/UnityTechnologies/URP-Defender-Character-Demo