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