Shader 锥形扫描遮挡效果

34 篇文章 5 订阅

效果图如下:
在这里插入图片描述

新建shader,代码如下:

Shader "Unlit/ConeScan"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
		_Color("Color",Color)=(1,1,1,1)//颜色
		_StrongFloat("_StrongFloat",float)=0.1//增强圆形边缘效果的值
		_AlphaDownFloat("_AlphaDownFloat",float)=0.2//降低锥形区域外的alpha
		_Angle("Angle",float)=25//25*2度角的锥形
		_GradientFloat("_GradientFloat",float)=0.3//渐变半圆弧的颜色
    }
    SubShader
    {
		Blend SrcAlpha OneMinusSrcAlpha
        Tags { "RenderType"="Transparent" "Queue"="Transparent" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
			fixed4 _Color;
			float _StrongFloat;
			float _AlphaDownFloat;
			float _Angle;
			float _GradientFloat;
			uniform float _FloatArray[256];

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {

				fixed4 col;
				//圆弧
				float2 uv=i.uv;
				uv.x=uv.x-0.5;
				uv.y=uv.y-0.5;
				//i.uv=i.uv-float2(0.5,0.5);
				//根据UV来计算出一个变化区域,一个以UV中心点为中心,半径为0.5的圆形,圆形内到外从1渐变到0
				fixed dis=1.0-sqrt(uv.x*uv.x+uv.y*uv.y)*2;
				//中心点向周围发射的向量(归一化)
				fixed2 fragmentDir=normalize(uv.xy);
				//半圆弧,圆弧中心向两旁的值从1逐渐变为0,cos正好满足
				fixed rightHalfCircle=clamp(dot(float2(1,0),fragmentDir.xy),0,1);
				//渐变半圆弧颜色(内到外)
				col=lerp(_Color,fixed4(1,1,1,1),dis*_GradientFloat);
				//衰减半圆弧两旁,衰减_Angle角后的
				fixed tempAngleCos=cos(radians(_Angle));
				//增强边缘效果
				fixed strongF=pow(dis,_StrongFloat);
				col.a=col.a*dis*rightHalfCircle*strongF;
				//大于_Angle角度区域的像素衰减(在视野之外的),_Angle是视野角度的一半
				if(rightHalfCircle<tempAngleCos)
				{
					col.a*=_AlphaDownFloat;
				}
				else
				{
					//扫描遮挡的核心:视野角度内(-_Angle,_Angle)范围内进行一个遮挡处理
					//计算出index
					//fragmentDir.y是归一化后的向量y值
					//因为sqrt(fragmentDir.y*fragmentDir.y+fragmentDir.x*fragmentDir.x)=1
					//sin(fragmentDir)与UV正x轴(0.5,0.5)的角度弧度为fragmentDir.y/1,即fragmentDir.y,
					//反过来说fragmentDir.y就是sin(角度)
					//输入的是正弦值 sin(角度)=对边/斜边=fragmentDir.y/1  斜边是1,因为fragmentDir是归一化向量
					//反正弦函数 输入[-1,1](sin值) 输出[-π/2, π/2](弧度)
					//简单来讲就是将偏移后的uv坐标点与中心点向量 和 正X轴的夹角角度转成了弧度..
					//知道什么是反正弦函数就很容易了。。 就是反着来,正弦函数是输入弧度 输出正弦值,反正弦就是输入正弦值输出弧度
					float curRad=asin(fragmentDir.y);
					curRad+=radians(_Angle);//偏移到正数(上面的弧度是指(-_Angle,_Angle)角度的当前片元所在的角度弧度)
					float f=curRad/radians(_Angle*2);//当前弧度/总弧度  得到一个系数
					float index=f*256;		//系数乘上索引最大值 获取索引
					//因为c#计算出的当index为0时,应该是照射区域上方,而此时Shader是不是0时上方,
					//答案不是,上方index为0时,curRad是0,在没有经过偏移时,它位于照射区域最下。所以应该取反索引
					index=256-index;
					float curFloat=_FloatArray[index];
					//dis是1到0,(1-dis)就是0到1,curFloat是锥形尖角的位置到目标障碍物的距离,*5要根据实际情况考虑
					if(curFloat>0&&(1-dis)*5>curFloat)
					{
						col*=0;
					}
				}
				return col;
            }
            ENDCG
        }
    }
}

