基本漫反射

代码编写

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
Shader "Unlit/017"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Diffuse("Diffuse", Color) = (1,1,1,1)

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

Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"

struct v2f
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
fixed3 worldNormal : TEXCOORD1;
float3 worldPos : TEXCOORD2;

};

sampler2D _MainTex;
float4 _MainTex_ST;
float4 _Diffuse;

v2f vert (appdata_base v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}

fixed4 frag (v2f i) : SV_Target
{
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

fixed4 albedo = tex2D(_MainTex, i.uv);
//漫反射
fixed3 worldLightDir = UnityWorldSpaceLightDir(i.worldPos);

float difLight = dot(worldLightDir, i.worldNormal)*0.5+0.5;
fixed3 diffuse = _LightColor0.rgb * albedo * _Diffuse.rgb * difLight;

return float4(ambient + diffuse, 1);
}
ENDCG
}
}
}

描边原理

第一步:

​ 使用一个纯颜色画一次,不进行其它操作

第二步:

​ 画第二遍时,会把第一次所画的覆盖住,为实现描边,此时可以将第一所画的沿其法线方向向外扩,即可实现描边

具体实现:

​ 先用一个Pass通道画一次模型的背面,再沿其法相方向往外扩。之后再用一个Pass通道正常操作。

实现法线外拓

方法一:物体空间法线外拓

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
Pass
{
Name "OutLine"
Cull Front
CGPROGRAM

#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"

float _OutLine; //_OutLine("OutLine", Range(0,0.2)) = 0.1
fixed _OutLineColor; //_OutLineColor("OutLineColor", Color) = (0,0,0,0)

struct v2f
{
float4 vertex : SV_POSITION;

};

v2f vert(appdata_base v)
{
v2f o;
v.vertex.xyz += v.normal * _OutLine;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}

float4 frag(v2f i):SV_Target
{
return _OutLineColor;
}

ENDCG
}

方法二:视角空间法线外拓

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
Pass
{
Name "OutLine"
Cull Front
CGPROGRAM

#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"

float _OutLine;
fixed _OutLineColor;

struct v2f
{
float4 vertex : SV_POSITION;

};

v2f vert(appdata_base v)
{
v2f o;

float4 pos = mul(UNITY_MATRIX_V, mul(unity_ObjectToWorld, v.vertex));
float3 normal = normalize(mul((float3x3)UNITY_MATRIX_IT_MV, v.normal));
pos = pos + float4(normal, 0) * _OutLine;
o.vertex = mul(UNITY_MATRIX_P, pos);

return o;
}

float4 frag(v2f i):SV_Target
{
return _OutLineColor;
}

ENDCG
}

方法三:裁剪空间法线外拓

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
Pass
{
Name "OutLine"
Cull Front
CGPROGRAM

#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"

float _OutLine;
fixed _OutLineColor;

struct v2f
{
float4 vertex : SV_POSITION;

};

v2f vert(appdata_base v)
{
v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);
float3 normal = normalize(mul((float3x3)UNITY_MATRIX_IT_MV, v.normal));
float2 viewNormal = TransformViewToProjection(normal.xy);
o.vertex.xy += viewNormal * _OutLine;

return o;
}

float4 frag(v2f i):SV_Target
{
return _OutLineColor;
}

ENDCG
}

颜色处理

实现卡通颜色

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

Properties
{
...
_Steps("Steps", Range(1, 30)) = 1
_ToonEffect("ToonEffect", Range(0,1)) = 0.5
}

float _Steps;
float _ToonEffect;

fixed4 frag (v2f i) : SV_Target
{
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

fixed4 albedo = tex2D(_MainTex, i.uv);
//漫反射
fixed3 worldLightDir = UnityWorldSpaceLightDir(i.worldPos);

//卡通颜色
float difLight = dot(worldLightDir, i.worldNormal)*0.5+0.5;
//将颜色平滑在[0,1]之间
difLight = smoothstep(0, 1, difLight);
//颜色离散化
float toon = floor(difLight * _Steps) / _Steps;
difLight = lerp(difLight, toon, _ToonEffect);

fixed3 diffuse = _LightColor0.rgb * albedo * _Diffuse.rgb * difLight;

return float4(ambient + diffuse, 1);
}

效果图

image-20241118123154373

