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):
"{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{&#34;LightMode&#34; = &#34;ForwardAdd&#34;}的pass处理其他光。
该pass大致一样,加上Blend One One表示混合,还要要在fragement里面重新求lightDir和光线衰减:
Tags{&#34;LightMode&#34; = &#34;ForwardAdd&#34;}
Blend One One
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdadd
#include &#34;Lighting.cginc&#34;
#include &#34;AutoLight.cginc&#34;
...
...
...
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 &#34;Custom/SSS_normal&#34;{
Properties
{
_MainTex (&#34;Texture&#34;, 2D) = &#34;white&#34; {}
_CurveTex (&#34;CurveMap&#34;, 2D) = &#34;white&#34; {}
_BumpTex (&#34;Normal Map&#34;, 2D) = &#34;bump&#34; {}
_BumpScale (&#34;Bump Scale&#34;, Float) = 1.0
_Color (&#34;Color Tint&#34;, Color) = (1, 1, 1, 1)
_Specular (&#34;Specular&#34;, Color) = (1, 1, 1, 1)
_tuneNormalBlur (&#34;tuneNormalBlur&#34;, Color) = (1, 1, 1, 1)
_SSSLUT (&#34;SSSLUT&#34;, 2D) = &#34;white&#34; {}
_SpecularLUT (&#34;BeckmannLUT&#34;, 2D) = &#34;white&#34; {}
_Brightness (&#34;Brightness&#34;, Range(0, 1)) = 1
_Roughness (&#34;Roughness&#34;, Range(0, 1)) = 1
_CurveFactor(&#34;CurveRate&#34;,Range(1,4))=1
}
SubShader
{
Tags { &#34;RenderType&#34; = &#34;Opaque&#34; }
Pass
{
Name &#34;FORWARD&#34;
Tags{&#34;LightMode&#34; = &#34;ForwardBase&#34;}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase_fullshadows
#pragma multi_compile_fog
#include &#34;UnityCG.cginc&#34;
#include &#34;Lighting.cginc&#34;
#include &#34;AutoLight.cginc&#34;
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 &#34;FORWARD_DELTA&#34;
Tags{&#34;LightMode&#34; = &#34;ForwardAdd&#34;}
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 &#34;UnityCG.cginc&#34;
#include &#34;Lighting.cginc&#34;
#include &#34;AutoLight.cginc&#34;
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 &#34;Standard&#34;
} [赞同]
页:
[1]