4.模型描边效果-具体实现
4.1 知识点
知识回顾:模型描边效果基本原理
模型描边效果采用两个 Pass 来渲染对象:
- 一个 Pass 用于渲染沿法线方向放大的模型(实现描边效果),
- 另一个 Pass 用于渲染正常模型。
需要注意:
- 第一个 Pass 中必须关闭深度写入,以确保描边效果能覆盖重合部分;
- Shader 的渲染队列应设置为 Transparent(透明队列)。
模型描边效果具体实现
主要步骤
- 新建 Shader(命名为 OutLine),删除无用代码。
- 属性声明:
- 边缘线颜色
_OutLineColor
- 边缘线粗细
_OutLineWidth
- 边缘线颜色
- 复制 Pass:
- 第一个 Pass 用于绘制边缘线,要求:
- 关闭深度写入
- 通过属性映射定义描边参数
- 结构体只保留顶点位置(不需要纹理坐标,因为颜色是固定的,不需要采样)
- 顶点着色器中,顶点沿法线方向偏移后转换到裁剪空间
- 片元着色器直接返回固定的边缘线颜色
- 第二个 Pass 用于正常渲染模型,采用传统的纹理采样 Pass 进行测试(如有光照需求,可自行添加)
- 第一个 Pass 用于绘制边缘线,要求:
- 设置 Fallback 为
"Diffuse"
,以便在不支持自定义 Shader 时使用默认效果。
新建 Shader(命名为 OutLine)并删除无用代码
Shader "Unlit/Lesson04_OutLine"
{
Properties
{
// 主纹理,用于显示模型的基础图像
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags
{
"RenderType"="Opaque"
}
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float2 uv : TEXCOORD0; // 纹理坐标
float4 vertex : SV_POSITION; // 裁剪空间的顶点位置
};
sampler2D _MainTex; // 主纹理
// 顶点着色器:用于膨胀模型,生成边缘
v2f vert(appdata_base appdata_base)
{
v2f v2f;
// 根据顶点法线方向偏移顶点位置,控制模型的膨胀程度
float3 newVertex = appdata_base.vertex + normalize(appdata_base.normal) * _OutLineWidth;
// 将膨胀后的顶点转换到裁剪空间
v2f.vertex = UnityObjectToClipPos(float4(newVertex, 1));
return v2f;
}
// 片段着色器:为边缘填充颜色
fixed4 frag(v2f v2f) : SV_Target
{
// 此处代码待补充,实际效果由第一个 Pass 实现
}
ENDCG
}
}
}
声明属性:主纹理、边缘线颜色和边缘线粗细
Properties
{
// 主纹理,用于显示模型的基础图像
_MainTex ("Texture", 2D) = "white" {}
// 边缘线颜色,控制描边的颜色
_OutLineColor("OutLineColor", Color) = (1,1,1,1)
// 边缘线宽度,控制描边的粗细
_OutLineWidth("OutLineWidth", Float) = 0.1
}
// 定义 Shader 属性变量
sampler2D _MainTex; // 主纹理
float4 _MainTex_ST; // 主纹理的 Tiling 和 Offset 数据
fixed4 _OutLineColor; // 边缘线颜色
fixed _OutLineWidth; // 边缘线宽度
复制一个Pass,第一个作为绘制边缘线Pass,第二个作为正常渲染模型Pass
SubShader
{
// 第一 Pass:绘制边缘线
Pass
{
//...
}
// 第二 Pass:正常渲染模型
Pass
{
//...
}
}
编写第一个绘制边缘线Pass。在第一个 Pass 中,关闭深度写入,通过属性映射定义描边参数,结构体仅保留顶点位置无需纹理坐标,顶点着色器将顶点沿法线方向偏移后转换到裁剪空间,片段着色器直接返回固定的边缘线颜色
// 第一 Pass:绘制边缘线
Pass
{
// 关闭深度写入,确保描边效果覆盖物体重合部分
ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
// 第一个 Pass 渲染纯色,不需要采样,因此不保留纹理坐标
float4 vertex : SV_POSITION; // 裁剪空间的顶点位置
};
sampler2D _MainTex; // 主纹理
fixed4 _OutLineColor; // 边缘线颜色
fixed _OutLineWidth; // 边缘线宽度
// 顶点着色器:用于膨胀模型,生成边缘
v2f vert(appdata_base appdata_base)
{
v2f v2f;
// 根据顶点法线方向偏移顶点位置,控制模型的膨胀程度
float3 newVertex = appdata_base.vertex + normalize(appdata_base.normal) * _OutLineWidth;
// 将膨胀后的顶点转换到裁剪空间
v2f.vertex = UnityObjectToClipPos(float4(newVertex, 1));
return v2f;
}
// 片段着色器:为边缘填充颜色
fixed4 frag(v2f v2f) : SV_Target
{
return _OutLineColor;
}
ENDCG
}
暂时先注释掉第二个Pass。创建材质,赋值Shader后给球体挂上,加上一个白色球对比,可以明显看出来往外扩了
编写第二个正常渲染模型Pass,用编写传统的纹理采样Pass进行测试即可(如果有光照相关的需求 自定添加即可)
// 第二 Pass:正常渲染模型
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float2 uv : TEXCOORD0; // 纹理坐标
float4 vertex : SV_POSITION; // 裁剪空间的顶点位置
};
sampler2D _MainTex; // 主纹理
half4 _MainTex_ST; // 主纹理的 Tiling 和 Offset 数据
// 顶点着色器:用于生成基础模型的裁剪空间位置和纹理坐标
v2f vert(appdata_base appdata_base)
{
v2f v2f;
// 转换顶点位置到裁剪空间
v2f.vertex = UnityObjectToClipPos(appdata_base.vertex);
// 根据 Tiling 和 Offset 变换纹理坐标
v2f.uv = TRANSFORM_TEX(appdata_base.texcoord, _MainTex);
return v2f;
}
// 片段着色器:采样并输出主纹理颜色
fixed4 frag(v2f v2f) : SV_Target
{
return tex2D(_MainTex, v2f.uv);
}
ENDCG
}
可以查看到描边效果了,但是由于没有改渲染队列,会看到剑覆盖了描边,这时需要添加透明渲染队列,就可以让描边覆盖到剑了
Tags
{
// 渲染类型为不透明,但队列设置为透明(使描边效果覆盖物体本身)
"RenderType"="Opaque" "Queue"="Transparent"
}
Shader 失败处理
// 使用 Diffuse Shader 的后备效果(在不支持自定义 Shader 时使用)
Fallback "Diffuse"
4.2 知识点代码
Lesson04_OutLine.shader
Shader "Unlit/Lesson04_OutLine"
{
Properties
{
// 主纹理,用于显示模型的基础图像
_MainTex ("Texture", 2D) = "white" {}
// 边缘线颜色,控制描边的颜色
_OutLineColor("OutLineColor", Color) = (1,1,1,1)
// 边缘线宽度,控制描边的粗细
_OutLineWidth("OutLineWidth", Float) = 0.1
}
SubShader
{
Tags
{
// 渲染类型为不透明,队列设置为透明(描边需要覆盖物体本身)
"RenderType"="Opaque" "Queue"="Transparent"
}
// 第一 Pass:绘制边缘线
Pass
{
// 关闭深度写入,确保描边效果覆盖物体重合部分
ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
//第一个Pass渲染纯色,不需要采样,可以不保留纹理
float4 vertex : SV_POSITION; // 裁剪空间的顶点位置
};
sampler2D _MainTex; // 主纹理
fixed4 _OutLineColor; // 边缘线颜色
fixed _OutLineWidth; // 边缘线宽度
// 顶点着色器:用于膨胀模型,生成边缘
v2f vert(appdata_base appdata_base)
{
v2f v2f;
// 根据顶点法线方向偏移顶点位置,控制模型的膨胀程度
float3 newVertex = appdata_base.vertex + normalize(appdata_base.normal) * _OutLineWidth;
// 将膨胀后的顶点转换到裁剪空间
v2f.vertex = UnityObjectToClipPos(float4(newVertex, 1));
return v2f;
}
// 片段着色器:为边缘填充颜色
fixed4 frag(v2f v2f) : SV_Target
{
return _OutLineColor;
}
ENDCG
}
// 第二 Pass:正常渲染模型
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float2 uv : TEXCOORD0; // 纹理坐标
float4 vertex : SV_POSITION; // 裁剪空间的顶点位置
};
sampler2D _MainTex; // 主纹理
half4 _MainTex_ST; // 主纹理的 Tiling 和 Offset 数据
// 顶点着色器:用于生成基础模型的裁剪空间位置和纹理坐标
v2f vert(appdata_base appdata_base)
{
v2f v2f;
// 转换顶点位置到裁剪空间
v2f.vertex = UnityObjectToClipPos(appdata_base.vertex);
// 根据 Tiling 和 Offset 变换纹理坐标
v2f.uv = TRANSFORM_TEX(appdata_base.texcoord, _MainTex);
return v2f;
}
// 片段着色器:采样并输出主纹理颜色
fixed4 frag(v2f v2f) : SV_Target
{
return tex2D(_MainTex, v2f.uv);
}
ENDCG
}
}
// 使用 Diffuse Shader 的后备效果(在不支持自定义 Shader 时使用)
Fallback "Diffuse"
}
Lesson04_模型描边效果_具体实现.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Lesson04_模型描边效果_具体实现 : MonoBehaviour
{
void Start()
{
#region 知识回顾 模型描边效果 基本原理
//两个Pass渲染对象,一个Pass用于渲染沿法线方向放大的模型,一个Pass用于渲染正常模型。
//第一个Pass需要关闭深度写入
//Shader的渲染队列应该设置为Transparent透明队列
#endregion
#region 知识点 模型描边效果 具体实现
//1.新建Shader 取名 OutLine
// 删除无用代码
//2.属性声明
// 边缘线颜色 _OutLineColor
// 边缘线粗细 _OutLineWidth
//3.复制一个Pass
//4.编写第一个Pass
// 4-1.关闭深度写入
// 4-2.属性映射
// 4-3.结构体相关
// 不需要纹理坐标,因为颜色定死 不需要采样
// 4-4.顶点着色器
// 顶点朝法线方向进行偏移后再转换到裁剪空间
// 4-5.片元着色器
// 直接返回边缘线颜色
//5.编写第二个Pass
// 编写传统的纹理采样Pass进行测试即可(如果有光照相关的需求 自定添加即可)
//6.FallBack "Diffuse"
#endregion
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com