您好,欢迎来到外链网!
当前位置:外链网 » 站长资讯 » 专业问答 » 文章详细 订阅RssFeed

自制快速模拟次表面散射

来源:互联网 浏览:43次 时间:2023-04-08

在unity中,shader以vf形式运行,很难获得mesh本身的体积,但是我们依然可以用一些经验公式模拟出次表面散射,做出如玉石等效果。先上核心算法:

inline float3 SSS(float3 lightDir, float3 viewDir, float3 normal, float thickness, float3 lightColor){float3 H = normalize(lightDir + normal * _Distortion);float VdotH = pow(saturate(dot(viewDir, -H) + 0.5), _Power) * _Scale;return lightColor * VdotH * thickness * _SSColor.rgb;}

所有的传入向量都要统一坐标轴且归一化(这里用的是世界坐标系),因为需要用到光照的属性,所以只能在forward pass中计算。这里H代表灯光与法线之和,Distortion决定物体透射效果受法线的影响程度。

VdotH与普通的高光运算相仿,只是需要反转法线,使其在背面受到影响。剩下的就是thickness了,thickness比较有趣,如果不使用自己编写的离线渲染工具,很难制造出表现表面厚度的贴图,而离线渲染难度又非常高,这里有两个方法,如果是一块石头玉石等表面不平滑的物体,可以反转遮挡贴图来模拟次表面效果,如果是人体表皮等特殊物体,可以让画师在PS中手动描绘,如头发,耳朵等部位涂浅色,将其他不透光的部位涂深色。

然后是光照衰减的运算,因为显示次表面透光效果的面在光的背面,所以一定会受到自身的阴影,而我们又不能因此关闭掉renderer的cast shadow,否则物体没有投影会显得非常诡异,所以这里采用了自定义的光照衰减计算,对正面的PBR采用有阴影计算,对背面的透射效果采用无阴影计算,将AutoLight.cginc中的宏定义修改一下并copy到shader中即可:

#ifdef POINT#define UNITY_LIGHT_ATTENUATION(destName, destNoShadow, input, worldPos) \? ? unityShadowCoord3 lightCoord = mul(unity_WorldToLight, unityShadowCoord4(worldPos, 1)).xyz; \? ? fixed shadow = UNITY_SHADOW_ATTENUATION(input, worldPos); \? ? fixed destNoShadow = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL; \? ? fixed destName = destNoShadow * shadow;#endif#ifdef SPOT#define UNITY_LIGHT_ATTENUATION(destName, destNoShadow, input, worldPos) \? ? unityShadowCoord4 lightCoord = mul(unity_WorldToLight, unityShadowCoord4(worldPos, 1)); \? ? fixed shadow = UNITY_SHADOW_ATTENUATION(input, worldPos); \? ? fixed destNoShadow = (lightCoord.z > 0) * UnitySpotCookie(lightCoord) * UnitySpotAttenuate(lightCoord.xyz);\? ? fixed destName = shadow * destNoShadow;#endif#ifdef DIRECTIONAL? ? #define UNITY_LIGHT_ATTENUATION(destName,destNoShadow, input, worldPos) fixed destName = UNITY_SHADOW_ATTENUATION(input, worldPos); fixed destNoShadow = 1;#endif#ifdef POINT_COOKIE#define UNITY_LIGHT_ATTENUATION(destName,destNoShadow, input, worldPos) \? ? unityShadowCoord3 lightCoord = mul(unity_WorldToLight, unityShadowCoord4(worldPos, 1)).xyz; \? ? fixed shadow = UNITY_SHADOW_ATTENUATION(input, worldPos); \? ? fixed destNoShadow = tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL * texCUBE(_LightTexture0, 香港vps lightCoord).w;\? ? fixed destName = destNoShadow * shadow;#endif#ifdef DIRECTIONAL_COOKIE#define UNITY_LIGHT_ATTENUATION(destName,destNoShadow, input, worldPos) \? ? unityShadowCoord2 lightCoord = mul(unity_WorldToLight, unityShadowCoord4(worldPos, 1)).xy; \? ? fixed shadow = UNITY_SHADOW_ATTENUATION(input, worldPos); \? ? fixed destNoShadow = tex2D(_LightTexture0, lightCoord).w; \? ? fixed destName = destNoShadow * shadow;#endif

这里同时计算一个正面PBR用到的dest和一个背面次表面无视阴影的destNoShadow。然后再在pass中计算次表面效果并将结果直接加到返回的色彩上:

c.rgb += SSS(lightDir, worldViewDir, o.Normal, tex2D(_SSTexture, IN.uv_SSTex).r, _LightColor0.rgb * noShadowAtten);

这里的lightDir,worldViewDir,o.Normal都已经是世界坐标下的归一化向量,noShadowAtten则是无视阴影的attenuation,考虑到次表面物体会吸收部分光照,这里的光线衰减是可以进行自定义计算的。

为了表现效果,这里用一个人头雕像,雕像材质半透明且表面雕刻较为粗糙(模型通过正版途径购买):


从侧面看,还是以PBR反射为主的着色,次表面效果较弱,这也基本符合物理定律,背面效果:


背面次表面效果强烈且有层次感,符合预期目标。

54780158