注意Main Camera的Allow HDR要取消勾选。

接下来就是射线检测部分了,通过射线检测将取得的数值传给shader中使用。
代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Scan : MonoBehaviour
{
    //旋转速度
    public float speed;
    //射线检测的角度大小
    public float angle;
    //存储射线检测的结果
    float[] arrayFloat;
    //射线长度
    public float rayLength;
    //扫描材质
    public Material mat;
    void Start()
    {
        arrayFloat = new float[256];
    }

    void Update()
    {
        transform.Rotate(transform.up * Time.deltaTime * speed);
        UpdateRay();
    }

    private void UpdateRay()
    {
        int index = 0;
        //角度转弧度
        float rad = Mathf.Deg2Rad*angle;
        float step = rad * 2 / 256;
        for (int i = 1; i <= 256; i++)
        {
            //step * i是视角范围内的一个弧度变化+自身角度弧度 进行旋转
            float curRad = step * i + Mathf.Deg2Rad * (transform.eulerAngles.y+180)-rad;
            //根据当前弧度计算出坐标
            float x = rayLength * Mathf.Cos(-curRad);
            float z = rayLength * Mathf.Sin(-curRad);
            Vector3 pos = new Vector3(x, 0, z);
            Ray ray = new Ray(transform.position, pos);
            RaycastHit hit;
            if(Physics.Raycast(ray,out hit))
            {
                arrayFloat[index] = hit.distance;
                //Debug.DrawLine(transform.position, pos, Color.red, arrayFloat[index]);
            }
            else
            {
                arrayFloat[index] = -1;
                //Debug.DrawLine(transform.position, pos,Color.blue,arrayFloat[index]);
            }
            index++;
        }
        mat.SetFloatArray("_FloatArray", arrayFloat);
    }
}

在这里插入图片描述

这样就完成效果了。

这个方式是GPU和CPU一直在通信,并且射线检测较多次,频率较高,性能可能不太好,下面这个是通过射线检测后绘制网格的,性能更好些。
在这里插入图片描述
在这里插入图片描述

IHideable接口:

/// <summary>
/// Interface that needs to be implemented by any object that gets affected by the Field of View of the player.
/// </summary>
public interface IHideable {

    void OnFOVEnter();
    void OnFOVLeave();
}

Hideable代码:

using UnityEngine;

public class Hideable : MonoBehaviour, IHideable {

    private MeshRenderer render;

    private void Awake()
    {
        OnFOVLeave();
    }

    public void OnFOVEnter() {
        if (render == null)
            //TryGetComponent(out render);
            render = GetComponent<MeshRenderer>();
        render.enabled = true;
    }
    public void OnFOVLeave() {
        if (render == null)
            //TryGetComponent(out render);
            render = GetComponent<MeshRenderer>();
        render.enabled = false;
    }
}

FieldOfView代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(MeshFilter))]
public class FieldOfView : MonoBehaviour {

    [Header("视野设置")]
    [Tooltip("玩家可以看到的半径或最大距离")] public float viewRadius = 50f;
    [Range(0, 360), Tooltip("视野角度")] public float viewAngle = 90f;
    
    [Header("周边视野设置")]
    [Tooltip("玩家是否有周边视野?")] public bool hasPeripheralVision = false;
    [Tooltip("玩家用其周边视觉所能看到的最大半径距离.")] public float viewRadiusPeripheralVision = 10f;
    
    [Header("边缘解析设置")]    [Tooltip("边缘分解算法的迭代(更高=更精确但也更昂贵)")] public int edgeResolveIterations = 1;
    public float edgeDstThreshold;

    [Header("常规设置")]
    [Range(0, 1), Tooltip("视场更新之间的延迟,隔多少秒设置一次扫描物体的隐藏显示")] public float delayBetweenFOVUpdates = 0.2f;

    [Header("层级设置")]
    [Tooltip("进入/离开视野时受到影响的物体。它们必须实现iHidable接口")] public LayerMask targetMask;
    [Tooltip("阻挡视野的对象")] public LayerMask obstacleMask;

