8.物体切割效果-具体实现
8.1 知识点
知识回顾:物体切割效果原理
在片元着色器中,通过比较片元的世界坐标与指定的切割坐标来判断是否满足切割条件:
- 如果满足条件,则直接丢弃该片元(使用 clip 函数)
- 同时,通过判断片元在模型中的正反面(使用 VFACE 语义),决定采用哪种纹理进行渲染
实现物体切割效果的 Shader 代码
主要步骤
- 新建 Shader(命名为 ObjectCutting)并删除无用代码
- 属性声明与属性映射
- 主纹理:_MainTex(2D,用于渲染模型正面的纹理)
- 背面纹理:_BackTex(2D,用于渲染模型背面的纹理)
- 切割方向:_CuttingDir(Float,用来控制比较 x、y、z 哪个轴)
- 是否翻转切割:_Invert(Float,用于控制是否反转丢弃逻辑)
- 切割位置:_CuttingPos(Vector,从 C# 传递的切割参考位置)
- 关闭剔除(Cull Off),以实现正反面同时渲染
- 添加编译指令
#pragma target 3.0
,提升 VFACE 的兼容性 - 声明结构体,包含纹理坐标、裁剪空间顶点位置以及世界空间坐标
- 顶点着色器函数:进行坐标转换、纹理赋值及世界坐标计算
- 片元着色器函数:
- 加入带有 VFACE 语义的参数,用于判断片元的正反面
- 根据正反面选择相应的纹理进行采样
- 根据切割方向比较切割参考位置与片元世界坐标,使用
step(edge, x)
函数判断是否丢弃(返回 0 表示丢弃,1 表示保留) - 根据 _Invert 参数决定是否反转丢弃逻辑
- 利用
clip
函数丢弃不满足条件的片元;若不丢弃,则返回采样后的颜色
新建 Shader,删除无用代码
Shader "Unlit/Lesson08_ObjectCutting"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {} // 用于渲染模型正面的纹理
}
SubShader
{
Tags
{
"RenderType"="Opaque" // 标记为不透明渲染类型
}
Cull Off // 禁用剔除,以便渲染正反两面
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 3.0
#include "UnityCG.cginc"
struct v2f
{
float2 uv : TEXCOORD0; // 纹理坐标
float4 vertex : SV_POSITION; // 裁剪空间顶点位置
};
sampler2D _MainTex;
v2f vert(appdata_base appdata_base)
{
// 顶点着色器具体实现
}
fixed4 frag(v2f v2f, fixed face:VFACE) : SV_Target
{
// 片元着色器具体实现
}
ENDCG
}
}
}
进行属性声明和属性映射,包括主纹理 _MainTex 2D,背面纹理 _BackTex 2D, 切割方向(用来控制比较x、y、z哪个轴)_CuttingDir Float,是否翻转切割 _Invert Float,切割位置(从C#传递过来)_CuttingPos Vector
Properties
{
_MainTex ("Texture", 2D) = "white" {} // 用于渲染模型正面的纹理
_BackTex ("BackTex", 2D) = "white" {} // 用于渲染模型背面的纹理
_CuttingDir("CuttingDir", Float) = 0 // 切割的方向:0-x,1-y,2-z
_Invert("Invert", Float) = 0 // 是否翻转切割:0-不翻转,1-翻转
_CuttingPos("CuttingPos", Vector) = (0,0,0,0) // 切割的参考位置
}
// 定义 Shader 属性变量
sampler2D _MainTex; // 用于渲染模型正面的纹理
sampler2D _BackTex; // 用于渲染模型背面的纹理
fixed _CuttingDir; // 切割的方向:0-x,1-y,2-z
fixed _Invert; // 是否翻转切割:0-不翻转,1-翻转
float4 _CuttingPos; // 切割的参考位置
关闭剔除设置,因为要两面渲染
Cull Off // 禁用剔除,以便渲染正反两面
添加编译指令 #pragma target 3.0 让VFACE兼容性更好
#pragma vertex vert
#pragma fragment frag
#pragma target 3.0
声明结构体,要包括uv、顶点、世界坐标
struct v2f
{
float2 uv : TEXCOORD0; // 纹理坐标
float4 vertex : SV_POSITION; // 裁剪空间顶点位置
float3 worldPos : TEXCOORD1; // 世界空间中的顶点位置
};
顶点着色器函数进行坐标转换、纹理赋值、世界坐标转换
v2f vert(appdata_base appdata_base)
{
v2f v2f;
// 将顶点从对象空间转换到裁剪空间
v2f.vertex = UnityObjectToClipPos(appdata_base.vertex);
// 获取顶点的纹理坐标
v2f.uv = appdata_base.texcoord;
// 将顶点从对象空间转换到世界空间
v2f.worldPos = mul(unity_ObjectToWorld, appdata_base.vertex).xyz;
return v2f;
}
片元着色器函数的实现步骤如下:首先,加入带有 VFACE 语义的参数,用于判断片元的正反面,并根据正反面选择对应的采样颜色。接着,根据切割方向确定片元是否丢弃,其中 0 表示丢弃,1 表示保留。可以通过使用 step(edge, x)
函数来实现该判断,其中 x >= edge
时返回 1,x < edge
时返回 0。此外,根据是否启用了翻转切割参数,决定是否反转丢弃逻辑。最终,使用 clip
函数丢弃片元;若片元未被丢弃,则直接返回采样的颜色值。
// 片元着色器加一个fixed face:VFACE参数
fixed4 frag(v2f v2f, fixed face:VFACE) : SV_Target
{
// 根据 face 判断是正面(>0)还是背面,选择对应的纹理
fixed4 col = face > 0 ? tex2D(_MainTex, v2f.uv) : tex2D(_BackTex, v2f.uv);
// 用于判断片元是否被丢弃的值 0代表丢弃 1代表不丢弃
fixed cutValue;
// 根据切割方向判断是否需要丢弃片元
// 使用step(edge, x)函数 edge<=x 返回 1 edge>x 返回 0
// _CuttingPos是C#传过来的切割的参考位置 和片元位置比较
if (_CuttingDir == 0)
cutValue = step(_CuttingPos.x, v2f.worldPos.x); // 比较 x 坐标
else if (_CuttingDir == 1)
cutValue = step(_CuttingPos.y, v2f.worldPos.y); // 比较 y 坐标
else if (_CuttingDir == 2)
cutValue = step(_CuttingPos.z, v2f.worldPos.z); // 比较 z 坐标
// 根据翻转状态调整 cutValue 如果要翻转 0变成1 1变成0
cutValue = _Invert ? 1 - cutValue : cutValue;
// 如果 cutValue 为 0,丢弃当前片元
if (cutValue == 0)
clip(-1); // clip 的值小于 0 表示丢弃片元
// 返回最终颜色
return col;
}
实现物体切割效果的 C# 代码
主要步骤
- 新建与 Shader 同名的 C# 脚本
- 加入
[ExecuteAlways]
特性,使脚本在编辑模式下也运行,从而预览效果 - 声明材质球和切割位置对象
- 在
Start
方法中初始化材质球 - 在
Update
方法中不断将切割物体的位置传递给 Shader
新建C#脚本 和Shader名一样,加入[ExecuteAlways]特性,让编辑模式下也运行,可以看到效果
using UnityEngine;
[ExecuteAlways] // 允许脚本在编辑器模式下运行
public class Lesson08_ObjectCutting : MonoBehaviour
{
// 此处可添加后续实现代码
}
声明材质球和切割位置对象,Start中材质球初始化,在Update中不停的将切割物体位置传递给Shader
using UnityEngine;
[ExecuteAlways] // 允许脚本在编辑器模式下运行
public class Lesson08_ObjectCutting : MonoBehaviour
{
// 材质,用于给对象应用切割效果
private Material material;
// 切割对象,用于提供切割位置
public GameObject cutObj;
void Start()
{
// 获取当前对象的材质(共享材质,非实例材质)
material = this.GetComponent<Renderer>().sharedMaterial;
}
void Update()
{
// 如果材质和切割对象存在,则更新材质中的切割位置属性
if (material != null && cutObj != null)
{
material.SetVector("_CuttingPos", cutObj.transform.position);
}
}
}
创建材质关联shader,给一个球体,球体下创建一个空对象,球体挂载C#脚本,C#脚本关联空对象,移动空对象可以看到基于空对象的切割效果,可以修改shader参数切割不同轴
8.2 知识点代码
Lesson08_ObjectCutting.shader
Shader "Unlit/Lesson08_ObjectCutting"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {} // 用于渲染模型正面的纹理
_BackTex ("BackTex", 2D) = "white" {} // 用于渲染模型背面的纹理
_CuttingDir("CuttingDir", Float) = 0 // 切割的方向:0-x,1-y,2-z
_Invert("Invert", Float) = 0 // 是否翻转切割:0-不翻转,1-翻转
_CuttingPos("CuttingPos", Vector) = (0,0,0,0) // 切割的参考位置
}
SubShader
{
Tags
{
"RenderType"="Opaque" // 标记为不透明渲染类型
}
Cull Off // 禁用剔除,以便渲染正反两面
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 3.0
#include "UnityCG.cginc"
struct v2f
{
float2 uv : TEXCOORD0; // 纹理坐标
float4 vertex : SV_POSITION; // 裁剪空间顶点位置
float3 worldPos : TEXCOORD1; // 世界空间中的顶点位置
};
sampler2D _MainTex;// 用于渲染模型正面的纹理
sampler2D _BackTex;// 用于渲染模型背面的纹理
fixed _CuttingDir;// 切割的方向:0-x,1-y,2-z
fixed _Invert;// 是否翻转切割:0-不翻转,1-翻转
float4 _CuttingPos;// 切割的参考位置
v2f vert(appdata_base appdata_base)
{
v2f v2f;
// 将顶点从对象空间转换到裁剪空间
v2f.vertex = UnityObjectToClipPos(appdata_base.vertex);
// 获取顶点的纹理坐标
v2f.uv = appdata_base.texcoord;
// 将顶点从对象空间转换到世界空间
v2f.worldPos = mul(unity_ObjectToWorld, appdata_base.vertex).xyz;
return v2f;
}
// 片元着色器加一个fixed face:VFACE参数
fixed4 frag(v2f v2f, fixed face:VFACE) : SV_Target
{
// 根据 face 判断是正面(>0)还是背面,选择对应的纹理
fixed4 col = face > 0 ? tex2D(_MainTex, v2f.uv) : tex2D(_BackTex, v2f.uv);
// 用于判断片元是否被丢弃的值 0代表丢弃 1代表不丢弃
fixed cutValue;
// 根据切割方向判断是否需要丢弃片元
// 使用step(edge, x)函数 edge<=x 返回 1 edge>x 返回 0
// _CuttingPos是C#传过来的切割的参考位置 和片元位置比较
if (_CuttingDir == 0)
cutValue = step(_CuttingPos.x, v2f.worldPos.x); // 比较 x 坐标
else if (_CuttingDir == 1)
cutValue = step(_CuttingPos.y, v2f.worldPos.y); // 比较 y 坐标
else if (_CuttingDir == 2)
cutValue = step(_CuttingPos.z, v2f.worldPos.z); // 比较 z 坐标
// 根据翻转状态调整 cutValue 如果要翻转 0变成1 1变成0
cutValue = _Invert ? 1 - cutValue : cutValue;
// 如果 cutValue 为 0,丢弃当前片元
if (cutValue == 0)
clip(-1); // clip 的值小于 0 表示丢弃片元
// 返回最终颜色
return col;
}
ENDCG
}
}
}
Lesson08_ObjectCutting.cs
using UnityEngine;
[ExecuteAlways] // 允许脚本在编辑器模式下运行
public class Lesson08_ObjectCutting : MonoBehaviour
{
// 材质,用于给对象应用切割效果
private Material material;
// 切割对象,用于提供切割位置
public GameObject cutObj;
void Start()
{
// 获取当前对象的材质(共享材质,非实例材质)
material = this.GetComponent<Renderer>().sharedMaterial;
}
void Update()
{
// 如果材质和切割对象存在,则更新材质中的切割位置属性
if (material != null && cutObj != null)
{
material.SetVector("_CuttingPos", cutObj.transform.position);
}
}
}
Lesson08_物体切割效果_具体实现.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Lesson08_物体切割效果_具体实现 : MonoBehaviour
{
void Start()
{
#region 知识回顾
//在片元着色器中判断片元的世界坐标是否满足切割条件(片元世界坐标和切割坐标比较)
//如果满足则直接抛弃片元不渲染(clip)
//判断片元在模型中的正反面,决定使用哪种纹理进行渲染(VFACE)
#endregion
#region 知识点一 实现物体切割效果的 Shader代码
//1.新建Shader ObjectCutting
// 删除无用代码
//2.属性声明 属性映射
// 主纹理 _MainTex 2D
// 背面纹理 _BackTex 2D
// 切割方向(用来控制比较x、y、z哪个轴)_CuttingDir Float
// 是否翻转切割 _Invert Float
// 切割位置(从C#传递过来)_CuttingPos Vector
//3.关闭剔除 因为要两面渲染
//4.编译指令 #pragma target 3.0 让VFACE兼容性更好
//5.结构体
// uv、顶点、世界坐标
//6.顶点着色器函数
// 坐标转换、纹理赋值、世界坐标转换
//7.片元着色器函数
// 7-1.加入VFACE语义参数
// 7-2.根据正反面决定采样颜色
// 7-3.根据切割方向判断是否丢弃(0代表丢弃,1代表不丢弃)
// 可以使用step(edge, x)函数
// x>=edge 返回 1;x<edge 返回 0
// 7-4.利用是否翻转切割参数决定是否反转丢弃
// 7-5.利用clip函数丢弃片元
// 7-6.若不丢弃 直接返回颜色
#endregion
#region 知识点二 实现物体切割效果的 C#代码
//1.新建C#脚本 和Shader名一样
//2.加入[ExecuteAlways]特性,让编辑模式下也运行,可以看到效果
//3.声明材质球和切割位置对象
//4.材质球初始化
//6.在Update中不停的将切割物体位置传递给Shader
#endregion
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com