Unity SRP自定义渲染管线 -- 1.Custom Pipeline
該篇是對Catlike Coding這篇文章的概要總結,本人能力有限,如果有不正確的地方歡迎指正??https://catlikecoding.com/unity/tutorials/scriptable-render-pipeline/custom-pipeline/
通過這篇文章,你將學習到
- Create a pipeline asset and instance. 創建一個渲染管線資源和實例
- Cull, filter, sort, render.? ?剪裁,過濾,排序和渲染
- Keep memory clean.? ?內存清理優化
- Provide a good editing experience.? 更好的編輯器用戶體驗
This tutorial is made with Unity 2018.3.0f2.? 這篇文章的實現基于Unity 2018.3.0f2版本,是在Unity中實現一個自定義渲染管線教程系列的第一篇。
1.創建一個管線
在Unity2018之前,Unity中有Foward 和 Deferred 兩種渲染管線,渲染管線中你可以操控的部分和內容非常少。在Unity2018中,提供了可編程渲染管線(SRP),有了它,我們可以自定義管線中的很多內容,雖然大多數步驟是Unity中已經封裝好的功能(比如Culling裁剪)。在2018中,SRP還處于preview階段,但是目前的版本提供的功能和穩定性足以讓我們實現自定義渲染管線。
1.1項目設置
創建一個標準3D項目,移除掉PackageManager中除Package Manager UI外的所有其他內容。
?將Unity2018默認的Gamma空間改成線性空間(Edit / Project Settings / Player? --?Color Space?in the?Other Settingssection to?Linear)
創建一些測試用的簡單材質資源:
- 默認的standard 材質,Rendering Mode 為opaque
- 默認的standard 材質,?Rendering Mode 為Transparent?
- Unlit/Color?shader,? ?Rendering Mode 為opaque
- Unlit/Color?shader,? ?Rendering Mode 為Transparent?
2.渲染管線資源(Pipeline Asset)
Unity默認使用的是傳統的前向渲染管線,如果要使用我們自定義的,需要在Edit / Project Settings / Graphics中設置
想要設置我們自定義的RenderPipelineAsset,我們需要先為我們的自定義渲染管線創建一個RenderPipelineAsset。它繼承于RenderPipelineAsset,我們把自定義的渲染管線命名為MyPipline,所以對應的管線資源文件就命名為MyPipelineAsset。我們需要使用UnityEngine.Experimental.Rendering命名空間,因為該功能還在preview階段,以后如果正式版本發布的話可能需要更改成對應的命名空間
RenderPipelineAsset的主要目的是幫助Unity創建一個渲染管線實例,RenderPipelineAsset自身其實就是存儲各渲染管線的配置。我們可以通過overriding the?InternalCreatePipeline?方法來創建一個渲染管線實例,因為我們現在還沒定義自己的渲染管線,所以現在先返回個Null。
?用CreateAssetMenu函數來使得在編輯器中可以創建我們的管線
創建出我們自己的管線資源:
將創建好的管線Asset賦值到SRP Setting
創建一個實現了IRenderPipeline接口的類,命名為MyPipeline,它將是用于渲染過程的實例
?為了方便,我們直接繼承RenderPipeline類,它是Unity總已經實現IRenderPipeline接口基本功能的類
?現在,我們將之前InternalCreatePipeline函數中返回Null部分的代碼替換成創建我們自定義好的MyPipeline類
2.渲染
pipeline每幀渲染,Unity做的事就是使用context和active狀態的camera作為參數,調用pipeline中的render函數,進行渲染。這個過程對game 窗口,scene窗口和材質預覽窗口都是一樣的。我們現在要做的就是正確的設置好各參數,用正確的順序繪制出應該被渲染的東西。
2.1 Context(上下文)
RenderPipeline需要實現Render這個方法,它的第一個參數是Context,一個Context是一個ScriptableRenderContext結構體,作用是對Native code的橋接。該函數的第二個參數是一個camera數組。
??基類RenderPipeline的RenderPipeline.Render函數并沒有實際繪制任何東西,只是檢查了管線中的物體是否合法用于渲染,如果不是的話會拋出異常。我們調用積累中的這個函數來保持這個檢查功能。
我們可以調用command是去控制渲染狀態和繪制各種東西,其中最簡單的一個例子就是繪制天空盒,我們調用DrawSkyBox函數完成這個功能。該函數需要傳入一個攝像機作為參數,為求簡便我們使用Camera數組中的第一個。
調用了這個函數我們還是看不到任何東西,這是因為我們這是把command傳入了buffer,實際起效需要我們通過submit函數submit 這些commands。
這回我們能在game視圖中看到skybox了,我們也能在frame debugger中看到。
?2.2 Cameras
場景中可能存在多個攝像機需要挨個渲染,我們可以為單個攝像機寫一個render函數,在我們目前要實現的渲染管線中,主要關注這個函數就可以了。
想要正確的渲染Skybox和整個場景,我們需要通過Camera的位置和視角設置MVP矩陣,Unity Shader中unity_MatrixVP?就是這個矩陣。我們需要將Camera中的配置信息通過SetupCameraProperties函數寫入Context中。
2.3 Command Buffers
Context 直到我們Submit才會進行實際的渲染,在這之前,我們需要配置它并且將comands加入其中等之后執行。一些任務比如繪制Skybox有專門的函數實現,但是很多commands都是用command buffer來完成。
command buffer在UnityEngine.Rendering命名空間中,它是在srp功能之前就有的功能,我們在繪制Skybox之前創建一個command buffer。
ExecuteCommandBuffer函數將command傳入context的內部,等待submit后執行
command buffer會消耗unity native層的內存資源,當我們不再需要它的時候要立刻釋放掉。Release函數可以完成這個功能。
執行一個空的command buffer沒有意義,我們加入command buffer是為了清除render target去保證之前渲染的東西不會影響到目前。我們向buffer中加入一個clear command通過調用ClearRenderTarget函數,它包括三個參數,第一個參數是否清除depth ,第二個參數是否清除color,第三個參數是將緩沖區清除成什么顏色。
在frame debugger中我肯能夠看到command buffer執行的結果,清除了Z緩沖和stencil緩沖。?
通過Camera中的clear flags和background color參數,我們可以清除掉這個函數的硬編碼,直接使用配置好的信息?
給Command Buffer設置一個名字,在這里我們將其設置成camera的name,在frame debugger中就可以看到這個相應信息。?
2.4 Culling
我們目前只渲染了Skybox還沒有渲染場景中的物體,在渲染物體之前,我們需要先通過攝像機的視椎體對物體進行裁剪剔除,只渲染那些被我們看見,應該被渲染的物體。
通過ScriptableCullingParameters結構體和?CullResults.GetCullingParameters?函數,我們可以針對某個Camera得到裁剪的配置信息。
優化一下,可以判斷一下Camera 設置是否valid,如果不是的話,該函數會返回false,直接不渲染東西return掉。
得到裁剪的配置信息后,通弄過CullResults.Cull 函數可以得到最終的裁剪結果?
2.5 Drawing
有了場景中的可視信息后,我們可以通過DrawRenderers函數進行下一步的渲染。該函數用剪裁后的cull.visibleRenderers和
DrawRendererSettings?and?FilterRenderersSettings配置信息進行渲染。
到現在我們還是看不到任何物體,這是因為我們需要設置?FilterRenderersSettings信息,通過置為true,可以渲染everything。·
通過設置DrawRendererSettings信息,我們設置用于渲染的shader pass,在這里我們使用SRPDefaultUnlit Shader Pass。
到這步在場景中我們就可以看到不透明的物體了?
在frame debugger中可以看到透明物體也被渲染了,但是在game視圖中沒看到,這是因為渲染順序的問題,透明物體渲染時不會寫入深度緩沖,應該在最后再渲染。當透明物體被渲染后,再渲染skybox就會導致透明物體渲染不正確。?
要解決這個問題,我們需要調整渲染順序,先只渲染不透明物體。?
?之后渲染skybox,最后再渲染透明物體
?現在不透明物體,透明物體和skybox都可以正常渲染了。
?我們先渲染不透明物體,后渲染skybox的原因是因為不透明物體一定在skybox之前,會擋住skybox,這樣的話就可以減少over draw。同樣的原理,也可以用于不透明物體之間的遮擋,我們可以先對不透明物體進行排序,明確前后關系后按順序渲染,這樣被擋住物體的部分就不會參與渲染,可以增加渲染效率。
通過設置SortFlags.CommonOpaque,可以從前向后的順序渲染不透明物體
為了渲染透明物體,我們要將渲染順序改回從后到前,通過設置SortFlags.CommonTransparent,可以實現該功能
?現在,我們的自定義渲染管線可以正確的渲染不透明和透明物體了。
3.Polishing
能夠正確的渲染只是渲染管線一小部分功能,我們還要考慮很多其他東西,比如內存的分配,和Editor是否結合的更好等等問題。
3.1 Memory Allocations
不要每幀都創建CullResults結構體,因為它里面有3個List,都需要分配內存
去掉Camera.name的調用,每次調用這個,都會新生成一個string,消耗內存
緩存command Buffer,不要每幀都分配,使用clear進行重置?
3.2 Frame Debugger Sampling
通過BeginSample 和 EndSample,我們可以控制frame debugger中顯示的層級,便于調試
3.3 Rendering the Default Pipeline
因為我們自定義的這個渲染管線只支持unlit shaders,當物體使用其他shader的時候就渲染不出來了。我們可以使用一個Unity的error shader,當物體不能正確渲染的時候可以顯示其形狀,通過使用DrawDefaultPipeline函數來實現這個功能。
?我們使用Unity默認的ForwardBase pass,我們不用考慮剪裁排序這些,因為他們本來就不該被正確的渲染出來
因為我們的管線不支持ForwardBase,所以這些物體渲染的并不正確,我們用Unity中的ErrorShader生成一個material,用于渲染,渲染結果就是粉粉的我們熟悉的那種效果了。
目前是只有使用了ForwardPass的shader會得到這種效果,我們通過設置drawSettings,可以將Unity中其他shader pass加入其中。
3.4 Conditional Code Execution
通過Conditional功能,我們可以控制函數只在development版本和Editor中編譯運行,在正式build版本中會被刪掉不進行編譯
3.5 UI in Scene Window
我們不用做任何處理,就可以在game視圖中正確顯示UI,這些Unity已經為我們做好了。但是想在Scene視圖中看到UI,我們需要調用ScriptableRenderContext.EmitWorldGeometryForSceneView函數。但是要注意的是調用這個函數會導致UI在game視圖中再渲染一遍,所以我們要根據CameraType.SceneView區分是否是在scene視圖中,并且用#if UNITY_EDITOR來保證只在Editor模式下編譯這段代碼。
?
?
總結
以上是生活随笔為你收集整理的Unity SRP自定义渲染管线 -- 1.Custom Pipeline的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何彻底禁止火狐浏览器/谷歌浏览器的自动
- 下一篇: MySQL中双NDBD节点Cluster