1 由深度纹理重构世界坐标

屏幕深度和法线纹理简介中对深度和法线纹理的来源、使用及推导过程进行了讲解,本文将介绍使用深度纹理重构世界坐标的方法,并使用重构后的世界坐标模拟激光雷达特效。

​ 本文完整资源见→Unity3D激光雷达特效

1)重构像素点世界坐标

​ 对于屏幕上的任意一点,它对应的世界坐标系中的点记为 P,对应的*裁剪*面上的点记为 Q,相机位置记为 O(坐标为 _WorldSpaceCameraPos),假设 P 点的深度为 depth(由 LinearEyeDepth 函数获取),相机到**面的距离为 near,如下图所示。

​ 根据上图,可以列出以下方程组关系。其中,公式 2 由三角形相似原理得到,公式 3 由 O、P、Q 三点共线得到。

​ 化简得:

​ Q 点在**面上,可以通过*裁剪*面的四个角插值得到,O 和 near 为定值,因此 (OQ / near) 也可以通过插值得到。假设*裁剪*面的四个角分别为 A、B、C、D,我们将 (OA / near)、(OB / near)、(OC / near)、(OD / near) 输入顶点着色器中,光珊化会自动为我们计算插值后的 (OQ / near)。

​ 如下,我们可以在插值寄存器中定义变量 interpolatedRay,用于存储向量 (OQ / near)。

struct v2f {
float4 pos : SV_POSITION; // 裁剪空间顶点坐标
half2 uv : TEXCOORD0; // 纹理uv坐标
float4 interpolatedRay : TEXCOORD1; // 插值射线向量(由相机指向**面上点的向量除以near后的坐标)
};

2)*裁剪*面四角射线向量计算

​ 记*裁剪*面上左下角、右下角、右上角、左上角、中心、右中心、上中心顶点分别为 A、B、C、D、Q、E、F,相机位置为 O 点,如下:

​ 根据几何关系,可以计算向量 OA、OB、OC、OD 如下:

​ 假设摄像机竖直方向的视野角度为 fov(通过 camera.fieldOfView 获取),屏幕宽高比为 aspect(通过 camera.aspect 获取),相机距离*裁剪*面的距离为 near(通过 camera.nearClipPlane 获取),相机向右、向上、向前方向的单位方向向量分别为 right、up、forward(通过 camera.transform 组件获取),则向量 OQ、QE、QF 的计算如下:

2 间距均匀的雷达波特效

2.1 雷达波扩散原理

​ 对于屏幕上任意一点,假设其对应的世界坐标为 worldPos,其线性深度值为 lineDepth(通过 LinearEyeDepth 函数获取),如果 lineDepth >= far - 1(far 通过 _ProjectionParams.z 获取),说明该点落在天空中,不参与雷达波计算,因此本文仅考虑 lineDepth < far - 1 的像素点雷达波计算。

​ 假设雷达波中心坐标为 waveCenter,波纹间距为 waveGap,波纹宽度为 waveLineWidth,雷达波的传播速度和传播时间分别为 waveSpeed、waveTime,雷达波的发射周期为 waveCastTime,雷达波发射的初始距离为 initWaveDist,当前顶点被采样为目标纹理颜色的比率因子为 factor,波纹颜色为 waveColor,当前顶点在叠加雷达波前后的颜色分别为 tex、finalColor,则 finalColor 的计算如下:

float len = length(worldPos - waveCenter); // 当前顶点距离雷达波中心的距离
float time = fmod(waveTime, waveCastTime); // 当前发射周期中, 雷达波传播的时间
float dist = initWaveDist + waveSpeed * time; // 当前发射周期中, 雷达波传播的距离
float mod = fmod(abs(dist - len), waveGap); // 当前顶点距离最*的内环波纹的距离
float rate = min(min(mod, waveGap - mod), waveLineWidth) / waveLineWidth; // 当前顶点处在波纹范围外的比率(值域: [0,1])
float factor = smoothstep(0, 1, rate); // 当前顶点被采样为目标纹理颜色的比率因子(值域: [0,1])
fixed4 finalColor = lerp(waveColor, tex, factor); // 当前顶点叠加雷达波后的颜色

2.2 点选设置雷达波中心

​ LaserRadar.cs

using UnityEngine;

