psql屏幕输出全部结果_CommandBuffer实现Distort屏幕扭曲效果

11c0f0d8d34eb87ffb6cf38d551ffcd8.gif

现在的游戏中很多特效都喜欢用到扭曲效果,常见的实现方案都是在Shader中使用GrabPass,来获取屏幕的画面然后去做计算。关于获取屏幕画面的几种方案的性能分析可以参考我上篇文章。

idleworm:Unity中GrabPass、OnRenderImage、TargetTexture性能测试​zhuanlan.zhihu.com
306ce504e5fd29cf3833c6400be4f3d3.png

我们先搭建一个简单的测试场景,背景是几个贴了数字格的面片,一个球体在最前面,三个面片作为扭曲的区域,测试机还是ViVO X5ProV。

456f9bd5999afa1050a1b8c91310719f.png

我们先来看下GrabPass的实现方案在低端机上的性能表现

7ce20e6889761f64ab395fe04033464d.png
GrabPass

下面来看下使用CommandBuffer实现方案在测试机上的性能表现

13e09faf471b9f12f2e21bb5074796f2.png

可以看到性能还是提升了很多的,接下来我们就来看下怎么通过CommandBuffer来实现扭曲效果。

实现的思路:

要把相机渲染的当前画面当做贴图传递给shader,然后通过一张动态的noise贴图来采样这张纹理,最后输出到屏幕上。我们每个相机都有一个Render Target,可以通过Camera.targetTexture来设置,如果这个目标为空的时候那就是直接写入到Back-Buffer里面,也就是显示在了屏幕上。GrabPass的实现就是在相机渲染完后,从Back-Buffer中把显示的内容拷贝到一张RenderTexture上面,再通过采样这种RT来扭曲画面。经过测试发现把Back-Buffer中的内容拷贝到RT上,这个操作的开销就不小了,所以我们用CommandBuffer的实现方案是,先将相机渲染的画面直接存到一张RT上面,再把RT传递给shader做计算,然后把结果通过CommandBuffer绘制到屏幕上。

033cfef0f896786cdfc753dafe0841f9.png

需要注意的几个地方:

1.当Camera设置了TargetTexture后就,原本渲染到的Back-Buffer就没有东西了,而是储存在了RT里面,所以屏幕上会显示黑色并提示没有相机在执行渲染。

0ad7e23987dc9635334acbc0b120782e.png

我们可以新建一个相机和一个Quad来负责专门显示最后输出的屏幕的结果,但是这样可能会比较麻烦。如果项目中本来就要将主相机的画面渲染到一张RenderTexture上,然后再把它显示在绘制UI的相机上,有些场景为了解决UI和三维场景穿插显示层级的时候,客户端会选择这样的方案。

这里我是在OnPreRender中设置TargetTexture,然后在OnPostRender中将TargetTexture设为null,再通过CommandBuffer在屏幕上绘制一个Quad来显示相机渲染的画面。

private void OnPreRender()
{
    renderCam.targetTexture = screenCopyRT;
}

private void OnPostRender()
{
    renderCam.targetTexture = null;
}

2.使用CommandBuffer来绘制最后显示的画面和扭曲的区域,扭曲的区域只能显示在相机原始画面的上面,这样扭曲的遮挡关系就没有办法处理了。所以我们还要把场景的深度信息也传递给Shader,在Shader里面通过深度的比较来确定遮挡关系。这里我们也不使用Camera.depthTextureMode = DepthTextureMode.Depth,然后在Shader中通过_CameraDepthTexture这样来拿到相机的深度信息。而是在设置相机的RenderTarget的时候把ColorBuffer和DepthBuffer分别输出到两张RT上,再把这两张RT传递给Shader就行了。

private void OnPreRender()
{
   renderCam.SetTargetBuffers(screenColorRT.colorBuffer , screenDepthRT.depthBuffer);
}

3.用CommandBuffer在绘制最终显示画面的时候一定要先ClearRenderTarget,把之前缓冲区的颜色和深度都清除掉,否则在移动端会有一些显示Bug。

CommandBuffer.ClearRenderTarget(true , true , Color.black);

完整的项目使用如下:

先将DistortManager脚本挂在场景的相机上

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.UI;

[RequireComponent(typeof(Camera))]
public class LX_DistortManager : MonoBehaviour
{
    //用Dictionary来管理场景中需要做扭曲效果的物体,通过挂在物体身上的脚本来添加或者移除
    private Dictionary<Renderer , Material> rendererDict = new Dictionary<Renderer , Material>();
    private const string commandBufferName = "DistortCommandBuffer";
    private const string screenColorName = "_ScreenColorTexture";
    private const string screenDepthName = "_ScreenDepthTexture";

    //CameraEvent用来指定CommandBuffer在什么时候执行
    private CameraEvent cameraEvent = CameraEvent.AfterImageEffects;
    //RT的分辨率,也会影响到最终输出到屏幕的分辨率
    public int DownSample = 0;
    //显示相机原本画面的Quad绘制距离,要在所有扭曲区域之后
    public float ShowMeshDistance = 100;
    private CommandBuffer commandBuffer;
    private Camera renderCam;
    private int screenColorID = -1;
    private int screenDepthID = -1;
    private Resolution currentResolution = new Resolution();
    private RenderTexture screenColorRT;
    private RenderTexture screenDepthRT;
    //用来显示相机原本画面的Quad
    private Mesh camShowMesh;
    private Material camShowMaterial;

