有些时候我们希望让三维物体在一个面某方向上可以看到,另一方向不能看到
就像这样
这时候需要写一个shader来实现这个功能
<0x01> 原理
既然是某个面一侧可以显示,一侧不能显示
我们可以联想到判断面正反的量——法向
所谓法向是一个垂直于平面的向量,向量正方向表示平面正面
而我们知道,通过向量乘法,我们可以判断俩向量夹角是否小于90°
通过俩向量得出的值即可判断渲染的部分在面的正侧还是背侧
<0x02> 实现
这里通过Unity Shader Graph实现
通过Shader Graph可以很方便地继承URP自带的那几个Shader的效果
比如默认的Lit Shader
首先先新建一个Shader Graph
选择Lit Shader Graph

在Shader Graph中创建三个参数
在Graph Inspector的Graph Settings中打开Alpha Clipping,然后这样连接
这里暴露3个参数PlanePosition表示平面的位置PlaneNormal表示平面法向方向BaseMap表示物体的材质
平面的参数需要通过脚本传入,所以要新建一个脚本,代码如下
using UnityEngine;
// 让脚本可以在编辑模式中运行
[ExecuteInEditMode]
public class PlaneCutShaderSetter : MonoBehaviour
{
public Transform plane;
private static readonly int PlanePositionID = Shader.PropertyToID("_PlanePosition");
private static readonly int PlaneNormalID = Shader.PropertyToID("_PlaneNormal");
private Renderer _renderer;
private Material _material;
// 通过Awake,使得添加脚本时自动更改材质
private void Awake()
{
_renderer = GetComponent<Renderer>();
_renderer.material = new Material(Shader.Find("Shader Graphs/PlaneCutShader"));
_material = _renderer.sharedMaterial;
}
private void Update()
{
if (!plane) return;
_material.SetVector(PlanePositionID, plane.position);
_material.SetVector(PlaneNormalID, plane.up);
}
// 通过OnDestroy,使得销毁组件时更改材质为默认材质
private void OnDestroy()
{
_renderer.material = new Material(Shader.Find("Universal Render Pipeline/Lit"));
}
}
<0x03> 测试
在场景中添加一个物体和一个平面
然后在物体上添加上面写的脚本组件
在组件中指定平面
效果如下
这就实现了简单的平面截断效果
<0x04> 优化
不指定平面情况
目前的实现是要求给一个平面,如果不给的话显示会有问题
如果需要不指定平面就正常显示的话有两个思路
首先是给PlaneNormal和PlanePosition设定默认值
比如说PlanePosition设定默认值给一个很低的值,然后给PlaneNormal设定(0,1,0)
也可以修改Shader Graph,添加新的参数控制是否进行截断处理
然后修改脚本代码,添加不指定平面的情况
using UnityEngine;
[ExecuteInEditMode]
public class PlaneCutShaderSetter : MonoBehaviour
{
public Transform plane;
private static readonly int CutEnableID = Shader.PropertyToID("_CutEnable");
private static readonly int PlanePositionID = Shader.PropertyToID("_PlanePosition");
private static readonly int PlaneNormalID = Shader.PropertyToID("_PlaneNormal");
private Renderer _renderer;
private Material _material;
private void Awake()
{
_renderer = GetComponent<Renderer>();
_renderer.material = new Material(Shader.Find("Shader Graphs/PlaneCutShader"));
_material = _renderer.sharedMaterial;
}
private void Update()
{
if (plane)
{
_material.SetFloat(CutEnableID, 1);
_material.SetVector(PlanePositionID, plane.position);
_material.SetVector(PlaneNormalID, plane.up);
}
else
{
_material.SetFloat(CutEnableID, 0);
}
}
private void OnDestroy()
{
_renderer.material = new Material(Shader.Find("Universal Render Pipeline/Lit"));
}
}
需要指定多个平面的情况
没啥特别好的办法,就是在Shader Graph中多复制几个平面判断
然后在脚本中传参
(因为我没找到办法实现数组类型传参)