[RequireComponent(typeof(Camera))] // 需要相机组件
public class LaserRadar : MonoBehaviour {
public Color waveColor = Color.red; // 雷达波的颜色
[Range(0.1f, 0.49f)]
public float waveLineWidth = 0.49f; // 雷达波纹的宽度
[Range(1, 10)]
public float waveGap = 2; // 雷达波的间距
[Range(0.5f, 10f)]
public float waveSpeed = 1f; // 雷达波传播的速度
[Range(3, 10)]
public float waveCastTime = 10; // 雷达波发射的时间周期
[Range(3, 10)]
public int waveNum = 5; // 每个发射周期的波纹数
[Range(0.1f, 20)]
public float initWaveDist = 3; // 雷达波初始的距离
[Range(10, 200)]
public float maxWaveDist = 30f; // 雷达波传播的最远距离 private bool enableWave = false; // 是否开启雷达波特效
private Vector4 waveCenter; // 雷达波中心
private float waveTime = 0; // 雷达波开始时间
private Camera cam; // 相机
private Material material = null; // 材质 private void Awake() {
cam = GetComponent<Camera>();
material = new Material(Shader.Find("MyShader/LaserRadar"));
material.hideFlags = HideFlags.DontSave;
} private void OnEnable() {
cam.depthTextureMode |= DepthTextureMode.Depth;
} private void Update() {
if (Input.GetMouseButtonDown(0)) {
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hitInfo;
if (Physics.Raycast(ray, out hitInfo)) {
enableWave = true;
material.SetInt("_Enable", 1);
waveCenter = hitInfo.point;
material.SetVector("_WaveCenter", waveCenter);
waveTime = 0;
}
}
if (enableWave) {
waveTime += Time.deltaTime;
if (waveTime > waveCastTime) {
enableWave = false;
material.SetInt("_Enable", 0);
}
}
} private void OnRenderImage(RenderTexture src, RenderTexture dest) {
if (enableWave) {
Matrix4x4 frustumCorners = GetFrustumCornersRay();
material.SetMatrix("_FrustumCornersRay", frustumCorners);
material.SetColor("_WaveColor", waveColor);
material.SetFloat("_WaveLineWidth", waveLineWidth);
material.SetFloat("_WaveGap", waveGap);
material.SetFloat("_WaveSpeed", waveSpeed);
material.SetFloat("_WaveTime", waveTime);
material.SetFloat("_WaveCastTime", waveCastTime);
material.SetFloat("_WaveNum", waveNum);
material.SetFloat("_InitWaveDist", initWaveDist);
material.SetFloat("_MaxWaveDist", maxWaveDist);
Graphics.Blit(src, dest, material);
} else {
Graphics.Blit(src, dest);
}
} private Matrix4x4 GetFrustumCornersRay() { // 获取插值射线向量(由相机指向**面上四个角点的向量除以near后的坐标)
Matrix4x4 frustumCorners = Matrix4x4.identity;
float fov = cam.fieldOfView;
float near = cam.nearClipPlane;
float aspect = cam.aspect;
float halfHeight = near * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad);
Vector3 toRight = cam.transform.right * halfHeight * aspect; // 指向右方的向量
Vector3 toTop = cam.transform.up * halfHeight; // 指向上方的向量
Vector3 toForward = cam.transform.forward * near; // 指向前方的向量
Vector3 bottomLeft = (toForward - toTop - toRight) / near; // 指向左下角的射线
Vector3 bottomRight = (toForward + toRight - toTop) / near; // 指向右下角的射线
Vector3 topRight = (toForward + toRight + toTop) / near; // 指向右上角的射线
Vector3 topLeft = (toForward + toTop - toRight) / near; // 指向左上角的射线
frustumCorners.SetRow(0, bottomLeft);
frustumCorners.SetRow(1, bottomRight);
frustumCorners.SetRow(2, topRight);
frustumCorners.SetRow(3, topLeft);
return frustumCorners;
}
}

​ LaserRadar.shader

Shader "MyShader/LaserRadar" { // 雷达波特效
Properties{
_MainTex("Base (RGB)", 2D) = "white" {} // 主纹理
_Enable("Enable", Int) = 0 // 是否启动雷达波特效
_WaveColor("WaveColor", Color) = (1, 0, 0, 1) // 雷达波的颜色
_WaveLineWidth("WaveLineWidth", Float) = 0.49 // 雷达波纹条的宽度
_WaveCenter("WaveCenter", Vector) = (0, 0, 0, 0) // 雷达的波中心
_WaveGap("WaveGap", Float) = 2 // 雷达波的间距
_WaveSpeed("WaveSpeed", Float) = 1 // 雷达波的传播速度
_WaveTime("WaveTime", Float) = 0 // 雷达波传播的时间
_WaveCastTime("WaveCastTime", Float) = 10 // 雷达波发射的时间周期
_WaveNum("WaveNum", Int) = 5 // 每个发射周期的波纹数
_InitWaveDist("InitWaveDist", Float) = 3 // 雷达波初始的距离
_MaxWaveDist("MaxWaveDist", Float) = 30 // 雷达波传播的最远距离
} SubShader{
Pass {
// 深度测试始终通过, 关闭深度写入
ZTest Always ZWrite Off CGPROGRAM #include "UnityCG.cginc" #pragma vertex vert
#pragma fragment frag sampler2D _MainTex; // 主纹理
sampler2D _CameraDepthTexture; // 深度纹理
float4x4 _FrustumCornersRay; // 视锥体四角射线向量(由相机指向**面上四个角点的向量除以near后的坐标)
int _Enable; // 是否启动雷达波特效
fixed4 _WaveColor; // 雷达波的颜色
float _WaveLineWidth; // 雷达波纹的宽度
float4 _WaveCenter; // 雷达波的中心
float _WaveGap; // 雷达波的间距
float _WaveSpeed; // 雷达波的速度
float _WaveTime; // 雷达波传播的时间
float _WaveCastTime; // 雷达波发射的时间周期
int _WaveNum; // 每个发射周期的波纹数
float _InitWaveDist; // 雷达波初始的距离
float _MaxWaveDist; // 雷达波传播的最远距离 struct v2f {
float4 pos : SV_POSITION; // 裁剪空间顶点坐标
half2 uv : TEXCOORD0; // 纹理uv坐标
float4 interpolatedRay : TEXCOORD1; // 插值射线向量(由相机指向**面上点的向量除以near后的坐标)
}; float4 getInterpolatedRay(half2 uv) { // 获取插值射线向量(由相机指向**面上四个角点的向量除以near后的坐标)
int index = 0;
if (uv.x < 0.5 && uv.y < 0.5) {
index = 0;
} else if (uv.x > 0.5 && uv.y < 0.5) {
index = 1;
} else if (uv.x > 0.5 && uv.y > 0.5) {
index = 2;
} else {
index = 3;
}
return _FrustumCornersRay[index];
} v2f vert(appdata_img v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex); // 计算裁剪坐标系中顶点坐标, 等价于: mul(unity_MatrixMVP, v.vertex)
o.uv = v.texcoord;
o.interpolatedRay = getInterpolatedRay(v.texcoord); // 获取插值射线向量(由相机指向**面上四个角点的向量除以near后的坐标)
return o;
} fixed4 frag(v2f i) : SV_Target{
if (_Enable == 0) {
return tex2D(_MainTex, i.uv);
}
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv); // 非线性的深度, tex2D(_CameraDepthTexture, i.uv).r
float linearDepth = LinearEyeDepth(depth); // 线性的深度
float factor = 1;
if (linearDepth < _ProjectionParams.z - 1) { // _ProjectionParams = (1, near, far, 1 / far)
float3 worldPos = _WorldSpaceCameraPos + linearDepth * i.interpolatedRay.xyz; // 顶点世界坐标
float len = length(worldPos - _WaveCenter.xyz); // 当前顶点距离雷达波中心的距离
if (len < _InitWaveDist || len > _MaxWaveDist) {
return tex2D(_MainTex, i.uv);
}
float time = fmod(_WaveTime, _WaveCastTime); // 当前发射周期中, 雷达波传播的时间
float dist = _InitWaveDist + _WaveSpeed * time; // 当前发射周期中, 雷达波传播的距离
if (len > dist + _WaveLineWidth || len < dist - _WaveGap * (_WaveNum - 1) - _WaveLineWidth) {
return tex2D(_MainTex, i.uv);
}
float mod = fmod(abs(dist - len), _WaveGap); // 当前顶点距离最*的内环波纹的距离
float rate = min(min(mod, _WaveGap - mod), _WaveLineWidth) / _WaveLineWidth; // 当前顶点处在波纹范围外的比率
factor = smoothstep(0, 1, rate); // 当前顶点被采样为目标纹理颜色的比率因子
}
fixed4 tex = tex2D(_MainTex, i.uv);
fixed4 color = lerp(_WaveColor, tex, factor);
return color;
} ENDCG
}
} FallBack off
}