    private static LX_DistortManager _distortManager;
    public static LX_DistortManager GetInstance
    {
        get
        {
            if (!_distortManager)
            {
                _distortManager = GameObject.FindObjectOfType<LX_DistortManager>();
            }
            return _distortManager;
        }
    }

    private Camera GetCamera
    {
        get
        {
            if (!renderCam)
            {
                renderCam = GetComponent<Camera>();
            }
            return renderCam;
        }
    }


    private void OnEnable()
    {

        Initialize();
        SetResolution();

        CreateCommandBuffer();
    }

    private void OnPreRender()
    {
        //当Dictionary里面有需要扭曲的物体时才执行,如果场景中没有需要扭曲的物体就会继续按照原来的方式渲染
        if (rendererDict.Count > 0)
        {
            //当屏幕分辨率发生变化是更新显示
            if (currentResolution.width != renderCam.pixelWidth || currentResolution.height != renderCam.pixelHeight)
            {
                SetResolution();
                CreateCommandBuffer();
            }
            //设置相机渲染目标的ColorBuffer和DepthBuffer分别设置到两张RenderTexture上
            renderCam.SetTargetBuffers(screenColorRT.colorBuffer , screenDepthRT.depthBuffer);
        }
    }

    private void OnPostRender()
    {
        if (rendererDict.Count > 0)
            renderCam.targetTexture = null;
    }

    private void OnDestroy()
    {
        //删除CommandBuffer
        DestroyCommandBuffer();
        //释放申请的两张RenderTexture
        RenderTexture.ReleaseTemporary(screenColorRT);
        RenderTexture.ReleaseTemporary(screenDepthRT);
        //清空Dictionary
        rendererDict.Clear();
        //删除QuadMesh
        Destroy(camShowMesh);
    }

    private void CreateCommandBuffer()
    {

        DestroyCommandBuffer();

        if (rendererDict.Count > 0)
        {
            RenderTexture.ReleaseTemporary(screenColorRT);
            RenderTexture.ReleaseTemporary(screenDepthRT);
            //申请两张RT,存储ColorBuffer的不需要深度值所以Depth值为0,存储DepthBuffer的格式为RenderTextureFormat.Depth
            screenColorRT = RenderTexture.GetTemporary(renderCam.pixelWidth >> DownSample , renderCam.pixelHeight >> DownSample , 0 , RenderTextureFormat.Default);
            screenDepthRT = RenderTexture.GetTemporary(renderCam.pixelWidth >> DownSample , renderCam.pixelHeight >> DownSample , 16 , RenderTextureFormat.Depth);
            screenColorRT.name = "CopyColorTempRT";
            screenDepthRT.name = "CopyDepthTempRT";

            commandBuffer = new CommandBuffer();
            commandBuffer.name = commandBufferName;
            commandBuffer.Clear();

            //清除之前缓冲区中的Color和Depth
            commandBuffer.ClearRenderTarget(true , true , Color.black);
            //将存储了Color和Depth的两张RT传递给shader
            commandBuffer.SetGlobalTexture(screenColorID , screenColorRT);
            commandBuffer.SetGlobalTexture(screenDepthID , screenDepthRT);
            //绘制一个Quad来显示相机原本渲染的画面
            camShowMesh = CreateCamShowMesh(ShowMeshDistance);
            commandBuffer.DrawMesh(camShowMesh , Matrix4x4.identity , camShowMaterial);
            //遍历Dictionary将场景中需要扭曲的物体绘制出来
            foreach (var dict in rendererDict)
            {
                commandBuffer.DrawRenderer(dict.Key , dict.Value);
            }
            renderCam.AddCommandBuffer(cameraEvent , commandBuffer);
        }
    }

    private void SetResolution()
    {
        currentResolution.width = renderCam.pixelWidth;
        currentResolution.height = renderCam.pixelHeight;
    }

    private void DestroyCommandBuffer()
    {
        if (commandBuffer != null)
        {
            GetCamera.RemoveCommandBuffer(cameraEvent , commandBuffer);
            commandBuffer.Clear();
            commandBuffer = null;
        }
    }

