14.素描风格渲染-具体实现
14.1 知识点
知识回顾 素描风格渲染基本原理
基本原理:
- 用漫反射系数决定采样权重,在多张具有不同密度和方向的素描纹理中进行采样,并将采样结果叠加得到最终效果
关键点:
- 多张具有不同密度和方向的素描纹理
- 漫反射系数决定采样权重
- 采样结果进行叠加
纹理资源导入、相关属性添加
主要步骤
- 下载素描纹理资源
- 新建 Shader —— Lesson14_Sketch,删除无用代码
- 添加属性:
- 颜色属性 Color —— 用于颜色叠加
- 平铺系数 TileFactor —— 用于平铺纹理,让采样细节更多
- 素描纹理 Sketch1~6 —— 用于采样模拟素描表现效果
- 进行属性映射
导入素描纹理资源
新建Shader,Lesson14_Sketch,删除无用代码。添加属性。包括颜色属性Color用于颜色叠加,平铺系数TileFactor用于平铺纹理,让采样细节更多,素描纹理Sketch1~6用于采样模拟素描表现效果,并进行属性映射
Shader "Unlit/Lesson14_Sketch"
{
Properties
{
// 整体颜色叠加,用于调整整个效果的颜色
_Color("Color", Color) = (1,1,1,1)
// 平铺纹理的系数,控制纹理的缩放,数值越大,纹理细节越多
_TileFactor("TileFactor", Float) = 1
// 6张素描纹理贴图,用于根据不同的光照强度从不同的纹理中采样
_Sketch0("Sketch0", 2D) = ""{}
_Sketch1("Sketch1", 2D) = ""{}
_Sketch2("Sketch2", 2D) = ""{}
_Sketch3("Sketch3", 2D) = ""{}
_Sketch4("Sketch4", 2D) = ""{}
_Sketch5("Sketch5", 2D) = ""{}
}
SubShader
{
Tags
{
"RenderType"="Opaque" // 渲染类型:不透明
}
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
// 声明Shader中使用的参数
fixed4 _Color; // 整体颜色
fixed _TileFactor; // 纹理平铺系数
sampler2D _Sketch0, _Sketch1, _Sketch2, _Sketch3, _Sketch4, _Sketch5; // 6张素描纹理
// 定义顶点着色器的数据结构
struct v2f
{
float4 vertex : SV_POSITION; // 顶点位置
float2 uv : TEXCOORD0; // 纹理坐标
};
// 顶点着色器函数
v2f vert(appdata_base appdata_base)
{
}
// 片段着色器函数
fixed4 frag(v2f v2f) : SV_Target
{
}
ENDCG
}
}
}
权重计算(顶点着色器函数逻辑)
主要步骤
- 声明 v2f 结构体,包含顶点位置、uv,以及两个 fixed3 用来记录素描纹理权重
- 实现顶点着色器函数:
- 顶点坐标转换
- uv 坐标平铺缩放(乘以平铺系数)
- 世界空间光照方向与法线转换
- 计算兰伯特漫反射光照系数
- 将光照系数从 0
1 扩展到 07 - 根据扩展后的系数决定各素描纹理的采样权重
v2f结构体声明,包括顶点位置、uv、用两个fixed3记录素描纹理权重
// 定义顶点着色器的数据结构
struct v2f
{
float4 vertex : SV_POSITION; // 顶点位置
float2 uv : TEXCOORD0; // 纹理坐标
fixed3 sketchWeights0 : TEXCOORD1; // 第1、2、3张素描纹理的权重
fixed3 sketchWeights1 : TEXCOORD2; // 第4、5、6张素描纹理的权重
float3 worldPos : TEXCOORD3; // 世界空间坐标
SHADOW_COORDS(4) // 用于阴影坐标
};
进行顶点着色器函数的实现。首先进行顶点坐标的转换,然后对UV坐标进行平铺和缩放,以适应纹理的平铺系数。接着,转换世界空间中的光照方向和法线,计算兰伯特漫反射光照系数,并将光照系数从0到1的范围扩展到0到7之间,最后根据计算得到的光照系数,决定素描纹理的权重。
// 顶点着色器函数
v2f vert(appdata_base appdata_base)
{
v2f v2f;
// 将模型空间的顶点坐标转换为裁剪空间坐标
v2f.vertex = UnityObjectToClipPos(appdata_base.vertex);
// 纹理坐标平铺缩放,数值越大纹理细节越多,越小越粗糙
v2f.uv = appdata_base.texcoord.xy * _TileFactor;
// 获取世界空间的光照方向
fixed3 worldLightDir = normalize(WorldSpaceLightDir(appdata_base.vertex));
// 将法线从模型空间转换到世界空间
fixed3 worldNormal = UnityObjectToWorldNormal(appdata_base.normal);
// 计算兰伯特漫反射光照强度(0~1)
fixed diff = max(0, dot(worldLightDir, worldNormal));
// 将光照强度扩展到0~7的范围
diff = diff * 7.0;
// 初始化素描纹理的权重,默认为0
v2f.sketchWeights0 = fixed3(0, 0, 0);
v2f.sketchWeights1 = fixed3(0, 0, 0);
// 根据光照强度分配不同的素描纹理权重
if (diff > 6.0)
{
// 最亮部分,不改变任何纹理权重
}
else if (diff > 5.0)
{
// 从第1张图采样,设置权重
v2f.sketchWeights0.x = diff - 5.0;
}
else if (diff > 4.0)
{
// 从第2张图采样,设置权重
v2f.sketchWeights0.y = diff - 4.0;
}
else if (diff > 3.0)
{
// 从第3张图采样,设置权重
v2f.sketchWeights0.z = diff - 3.0;
}
else if (diff > 2.0)
{
// 从第4张图采样,设置权重
v2f.sketchWeights0.x = diff - 2.0;
}
else if (diff > 1.0)
{
// 从第5张图采样,设置权重
v2f.sketchWeights1.y = diff - 1.0;
}
else
{
// 从第6张图采样,设置权重
v2f.sketchWeights1.z = diff;
}
return v2f;
}
颜色采样(片元着色器函数逻辑)
主要步骤
- 在片段着色器中,对 1~6 张纹理分别进行采样,并乘以各自的权重,得到各纹理采样颜色
- 根据各纹理权重计算出白色高光部分的颜色
- 将所有纹理采样颜色与白色部分相加,得到最终叠加的颜色
对 1~6 张纹理进行采样 并乘以权重 得到各纹理采样颜色并计算出白色部分,汇总颜色后返回
// 片段着色器函数
fixed4 frag(v2f v2f) : SV_Target
{
// 根据顶点着色器计算的纹理权重,从不同的素描纹理中采样
fixed4 sketchColor0 = tex2D(_Sketch0, v2f.uv) * v2f.sketchWeights0.x;
fixed4 sketchColor1 = tex2D(_Sketch1, v2f.uv) * v2f.sketchWeights0.y;
fixed4 sketchColor2 = tex2D(_Sketch2, v2f.uv) * v2f.sketchWeights0.z;
fixed4 sketchColor3 = tex2D(_Sketch3, v2f.uv) * v2f.sketchWeights1.x;
fixed4 sketchColor4 = tex2D(_Sketch4, v2f.uv) * v2f.sketchWeights1.y;
fixed4 sketchColor5 = tex2D(_Sketch5, v2f.uv) * v2f.sketchWeights1.z;
// 计算最亮部分的白色叠加
fixed4 whiteColor = fixed4(1, 1, 1, 1) * (1 - v2f.sketchWeights0.x - v2f.sketchWeights0.y - v2f.sketchWeights0.z - v2f.sketchWeights1.x - v2f.sketchWeights1.y - v2f.sketchWeights1.z);
// 汇总所有的素描颜色
fixed4 sketchColor = sketchColor0 + sketchColor1 + sketchColor2 + sketchColor3 + sketchColor4 + sketchColor5 + whiteColor;
// 最终返回计算后的颜色值
return fixed4(sketchColor.rgb * _Color.rgb, 1);
}
当前计算逻辑的问题
目前的计算逻辑会导致白色占了大部分——例如当 diff 较高时,大部分采样结果都是白色,显得有些奇怪:
修改权重算法以减少白色部分
采用相邻两张图的采样替代大部分白色的情况,修改顶点着色器中权重分配的算法:
// 根据光照强度分配不同的素描纹理权重
if (diff > 6.0)
{
// 最亮部分,不改变任何纹理权重
}
else if (diff > 5.0)
{
// 从第1张图采样,设置权重
v2f.sketchWeights0.x = diff - 5.0;
}
else if (diff > 4.0)
{
// 从第1、2张图采样,设置权重
v2f.sketchWeights0.x = diff - 4.0;
v2f.sketchWeights0.y = 1 - v2f.sketchWeights0.x;
}
else if (diff > 3.0)
{
// 从第2、3张图采样,设置权重
v2f.sketchWeights0.y = diff - 3.0;
v2f.sketchWeights0.z = 1 - v2f.sketchWeights0.y;
}
else if (diff > 2.0)
{
// 从第3、4张图采样,设置权重
v2f.sketchWeights0.z = diff - 2.0;
v2f.sketchWeights1.x = 1 - v2f.sketchWeights0.z;
}
else if (diff > 1.0)
{
// 从第4、5张图采样,设置权重
v2f.sketchWeights1.x = diff - 1.0;
v2f.sketchWeights1.y = 1 - v2f.sketchWeights1.x;
}
else
{
// 从第5、6张图采样,设置权重
v2f.sketchWeights1.y = diff;
v2f.sketchWeights1.z = 1 - diff;
}
可以看到没有那么多白色了
外轮廓、阴影相关添加
主要步骤
外轮廓
复用 Lesson12_ToonShader 中的外轮廓 Pass 代码,并为该 Pass 添加名称阴影相关添加
- 添加 SHADOW_COORDS、TRANSFER_SHADOW、UNITY_LIGHT_ATTENUATION
- 添加回退(Fallback)到默认的 Diffuse 着色器
给Lesson12_ToonShader中的外轮廓Pass添加名字
// 第一个Pass:用于渲染描边效果
Pass
{
Name "OUTLINE"
//...
}
Lesson14_Sketch添加边缘线参数和UsePass ,使用外轮廓Pass。不需要属性映射,因为是在外部Pass内部处理的
Properties
{
// 整体颜色叠加,用于调整整个效果的颜色
_Color("Color", Color) = (1,1,1,1)
// 平铺纹理的系数,控制纹理的缩放,数值越大,纹理细节越多
_TileFactor("TileFactor", Float) = 1
// 6张素描纹理贴图,用于根据不同的光照强度从不同的纹理中采样
_Sketch0("Sketch0", 2D) = ""{}
_Sketch1("Sketch1", 2D) = ""{}
_Sketch2("Sketch2", 2D) = ""{}
_Sketch3("Sketch3", 2D) = ""{}
_Sketch4("Sketch4", 2D) = ""{}
_Sketch5("Sketch5", 2D) = ""{}
// 边缘线相关参数
_OutLineColor("OutLineColor", Color) = (0,0,0,1) // 边缘线颜色
_OutLineWidth("OutLineWidth", Range(0,1)) = 0.04 // 边缘线宽度
}
SubShader
{
Tags
{
"RenderType"="Opaque" // 渲染类型:不透明
}
// 使用外部Pass,用于实现轮廓线
UsePass "Unlit/Lesson12_ToonShader/OUTLINE"
//...
}
添加阴影相关处理
SubShader
{
Tags
{
"RenderType"="Opaque" // 渲染类型:不透明
}
// 使用外部Pass,用于实现轮廓线
UsePass "Unlit/Lesson12_ToonShader/OUTLINE"
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
// 引入Unity内置的常用CG库
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
// 声明Shader中使用的参数
fixed4 _Color; // 整体颜色
fixed _TileFactor; // 纹理平铺系数
sampler2D _Sketch0, _Sketch1, _Sketch2, _Sketch3, _Sketch4, _Sketch5; // 6张素描纹理
// 定义顶点着色器的数据结构
struct v2f
{
float4 vertex : SV_POSITION; // 顶点位置
float2 uv : TEXCOORD0; // 纹理坐标
fixed3 sketchWeights0 : TEXCOORD1; // 第1、2、3张素描纹理的权重
fixed3 sketchWeights1 : TEXCOORD2; // 第4、5、6张素描纹理的权重
float3 worldPos : TEXCOORD3; // 世界空间坐标
SHADOW_COORDS(4) // 用于阴影坐标
};
// 顶点着色器函数
v2f vert(appdata_base appdata_base)
{
v2f v2f;
// 将模型空间的顶点坐标转换为裁剪空间坐标
v2f.vertex = UnityObjectToClipPos(appdata_base.vertex);
// 纹理坐标平铺缩放,数值越大纹理细节越多,越小越粗糙
v2f.uv = appdata_base.texcoord.xy * _TileFactor;
// 获取世界空间的光照方向
fixed3 worldLightDir = normalize(WorldSpaceLightDir(appdata_base.vertex));
// 将法线从模型空间转换到世界空间
fixed3 worldNormal = UnityObjectToWorldNormal(appdata_base.normal);
// 计算兰伯特漫反射光照强度(0~1)
fixed diff = max(0, dot(worldLightDir, worldNormal));
// 将光照强度扩展到0~7的范围
diff = diff * 7.0;
// 初始化素描纹理的权重,默认为0
v2f.sketchWeights0 = fixed3(0, 0, 0);
v2f.sketchWeights1 = fixed3(0, 0, 0);
// 根据光照强度分配不同的素描纹理权重
if (diff > 6.0)
{
// 最亮部分,不改变任何纹理权重
}
else if (diff > 5.0)
{
// 从第1张图采样,设置权重
v2f.sketchWeights0.x = diff - 5.0;
}
else if (diff > 4.0)
{
// 从第1、2张图采样,设置权重
v2f.sketchWeights0.x = diff - 4.0;
v2f.sketchWeights0.y = 1 - v2f.sketchWeights0.x;
}
else if (diff > 3.0)
{
// 从第2、3张图采样,设置权重
v2f.sketchWeights0.y = diff - 3.0;
v2f.sketchWeights0.z = 1 - v2f.sketchWeights0.y;
}
else if (diff > 2.0)
{
// 从第3、4张图采样,设置权重
v2f.sketchWeights0.z = diff - 2.0;
v2f.sketchWeights1.x = 1 - v2f.sketchWeights0.z;
}
else if (diff > 1.0)
{
// 从第4、5张图采样,设置权重
v2f.sketchWeights1.x = diff - 1.0;
v2f.sketchWeights1.y = 1 - v2f.sketchWeights1.x;
}
else
{
// 从第5、6张图采样,设置权重
v2f.sketchWeights1.y = diff;
v2f.sketchWeights1.z = 1 - diff;
}
// // 根据光照强度分配不同的素描纹理权重
// if (diff > 6.0)
// {
// // 最亮部分,不改变任何纹理权重
// }
// else if (diff > 5.0)
// {
// // 从第1张图采样,设置权重
// v2f.sketchWeights0.x = diff - 5.0;
// }
// else if (diff > 4.0)
// {
// // 从第2张图采样,设置权重
// v2f.sketchWeights0.y = diff - 4.0;
// }
// else if (diff > 3.0)
// {
// // 从第3张图采样,设置权重
// v2f.sketchWeights0.z = diff - 3.0;
// }
// else if (diff > 2.0)
// {
// // 从第4张图采样,设置权重
// v2f.sketchWeights0.x = diff - 2.0;
// }
// else if (diff > 1.0)
// {
// // 从第5张图采样,设置权重
// v2f.sketchWeights1.y = diff - 1.0;
// }
// else
// {
// // 从第6张图采样,设置权重
// v2f.sketchWeights1.z = diff;
// }
// 将顶点坐标转换为世界坐标
v2f.worldPos = mul(unity_ObjectToWorld, appdata_base.vertex).xyz;
TRANSFER_SHADOW(v2f); // 传递阴影坐标
return v2f;
}
// 片段着色器函数
fixed4 frag(v2f v2f) : SV_Target
{
// 根据顶点着色器计算的纹理权重,从不同的素描纹理中采样
fixed4 sketchColor0 = tex2D(_Sketch0, v2f.uv) * v2f.sketchWeights0.x;
fixed4 sketchColor1 = tex2D(_Sketch1, v2f.uv) * v2f.sketchWeights0.y;
fixed4 sketchColor2 = tex2D(_Sketch2, v2f.uv) * v2f.sketchWeights0.z;
fixed4 sketchColor3 = tex2D(_Sketch3, v2f.uv) * v2f.sketchWeights1.x;
fixed4 sketchColor4 = tex2D(_Sketch4, v2f.uv) * v2f.sketchWeights1.y;
fixed4 sketchColor5 = tex2D(_Sketch5, v2f.uv) * v2f.sketchWeights1.z;
// 计算最亮部分的白色叠加
fixed4 whiteColor = fixed4(1, 1, 1, 1) * (1 - v2f.sketchWeights0.x - v2f.sketchWeights0.y - v2f.
sketchWeights0.z - v2f.sketchWeights1.x - v2f.sketchWeights1.y - v2f.sketchWeights1.z);
// 汇总所有的素描颜色
fixed4 sketchColor = sketchColor0 + sketchColor1 + sketchColor2 + sketchColor3 + sketchColor4 +
sketchColor5 + whiteColor;
// 考虑光照衰减
UNITY_LIGHT_ATTENUATION(atten, v2f, v2f.worldPos);
// 最终返回计算后的颜色值
return fixed4(sketchColor.rgb * atten * _Color.rgb, 1);
// return fixed4(sketchColor.rgb * _Color.rgb, 1);
}
ENDCG
}
}
// 回退到默认的Diffuse着色器
Fallback "Diffuse"
最终效果可以看到有描边和阴影效果了。
14.2 知识点代码
Lesson14_Sketch.shader
Shader "Unlit/Lesson14_Sketch"
{
Properties
{
// 整体颜色叠加,用于调整整个效果的颜色
_Color("Color", Color) = (1,1,1,1)
// 平铺纹理的系数,控制纹理的缩放,数值越大,纹理细节越多
_TileFactor("TileFactor", Float) = 1
// 6张素描纹理贴图,用于根据不同的光照强度从不同的纹理中采样
_Sketch0("Sketch0", 2D) = ""{}
_Sketch1("Sketch1", 2D) = ""{}
_Sketch2("Sketch2", 2D) = ""{}
_Sketch3("Sketch3", 2D) = ""{}
_Sketch4("Sketch4", 2D) = ""{}
_Sketch5("Sketch5", 2D) = ""{}
// 边缘线相关参数
_OutLineColor("OutLineColor", Color) = (0,0,0,1) // 边缘线颜色
_OutLineWidth("OutLineWidth", Range(0,1)) = 0.04 // 边缘线宽度
}
SubShader
{
Tags
{
"RenderType"="Opaque" // 渲染类型:不透明
}
// 使用外部Pass,用于实现轮廓线
UsePass "Unlit/Lesson12_ToonShader/OUTLINE"
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
// 引入Unity内置的常用CG库
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
// 声明Shader中使用的参数
fixed4 _Color; // 整体颜色
fixed _TileFactor; // 纹理平铺系数
sampler2D _Sketch0, _Sketch1, _Sketch2, _Sketch3, _Sketch4, _Sketch5; // 6张素描纹理
// 定义顶点着色器的数据结构
struct v2f
{
float4 vertex : SV_POSITION; // 顶点位置
float2 uv : TEXCOORD0; // 纹理坐标
fixed3 sketchWeights0 : TEXCOORD1; // 第1、2、3张素描纹理的权重
fixed3 sketchWeights1 : TEXCOORD2; // 第4、5、6张素描纹理的权重
float3 worldPos : TEXCOORD3; // 世界空间坐标
SHADOW_COORDS(4) // 用于阴影坐标
};
// 顶点着色器函数
v2f vert(appdata_base appdata_base)
{
v2f v2f;
// 将模型空间的顶点坐标转换为裁剪空间坐标
v2f.vertex = UnityObjectToClipPos(appdata_base.vertex);
// 纹理坐标平铺缩放,数值越大纹理细节越多,越小越粗糙
v2f.uv = appdata_base.texcoord.xy * _TileFactor;
// 获取世界空间的光照方向
fixed3 worldLightDir = normalize(WorldSpaceLightDir(appdata_base.vertex));
// 将法线从模型空间转换到世界空间
fixed3 worldNormal = UnityObjectToWorldNormal(appdata_base.normal);
// 计算兰伯特漫反射光照强度(0~1)
fixed diff = max(0, dot(worldLightDir, worldNormal));
// 将光照强度扩展到0~7的范围
diff = diff * 7.0;
// 初始化素描纹理的权重,默认为0
v2f.sketchWeights0 = fixed3(0, 0, 0);
v2f.sketchWeights1 = fixed3(0, 0, 0);
// 根据光照强度分配不同的素描纹理权重
if (diff > 6.0)
{
// 最亮部分,不改变任何纹理权重
}
else if (diff > 5.0)
{
// 从第1张图采样,设置权重
v2f.sketchWeights0.x = diff - 5.0;
}
else if (diff > 4.0)
{
// 从第1、2张图采样,设置权重
v2f.sketchWeights0.x = diff - 4.0;
v2f.sketchWeights0.y = 1 - v2f.sketchWeights0.x;
}
else if (diff > 3.0)
{
// 从第2、3张图采样,设置权重
v2f.sketchWeights0.y = diff - 3.0;
v2f.sketchWeights0.z = 1 - v2f.sketchWeights0.y;
}
else if (diff > 2.0)
{
// 从第3、4张图采样,设置权重
v2f.sketchWeights0.z = diff - 2.0;
v2f.sketchWeights1.x = 1 - v2f.sketchWeights0.z;
}
else if (diff > 1.0)
{
// 从第4、5张图采样,设置权重
v2f.sketchWeights1.x = diff - 1.0;
v2f.sketchWeights1.y = 1 - v2f.sketchWeights1.x;
}
else
{
// 从第5、6张图采样,设置权重
v2f.sketchWeights1.y = diff;
v2f.sketchWeights1.z = 1 - diff;
}
// // 根据光照强度分配不同的素描纹理权重
// if (diff > 6.0)
// {
// // 最亮部分,不改变任何纹理权重
// }
// else if (diff > 5.0)
// {
// // 从第1张图采样,设置权重
// v2f.sketchWeights0.x = diff - 5.0;
// }
// else if (diff > 4.0)
// {
// // 从第2张图采样,设置权重
// v2f.sketchWeights0.y = diff - 4.0;
// }
// else if (diff > 3.0)
// {
// // 从第3张图采样,设置权重
// v2f.sketchWeights0.z = diff - 3.0;
// }
// else if (diff > 2.0)
// {
// // 从第4张图采样,设置权重
// v2f.sketchWeights0.x = diff - 2.0;
// }
// else if (diff > 1.0)
// {
// // 从第5张图采样,设置权重
// v2f.sketchWeights1.y = diff - 1.0;
// }
// else
// {
// // 从第6张图采样,设置权重
// v2f.sketchWeights1.z = diff;
// }
// 将顶点坐标转换为世界坐标
v2f.worldPos = mul(unity_ObjectToWorld, appdata_base.vertex).xyz;
TRANSFER_SHADOW(v2f); // 传递阴影坐标
return v2f;
}
// 片段着色器函数
fixed4 frag(v2f v2f) : SV_Target
{
// 根据顶点着色器计算的纹理权重,从不同的素描纹理中采样
fixed4 sketchColor0 = tex2D(_Sketch0, v2f.uv) * v2f.sketchWeights0.x;
fixed4 sketchColor1 = tex2D(_Sketch1, v2f.uv) * v2f.sketchWeights0.y;
fixed4 sketchColor2 = tex2D(_Sketch2, v2f.uv) * v2f.sketchWeights0.z;
fixed4 sketchColor3 = tex2D(_Sketch3, v2f.uv) * v2f.sketchWeights1.x;
fixed4 sketchColor4 = tex2D(_Sketch4, v2f.uv) * v2f.sketchWeights1.y;
fixed4 sketchColor5 = tex2D(_Sketch5, v2f.uv) * v2f.sketchWeights1.z;
// 计算最亮部分的白色叠加
fixed4 whiteColor = fixed4(1, 1, 1, 1) * (1 - v2f.sketchWeights0.x - v2f.sketchWeights0.y - v2f.
sketchWeights0.z - v2f.sketchWeights1.x - v2f.sketchWeights1.y - v2f.sketchWeights1.z);
// 汇总所有的素描颜色
fixed4 sketchColor = sketchColor0 + sketchColor1 + sketchColor2 + sketchColor3 + sketchColor4 +
sketchColor5 + whiteColor;
// 考虑光照衰减
UNITY_LIGHT_ATTENUATION(atten, v2f, v2f.worldPos);
// 最终返回计算后的颜色值
return fixed4(sketchColor.rgb * atten * _Color.rgb, 1);
// return fixed4(sketchColor.rgb * _Color.rgb, 1);
}
ENDCG
}
}
// 回退到默认的Diffuse着色器
Fallback "Diffuse"
}
Lesson14_素描风格渲染_具体实现.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Lesson14_素描风格渲染_具体实现 : MonoBehaviour
{
void Start()
{
#region 知识回顾
//基本原理:
//用漫反射系数决定采样权重,在多张具有不同密度和方向的素描纹理中
//进行采样,并将采样结果进行叠加得到最终效果
//关键点:
//1 多张具有不同密度和方向的素描纹理
//2 漫反射系数决定采样权重
//3 采样结果进行叠加
#endregion
#region 知识点一 纹理资源导入、相关属性添加
//1.下载素描纹理资源
//2.新建Shader——Lesson14_Sketch,删除无用代码
//3.添加属性
// 颜色属性Color —— 用于颜色叠加
// 平铺系数TileFactor —— 用于平铺纹理,让采样细节更多
// 素描纹理Sketch1~6 —— 用于采样模拟素描表现效果
//4.进行属性映射
#endregion
#region 知识点二 权重计算(顶点着色器函数逻辑)
//1.v2f结构体声明
// 顶点位置、uv、用两个fixed3记录素描纹理权重
//2.顶点着色器函数实现
// 2-1 顶点坐标转换
// 2-2 uv坐标平铺缩放 让纹理*平铺系数
// 2-3 世界空间光照方向、世界空间法线转换
// 2-4 兰伯特漫反射光照系数计算
// 2-5 将光照系数 从 0~1 扩充到 0~7
// 2-6 根据系数决定素描纹理权重
#endregion
#region 知识点三 颜色采样(片元着色器函数逻辑)
//在片元着色器中实现
//1.对 1~6 张纹理进行采样 并乘以权重 得到各纹理采样颜色
//2.根据 1~6张纹理权重计算出白色高光部分颜色
//3.将1~6张纹理采样颜色 和 白色部分 相加得到最终叠加颜色
#endregion
#region 知识点四 外轮廓、阴影相关添加
//1.外轮廓
// 直接复用Lesson12_ToonShader中的外轮廓Pass代码 给Lesson12_ToonShader中的外轮廓Pass添加名字
//2.阴影相关添加
// 加入 SHADOW_COORDS、TRANSFER_SHADOW、UNITY_LIGHT_ATTENUATION
// 加入 FallBack "Diffuse"
#endregion
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com