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

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() {
        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 topLeft = cam.transform.forward * near + toTop - toRight;
        topLeft /= near;
        Vector3 topRight = cam.transform.forward * near + toRight + toTop;
        topRight /= near;
        Vector3 bottomLeft = cam.transform.forward * near - toTop - toRight;
        bottomLeft /= near;
        Vector3 bottomRight = cam.transform.forward * near + toRight - toTop;
        bottomRight /= 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{ // v2f_img为内置结构体, 里面只包含pos和uv
				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() {
        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 topLeft = cam.transform.forward * near + toTop - toRight;
        topLeft /= near;
        Vector3 topRight = cam.transform.forward * near + toRight + toTop;
        topRight /= near;
        Vector3 bottomLeft = cam.transform.forward * near - toTop - toRight;
        bottomLeft /= near;
        Vector3 bottomRight = cam.transform.forward * near + toRight - toTop;
        bottomRight /= 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{ // v2f_img为内置结构体, 里面只包含pos和uv
				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() {
        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 topLeft = cam.transform.forward * near + toTop - toRight;
        topLeft /= near;
        Vector3 topRight = cam.transform.forward * near + toRight + toTop;
        topRight /= near;
        Vector3 bottomLeft = cam.transform.forward * near - toTop - toRight;
        bottomLeft /= near;
        Vector3 bottomRight = cam.transform.forward * near + toRight - toTop;
        bottomRight /= 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 { // v2f_img为内置结构体, 里面只包含pos和uv
				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() {
        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 topLeft = cam.transform.forward * near + toTop - toRight;
        topLeft /= near;
        Vector3 topRight = cam.transform.forward * near + toRight + toTop;
        topRight /= near;
        Vector3 bottomLeft = cam.transform.forward * near - toTop - toRight;
        bottomLeft /= near;
        Vector3 bottomRight = cam.transform.forward * near + toRight - toTop;
        bottomRight /= 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 { // v2f_img为内置结构体, 里面只包含pos和uv
				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
}

        运行效果如下:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/29830.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于51单片机的智能火灾报警系统温度烟雾光

wx供重浩&#xff1a;创享日记 对话框发送&#xff1a;火灾报警 获取完整源码源文件电路图仿真文件论文报告等 功能简介 51单片机MQ-2烟雾传感ADC0832模数转换芯片DS18B20温度传感器数码管显示按键模块声光报警模块 具体功能&#xff1a; 1、实时监测及显示温度值和烟雾浓度…

管理类联考——英语二——技巧篇——写作——B节——议论文——必备替换句型

议论文必备替换句型 (一&#xff09;表示很明显/众所周知的句型 It is obvious thatIt is clear thatIt is apparent thatIt is evident thatlt is self-evident thatIt is manifest thatIt is well-knownIt is known to all thatIt is widely-accepted thatIt is crystal-cl…

蓝牙客户端QBluetoothSocket的使用——Qt For Android

了解蓝牙 经典蓝牙和低功耗蓝牙差异 经典蓝牙&#xff08;Bluetooth Classic&#xff09;&#xff1a;分为基本速率/增强数据速率(BR/EDR)&#xff0c; 79个信道&#xff0c;在2.4GHz的(ISM)频段。支持点对点设备通信&#xff0c;主要用于实现无线音频流传输&#xff0c;已成…

Ceph:关于Ceph 集群管理的一些笔记

写在前面 准备考试&#xff0c;整理ceph 相关笔记博文内容涉及&#xff0c;Ceph 管理工具 cephadm&#xff0c;ceph 编排器&#xff0c;Ceph CLI 和 Dashboard GUI 介绍理解不足小伙伴帮忙指正 对每个人而言&#xff0c;真正的职责只有一个&#xff1a;找到自我。然后在心中坚守…

大数据分析平台释疑专用帖第二弹

不管是想要快速了解BI大数据分析平台&#xff0c;还是想要了解BI和自己的需求匹配度&#xff0c;都可关注我们的释疑专用贴。 1、可以分析直播数据吗&#xff1f; 严格来说&#xff0c;只要能够提供数据&#xff0c;就可以做数据可视化分析&#xff0c;直播数据也同理。 如果…

solr快速上手:整合SolrJ实现客户端操作(九)

0. 引言 我们前面学习了solr的服务端基础操作&#xff0c;实际项目中我们还需要在客户端调用solr&#xff0c;就像调用数据库一样&#xff0c;我们可以基于solrJ来实现对solr的客户端操作 1. SolrJ简介 SolrJ 是 Solr官方提供的 Java 客户端库&#xff0c;主要用于与 Solr 服…

Python 请求分页

文章目录 什么是 Python 中的分页带有下一个按钮的 Python 分页没有下一个按钮的 Python 分页无限滚动的 Python 分页带有加载更多按钮的分页 在本文中&#xff0c;我们将了解分页以及如何克服 Python 中与分页相关的问题。 读完本文后&#xff0c;我们将能够了解 Python 分页以…

经典目标检测YOLO系列(1)YOLO-V1算法及其在VOC2007数据集上的应用

经典目标检测YOLO系列(1)YOLO-V1算法及其在VOC2007数据集上的应用 1 YOLO-V1的简述 1.1 目标检测概述 ​ 目标检测有非常广泛的应用&#xff0c; 例如&#xff1a;在安防监控、手机支付中的人脸检测&#xff1b;在智慧交通&#xff0c;自动驾驶中的车辆检测&#xff1b;在智…

Parallel Desktop中按照的centos在切换root用户时,密码正确,但一直切换不成功,显示su: Authentication failure

目录 一、出现问题二、分析问题三、解决问题四、参考资料 一、出现问题 我的密码明明是输入正确的&#xff0c;但又一直给我报下面的错误 二、分析问题 我怀疑是我密码记错了&#xff0c;所以我点击Log Out&#xff0c;重新去输入了一下密码&#xff0c;发现是正确的我确认…

Build your own unconditional confidence

不要活在既定的社会价值体系中 人类的偏好大多数时候都是愚昧的 I play whatever gods give me 情绪价值稳定 解决问题的能力 Dont label yourself 真正的强者不会吝啬对他人的赞美 敬畏自然&#xff0c;敬畏未知事物 核心是你对这个事情是否感兴趣&#xff0c;觉得有价…

PHP 使用html创建PDF并设置水印

使用TCPDF库给PDF文件加水印&#xff0c;需要注意无法直接使用文本&#xff0c;需要创建水印图片后&#xff0c;通过图片来设置水印效果。 目录 创建PDF 创建合同模板 创建pdf文件 简单创建 设置文档信息 去掉默认页头脚 设置间距 设置字体支持中文 设置图片比例因子 …

代理ip数据采集的优缺点

随着互联网时代的到来&#xff0c;数据已经成为企业发展和决策的关键。但是&#xff0c;不同的网站它对于数据访问的限制和反爬虫措施却是给企业的数据采集带来了挑战。针对这一问题&#xff0c;代理IP数据采集技术应运而生。但是使用代理ip来进行数据采集也有优缺点。 一、代理…

【微信小程序】wxml、wxss、js、json文件介绍

&#x1f609;博主&#xff1a;初映CY的前说(前端领域) ,&#x1f4d2;本文核心&#xff1a;微信小程序的入门介绍 【前言】书接上回&#xff0c;我们知道了一个小程序的构成结构&#xff0c;接下来我们来进一步学习小程序的目录结构中的.wxml、.wxss、.js、.json。 目录 ⭐ 一…

List容器(Java)

文章目录 1.容器介绍1.1 容器接口结构1.2 简单解析 2. List容器创建(Member functions)3. 访问操作(Element access)3.1 get()3.2 Iterator遍历3.3 增强for循环 4. 修改操作(Modifiers)4.1 add()4.2 addAll()4.3 remove()4.4 set() 5. 容量操作(Member functions)5.1 clear() …

傻白入门芯片设计,形式化验证方法学——AveMC工具学习(二十)

目录 一、形式验证方法学 &#xff08;一&#xff09;什么是形式化验证&#xff1f; &#xff08;二&#xff09;与传统验证的区别&#xff1f; 二、AveMC工具学习 &#xff08;一&#xff09;什么是AveMC&#xff1f; &#xff08;二&#xff09;AveMC的工作逻辑&#x…

Redis整合jedis 学习

前言 redis简单来讲是内存数据库&#xff0c;因为是将数据存放到内存&#xff0c;因此读写速率高快(传统数据库&#xff0c;不是先从内存转到硬存&#xff0c;慢) 1.jedis连接Redis redis是java操作redis的客户端&#xff0c;通过他我们能通过java语句操控redis。 jedis对于…

全国大数据与计算智能挑战赛:面向低资源的命名实体识别基线方案,排名13/64,组织单位:国防科技大学系统工程学院(大数据与决策实验室)

NLP专栏简介:数据增强、智能标注、意图识别算法|多分类算法、文本信息抽取、多模态信息抽取、可解释性分析、性能调优、模型压缩算法等 专栏详细介绍:NLP专栏简介:数据增强、智能标注、意图识别算法|多分类算法、文本信息抽取、多模态信息抽取、可解释性分析、性能调优、模型…

一起学SF框架系列6.1-模块core-Resource

Java虽然提供了java.net.URL类和各种URL前缀处理程序来负责处理对各种资源的访问&#xff0c;但对于低级别资源的访问来说还是不够充分。例如&#xff0c;没有标准化的实现可用于访问需要从类路径中获取或者相对于一个ServletContext的资源&#xff1b;也没有检查所指向的资源是…

Linux 正则表达式

正则表达式有什么用&#xff1f;最基本的爬虫会用到&#xff0c;正则表达式简单来说就是匹配字符串的 比如&#xff1a;你匹配所有图片的链接地址 <img src"------(图片链接地址)"> 比如、abc/def URL:在WWW上&#xff0c;每一信息资源都有统一的且在网上…

(opencv)图像几何变换——缩放

图像缩放是指将图像的尺寸变小或变大的过程&#xff0c;也就是减少或增加源图像数据的像素个数。图像缩放一定程度上会造成信息的丢失&#xff0c;因此需要考虑适宜的方法进行操作。 下面介绍两种常用的图像缩放方法的原理及实现 1.基于等间隔提取图像缩放 等间隔提取图像缩…