    private void Initialize()
    {
        if (!renderCam)
        {
            renderCam = GetComponent<Camera>();
        }

        if (screenColorID == -1)
        {
            screenColorID = Shader.PropertyToID(screenColorName);
        }
        if (screenDepthID == -1)
        {
            screenDepthID = Shader.PropertyToID(screenDepthName);
        }
        if (camShowMaterial == null)
        {
            camShowMaterial = new Material(Shader.Find("Custom/Common/LX_CommandBufferTex"));
        }
    }
    //往Dictionary中添加需要扭曲的物体
    public void AddRenderer(Renderer renderer , Material material)
    {
        rendererDict.Add(renderer , material);
        CreateCommandBuffer();
        //Debug.Log(rendererDict.Count);
    }
    //从Dictionary中移除不需要扭曲的物体
    public void RemoveRenderer(Renderer renderer)
    {
        rendererDict.Remove(renderer);
        CreateCommandBuffer();
        //Debug.Log(rendererDict.Count);
    }
    //通过屏幕四个点和distance在场景中绘制一个Quad
    private Mesh CreateCamShowMesh(float distance)
    {
        Vector3[] vertices = new Vector3[4];
        vertices[0] = renderCam.ScreenToWorldPoint(new Vector3(0 , 0 , distance));
        vertices[1] = renderCam.ScreenToWorldPoint(new Vector3(0 , renderCam.pixelHeight , distance));
        vertices[2] = renderCam.ScreenToWorldPoint(new Vector3(renderCam.pixelWidth , renderCam.pixelHeight , distance));
        vertices[3] = renderCam.ScreenToWorldPoint(new Vector3(renderCam.pixelWidth , 0 , distance));

        Vector2[] uvs = new Vector2[4];
        uvs[0] = new Vector2(0 , 0);
        uvs[1] = new Vector2(0 , 1);
        uvs[2] = new Vector2(1 , 1);
        uvs[3] = new Vector2(1 , 0);

        int[] triangleID = new int[6];
        triangleID[0] = 0;
        triangleID[1] = 1;
        triangleID[2] = 2;
        triangleID[3] = 2;
        triangleID[4] = 3;
        triangleID[5] = 0;

        Mesh drawMesh = new Mesh();
        drawMesh.vertices = vertices;
        drawMesh.uv = uvs;
        drawMesh.triangles = triangleID;

        return drawMesh;
    }
}

将AddDistort脚本挂在需要扭曲的物体上

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

[RequireComponent(typeof(Renderer))]
public class LX_AddDistort : MonoBehaviour
{
    private Renderer _renderer;
    private Material _material;
    private LX_DistortManager _DistortManager;

    void Start()
    {
        _renderer = this.GetComponent<Renderer>();
        _material = _renderer.sharedMaterial;
        _DistortManager = LX_DistortManager.GetInstance;
        _DistortManager.AddRenderer(_renderer , _material);
        _renderer.enabled = false;
        
    }

    private void OnEnable()
    {
        if (_DistortManager)
            _DistortManager.AddRenderer(_renderer , _material);
    }

    private void OnDisable()
    {
        if (_DistortManager)
            _DistortManager.RemoveRenderer(_renderer);
    }
    
}

用来渲染QuadMesh的Shader

Shader "Custom/Common/LX_CommandBufferTex"
{
    Properties
    {
    }
    SubShader
    {
        Tags
        { 
            "RenderType"="Opaque" 
        }
        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 pos : SV_POSITION;
            };

            sampler2D _ScreenColorTexture;

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

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_ScreenColorTexture, i.uv);
                return col;
            }
            ENDCG
        }
    }
}

用来渲染扭曲物体的Shader

Shader "Custom/FX/LX_FX_DistortCommandBuffer"
{
	Properties
	{
            _NoiseTex ("NoiseTexture", 2D) = "white" {}
	    _DistortStrength ("DistortStrength", Range(0,2)) = 0.2  
            _DistortTimeFactor ("DistortTimeFactor", Range(0,2)) = 1
            _Offset ("Offset", float) = 0
	}

	SubShader
	{
	    Tags  
            {   
                "RenderType" = "Transparent"  
                "Queue" = "Transparent"
		"IgnoreProjector"="True"
		"PreviewType"="Plane"
                "DisableBatching" = "True"
            }
		LOD 100
		Cull Off Lighting Off ZWrite Off

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

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

		struct v2f
		{
		    float2 uv : TEXCOORD0;
		    float4 grabPos : TEXCOORD1;  
		    float4 pos : SV_POSITION;
		};

		sampler2D _NoiseTex; float4 _NoiseTex_ST;
                sampler2D _ScreenColorTexture;
                sampler2D _ScreenDepthTexture;
		fixed _DistortStrength;
		fixed _DistortTimeFactor;
                fixed _Offset;
			
		v2f vert (appdata v)
		{
		    v2f o;
		    o.pos = UnityObjectToClipPos(v.vertex);
		    o.uv.xy = TRANSFORM_TEX(v.uv, _NoiseTex);
                    o.color = v.color;
                    o.grabPos = ComputeScreenPos(o.pos);
                    COMPUTE_EYEDEPTH(o.grabPos.z);
		    return o;
		}
			
		fixed4 frag (v2f i) : SV_Target
		{
                    float Zbuffer = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE_PROJ(_ScreenDepthTexture, UNITY_PROJ_COORD(i.grabPos)));
                    float2 offset = tex2D(_NoiseTex, i.uv.xy - _Time.y * _DistortTimeFactor).xy;
                    i.grabPos.xy -= offset.xy  * _DistortStrength * i.color.a;
                    fixed4 col = tex2Dproj(_ScreenColorTexture, i.grabPos + _Offset) * saturate(Zbuffer - i.grabPos.z);
		    return col;
		}
		ENDCG
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值