johnsoncodehk 发表于 2021-11-17 10:53

Unity|GrabPass&CommandBuffer&OnRenderImage基础

1GrabPass
GrabPass来实现半透材质折射、热空气扭曲等效果。但是性能堪忧,特别是受限于移动端硬件,每次抓屏得到的数据要从GPU拷贝回CPU进行计算,这个数据传输过程是非常耗时的,GrabPass获取到的图像分辨率和显示屏是一致的,这意味着在高分辨率的设备上,这个过程极易造成带宽阻塞。而且GrabPass最耗费性能,OnRenderImage次之,RenderTexture最省。但是抛开剂量谈效果都是耍流氓。RenderTexture需要两个摄像机,DrawCall会翻倍,所以在复杂的场景中,OnRenderImage是最合适做抓屏的。
这里要说一下的是:URP管线并不支持GrabPass,虽然官方提到可以用OpaqueTexture代替功能,顾名思义,OpaqueTexture是在不透明物体渲染之后截取的一张RT,其中不包含半透明物体。如果想要完全和GrabPass一致,可能是不太容易做到的,Unity自带的GrabPass,能在此物体DrawCall的前一次DrawCall来Blit一张图片,而我们是无法打开一个渲染队列的,例如半透明物体整体在一个队列中,我们无法在其中插一个Blit。
2GrabPass用法
SubShader {
      Pass {
      ......
      }
    GrabPass {
          "_GrabTexture"
      }
      sampler2D _GrabTexture
    Pass {
      ......
    }
}
简单的 GrabPass { } 可将当前屏幕内容抓取到某个纹理中。在随后的通道中可通过 _GrabTexture 名称访问该纹理。注意:这种抓取通道的形式将为使用它的每个对象执行耗时的屏幕抓取操作。
GrabPass { "TextureName" } 可将当前屏幕内容抓取到纹理中,但仅为使用给定纹理名称的第一个对象在每一帧执行一次该操作。在后续通道中可通过给定纹理名称访问该纹理。场景中有多个对象在使用 GrabPass 时,这种方法更高效。
3OnRenderImage
unity shader中的pass是会顺序执行的,但是由于在图像处理中我们常常需要使用一个pass的处理结果作为另一个pass的输入,这个时候就需要用到OnRenderImage()函数了。
函数解释:MonoBehaviour.OnRenderImage(RenderTexture source,RenderTexture destination) 该函数在所有的渲染完成后由monobehavior自动调用。
官方解释:该函数允许我们使用着色器滤波操作来修改最终的图像,输入原图像source,输出的图像放在desitination里。所以,该脚本必须挂载在有相机组件的游戏对象上,在该相机渲染完成后调用OnRenderImage()函数。这里要说一下的是OnRenderImage Scriptable Render Pipeline 并不支持。要在通用渲染管线 (URP) 中创建自定义全屏效果,你需要用ScriptableRenderPass API。如果要在高清渲染管线 (HDRP) 中创建自定义全屏效果,就用Fullscreen Custom Pass的API。
代码示例:
using UnityEngine;


public class ExampleClass : MonoBehaviour
{
    // 用于处理图像的材质
    public Material mat;


    void OnRenderImage(RenderTexture src, RenderTexture dest)
    {
      // 从源 RenderTexture 读取像素,应用材质,将更新的结果复制到目标 RenderTexture
      Graphics.Blit(src, dest, mat);
      mat.SetTexture("_MainTex", source);
    }
}
4CommandBuffer
CommandBuffer与OnRenderImage看起来极为类似,不过OnRenderImage是在渲染结束后执行,而Command Buffer可以插进渲染的任何一步:
Graphics.Blit(RenderTexture src,RenderTexture dest,Material mat=null);//OnRenderImage
CommandBuffer CB=new CommandBuffer();
CB.Blit(RenderTargetIdentifier source,RenderTargetIdentifier dest,Material mat=null);//Command Buffer
如下图,我们可以根据参数指定CommandBuffer在绿色的点上添加执行命令。



好的,让我们现在来实现一个最简单的CommandBuffer。

新建一个Cube ,新建一个Shader,并且将Shader的颜色输出改为绿色。
新建脚本TestCommandBuffer.cs,挂到Cube,脚本上挂新建的Shader。
using UnityEngine;
using UnityEngine.Rendering;



public class TestCommandBuffer : MonoBehaviour
{
    public Shader shader;
    private void OnEnable()
    {
      CommandBuffer buf = new CommandBuffer();
      //设置自己的渲染。
      buf.DrawRenderer(GetComponent<Renderer>(), new Material(shader));
      //不透明物体渲染完后执行
      Camera.main.AddCommandBuffer(CameraEvent.AfterForwardOpaque, buf);
    }
}
同是一个Cube,Game中的为绿色,而Scene中的依旧。打开FrameDebug看一下,发现Cube被绘制了两次。额外多出的一次红色框框那个就是CommandBuffer的渲染。能看得出,CommandBuffer作用就是在相机渲染的某个阶段,再另外执行自己的额外渲染,至于渲染的结果就看我们怎么用了。



如果我们需要做一些需要抓屏幕的操作,如泡泡,高斯模糊玻璃面板这些是会被需要到的。具体用哪种方法,需要参考当前项目选择最优解。
页: [1]
查看完整版本: Unity|GrabPass&CommandBuffer&OnRenderImage基础