【Unity3D】激光雷达特效

news2024/9/22 15:32:54

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
}

        运行效果如下:

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++linux高并发服务器项目实践 day12

Clinux高并发服务器项目实践 day12 socket介绍字节序字节序转换函数 socket地址IP地址转换(字符串ip-整数&#xff0c;主机、网络字节序的转换)TCP通信流程套接字函数TCP三次握手TCP滑动窗口TCP四次挥手 socket介绍 socket是网络环境中进程间通信的API&#xff0c;也是可以被命…

CAPL如何仿真ARP报文

文章目录 前言一、环境搭建二、IG生成器仿真ARP报文三、CAPL仿真ARP报文前言 随着智能电动汽车的普及,车载以太网的应用逐渐广泛,所以在汽车电子台架测试过程中,免不了仿真模拟发送以太网报文,本文就介绍两种方法模拟仿真发送以太网ARP报文。 一、环境搭建 CANoe安装 VN5…

FPGA时序约束--实战篇(Vivado添加时序约束)

前面几篇文章已经详细介绍了FPGA时序约束基础知识以及常用的时序约束命令&#xff0c;相信大家已经基本掌握了时序约束的方法。 今天介绍一下&#xff0c;如何在Vivado中添加时序约束&#xff0c;Vivado添加约束的方法有3种&#xff1a;xdc文件、时序约束向导&#xff08;Cons…

vue基础-全选,使用计算属性 和 every遍历数组的返回值 true or false

购物车--计算购物车价格 1、计算属性--只要被依赖的数据 发生变化&#xff0c;结果就会变化 2、全选实现 根据选项&#xff0c;动态计算出布尔值&#xff0c;通过计算属性 得到布尔值&#xff1a; 通过every遍历数组list&#xff0c;只要所有 都满足 item > item.goods_s…

递推算法介绍

递推算法 给定一个数的序列H0,H1,…,Hn,…若存在整数n0&#xff0c;使当n>n0时,可以用等号(或大于号、小于号)将Hn与其前面的某些项Hi(0<i<n)联系起来&#xff0c;这样的式子就叫做递推关系。 递推算法是一种简单的算法&#xff0c;即通过已知条件&#xff0c;利用特…

CVPR 2023|EfficientViT:让ViT更高效部署实现实时推理(附源码)

点击蓝字 关注我们 关注并星标 从此不迷路 计算机视觉研究院 公众号ID&#xff5c;计算机视觉研究院 学习群&#xff5c;扫码在主页获取加入方式 论文地址&#xff1a;https://arxiv.org/pdf/2305.07027.pdf 项目代码&#xff1a;https://github.com/microsoft/Cream/tree/main…

打通B端企业私域运营体系:海康威视企业公众号矩阵一探究竟

B端企业私域运营体系的打造需要全新的思路和流程重构&#xff0c;要紧紧围绕B端客户的需求和特性来构建矩阵号&#xff0c;而且要时刻意识到与C端私域运营的巨大差异。 B端企业的获客是一个大部分企业都十分关注但难以提升的部分&#xff0c;传统B端企业的营销以百度等在线渠道…

candence:常见表贴焊盘绘制举例

常见表贴焊盘绘制举例 一、先来看看X7R电容的相关信息 以贴装瓷片电容X7R系列为例 1、误差范围&#xff1a; 2、尺寸大小 3、推荐焊盘尺寸 二、绘制 0603 (inch) 电容的焊盘 下面开始绘制焊盘&#xff1a; 1、 双击打开Pad Designer 2、设置单位等。 3、 点击"LAYER&…

GeSciLiVis | 想知道你感兴趣的基因有多少人在研究吗!?用这个包来解决吧!!!~

1写在前面 天气好热啊&#xff0c;我这里还下着大暴雨&#xff01;~&#x1f625; 不知道各位小伙伴那里的温度怎么样&#xff0c;端午临近&#xff0c;各位有假期吗&#xff01;&#xff1f;&#x1f618; 换组后工作轻松了不少&#xff0c;也有时间做点自己的事情了。&#x…

JDK8-2-流(2.1)- 流操作-distinct

JDK8-2-流&#xff08;2.1&#xff09;- 流操作-distinct 去重操作&#xff0c;如下开头两个菜品一样&#xff0c;对 menu 去重如下&#xff1a; public class DishDistinctTest1 {public static final List<Dish> menu Arrays.asList(new Dish("pork", fal…

如何白嫖一年CSDN会员?618活动!亲测有效!!!

活动详情 CSDN会员免费送一年&#xff0c;仅剩3天&#xff01; 下载权益同样延长一年&#xff01; 一年一次的机会&#xff0c;走过不要错过&#xff01; 博主已经领取到了&#xff01; 会员权益 1、修改专属域名&#xff0c;别人都是https://blog.csdn.net/qq_xxxxxxxx&…

用ipad2022款pro平板和平精英吃鸡120帧是种什么体验

用ipad2022款pro平板和平精英吃鸡120帧是种什么体验&#xff0c;平板玩游戏把把吃鸡的秘密#和平精英 #吃鸡 #我眼中的电子竞技 和平精英 微信游戏 怎么很多小伙伴都准备入手二款的 iPad Pro&#xff0c;绝大多数小伙伴都是冲着这个吃鸡来的&#xff0c;咱们今天就看一下新款平…

AI绘图新玩法「艺术风二维码」保姆级教程分享,注册账号就能玩,一分钟出图,定制自己的二维码!

大家好&#xff0c;我是卷了又没卷&#xff0c;薛定谔的卷的AI算法工程师「陈城南」~ 担任某大厂的算法工程师&#xff0c;带来最新的前沿AI知识和工具&#xff0c;包括AI相关技术、ChatGPT、AI绘图等&#xff0c;欢迎大家交流~。 最近AI绘图界又出了一个现象级的玩法&#xf…

FPGA基础知识-开关级建模

目录 学习目标 学习内容 1.MOS开关 2.CMOS开关 3.双向开关 4.电源和地 5.阻抗开关 6.开关中的延迟说明 学习时间 学习总结 学习目标&#xff1a; 提示&#xff1a;这里可以添加学习目标 1.能够描述基本 MOS开关:nmos.pmos和cmos。 2.理解双向传输开关、电源和地的建…

webpack配置preload和prefetch预加载技术

我们前面已经做了代码分割&#xff0c;同时会使用 import 动态导入语法来进行代码按需加载&#xff08;我们也叫懒加载&#xff0c;比如路由懒加载就是这样实现的&#xff09;。 但是加载速度还不够好&#xff0c;比如&#xff1a;是用户点击按钮时才加载这个资源的&#xff0…

【第二次】21级计科计算机组成原理课外练习

【第二次】21级计科计算机组成原理课外练习 一、单选题二、填空题 一、单选题 2-1 设计算机字长 8位&#xff0c;设x -10, [x]补 为 ( ) A.F0H B.E6H C.FAH D.F6H 2-2 对字长为8位的二进制代码10110110&#xff0c;下列说法错误的是&#xff08;&#xff09;。 A.如果代码为…

主流通信协议详解、二进制协议和文本协议的区别和使用场景

二进制协议和文本协议的特征和使用场景 二进制协议和文本协议具有不同的特征和适用场景&#xff1a; 二进制协议的特征&#xff1a; 数据表示&#xff1a;使用二进制编码来表示数据&#xff0c;以字节为单位进行传输。效率&#xff1a;由于数据以原始二进制形式传输&#xff…

年龄越来越大,技术人究竟该往哪个方向提升?(很多人都有这个困惑)

【1】 有位匿名朋友&#xff0c;在星球中提问&#xff1a; 想咨询一下沟通交流能力应该如何培养。平时遇到问题话到嘴边不会说&#xff0c;或者说出来的和开始想的不一致&#xff0c;请沈总赐教。 分享下自己的实践&#xff1a; 首先&#xff0c;充分准备。 我在和老板&#xf…

Linux ACL访问控制

文章目录 1、场景2、ACL权限设置1) ACL权限管理命令2) 给用户和用户组添加ACL权限3) 最大有效权限mask4) 默认ACL权限和递归ACL权限5) 删除ACL权限 1、场景 在普通权限中&#xff0c;用户对文件只有三种身份&#xff0c;就是属主、属组和其他人&#xff1b;每种用户身份拥有读…

实验篇(7.2) 14. 站对站安全隧道 - 多条隧道冗余(FortiGate-IPsec) ❀ 远程访问

【简介】IPsec VPN虽然价廉物美&#xff0c;但是由运营商原因&#xff0c;偶尔出现不稳定情况&#xff0c;例如访问慢甚至断开等&#xff0c;好在现在大多数企业都有二条甚至更多条宽带&#xff0c;我们可以创建多条IPsec VPN&#xff0c;来保证不间断访问。 实验要求与环境 Ol…