    [Header("可视化设置")]
    [Tooltip("视野可视化吗?")] public bool visualizeFieldOfView = true;
    [Tooltip("影响重新计算视场时射出的射线数量。光线投射计数=视角*网格分辨率")] public float meshResolution = 1;
    [Tooltip("影响重新计算玩家周边视野时投射出的射线数量。价值越高,成本就越高!光线投射计数")] public int meshResolutionPeripheralVision = 10;
    private MeshFilter viewMeshFilter;
    private Mesh viewMesh;


    //变量在DrawFieldOfView方法中使用(在这里存储效率更高-GC.collect…)
    private List<Vector3> viewPoints = new List<Vector3>();


    private void Start() {
        //TryGetComponent(out viewMeshFilter);  2019.2以上版本才有的API.....................................
        viewMeshFilter = GetComponent<MeshFilter>();
        viewMesh = new Mesh
        {
            name = "View Mesh"
        };
        viewMeshFilter.mesh = viewMesh;
    }
    void OnEnable()
    {
        StartCoroutine("FindTargetsWithDelay", delayBetweenFOVUpdates);
    }

    private void LateUpdate()
    {
        if (visualizeFieldOfView)
        {
            viewMeshFilter.mesh = viewMesh;
            DrawFieldOfView();
        } else
        {
            viewMeshFilter.mesh = null;
        }
    }

    private readonly List<int> triangles = new List<int>();
    private readonly List<Vector3> vertices = new List<Vector3>();
    /// <summary>
    /// 画出视野
    /// </summary>
    void DrawFieldOfView()
    {
        
        viewPoints.Clear();
        ViewCastInfo oldViewCast = new ViewCastInfo();

        /* 计算法向视野 */
        for (int i = 0; i <= Mathf.RoundToInt(viewAngle * meshResolution); i++)
        {
            //存储了射线检测后的结果
            ViewCastInfo newViewCast = ViewCast(transform.eulerAngles.y - viewAngle / 2 + (viewAngle / Mathf.RoundToInt(viewAngle * meshResolution)) * i, viewRadius);
            if (i > 0)
            {
                if (oldViewCast.hit != newViewCast.hit || (oldViewCast.hit && newViewCast.hit && Mathf.Abs(oldViewCast.distance - newViewCast.distance) > edgeDstThreshold))
                {
                    EdgeInfo edge = FindEdge(oldViewCast, newViewCast, viewRadius);
                    if (edge.pointA != Vector3.zero)
                    {
                        viewPoints.Add(edge.pointA);
                    }
                    if (edge.pointB != Vector3.zero)
                    {
                        viewPoints.Add(edge.pointB);
                    }
                }
            }
            viewPoints.Add(newViewCast.point);
            oldViewCast = newViewCast;
        }
        /* 计算周边视野 */
        if (hasPeripheralVision && viewAngle < 360)
        {
            //把较短的光线投射到周围,以确保他总是能从各个方向看一点东西
            for (int i = 0; i < meshResolutionPeripheralVision + 1; i++)
            {
                ViewCastInfo newViewCast = ViewCast(transform.eulerAngles.y + viewAngle / 2 + i * (360 - viewAngle) / meshResolutionPeripheralVision, viewRadiusPeripheralVision);
                //viewPoints.Add(newViewCast.point);
                if (i > 0)
                {
                    if (oldViewCast.hit != newViewCast.hit || (oldViewCast.hit && newViewCast.hit && Mathf.Abs(oldViewCast.distance - newViewCast.distance) > edgeDstThreshold))
                    {
                        EdgeInfo edge = FindEdge(oldViewCast, newViewCast, viewRadiusPeripheralVision);
                        if (edge.pointA != Vector3.zero)
                        {
                            viewPoints.Add(edge.pointA);
                        }
                        if (edge.pointB != Vector3.zero)
                        {
                            viewPoints.Add(edge.pointB);
                        }
                    }
                }
                viewPoints.Add(newViewCast.point);
                oldViewCast = newViewCast;
            }
        }
        /* 画出网格 */
        int vertexCount = viewPoints.Count + 1;
        vertices.Clear();
        triangles.Clear();
        vertices.Add(Vector3.zero);
        for (int i = 0; i < vertexCount - 1; i++)
        {
            vertices.Add(transform.InverseTransformPoint(viewPoints[i]));
            if (i < vertexCount - 2)
            {
                triangles.Add(0);
                triangles.Add(i + 1);
                triangles.Add(i + 2);
            }
        }
        viewMesh.Clear();
        viewMesh.SetVertices(vertices) ;
        //Unity中,可以有submesh。0表示主mesh
        viewMesh.SetTriangles(triangles,0) ;
        viewMesh.RecalculateNormals();
    }

