franciscochonge 发表于 2022-4-19 10:44

Vrchat中实现次表明散射(SSS)材质【持续更新】

参考文献:GPU-Pro2
本贴github地址:BlazeWYSB/SSS_LUT_Tool: create lut texture and realize SSS shader (github.com)
先上效果图


https://www.zhihu.com/video/1498881372097454080
一、GPU Pro2上笔记

次表明散射和三个参数有关:曲率、凹凸度、阴影(光照角度)
使用预积分(光照函数是计算蒙特卡洛积分,将其中一部分积分结果打成表读取做近似,减少积分中的变量,就是一种常用的优化手段)
球谐函数调参数不能同时模拟曲率高和曲率低的两种情况
使用预积分记录曲率和N.dot(L)(光照角度)的纹理。
这种做法假设所有的皮肤都类似于一个球体,到达一个给定点的散射光取决于该点本身的曲率,但实际上,它取决于表面上所有周围点的曲率。因此,这种近似将在光滑的表面上工作得很好,没有曲率的快速变化,但当曲率变化太快时就会失效。好在大部分模型用mesh表现皮肤光滑结构,使用法线贴图表现曲率变化快的细节,误差不会太大。
二、生成预积分纹理

1、使用python验证拟合曲线

       研究者对一个超平坦的皮肤光照,用一张图表示距中心点的距离和颜色的关系,这张图就叫扩散剖面。这样的以距离为自变量,颜色为因变量的曲线就是我们的拟合曲线。最终,研究者发现可以用用6个高斯函数来拟合这条曲线,反映皮肤材质。



扩散剖面



拟合函数的参数

因为matlab各种绿色版问题,还是选择万能的python来画函数。
注意要安装以下库:pip install numpy、pip install matplotlib
import matplotlib.pyplot as plt
import numpy as np
import meep as mp
import math


class Vector3():
        def __init__(self, x, y, z):
                self.x = x
                self.y = y
                self.z = z

        # 重载 "*" 操作符
        def __mul__(self, a):
                return Vector3(self.x * a, self.y * a, self.z * a)
        # 置为零向量
        def zero():
                self.x = 0
                self.y = 0
                self.z = 0
        # 重载 "==" 操作符
        def __eq__(self, other):
                return self.x == other.x and self.y == other.y and self.z == other.z

        # 重载 "!=" 操作符
        def __ne__(self, other):
                return self.x != other.x or self.y != other.y or self.z != other.z

        # 重载一元 "-" 操作符
        def __neg__(self):
                return Vector3(-self.x, -self.y, -self.z)

        # 重载 "+" 操作符
        def __add__(self, other):
                return Vector3(self.x + other.x, self.y + other.y, self.z + other.z)

        # 重载 "-" 操作符
        def __sub__(self, other):
                return Vector3(self.x - other.x, self.y - other.y, self.z - other.z)




        # 重载 "/" 操作符
        def __div__(self, a):
                if (a != 0):
                        return Vector3(self.x / a, self.y / a, self.z / a)
                else:
                        return None

        # 重载 "+=" 操作符
        def __iadd__(self, other):
                self.x += other.x
                self.y += other.y
                self.z += other.z
                return self

        # 重载 "-=" 操作符
        def __isub__(self, other):
                self.x -= other.x
                self.y -= other.y
                self.z -= other.z
                return self

        # 重载 "*=" 操作符
        def __imul__(self, a):
                self.x *= a
                self.y *= a
                self.z *= a
                return self

        # 重载 "/=" 操作符
        def __idiv__(self, a):
                if (a != 0):
                        self.x /= a
                        self.y /= a
                        self.z /= a
                return self

        # 向量标准化
        def normalize(self):
                magSq = self.x * self.x + self.y * self.y + self.z * self.z
                if (magSq > 0):
                        oneOverMag = 1.0 / math.sqrt(magSq)
                        self.x *= oneOverMag
                        self.y *= oneOverMag
                        self.z *= oneOverMag

        # 向量求模
        def vectorMag(self):
                return math.sqrt(self.x * self.x + self.y * self.y + self.z * self.z)

        # 向量显示
        def toString(self):
                print
                "{x:" + str(self.x) + ",y:" + str(self.y) + ",z:" + str(self.z) + "}"


