unity-shader-头发渲染-各向异性

unity-shader-头发渲染-各向异性


前篇

相关参考文章


效果

丢在模型上的效果, 头发的uv有部分展开的不好, 有断层.
这是从mmd吧获取并提取出来的模型. ( 提取方法: art-记一次提取mmd模型到max和unity )

建了个模拟头发的模型, 展开的uv根据 高光扰动图的方向 ( 我也知道很假 (:逃 )

丢在 unity 中的效果

再来一波, 头发的uv展开基本都这样


各向异性高光

Kajiya-Kay Shading

( 直接截图 Thorsten Scheuermann 的 PPT 讲解 )

这个 T 参数是指 发梢 到 发根 的方向 , 也就是 副切线 的方向, 而顶点中的 tangent 是指 垂直于 发梢 到 发根 方向 的向量, 所有求 T 的方法是 用这个 tangentnormal 叉乘得到

1
2
3
4
5
// vert
//求出沿着发梢到发根方向的切线
half4 p_tangent = mul(unity_ObjectToWorld, v.tangent);
o.tangent = normalize(p_tangent).xyz;
o.tangent = cross(o.tangent, o.worldNormal);

如图所示

  • 红色 为 法线,
  • 绿色 为 切线,
  • 蓝色 为 副切线 ( 即需要求的参数 T )

高光计算部分

$$
specular=K_{s 1} * \text { smoothstep }\left(-1,0, d o t\left(t_{1}, h\right)\right) * \sin \left(t_{1}, h\right)^{p 1}+K_{s 2} * \text { smoothstep }\left(-1,0, \operatorname{dot}\left(t_{2}, h\right)\right) * \sin \left(t_{2}, h\right)^{p_{2}}
$$

specular = 主高光 + 副高光

1
2
3
4
5
6
7
8
float StrandSpecular(float3 T, float3 V, float3 L, float exponent)
{
float3 halfDir = normalize(L + V);
float dotTH = dot(T, halfDir);
float sinTH = max(0.01,sqrt(1 - pow(dotTH, 2)));
float dirAtten = smoothstep(-1,0, dotTH);
return dirAtten * pow(sinTH, exponent);
}

t1, t2 是指 副切线 的偏移

1
2
3
4
5
float3 ShiftTangent(float3 T, float3 N, float shift)
{
float3 shiftedT = T + (shift * N);
return normalize(shiftedT);
}

头发透明渲染深度问题

因为透明渲染是不写入深度缓存, 可以增加多一个pass只写如深度, 不输出颜色

1
2
3
4
5
Pass
{
ZWrite On //写入深度,被遮挡的像素将不能通过深度测试
ColorMask 0 //不输出颜色
}

shader

材质球相关参数.
LightMapMask 暂时没用. ( 也就是AO ), 副高光也没用到 ( FuHair… )

简单粗暴贴代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
Shader "test/NPR07_hair"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Color("Color",Color) = (1,1,1,1)

_Ramp ("Ramp", 2D) = "white" {}
_Bump ("Normal", 2D) = "white" {}
_HairLightRamp ("HairLightRamp", 2D) = "white" {}
_LightMapMask ("LightMapMask", 2D) = "white" {}

[Space(10)][Header(xxxxxxxxxxxxxxxx)]
_Specular("Specular",Color) = (1,1,1,1)
_SpecularScale("SpecularScale", Range(0, 5)) = 1

[Space(10)][Header(xxxxxxxxxxxxxxxx)]
_MainSpecularSmooth("MainHairSpecularSmooth", Range(-10, 100)) = 1
_FuSpecularSmooth("FuHairSpecularSmooth", Range(-10, 100)) = 1
_MainSpecularOff("MainHairSpecularOff", Range(-10, 10)) = 1
_FuSpecularOff("FuHairSpecularOff", Range(-10, 10)) = 1

[Space(10)][Header(xxxxxxxxxxxxxxxx)]
_RimPower("RimPower", Range(0.2, 10)) = 1
}

SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100

Pass {
Tags { "LightMode"="ForwardBase" }

Cull off

CGPROGRAM

#pragma vertex vert
#pragma fragment frag

#pragma multi_compile_fwdbase

#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
#include "UnityShaderVariables.cginc"

fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;

sampler2D _Bump;
float4 _Bump_ST;

sampler2D _Ramp;

fixed4 _Specular;
fixed _SpecularScale;

fixed _MainSpecularSmooth;
fixed _FuSpecularSmooth;
float _MainSpecularOff;
float _FuSpecularOff;

sampler2D _HairLightRamp;
float4 _HairLightRamp_ST;

float _RimPower;

sampler2D _LightMapMask;

struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
float4 tangent : TANGENT;
};