​ 运行效果如下:

2.3 雷达波中心跟随物体运动

​ LaserRadar.cs

using UnityEngine;

[RequireComponent(typeof(Camera))] // 需要相机组件
public class LaserRadar : MonoBehaviour {
public Color waveColor = Color.red; // 雷达波的颜色
[Range(0.1f, 0.49f)]
public float waveLineWidth = 0.49f; // 雷达波纹的宽度
[Range(1, 10)]
public float waveGap = 2; // 雷达波的间距
[Range(0.5f, 10f)]
public float waveSpeed = 1f; // 雷达波传播的速度
[Range(3, 10)]
public float waveCastTime = 10; // 雷达波发射的时间周期
[Range(3, 10)]
public int waveNum = 5; // 每个发射周期的波纹数
[Range(0.1f, 20)]
public float initWaveDist = 3; // 雷达波初始的距离
[Range(10, 200)]
public float maxWaveDist = 30f; // 雷达波传播的最远距离 private bool enableWave = false; // 是否开启雷达波特效
private Vector4 waveCenter; // 雷达波中心
private Camera cam; // 相机
private Material material = null; // 材质
private Transform target; // 发射雷达波的目标物体 private void Awake() {
cam = GetComponent<Camera>();
material = new Material(Shader.Find("MyShader/LaserRadar"));
material.hideFlags = HideFlags.DontSave;
target = GameObject.Find("Car").transform;
} private void OnEnable() {
cam.depthTextureMode |= DepthTextureMode.Depth;
enableWave = true;
material.SetInt("_Enable", 1);
} private void OnRenderImage(RenderTexture src, RenderTexture dest) {
if (enableWave) {
Matrix4x4 frustumCorners = GetFrustumCornersRay();
material.SetMatrix("_FrustumCornersRay", frustumCorners);
material.SetColor("_WaveColor", waveColor);
waveCenter = target.position;
material.SetVector("_WaveCenter", waveCenter);
material.SetFloat("_WaveLineWidth", waveLineWidth);
material.SetFloat("_WaveGap", waveGap);
material.SetFloat("_WaveSpeed", waveSpeed);
material.SetFloat("_WaveCastTime", waveCastTime);
material.SetFloat("_WaveNum", waveNum);
material.SetFloat("_InitWaveDist", initWaveDist);
material.SetFloat("_MaxWaveDist", maxWaveDist);
Graphics.Blit(src, dest, material);
} else {
Graphics.Blit(src, dest);
}
} private Matrix4x4 GetFrustumCornersRay() { // 获取插值射线向量(由相机指向**面上四个角点的向量除以near后的坐标)
Matrix4x4 frustumCorners = Matrix4x4.identity;
float fov = cam.fieldOfView;
float near = cam.nearClipPlane;
float aspect = cam.aspect;
float halfHeight = near * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad);
Vector3 toRight = cam.transform.right * halfHeight * aspect; // 指向右方的向量
Vector3 toTop = cam.transform.up * halfHeight; // 指向上方的向量
Vector3 toForward = cam.transform.forward * near; // 指向前方的向量
Vector3 bottomLeft = (toForward - toTop - toRight) / near; // 指向左下角的射线
Vector3 bottomRight = (toForward + toRight - toTop) / near; // 指向右下角的射线
Vector3 topRight = (toForward + toRight + toTop) / near; // 指向右上角的射线
Vector3 topLeft = (toForward + toTop - toRight) / near; // 指向左上角的射线
frustumCorners.SetRow(0, bottomLeft);
frustumCorners.SetRow(1, bottomRight);
frustumCorners.SetRow(2, topRight);
frustumCorners.SetRow(3, topLeft);
return frustumCorners;
}
}

​ LaserRadar.shader

