US4L4——物体切割效果
US4L4——物体切割效果
本章代码关键字
1 | VFACE // 片元位于模型正面还是反面的语义,一般在片元着色器的浮点数参数中使用,Unity Shader会自动传入,1表示为正面,-1表示为背面 |
物体切割效果
在游戏开发中,物体切割效果就是,物体看似被切割、分割或隐藏一部分的视觉效果。
这种效果常用与游戏和动画中,比如角色攻击时的切割效果,场景中的墙壁切割效果等等。
物体切割效果的基本原理
一句话描述它的基本原理:在片元着色器中判断片元的世界坐标是否满足切割条件,
如果满足则直接抛弃片元不渲染,判断片元在模型中的正反面,决定使用哪种纹理进行渲染
关键点:
-
如何判断世界坐标
在 Shader 中声明一个
Vector
坐标变量,通过 C# 代码把控制切割位置的物体世界空间位置传递给 Shader
在 Shader 中用片元的世界坐标和传递进来的世界坐标做比较,在这里,我们可以分三种模式,分别比较 x、y、z 坐标,切割 x、y、z 方向的片元1
material.SetVector("_CuttingPosition", cutObj.transform.position);
-
如何抛弃片元不渲染
Unity Shader 中提供了一个内置函数 clip(x),它的作用就是在片元着色器中调用时来丢弃片元的,之前在透明度测试时使用过
具体详见:US3S3L4——透明度测试
传入的值
x
小于0,则会丢弃当前片元,被丢弃的片元不会被进一步处理也就不会被渲染了,
也就是说,当我们判断片元位置需要被切割时,直接执行clip
函数传入一个负数,这个片元就会被丢弃,不会被渲染了 -
如何判断片元的正反面
Unity Shader 中提供了一个语义
VFACE
,它只能在片元着色器中使用,它可以作为参数传递给片元着色器
传入值1
表示为正面,-1
表示为背面,我们可以利用它判断该片元是模型正面还是背面片元,决定使用哪种纹理或颜色进行渲染1
2
3
4fixed4 frag(v2f i, fixed face: VFACE) : SV_Target
{
fixed4 col = face > 0 ? tex2D(_MainTex, i.uv) : tex2D(_BackTex, i.uv);
}注意:在使用它时建议加上编译指令
#pragma target 3.0
或4.0
、5.0
表示设置着色器模型版本,可以让VFACE
语义使用上的兼容性和稳定性更好1
2
3
4
5
6
7
8
9
10Pass
{
CGPROGRAM
//...
ENDCG
}
实现物体切割效果的 Shader 代码
-
新建 Shader
ObjectCutting
,删除无用代码 -
属性声明 属性映射
- 主纹理
_MainTex 2D
- 背面纹理
_BackTex 2D
- 切割方向(用来控制比较x、y、z哪个轴)
_CuttingDir Float
- 是否翻转切割
_Invert Float
- 切割位置(从 C# 传递过来)
_CuttingPos Vector
1
2
3
4
5
6
7
8Properties
{
_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) // 切割的位置
} - 主纹理
-
关闭剔除 因为要两面渲染
1
2
3
4
5
6SubShader
{
Tags { "RenderType"="Opaque" }
Cull Off // 正反都要渲染
Pass { /*...*/ }
} -
编译指令
#pragma target 3.0
让VFACE
兼容性更好1
2
3
4
5
6
7
8CGPROGRAM
// ...
ENDCG -
结构体
UV、顶点、世界坐标
1
2
3
4
5
6struct v2f
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
float3 worldPos : TEXCOORD1; // 世界坐标位置
}; -
顶点着色器函数
坐标转换、纹理赋值、世界坐标转换
1
2
3
4
5
6
7
8
9v2f vert (appdata_base v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
} -
片元着色器函数
-
加入
VFACE
语义参数 -
根据正反面决定采样颜色
-
根据切割方向判断是否丢弃(
0
代表丢弃,1
代表不丢弃)可以使用
step(edge, x)
函数-
x >= edge
返回1
-
x < edge
返回0
-
-
利用是否翻转切割参数决定是否反转丢弃
-
利用
clip
函数丢弃片元 -
若不丢弃,直接返回颜色
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23fixed4 frag (v2f i, fixed face : VFACE) : SV_Target
{
// 通过face来进行正反面判断,因为face的语义是VFACE,因此Unity Shader会自动传入对应的参数
fixed4 color = face > 0 ? tex2D(_MainTex, i.uv) : tex2D(_BackTex, i.uv);
// 计算丢弃中间值,其中_CuttingDir决定比较哪个方向
fixed cutValue;
if (_CuttingDir == 0)
cutValue = step(_CuttingPos.x, i.worldPos.x); // 若片元的世界坐标x轴方向小于切割位置,说明需要切割
else if (_CuttingDir == 1)
cutValue = step(_CuttingPos.y, i.worldPos.y); // 若片元的世界坐标y轴方向小于切割位置,说明需要切割
else if (_CuttingDir == 2)
cutValue = step(_CuttingPos.z, i.worldPos.z); // 若片元的世界坐标z轴方向小于切割位置,说明需要切割
// 如果是切割翻转的,则需要反转丢弃中间值,让原来被切割的变为不被切割的,原来未被切割的变为被切割的
cutValue = _Invert ? 1 - cutValue : cutValue;
// 如果丢弃中间值为0,则需要切割,此片元直接丢弃
if (cutValue == 0)
clip(-1);
return color;
} -
完整 Shader 代码如下:
1 | Shader "TeachShader/ObjectCutting" |
实现物体切割效果的 C# 代码
- 新建 C# 脚本 和 Shader 名一样
- 加入 [ExecuteAlways] 特性,让编辑模式下也运行,可以看到效果
- 声明材质球和切割位置对象
- 材质球初始化
- 在
Update
中不停的将切割物体位置传递给 Shader
1 | using UnityEngine; |
显示效果
在将被切割的物体上添加一个 CutObj
子对象,并将其关联到 ObjectCutting
C# 脚本上
通过设置添加了 ObjectCutting
Shader 的 Material 上的 CuttingDir
和 Invert
,并拖动 CutObj
的位置,即可修改切割效果
可见,物体呈现出了被切割的效果,显示出了其中的内部纹理