非真实感渲染(Non-Photorealistic Rendering)
非真实感渲染(Non-Photorealistic Rendering)
卡通风格渲染
渲染轮廓线
方法
- 基于观察角度和表面法线的轮廓线渲染 ,视角方向和表面法线点乘结果来得到轮廓线。这种方法简单迅速可以在一个 pass 中就得到渲染结果,但时局限性很大,很多模型得不到满意的描边效果
- 过程式几何轮廓线渲染,使用两个 pass,一个渲染正面,一个渲染背面,并让轮廓可见。优点快速有效,适用于大部分表面平滑模型,不适合类似于正方体这样平整模型
- 基于图像处理的轮廓线渲染 ,例如使用 sobel 算子的边缘检测,缺点是无法检测深度和法线变化很小的轮廓线,例如桌上的一张纸
- 基于轮廓边检测的轮廓线渲染,上面的方法无法控制轮廓线的渲染风格。用于精确检测轮廓边并控制渲染风格 \(( \vec{n_0} \cdot \vec v>0) \neq (\vec{n_1} \cdot \vecv>0)\) n0 n1 分别表示这条边相邻两个三角面皮的法线,v 是视角到该变任意顶点的方向
顶点扩张
在视角空间下把顶点沿法线向往外扩张一段距离,以此来让背部轮廓线可见。但是如果直接对顶点进行扩张,对于一些内凹模型,就会发生背面面片遮挡正面面片的情况 为了尽量避免这种情况,在扩张背面顶点之前,先对顶点法线的 z 分量进行一定处理,使他们等于一个定值,然后把法线归一化后进行扩张。
1
2
3
viewNormal.z=-0.5;
viewNormal = normalize(viewNormal);
viewPos = viewPos +viewNormal*_Outline;
添加高光
卡通风格渲染中的高光往往是一块分界明显的纯色区域,对于卡通渲染需要的高光反射光照模型,我们会 normalDir 和 halfdir 的点乘结果和一个阈值比较,若小于则高光反射系数 返回 0,否则返回 1。
\[\mathtt{c}_{specular}=(\mathtt{c}_{light}\cdot\mathtt{m}_{specular})max(0,\vec n\cdot\vec h)^{m_{gloss}}\]实现
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
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_Ramp ("Ramp Texture", 2D) = "white" {}
_Outline ("Outline", Range(0, 1)) = 0.1
_OutlineColor ("Outline Color", Color) = (0, 0, 0, 1)
_Specular ("Specular", Color) = (1, 1, 1, 1)
_SpecularScale ("Specular Scale", Range(0, 0.1)) = 0.01
}
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry"}
Pass {
NAME "OUTLINE"
//剔除前面,只渲染背面
Cull Front
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
float _Outline;
fixed4 _OutlineColor;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
};
v2f vert (a2v v) {
v2f o;
//视角空间下顶点
float4 pos = mul(UNITY_MATRIX_MV, v.vertex);
//法线
float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);
//扩展前指定 z 的值
normal.z = -0.5;
//延法线方向扩展 顶点
pos = pos + float4(normalize(normal), 0) * _Outline;
//顶点变换到裁剪空间
o.pos = mul(UNITY_MATRIX_P, pos);
return o;
}
float4 frag(v2f i) : SV_Target {
//背面颜色指定为轮廓颜色
return float4(_OutlineColor.rgb, 1);
}
ENDCG
}
Pass {
Tags { "LightMode"="ForwardBase" }
//背面已经在上一pass着色
Cull Back
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 _Ramp;
fixed4 _Specular;
fixed _SpecularScale;
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)
};
v2f vert (a2v v) {
v2f o;
o.pos = UnityObjectToClipPos( v.vertex);
o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
TRANSFER_SHADOW(o);
return o;
}
float4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
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;
fixed spec = dot(worldNormal, worldHalfDir);
//邻像素之间近似导数值
fixed w = fwidth(spec) * 2.0;
fixed3 specular = _Specular.rgb * lerp(0, 1, smoothstep(-w, w, spec + _SpecularScale - 1)) * step(0.0001, _SpecularScale);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
本文由作者按照 CC BY 4.0 进行授权