Shader "MyShader/LaserRadar" { // 雷达波特效
Properties{
_MainTex("Base (RGB)", 2D) = "white" {} // 主纹理
_Enable("Enable", Int) = 0 // 是否启动雷达波特效
_WaveColor("WaveColor", Color) = (1, 0, 0, 1) // 雷达波的颜色
_WaveLineWidth("WaveLineWidth", Float) = 0.49 // 雷达波纹条的宽度
_WaveCenter("WaveCenter", Vector) = (0, 0, 0, 0) // 雷达的波中心
_WaveGap("WaveGap", Float) = 2 // 雷达波的间距
_WaveSpeed("WaveSpeed", Float) = 1 // 雷达波的传播速度
_WaveCastTime("WaveCastTime", Float) = 10 // 雷达波发射的时间周期
_WaveNum("WaveNum", Int) = 5 // 每个发射周期的波纹数
_InitWaveDist("InitWaveDist", Float) = 3 // 雷达波初始的距离
_MaxWaveDist("MaxWaveDist", Float) = 30 // 雷达波传播的最远距离
} SubShader{
Pass {
// 深度测试始终通过, 关闭深度写入
ZTest Always ZWrite Off CGPROGRAM #include "UnityCG.cginc" #pragma vertex vert
#pragma fragment frag sampler2D _MainTex; // 主纹理
sampler2D _CameraDepthTexture; // 深度纹理
float4x4 _FrustumCornersRay; // 视锥体四角射线向量(由相机指向**面上四个角点的向量除以near后的坐标)
int _Enable; // 是否启动雷达波特效
fixed4 _WaveColor; // 雷达波的颜色
float _WaveLineWidth; // 雷达波纹的宽度
float4 _WaveCenter; // 雷达波的中心
float _WaveGap; // 雷达波的间距
float _WaveSpeed; // 雷达波的速度
float _WaveCastTime; // 雷达波发射的时间周期
int _WaveNum; // 每个发射周期的波纹数
float _InitWaveDist; // 雷达波初始的距离
float _MaxWaveDist; // 雷达波传播的最远距离 struct v2f {
float4 pos : SV_POSITION; // 裁剪空间顶点坐标
half2 uv : TEXCOORD0; // 纹理uv坐标
float4 interpolatedRay : TEXCOORD1; // 插值射线向量(由相机指向**面上点的向量除以near后的坐标)
}; float4 getInterpolatedRay(half2 uv) { // 获取插值射线向量(由相机指向**面上四个角点的向量除以near后的坐标)
int index = 0;
if (uv.x < 0.5 && uv.y < 0.5) {
index = 0;
} else if (uv.x > 0.5 && uv.y < 0.5) {
index = 1;
} else if (uv.x > 0.5 && uv.y > 0.5) {
index = 2;
} else {
index = 3;
}
return _FrustumCornersRay[index];
} v2f vert(appdata_img v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex); // 计算裁剪坐标系中顶点坐标, 等价于: mul(unity_MatrixMVP, v.vertex)
o.uv = v.texcoord;
o.interpolatedRay = getInterpolatedRay(v.texcoord); // 获取插值射线向量(由相机指向**面上四个角点的向量除以near后的坐标)
return o;
} fixed4 frag(v2f i) : SV_Target{
if (_Enable == 0) {
return tex2D(_MainTex, i.uv);
}
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv); // 非线性的深度, tex2D(_CameraDepthTexture, i.uv).r
float linearDepth = LinearEyeDepth(depth); // 线性的深度
float factor = 1;
if (linearDepth < _ProjectionParams.z - 1) { // _ProjectionParams = (1, near, far, 1 / far)
float3 worldPos = _WorldSpaceCameraPos + linearDepth * i.interpolatedRay.xyz; // 顶点世界坐标
float len = length(worldPos - _WaveCenter.xyz); // 当前顶点距离雷达波中心的距离
if (len < _InitWaveDist || len > _MaxWaveDist) {
return tex2D(_MainTex, i.uv);
}
float time = fmod(_Time.y, _WaveCastTime); // 当前发射周期中, 雷达波传播的时间, _Time = (t/20, t, t*2, t*3)
float dist = _InitWaveDist + _WaveSpeed * time; // 当前发射周期中, 雷达波传播的距离
if (len > dist + _WaveLineWidth || len < dist - _WaveGap * (_WaveNum - 1) - _WaveLineWidth) {
return tex2D(_MainTex, i.uv);
}
float mod = fmod(abs(dist - len), _WaveGap); // 当前顶点距离最*的内环波纹的距离
float rate = min(min(mod, _WaveGap - mod), _WaveLineWidth) / _WaveLineWidth; // 当前顶点处在波纹范围外的比率
factor = smoothstep(0, 1, rate); // 当前顶点被采样为目标纹理颜色的比率因子
}
fixed4 tex = tex2D(_MainTex, i.uv);
fixed4 color = lerp(_WaveColor, tex, factor);
return color;
} ENDCG
}
} FallBack off
}

​ 运行效果如下:

3 间距递增的雷达波特效

3.1 雷达波扩散原理

​ 对于屏幕上任意一点,假设其对应的世界坐标为 worldPos,其线性深度值为 lineDepth(通过 LinearEyeDepth 函数获取),如果 lineDepth >= far - 1(far 通过 _ProjectionParams.z 获取),说明该点落在天空中,不参与雷达波计算,因此本文仅考虑 lineDepth < far - 1 的像素点雷达波计算。

​ 假设雷达波中心坐标为 waveCenter,波纹间距为 waveGap,波纹宽度为 waveLineWidth,雷达波的传播速度和传播时间分别为 waveSpeed、waveTime,雷达波的发射周期为 waveCastTime,雷达波发射的初始距离为 initWaveDist,当前顶点被采样为目标纹理颜色的比率因子为 factor,波纹颜色为 waveColor,当前顶点在叠加雷达波前后的颜色分别为 tex、finalColor,则 finalColor 的计算如下:

float len = length(worldPos - waveCenter); // 当前顶点距离雷达波中心的距离
float time = fmod(waveTime, waveCastTime); // 当前发射周期中, 雷达波传播的时间
float waveGap = initWaveDist + waveSpeed * time; // 当前发射周期中, 雷达波传播的距离
float mod = fmod(len, waveGap); // 当前顶点距离最*的内环波纹的距离
float rate = min(min(mod, waveGap - mod), waveLineWidth) / waveLineWidth; // 当前顶点处在波纹范围外的比率(值域: [0,1])
float factor = smoothstep(0, 1, rate); // 当前顶点被采样为目标纹理颜色的比率因子(值域: [0,1])
fixed4 finalColor = lerp(waveColor, tex, factor); // 当前顶点叠加雷达波后的颜