    /// <summary>
    /// 以给定的角度投射光线,结果返回ViewCastInfo结构。
    /// </summary>
    /// <param name="globalAngle">每条射线的角度</param>
    /// <returns></returns>
    ViewCastInfo ViewCast(float globalAngle, float viewRadius)
    {
        Vector3 dir = DirFromAngle(globalAngle, true);
        Physics.autoSyncTransforms = false;
        if (Physics.Raycast(transform.position, dir, out RaycastHit hit, viewRadius, obstacleMask))
        {
            Physics.autoSyncTransforms = true;
            return new ViewCastInfo(true, hit.point, hit.distance, globalAngle);
        } else
        {
            Physics.autoSyncTransforms = true;
            return new ViewCastInfo(false, transform.position + dir * viewRadius, viewRadius, globalAngle);
        }
    }
    /// <summary>
    /// 找到碰撞体的边缘
    /// </summary>
    /// <param name="minViewCast"></param>
    /// <param name="maxViewCast"></param>
    /// <returns></returns>
	EdgeInfo FindEdge(ViewCastInfo minViewCast, ViewCastInfo maxViewCast, float viewRadius)
    {
        float minAngle = minViewCast.angle;
        float maxAngle = maxViewCast.angle;
        Vector3 minPoint = Vector3.zero;
        Vector3 maxPoint = Vector3.zero;

        for (int i = 0; i < edgeResolveIterations; i++)
        {
            float angle = (minAngle + maxAngle) / 2;
            ViewCastInfo newViewCast = ViewCast(angle, viewRadius);

            bool edgeDstThresholdExceeded = Mathf.Abs(minViewCast.distance - newViewCast.distance) > edgeDstThreshold;
            if (newViewCast.hit == minViewCast.hit && !edgeDstThresholdExceeded)//xxxxxxxxxxxxxxxxx
            {
                minAngle = angle;
                minPoint = newViewCast.point;
            }
            else
            {
                maxAngle = angle;
                maxPoint = newViewCast.point;
            }
        }
        return new EdgeInfo(minPoint, maxPoint);
    }

    /// <summary>
    /// 每1秒运行一次FindVisibleTargets方法
    /// </summary>
    /// <param name="delay"></param>
    /// <returns></returns>
    IEnumerator FindTargetsWithDelay(float delay)
    {
        while (true)
        {
            FindVisibleTargets();
            yield return new WaitForSeconds(delay);
        }
    }

    Collider[] targetsInViewRadius = new Collider[10];
    /// <summary>
    /// 查找所有可见目标并将其添加到“可见目标”列表中.
    /// </summary>
    void FindVisibleTargets()
    {
        int length = Physics.OverlapSphereNonAlloc(transform.position, viewRadius , targetsInViewRadius, targetMask);
        //在执行下一次FixedUpdate之前,对碰撞体的改动不会立即同步到物理场景
        Physics.autoSyncTransforms = false;

        /* check normal field of view */
        for (int i = 0; i < length; i++)
        {
            Transform target = targetsInViewRadius[i].transform;
            bool isInFOV = false;
            //检查是否应该隐藏
            Vector3 dirToTarget = (target.position - transform.position).normalized;
            if (Vector3.Angle(transform.forward, dirToTarget) < viewAngle / 2)
            {
                float dstToTarget = Vector3.Distance(transform.position, target.position);
                if (!Physics.Raycast(transform.position, dirToTarget, dstToTarget, obstacleMask))
                {
                    isInFOV = true;
                }
            } else if (hasPeripheralVision)
            {
                float dstToTarget = Vector3.Distance(transform.position, target.position);
                // 这里我们必须检查到目标的距离,因为周围的视野可能有不同于正常视野的半径
                if (dstToTarget < viewRadiusPeripheralVision && !Physics.Raycast(transform.position, dirToTarget, dstToTarget, obstacleMask))
                {
                    isInFOV = true;
                }
            }
            //apply effect to IHideable
            IHideable hideable ;
            //target.TryGetComponent(out hideable);............................................
            hideable = target.GetComponent<IHideable>();
            if (hideable != null)
            {
                if (isInFOV)
                {
                    hideable.OnFOVEnter();
                } else
                {
                    hideable.OnFOVLeave();
                }
            }
        }
        Physics.autoSyncTransforms = true;
    }