​ 可以通过调整Steps来实现阴影具体的离散化程度,ToonEffect来实现每层之间的渐变深度。

除此方法外还可以使用渐变纹理实现

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

Properties
{
...
//渐进纹理
_RampTex("RampTex", 2D) = "white" {}
}

sampler2D _RampTex;

fixed4 frag (v2f i) : SV_Target
{
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

fixed4 albedo = tex2D(_MainTex, i.uv);
//漫反射
fixed3 worldLightDir = UnityWorldSpaceLightDir(i.worldPos);

//卡通颜色
float difLight = dot(worldLightDir, i.worldNormal)*0.5+0.5;

//渐进纹理采样
float4 rampColor = tex2D(_RampTex, fixed2(difLight, difLight));

difLight = lerp(difLight, toon, _ToonEffect);

fixed3 diffuse = _LightColor0.rgb * albedo * _Diffuse.rgb * rampColor;

return float4(ambient + diffuse, 1);
}

边缘光

原理

image-20241119092824710

​ 当视角方向与法线垂直,此时便能找到边缘。

代码

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
_RimColor("RimColor", Color) = (0,0,0,0)
_RimPower("RimPower", Range(0.001, 3)) = 1

fixed4 _RimColor;
float _RimPower;

fixed4 frag (v2f i) : SV_Target
{
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

fixed4 albedo = tex2D(_MainTex, i.uv);

fixed3 worldLightDir = UnityWorldSpaceLightDir(i.worldPos);
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));

//卡通颜色
float difLight = dot(worldLightDir, i.worldNormal)*0.5+0.5;
//渐进纹理采样
//float4 rampColor = tex2D(_RampTex, fixed2(difLight, difLight));

//将颜色平滑在[0,1]之间
difLight = smoothstep(0, 1, difLight);
//颜色离散化
float toon = floor(difLight * _Steps) / _Steps;
difLight = lerp(difLight, toon, _ToonEffect);

fixed3 diffuse = _LightColor0.rgb * albedo * _Diffuse.rgb * difLight;

float rim = 1 - dot(i.worldNormal, viewDir);
fixed3 rimColor = _RimColor * pow(rim, 1/_RimPower);

return float4(ambient + diffuse + rimColor, 1);
}

遮挡效果

Xray实现原理

​ 实现效果:当一个人物在游戏中被遮挡后,实现一个效果可以看见被物体遮挡的人物。

​ 在正常的深度测试时,只有当一个物体的深度值小于等于已有的深度值时,这个物体才能被画出来,所以我们可以反着来,只有当深度值大于已有的深度值时,它才被画出来(需要另起一个Pass通道),这样便可以实现前面所说的遮挡效果。

代码

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
Pass
{
Blend DstAlpha OneMinusSrcColor
ZWrite Off
ZTest Greater
CGPROGRAM

#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
fixed4 _XRayColor;
float _XRayPower;

struct v2f
{
float4 vertex : SV_POSITION;
float3 worldPos : TEXCOORD0;
float3 worldNormal : TEXCOORD1;
};

v2f vert(appdata_base v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
return o;
}

fixed4 frag(v2f i) : SV_Target
{
float3 normal = normalize(i.worldNormal);
float3 viewDir = normalize(UnityObjectToViewPos(i.worldPos));
float rim = 1 - dot(normal, viewDir);
return _XRayColor * pow(rim, 1/_XRayPower);
}

ENDCG
}