# 向量点乘
def dotProduct(va, vb):
        return va.x * vb.x + va.y * vb.y + va.z * vb.z


# 向量叉乘
def crossProduct(va, vb):
        x = va.y * vb.z - va.z * vb.y
        y = va.z * vb.x - va.x * vb.z
        z = va.x * vb.y - va.y * vb.x
        return Vector3(x, y, z)


# 计算两点间的距离
def distance(va, vb):
        dx = va.x - vb.x
        dy = va.y - vb.y
        dz = va.z - vb.z
        return math.sqrt(dx * dx + dy * dy + dz * dz)





def Gaussian(v, r):
    return 1.0 / math.sqrt(2.0 * math.pi * v) * np.exp(-(r * r) / (2 * v))


def Scatter(r):
    # Coefficients from GPU Gems 3‘ ‘ Advanced Skin Rendering
    return Vector3(0.233, 0.455, 0.649)*Gaussian(0.0064 * 1.414, r)\
                   + Vector3(0.100,0.336, 0.344) * Gaussian(0.0484 * 1.414, r)\
                   + Vector3(0.118, 0.198, 0.0) *Gaussian(0.1870 * 1.414, r) \
                   + Vector3(0.113, 0.007,0.007)*Gaussian(0.5670 * 1.414, r)\
                   + Vector3(0.358, 0.004, 0.0)*Gaussian(1.9900 * 1.414, r)\
                   + Vector3(0.078, 0.000, 0.0)* Gaussian(7.4100 * 1.414, r)


x = np.arange(-2.5, 2.5, 0.001)

y = Scatter(x).x
b = Scatter(x).y
f = Scatter(x).z

plt.title("gaussian kernel")
plt.plot(x, f)
plt.plot(x, y)
plt.plot(x, b)

plt.show()

可以看到,使用该函数拟合出来的曲线和文档中的一致,没有问题。
2、使用Unity预览扩散剖面

       我们回过来理一下,上面的函数表示的是扩散剖面随着距离的颜色衰减,那么不妨我们写一个简单的工具,基于改函数显示扩散剖面。我们先看一下效果:

扩散剖面的最终效果
https://www.zhihu.com/video/1498592196933062656
      如上,我设置了亮度和范围两个可调参数,可以看到使用改高斯函数拟合出来的曲线非常有皮肤的质感。
      具体实现可以参考本人github,这里说一下主要思路,使用texture2D画出扩散剖面。由于我是用imgui写的,需要注意一下,这个计算还是挺耗的,不要每帧刷新。(输入参数变化再刷新)
void button_SSSRing()
    {
      
      for (int y = 0; y < 256; y++)
      {
            for (int x = 0; x < 256; x++)
            {
                float xx = x;
                float yy = y;
                float radius = Mathf.Sqrt((256 / 2.00f - xx) * (256 / 2.00f - xx) + (256 / 2.00f - yy) * (256 / 2.00f - yy));
                Color res = Scatter(radius / 100f* range);
                DiffuseScatteringOnRing.SetPixel(x, y, res/offset);
            }
      }
      DiffuseScatteringOnRing.Apply();
    }
3、生成lut图

       lut图,既预积分图,SSS的lut图是存储了曲率与光照角度为自变量,散射颜色的一张2d纹理。他的y轴是曲率的倒数(倒数存储的一个原因是r越小分辨率越高,曲率过大的点在sss中不重要);x轴是法线点乘光照向量(NdotL),范围是-1~1。
       SSS旨在表现薄物体的透光效果,而普通的厚度图无法考虑光入射角对厚度的影响。但是如果同时记录曲率和光照角度,那就可以一定程度模拟厚度了。



