前话
本期分享一个超酷的光剑效果。使用工具如下:ShaderGraph、PostProcess、UniRX(可选),可用脚本控制光剑的收放。先预览一下效果:

制作思路
Shader连线图如下

- Position 节点取到当前网格所在空间(对象、视口、世界、切线)下的顶点坐标,输出为Vector3类型,作用于顶点着色阶段。
- Split 节点将输入的向量(Vector)进行拆分,输出为一维向量(Vector1)
- Step 有两个输入口,均可输入动态向量(可以是一维、二维、三维、四维);In代表输入值,作为参与比较的值;Edge代表步长值,作为比较值。输出值为动态向量,当前输入的In值>=Step值时,则输出值为1,否则输出值为0.
- Color 只有输出端口,可选模式有默认的RGB和HDR(高动态范围)
- PBR Master
- Alpha属性 作为输入端口,输入类型为Vetor1。定义当前材质的Alpha值,用于透明或者透明度裁剪。取值范围为0-1;0表示完全透明,0.5表示半透明,1表示不透明。
- AlphaClipThreshold 属性作为透明度裁剪的阈值,输入属性为Vector1;当当前片元的Alpha值低于这个值时,当前片元不会被渲染。取值范围为0-1。
分析一下制作思路:
1. 取到当前模型在模型空间中的顶点坐标,将得到的顶点坐标进行拆分,取剑身Z轴方向上的顶点坐标(本例中是剑身是Z轴朝向,实际根据你的模型剑身朝向进行拆分取值)。我们可以看一下当前网格顶点坐标的Z值,我这个模型的剑身有80个顶点,我用一个10*8的矩阵打印展示一下吧,网格顶点坐标如下(保留了三位小数):
6.237, 6.237, 0.026,-0.045,-0.045, 6.744, 6.744, 0.026, 6.237, 6.237,
-0.045,-0.045, 6.744, 6.744, 6.237, 6.744, 6.744, 6.237, 0.026,-0.045,
-0.045, 6.237, 6.744, 6.744, 6.237,-0.045, 0.026,-0.045, 0.026, 6.237,
6.744, 6.744, 6.237, 0.026,-0.045,-0.045, 6.237, 6.744, 6.744, 6.237,
-0.045, 0.026,-0.045, 0.026,-0.047,-0.045, 0.026,-0.054,-0.066,-0.080,
-0.097,-0.116,-0.045, 0.026,-0.045,-0.047,-0.054,-0.066,-0.080,-0.097,
-0.116,-0.045,-0.116,-0.045, 0.026,-0.097,-0.080,-0.066,-0.054,-0.047,
-0.045, 0.026,-0.045,-0.116,-0.097,-0.080,-0.066,-0.054,-0.047,-0.045,
遍历顶点坐标代码如下:
using System.Text;
using UnityEngine;
public class MeshUtillity : MonoBehaviour
{
void Start()
{
Mesh mesh=this.GetComponent<MeshFilter>().mesh;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 10; j++)
{
if (mesh.vertices[i * 10 + j].z > 0)
{
sb.Append(string.Format("{0}{1}{2}"," ", mesh.vertices[i * 10 + j].z.ToString("F3"), ","));
continue;
}
sb.Append(string.Format("{0}{1}", mesh.vertices[i * 10 + j].z.ToString("F3"), ","));
}
sb.Append("\n");
}
Debug.Log(sb.ToString());
}
}
将上面中的Z轴顶点坐标在每一帧中作为Step函数的输入值与给定的Edge值(Edge值作为属性,可通过脚本或者Shader面板调节),如果顶点Z坐标>=Edge值,则Step输出值为1;由于Step输出值连接到PBR Master的AlphaClipThreshold节点上,所以AlphaClipThreshold的输入值为1,如果当前片元的Alpha低于这个值,则这个片元就不会被渲染。(这个过程在片元着色器中进行),另外注意,我们也设置了PBR Master 节点上的Alpha值为0或0.5,表示当前材质是可完全透明或者半透明的,如果这个值设置为1,则AlphaTClipTheshold值无能如何变化,材质的透明度无论如何变化,都不会对Alpha进行任何裁剪。
2. PostProcess(屏幕后处理)
使用Bloom(泛光)效果:泛光是用于再现真实摄像机成像瑕疵的效果。该效果会产生从图像明亮区域边界向外延伸的光线条纹,给人的感觉是极其明亮的光线压制住了摄像机或是透过眼睛看到该场景。
Intensity //泛光过滤的强度
Threshold //过滤亮度低于这个值的像素
Soft Knee 渐变过渡的阈值,为0,过度较生硬;为1,过度较柔和
Dissusion //扩散、散射
设置如图:

3. 脚本控制光剑的收放
UniRX写法
using System;
using System.Collections;
using UnityEngine;
using UniRx;
public class ControllerSwordUniRX : MonoBehaviour
{
public Material mat;
public float disolveTime = 3.0f;
void Start()
{
Observable.EveryUpdate()
.Where(_ => Input.GetMouseButtonDown(0))
.Subscribe(_ =>
{
Observable.FromCoroutine<float>((observer) => StartDisolveForFromCoroutine(observer, 6.75f)).Subscribe(x =>
{
mat.SetFloat("_Disolve", x);
}, onCompleted: () =>
{
Debug.Log("剑已出鞘");
});
});
Observable.EveryUpdate()
.Where(_ => Input.GetMouseButtonDown(1))
.Subscribe(_ =>
{
Observable.FromCoroutine<float>((observer) => StartDisolveForFromCoroutine(observer, -0.2f)).Subscribe(x =>
{
mat.SetFloat("_Disolve", x);
}, onCompleted: () =>
{
Debug.Log("剑已入鞘");
});
});
}
IEnumerator StartDisolveForFromCoroutine(IObserver<float> observer, float target)
{
float temp = 0;
float disolve = mat.GetFloat("_Disolve");
float tempDisolveValue = 0;
while (temp < disolveTime)
{
yield return null;
tempDisolveValue = Mathf.Lerp(disolve, target, temp / disolveTime);
observer.OnNext(tempDisolveValue);
temp += Time.deltaTime;
}
observer.OnCompleted();
}
}
常规Update写法
using System.Collections;
using UnityEngine;
public class ControllerSword : MonoBehaviour
{
public Material mat;
public float disolveTime;
void Start()
{
mat.SetFloat("_Disolve", 0);
}
void Update()
{
if (Input.GetMouseButtonDown(0))
{
StartCoroutine(StartDisolveForFromCoroutine(6.7f));
}
if (Input.GetMouseButtonDown(1))
{
StartCoroutine(StartDisolveForFromCoroutine(-0.2f));
}
}
IEnumerator StartDisolveForFromCoroutine(float target)
{
float temp = 0;
float disolve = mat.GetFloat("_Disolve");
float tempDisolveValue = 0;
while (temp < disolveTime)
{
yield return null;
tempDisolveValue = Mathf.Lerp(disolve, target, temp / disolveTime);
temp += Time.deltaTime;
mat.SetFloat("_Disolve", tempDisolveValue);
}
}
}
注意,下图红框中两处,保持名字一致,否则,你在脚本中是访问不到Shader的属性的。

更多内容,欢迎访问:
