Unity-实现场景地图扫描效果

文章目录[x]
  1. 0.1:一、使用深度图的方式
  2. 0.2:二、使用Projector组件

游戏中许多地方会需要用到扫描效果,比如无人深空中的扫描,手雷扔出去前的预判范围,死亡搁浅等等。

一、使用深度图的方式

相关链接:https://zhuanlan.zhihu.com/p/143788955

https://github.com/Broxxar/NoMansScanner

Shader "Hidden/ScannerEffect"
{
    Properties
    {
        _MainTex("Texture", 2D) = "white" {}
        _DetailTex("Texture", 2D) = "white" {}
        _ScanDistance("Scan Distance", float) = 0
        _ScanWidth("Scan Width", float) = 10
        _LeadSharp("Leading Edge Sharpness", float) = 10
        _LeadColor("Leading Edge Color", Color) = (1, 1, 1, 0)
        _MidColor("Mid Color", Color) = (1, 1, 1, 0)
        _TrailColor("Trail Color", Color) = (1, 1, 1, 0)
        _HBarColor("Horizontal Bar Color", Color) = (0.5, 0.5, 0.5, 0)
    }
    SubShader
    {
        // No culling or depth
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            #include "UnityCG.cginc"

            struct VertIn
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float4 ray : TEXCOORD1;
            };

            struct VertOut
            {
                float4 vertex : SV_POSITION;
                float2 uv : TEXCOORD0;
                float2 uv_depth : TEXCOORD1;
                float4 interpolatedRay : TEXCOORD2;
            };

            float4 _MainTex_TexelSize;
            float4 _CameraWS;

            VertOut vert(VertIn v)
            {
                VertOut o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv.xy;
                o.uv_depth = v.uv.xy;

                #if UNITY_UV_STARTS_AT_TOP
                if (_MainTex_TexelSize.y < 0)
                    o.uv.y = 1 - o.uv.y;
                #endif				

                o.interpolatedRay = v.ray;

                return o;
            }

            sampler2D _MainTex;
            sampler2D _DetailTex;
            sampler2D_float _CameraDepthTexture;
            float4 _WorldSpaceScannerPos;
            float _ScanDistance;
            float _ScanWidth;
            float _LeadSharp;
            float4 _LeadColor;
            float4 _MidColor;
            float4 _TrailColor;
            float4 _HBarColor;

            float4 horizBars(float2 p)
            {
                return 1 - saturate(round(abs(frac(p.y * 100) * 2)));
            }

            float4 horizTex(float2 p)
            {
                return tex2D(_DetailTex, float2(p.x * 30, p.y * 40));
            }

            half4 frag (VertOut i) : SV_Target
            {
                half4 col = tex2D(_MainTex, i.uv);

                float rawDepth = DecodeFloatRG(tex2D(_CameraDepthTexture, i.uv_depth));
                float linearDepth = Linear01Depth(rawDepth);
                float4 wsDir = linearDepth * i.interpolatedRay;
                float3 wsPos = _WorldSpaceCameraPos + wsDir;
                half4 scannerCol = half4(0, 0, 0, 0);

                float dist = distance(wsPos, _WorldSpaceScannerPos);
                //控制扫描线范围 linearDepth<1为了不出现在天空盒上
                if (dist < _ScanDistance && dist > _ScanDistance - _ScanWidth && linearDepth < 1)
                {
                    float diff = 1 - (_ScanDistance - dist) / (_ScanWidth);
                    half4 edge = lerp(_MidColor, _LeadColor, pow(diff, _LeadSharp));
                    scannerCol = lerp(_TrailColor, edge, diff) + horizBars(i.uv) * _HBarColor;
                    scannerCol *= diff;
                }

                return col + scannerCol;
            }
            ENDCG
        }
    }
}
using UnityEngine;
using System.Collections;

[ExecuteInEditMode]
public class ScannerEffectDemo : MonoBehaviour
{
    public Transform ScannerOrigin;
    public Material EffectMaterial;
    public float ScanDistance;

    private Camera _camera;


    void Start()
    {
    }

    void Update()
    {
        ScanDistance += Time.deltaTime * 12;
        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = _camera.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;

            if (Physics.Raycast(ray, out hit))
            {
                ScanDistance = 0;
                ScannerOrigin.position = hit.point;
            }
        }
    }

    void OnEnable()
    {
        _camera = GetComponent<Camera>();
        _camera.depthTextureMode = DepthTextureMode.Depth;
    }

    [ImageEffectOpaque]//只对不透明物体施加特效
    void OnRenderImage(RenderTexture src, RenderTexture dst)
    {
        EffectMaterial.SetVector("_WorldSpaceScannerPos", ScannerOrigin.position);
        EffectMaterial.SetFloat("_ScanDistance", ScanDistance);
        RaycastCornerBlit(src, dst, EffectMaterial);
    }

    //所以想要获取当前像素在世界空间的坐标,我们需要使用深度贴图来重构世界空间坐标
    //不能直接使用Graphics.Bilt(src,dst,mat);
    void RaycastCornerBlit(RenderTexture source, RenderTexture dest, Material mat)
    {
        float camFar = _camera.farClipPlane;
        float camFov = _camera.fieldOfView;
        float camAspect = _camera.aspect;

        float fovWHalf = camFov * 0.5f;

        Vector3 toRight = _camera.transform.right * Mathf.Tan(fovWHalf * Mathf.Deg2Rad) * camAspect;
        Vector3 toTop = _camera.transform.up * Mathf.Tan(fovWHalf * Mathf.Deg2Rad);

        Vector3 topLeft = (_camera.transform.forward - toRight + toTop);
        float camScale = topLeft.magnitude * camFar;

        topLeft.Normalize();
        topLeft *= camScale;

        Vector3 topRight = (_camera.transform.forward + toRight + toTop);
        topRight.Normalize();
        topRight *= camScale;

        Vector3 bottomRight = (_camera.transform.forward + toRight - toTop);
        bottomRight.Normalize();
        bottomRight *= camScale;

        Vector3 bottomLeft = (_camera.transform.forward - toRight - toTop);
        bottomLeft.Normalize();
        bottomLeft *= camScale;

        // Custom Blit, encoding Frustum Corners as additional Texture Coordinates
        RenderTexture.active = dest;

        mat.SetTexture("_MainTex", source);

        GL.PushMatrix();
        GL.LoadOrtho();

        mat.SetPass(0);

        GL.Begin(GL.QUADS);

        GL.MultiTexCoord2(0, 0.0f, 0.0f);
        GL.MultiTexCoord(1, bottomLeft);
        GL.Vertex3(0.0f, 0.0f, 0.0f);

        GL.MultiTexCoord2(0, 1.0f, 0.0f);
        GL.MultiTexCoord(1, bottomRight);
        GL.Vertex3(1.0f, 0.0f, 0.0f);

        GL.MultiTexCoord2(0, 1.0f, 1.0f);
        GL.MultiTexCoord(1, topRight);
        GL.Vertex3(1.0f, 1.0f, 0.0f);

        GL.MultiTexCoord2(0, 0.0f, 1.0f);
        GL.MultiTexCoord(1, topLeft);
        GL.Vertex3(0.0f, 1.0f, 0.0f);

        GL.End();
        GL.PopMatrix();
    }
}