struct v2f {
float4 pos : POSITION;
float2 uv : TEXCOORD0;
float3 worldNormal : TEXCOORD1;
float3 worldPos : TEXCOORD2;

SHADOW_COORDS(3)
float3 tangent : TEXCOORD4;
float2 hairLightUV:TEXCOORD5;
float2 uv_Bump : TEXCOORD6;
float3 normal : TEXCOORD7;
};

v2f vert (a2v v) {
v2f o;

o.pos = UnityObjectToClipPos( v.vertex);
o.normal = v.normal;
o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);
o.hairLightUV = TRANSFORM_TEX(v.texcoord, _HairLightRamp);
o.uv_Bump = TRANSFORM_TEX(v.texcoord, _Bump);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
TRANSFER_SHADOW(o);

//求出沿着发梢到发根方向的切线
half4 p_tangent = mul(unity_ObjectToWorld, v.tangent);
o.tangent = normalize(p_tangent).xyz;
o.tangent = cross(o.tangent, o.worldNormal);
return o;
}

float3 ShiftTangent(float3 T, float3 N, float shift)
{
float3 shiftedT = T + (shift * N);
return normalize(shiftedT);
}

float StrandSpecular(float3 T, float3 V, float3 L, float exponent)
{
float3 halfDir = normalize(L + V);
float dotTH = dot(T, halfDir);
float sinTH = max(0.01,sqrt(1 - pow(dotTH, 2)));
float dirAtten = smoothstep(-1,0, dotTH);
return dirAtten * pow(sinTH, exponent);
}

float3 LightMapColor(fixed3 worldLightDir,fixed3 worldNormalDir, fixed2 uv)
{
float LdotN = max(0, dot(worldLightDir, worldNormalDir));
float3 lightColor = LdotN * tex2D(_LightMapMask, uv);
return lightColor;
}

float4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
// fixed3 tangentNormal = UnpackNormal(tex2D(_Bump, i.uv_Bump)); // 暂时没用 法线图
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 worldHalfDir = normalize(worldLightDir + worldViewDir);

//漫反射贴图采样
fixed4 c = tex2D (_MainTex, i.uv);
fixed3 albedo = c.rgb * _Color.rgb;

fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos); //阴影值计算

fixed diff = dot(worldNormal, worldLightDir); //世界空间的法线坐标和光照方向点乘得到漫反射颜色
diff = (diff * 0.5 + 0.5) * atten; //暗部提亮 当然这里也可以不提亮

//将光线颜色和环境光颜色以及梯度图采样相乘得到最终的漫反射颜色
fixed3 diffuse = _LightColor0.rgb * albedo * tex2D(_Ramp, float2(diff, diff)).rgb;

//头发高光图采样, 使用一张扰动图沿着物体切线方向扰动法线来达到效果
float3 speTex = tex2D(_HairLightRamp, i.hairLightUV);
//头发 主高光 偏移
float3 Ts = ShiftTangent(i.tangent, worldNormal, _MainSpecularOff * speTex);
//头发 副高光 偏移
float3 Tf = ShiftTangent(i.tangent, worldNormal, _FuSpecularOff * speTex);

//头发 主副 高光值
float specMain = StrandSpecular(Ts, worldViewDir, worldLightDir, _MainSpecularSmooth);
float specFu = StrandSpecular(Tf, worldViewDir, worldLightDir, _FuSpecularSmooth);

float specFinal = specMain;
// 这里可以添加一个副高光
// float specMask = tex2D(tSpecMask, uv);
// specFinal += specMask * specFu;
// specFinal += specFu;

specFinal *= _SpecularScale ;

fixed3 specular = _Specular.rgb * specFinal * atten;

//rim light term
half rim = 1.0 - saturate(dot(worldViewDir, worldNormal));
rim = pow(rim, _RimPower);

fixed3 ao = LightMapColor(worldLightDir, worldNormal,i.uv).rgb;

return fixed4((ambient + diffuse + specular + rim) /* * ao */, 1.0 );
}

ENDCG
}
}
FallBack "Diffuse"
}

Hair Shader 1.0.4 插件

  • 效果图

  • uv展开

  • 各种贴图