最终优化后代码

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
Shader "Unlit/017"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Diffuse("Diffuse", Color) = (1,1,1,1)
_OutLine("OutLine", Range(0,0.2)) = 0.1
_OutLineColor("OutLineColor", Color) = (0,0,0,0)
_Steps("Steps", Range(1, 30)) = 1
_ToonEffect("ToonEffect", Range(0,1)) = 0.5
//渐进纹理
//_RampTex("RampTex", 2D) = "white" {}
//边缘光
_RimColor("RimColor", Color) = (1,1,1,1)
_RimPower("RimPower", Range(0.001, 3)) = 1
//Xray
_XRayColor("XRayColor", Color) = (1,1,1,1)
_XRayPower("XRayPower", Range(0.001, 3)) = 1
}
SubShader
{
Tags {"Queue"="Geometry+1000" "RenderType"="Opaque" }
LOD 100

Pass
{
Name "Xray"
Tags {"ForceNoShadowCasting"="true"}
Blend DstAlpha One
ZWrite Off
ZTest Greater
CGPROGRAM

#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"

fixed4 _XRayColor;
float _XRayPower;

struct v2f
{
float4 vertex : SV_POSITION;
float3 viewDir : TEXCOORD0;
float3 Normal : TEXCOORD1;
};

v2f vert(appdata_base v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.Normal = v.normal;
o.viewDir = UnityWorldSpaceViewDir(v.vertex);
return o;
}

fixed4 frag(v2f i) : SV_Target
{
float3 normal = normalize(i.Normal);
float3 viewDir = normalize(i.viewDir);
float rim = 1 - dot(normal, viewDir);
return _XRayColor * pow(rim, 1/_XRayPower);
}

ENDCG
}

Pass
{
Name "OutLine"
Cull Front
CGPROGRAM

#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"

float _OutLine;
fixed _OutLineColor;

struct v2f
{
float4 vertex : SV_POSITION;

};

v2f vert(appdata_base v)
{
v2f o;
//物体空间外拓实现描边效果
// v.vertex.xyz += v.normal * _OutLine;
// o.vertex = UnityObjectToClipPos(v.vertex);

//视角空间外拓实现描边效果
// float4 pos = mul(UNITY_MATRIX_V, mul(unity_ObjectToWorld, v.vertex));
// float3 normal = normalize(mul((float3x3)UNITY_MATRIX_IT_MV, v.normal));
// pos = pos + float4(normal, 0) * _OutLine;
// o.vertex = mul(UNITY_MATRIX_P, pos);

//裁剪空间外拓实现描边效果
o.vertex = UnityObjectToClipPos(v.vertex);
float3 normal = normalize(mul((float3x3)UNITY_MATRIX_IT_MV, v.normal));
float2 viewNormal = TransformViewToProjection(normal.xy);
o.vertex.xy += viewNormal * _OutLine;

return o;
}

float4 frag(v2f i):SV_Target
{
return _OutLineColor;
}

ENDCG
}

Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"

struct v2f
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
fixed3 worldNormal : TEXCOORD1;
float3 worldPos : TEXCOORD2;

};

sampler2D _MainTex;
float4 _MainTex_ST;
float4 _Diffuse;
float _Steps;
float _ToonEffect;
//sampler2D _RampTex;
fixed4 _RimColor;
float _RimPower;

v2f vert (appdata_base v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}

fixed4 frag (v2f i) : SV_Target
{
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

fixed4 albedo = tex2D(_MainTex, i.uv);

fixed3 worldLightDir = UnityWorldSpaceLightDir(i.worldPos);
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));

//卡通颜色
float difLight = dot(worldLightDir, i.worldNormal)*0.5+0.5;
//渐进纹理采样
//float4 rampColor = tex2D(_RampTex, fixed2(difLight, difLight));

//将颜色平滑在[0,1]之间
difLight = smoothstep(0, 1, difLight);
//颜色离散化
float toon = floor(difLight * _Steps) / _Steps;
difLight = lerp(difLight, toon, _ToonEffect);

fixed3 diffuse = _LightColor0.rgb * albedo * _Diffuse.rgb * difLight;

float rim = 1 - dot(i.worldNormal, viewDir);
fixed3 rimColor = _RimColor * pow(rim, 1/_RimPower);

return float4(ambient + diffuse + rimColor, 1);
}
ENDCG
}
}
}

卡通场景

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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Snow : MonoBehaviour
{
private const string SNOW_ON = "SNOW_NON";

private const string SNOWLEVEL = "_Snow";

private float m_Time = 0f;

private bool m_IsSnow = true;
// Start is called before the first frame update
void Start()
{
Shader.EnableKeyword("SNOW_ON");
}

// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.A))
{
if (m_IsSnow)
{
m_Time += Time.deltaTime;
if (m_Time > 5f)
{
m_IsSnow = false;
m_Time = 0f;
}
Shader.SetGlobalFloat(SNOWLEVEL, m_Time / 25f);
}
}
else if (Input.GetKeyDown(KeyCode.D))
{
Shader.SetGlobalFloat(SNOWLEVEL, 0f);
}
}
}

场景效果

积雪

没有积雪