    /// <summary>
    /// 将角度转换为方向矢量.
    /// </summary>
    /// <param name="angleInDegrees"></param>
    /// <returns></returns>
    public Vector3 DirFromAngle(float angleInDegrees, bool IsAngleGlobal)
    {
        if (!IsAngleGlobal)
        {
            angleInDegrees += transform.eulerAngles.y;
        }
        return new Vector3(Mathf.Sin(angleInDegrees * Mathf.Deg2Rad), 0, Mathf.Cos(angleInDegrees * Mathf.Deg2Rad));
    }
}


/// <summary>
/// 用于存储有关视图光线投射的信息的结构体
/// </summary>
public struct ViewCastInfo
{
    public bool hit;
    public Vector3 point;
    public float distance;
    public float angle;

    public ViewCastInfo(bool hit, Vector3 point, float distance, float angle)
    {
        this.hit = hit;
        this.point = point;
        this.distance = distance;
        this.angle = angle;
    }
}
/// <summary>
/// 保存边缘信息的结构体
/// </summary>
public struct EdgeInfo
{
    public Vector3 pointA;
    public Vector3 pointB;

    public EdgeInfo(Vector3 pointA, Vector3 pointB)
    {
        this.pointA = pointA;
        this.pointB = pointB;
    }
}
  • 5
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,下面是一个使用 GLSL 着色器实现扇形雷达扫描效果的示例代码。 首先是顶点着色器代码: ``` #version 330 uniform float angle; in vec3 position; out float scanPos; void main() { gl_Position = vec4(position, 1.0); scanPos = degrees(atan(position.y, position.x)); if(scanPos < 0.0) { scanPos += 360.0; } scanPos = mod(scanPos - angle, 360.0); } ``` 这个顶点着色器使用一个 uniform 变量 angle 表示当前扫描的角度,使用 in 变量 position 表示顶点的位置。在主函数中,先计算出当前顶点对应的角度 scanPos,然后根据 angle 和 scanPos 计算出该点在扇形雷达扫描中的位置。 接下来是片段着色器代码: ``` #version 330 uniform vec4 scanColor; uniform vec4 bgColor; in float scanPos; out vec4 fragColor; void main() { if(scanPos < 90.0 || scanPos > 270.0) { fragColor = scanColor; } else { fragColor = bgColor; } } ``` 这个片段着色器使用两个 uniform 变量 scanColor 和 bgColor 分别表示雷达波形颜色和背景颜色。使用 in 变量 scanPos 表示当前点在扇形雷达扫描中的位置。在主函数中,根据 scanPos 判断当前点是否在雷达扫描范围内,如果在范围内,则使用雷达波形颜色,否则使用背景颜色。 最后,在主程序中使用以下代码来设置 uniform 变量和绘制场景: ``` // 设置扫描角度 glUniform1f(glGetUniformLocation(shaderProgram, "angle"), angle); // 设置雷达波形颜色 glUniform4f(glGetUniformLocation(shaderProgram, "scanColor"), scanColor.r, scanColor.g, scanColor.b, scanColor.a); // 设置背景颜色 glUniform4f(glGetUniformLocation(shaderProgram, "bgColor"), bgColor.r, bgColor.g, bgColor.b, bgColor.a); // 绘制场景 glDrawArrays(GL_TRIANGLE_FAN, 0, vertexCount); ``` 其中,shaderProgram 是编译好的着色器程序,angle 是扫描角度,scanColor 是雷达波形颜色,bgColor 是背景颜色,vertexCount 是场景顶点数量。 以上就是一个简单的使用 GLSL 着色器实现扇形雷达扫描效果的示例代码,希望对您有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值