我所生成的lut图

   想要计算LUT,可以考虑一个环面的情况(如下图),以推导出一个和曲率、NdL有关的公式。改公式在gpu pro2上写错了(但代码是对的)。所以强烈建议自己重新推导一遍公式加深理解,这里可以参考一个知乎帖子:Pre-Integrated Skin Shading 数学模型理解 - 知乎 (zhihu.com)


   最终就是用代码计算如下积分:


在unity实现的过程中,有一些细节:

[*]使用高斯拟合曲线的时候,因为已知距离过远,散色现象就看不到了,所以可以做一个优化:如距离大于7.4,返回颜色直接取黑色,避免6次高斯的计算。



断点调试看到该你和曲线在距离不大于7.47的时候,颜色首次不为0,0,0(r=0.001g=0,b=0)


[*]最后返回颜色会有一个除法,要注意的是,分母可能为0,然后爆NA。



精准打击爆炸0现场

这里加了个条件判断,把爆炸0给ban了(所以一定要小心诡计多端的0啊)。如果为0,则输出ndl。(不然底部会有一条黑条)

[*]直接生成效果不是很明显,转gamma空间的后才达到预期。
       最终,写了一个不错的交互面板,可以在github里找到。


4、tone-mapping

简单来说,给与积分结果pow一个比1小的数。为什么要这么做我之前一直不明白,直到我把ambient调高了,他太高以至于把背光的红色盖掉了,看不出SSS效果了。这就是tone mapping的作用,tone mapping(类似gamma矫正)把整体,尤其是暗部颜色抬高了,使得SSS效果更明显。(不一定pow(x,1/2.2),根据实际需求往上拉就行了)





ambient=0.3*albedo时,过渡带基本看不到了



tone mapping后又看得到了

二、基本diffuse

1、简单SSS模型

       有了与积分图,可以考虑简单shader了。我们先实现一个只考虑曲率、光照角度(既厚薄)的简单SSS模型(法线、高光先不考虑)。
使用贴图采样替换原来布林冯光照模型的NdL计算diffuse即可:
diffuse = _LightColor0.rgb * albedo *tex2D(_SSSLUT,float2(NoL*0.5+0.5,cuv));其中曲率可以在片段着色器中这样求出:
float cuv =saturate( length(fwidth(worldBump)) / length(fwidth(worldPos)) / 100 * _CurveFactor);


直接显示求出的曲率

      可以看到这个曲率是没有插值的,低模根本不能用。(这个精度就别高斯模糊了,救不了,还是用别的方法生成贴图吧,如用blender生成曲率)
2、高级曲率

通过blender节点烘焙贴图




      blender一个尖锐度,就得到了非常精细的曲率图,效果非常好。vrchat的各位一定有能力处理的,可以参考这个视频生成曲率图。Blender2.80 烘培曲率素材与简单的材质设定_哔哩哔哩_bilibili
       但这样我的工程还是没有形成完美闭环,后续还是应该继承到unity中完成。但总之,用blender生成的曲率图非常精细,而且不受模型缩放影响,不会因fwith造成额外计算开销。


先不考虑曲率的问题,可以看到最终效果还行。正对光有着不错的质感,背对光有不错的透光。



正对光



背对光

然而问题来了,导入vrchat后黑了:


       原因是我只写了一个pass处理平行光,没有考虑多光源。因此可以添加一个Tags{"LightMode" = "ForwardAdd"}的pass处理其他光。
该pass大致一样,加上Blend One One表示混合,还要要在fragement里面重新求lightDir和光线衰减:
Tags{"LightMode" = "ForwardAdd"}
Blend One One
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdadd
#include "Lighting.cginc"
#include "AutoLight.cginc"
...
...
...
fixed4 frag (v2f i) : SV_Target
{
      #ifdef USING_DIRECTIONAL_LIGHT//平行光下可以直接获取世界空间下的光照方向
                fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
      #else//其他光源下_WorldSpaceLightPos0代表光源的世界坐标,与顶点的世界坐标的向量相减可得到世界空间下的光照方向
                fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz - worldPos.xyz);
      #endif