3.2 点选设置雷达波中心

​ LaserRadar.cs

using UnityEngine;

[RequireComponent(typeof(Camera))] // 需要相机组件
public class LaserRadar1 : MonoBehaviour { public Color waveColor = Color.red; // 雷达波的颜色
[Range(0.1f, 0.49f)]
public float waveLineWidth = 0.49f; // 雷达波纹的宽度
[Range(0.5f, 10f)]
public float waveSpeed = 1f; // 雷达波传播的速度
[Range(3, 10)]
public float waveCastTime = 5; // 雷达波发射的时间周期
[Range(0.1f, 20)]
public float initWaveDist = 3; // 雷达波初始的距离
[Range(10, 200)]
public float maxWaveDist = 30f; // 雷达波传播的最远距离 private bool enableWave = false; // 是否开启雷达波特效
private Vector4 waveCenter; // 雷达波中心
private float waveTime = 0; // 雷达波开始时间
private Camera cam; // 相机
private Material material = null; // 材质 private void Awake() {
cam = GetComponent<Camera>();
material = new Material(Shader.Find("MyShader/LaserRadar"));
material.hideFlags = HideFlags.DontSave;
} private void OnEnable() {
cam.depthTextureMode |= DepthTextureMode.Depth;
} private void Update() {
if (Input.GetMouseButtonDown(0)) {
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hitInfo;
if (Physics.Raycast(ray, out hitInfo)) {
enableWave = true;
material.SetInt("_Enable", 1);
waveCenter = hitInfo.point;
material.SetVector("_WaveCenter", waveCenter);
waveTime = 0;
}
}
if (enableWave) {
waveTime += Time.deltaTime;
if (waveTime > waveCastTime) {
enableWave = false;
material.SetInt("_Enable", 0);
}
}
} private void OnRenderImage(RenderTexture src, RenderTexture dest) {
if (enableWave) {
Matrix4x4 frustumCorners = GetFrustumCornersRay();
material.SetMatrix("_FrustumCornersRay", frustumCorners);
material.SetColor("_WaveColor", waveColor);
material.SetFloat("_WaveLineWidth", waveLineWidth);
material.SetFloat("_WaveSpeed", waveSpeed);
material.SetFloat("_WaveTime", waveTime);
material.SetFloat("_WaveCastTime", waveCastTime);
material.SetFloat("_InitWaveDist", initWaveDist);
material.SetFloat("_MaxWaveDist", maxWaveDist);
Graphics.Blit(src, dest, material);
} else {
Graphics.Blit(src, dest);
}
} private Matrix4x4 GetFrustumCornersRay() { // 获取插值射线向量(由相机指向**面上四个角点的向量除以near后的坐标)
Matrix4x4 frustumCorners = Matrix4x4.identity;
float fov = cam.fieldOfView;
float near = cam.nearClipPlane;
float aspect = cam.aspect;
float halfHeight = near * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad);
Vector3 toRight = cam.transform.right * halfHeight * aspect; // 指向右方的向量
Vector3 toTop = cam.transform.up * halfHeight; // 指向上方的向量
Vector3 toForward = cam.transform.forward * near; // 指向前方的向量
Vector3 bottomLeft = (toForward - toTop - toRight) / near; // 指向左下角的射线
Vector3 bottomRight = (toForward + toRight - toTop) / near; // 指向右下角的射线
Vector3 topRight = (toForward + toRight + toTop) / near; // 指向右上角的射线
Vector3 topLeft = (toForward + toTop - toRight) / near; // 指向左上角的射线
frustumCorners.SetRow(0, bottomLeft);
frustumCorners.SetRow(1, bottomRight);
frustumCorners.SetRow(2, topRight);
frustumCorners.SetRow(3, topLeft);
return frustumCorners;
}
}

​ LaserRadar.shader

