Unity CommandBuffer的一些學習整理


 

1.前言

近期在整理CommandBuffer這塊資料,之前的了解一直較為混亂。

算不上新東西了,但個人覺得有些時候要比加一個攝像機再轉RT廉價一些,至少省了深度排序這些操作。

本文使用兩個例子講解CommandBuffer如何使用,但在此之前稍稍總結一下官方CommandBuffer的案例。

 

 

2.官方案例

案例地址如下:

https://blogs.unity3d.com/cn/2015/02/06/extending-unity-5-rendering-pipeline-command-buffers/

文章尾部有Demo下載鏈接。

 

該demo包含3個例子。

第一個例子BlurryRefraction,和新建攝像機渲染RT類似,在渲染透明對象之前渲染屏幕,並做模糊處理。然后丟給shader轉換到對應的UV空間,轉換的代碼和Grab一樣不做贅述。

第二個例子DeferredCustomLights,這里燈光的容器模型和第三個例子的貼花容器模型差不多,都是為了空間剔除而建立的模型,燈光部分直接拿到GBuffer的數據進行繪制。

第三個例子DeferredDecals,和第二個差不多,容器模型直接是方塊,而方塊的投影方式又有點像地形的三方向投影。

 

 

3.學習案例

 

3.1 - 在延遲渲染環境下創建一個standard小球

總的來說坑還是蠻多的,unity的pbr這塊本身和管線有所交互,所以commandBuffer要在光照和GBuffer兩個階段做插入。

其實最后光照還是有一些問題,所以暫時得出結論不要嘗試直接繪制延遲光照的材質物體。

但如果是普通的vf shader沒有太多問題,放在屏幕特效前做插入即可。

總之可以算作一次實踐。

 

這是完成效果。

 

那么從頭開始,首先按照常規思路是在GBuffer之后繪制一個球。

初始代碼:

void OnEnable()
{
    mCacheCommandBuffer = new CommandBuffer();
    mCacheCommandBuffer.name = "TestCommandBuffer";
    mCacheCommandBuffer.DrawRenderer(testRenderer, testMaterial, 0, -1);
    Camera.main.AddCommandBuffer(CameraEvent.AfterGBuffer, mCacheCommandBuffer);
}

釋放:

void OnDisable()
{
    Camera.main.RemoveCommandBuffer(CameraEvent.AfterGBuffer, mCacheCommandBuffer);
    mCacheCommandBuffer.Dispose();
}

釋放時Dispose要放在RemoveCommandBuffer之后調用。

2019/07/27補充:后來我又看了一下RenderingCommandBuffers的例子,釋放時不需要手動調用Disposeh或是Release,只需要

RemoveCommandBuffer即可。

 

DrawRenderer比起DrawMesh多了很多自由度,但缺點是遇到多維子材質會比較棘手。

這里第四個參數是對應shader的pass,如果填寫-1則所有pass都繪制

 

 

繪制效果如下

(直接畫肯定是有問題的)

 

 

打開FrameDebugger看問題,把standard里所有的pass都繪制了出來這不是想要的。

這里看了下standard的pass,第三個pass針對的是延遲光照,后面都用pass 3來繪制。

 

而且還有個問題RT3的自發光信息不正確。

 

unity的GBuffer中四個RT分別是RT0-漫反射,RT1-高光,RT2-屏幕法線,RT3-自發光

翻閱了一下standard shader源碼,發現可能是缺失了間接光照信息,而間接光照信息可能沒有正確的初始化

不過發現了這么一個東西

void fragDeferred(
    VertexOutputDeferred i,
    out half4 outGBuffer0 : SV_Target0,
    out half4 outGBuffer1 : SV_Target1,
    out half4 outGBuffer2 : SV_Target2,
    out half4 outEmission : SV_Target3          // RT3: emission (rgb), --unused-- (a)
#if defined(SHADOWS_SHADOWMASK) && (UNITY_ALLOWED_MRT_COUNT > 4)
    , out half4 outShadowMask : SV_Target4       // RT4: shadowmask (rgba)
#endif
)
{
    //...
}

 

也就是說可以自己定義輸出的GBuffer,而且也可以指定只輸出某一項GBuffer的值

void frag(
    v2f i,
    out half4 outEmission : SV_Target3
)
{
    outEmission = 0;
}

有時候這個還是蠻管用的,因為在CommandBuffer里Blit很多通道拷貝不了(應該是我技術不行)。

 

繪制多加一次:

mCacheCommandBuffer.DrawRenderer(testRenderer, testMaterial, 0, 3);
mCacheCommandBuffer.DrawRenderer(testRenderer, testMaterial_fix, 0, -1);
Camera.main.AddCommandBuffer(CameraEvent.AfterGBuffer, mCacheCommandBuffer);

 

解決是解決了,但是和天空盒接觸的地方就會沒有光照。

 

查看了FrameDebugger,確認是光照部分出了問題,踩了一些坑之后發現在AfterLighting處再繪制一次即可。