...
...
...
      #ifdef USING_DIRECTIONAL_LIGHT//平行光下不存在光照衰减,恒值为1
                fixed atten = 1.0;
      #else
                #if defined (POINT)    //点光源的光照衰减计算
                        //unity_WorldToLight内置矩阵,世界空间到光源空间变换矩阵。与顶点的世界坐标相乘可得到光源空间下的顶点坐标
                        float3 lightCoord = mul(unity_WorldToLight, float4(worldPos, 1)).xyz;
                        //利用Unity内置函数tex2D对Unity内置纹理_LightTexture0进行纹理采样计算光源衰减,获取其衰减纹理,
                        //再通过UNITY_ATTEN_CHANNEL得到衰减纹理中衰减值所在的分量,以得到最终的衰减值
                        fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
                #elif defined (SPOT)   //聚光灯的光照衰减计算
                        float4 lightCoord = mul(unity_WorldToLight, float4(worldPos, 1));
                        //(lightCoord.z > 0):聚光灯的深度值小于等于0时,则光照衰减为0
                        //_LightTextureB0:如果该光源使用了cookie,则衰减查找纹理则为_LightTextureB0
                        fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
                #else
                        fixed atten = 1.0;
                #endif               
      #endif
      return fixed4((diffuse+specular)*atten, 1.0);//ambient一个够了
}
ENDCG
...
...
...

完成,模型可以接受点光照射了。
三、shadermap问题

gpu pro2上的阴影预积分需要半影,先不实现,但光普通阴影(需要shader实现,不赘述,详见GitHub),也有一点问题:



SSS no Shadow



SSS with Shadow

可以看到本来背面透光的部分,被影子遮住了,导致SSS的效果大打折扣!查阅资料,发现这个问题目前也没有什么优秀的解决方案。



资料

      因此,我选择让他不投射阴影,只接受外部阴影。而自身阴影可以后期考虑shadow map重新定制。



接受阴影的同时,肩部保留了SSS效果

四、法线分层模糊

       之前的光照模型只考虑了曲率、光照角度对散色的影响,但没考虑凹凸,也就是法线贴图。Gpu Pro上仅仅提到了要把法线分层3层,但没说具体每层法线到底模糊度是多少。因此我决定把参数放开来,用一个Color来调节各层法线模糊度。


      因为在片段着色器中,法线要从切线空间转到法线空间,如果使用mipmap,就要采样3次纹理,并且进行3次矩阵变换,太耗了。GPU Pro的推荐是读一次高清,读一次模糊,都转世界空间后插值。而我模糊法线直接读顶点法线,连模糊法线的纹理采样和矩阵变换都省了,太好了!
//SSS NdL
fixed4 bumpColor = tex2D(_BumpTex, i.uv.zw);
fixed3 tangentNormal;
tangentNormal = UnpackNormal(bumpColor);
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
float3x3 t2wMatrix = float3x3(i.TtoW0.xyz, i.TtoW1.xyz, i.TtoW2.xyz);
float3 N_high = normalize(half3(mul(t2wMatrix, tangentNormal)));
float3 rN=lerp(N_high,N_low,_tuneNormalBlur.x);
float3 gN=lerp(N_high,N_low,_tuneNormalBlur.y);
float3 bN=lerp(N_high,N_low,_tuneNormalBlur.z);
float3 NdotL=float3(dot(rN,lightDir),dot(gN,lightDir),dot(bN,lightDir));

//SSS DIFFUSE
fixed3 diffuseSSS;
float3 lookup=NdotL*0.5+0.5;
diffuseSSS.r= tex2D(_SSSLUT,float2(lookup.r,curve* _CurveFactor)).r;
diffuseSSS.g= tex2D(_SSSLUT,float2(lookup.g,curve* _CurveFactor)).g;
diffuseSSS.b= tex2D(_SSSLUT,float2(lookup.b,curve* _CurveFactor)).b;
fixed3 diffuse=_LightColor0.rgb * albedo *diffuseSSS* shadow;但是,还是绕不开调参数的问题,我找到一张参考图,嗯,看起来红色最模糊,绿色稍微好一点,蓝色高清一点。


