US4L6——卡通风格渲染
US4L6——卡通风格渲染
卡通风格渲染
卡通风格渲染(Cartoon Shading),也称为非真实感渲染(NPR)或卡通渲染(Toon Shading)
主要目的是使 3D 模型看起来更像手绘的二维卡通或漫画风格,而不是逼真写实的 3D 渲染效果。
这种风格的渲染常用于游戏、动画和电影中,用来创造一种独特的艺术风格
卡通风格渲染基本原理
一句话总结卡通风格渲染基本原理:让光的过渡效果变硬并且实现轮廓描边!
关键点:
-
如何让光的过渡效果变硬
影响对象光照效果的部分主要是:漫反射的计算 + 高光反射的计算
因此,想要光的过渡效果变硬,只需要从这两方面去考虑即可这里可以从 Blinn Phong 光照模型公式下手:详见:US3S1L7——Blinn-Phong光照模型
其中:
-
环境光颜色 =
UNITY_LIGHTMODEL_AMBIENT
(unity_AmbientSky
、unity_AmbientEquator
、unity_AmbientGround
)关于上边四个变量,详见:US3S1L5——Phong光照模型
-
漫反射光颜色 = 兰伯特光照模型 或 半兰伯特光照模型 计算得到的颜色
-
高光反射光颜色 = Blinn Phong 式高光反射光照模型 计算得到的颜色
漫反射部分的变硬我们需要回顾之前学习的渐变纹理知识,详见:US3S2L8——渐变纹理基本概念
渐变纹理的基本原理就是在计算漫反射时利用 半兰伯特光照模型 公式中后半部分
半兰伯特光照模型后半部分会得到一个 0~1 区间的值,将这个值作为 uv 坐标中的 uv 值,从渐变纹理中取出颜色
与公式中前面部分进行颜色叠加,最终得到漫反射光照颜色。也就是说,决定漫反射明暗的不再是单由 0~1 这个值决定,而是通过 0~1 这个值从渐变纹理中取出的颜色进行叠加达到最终效果
这意味着,原本由光照带来的明暗变化会变成通过从渐变纹理内映射颜色来叠加颜色,这样可以改变一个模型光照的明暗表现
高光反射部分的变硬,我们只需要基于它的公式修改计算规则即可:
公式:
- 得到的结果就是
- 光泽度是幂运算,假设光泽度为 ,相当于:
我们把 这部分直接进行简化
1
2
3fixed spec = dot(worldNormal, worldHalfDir); // 直接利用法线和半角向量的点乘结果进行计算
spec = step(_SpecularScale, spec); // 用一个阈值来进行比较,如果小于这个阈值,系数为0,大于阈值则系数为1
fixed3 specular = _Specular.rgb * spec; // 直接用0或者1乘以高光颜色,进行叠加即可相当于之前平滑的值变化变得只有 1 和 0 两种情况,要不有,要不没有
-
-
如何实现轮廓描边
我们之前其实已经学习过模型描边效果的实现,它的基本原理是:
使用两个
Pass
渲染对象:- 一个
Pass
用于渲染沿法线方向放大的模型 - 一个
Pass
用于正常渲染正常模型
相当于先用纯色渲染一次放大后的模型,再用模型本来的颜色覆盖重合部分。
详见:US4L2——模型描边效果
但是我们在实现卡通风格渲染时不会使用这种方式,我们将采用一种新的方式来制作轮廓描边。
新的方法同样两个
Pass
渲染对象:- 一个
Pass
渲染背面将模型背面顶点沿法线方向偏移扩大 - 一个
Pass
渲染正面正常渲染
这样实现的效果会让模型上有重叠的结构出现描边效果
注意:
- 模型背面就是法线方向和摄像机面朝向呈锐角的部分
- 模型正面就是法线方向和摄像机面朝向呈钝角的部分
- 一个
实现漫反射变硬和外轮廓效果
-
新建 Shader ,将渐变纹理的综合实践相关代码直接复制过来
代码详见:US3S2L10——渐变纹理综合实现
-
将复制过来的代码的
Pass
改为只渲染背面1
2
3
4
5
6
7
8
9
10// Base Pass 基础渲染通道
Pass
{
Tags { "LightMode" = "ForwardBase" }
Cull Back
CGPROGRAM
// ...
ENDCG
} -
加入阴影接收相关内容
-
编译指令
#pragma multi_compile_fwdbase
-
内置文件
#include "AutoLight.cginc"
-
v2f
结构体中加入float3 worldPos
和SHADOW_COORDS(4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct v2f
{
float4 pos: SV_POSITION;
float4 uv: TEXCOORD0; //可以使用一个float4来同时存储主要纹理的uv(xy存储)和法线纹理的uv(zw存储)
float3 lightDir: TEXCOORD1; //相对于切线空间下的光的方向
float3 viewDir: TEXCOORD2; //相对于切线空间下的视角方向
float3 worldPos: TEXCOORD3; //世界空间下顶点坐标
SHADOW_COORDS(4) //阴影坐标宏
}; -
顶点着色器计算中加入
世界空间顶点坐标计算:
TRANSFER_SHADOW(o);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25v2f vert (appdata_full v)
{
v2f data;
data.pos = UnityObjectToClipPos(v.vertex); //计算裁剪空间下顶点坐标
// 分别计算主纹理和法线纹理的缩放平移
data.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
data.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
// 计算副切线
float3 binormal = cross(normalize(v.tangent), normalize(v.normal)) * v.tangent.w;
// 得到模型空间到切线空间的转换矩阵
float3x3 rotation = float3x3(
v.tangent.xyz, //切线
binormal, //副切线
v.normal //法线
);
data.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)); // 切线空间下的光的方向
data.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)); // 切线空间下的视角方向
data.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; // 世界空间下的顶点坐标
TRANSFER_SHADOW(data); // 阴影坐标转换宏
return data;
} -
光照衰减相关计算
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
用来和halfLambertNum
进行乘法运算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
26fixed4 frag (v2f i) : SV_Target
{
float4 packedNormal = tex2D(_BumpMap, i.uv.zw); // 获取法线纹理的颜色数据
float3 tangentNormal = UnpackNormal(packedNormal); // 将颜色数据逆运算并解压缩,得到切线空间下法线数据
// 将法线数据的xy乘以凹凸系数,根据xy修正z,避免凹凸系数影响光照亮度
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
// 计算带纹理颜色的BlinnPhong光照计算,这里使用已经计算好的切线数据
fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _MainColor.rgb; // 反射率
// 漫反射光照计算:使用已经计算完毕的切线数据和光照方向,结合渐变纹理,先计算渐变纹理的uv坐标,再计算漫反射颜色
fixed halfLambertNum = dot(tangentNormal, normalize(i.lightDir)) * 0.5 + 0.5;
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos); // 使用UNITY_LIGHT_ATTENUATION计算光照衰减
halfLambertNum *= atten;
fixed3 diffuseColor = _LightColor0.rgb * albedo.rgb * tex2D(_RampTex, fixed2(halfLambertNum, halfLambertNum)).rgb;
// 高光反射光照计算:这里需要使用已经计算完毕的切线数据和光照方向
float3 halfA = normalize(normalize(i.viewDir) + normalize(i.lightDir)); // 半角向量
fixed3 specularColor = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(tangentNormal, halfA)), _SpecularNum);
// 最终颜色计算
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + diffuseColor + specularColor;
return fixed4(color.rgb, 1);
} -
FallBack "Diffuse"
-
-
处理渲染外轮廓(描边)
-
加入边缘线颜色和宽度属性
_OutLineColor
、_OutLineWidth
1
2
3
4
5
6
7
8
9
10
11
12Properties
{
_MainColor("MainColor", Color) = (1, 1, 1, 1)
_MainTex("MainTex", 2D) = ""{}
_BumpMap("BumpMap", 2D) = ""{}
_BumpScale("BumpScale", Range(0, 1)) = 1
_RampTex("_RampTex", 2D) = ""{}
_SpecularColor("SpecularColor", Color) = (1, 1, 1, 1)
_SpecularNum("SpecularNum", Range(0, 20)) = 18
_OutLineColor("OutLineColor", Color) = (0, 0, 0, 0)
_OutLineWidth("OutLineWidth", Float) = 0.005
} -
加入背面渲染的
Pass
,用于处理轮廓描边使用
Cull Front
剔除正面,让背面的顶点沿其法线方向拓展
这里使用 Name 为Pass
命名,方便后续 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// OutLine Pass 描边渲染通道
Pass
{
Name "OutLinePass"
Cull Front
CGPROGRAM
struct v2f
{
float4 pos: SV_POSITION;
};
fixed4 _OutLineColor; // 边缘线颜色
float _OutLineWidth; // 边缘线宽度
v2f vert(appdata_base v)
{
v2f o;
// 把背面看不到的顶点朝法线方向往外扩展
v.vertex.xyz += normalize(v.normal) * _OutLineWidth;
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
// 直接返回边缘线颜色,相当于背面是纯色
return _OutLineColor;
}
ENDCG
}
-
实现高光变硬效果
主要修改高光反射颜色计算相关内容
-
计算出半角向量
-
用法线和半角向量进行点乘
-
用点乘的结果和一个阈值进行比较 如果小于阈值,取0、大于阈值,取1
对原来的
_SpecularNum
属性修改,让其变为光照的阈值1
2
3
4
5
6
7
8
9
10
11
12Properties
{
_MainColor("MainColor", Color) = (1, 1, 1, 1)
_MainTex("MainTex", 2D) = ""{}
_BumpMap("BumpMap", 2D) = ""{}
_BumpScale("BumpScale", Range(0, 1)) = 1
_RampTex("_RampTex", 2D) = ""{}
_SpecularColor("SpecularColor", Color) = (1, 1, 1, 1)
_SpecularNum("SpecularNum", Range(0, 1)) = 0.5 // 高光阈值
_OutLineColor("OutLineColor", Color) = (0, 0, 0, 0)
_OutLineWidth("OutLineWidth", Float) = 0.005
} -
利用这个结果和高光颜色进行叠加
-
最后参与到布林方颜色公式计算中
1 | fixed4 frag (v2f i) : SV_Target |
显示效果
完整 Shader 代码如下:
1 | Shader "TeachShader/ToonShader" |
将高光阈值设置为0.95,边缘线宽度设置为0.005
可见,图内的机器人出现了的阴影和描边效果,光照颜色和高光颜色的过渡也变得更硬了