使用的时候我们利用上面的shader创建一个材质球,然后在camera上挂载ScannerEffectDemo 脚本,并且把先前创建的材质球赋值到组件上。再创建一个新的空物体赋值到组件上就可以了。我们用鼠标点击场景就出现效果了。

二、使用Projector组件

1.先写一个projector用的shader

Shader "Custom/ProjectorDecal" 
{
    Properties
    {
        //调色
        _Color("Color", Color) = (1,1,1,1)
        //投影图片
        _MainTex("Albedo (RGB)", 2D) = "white" {}
        //根据投影仪视距渐变的图片
        _FalloffTex("Falloff",2D) = "white"{}
    }
    SubShader
    {
        Pass
        {
            ZWrite Off
            //解决ZFighting现象
            Offset -1, -1
            Blend SrcAlpha OneMinusSrcAlpha

            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            float4 _Color;
            //将投影仪剪辑空间的X和Y轴映射到U和V坐标,这些坐标通常用于对径向衰减纹理进行采样。
            float4x4 unity_Projector;
            //将投影仪视图空间的Z轴映射到U坐标(可能在V中复制它),该坐标可用于采样渐变纹理,该纹理定义投影仪随距离衰减。u值在投影仪近平面处为0,在投影仪远平面处为1。
            float4x4 unity_ProjectorClip;
            sampler2D _MainTex;
            sampler2D _FalloffTex;

            struct v2f {
                float4 uvDecal:TEXCOORD0;
                float4 uvFalloff:TEXCOORD1;
                float4 pos:SV_POSITION;
            };

            v2f vert(appdata_img v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                //四元纹理坐标给UNITY_PROJ_COORD读取					
                o.uvDecal = mul(unity_Projector, v.vertex);
                o.uvFalloff = mul(unity_ProjectorClip, v.vertex);
                return o;
            }

            float4 frag(v2f i) :SV_Target
            {
                float4 decal;
                //解决图片四周拖影
                if (i.uvDecal.x / i.uvDecal.w < 0.0001 || i.uvDecal.x / i.uvDecal.w>0.9999 || i.uvDecal.y / i.uvDecal.w < 0.0001 || i.uvDecal.y / i.uvDecal.w>0.9999)
                {
                    decal = float4(0, 0, 0, 0);
                }
                else
                {
                    //采样齐次uv,分量都除以了w
                    decal = tex2Dproj(_MainTex, UNITY_PROJ_COORD(i.uvDecal));
                }
                float falloff = tex2Dproj(_FalloffTex, UNITY_PROJ_COORD(i.uvFalloff)).r;
                return float4(decal.rgb * _Color.rgb,decal.a * falloff * _Color.a);
            }
            ENDCG

        }
    }
}

2.写一个控制projector缩放的脚本。

using UnityEngine;

public class DecalManager : MonoBehaviour
{
    private Projector projector;
    [SerializeField]
    private bool looping = false;
    private float size = 0;
    public float spreadSpeed = 1f;
    public float maxSize = 10f;
    private Color alphaColor;
    private void Start()
    {
        projector = GetComponent<Projector>();
        projector.orthographic = true;
        projector.orthographicSize = 0;
        alphaColor = new Color(projector.material.color.r, projector.material.color.g, projector.material.color.b, 0);
    }

    private void Update()
    {
        size += Time.deltaTime * spreadSpeed;
        if(size<maxSize)
        {
            projector.orthographicSize = size;
            alphaColor.a = 1 - size / maxSize;
            projector.material.color = alphaColor;
        }
        else
        {
            if(looping)
            {
                size = projector.orthographicSize = 0;
            }
            else
            {
                Destroy(gameObject);
            }
        }
    }
}

3.在场景中创建一个空物体,然后添加Projector组件,再用上面写的shader创建一个材质球赋值到projector上面。

4.把上面写的脚本挂载到projector上面,运行。

源码地址:http://horse7.cn/download/scenescanner.unitypackage

 

点赞

发表回复

昵称和uid可以选填一个,填邮箱必填(留言回复后将会发邮件给你)
tips:输入uid可以快速获得你的昵称和头像(已失效)