最后效果区别在于凹凸纹理的颜色可调节,而不是死黑。但要实现最佳效果,如Gpu pro所说,不要使用归一化法线,而且原始法线要足够高清。(使用一张已经模糊过的法线来插值效果会比较差)



右上使用分层模糊法线,凹凸处不会产生死黑

调节后对比可以看到,我可以控制箭头所指处的颜色,避免死黑。
五、高光

GPUPro2未做高光,因此使用GPU Gem方案。
Kelemen/Szirmay-Kalos specular BRDF是专门用于模拟油脂层的高光,也是一个预积分。
float integrateSzirmayKalosSpecular(float ndoth, float m)
    {
      float alpha = Mathf.Acos(ndoth);
      float ta = Mathf.Tan(alpha);
      float val = 1.0f / (m * m * Mathf.Pow(ndoth, 4.0f)) * Mathf.Exp(-(ta * ta) / (m * m));
      return val;
    }已经集成到lut生成工具,因为计算很快,我采用了超采样减少锯齿。



这里有锯齿是因为截图被压缩过了

以下是shader中如何读alpha8与积分图生产高光。
//SPECULAR(记得自己算一下ndl...还是直接看我github吧)
fixed3 halfDir = normalize(viewDir + lightDir);
float ndh=dot(normalize(N_high), halfDir);
fixed4 alpha=tex2D(_SpecularLUT,float2(ndh,_Rough));
float ph=pow( 2.0*alpha.w, 10.0 );
float F = fresnelReflectance( halfDir, viewDir, 0.028 );
float frSpec = max( ph * F / dot( halfDir, halfDir ), 0 );
float res=_LightColor0.rgb*shadow*saturate(NoL)*_Gloss*frSpec;
fixed3 specular =fixed3(res,res,res);看到好多帖子说这个高光太油腻了不好,但其实这个高光有两个参数可以调,亮度和粗糙度,调好效果还是很棒的。







我调的参数

到此,SSS皮肤全部完成,可喜可贺可喜可贺!
最后再看一下我们的效果:


https://www.zhihu.com/video/1498870636949164032


六、Shader源码