void OnEnable()
{
    mCacheCommandBuffer = new CommandBuffer();
    mCacheCommandBuffer.name = "TestCommandBuffer";

    mCacheCommandBuffer.DrawRenderer(testRenderer, testMaterial, 0, 3);

    Camera.main.AddCommandBuffer(CameraEvent.AfterGBuffer, mCacheCommandBuffer);

    mCacheCommandBuffer2 = new CommandBuffer();
    mCacheCommandBuffer2.name = "TestCommandBuffer2";
    mCacheCommandBuffer2.DrawRenderer(testRenderer, testMaterial, 0, 0);
    Camera.main.AddCommandBuffer(CameraEvent.AfterLighting, mCacheCommandBuffer2);
}

 

 

但這樣依然有很多問題,例如只支持平行光,而且高光位置是錯誤的。應該是光照的初始化問題。

所以這個例子只能算作實踐,總之不推薦這類對象的繪制。

 

 

3.2 - 使用CommandBuffer對挖洞模型進行模糊

之前別人做過,覺得這個案例有些意思,自己試了一下。

完成效果如下

這是完成效果,使用Stencil挖洞可以達到模擬半透明的效果,但如果漸變速度較慢時則會造成視覺上的不適。

而通過stencil來標記主角在屏幕中的位置,然后對主角在挖洞的基礎上再做一次高斯模糊可以緩解這種視覺上的不適。

 

 

直接在原始模型上做Stencil會導致在shadow階段Stencil數據被清除。

(2018/12/14補充: 在unity的camera中有這樣一個接口:Camera.main.clearStencilAfterLightingPass,也可以嘗試使用)

 

 

但是先不急着改變CommandBuffer的位置,先切換到正向渲染下看看Stencil不被清除的結果

可以看見即使Stencil生效,挖洞區域的Stencil也被挖掉了。所以必須想另外一個辦法覆蓋這個壞的Stencil。

 

 

 

我的思路是通過DrawRenderer在RenderSkybox之后繪制一個alpha為0的主角Renderer,並且帶有正常Stencil,來覆蓋掉舊的。

 

也就是需要兩個CommandBuffer

mStencilFixCommandBuffer = new CommandBuffer();
mStencilFixCommandBuffer.name = "StencilFix";

for (int i = 0; i < playerRenderers.Length; i++)
{
    var item = playerRenderers[i];
    mStencilFixCommandBuffer.DrawRenderer(item, playerReplaceMaterial, 0, -1);
}

Camera.main.AddCommandBuffer(CameraEvent.AfterSkybox, mStencilFixCommandBuffer);

 

Camera.main.AddCommandBuffer(CameraEvent.BeforeReflections, mBlurCommandBuffer);

 

可以看見這個正確的Stencil已經繪制上去了(當然也可以用這個方法修改深度,GBuffer)

 

然后就是模糊采樣的問題,在CommandBuffer中你不能插入Lambda的CPU代碼去執行異步內容

所以這里用幾個RT來回切換做到重復采樣,這里參考官方CommandBuffer里的第一個例子,也是需要用兩個RT來回切換。

 

CommandBuffer里新建RT建議像下面這樣,而不是用RenderTexture創建:

mBlurTempRT1 = Shader.PropertyToID("BlurTempRT1");
mBlurCommandBuffer.GetTemporaryRT(mBlurTempRT1, -1, -1, 0);

當然這么用也取不出RT對象,只能通過索引進行操作。

這里GetTemporaryRT方法第二和三個參數指定了分辨率,-1為默認值,-2為一半大小分辨率,-3為1/3以此類推

 

這時我遇到了第二個坑,Stencil信息不能通過通道單獨拷貝出來,只有在和CameraTarget進行Blit操作時,才能讀到Stencil信息

讀Stencil信息是這樣的,必須Blit的目標通道有Stencil才行(有待查證),比如下面這種就有問題:

mBlurCommandBuffer.Blit(BuiltinRenderTextureType.CameraTarget, mBlurTempRT1, mat);
mBlurCommandBuffer.Blit(mBlurTempRT1, BuiltinRenderTextureType.CameraTarget);


正確用法:

mBlurCommandBuffer.Blit(BuiltinRenderTextureType.CameraTarget, mBlurTempRT1);

for (int i = 0; i < sampleNum - 1; i++)
{
    mBlurCommandBuffer.Blit(mBlurTempRT1, BuiltinRenderTextureType.CameraTarget, blurMaterial);
    mBlurCommandBuffer.Blit(BuiltinRenderTextureType.CameraTarget, mBlurTempRT1);
}

mBlurCommandBuffer.Blit(mBlurTempRT1, BuiltinRenderTextureType.CameraTarget, blurMaterial);

所以這里的模糊迭代這么做(這里理解的不太清晰,代碼還可以優化)

 

 最后效果也就達到了

 

 

 

就寫到這里,本來還想做一個用到CommandBuffer的UI面板3D模型展示,但后來還是遇到了一些問題。

所以還是創建一個新相機吧。

 

 

測試工程地址:https://gitee.com/Hont/CommandBufferExample

(unity 2017.4)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM