人物3渲2学习总结

人物3渲2学习总结

五月 11, 2022

描边

法线外扩法

中文一般称为“法线外扩法”、“背面膨胀法”,日文资料中会称作「背面法」,它们指的是同一个技术

解决方式

第一个Pass:正常打光和绘制
第二个Pass:

Vertex Shader阶段将顶点向顶点法线方向移动一定距离
Rasterization阶段将Cull Mode设置为Cull Front

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
Pass
{
Name "OUTLINE"
Cull Front
ZWrite On
ColorMask RGB
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texCoord : TEXCOORD0;
float4 vertexColor : COLOR;
};
struct v2f {
float4 pos : POSITION;
float4 color : COLOR;
float4 tex : TEXCOORD0;
};
sampler2D _MainTex;
float _Outline;
float4 _OutlineColor;
v2f vert(appdata v) {
// just make a copy of incoming vertex data but scaled according to normal direction
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
float3 norm = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);
float2 offset = TransformViewToProjection(norm.xy);
// float2 offset = (norm.xy);
o.pos.xy += offset * _Outline*0.0001;
o.tex = v.texCoord;
o.color = v.vertexColor;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
half4 base_col = tex2D(_MainTex,i.tex);
return _OutlineColor*base_col;
}
ENDCG
}

缺点

  1. 描边出现断裂,或者出现了看起来很奇怪的描边
    在这里插入图片描述
    解决方案:用法线刷修改顶点让顶点平滑,出现这个问题的原因是每个面的法线都是垂直于平面的,所以在边角处就连接不上了。一般这种问题的解决方案是另外存储一套平滑法线。具体做法是计算与该点位置相同的所有点的法线的平均值并存储在顶点上。
  2. 描边线条控制
    想眼睛,头发这种细节描边需要修正的
    头发,由于模型的复杂性,在使用法线外扩法时往往会出现不太想要的描边。为了防止出现这些不受欢迎的描边,可以给每个顶点设置一个深度值、将顶点埋进里面从而被遮挡住。《罪恶装备Xrd》里就将这个信息写入了顶点色B通道中。
    细节:由于法线外扩法是基于模型的,所以实际上对线条还是有较高的自由度来控制。如果想要做到有粗细变化的描边,可以参考罪恶装备Xrd中将粗细变化的数值写入顶点色当中。实际上就是一个外扩系数:数值越大,越向外扩,线条越粗。至于线条的颜色,如果顶点色或者其他UV通道够用,可以将描边颜色写进顶点里面。然后相乘。

    菲涅尔方程

    解决方法

    N*V
    适用于球形物体的描边

    问题

    但平面时不好控制轮廓线

    顶点着色器

    blender 的Freestyle

    解决方法

    他们首先通过预处理的方式提取那些感兴趣的边缘,保存到额外的Mesh资源上。在渲染时通过Geometry Shader将这些边缘绘制出来。
    出于性能跟兼容性问题还是尽量避免使用Geometry Shader的好。但是这种思路还是非常不错的,因为这种方式对线条的控制也有很高的自由度,并且相比起法线外扩法可以创造更多细节。而且目前Compute Shader已经相当成熟且普及了,所以考虑用Compute Shader+软光栅的方式说不定也是一条走得通的道路。

    问题

    颜色不能自定义,塞到顶点色里面,而且性价比不高。

    基于图像空间的线条检测

参考文章
通过检测场景图像中 Normal 和 Depth 的不连续性,我们可以获得细节较为丰富的勾线。无论场景的复杂性如何,这种方法的性能都是恒定的,我们还添加了对勾线颜色的色相、明度、饱和度的调整,使勾线更为自然。
在这里插入图片描述

常用的算法是Sobel算子
缺点则是较难控制勾线的宽度,如果我们想实现距离相关的线宽,我们只能在几个像素的范围内调整它,因此基于图像的方法主要适用于场景线条渲染,对于靠近摄像头很近的物体,我们最好使用 Backface 的方法。
在这里插入图片描述
看这里实现方式

对于后处理进行canny卷积

我卷失败了,没卷出来以后有时间再搞。
和上一方法有异曲同工之处。
转化为灰度图进行canny卷积

打光

皮肤和衣服部分

多通道Ramp的shading方法

T_SpecularRamp1.png
在这里插入图片描述
在这里插入图片描述
采用多层上色,我的效果好拉,没官方好看。

解释

RampLayerSoftness越大越软阴影,小则硬
我没将_TintBase 叠加上去
我没加阴影是因为,当阴影加上后会映在人身上看起来就不二次元了。

代码

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
Properties :
[Header(Tint Base)]
_DiffuseRamp ("DiffuseRamp", 2D) = "Black" {}
_SpecularRamp ("SpecularRamp", 2D) = "Black" {}
_TintBase ("Tint Base", COLOR) = (0,0,0,1)
[Header(RampLayer1)]
_RampLayerOffset1 ("RampLayerOffset1", Range(-0.5,0.5)) = 0.5
_TintLayer1 ("Tint_Layer1", COLOR) = (0,0,0,1)
[Header(RampLayer2)]
_RampLayerOffset2 ("RampLayerOffset2", Range(-0.5,0.5)) = 0.5
_RampLayerSoftness2 ("RampLayerSoftness2", Range(0,1)) = 0.5
__TintLayer2 ("Tint Layer2", COLOR) = (0,0,0,1)
[Header(RampLayer3)]
_RampLayerOffset3 ("RampLayerOffset3", Range(-0.5,0.5)) = 0.5
_RampLayerSoftness3 ("RampLayerSoftness3", Range(0,1)) = 0.5
__TintLayer3 ("Tint Layer3", COLOR) = (0,0,0,1)
[Header(Specular)]
_Shineness ("Shineness", Float) = 0.5
_SpecularColor ("Specular Color", COLOR) = (0,0,0,1)
_SpecularIntensity ("SpecularIntensity", Float) = 1
_SpecularSmooth ("Specular Smooth", Float) = 1
Pass :
sampler2D _DiffuseRamp;
sampler2D _SpecularRamp;
float4 _TintBase;
float4 __TintLayer1;
float _RampLayerOffset1;

float4 __TintLayer2;
float _RampLayerOffset2;
float _RampLayerSoftness2;

float4 __TintLayer3;
float _RampLayerOffset3;
float _RampLayerSoftness3;

float _Shineness;
float _SpecularIntensity;
float4 _SpecularColor;
float _SpecularSmooth;
frag :
half atten = LIGHT_ATTENUATION (i);
half3 base_col = tex2D(_MainTex, i.uv).xyz;
half3 normal_world= normalize(i.normalDir);
half3 tangent_world= normalize(i.tangentDir);
half3 binormal_world= normalize(i.binormalDir);
half3 pos_world= normalize(i.pos_world);
half3 normal_data = UnpackNormal(tex2D(_Normal,i.uv));
half3x3 TBN = half3x3 (tangent_world,binormal_world,normal_world);

normal_world = mul(normal_data,TBN);
half3 ViewDir = normalize(_WorldSpaceCameraPos.xyz-pos_world);
half3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
//漫反射
half NdotL = max(0,dot(normal_world,lightDir));
half half_lambert = (NdotL+1.0)*0.5;
//half half_lambert = half_lambert*ao //本来要乘上AO因为素材没有算了

half3 tint_Base_color1 = base_col;

//第一层ramp
half2 uv_ramp1 = half2(half_lambert+_RampLayerOffset1,0.5);
half toon_diffuse1 = tex2D(_DiffuseRamp,uv_ramp1).r;
half3 tint_color1 = lerp(half3(1,1,1),__TintLayer1,toon_diffuse1*__TintLayer1.a);

//第二层ramp
half2 uv_ramp2 = half2(half_lambert+_RampLayerOffset2,1-i.VertexColor.g+_RampLayerSoftness2);//1-i.VertexColor.g可以用_RampLayerSoftness2代替越大越柔和
half toon_diffuse2 = tex2D(_DiffuseRamp,uv_ramp2).g;
half3 tint_color2 = lerp(half3(1,1,1),__TintLayer2,toon_diffuse2*__TintLayer2.a);

//第三层ramp
half2 uv_ramp3 = half2(half_lambert+_RampLayerOffset3,1-i.VertexColor.b+_RampLayerSoftness3);//1-i.VertexColor.b可以用_RampLayerSoftness3代替越大越柔和
half toon_diffuse3 = tex2D(_DiffuseRamp,uv_ramp3).b;
half3 tint_color3 = lerp(half3(1,1,1),__TintLayer3,toon_diffuse3*__TintLayer3.a);

half3 final_diffuse = tint_Base_color1 * tint_color1 *tint_color2 *tint_color3 ;
//高光
//half3 half_R = normalize(lightDir+ViewDir);
half3 R = normalize(reflect(-lightDir,normal_world));
half specular_term = max(pow(dot(R,ViewDir),_Shineness),0.0001);//本来要乘上AO因为素材没有算了
specular_term = smoothstep(0.5-_SpecularSmooth*0.5,0.5+_SpecularSmooth*0.5,specular_term);//风格化高光

