【Unity3D】激光雷达特效

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)。

cpp 复制代码
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 的计算如下:

cpp 复制代码
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

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

cpp 复制代码
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

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

cpp 复制代码
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 的计算如下:

cpp 复制代码
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

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

cpp 复制代码
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

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

cpp 复制代码
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】激光雷达特效

相关推荐
lin zaixi()7 天前
手把手教你写Unity3D飞机大战(2)天空盒布置
unity3d
Thomas_YXQ12 天前
Unity3D中管理Shader效果详解
开发语言·游戏·unity·unity3d·游戏开发
羊羊203513 天前
线性代数:Matrix2x2和Matrix3x3
线性代数·数学建模·unity3d
天人合一peng18 天前
Unity hub登录时一直无法进入license
unity3d
天涯学馆20 天前
Three.js灯光阴影与动画交互
前端·unity3d·three.js
Cool-浩21 天前
Unity3D 开发技巧
开发语言·前端·unity·c#·unity3d·实用技巧·unity开发教程
Cool-浩23 天前
Unity Vision Pro 保姆级开发教程-PolySpatial VisionOS Samples 示例场景
unity·游戏引擎·unity3d·案例·polyspatial·applevision pro·vision pro教程
Thomas_YXQ1 个月前
Unity3D中Excel表格的数据处理模块详解
linux·windows·算法·excel·unity3d·游戏开发
Thomas_YXQ1 个月前
Unity3D ScrollView 滚动视图组件详解及代码实现
开发语言·游戏·unity·架构·unity3d
指尖上的生活1 个月前
Unity使用jslib构建失败
unity3d·webgl