Shader "MyShader/LaserRadar" { // 雷达波特效
Properties{
_MainTex("Base (RGB)", 2D) = "white" {} // 主纹理
_Enable("Enable", Int) = 0 // 是否启动雷达波特效
_WaveColor("WaveColor", Color) = (1, 0, 0, 1) // 雷达波的颜色
_WaveLineWidth("WaveLineWidth", Float) = 0.3 // 雷达波纹的宽度
_WaveCenter("WaveCenter", Vector) = (0, 0, 0, 0) // 雷达的波中心
_WaveSpeed("WaveSpeed", Float) = 1 // 雷达波的传播速度
_WaveTime("WaveTime", Float) = 0 // 雷达波传播的时间
_WaveCastTime("WaveCastTime", Float) = 5 // 雷达波发射的时间周期
_InitWaveDist("InitWaveDist", Float) = 3 // 雷达波初始的距离
_MaxWaveDist("MaxWaveDist", Float) = 30 // 雷达波传播的最远距离
} SubShader{
Pass {
// 深度测试始终通过, 关闭深度写入
ZTest Always ZWrite Off CGPROGRAM #include "UnityCG.cginc" #pragma vertex vert
#pragma fragment frag sampler2D _MainTex; // 主纹理
sampler2D _CameraDepthTexture; // 深度纹理
float4x4 _FrustumCornersRay; // 视锥体四角射线向量(由相机指向**面上四个角点的向量除以near后的坐标)
int _Enable; // 是否启动雷达波特效
fixed4 _WaveColor; // 雷达波的颜色
float _WaveLineWidth; // 雷达波纹的宽度
float4 _WaveCenter; // 雷达波的中心
float _WaveSpeed; // 雷达波的速度
float _WaveTime; // 雷达波传播的时间
float _WaveCastTime; // 雷达波发射的时间周期
float _InitWaveDist; // 雷达波初始的距离
float _MaxWaveDist; // 雷达波传播的最远距离 struct v2f {
float4 pos : SV_POSITION; // 裁剪空间顶点坐标
half2 uv : TEXCOORD0; // 纹理uv坐标
float4 interpolatedRay : TEXCOORD1; // 插值射线向量(由相机指向**面上点的向量除以near后的坐标)
}; float4 getInterpolatedRay(half2 uv) { // 获取插值射线向量(由相机指向**面上四个角点的向量除以near后的坐标)
int index = 0;
if (uv.x < 0.5 && uv.y < 0.5) {
index = 0;
} else if (uv.x > 0.5 && uv.y < 0.5) {
index = 1;
} else if (uv.x > 0.5 && uv.y > 0.5) {
index = 2;
} else {
index = 3;
}
return _FrustumCornersRay[index];
} v2f vert(appdata_img v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex); // 计算裁剪坐标系中顶点坐标, 等价于: mul(unity_MatrixMVP, v.vertex)
o.uv = v.texcoord;
o.interpolatedRay = getInterpolatedRay(v.texcoord); // 获取插值射线向量(由相机指向**面上四个角点的向量除以near后的坐标)
return o;
} fixed4 frag(v2f i) : SV_Target {
if (_Enable == 0) {
return tex2D(_MainTex, i.uv);
}
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv); // 非线性的深度, tex2D(_CameraDepthTexture, i.uv).r
float linearDepth = LinearEyeDepth(depth); // 线性的深度
float factor = 1;
if (linearDepth < _ProjectionParams.z - 1) { // _ProjectionParams = (1, near, far, 1 / far)
float3 worldPos = _WorldSpaceCameraPos + linearDepth * i.interpolatedRay.xyz; // 顶点世界坐标
float len = length(worldPos - _WaveCenter.xyz); // 当前顶点距离雷达波中心的距离
if (len < _InitWaveDist || len > _MaxWaveDist) {
return tex2D(_MainTex, i.uv);
}
float time = fmod(_WaveTime, _WaveCastTime); // 当前发射周期中, 雷达波传播的时间
float waveGap = _InitWaveDist + _WaveSpeed * time; // 当前发射周期中, 雷达波传播的距离
float mod = fmod(len, waveGap); // 当前顶点距离最*的内环波纹的距离
float rate = min(min(mod, waveGap - mod), _WaveLineWidth) / _WaveLineWidth; // 当前顶点处在波纹范围外的比率
factor = smoothstep(0, 1, rate); // 当前顶点被采样为目标纹理颜色的比率因子
}
fixed4 tex = tex2D(_MainTex, i.uv);
fixed4 color = lerp(_WaveColor, tex, factor);
return color;
} ENDCG
}
} FallBack off
}

​ 运行效果如下:

3.3 雷达波中心跟随物体运动

​ LaserRadar.cs

using UnityEngine;

[RequireComponent(typeof(Camera))] // 需要相机组件
public class LaserRadar : MonoBehaviour {
public Color waveColor = Color.red; // 雷达波的颜色
[Range(0.1f, 0.49f)]
public float waveLineWidth = 0.49f; // 雷达波纹的宽度
[Range(0.5f, 10f)]
public float waveSpeed = 1f; // 雷达波传播的速度
[Range(3, 10)]
public float waveCastTime = 5; // 雷达波发射的时间周期
[Range(0.1f, 20)]
public float initWaveDist = 3; // 雷达波初始的距离
[Range(10, 200)]
public float maxWaveDist = 30f; // 雷达波传播的最远距离 private bool enableWave = false; // 是否开启雷达波特效
private Vector4 waveCenter; // 雷达波中心
private Camera cam; // 相机
private Material material = null; // 材质
private Transform target; // 发射雷达波的目标物体 private void Awake() {
cam = GetComponent<Camera>();
material = new Material(Shader.Find("MyShader/LaserRadar"));
material.hideFlags = HideFlags.DontSave;
target = GameObject.Find("Car").transform;
} private void OnEnable() {
cam.depthTextureMode |= DepthTextureMode.Depth;
enableWave = true;
material.SetInt("_Enable", 1);
} private void OnRenderImage(RenderTexture src, RenderTexture dest) {
if (enableWave) {
Matrix4x4 frustumCorners = GetFrustumCornersRay();
material.SetMatrix("_FrustumCornersRay", frustumCorners);
material.SetColor("_WaveColor", waveColor);
waveCenter = target.position;
material.SetVector("_WaveCenter", waveCenter);
material.SetFloat("_WaveLineWidth", waveLineWidth);
material.SetFloat("_WaveSpeed", waveSpeed);
material.SetFloat("_WaveCastTime", waveCastTime);
material.SetFloat("_InitWaveDist", initWaveDist);
material.SetFloat("_MaxWaveDist", maxWaveDist);
Graphics.Blit(src, dest, material);
} else {
Graphics.Blit(src, dest);
}
} private Matrix4x4 GetFrustumCornersRay() { // 获取插值射线向量(由相机指向**面上四个角点的向量除以near后的坐标)
Matrix4x4 frustumCorners = Matrix4x4.identity;
float fov = cam.fieldOfView;
float near = cam.nearClipPlane;
float aspect = cam.aspect;
float halfHeight = near * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad);
Vector3 toRight = cam.transform.right * halfHeight * aspect; // 指向右方的向量
Vector3 toTop = cam.transform.up * halfHeight; // 指向上方的向量
Vector3 toForward = cam.transform.forward * near; // 指向前方的向量
Vector3 bottomLeft = (toForward - toTop - toRight) / near; // 指向左下角的射线
Vector3 bottomRight = (toForward + toRight - toTop) / near; // 指向右下角的射线
Vector3 topRight = (toForward + toRight + toTop) / near; // 指向右上角的射线
Vector3 topLeft = (toForward + toTop - toRight) / near; // 指向左上角的射线
frustumCorners.SetRow(0, bottomLeft);
frustumCorners.SetRow(1, bottomRight);
frustumCorners.SetRow(2, topRight);
frustumCorners.SetRow(3, topLeft);
return frustumCorners;
}
}

