【shader】UE4 Subsurface Profile shader提取

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/birdy_/article/details/82620923

尝试把UE4里面的人像提取出来(因为无法直接获得UE4使用的shader代码),这个文章是基于UE4使用的sss。此外还有其他的sss呈现方式。
原作连接https://docs.unrealengine.com/en-us/Engine/Rendering/Materials/LightingModels/SubSurfaceProfile
(我突然发现这个人像又没有头发又没有眼睛哈哈哈哈哈省了很多工作量呢)

算法分析

UE4的文档中指出:
1. 这个shader基于屏幕空间上而不是纹理空间【第一段】
2. 对于非镜面反射,使用一种类似于高斯模糊的双通滤波方式,最后再将镜面和非镜面部分叠加(镜面部分会受到位置的影响)【Technical Details】
4. 一个实现上的优化:把镜面和非镜面光照(view-dependent and non-view-dependent lighting)分别存储在64bit的高低32bit上。【Technical Details】

接下来结合论文
http://www.iryoku.com/sssss/downloads/Screen-Space-Perceptual-Rendering-of-Human-Skin.pdf

论文提出的关于sss的前提:
前提
由此得出了一个不知道怎么得出来的满足前提的公式(这不是在建模的我吗)
这里写图片描述
α β 的值需要最后重新确认。作者取出的值大概在11和800.
所以觉得很好的那些效果其实实现的核心只是一个滤波/模糊(没想到吧)

最后论文作者邀请了一堆人来目测这个结果和之前的结果哪个更像人(……)

Main Parameter

接下来看看UE4中sss相关的参数。

Property Name Description 猜测影响
r.SSS.Scale Can be used to scale the effect for quick experiments. Setting this to 0 will disable the effect. Setting numbers higher than 0 will increase the effect that can be seen in the image sequence below. 对应 α
r.SSS.SampleSet Sets the number of samples used. Decreasing this will cause the effect to run faster. However, this will mean that the effect will have a lower quality, and rendering artifacts could show up. 影响滤波采样率(后来写的过程中感觉应该是用户自定Render Texture的采样率)
Scatter Radius The distance in world space units to perform the scatter. 对应 β
Subsurface Color The Subsurface Color can be used as a weight for the Subsurface effect. Black means there is no Subsurface scattering. White means all lighting is entering the material and gets scattered around. A non-grayscale value gives more control over what color contributions are entering the surface, resulting in a more complex looking shading. F(Col*Subsurface Col*Falloff Col)+Col*(1-Subsurface Col)
Falloff Color The Falloff Color defines the material scattering color once the light has entered the material. You should avoid using a vivid color here if you want to get a more complex shading variation over the area where you see the scattering. F(Col*Subsurface Col*Falloff Col)+Col*(1-Subsurface Col)

前面两个处理的是shader的总的参数,后面三个处理的是单个物体的参数。
解释一下最后两个公式。虽然是我脑补的……
根据sss的特点,照射到物体的光线一部分直接正常的反射,另一部分由于物体性质进入内部也就是我们要做的sss。所以分为两部分。
接下来考虑到进入内部的衰减,也就是Falloff Color。猜测这个颜色如果过于明亮权值会偏大,整个图的结果会比较模糊。
F()是指对这一部分进行滤波。

实现

虽然知道怎么做了但是我写不出来啊(叉腰……)打算用Unity实现。但是这不完全的说应该是我做的第一个shader,所以可能写的不是很好看orz欢迎批评。

最后的连接在
https://gitlab.com/Birdy-C/Subsurface-Shader
(好像太大了github传不上去)

入门学习资料

(顺便找到了Unity的插件又不想自己写了orz

第一反应是和景深很像了。我的学习流程大概是,了解shader,看了看后处理,着(fu)手(zhi)实现一个景深。
贴几个我用来入门的网站

景深以及这个系列前面几篇
跟着这个大大的一系列从改亮度、高斯模糊写到景深,能够对后处理有一个基本的了解。

荧光效果
跟着这个网站了解了自定义纹理的使用。

此外发现shader是可以直接获取的
https://unity3d.com/get-unity/download/archive
shader
我尝试了一下用standard shader开始改,后来发现我做不到(
于是最后是用了右键生成shader之后的那个shader开始改(……)

小哥哥推荐了我ShaderForge,然后我打不开(叉腰),大家可以尝试一下。

可以通过改变相机的深度值来改变渲染顺序,参考链接.

冷静分析

需要得到的:
正常的shader得到的图,分为镜面和非镜面光照。这个网上找个pbr吧……分高位地位放一起。
此外因为一个场景中可能有不同的物体还需要传递不同的Scatter Radius【1】、Subsurface Color【2】、Falloff Color【3】这三个参数。
感觉重复绘制很麻烦……但是好像Unity也没什么办法一个相机绘制不同的图。希望内部有什么迷幻的加速策略……

然后得到这三个数据之后,需要计算出模糊之后的图。【4】
最后把模糊之后的图和原图按一定比例叠加。【5】

所以最后场景里有总共【5】个相机。一个为Main,另外四个挂在这个下面以减少同步运动的难度。然后渲染顺序是【1】【2】【3】>【4】>【5】;

SSS.Sample Set影响纹理模糊大小,它和SSS.Scale一起挂在MainCamera的SSS_Setting脚本下。

  • Material里面可以定义shader的参数,然后有些值在shader里改了忘了在具体的Material里改。

  • 导入的模型无法自己设置shader

  • 不知道为什么vs的shader对齐那么迷……

  • 最大的坑大概是不会写shader(摊手)全程靠改教程

脑补出来的算法

很难折腾的是模糊。它给出的核的宽度的定义很难加以应用。

我一开始在想, 模糊半径小的核多次叠加能不能当一个大的模糊半径的高斯核用?然后打开matlab算了一下应该是不能……

然后后来参考了景深的用法(所以最后结果没有完全按照核的宽度来,求出的半径应用起来更类似于一个权重的概念)。
在运行前,先进行横向竖向两次的模糊,得到两张图。然后根据R的大小在原图和模糊之后的图中间插值得到近似的结果。
最后对于横向和纵向两个结果,用R进行加权。(这个加权没什么科学的道理,只是我想了一下比如一个1*n的卷积核的颜色完全是由n来决定的而和纵向没有关系,所以觉得不能简单的取平均)

代码如下

    // 分别取两个方向模糊之后的结果
    fixed4 blurX = tex2D(_BlurTexX, i.uv);
    fixed4 blurY = tex2D(_BlurTexY, i.uv);

    // 这两个是我们求出来的半径大小(我手动调了一下参数调到了0-1之间)
    float RX =  //……
    float RY =  //……

    // 用插值近似了一下XY方向模糊之后的结果
    fixed4 finalX = lerp(ori, blurX, clamp(RX, 0, 1));
    fixed4 finalY = lerp(ori, blurY, clamp(RY, 0, 1));

    // 用RX RY作为权重
    fixed4 final = (finalX * RX + finalY * RY) / (RX + RY);

没做的

本来记录Scatter的Texture 只需要一位float了,但是不会写所以变成GBA。

因为很懒,所以直接用了默认的shader,所以,没有镜面光也就没有把镜面光分离出来。

那些参数还不是很准确,可能有的时候调参数的时候会感觉自己瞎了(

以及,有的时候模型和背景之间的分界线可能没法那么精确。在两者临界处可能会有意义不明的分界。

结果贴图

原图
原图
是一个免费的宝石素材

最后的结果(看不出啥来)
糊了

深度图
深度图

X方向梯度图
X方向梯度

Y方向梯度图
Y方向梯度图

然后对于Scatter Radius的理解不知道准不准确。
Radius
最前面的Radius最小。Radius最小的时候可以理解为场景表现的东西在远处,需要均匀的模糊。Radius大的时候边缘做的模糊比较少,能保持一个清晰的边缘。

最后的效果不知道如何截图——
觉得自己是瞎的()
大概截个人脸图吧,我也不知道怎么评价它的效果(顺便发现CSDN会压图片 口亨)
result

猜你喜欢

转载自blog.csdn.net/birdy_/article/details/82620923