这里不保证实时更新,最新还是看GitHub。
Shader "Custom/SSS_normal"{
Properties
        {
                _MainTex ("Texture", 2D) = "white" {}
                _CurveTex ("CurveMap", 2D) = "white" {}
                _BumpTex ("Normal Map", 2D) = "bump" {}
                _BumpScale ("Bump Scale", Float) = 1.0
                _Color ("Color Tint", Color) = (1, 1, 1, 1)
                _Specular ("Specular", Color) = (1, 1, 1, 1)
                _tuneNormalBlur ("tuneNormalBlur", Color) = (1, 1, 1, 1)
                _SSSLUT ("SSSLUT", 2D) = "white" {}
                _SpecularLUT ("BeckmannLUT", 2D) = "white" {}
                _Brightness ("Brightness", Range(0, 1)) = 1
                _Roughness ("Roughness", Range(0, 1)) = 1
                _CurveFactor("CurveRate",Range(1,4))=1
        }
        SubShader
        {
                Tags { "RenderType" = "Opaque" }
                Pass
                {
            Name "FORWARD"
                        Tags{"LightMode" = "ForwardBase"}
                       
                        CGPROGRAM
                        #pragma vertex vert
                        #pragma fragment frag
            #pragma multi_compile_fwdbase_fullshadows
            #pragma multi_compile_fog

                        #include "UnityCG.cginc"
                        #include "Lighting.cginc"
                        #include "AutoLight.cginc"
                        sampler2D _MainTex;
                        sampler2D _CurveTex;
                        float4 _MainTex_ST;
                        sampler2D _BumpTex;
                        sampler2D _SSSLUT;
                        sampler2D _SpecularLUT;
                        float4 _BumpTex_ST;
                        float _BumpScale;
                        fixed4 _Color;
                        fixed4 _Specular;
                        fixed4 _tuneNormalBlur;
                        float _Brightness;
                        float _Roughness;
                        float _CurveFactor;

                        float fresnelReflectance( float3 H, float3 V, float F0 )
                        {
                                float base = 1.0 - dot( V, H );
                                float exponential = pow( base, 5.0 );
                                return exponential + F0 * ( 1.0 - exponential );
                        }
                        struct appdata
                        {
                                float4 vertex : POSITION;
                                float4 uv : TEXCOORD0;
                                float3 normal : NORMAL;
                                float4 tangent : TANGENT;
                        };

                        struct v2f
                        {
                                float4 vertex : SV_POSITION;
                                float4 uv : TEXCOORD0;
                                float4 TtoW0 : TEXCOORD1;
                                float4 TtoW1 : TEXCOORD2;
                                float4 TtoW2 : TEXCOORD3;
                                float4 pos : CLIP_POS;
                                SHADOW_COORDS(4)
                UNITY_FOG_COORDS(5)
                        };
                       
                        v2f vert (appdata v)
                        {
                                v2f o;
                                o.vertex= UnityObjectToClipPos(v.vertex);
                                o.pos = UnityObjectToClipPos(v.vertex);

                                o.uv.xy = TRANSFORM_TEX(v.uv.xy, _MainTex);
                                o.uv.zw = TRANSFORM_TEX(v.uv.xy, _BumpTex);
                               
                                float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                                fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
                                fixed3 worldTangent = UnityObjectToWorldDir(v.tangent);
                                fixed3 binormal = cross(worldNormal, worldTangent) * v.tangent.w;

                                o.TtoW0 = float4(worldTangent.x, binormal.x, worldNormal.x, worldPos.x);
                                o.TtoW1 = float4(worldTangent.y, binormal.y, worldNormal.y, worldPos.y);
                                o.TtoW2 = float4(worldTangent.z, binormal.z, worldNormal.z, worldPos.z);
                                TRANSFER_SHADOW(o);
                UNITY_TRANSFER_FOG(o,o.pos);
                                return o;
                        }
                       


                        fixed4 frag (v2f i) : SV_Target
                        {
                                //Basis
                                fixed shadow = SHADOW_ATTENUATION(i);
                                float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
                                float3 N_low = float3(i.TtoW0.z, i.TtoW1.z, i.TtoW2.z);
                                fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
                                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
                               
                                //SSS NdL
                                fixed4 bumpColor = tex2D(_BumpTex, i.uv.zw);
                                fixed3 tangentNormal;
                                tangentNormal = UnpackNormal(bumpColor);
                                tangentNormal.xy *= _BumpScale;
                                tangentNormal.z = normalize(sqrt(1 - saturate(dot(tangentNormal.xy, tangentNormal.xy))));
                                float3x3 t2wMatrix = float3x3(i.TtoW0.xyz, i.TtoW1.xyz, i.TtoW2.xyz);
                                float3 N_high = half3(mul(t2wMatrix, tangentNormal));
                                float3 rN=lerp(N_high,N_low,_tuneNormalBlur.x);
                                float3 gN=lerp(N_high,N_low,_tuneNormalBlur.y);
                                float3 bN=lerp(N_high,N_low,_tuneNormalBlur.z);
                                float3 NdotL=float3(dot(rN,lightDir),dot(gN,lightDir),dot(bN,lightDir));
                                float NoL=dot(normalize(N_high),lightDir);

                                //Ambient
                                fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Color.rgb;
                                fixed3 ambient = (UNITY_LIGHTMODEL_AMBIENT.xyz+float3(0.17,0.17,0.17)) * albedo;

                                //SPECULAR
                                fixed3 halfDir = normalize(viewDir + lightDir);
                                float ndh=dot(normalize(N_high), halfDir);
                                fixed4 alpha=tex2D(_SpecularLUT,float2(ndh,_Roughness));
                                float ph=pow( 2.0*alpha.w, 10.0 );
                                float F = fresnelReflectance( halfDir, viewDir, 0.028 );
                                float frSpec = max( ph * F / dot( halfDir, halfDir ), 0 );
                                float res=_LightColor0.rgb*shadow*saturate(NoL)*_Brightness*frSpec;
                                fixed3 specular =fixed3(res,res,res);
                               
                                //SSS DIFFUSE
                                fixed3 diffuseSSS;
                                float3 lookup=NdotL*0.5+0.5;
                                fixed curve=tex2D(_CurveTex, i.uv.xy).r;
                                diffuseSSS.r= tex2D(_SSSLUT,float2(lookup.r,curve* _CurveFactor)).r;
                                diffuseSSS.g= tex2D(_SSSLUT,float2(lookup.g,curve* _CurveFactor)).g;
                                diffuseSSS.b= tex2D(_SSSLUT,float2(lookup.b,curve* _CurveFactor)).b;
                                fixed3 diffuse=_LightColor0.rgb * albedo *diffuseSSS* shadow;
                                //diffuse = tex2D(_SSSLUT,float2(NoL*0.5+0.5,cuv));
                                //diffuse = fixed4(curve* _CurveFactor,curve* _CurveFactor,curve* _CurveFactor,1);
                                //eturn fixed4(specular, 1.0);
                                fixed4 col=fixed4(diffuse+ambient+specular, 1.0);
                                UNITY_APPLY_FOG(i.fogCoord, col);
                                return col;
                        }
                        ENDCG
                }Pass
                {
            Name "FORWARD_DELTA"
                        Tags{"LightMode" = "ForwardAdd"}
            Cull Back
                        Blend One One
                        CGPROGRAM
                        #pragma vertex vert
                        #pragma fragment frag
            #pragma multi_compile_fwdadd_fullshadows
            #pragma multi_compile_fog
            #pragma only_renderers d3d9 d3d11 glcore gles
            #pragma target 4.0

                        #include "UnityCG.cginc"
                        #include "Lighting.cginc"
                        #include "AutoLight.cginc"
                        sampler2D _MainTex;
                        sampler2D _CurveTex;
                        float4 _MainTex_ST;
                        sampler2D _BumpTex;
                        sampler2D _SSSLUT;
                        sampler2D _SpecularLUT;
                        float4 _BumpTex_ST;
                        float _BumpScale;
                        fixed4 _Color;
                        fixed4 _Specular;
                        fixed4 _tuneNormalBlur;
                        float _Brightness;
                        float _Roughness;
                        float _CurveFactor;

                        float fresnelReflectance( float3 H, float3 V, float F0 )
                        {
                                float base = 1.0 - dot( V, H );
                                float exponential = pow( base, 5.0 );
                                return exponential + F0 * ( 1.0 - exponential );
                        }
                        struct appdata
                        {
                                float4 vertex : POSITION;
                                float4 uv : TEXCOORD0;
                                float3 normal : NORMAL;
                                float4 tangent : TANGENT;
                        };

                        struct v2f
                        {
                                float4 vertex : SV_POSITION;
                                float4 uv : TEXCOORD0;
                                float4 TtoW0 : TEXCOORD1;
                                float4 TtoW1 : TEXCOORD2;
                                float4 TtoW2 : TEXCOORD3;
                                float4 pos : CLIP_POS;
                                SHADOW_COORDS(4)
                UNITY_FOG_COORDS(5)
                        };
                       
                        v2f vert (appdata v)
                        {
                                v2f o;
                                o.vertex= UnityObjectToClipPos(v.vertex);
                                o.pos = UnityObjectToClipPos(v.vertex);

                                o.uv.xy = TRANSFORM_TEX(v.uv.xy, _MainTex);
                                o.uv.zw = TRANSFORM_TEX(v.uv.xy, _BumpTex);
                               
                                float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                                fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
                                fixed3 worldTangent = UnityObjectToWorldDir(v.tangent);
                                fixed3 binormal = cross(worldNormal, worldTangent) * v.tangent.w;

                                o.TtoW0 = float4(worldTangent.x, binormal.x, worldNormal.x, worldPos.x);
                                o.TtoW1 = float4(worldTangent.y, binormal.y, worldNormal.y, worldPos.y);
                                o.TtoW2 = float4(worldTangent.z, binormal.z, worldNormal.z, worldPos.z);
                                TRANSFER_SHADOW(o);
                UNITY_TRANSFER_FOG(o,o.pos);
                                return o;
                        }
                       

                        fixed4 frag (v2f i) : SV_Target
                        {
                                //Basis
                                float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
                                float3 N_low = float3(i.TtoW0.z, i.TtoW1.z, i.TtoW2.z);
                                float3 lightDir = normalize(lerp(_WorldSpaceLightPos0.xyz, _WorldSpaceLightPos0.xyz - worldPos.xyz,_WorldSpaceLightPos0.w));
                                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
                                fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Color.rgb;

                                //SSS NdL
                                fixed4 bumpColor = tex2D(_BumpTex, i.uv.zw);
                                fixed3 tangentNormal;
                                tangentNormal = UnpackNormal(bumpColor);
                                tangentNormal.xy *= _BumpScale;
                                tangentNormal.z = normalize(sqrt(1 - saturate(dot(tangentNormal.xy, tangentNormal.xy))));
                                float3x3 t2wMatrix = float3x3(i.TtoW0.xyz, i.TtoW1.xyz, i.TtoW2.xyz);
                                float3 N_high = half3(mul(t2wMatrix, tangentNormal));
                                float3 rN=lerp(N_high,N_low,_tuneNormalBlur.x);
                                float3 gN=lerp(N_high,N_low,_tuneNormalBlur.y);
                                float3 bN=lerp(N_high,N_low,_tuneNormalBlur.z);
                                float3 NdotL=float3(dot(rN,lightDir),dot(gN,lightDir),dot(bN,lightDir));
                                float NoL=dot(normalize(N_high),lightDir);

                                //SPECULAR
                                fixed3 halfDir = normalize(viewDir + lightDir);
                                float ndh=dot(normalize(N_high), halfDir);
                                fixed4 alpha=tex2D(_SpecularLUT,float2(ndh,_Roughness));
                                float ph=pow( 2.0*alpha.w, 10.0 );
                                float F = fresnelReflectance( halfDir, viewDir, 0.028 );
                                float frSpec = max( ph * F / dot( halfDir, halfDir ), 0 );
                                float res=_LightColor0.rgb*saturate(NoL)*_Brightness*frSpec;
                                fixed3 specular =fixed3(res,res,res);
                               
                                //SSS DIFFUSE
                                fixed3 diffuseSSS;
                                float3 lookup=NdotL*0.5+0.5;
                                fixed curve=tex2D(_CurveTex, i.uv.xy).r;
                                diffuseSSS.r= tex2D(_SSSLUT,float2(lookup.r,curve* _CurveFactor)).r;
                                diffuseSSS.g= tex2D(_SSSLUT,float2(lookup.g,curve* _CurveFactor)).g;
                                diffuseSSS.b= tex2D(_SSSLUT,float2(lookup.b,curve* _CurveFactor)).b;
                               
                                fixed3 diffuse=_LightColor0.rgb * albedo *diffuseSSS;

                               
                                UNITY_LIGHT_ATTENUATION(attenuation,i, worldPos.xyz);

                               
                               
                                fixed4 col=fixed4((diffuse+specular)*attenuation, 1.0);
                                UNITY_APPLY_FOG(i.fogCoord, col);
                                return col;
                        }
                        ENDCG
                }
        }

        FallBack "Standard"
}

ChuanXin 发表于 2022-4-19 10:45

[赞同]
页: [1]
查看完整版本: Vrchat中实现次表明散射(SSS)材质【持续更新】