​ LaserRadar.shader

Shader "MyShader/LaserRadar" { // 雷达波特效
Properties{
_MainTex("Base (RGB)", 2D) = "white" {} // 主纹理
_Enable("Enable", Int) = 0 // 是否启动雷达波特效
_WaveColor("WaveColor", Color) = (1, 0, 0, 1) // 雷达波的颜色
_WaveLineWidth("WaveLineWidth", Float) = 0.49 // 雷达波纹的宽度
_WaveCenter("WaveCenter", Vector) = (0, 0, 0, 0) // 雷达的波中心
_WaveSpeed("WaveSpeed", Float) = 1 // 雷达波的传播速度
_WaveCastTime("WaveCastTime", Float) = 5 // 雷达波发射的时间周期
_InitWaveDist("InitWaveDist", Float) = 3 // 雷达波初始的距离
_MaxWaveDist("MaxWaveDist", Float) = 30 // 雷达波传播的最远距离
} SubShader{
Pass {
// 深度测试始终通过, 关闭深度写入
ZTest Always ZWrite Off CGPROGRAM #include "UnityCG.cginc" #pragma vertex vert
#pragma fragment frag sampler2D _MainTex; // 主纹理
sampler2D _CameraDepthTexture; // 深度纹理
float4x4 _FrustumCornersRay; // 视锥体四角射线向量(由相机指向**面上四个角点的向量除以near后的坐标)
int _Enable; // 是否启动雷达波特效
fixed4 _WaveColor; // 雷达波的颜色
float _WaveLineWidth; // 雷达波纹的宽度
float4 _WaveCenter; // 雷达波的中心
float _WaveSpeed; // 雷达波的速度
float _WaveCastTime; // 雷达波发射的时间周期
float _InitWaveDist; // 雷达波初始的距离
float _MaxWaveDist; // 雷达波传播的最远距离 struct v2f {
float4 pos : SV_POSITION; // 裁剪空间顶点坐标
half2 uv : TEXCOORD0; // 纹理uv坐标
float4 interpolatedRay : TEXCOORD1; // 插值射线向量(由相机指向**面上点的向量除以near后的坐标)
}; float4 getInterpolatedRay(half2 uv) { // 获取插值射线向量(由相机指向**面上四个角点的向量除以near后的坐标)
int index = 0;
if (uv.x < 0.5 && uv.y < 0.5) {
index = 0;
}
else if (uv.x > 0.5 && uv.y < 0.5) {
index = 1;
} else if (uv.x > 0.5 && uv.y > 0.5) {
index = 2;
} else {
index = 3;
}
return _FrustumCornersRay[index];
} v2f vert(appdata_img v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex); // 计算裁剪坐标系中顶点坐标, 等价于: mul(unity_MatrixMVP, v.vertex)
o.uv = v.texcoord;
o.interpolatedRay = getInterpolatedRay(v.texcoord); // 获取插值射线向量(由相机指向**面上四个角点的向量除以near后的坐标)
return o;
} fixed4 frag(v2f i) : SV_Target {
if (_Enable == 0) {
return tex2D(_MainTex, i.uv);
}
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv); // 非线性的深度, tex2D(_CameraDepthTexture, i.uv).r
float linearDepth = LinearEyeDepth(depth); // 线性的深度
float factor = 1;
if (linearDepth < _ProjectionParams.z - 1) { // _ProjectionParams = (1, near, far, 1 / far)
float3 worldPos = _WorldSpaceCameraPos + linearDepth * i.interpolatedRay.xyz; // 顶点世界坐标
float len = length(worldPos - _WaveCenter.xyz); // 当前顶点距离雷达波中心的距离
if (len < _InitWaveDist || len > _MaxWaveDist) {
return tex2D(_MainTex, i.uv);
}
float time = fmod(_Time.y, _WaveCastTime); // 当前发射周期中, 雷达波传播的时间, _Time = (t/20, t, t*2, t*3)
float waveGap = _InitWaveDist + _WaveSpeed * time; // 当前发射周期中, 雷达波传播的距离
float mod = fmod(len, waveGap); // 当前顶点距离最*的内环波纹的距离
float rate = min(min(mod, waveGap - mod), _WaveLineWidth) / _WaveLineWidth; // 当前顶点处在波纹范围外的比率
factor = smoothstep(0, 1, rate); // 当前顶点被采样为目标纹理颜色的比率因子
}
fixed4 tex = tex2D(_MainTex, i.uv);
fixed4 color = lerp(_WaveColor, tex, factor);
return color;
} ENDCG
}
} FallBack off
}

​ 运行效果如下:

​ 声明:本文转自【Unity3D】激光雷达特效

