12.卡通风格渲染-具体实现
12.1 知识点
知识回顾 卡通风格渲染基恩原理
卡通风格渲染的基本原理是让光的过渡效果变硬,并且实现轮廓描边。
关键点:
- 如何让光的过渡效果变硬
利用渐变纹理处理漫反射,并通过自定义简化公式计算高光反射 - 如何实现轮廓描边
通过两个 Pass 实现:一个 Pass 渲染背面以实现轮廓描边,另一个 Pass 正常渲染正面
不需要关闭深度写入,模型正面的内容可以通过深度测试
实现漫反射变硬和外轮廓效果
主要步骤
- 新建 Shader,将渐变纹理的综合实践相关代码直接复制过来
- 将复制过来的代码的 Pass 改为只渲染背面
- 加入阴影接收相关内容
- 编译指令:
#pragma multi_compile_fwdbase
- 内置文件:
#include "AutoLight.cginc"
- 在 v2f 结构体中加入:
float3 worldPos
和SHADOW_COORDS(4)
- 在顶点着色器中加入:
- 世界空间顶点坐标的计算
- 调用
TRANSFER_SHADOW(o);
传递阴影数据
- 在光照衰减计算中使用:
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
用于与半兰伯特值相乘
- 添加回退方案:
Fallback "Diffuse"
- 编译指令:
- 处理渲染外轮廓(描边)
- 加入边缘线颜色和宽度属性:
_OutLineColor
、_OutLineWidth
- 新增一个专门渲染背面描边效果的 Pass
- 加入边缘线颜色和宽度属性:
新建 Shader(将 Shader 开发入门中渐变纹理的综合实践代码直接复制过来)
Shader "Unlit/Lesson12_ToonShader"
{
Properties
{
_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(8,256)) = 18
}
SubShader
{
Pass
{
Tags
{
"LightMode"="ForwardBase"
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
float4 _MainColor; //漫反射颜色
sampler2D _MainTex; //颜色纹理
float4 _MainTex_ST; //颜色纹理的缩放和平移
sampler2D _BumpMap; //法线纹理
float4 _BumpMap_ST; //法线纹理的缩放和平移
float _BumpScale; //凹凸程度
sampler2D _RampTex; //渐变纹理
float4 _RampTex_ST; //渐变纹理的缩放和平移(基本不会用)
float4 _SpecularColor; //高光颜色
fixed _SpecularNum; //光泽度
struct v2f
{
float4 pos:SV_POSITION;
//float2 uvTex:TEXCOORD0;
//float2 uvBump:TEXCOORD1;
//我们可以单独的声明两个float2的成员用于记录 颜色和法线纹理的uv坐标
//也可以直接声明一个float4的成员 xy用于记录颜色纹理的uv,zw用于记录法线纹理的uv
float4 uv:TEXCOORD0;
//光的方向 相对于切线空间下的
float3 lightDir:TEXCOORD1;
//视角的方向 相对于切线空间下的
float3 viewDir:TEXCOORD2;
};
v2f vert(appdata_full appdata_full)
{
v2f v2f;
//把模型空间下的顶点转到裁剪空间下
v2f.pos = UnityObjectToClipPos(appdata_full.vertex);
//计算纹理的缩放偏移
v2f.uv.xy = appdata_full.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
v2f.uv.zw = appdata_full.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
//在顶点着色器当中 得到 模型空间到切线空间的 转换矩阵
//切线、副切线、法线
//计算副切线 计算叉乘结果后 垂直与切线和法线的向量有两条 通过乘以 切线当中的w,就可以确定是哪一条
float3 binormal = cross(normalize(appdata_full.tangent), normalize(appdata_full.normal)) * appdata_full.
tangent.w;
//转换矩阵
float3x3 rotation = float3x3(appdata_full.tangent.xyz,
binormal,
appdata_full.normal);
//模型空间下的光的方向
//data.lightDir = ObjSpaceLightDir(v.vertex);
//乘以模型空间到切线空间的转换矩阵 就可以得到切线空间下的 光的方向了
v2f.lightDir = mul(rotation, ObjSpaceLightDir(appdata_full.vertex));
//模型空间下的视角的方向
//data.viewDir = ObjSpaceViewDir(v.vertex);
v2f.viewDir = mul(rotation, ObjSpaceViewDir(appdata_full.vertex));
return v2f;
}
fixed4 frag(v2f v2f) : SV_Target
{
//通过纹理采样函数 取出法线纹理贴图当中的数据
float4 packedNormal = tex2D(_BumpMap, v2f.uv.zw);
//将我们取出来的法线数据 进行逆运算并且可能会进行解压缩的运算,最终得到切线空间下的法线数据
float3 tangentNormal = UnpackNormal(packedNormal);
//乘以凹凸程度的系数
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
//接下来就来处理 带颜色纹理的 布林方光照模型计算
//颜色纹理和漫反射颜色的 叠加
fixed3 albedo = tex2D(_MainTex, v2f.uv.xy) * _MainColor.rgb;
//修改为 渐变纹理相关的计算方式
fixed halfLambertNum = dot(normalize(tangentNormal), normalize(v2f.lightDir)) * 0.5 + 0.5;
//渐变纹理 漫反射计算方式
fixed3 diffuseColor = _LightColor0.rgb * albedo.rgb *
tex2D( _RampTex, fixed2(halfLambertNum, halfLambertNum)).rgb;
//半角向量
float3 halfA = normalize(normalize(v2f.viewDir) + normalize(v2f.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);
}
ENDCG
}
}
}
之前的渐变纹理的综合实践是不能投射阴影的,需要修改
将复制过来的代码的 Pass 改为只渲染背面:
Cull Back // 绘制正面以避免背面渲染
// 光照模型与透明混合设置可以在此扩展(目前未启用)
加入阴影接收相关内容。首先,通过编译指令 #pragma multi_compile_fwdbase
开启多重编译支持,并引入内置文件 AutoLight.cginc
来处理光照计算。接着,在 v2f
结构体中加入 float3 worldPos
和 SHADOW_COORDS(4)
,以便储存世界空间顶点坐标和阴影相关数据。在顶点着色器中,需要计算世界空间顶点坐标,并使用 TRANSFER_SHADOW(o)
完成阴影传递的相关操作。同时,在光照衰减计算中使用 UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos)
来获取光照衰减值,并将其与半兰伯特模型的计算结果(halfLambertNum
)相乘,以调整最终光照效果。最后,为了处理未定义的情况下,添加回退选项 FallBack "Diffuse"
。
SubShader
{
Pass
{
Tags
{
"LightMode"="ForwardBase"
}
Cull Back // 剔除背面,只渲染正面
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase // 编译多个光照模式
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc" // 自动光照相关函数
float4 _MainColor; //漫反射颜色
sampler2D _MainTex; //颜色纹理
float4 _MainTex_ST; //颜色纹理的缩放和平移
sampler2D _BumpMap; //法线纹理
float4 _BumpMap_ST; //法线纹理的缩放和平移
float _BumpScale; //凹凸程度
sampler2D _RampTex; //渐变纹理
float4 _RampTex_ST; //渐变纹理的缩放和平移(基本不会用)
float4 _SpecularColor; //高光颜色
fixed _SpecularNum; //光泽度
struct v2f
{
float4 pos:SV_POSITION;
//float2 uvTex:TEXCOORD0;
//float2 uvBump:TEXCOORD1;
//我们可以单独的声明两个float2的成员用于记录 颜色和法线纹理的uv坐标
//也可以直接声明一个float4的成员 xy用于记录颜色纹理的uv,zw用于记录法线纹理的uv
float4 uv:TEXCOORD0;
//光的方向 相对于切线空间下的
float3 lightDir:TEXCOORD1;
//视角的方向 相对于切线空间下的
float3 viewDir:TEXCOORD2;
float3 worldPos : TEXCOORD3; // 世界空间的顶点位置
SHADOW_COORDS(4) // 阴影相关数据
};
v2f vert(appdata_full appdata_full)
{
v2f v2f;
//把模型空间下的顶点转到裁剪空间下
v2f.pos = UnityObjectToClipPos(appdata_full.vertex);
//计算纹理的缩放偏移
v2f.uv.xy = appdata_full.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
v2f.uv.zw = appdata_full.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
//在顶点着色器当中 得到 模型空间到切线空间的 转换矩阵
//切线、副切线、法线
//计算副切线 计算叉乘结果后 垂直与切线和法线的向量有两条 通过乘以 切线当中的w,就可以确定是哪一条
float3 binormal = cross(normalize(appdata_full.tangent), normalize(appdata_full.normal)) * appdata_full.
tangent.w;
//转换矩阵
float3x3 rotation = float3x3(appdata_full.tangent.xyz,
binormal,
appdata_full.normal);
//模型空间下的光的方向
//data.lightDir = ObjSpaceLightDir(v.vertex);
//乘以模型空间到切线空间的转换矩阵 就可以得到切线空间下的 光的方向了
v2f.lightDir = mul(rotation, ObjSpaceLightDir(appdata_full.vertex));
//模型空间下的视角的方向
//data.viewDir = ObjSpaceViewDir(v.vertex);
v2f.viewDir = mul(rotation, ObjSpaceViewDir(appdata_full.vertex));
// 世界空间的顶点位置
v2f.worldPos = mul(unity_ObjectToWorld, appdata_full.vertex).xyz;
// 传递阴影数据
TRANSFER_SHADOW(v2f);
return v2f;
}
fixed4 frag(v2f v2f) : SV_Target
{
//通过纹理采样函数 取出法线纹理贴图当中的数据
float4 packedNormal = tex2D(_BumpMap, v2f.uv.zw);
//将我们取出来的法线数据 进行逆运算并且可能会进行解压缩的运算,最终得到切线空间下的法线数据
float3 tangentNormal = UnpackNormal(packedNormal);
//乘以凹凸程度的系数
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
//接下来就来处理 带颜色纹理的 布林方光照模型计算
//颜色纹理和漫反射颜色的 叠加
fixed3 albedo = tex2D(_MainTex, v2f.uv.xy) * _MainColor.rgb;
//修改为 渐变纹理相关的计算方式
fixed halfLambertNum = dot(normalize(tangentNormal), normalize(v2f.lightDir)) * 0.5 + 0.5;
// 计算光照衰减,考虑距离影响
UNITY_LIGHT_ATTENUATION(atten, v2f, v2f.worldPos);
// 将光照衰减与半 Lambertian 反射值相乘
halfLambertNum *= atten;
//渐变纹理 漫反射计算方式
fixed3 diffuseColor = _LightColor0.rgb * albedo.rgb *
tex2D(_RampTex, fixed2(halfLambertNum, halfLambertNum)).rgb;
//半角向量
float3 halfA = normalize(normalize(v2f.viewDir) + normalize(v2f.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);
}
ENDCG
}
}
Fallback "Diffuse" // 当Shader不支持时,使用Diffuse作为降级方案
修改后,和原来的渐变纹理相比,可以接受阴影和投射阴影了
处理渲染外轮廓(描边),加入边缘线颜色和宽度属性 _OutLineColor _OutLineWidth,再加入背面渲染的Pass,用于处理轮廓描边
Properties
{
// 主颜色(用于调节整体颜色)
_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(8,256)) = 18
// 描边颜色(控制物体边缘描边的颜色)
_OutLineColor("OutLineColor", Color) = (0,0,0,1)
// 描边宽度(用于调节边缘线的粗细)
_OutLineWidth("OutLineWidth", Range(0,1)) = 0.04
}
// 第一个Pass:用于渲染描边效果
Pass
{
Cull Front // 剔除正面,只渲染背面(为描边效果做准备)
CGPROGRAM
#pragma vertex vert // 顶点着色器
#pragma fragment frag // 片段着色器
#include "UnityCG.cginc" // 引入Unity的通用着色器函数库
struct v2f
{
float4 vertex : SV_POSITION; // 裁剪空间坐标
};
// 描边颜色
fixed4 _OutLineColor;
// 描边宽度
float _OutLineWidth;
// 顶点着色器:处理模型的顶点位置,用于实现描边
v2f vert(appdata_base appdata_base)
{
v2f v2f;
// 将顶点沿法线方向偏移,扩大模型尺寸
appdata_base.vertex.xyz += normalize(appdata_base.normal) * _OutLineWidth;
// 将偏移后的顶点从模型空间转换到裁剪空间
v2f.vertex = UnityObjectToClipPos(appdata_base.vertex);
return v2f;
}
// 片段着色器:为描边赋予颜色
fixed4 frag(v2f v2f) : SV_Target
{
// 返回预设的描边颜色
return _OutLineColor;
}
ENDCG
}
此 Pass 渲染完成后,即可看到描边效果,并可通过调整 _OutLineWidth
修改描边粗细。
实现高光变硬效果
主要步骤
主要修改高光反射颜色计算相关内容,步骤包括:
- 计算出半角向量
- 用法线和半角向量进行点乘
- 将点乘的结果与一个阈值进行比较:如果小于阈值则取 0,大于阈值则取 1
- 利用这个比较结果与高光颜色进行叠加
- 最后将此结果参与到布林方颜色公式的计算中
修改 _SpecularNum 范围
将 _SpecularNum 的范围修改为 0-1,用作高光反射的阈值。属性声明如下:
Properties
{
// 主颜色(用于调节整体颜色)
_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,1)
// 描边宽度(用于调节边缘线的粗细)
_OutLineWidth("OutLineWidth", Range(0,1)) = 0.04
}
计算高光反射
注释高光反射相关的计算过程如下,主要内容包括:
- 计算半角向量
- 用法线和半角向量进行点乘
- 将点乘结果与预设的光泽度 (_SpecularNum) 阈值进行比较:如果点乘值小于阈值则取 0,大于或等于阈值则取 1
// 计算光泽反射(高光),根据法线与半角向量的点积判断反射强度
// 点积计算:通过计算法线 (tangentNormal) 与视角和光照方向的半角向量 (halfA) 的点积,判断它们的夹角关系。
// 点积值范围为 -1 到 1,其中 1 表示完全一致的方向(完美镜面反射),-1 表示完全相反,0 表示垂直。
// 使用 normalize 确保法线和半角向量的方向是标准化的。
// step 函数:根据点积结果与预设光泽度 (_SpecularNum) 进行比较,如果点积值大于或等于光泽度则返回 1,表示有高光反射;
// 如果点积值小于光泽度则返回 0,表示没有高光反射。
// 最终,spec 的值为 0 或 1:
// - spec = 1:表示存在高光反射,可以计算高光颜色;
// - spec = 0:表示无高光反射,反射强度为 0。
fixed spec = step(_SpecularNum, dot(normalize(tangentNormal), normalize(halfA)));
// 根据光泽度和高光颜色计算最终的高光反射颜色
fixed3 specularColor = _SpecularColor.rgb * spec;
查看效果,关闭材质的发现后,可以看到白光非常硬,这正是卡通效果的体现。
12.2 知识点代码
Lesson12_ToonShader.shader
Shader "Unlit/Lesson12_ToonShader"
{
Properties
{
// 主颜色(用于调节整体颜色)
_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,1)
// 描边宽度(用于调节边缘线的粗细)
_OutLineWidth("OutLineWidth", Range(0,1)) = 0.04
}
SubShader
{
// 第一个Pass:用于渲染描边效果
Pass
{
Name "OUTLINE"
Cull Front // 剔除正面,只渲染背面(为描边效果做准备)
CGPROGRAM
#pragma vertex vert // 顶点着色器
#pragma fragment frag // 片段着色器
#include "UnityCG.cginc" // 引入Unity的通用着色器函数库
struct v2f
{
float4 vertex : SV_POSITION; // 裁剪空间坐标
};
// 描边颜色
fixed4 _OutLineColor;
// 描边宽度
float _OutLineWidth;
// 顶点着色器:处理模型的顶点位置,用于实现描边
v2f vert(appdata_base appdata_base)
{
v2f v2f;
// 将顶点沿法线方向偏移,扩大模型尺寸
appdata_base.vertex.xyz += normalize(appdata_base.normal) * _OutLineWidth;
// 将偏移后的顶点从模型空间转换到裁剪空间
v2f.vertex = UnityObjectToClipPos(appdata_base.vertex);
return v2f;
}
// 片段着色器:为描边赋予颜色
fixed4 frag(v2f v2f) : SV_Target
{
// 返回预设的描边颜色
return _OutLineColor;
}
ENDCG
}
// 第二个Pass:用于渲染主物体表面
Pass
{
Tags
{
"LightMode"="ForwardBase"
}
Cull Back // 剔除背面,只渲染正面
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase // 编译多个光照模式
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc" // 自动光照相关函数
float4 _MainColor; //漫反射颜色
sampler2D _MainTex; //颜色纹理
float4 _MainTex_ST; //颜色纹理的缩放和平移
sampler2D _BumpMap; //法线纹理
float4 _BumpMap_ST; //法线纹理的缩放和平移
float _BumpScale; //凹凸程度
sampler2D _RampTex; //渐变纹理
float4 _RampTex_ST; //渐变纹理的缩放和平移(基本不会用)
float4 _SpecularColor; //高光颜色
fixed _SpecularNum; //光泽度
struct v2f
{
float4 pos:SV_POSITION;
//float2 uvTex:TEXCOORD0;
//float2 uvBump:TEXCOORD1;
//我们可以单独的声明两个float2的成员用于记录 颜色和法线纹理的uv坐标
//也可以直接声明一个float4的成员 xy用于记录颜色纹理的uv,zw用于记录法线纹理的uv
float4 uv:TEXCOORD0;
//光的方向 相对于切线空间下的
float3 lightDir:TEXCOORD1;
//视角的方向 相对于切线空间下的
float3 viewDir:TEXCOORD2;
float3 worldPos : TEXCOORD3; // 世界空间的顶点位置
SHADOW_COORDS(4) // 阴影相关数据
};
v2f vert(appdata_full appdata_full)
{
v2f v2f;
//把模型空间下的顶点转到裁剪空间下
v2f.pos = UnityObjectToClipPos(appdata_full.vertex);
//计算纹理的缩放偏移
v2f.uv.xy = appdata_full.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
v2f.uv.zw = appdata_full.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
//在顶点着色器当中 得到 模型空间到切线空间的 转换矩阵
//切线、副切线、法线
//计算副切线 计算叉乘结果后 垂直与切线和法线的向量有两条 通过乘以 切线当中的w,就可以确定是哪一条
float3 binormal = cross(normalize(appdata_full.tangent), normalize(appdata_full.normal)) * appdata_full.
tangent.w;
//转换矩阵
float3x3 rotation = float3x3(appdata_full.tangent.xyz,
binormal,
appdata_full.normal);
//模型空间下的光的方向
//data.lightDir = ObjSpaceLightDir(v.vertex);
//乘以模型空间到切线空间的转换矩阵 就可以得到切线空间下的 光的方向了
v2f.lightDir = mul(rotation, ObjSpaceLightDir(appdata_full.vertex));
//模型空间下的视角的方向
//data.viewDir = ObjSpaceViewDir(v.vertex);
v2f.viewDir = mul(rotation, ObjSpaceViewDir(appdata_full.vertex));
// 世界空间的顶点位置
v2f.worldPos = mul(unity_ObjectToWorld, appdata_full.vertex).xyz;
// 传递阴影数据
TRANSFER_SHADOW(v2f);
return v2f;
}
fixed4 frag(v2f v2f) : SV_Target
{
//通过纹理采样函数 取出法线纹理贴图当中的数据
float4 packedNormal = tex2D(_BumpMap, v2f.uv.zw);
//将我们取出来的法线数据 进行逆运算并且可能会进行解压缩的运算,最终得到切线空间下的法线数据
float3 tangentNormal = UnpackNormal(packedNormal);
//乘以凹凸程度的系数
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
//接下来就来处理 带颜色纹理的 布林方光照模型计算
//颜色纹理和漫反射颜色的 叠加
fixed3 albedo = tex2D(_MainTex, v2f.uv.xy) * _MainColor.rgb;
//修改为 渐变纹理相关的计算方式
fixed halfLambertNum = dot(normalize(tangentNormal), normalize(v2f.lightDir)) * 0.5 + 0.5;
// 计算光照衰减,考虑距离影响
UNITY_LIGHT_ATTENUATION(atten, v2f, v2f.worldPos);
// 将光照衰减与半 Lambertian 反射值相乘
halfLambertNum *= atten;
//渐变纹理 漫反射计算方式
fixed3 diffuseColor = _LightColor0.rgb * albedo.rgb *
tex2D(_RampTex, fixed2(halfLambertNum, halfLambertNum)).rgb;
//半角向量
float3 halfA = normalize(normalize(v2f.viewDir) + normalize(v2f.lightDir));
// //高光反射
// fixed3 specularColor = _LightColor0.rgb * _SpecularColor.rgb * pow(
// max(0, dot(tangentNormal, halfA)), _SpecularNum);
// 计算光泽反射(高光),根据法线与半角向量的点积判断反射强度
// 点积计算:通过计算法线(tangentNormal)与视角和光照方向的半角向量(halfA)的点积,判断它们的夹角关系。
// 点积值范围:-1 到 1,其中1表示完全一致的方向(完美镜面反射),-1表示完全相反的方向,0表示垂直关系。
// 使用normalize确保法线和半角向量的方向是标准化的。
// step函数:根据计算得到的点积结果和预设的光泽度(_SpecularNum)值进行比较,如果点积值大于或等于光泽度值(_SpecularNum),则返回1,表示有高光反射;如果点积值小于光泽度值,则返回0,表示没有高光反射。
// 最终,spec会是0或1:
// - spec = 1:表示反射强度为高光反射,可以计算高光颜色。
// - spec = 0:表示没有高光反射,反射强度为0。
fixed spec = step(_SpecularNum, dot(normalize(tangentNormal), normalize(halfA)));
// 根据光泽度和高光颜色计算最终的高光反射颜色
fixed3 specularColor = _SpecularColor.rgb * spec;
//布林方
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + diffuseColor + specularColor;
return fixed4(color.rgb, 1);
}
ENDCG
}
}
Fallback "Diffuse" // 当Shader不支持时,使用Diffuse作为降级方案
}
Lesson12_卡通风格渲染_具体实现.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Lesson12_卡通风格渲染_具体实现 : MonoBehaviour
{
void Start()
{
#region 知识回顾
//卡通风格渲染 基本原理
//让光的过渡效果变硬并且实现轮廓描边!
//关键点:
//1.如何让光的过渡效果变硬
// 利用渐变纹理处理漫反射,利用自定义简化公式计算高光反射
//2.如何实现轮廓描边
// 用两个Pass,一个渲染背面实现轮廓描边,一个正常渲染正面
// 不需要关闭深度写入,模型正面的内容能够通过深度测试
#endregion
#region 知识点一 实现漫反射变硬和外轮廓效果
//1.新建Shader 将渐变纹理的综合实践相关代码直接复制过来
//2.将复制过来的代码的Pass 改为只渲染背面
//3.加入阴影接收相关内容
// 2-1.编译指令 #pragma multi_compile_fwdbase
// 2-2.内置文件 #include "AutoLight.cginc"
// 2-3.v2f结构体中加入 float3 worldPos 和 SHADOW_COORDS(4)
// 2-4.顶点着色器计算中加入
// 世界空间顶点坐标计算
// TRANSFER_SHADOW(o);
// 2-5.光照衰减相关计算
// UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
// 用来和halfLambertNum进行乘法运算
// 2-6.FallBack "Diffuse"
//4.处理渲染外轮廓(描边)
// 4-1.加入边缘线颜色和宽度属性 _OutLineColor _OutLineWidth
// 4-2.加入背面渲染的Pass,用于处理轮廓描边
#endregion
#region 知识点二 实现高光变硬效果
//主要修改高光反射颜色计算相关内容
//1.计算出半角向量
//2.用法线和半角向量进行点乘
//3.用点乘的结果和一个阈值进行比较 如果小于阈值 取0 大于阈值 取1
//4.利用这个结果和高光颜色进行叠加
//5.最后参与到布林方颜色公式计算中
#endregion
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com