[Unity]基于深度缓冲实现扫描效果

在这里插入图片描述
在这里插入图片描述
如果想看视频的话,可以观看我在B站翻译的教程:BV1vt4y1U7HV
实现扫描效果不可能对每个着色器都去增加一段shader代码来实现扫描效果,这是不合理的,不利于项目扩展。所以正确的做法是对于屏幕中看到的像素去做处理,也就是像后处理一样,把rendertexture进行处理后在渲染在屏幕上。

获取深度缓冲

在获取当前渲染的摄像机组件后,对其成员depthTextureMode 赋值就能获得深度贴图。

GetComponent<Camera>().depthTextureMode = DepthTextureMode.Depth;

于是shader中就能够通过声明sampler2D _CameraDepthTexture来获取深度贴图。
亲自尝试了发现:
如果场景中有灯光能投射阴影,即使不去设置摄像机的深度贴图模式也能获取到深度贴图。

重构空间坐标

因为要实现扫描效果,我们需要计算像素点离扫描点的距离。但如何获得空间顶点坐标呢?因为是后处理,所以在shader中处理的顶点其实只有屏幕四个角落的点。所以想要获取当前像素在世界空间的坐标,我们需要使用深度贴图来重构世界空间坐标。
重构空间坐标有两种方法

  • 构建出当前像素的NDC坐标,再通过当前摄像机的视角*投影矩阵的逆矩阵来得到世界空间下的像素坐标。(但这需要在片元着色器中进行矩阵乘法操作,会影响游戏性能)
  • 对图像空间下的视锥体射线(从摄像机触发,指向图像上的某点的射线)进行插值,这条射线存储了该像素在世界空间下到摄像机的方向信息。然后,我们把该射线和线性化后的视角空间下的深度值相乘,再加上摄像机的世界位置,就可以得到该像素在世界空间下的位置。
  • 在这里插入图片描述

计算视锥体射线

		float camFar = Camera.main.farClipPlane;
        float camFov = Camera.main.fieldOfView;
        float camAspect = Camera.main.aspect;

        float fovWHalf = camFov * 0.5f;

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

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

        topLeft.Normalize();
        topLeft *= camScale;

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

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

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

通过以上代码计算完四个角落的视锥体射线后,我们需要把它传入到shader的TEXCOORD中,这样它在传递到片元着色器的适合就能够插值。

但和普通后处理不一样,我们需要给shader的插值寄存器(TEXCOORD)灌值。不能使用简单的Graphic.Blit函数。我们需要自定义这个流程。
所以我们需要使用Unity的GL库(底层图形库)来绘制结果图像在屏幕上。

以下代码写在摄像机的脚本的OnRenderImage中

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


        mat.SetTexture("_MainTex", source);

        GL.PushMatrix();
        GL.LoadOrtho();//用于绘制2D图形,详情看官方文档

        mat.SetPass(0);//设置使用哪个pass来渲染

        GL.Begin(GL.QUADS);//渲染一个四边形,接下来的代码中出现的Vertex3,将每4个绘制一个四边形。

        GL.MultiTexCoord2(0, 0.0f, 0.0f);//意思是在TEXCOORD0上传入(0,0)
        GL.MultiTexCoord(1, bottomLeft);//意思是在TEXCOORD1上传入视锥体射线bottomLeft
        GL.Vertex3(0.0f, 0.0f, 0.0f);//声明以上配置针对顶点(0,0,0)
//以下类似
        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();

GL.PushMatrix和GL.PopMatrix可以视为一个固定模版,保证转换矩阵正确。

shader代码

Shader "Hidden/TestImage"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _CenterPoint("中心点",Vector)=(0,0,0,0)
        [HDR]_EdgeColor("边缘颜色",Color)=(1,1,1,1)
        [HDR]_EdgeColor2("边缘颜色2",Color)=(1,1,1,1)
        _Radius("半径",Range(0,100))=1
        _Width("宽度",Range(0,100))=1
    }
    SubShader
    {
        // No culling or depth
        Cull off 
        ZWrite off
        ZTest always
        Tags{"queue"="transparent"}
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            float4 _CenterPoint;
            float4 _EdgeColor;
            float4 _EdgeColor2;

            sampler2D _CameraDepthTexture;
            float _Radius;
            float _Width;
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float4 ray:TEXCOORD1;
            };

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

            v2f vert (appdata v)
            {
                v2f o;
                o.interpolatedRay=v.ray;
                o.vertex = UnityObjectToClipPos(v.vertex);
                // v.ray=o.vertex-fixed4(_WorldSpaceCameraPos,0);
                o.uv = v.uv;
                return o;
            }

            sampler2D _MainTex;

            fixed4 frag (v2f i) : SV_Target
            {
                float rawDepth=DecodeFloatRG(tex2D(_CameraDepthTexture,i.uv));
                float linearDepth=Linear01Depth(rawDepth);
                float4 wsDir=linearDepth*i.interpolatedRay;
                float3 wsPos=_WorldSpaceCameraPos+wsDir;

                float dist=distance(wsPos,_CenterPoint);
                fixed4 col = tex2D(_MainTex, i.uv);

                if(dist<_Radius&&dist>_Radius-_Width&&linearDepth<1)
                {
                    float diffvalue=1-(_Radius-dist)/_Width;
                    fixed4 tepColor= lerp(col,_EdgeColor,diffvalue);
                    tepColor=lerp(tepColor,_EdgeColor2,pow(diffvalue,3));
                    return tepColor;
                }
                return col;
            }
            ENDCG
        }
    }
}

另外要注意的是,对于天空盒,它的深度信息是最远的,和远裁剪平面同一级。所以要想扫描不出现在天空上,需要对深度扫描的范围进行约束,使Linear01后的深度值<1。

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是一个基于URP(通用渲染管线)的扫描效果Shader示例: Shader "Custom/ScanEffect" { Properties { _ScanTex ("Scan Texture", 2D) = "white" {} _ScanSpeed ("Scan Speed", Range(0, 10)) = 1 _ScanColor ("Scan Color", Color) = (0, 1, 0, 1) _ScanWidth ("Scan Width", Range(0, 1)) = 0.1 } SubShader { Tags { "RenderType"="Opaque" "Queue"="Geometry" } 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 _ScanTex; float _ScanSpeed; float4 _ScanColor; float _ScanWidth; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; return o; } fixed4 frag (v2f i) : SV_Target { float scanline = tex2D(_ScanTex, i.uv).r; float scanpos = _Time.y * _ScanSpeed - i.uv.y; float scan = smoothstep(_ScanWidth, _ScanWidth + 0.01, scanline) * step(0, scanpos); return lerp(_ScanColor, tex2D(_MainTex, i.uv), scan); } ENDCG } } FallBack "Diffuse" } 这个Shader有四个属性: - _ScanTex:扫描线纹理 - _ScanSpeed:扫描速度 - _ScanColor:扫描线颜色 - _ScanWidth:扫描线宽度 在Pass中,首先定义了两个结构体appdata和v2f,分别用于传递输入数据和输出数据。然后定义了四个属性,其中_ScanTex使用了2D纹理类型,其他三个属性分别是一个Range类型、一个Color类型和一个Range类型。 在vert函数中,将输入数据转换为输出数据。在frag函数中,首先获取扫描线的值,并计算扫描线位置和扫描线强度。最后,使用lerp函数将扫描线颜色和原始颜色混合起来,达到扫描效果。 使用这个Shader时,只需要将它附加到需要扫描效果的材质上,并设置相应的属性即可。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值