half3 final_specular = base_col * specular_term *_SpecularColor *atten *_LightColor0.xyz *_SpecularIntensity ;
half3 final_col = final_diffuse+final_specular ;

卖家秀

在这里插入图片描述

买家秀

在这里插入图片描述

边缘光,环境光

在这里插入图片描述

解释

_MatCap是我自己加上去的,有点多余

实现

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
Properties :
_MatCap ("MatCap", 2D) = "Black" {}
_Envmap ("Envmap", Cube) = "Black" {}
[Header(Rim)]
_RimMin("RimMin", Range(-2,2)) = 0.5
_RimMax("RimMax", Range(-2,2)) = 0.5
_Roughness("Roughness", Float) = 0
_EnvIntensity("Env Intensity", Float) = 0
pass :
sampler2D _MatCap;
samplerCUBE _Envmap;
float _RimMax;
float _RimMin;
float _Roughness;
float _EnvIntensity;
float4 _Envmap_HDR;
frag :
//边缘光,环境光

half fresnel = 1- dot(ViewDir,normal_world);
half rim = smoothstep(_RimMin,_RimMax,fresnel);//本来要乘上AO因为素材没有算了
half3 mc=tex2D(_MatCap,fresnel.xx);
half3 r = reflect(-ViewDir,normal_world);
half roughness = lerp(0,0.95,saturate(_Roughness));
roughness = roughness * (1.7-0.7*roughness );
half mip_level = roughness * 6.0;
half4 color_cubemap = texCUBElod(_Envmap,half4(r,mip_level));
half3 color_env = DecodeHDR(color_cubemap,_Envmap_HDR);
half3 final_env =color_env * rim * _EnvIntensity;

总体代码

Character_Skin.shader

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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
Shader "Character/Skin"
{
Properties
{
_MainTex ("MainTex", 2D) = "white" {}
_Normal("Normal",2D) = "bump" {}
_DiffuseRamp ("DiffuseRamp", 2D) = "Black" {}
_Envmap ("Envmap", Cube) = "Black" {}


[Header(Tint Base)]
_TintBase ("Tint Base", COLOR) = (0,0,0,1)
[Header(RampLayer1)]
_RampLayerOffset1 ("RampLayerOffset1", Range(-0.5,0.5)) = 0.5
_TintLayer1 ("Tint_Layer1", COLOR) = (0,0,0,1)
[Header(RampLayer2)]
_RampLayerOffset2 ("RampLayerOffset2", Range(-0.5,0.5)) = 0.5
_RampLayerSoftness2 ("RampLayerSoftness2", Range(0,1)) = 0.5
__TintLayer2 ("Tint Layer2", COLOR) = (0,0,0,1)
[Header(RampLayer3)]
_RampLayerOffset3 ("RampLayerOffset3", Range(-0.5,0.5)) = 0.5
_RampLayerSoftness3 ("RampLayerSoftness3", Range(0,1)) = 0.5
__TintLayer3 ("Tint Layer3", COLOR) = (0,0,0,1)
[Header(Specular)]
_Shineness ("Shineness", Float) = 0.5
_SpecularColor ("Specular Color", COLOR) = (0,0,0,1)
_SpecularIntensity ("SpecularIntensity", Float) = 1
_SpecularSmooth ("Specular Smooth", Float) = 1


[Header(Rim)]
_RimMin("RimMin", Range(-2,2)) = 0.5
_RimMax("RimMax", Range(-2,2)) = 0.5
_Roughness("Roughness", Float) = 0
_EnvIntensity("Env Intensity", Float) = 0

[Header(Outline)]
_Outline ("Outline", Float) = 0.6
_OutlineColor ("OutlineColor", COLOR) = (0,0,0,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100

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

CGPROGRAM

#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
// make fog work
#pragma multi_compile_fog
#include "AutoLight.cginc"
#include "UnityCG.cginc"

struct appdata
{
float4 vertex : POSITION;
float2 texcoord0 : TEXCOORD0;
float4 normal :NORMAL;
float4 tangent:TANGENT;
float4 VertexColor:COLOR;
};

struct v2f
{

float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;

float3 normalDir: TEXCOORD1;
float3 tangentDir: TEXCOORD2;
float3 binormalDir: TEXCOORD3;
float4 VertexColor: TEXCOORD4;
float3 pos_world: TEXCOORD5;
LIGHTING_COORDS(6,7)
};

sampler2D _MainTex;
sampler2D _Normal;
sampler2D _DiffuseRamp;
samplerCUBE _Envmap;

float4 _LightColor0;

float4 _MainTex_ST;
float4 _TintBase;
float4 __TintLayer1;
float _RampLayerOffset1;

float4 __TintLayer2;
float _RampLayerOffset2;
float _RampLayerSoftness2;

float4 __TintLayer3;
float _RampLayerOffset3;
float _RampLayerSoftness3;

float _Shineness;
float _SpecularIntensity;
float4 _SpecularColor;
float _SpecularSmooth;

float _RimMax;
float _RimMin;
float _Roughness;
float _EnvIntensity;
float4 _Envmap_HDR;

v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord0;
o.normalDir=normalize( mul(unity_ObjectToWorld,v.normal).xyz);
o.tangentDir=normalize( mul(unity_ObjectToWorld,v.tangent).xyz);
o.pos_world=normalize( mul(unity_ObjectToWorld,v.vertex).xyz);
o.binormalDir=normalize( cross(o.normalDir,o.tangentDir)*v.tangent.w);
o.VertexColor = v.VertexColor;
TRANSFER_VERTEX_TO_FRAGMENT(o);
return o;
}

half4 frag (v2f i) : SV_Target
{
// sample the texture
half atten = LIGHT_ATTENUATION (i);
half3 base_col = tex2D(_MainTex, i.uv).xyz;
half3 normal_world= normalize(i.normalDir);
half3 tangent_world= normalize(i.tangentDir);
half3 binormal_world= normalize(i.binormalDir);
half3 pos_world= normalize(i.pos_world);
half3 normal_data = UnpackNormal(tex2D(_Normal,i.uv));
half3x3 TBN = half3x3 (tangent_world,binormal_world,normal_world);

normal_world = mul(normal_data,TBN);
half3 ViewDir = normalize(_WorldSpaceCameraPos.xyz-pos_world);
half3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
//漫反射
half NdotL = max(0,dot(normal_world,lightDir));
half half_lambert = (NdotL+1.0)*0.5;
//half half_lambert = half_lambert*ao //本来要乘上AO因为素材没有算了

half3 tint_Base_color1 = base_col;

//第一层ramp
half2 uv_ramp1 = half2(half_lambert+_RampLayerOffset1,0.5);
half toon_diffuse1 = tex2D(_DiffuseRamp,uv_ramp1).r;
half3 tint_color1 = lerp(half3(1,1,1),__TintLayer1,toon_diffuse1*__TintLayer1.a);

//第二层ramp
half2 uv_ramp2 = half2(half_lambert+_RampLayerOffset2,1-i.VertexColor.g+_RampLayerSoftness2);//1-i.VertexColor.g可以用_RampLayerSoftness2代替越大越柔和
half toon_diffuse2 = tex2D(_DiffuseRamp,uv_ramp2).g;
half3 tint_color2 = lerp(half3(1,1,1),__TintLayer2,toon_diffuse2*__TintLayer2.a);

//第三层ramp
half2 uv_ramp3 = half2(half_lambert+_RampLayerOffset3,1-i.VertexColor.b+_RampLayerSoftness3);//1-i.VertexColor.b可以用_RampLayerSoftness3代替越大越柔和
half toon_diffuse3 = tex2D(_DiffuseRamp,uv_ramp3).b;
half3 tint_color3 = lerp(half3(1,1,1),__TintLayer3,toon_diffuse3*__TintLayer3.a);

half3 final_diffuse = tint_Base_color1 * tint_color1 *tint_color2 *tint_color3 ;
//高光
//half3 half_R = normalize(lightDir+ViewDir);
half3 R = normalize(reflect(-lightDir,normal_world));
half specular_term = max(pow(dot(R,ViewDir),_Shineness),0.0001);//本来要乘上AO因为素材没有算了
specular_term = smoothstep(0.5-_SpecularSmooth*0.5,0.5+_SpecularSmooth*0.5,specular_term);//风格化高光

half3 final_specular = base_col * specular_term *_SpecularColor *atten *_LightColor0.xyz *_SpecularIntensity ;

//边缘光,环境光
half NDL = NdotL>0 ? 1:0;
half fresnel = 1- dot(ViewDir,normal_world);
half rim = smoothstep(_RimMin,_RimMax,fresnel);//本来要乘上AO因为素材没有算了
rim = rim * NDL;

half3 r = reflect(-ViewDir,normal_world);
half roughness = lerp(0,0.95,saturate(_Roughness));
roughness = roughness * (1.7-0.7*roughness );
half mip_level = roughness * 6.0;
half4 color_cubemap = texCUBElod(_Envmap,half4(r,mip_level));
half3 color_env = DecodeHDR(color_cubemap,_Envmap_HDR);
half3 final_env =color_env * rim * _EnvIntensity ;

half3 final_col = final_diffuse+final_specular + final_env;
return half4(final_col,1);

}
ENDCG
}
Pass
{
Name "OUTLINE"
Cull Front
ZWrite On
ColorMask RGB
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texCoord : TEXCOORD0;
float4 vertexColor : COLOR;
};
struct v2f {
float4 pos : POSITION;
float4 color : COLOR;
float4 tex : TEXCOORD0;
};
sampler2D _MainTex;
float _Outline;
float4 _OutlineColor;
v2f vert(appdata v) {
// just make a copy of incoming vertex data but scaled according to normal direction
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
float3 norm = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);
float2 offset = TransformViewToProjection(norm.xy);
// float2 offset = (norm.xy);
o.pos.xy += offset * _Outline*0.0001;
o.tex = v.texCoord;
o.color = v.vertexColor;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
half4 base_col = tex2D(_MainTex,i.tex);
return _OutlineColor*base_col;
}
ENDCG
}
}
FallBack "Diffuse"
}


眼睛

眼睛折射效果,我的模型不需要进行眼球移动,也不适合做这个,所以没弄
其中uworld和vworld是指UV线在世界空间下在方向。
在这里插入图片描述
以下也没搞清除
在这里插入图片描述

关于头发遮挡眼睛,解决办法

模板测试

在进行眼睛渲染的Pass时,将模板值设置为某一固定值:

1
2
3
4
5
6
Stencil {
Ref 1
Comp Always
Pass Replace
Fail Replace
}

头发等遮挡部分

1
2
3
4
5
6
Stencil {
Ref 1
Comp NotEqual
Pass Keep
Fail Keep
}

在这里插入图片描述

将遮挡脸部透明部分写入顶点色A通道

将头发渲染队列改为Transparent
让最后输出的颜色的A等于顶点色A

Eye.shader

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
Shader "Character/Eye"
{
Properties
{
_MainTex ("MainTex", 2D) = "white" {}
_Normal("Normal",2D) = "bump" {}


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

Pass
{
Stencil {
Ref 1
Comp Always
Pass Replace
Fail Replace
}
Name "Eye"
Tags { "LightMode" = "ForwardBase" }

CGPROGRAM

#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
// make fog work
#pragma multi_compile_fog
#include "AutoLight.cginc"
#include "UnityCG.cginc"

struct appdata
{
float4 vertex : POSITION;
float2 texcoord0 : TEXCOORD0;
float4 normal :NORMAL;
float4 tangent:TANGENT;
float4 VertexColor:COLOR;
};

struct v2f
{

float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;

float3 normalDir: TEXCOORD1;
float3 tangentDir: TEXCOORD2;
float3 binormalDir: TEXCOORD3;
float4 VertexColor: TEXCOORD4;
float3 pos_world: TEXCOORD5;
LIGHTING_COORDS(6,7)
};

sampler2D _MainTex;
sampler2D _Normal;
float4 _MainTex_ST;

v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord0;
o.normalDir=normalize( mul(unity_ObjectToWorld,v.normal).xyz);
o.tangentDir=normalize( mul(unity_ObjectToWorld,v.tangent).xyz);
o.pos_world=normalize( mul(unity_ObjectToWorld,v.vertex).xyz);
o.binormalDir=normalize( cross(o.normalDir,o.tangentDir)*v.tangent.w);
o.VertexColor = v.VertexColor;
TRANSFER_VERTEX_TO_FRAGMENT(o);
return o;
}

fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 base_col = tex2D(_MainTex, i.uv);
half3 normal_world= normalize(i.normalDir);
half3 tangent_world= normalize(i.tangentDir);
half3 binormal_world= normalize(i.binormalDir);
half3 normal_data = UnpackNormal(tex2D(_Normal,i.uv));
half3x3 TBN = half3x3 (tangent_world,binormal_world,normal_world);
normal_world = mul(TBN,normal_world);
half3 ViewDir = normalize(_WorldSpaceCameraPos.xyz-i.pos_world);
half3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
half fresnel = 1- saturate(dot(normal_world ,ViewDir ));
return base_col;
// return half4(normal_world,1);
}
ENDCG
}
}
FallBack "Diffuse"
}

头发

各项异性高光
原理见移动人物渲染

使用shiftmap增强质感

在这里插入图片描述

1
2
3
half2 uv_shift = i.uv * _ShiftRamp_ST.xy + _ShiftRamp_ST.zw;
half3 shift_col = tex2D(_ShiftRamp,uv_shift);
binormal_world = normalize( binormal_world + (shift_col+_ShiftOffset)* normal_world);

_ShiftRamp_ST.x调节质感,_ShiftOffset调节头发的上下偏移

初版

通过shininess调节柔和度
调大shininess让过度变硬

1
2
3
4
5
6
7
8
9
half StrandSpecular(half3 T, half3 V, half3 L, half exponent)
{
half3 H = normalize(L + V);
half dotTH = dot(T, H);
half sinTH = sqrt(1 - dotTH * dotTH);
half dirAtten = smoothstep(-1.0, 0.0, dotTH);
return dirAtten * pow(sinTH, exponent);
}
half3 final_specular = StrandSpecular(binormal_world,lightDir,ViewDir,_Shineness)*_SpecularColor;

改进

把柔和度调整到0,1中通过shininess值

1
2
3
4
5
6
7
8
9
10
half NdotV = max(0.0 ,dot(normal_world , ViewDir));
half3 H = normalize(lightDir+ViewDir);
half NdotH = dot(normal_world ,H );
half TdotH = dot(tangent_world ,H);
half BdotH = dot(binormal_world ,H);

BdotH /=_Shineness;
half spec_term =exp(-(TdotH*TdotH+BdotH*BdotH)/(1.0+NdotH));
half spec_atten = saturate(sqrt(max(0.0,half_lambert/NdotV)));
half3 final_specular = spec_term*spec_atten*_SpecularColor;

在第二层高光叠加的时候

再乘上一层shift_col当噪声

在这里插入图片描述