【Unity3D】激光雷达特效的更多相关文章

  1. Unity3d粒子特效:制作火焰效果

    效果 分析 真实的火焰效果,通常包括:火.火光.火星等组成部分,而火焰对周围环境的烘焙,可以通过灯光实现,如点光源. 针对火焰组成部分,我们可以创建对应的粒子系统组件,实现相应的效果,如下图所示: 1 ...

  2. 【Unity3D】基于粒子系统实现烟花特效

    1 需求实现 ​ 粒子系统ParticleSystem 中介绍了粒子初始化.粒子发射.发射器形状.渲染器.碰撞.子发射器.拖尾等粒子系统的基本用法,本节将基于粒子系统实现烟花特效. ​ 实现需求如下( ...

  3. Unity塔防游戏开发

    Unity3D塔防开发流程 配置环境及场景搭建编程语言:C#,略懂些许设计模式,如果不了解设计模式,BUG More开发工具:Unity3D编辑器.Visual Studio编译器开发建议:了解Uni ...

  4. Unity3D特效-场景淡入淡出

    最近公司开始搞Unity3D..整个游戏..特效需求还是比较多的.关于UI部分的特效淡入淡出.看网上用的方法都是用个黑东东遮挡然后设置alpha这么搞....本大神感觉非常的low.而且很渣.故奋笔疾 ...

  5. unity3d 游戏插件 溶解特效插件 - Dissolve Shader

    unity3d 游戏插件 溶解特效插件 - Dissolve Shader   链接: https://pan.baidu.com/s/1hr7w39U 密码: 3ed2

  6. Unity3D之挥动武器产生的剑痕特效

    网维教程网 观看很多其它教程 眼下已知3种方法能够做这样的剑痕特效 1.尾随特效 2.程序实现动态面来处理剑痕动画. 3.美术实现剑痕动画,直接坐在模型动画里面 (由于我不会美术所以这个忽略 嘿嘿) ...

  7. [Unity3D]Unity3D游戏开发之刀光剑影特效的实现

    大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是blog.csdn.net/qinyuanpei. 我实在不明确有的人为什么不喜欢武侠/仙侠类游戏,也许是因为武侠/仙侠类游戏身上被永远烙上的国 ...

  8. Unity3D学习笔记——组件之Effects(效果/特效)——Particle System(粒子系统)

    Effects:效果/特效. Particle System:粒子系统.可用于创建烟雾.气流.火焰.涟漪等效果. 在Unity3D 3.5版本之后退出了新的shuriken粒子系统:   添加组件之后 ...

  9. WPF特效-绘制实时2D激光雷达图

    原文:WPF特效-绘制实时2D激光雷达图 接前两篇: https://blog.csdn.net/u013224722/article/details/80738619 https://blog.cs ...

  10. unity3d笔记:控制特效的播放速度

           一般在游戏中,主角或者怪物会受到减速效果,或者攻击速度减慢等类似的状态.本身动作减速的同时,衔接在角色上的特效也需要改变相应的播放速度.一般特效有三个游戏组件:   关键点就是改变Ani ...

随机推荐

  1. 2022-12-28:有n个黑白棋子,它们的一面是黑色,一面是白色, 它们被排成一行,位置0~n-1上。一开始所有的棋子都是黑色向上, 一共有q次操作,每次操作将位置标号在区间[L,R]内的所有棋子翻

    2022-12-28:有n个黑白棋子,它们的一面是黑色,一面是白色, 它们被排成一行,位置0~n-1上.一开始所有的棋子都是黑色向上, 一共有q次操作,每次操作将位置标号在区间[L,R]内的所有棋子翻 ...

  2. 2020-08-13:Hadoop生态圈的了解?

    福哥答案2020-08-13: 该项目包括以下模块:1.Common(公共工具)支持其他Hadoop模块的公共工具. 2.HDFS(Hadoop分布式文件系统)提供对应用程序数据的高吞吐量访问的分布式 ...

  3. 2021-12-03:石子游戏 IV。Alice 和 Bob 两个人轮流玩一个游戏,Alice 先手。 一开始,有 n 个石子堆在一起。每个人轮流操作,正在操作的玩家可以从石子堆里拿走 任意 非零 平

    2021-12-03:石子游戏 IV.Alice 和 Bob 两个人轮流玩一个游戏,Alice 先手. 一开始,有 n 个石子堆在一起.每个人轮流操作,正在操作的玩家可以从石子堆里拿走 任意 非零 平 ...

  4. 2021-08-30:给定两个字符串str1和str2,在str1中寻找一个最短子串,能包含str2的所有字符,字符顺序无所谓,str1的这个最短子串也可以包含多余的字符。返回这个最短包含子串。

    2021-08-30:给定两个字符串str1和str2,在str1中寻找一个最短子串,能包含str2的所有字符,字符顺序无所谓,str1的这个最短子串也可以包含多余的字符.返回这个最短包含子串. 福大 ...

  5. ubuntu为navicat创建快捷方式

    一.前言 最近在ubuntu上安装了navicat,但是发现不能将其固定在启动栏阿!!!不能每次都用terminal运行吧!于是在上网查,有一说一,网上很多文章写的方法都不能实现(不排除是ubuntu ...

  6. SVM主体思路和代码实现

    之前学习的KNN算法属于直接将所有的训练图片数据化,根据图片的像素值进行判断,最简单的NN算法是用与待判断图片的差距最小(距离最近)的那张图片的类别当做此图片的类别,我们不难看到,1NN算法的正确性很 ...

  7. javascript5 定时器功能

    定时器功能: 定时器功能是window对象方法,涉及到 定时器和延时器,具体 看代码 定时器 timer=setInterval(function (){},300) 清除定时器: clearInte ...

  8. 一分钟学一个 Linux 命令 - cd

    前言 大家好,我是 god23bin.欢迎来到这个系列,每天只需一分钟,记住一个 Linux 命令不成问题.今天让我们从 cd 命令开始,掌握在 Linux 系统中切换目录的技巧. 什么是 cd 命令 ...

  9. MySQL-class

    1.数据库和SQL概念 数据库(Database)是按照数据结构来组织.存储和管理数据的仓库,它的产生距今已有六十多年.随着信息技术和市场的发展,数据库变的无处不在:它在电子商务.银行系统等众多领域都 ...

  10. C#里的var和dynamic区别到底是什么,你真的搞懂了嘛

    前言 这个var和dynamic都是不确定的初始化类型,但是这两个本质上的不同.不同在哪儿呢?var编译阶段确定类型,dynamic运行时阶段确定类型.这种说法对不对呢?本篇看下 概括 以下详细叙述下 ...