Hair.shader

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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Character/Hair"
{
Properties
{
_MainTex ("MainTex", 2D) = "white" {}
_Normal("Normal",2D) = "bump" {}
_DiffuseRamp ("DiffuseRamp", 2D) = "Black" {}
_Envmap ("Envmap", Cube) = "Black" {}
_ShiftRamp("ShiftRamp",2D)="Black"{}

[Header(Tint Base)]
_TintBase ("Tint Base", COLOR) = (0,0,0,1)
[Header(RampLayer1)]
_RampLayerOffset1 ("RampLayerOffset1", Range(-0.5,0.5)) = 0.5
_TintLayer1 ("Tint_Layer1", COLOR) = (0,0,0,1)
[Header(RampLayer2)]
_RampLayerOffset2 ("RampLayerOffset2", Range(-0.5,0.5)) = 0.5
_RampLayerSoftness2 ("RampLayerSoftness2", Range(0,1)) = 0.5
__TintLayer2 ("Tint Layer2", COLOR) = (0,0,0,1)
[Header(RampLayer3)]
_RampLayerOffset3 ("RampLayerOffset3", Range(-0.5,0.5)) = 0.5
_RampLayerSoftness3 ("RampLayerSoftness3", Range(0,1)) = 0.5
__TintLayer3 ("Tint Layer3", COLOR) = (0,0,0,1)
[Header(Specular1)]
_SpecularColor1 ("Specular Color1", COLOR) = (0,0,0,1)
_SpecularIntensity1 ("SpecularIntensity1", Float) = 1
_SpecularSmooth1 ("Specular Smooth1", Range(0,1)) = 1
_ShiftOffset1 ("Shift Offset1", Float) = 0
[Header(Specular2)]
_SpecularColor2 ("Specular Color2", COLOR) = (0,0,0,1)
_SpecularIntensity2 ("SpecularIntensity2", Float) = 1
_SpecularSmooth2 ("Specular Smooth2", Range(0,1)) = 1
_ShiftOffset2 ("Shift Offset2", Float) = 0



[Header(Rim)]
_RimMin("RimMin", Range(-2,2)) = 0.5
_RimMax("RimMax", Range(-2,2)) = 0.5
_Roughness("Roughness", Float) = 0
_EnvIntensity("Env Intensity", Float) = 0

[Header(Outline)]
_Outline ("Outline", Float) = 0.6
_OutlineColor ("OutlineColor", COLOR) = (0,0,0,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100

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

Stencil {
Ref 1
Comp NotEqual
Pass Keep
Fail Keep
}

CGPROGRAM

#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
// make fog work
#pragma multi_compile_fog
#include "AutoLight.cginc"
#include "UnityCG.cginc"

struct appdata
{
float4 vertex : POSITION;
float2 texcoord0 : TEXCOORD0;
float4 normal :NORMAL;
float4 tangent:TANGENT;
float4 VertexColor:COLOR;
};

struct v2f
{

float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;

float3 normalDir: TEXCOORD1;
float3 tangentDir: TEXCOORD2;
float3 binormalDir: TEXCOORD3;
float4 VertexColor: TEXCOORD4;
float3 pos_world: TEXCOORD5;
LIGHTING_COORDS(6,7)
};

sampler2D _MainTex;
sampler2D _Normal;
sampler2D _DiffuseRamp;
samplerCUBE _Envmap;
sampler2D _ShiftRamp;
float4 _ShiftRamp_ST;

float4 _LightColor0;

float4 _MainTex_ST;
float4 _TintBase;
float4 __TintLayer1;
float _RampLayerOffset1;

float4 __TintLayer2;
float _RampLayerOffset2;
float _RampLayerSoftness2;

float4 __TintLayer3;
float _RampLayerOffset3;
float _RampLayerSoftness3;


float _ShiftOffset1;
float _SpecularIntensity1;
float4 _SpecularColor1;
float _SpecularSmooth1;

float _ShiftOffset2;
float _SpecularIntensity2;
float4 _SpecularColor2;
float _SpecularSmooth2;

float _RimMax;
float _RimMin;
float _Roughness;
float _EnvIntensity;
float4 _Envmap_HDR;

v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord0;
o.normalDir=normalize( mul(unity_ObjectToWorld,v.normal).xyz);
o.tangentDir=normalize( mul(unity_ObjectToWorld,v.tangent).xyz);
o.pos_world=normalize( mul(unity_ObjectToWorld,v.vertex).xyz);
o.binormalDir=normalize( cross(o.normalDir,o.tangentDir)*v.tangent.w);
o.VertexColor = v.VertexColor;
TRANSFER_VERTEX_TO_FRAGMENT(o);
return o;
}



half StrandSpecular(half3 T, half3 V, half3 L, half exponent)
{
half3 H = normalize(L + V);
half dotTH = dot(T, H);
half sinTH = sqrt(1 - dotTH * dotTH);
half dirAtten = smoothstep(-1.0, 0.0, dotTH);
return dirAtten * pow(sinTH, exponent);
}

half4 frag (v2f i) : SV_Target
{
// sample the texture
half atten = LIGHT_ATTENUATION (i);
half3 base_col = tex2D(_MainTex, i.uv).xyz;
half3 normal_world = normalize(i.normalDir);
half3 tangent_world= normalize(i.tangentDir);
half3 binormal_world= normalize(i.binormalDir);

half3 normal_data = UnpackNormal(tex2D(_Normal,i.uv));
half3x3 TBN = half3x3 (tangent_world,binormal_world,normal_world);

normal_world = mul(normal_data,TBN);
half3 ViewDir = normalize(_WorldSpaceCameraPos.xyz-i.pos_world);
half3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
//漫反射
half NdotL = max(0,dot(normal_world,lightDir));
half half_lambert = (NdotL+1.0)*0.5;
//half half_lambert = half_lambert*ao //本来要乘上AO因为素材没有算了

half3 tint_Base_color1 = base_col;

//第一层ramp
half2 uv_ramp1 = half2(half_lambert+_RampLayerOffset1,0.5);
half toon_diffuse1 = tex2D(_DiffuseRamp,uv_ramp1).r;
half3 tint_color1 = lerp(half3(1,1,1),__TintLayer1,toon_diffuse1*__TintLayer1.a);

//第二层ramp
half2 uv_ramp2 = half2(half_lambert+_RampLayerOffset2,1-i.VertexColor.g+_RampLayerSoftness2);//1-i.VertexColor.g可以用_RampLayerSoftness2代替越大越柔和
half toon_diffuse2 = tex2D(_DiffuseRamp,uv_ramp2).g;
half3 tint_color2 = lerp(half3(1,1,1),__TintLayer2,toon_diffuse2*__TintLayer2.a);

//第三层ramp
half2 uv_ramp3 = half2(half_lambert+_RampLayerOffset3,1-i.VertexColor.b+_RampLayerSoftness3);//1-i.VertexColor.b可以用_RampLayerSoftness3代替越大越柔和
half toon_diffuse3 = tex2D(_DiffuseRamp,uv_ramp3).b;
half3 tint_color3 = lerp(half3(1,1,1),__TintLayer3,toon_diffuse3*__TintLayer3.a);

half3 final_diffuse = tint_Base_color1 * tint_color1 *tint_color2 *tint_color3 ;
//各向异性高光
half2 uv_shift = i.uv * _ShiftRamp_ST.xy + _ShiftRamp_ST.zw;
half3 shift_col = tex2D(_ShiftRamp,uv_shift);
half3 binormal_world1 = normalize( binormal_world + (shift_col+_ShiftOffset1)* normal_world);
half3 binormal_world2 = normalize( binormal_world + (shift_col+_ShiftOffset2)* normal_world);
half NdotV = max(0.00001 ,dot(normal_world , ViewDir));
half3 H = normalize(lightDir+ViewDir);
half NdotH = dot(normal_world ,H );
half TdotH = dot(tangent_world ,H);


half BdotH1 =dot(binormal_world1 ,H)/_SpecularSmooth1;
half spec_term1 =exp(-(TdotH*TdotH+BdotH1*BdotH1)/(1.0+NdotH));
half spec_atten1 = saturate(sqrt(max(0.0,half_lambert/NdotV)));
half3 specular_col1 = spec_term1* spec_atten1 * _LightColor0.xyz * _SpecularIntensity1 * atten * _SpecularColor1.xyz;

half BdotH2 =dot(binormal_world2 ,H)/_SpecularSmooth2;
half spec_term2 =exp(-(TdotH*TdotH+BdotH2*BdotH2)/(1.0+NdotH));
half spec_atten2 = saturate(sqrt(max(0.0,half_lambert/NdotV)));
half3 specular_col2 = spec_term2* spec_atten2 * _LightColor0.xyz * _SpecularIntensity2 * atten *_SpecularColor1.xyz*shift_col;

half3 final_specular = specular_col1 + specular_col2 ;

//half3 half_R = normalize(lightDir+ViewDir);
//half3 R = normalize(reflect(-lightDir,normal_world));
//half specular_term = max(pow(dot(R,ViewDir),_Shineness),0.0001);//本来要乘上AO因为素材没有算了
//specular_term = smoothstep(0.5-_SpecularSmooth*0.5,0.5+_SpecularSmooth*0.5,specular_term);//风格化高光

//half3 final_specular = base_col * specular_term *_SpecularColor *atten *_LightColor0.xyz *_SpecularIntensity ;

//边缘光,环境光
half NDL = NdotL>0 ? 1:0;
half fresnel = 1- dot(ViewDir,normal_world);
half rim = smoothstep(_RimMin,_RimMax,fresnel);//本来要乘上AO因为素材没有算了
rim = rim * NDL;

half3 r = reflect(-ViewDir,normal_world);
half roughness = lerp(0,0.95,saturate(_Roughness));
roughness = roughness * (1.7-0.7*roughness );
half mip_level = roughness * 6.0;
half4 color_cubemap = texCUBElod(_Envmap,half4(r,mip_level));
half3 color_env = DecodeHDR(color_cubemap,_Envmap_HDR);
half3 final_env =color_env * rim * _EnvIntensity ;

half3 final_col = final_diffuse +final_specular +final_env;
return half4(final_col,1);

}
ENDCG
}
Pass
{
Name "OUTLINE"
Cull Front
ZWrite On
ColorMask RGB
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texCoord : TEXCOORD0;
float4 vertexColor : COLOR;
};
struct v2f {
float4 pos : POSITION;
float4 color : COLOR;
float4 tex : TEXCOORD0;
};
sampler2D _MainTex;
float _Outline;
float4 _OutlineColor;
v2f vert(appdata v) {
// just make a copy of incoming vertex data but scaled according to normal direction
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
float3 norm = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);
float2 offset = TransformViewToProjection(norm.xy);
// float2 offset = (norm.xy);
o.pos.xy += offset * _Outline*0.0001;
o.tex = v.texCoord;
o.color = v.vertexColor;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
half4 base_col = tex2D(_MainTex,i.tex);
return _OutlineColor*base_col;
}
ENDCG
}
}
FallBack "Diffuse"
}

脸部

在皮肤的基础上提亮一点。