Custom Render Pipeline(自定義渲染管線)
——控制渲染
本節(jié)內(nèi)容
- 創(chuàng)建一個(gè)渲染管線資源和實(shí)例
- 渲染相機(jī)的視圖
- 執(zhí)行裁剪、過(guò)濾及排序
- 分離非透明侈贷、透明、以及無(wú)效的通道
- 使用多個(gè)相機(jī)
這是一個(gè)關(guān)于如何創(chuàng)建一個(gè)Custom SRP的系列教程的第一個(gè)部分牍汹,它包含了一個(gè)最基本的渲染管線的創(chuàng)建铐维,我們會(huì)在之后的章節(jié)中陸續(xù)擴(kuò)展它。
這個(gè)教程使用的Unity版本是2019.2.6f1.
(ps:文章總被吞…最后偶然看到可能會(huì)被吞的一些詞兒…嘗試改了點(diǎn)但有些意思感覺(jué)不到位~)
其他的SRP系列呢?
.
我有另一個(gè)涵蓋了可編程渲染管線的教程慎菲,但它使用的SRP的API只適用于Unity2018版本嫁蛇,而這個(gè)系列則為Unity2019及之后的版本。本系列采用一種不同的露该、更貼近現(xiàn)在技術(shù)潮流的方法睬棚,但有許多相同的主題都會(huì)被涵蓋到。如果你等不及本系列教程的更新節(jié)奏的話解幼,Unity2018系列教程仍舊對(duì)工作很有幫助抑党,直到這個(gè)系列教程的更新能夠趕上進(jìn)度。
1. 一個(gè)新的渲染管線(A new Render Pipeline)
為了渲染任何東西撵摆,Unity需要去決定繪制什么形狀底靠,在什么地方,什么時(shí)候特铝,使用了什么設(shè)置——這使得整個(gè)渲染過(guò)程變得非常復(fù)雜暑中,其復(fù)雜程度主要取決于有多少東西產(chǎn)生了影響:燈光、陰影鲫剿、透明度鳄逾、圖像效果、體積效果……這些都必須按照正確的順序處理灵莲,才能得到最終的圖像——這就是渲染管線所做的雕凹。
在過(guò)去,Unity只支持一些內(nèi)置的渲染方式(built-in RP)政冻。在Unity2018版本中則引入了可編程渲染管線(SRP)——它使得我們實(shí)現(xiàn)自己想要的一切成為了一種可能枚抵,在此同時(shí)卻仍然能夠依賴于Unity本身的一些基礎(chǔ)功能例如裁剪。在Unity2018中還基于這種新的方式添加了兩種試驗(yàn)性質(zhì)的渲染管線:輕量級(jí)渲染管線(LWRP
明场,Unity 2019.3 后變更為URP)和高清渲染管線(HDRP)俄精。而在Unity2019.3中LWRP
就不再是試驗(yàn)性質(zhì)的了,更名為通用渲染管線(URP)榕堰。
URP注定要取代當(dāng)前遺留的管線從而作為默認(rèn)選項(xiàng)竖慧。原因是嫌套,它是一個(gè)能適應(yīng)大多數(shù)選擇的渲染管線,而且能很輕松的客制化定制圾旨。本系列教程將從頭開(kāi)始創(chuàng)建一個(gè)完整的渲染通道踱讨,而不是自定義一個(gè)如上所述的渲染通道。
這個(gè)教程將以最簡(jiǎn)單的渲染管線為基礎(chǔ)砍的,使用正向渲染繪制無(wú)光照的幾何形狀痹筛。如果這一步完成了,我們就可以在之后的教程中逐漸擴(kuò)展我們的渲染管線廓鞠,例如添加照明帚稠、陰影、采用不同的渲染方法床佳、和更高級(jí)的功能和特性滋早。
1.1 工程設(shè)置(Project Setup)
在Unity 2019.2.6或更高版本中創(chuàng)建一個(gè)新的3D項(xiàng)目。我們將創(chuàng)建自己的管線砌们,所以不要選擇任何一個(gè)項(xiàng)目模板杆麸。當(dāng)項(xiàng)目打開(kāi)后,您可以到包管理器里刪除不需要的所有包浪感。在本教程中昔头,我們將只使用Unity UI包來(lái)嘗試?yán)L制UI,所以你可以保留那個(gè)包影兽。
我們將專門在線性顏色空間中工作揭斧,但Unity 2019.2仍然使用伽馬空間作為默認(rèn)值。通過(guò)Edit/Project Settings/Player/Other Settings
進(jìn)入設(shè)置峻堰,切換Color Space
為linear
未蝌。
創(chuàng)建一些不同的對(duì)象來(lái)填充默認(rèn)場(chǎng)景,例如使用標(biāo)準(zhǔn)的茧妒、無(wú)光照的不透明的或者透明的材質(zhì)。Unlit/Transparent
著色器需要一個(gè)紋理才生效左冬,所以這里提供一個(gè)UV球面映射圖桐筏。
我在測(cè)試場(chǎng)景中放置了幾個(gè)立方體,它們都是不透明的拇砰。紅色的使用標(biāo)準(zhǔn)著色器(Standard shader
)梅忌,而綠色和黃色的使用了采用Unlit/Color
著色器的材質(zhì)。藍(lán)色的球體使用標(biāo)準(zhǔn)著色器除破,渲染模式設(shè)置為透明(Transparent
)牧氮,而白色的球體使用Unlit/Transparent
著色器。
1.2 管線資源(Pipeline Asset)
目前瑰枫,Unity使用了默認(rèn)的渲染管線踱葛,我們需要用自定義渲染管線來(lái)替換它丹莲,但我們首先要為它創(chuàng)建一個(gè)資源。我們將采用Unity為了URP使用的大致相同的文件夾結(jié)構(gòu)尸诽。創(chuàng)建如下圖所示的文件夾結(jié)構(gòu)甥材,并在Runtime
文件夾下創(chuàng)建一個(gè)名為CustomRenderPipelineAsset
的c#腳本。
這個(gè)腳本必須繼承于UnityEngine.Rendering
命名空間下的RenderPipelineAsset對(duì)象性含。
using UnityEngine;
using UnityEngine.Rendering;
public class CustomRenderPipelineAsset : RenderPipelineAsset {}
RenderPipelineAsset
的主要作用是讓Unity能夠獲得一個(gè)負(fù)責(zé)渲染的管線對(duì)象實(shí)例洲赵。CustomRenderPipelineAsset
本身只是一個(gè)句柄和存儲(chǔ)設(shè)置的工具。我們還沒(méi)有進(jìn)行任何的設(shè)置商蕴,所以我們接下來(lái)要做的就是給Unity一個(gè)獲取渲染管線對(duì)象實(shí)例的方法叠萍。這就需要通過(guò)重寫(xiě)RenderPipelineAsset
里定義的抽象方法CreatePipeline()
,該方法應(yīng)該返回一個(gè)RenderPipeline
實(shí)例绪商。但是我們現(xiàn)在還沒(méi)有定義自定義的RenderPipeline
類苛谷,所以先返回一個(gè)null
。
CreatePipeline()
是用受保護(hù)的訪問(wèn)修飾符protected
定義的部宿,這意味著只有定義和擴(kuò)展RenderPipelineAsset
類才能訪問(wèn)它抄腔。
protected override RenderPipeline CreatePipeline () {
return null;
}
現(xiàn)在我們需要將CustomRenderPipelineAsset
添加到項(xiàng)目中。要做到這一點(diǎn)理张,可以在CustomRenderPipelineAsset
開(kāi)頭添加一個(gè)CreateAssetMenu屬性赫蛇。
[CreateAssetMenu]
public class CustomRenderPipelineAsset : RenderPipelineAsset { … }
這將在Asset/Create
菜單中添加一個(gè)選項(xiàng), 讓我們把它放到Rendering
子菜單中雾叭。我們將CreateAssetMenu
的menuName
屬性設(shè)置為Rendering/Custom Render Pipeline
悟耘。可以在CreateAssetMenu
之后的圓括號(hào)內(nèi)直接設(shè)置這個(gè)屬性织狐。
[CreateAssetMenu(menuName = "Rendering/Custom Render Pipeline")]
public class CustomRenderPipelineAsset : RenderPipelineAsset { … }
使用新的菜單項(xiàng)將CustomRenderPipelineAsset
添加到項(xiàng)目中暂幼,然后轉(zhuǎn)到Edit->Project settings->Graphics
設(shè)置中,并在Scriptable Render Pipeline settings
下選擇它移迫。
替換默認(rèn)的渲染管線改變了一些東西旺嬉。首先,許多選項(xiàng)從Graphics
設(shè)置中消失了厨埋,這在信息面板中也提示了邪媳。其次,我們禁用了默認(rèn)的渲染管線荡陷,而沒(méi)有提供一個(gè)有效的替換選擇雨效,因此它不再渲染任何內(nèi)容。游戲窗口、場(chǎng)景窗口和材質(zhì)預(yù)覽不再具備任何功能。如果你打開(kāi)幀調(diào)試器(Window/Analysis/Frame Debugger
)并啟用它触创,你會(huì)發(fā)現(xiàn)在游戲窗口中沒(méi)有繪制任何內(nèi)容叫潦。
1.3 渲染管線實(shí)例(Render Pipeline Instance)
創(chuàng)建一個(gè)CustomRenderPipeline
類,并將其腳本文件放在與CustomRenderPipelineAsset
相同的文件夾中凿菩。這將是我們的CustomRenderPipelineAsset
返回的渲染管線實(shí)例類型凄敢,因此它必須繼承自RenderPipeline韧衣。
using UnityEngine;
using UnityEngine.Rendering;
public class CustomRenderPipeline : RenderPipeline {}
RenderPipeline
定義了一個(gè)受保護(hù)的抽象方法Render
屠尊,我們必須重寫(xiě)它來(lái)創(chuàng)建一個(gè)具體的管線實(shí)例旷祸。它有兩個(gè)參數(shù):ScriptableRenderContext和Camera[],我們暫時(shí)將該方法內(nèi)部保留為空的讼昆。
protected override void Render (ScriptableRenderContext context, Camera[] cameras) {}
使CustomRenderPipelineAsset.CreatePipeline()
返回一個(gè)新的CustomRenderPipeline
實(shí)例托享。這將為我們提供一個(gè)有效且實(shí)用的管線,盡管它到現(xiàn)在為止還沒(méi)有渲染任何東西浸赫。
protected override RenderPipeline CreatePipeline () {
return new CustomRenderPipeline();
}
2. 渲染(Rendering)
Unity會(huì)在每一幀中調(diào)用一次渲染管線實(shí)例中的Render()
方法闰围。它傳遞一個(gè)ScriptableRenderContext
結(jié)構(gòu),提供到本地引擎的鏈接既峡,我們可以使用這個(gè)接口進(jìn)行渲染羡榴。它還會(huì)傳遞一個(gè)相機(jī)數(shù)組,因?yàn)閳?chǎng)景中可能有多個(gè)激活的相機(jī)运敢。它是RenderPipeline
提供的負(fù)責(zé)按順序渲染所有相機(jī)的一個(gè)接口校仑。
2.1 相機(jī)渲染(Camera Renderer)
每個(gè)相機(jī)都是獨(dú)立渲染的, 因此传惠,我們不會(huì)讓CustomRenderPipeline
渲染所有的相機(jī)迄沫,而是把這個(gè)職責(zé)交給單獨(dú)提供提供相機(jī)渲染功能的一個(gè)新類。我們將它命名為CameraRenderer
卦方,并給它提供一個(gè)帶有ScriptableRenderContext
和Camera
類型參數(shù)的公共的Rendert()
方法羊瘩。為了方便起見(jiàn),我們將這些參數(shù)存儲(chǔ)在字段中盼砍。
using UnityEngine;
using UnityEngine.Rendering;
public class CameraRenderer {
ScriptableRenderContext context;
Camera camera;
public void Render (ScriptableRenderContext context, Camera camera) {
this.context = context;
this.camera = camera;
}
}
讓CustomRenderPipeline
在創(chuàng)建渲染器時(shí)創(chuàng)建一個(gè)CameraRenderer
實(shí)例尘吗,然后使用Render()
方法來(lái)循環(huán)渲染所有的相機(jī)。
CameraRenderer renderer = new CameraRenderer();
protected override void Render (ScriptableRenderContext context, Camera[] cameras){
foreach (Camera camera in cameras) {
renderer.Render(context, camera);
}
}
我們的相機(jī)渲染器大致相當(dāng)于URP的可編程渲染器浇坐。這種方法將使未來(lái)支持每個(gè)相機(jī)的不同的渲染方法變得簡(jiǎn)單睬捶,例如一個(gè)用于第一人稱視角,一個(gè)用于3D地圖疊加近刘,或者正向渲染和延遲渲染擒贸。但是僅現(xiàn)在而言,我們將以相同的方式渲染所有的相機(jī)跌宛。
2.2 繪制天空盒(Drawing the Skybox)
CameraRenderer.Render()
的工作是繪制相機(jī)可以看到的所有幾何圖形。提供一個(gè)單獨(dú)的DrawVisibleGeometry()
方法使該目標(biāo)和代碼干凈整潔积仗。首先疆拘,我們將讓這個(gè)方法繪制默認(rèn)的天空盒(SkyBox),這可以通過(guò)調(diào)用ScriptableRenderContext
中提供了Camera
作為參數(shù)的DrawSkybox()
方法寂曹。
public void Render (ScriptableRenderContext context, Camera camera) {
this.context = context;
this.camera = camera;
DrawVisibleGeometry();
}
void DrawVisibleGeometry () {
context.DrawSkybox(camera);
}
這些操作還不會(huì)使天空盒出現(xiàn)哎迄,這是因?yàn)槲覀兿?code>context發(fā)出的命令被緩沖了回右。我們必須通過(guò)在context
上調(diào)用submit()
來(lái)提交排序好的指令用以執(zhí)行。讓我們?cè)谝粋€(gè)單獨(dú)的Submit()
方法中做這件事漱挚,在DrawVisibleGeometry()
之后調(diào)用它翔烁。
public void Render (ScriptableRenderContext context, Camera camera) {
this.context = context;
this.camera = camera;
DrawVisibleGeometry();
Submit();
}
void Submit () {
context.Submit();
}
天空盒最終出現(xiàn)在游戲(Game
)和場(chǎng)景(Scene
)窗口中。當(dāng)啟用天空盒時(shí)旨涝,你還可以在幀調(diào)試器(Frame Debugger
)中看到有關(guān)它的條目蹬屹, 它被列為Camera.RenderSkybox
,在它下面有一個(gè)單獨(dú)的Draw Mesh
項(xiàng)白华,它代表實(shí)際的繪制調(diào)用(DrawCall
)慨默。這對(duì)應(yīng)于游戲窗口的渲染。幀調(diào)試器不顯示其他窗口中的繪制項(xiàng)弧腥。
注意厦取,相機(jī)的朝向目前并不影響天空盒的渲染方式。我們將相機(jī)傳遞給DrawSkybox管搪,但它只決定是否應(yīng)該繪制天空盒虾攻,這是通過(guò)相機(jī)的clear flags
來(lái)控制的。
為了正確地渲染天空盒和整個(gè)場(chǎng)景更鲁,我們必須建立視圖投影矩陣(view-projection matrix
)霎箍。這個(gè)變換矩陣將相機(jī)的位置和方向(視圖矩陣)與相機(jī)的透視或投影矩陣結(jié)合在一起。它在著色器(Shader)中被稱為unity_MatrixVP
岁经,是繪制幾何圖形時(shí)使用的著色器屬性之一朋沮。當(dāng)繪制調(diào)用被選中時(shí),你可以在幀調(diào)試器的ShaderProperties
部分中查看到這個(gè)矩陣缀壤。
此時(shí)樊拓,unity_MatrixVP
矩陣總是不變的。我們必須通過(guò)SetupCameraProperties
方法塘慕,將相機(jī)的屬性應(yīng)用到ScriptableRenderContext
中筋夏。這個(gè)步驟裝配了這個(gè)矩陣和其他一些屬性。在調(diào)用DrawVisibleGeometry()
之前图呢,新建一個(gè)單獨(dú)的Setup()
方法來(lái)執(zhí)行這個(gè)操作条篷。
public void Render (ScriptableRenderContext context, Camera camera) {
this.context = context;
this.camera = camera;
Setup();
DrawVisibleGeometry();
Submit();
}
void Setup () {
context.SetupCameraProperties(camera);
}
2.3 指令緩沖區(qū)(Command Buffers)
ScriptableRenderContext
延遲了實(shí)際的渲染,直到我們提交它蛤织。在此之前赴叹,我們需要對(duì)它進(jìn)行配置,并向它添加命令以供以后執(zhí)行指蚜。有些指令(如繪制天空盒)可以通過(guò)專用接口執(zhí)行乞巧,但其他指令必須通過(guò)單獨(dú)的命令緩沖區(qū)間接發(fā)出。我們需要這樣的緩沖來(lái)繪制場(chǎng)景中的其他幾何體摊鸡。
為了獲得一個(gè)緩沖區(qū)绽媒,我們必須創(chuàng)建一個(gè)新的CommandBuffer
實(shí)例對(duì)象蚕冬。我們只需要一個(gè)緩沖區(qū),所以為CameraRenderer
創(chuàng)建一個(gè)默認(rèn)的緩沖區(qū)是辕,并在字段中存儲(chǔ)對(duì)它的引用囤热。然后為這個(gè)緩沖區(qū)命名,這樣我們就可以在幀調(diào)試器中識(shí)別它获三。這樣就可以實(shí)現(xiàn)相機(jī)渲染功能了旁蔼。
const string bufferName = "Render Camera";
CommandBuffer buffer = new CommandBuffer {
name = bufferName
};
對(duì)象初始化器語(yǔ)法是如何工作的?
.
這就好像我們寫(xiě)了buffer.name = bufferName;
作為調(diào)用構(gòu)造函數(shù)后的單獨(dú)語(yǔ)句。但在創(chuàng)建一個(gè)新的對(duì)象時(shí)石窑,可以將一個(gè)代碼塊附加到構(gòu)造函數(shù)的調(diào)用中牌芋。然后,你可以在這個(gè)代碼塊中設(shè)置對(duì)象的字段和屬性松逊,而不必顯式地引用對(duì)象的實(shí)例躺屁。它明確指出,實(shí)例應(yīng)該在設(shè)置了這些字段和屬性之后才被使用经宏。除此之外犀暑,它還允許只允許一條語(yǔ)句的情況下進(jìn)行初始化,例如广辰,我們?cè)谶@里使用的字段初始化夯接,而不需要帶有許多參數(shù)變體的構(gòu)造函數(shù)。
.
請(qǐng)注意,我們省略了調(diào)用構(gòu)造函數(shù)時(shí)的空的形參列表优俘,在使用對(duì)象初始化器語(yǔ)法時(shí)這樣做是可以的。
我們可以使用命令緩沖區(qū)來(lái)注入分析器采樣浑娜,這些采樣將同時(shí)顯示在分析器(Profiler
)和幀調(diào)試器(Frame Debugger
)中佑力。這是通過(guò)在適合的地方調(diào)用BeginSample
和EndSample
來(lái)完成的,在我們的例子中筋遭,這是在Setup()
和`Submit()方法的開(kāi)始位置打颤。必須為這兩個(gè)方法提供相同的采樣名,我們將使用緩沖區(qū)的名字漓滔。
void Setup () {
buffer.BeginSample(bufferName);
context.SetupCameraProperties(camera);
}
void Submit () {
buffer.EndSample(bufferName);
context.Submit();
}
要執(zhí)行緩沖區(qū)编饺,需要使用緩沖區(qū)作為參數(shù)在context
中調(diào)用ExecuteCommandBuffer()
方法。這將從緩沖區(qū)中復(fù)制命令响驴,但這里不會(huì)清除它透且,如果我們想重用它,我們必須在之后顯式地調(diào)用清除操作, 因?yàn)閳?zhí)行和清理總是一起完成的秽誊,所以添加一個(gè)同時(shí)做這兩件事的方法就顯得很方便鲸沮。
void Setup () {
buffer.BeginSample(bufferName);
ExecuteBuffer();
context.SetupCameraProperties(camera);
}
void Submit () {
buffer.EndSample(bufferName);
ExecuteBuffer();
context.Submit();
}
void ExecuteBuffer () {
context.ExecuteCommandBuffer(buffer);
buffer.Clear();
}
Camera.RenderSkyBox
采樣現(xiàn)在被嵌套在Render Camera
中。
2.4 清除渲染目標(biāo)(Clearing the Render Target)
無(wú)論我們繪制什么锅论,最終都會(huì)渲染到相機(jī)的渲染目標(biāo)(RenderTarget
)中讼溺,它可以是默認(rèn)的幀緩沖,但也可以是渲染紋理最易。上一幀被繪制到目標(biāo)的圖像仍然存儲(chǔ)在那里怒坯,這可能會(huì)干擾我們現(xiàn)在這一幀要渲染的圖像。為了保證正確的渲染藻懒,我們必須清除渲染目標(biāo)以去除它的舊內(nèi)容剔猿。這是通過(guò)在命令緩沖區(qū)上調(diào)用ClearRenderTarget()
方法來(lái)完成的,該一步應(yīng)該放到Setup()
方法中嬉荆。
CommandBuffer.ClearRenderTarget()
方法至少需要三個(gè)參數(shù)归敬。前兩個(gè)標(biāo)志著是否應(yīng)該清除深度和顏色數(shù)據(jù),這兩個(gè)都被設(shè)為true
鄙早。第三個(gè)參數(shù)是用作清除的顏色弄慰,我們將使用Color.clear
。
void Setup () {
buffer.BeginSample(bufferName);
buffer.ClearRenderTarget(true, true, Color.clear);
ExecuteBuffer();
context.SetupCameraProperties(camera);
}
幀調(diào)試器中現(xiàn)在出現(xiàn)了一個(gè)
Draw GL
項(xiàng)用于顯示清除操作蝶锋,它嵌套在一個(gè)額外的Render Camera
項(xiàng)中陆爽。發(fā)生這種情況是因?yàn)?code>ClearRenderTarget用命令緩沖區(qū)的名稱囊括了示例中的清除操作。在開(kāi)始我們自己的采樣之前扳缕,我們可以通過(guò)清理操作來(lái)清除多余的嵌套慌闭。這將使兩個(gè)相鄰的Render Camera
采樣被合并。
void Setup () {
buffer.ClearRenderTarget(true, true, Color.clear);
buffer.BeginSample(bufferName);
// buffer.ClearRenderTarget(true, true, Color.clear);
ExecuteBuffer();
context.SetupCameraProperties(camera);
}
Draw GL
項(xiàng)表示使用Hidden/InternalClear
著色器繪制一個(gè)全屏四邊形躯舔,該著色器被寫(xiě)入渲染目標(biāo)驴剔,這不是清除操作最有效的方式。使用這種方法是因?yàn)槲覀冃枰谠O(shè)置相機(jī)屬性之前執(zhí)行清理操作粥庄。如果我們交換這兩個(gè)步驟的順序丧失,我們就得到了快速清除的方法。
void Setup () {
context.SetupCameraProperties(camera);
buffer.ClearRenderTarget(true, true, Color.clear);
buffer.BeginSample(bufferName);
ExecuteBuffer();
//context.SetupCameraProperties(camera);
}
現(xiàn)在我們可以看到
Clear (color+Z+stencil)
惜互,這表明顏色緩沖區(qū)和深度緩沖區(qū)都被清理干凈了布讹。Z
表示深度緩沖區(qū),模板數(shù)據(jù)(stencil
)是同一緩沖區(qū)的一部分训堆。2.5 裁剪(Culling)
我們現(xiàn)在可以看到天空盒描验,但看不到我們放入場(chǎng)景中的任何物體。我們將只渲染那些對(duì)相機(jī)可見(jiàn)的物體坑鱼,而不是繪制每個(gè)對(duì)象膘流。我們從場(chǎng)景中所有帶有Renderer
組件的對(duì)象開(kāi)始,然后剔除那些處于相機(jī)視圖錐體(view frustum
)外的對(duì)象。
想要知道哪些東西可以被剔除呼股,我們跟蹤多個(gè)相機(jī)的設(shè)置和矩陣耕魄,我們可以使用ScriptableCullingParameters
結(jié)構(gòu)。我們可以調(diào)用camera
的TryGetCullingParameters()
方法彭谁,而不是自己填充這個(gè)結(jié)構(gòu)的數(shù)據(jù)屎开。它返回參數(shù)是否可以成功裁剪,如果失敗的話可以避免相機(jī)設(shè)置后邊的步驟(return
)马靠。為了獲得參數(shù)數(shù)據(jù),我們必須把它作為輸出參數(shù)提供蔼两,在前面添加out
甩鳄。在一個(gè)單獨(dú)的Cull()
方法中執(zhí)行這些操作,該方法返回true
或'false'额划。
bool Cull () {
ScriptableCullingParameters p
if (camera.TryGetCullingParameters(out p)) {
return true;
}
return false;
}
我們?yōu)槭裁匆獙?xiě)一個(gè)
out
關(guān)鍵字?
.
當(dāng)一個(gè)結(jié)構(gòu)體類型的參數(shù)被定義為輸出參數(shù)時(shí)妙啃,它的表現(xiàn)就像一個(gè)對(duì)象的引用,指向該參數(shù)所在的內(nèi)存堆棧的位置俊戳。當(dāng)方法內(nèi)部更改了參數(shù)時(shí)揖赴,它將影響該值,而不僅僅是一個(gè)副本拷貝抑胎。
.
out
關(guān)鍵字告訴我們燥滑,該方法負(fù)責(zé)正確設(shè)置參數(shù),替換之前的值阿逃。
.
Try-get
方法是表示成功或失敗以及產(chǎn)生結(jié)果的常用方法铭拧。
當(dāng)作為輸出參數(shù)使用時(shí),可以將變量聲明內(nèi)聯(lián)到參數(shù)列表中恃锉,所以讓我們這樣做:
bool Cull () {
//ScriptableCullingParameters p
if (camera.TryGetCullingParameters(out ScriptableCullingParameters p)) {
return true;
}
return false;
}
在Render()
方法中調(diào)用Setup()
之前調(diào)用Cull()
搀菩,如果Cull()
返回false
則中止它(return
)。
public void Render (ScriptableRenderContext context, Camera camera) {
this.context = context;
this.camera = camera;
if (!Cull()) {
return;
}
Setup();
DrawVisibleGeometry();
Submit();
}
實(shí)際的剔除操作是通過(guò)調(diào)用ScriptableRenderContext
中的Cull()
方法來(lái)完成的破托,它會(huì)產(chǎn)生一個(gè)CullingResults
結(jié)構(gòu)體肪跋。在Cull()
中,如果'camera.TryGetCullingParameters()'返回true
土砂,則執(zhí)行上述裁剪操作州既,并將裁剪結(jié)果存儲(chǔ)在一個(gè)字段中。在這種情況下萝映,我們必須將剔除參數(shù)p
作為引用參數(shù)傳遞易桃,方法是在參數(shù)前面寫(xiě)上ref
。
CullingResults cullingResults;
…
bool Cull () {
if (camera.TryGetCullingParameters(out ScriptableCullingParameters p)) {
cullingResults = context.Cull(ref p);
return true;
}
return false;
}
我們?yōu)槭裁匆?code>ref關(guān)鍵字?
.
ref
關(guān)鍵字的工作原理與out
類似锌俱,只是在方法中不需要為參數(shù)賦值晤郑。調(diào)用含有ref
參數(shù)方法的地方需要初始化ref
參數(shù)的值。因此,它可以用于輸入造寝,也可以用于輸出磕洪。
.
在這種情況下,ref
關(guān)鍵字被用作優(yōu)化手段诫龙,以防止傳遞相當(dāng)大的ScriptableCullingParameters
結(jié)構(gòu)體的副本拷貝析显。它是一個(gè)結(jié)構(gòu)體而不是一個(gè)對(duì)象,這就是另一個(gè)優(yōu)化签赃,以防止內(nèi)存分配谷异。
2.6 繪制幾何體(Drawing Geometry)
一旦我們知道哪些東西是可見(jiàn)的,我們就可以繼續(xù)渲染這些東西锦聊。這是通過(guò)調(diào)用context
中的DrawRenderers()
方法實(shí)現(xiàn)的歹嘹。將篩選結(jié)果cullingResults
作為參數(shù),告訴它使用了哪個(gè)渲染器孔庭。除此之外尺上,我們還必須提供繪制設(shè)置和過(guò)濾設(shè)置。這兩個(gè)都是結(jié)構(gòu)體——DrawingSettings和FilteringSettings
——我們將首先使用它們的默認(rèn)構(gòu)造函數(shù)圆到,兩者都必須通過(guò)引用(ref
)傳遞怎抛。在繪制天空盒之前,在DrawVisibleGeometry()
方法中做這些芽淡。
void DrawVisibleGeometry () {
var drawingSettings = new DrawingSettings();
var filteringSettings = new FilteringSettings();
context.DrawRenderers(
cullingResults, ref drawingSettings, ref filteringSettings
);
context.DrawSkybox(camera);
}
我們現(xiàn)在看不到任何東西马绝,因?yàn)槲覀冞€必須指出哪種著色器通道(shader pass
)是允許的。因?yàn)槲覀冊(cè)诒菊鹿?jié)中只支持無(wú)光照著色器挣菲,我們必須為SRPDefaultUnlit Pass
獲取著色器標(biāo)簽ID迹淌,讓我們這樣做并將其緩存到一個(gè)靜態(tài)字段中:
static ShaderTagId unlitShaderTagId = new ShaderTagId("SRPDefaultUnlit");
將它作為DrawingSettings
構(gòu)造函數(shù)的第一個(gè)參數(shù),同時(shí)提供一個(gè)新的SortingSettings結(jié)構(gòu)體的值己单。將相機(jī)傳遞給SortingSettings
的構(gòu)造函數(shù)唉窃,它將用于確定是采用正交排序還是基于距離的排序。
void DrawVisibleGeometry () {
var sortingSettings = new SortingSettings(camera);
var drawingSettings = new DrawingSettings(unlitShaderTagId, sortingSettings);
…
}
此外纹笼,我們還必須指出哪些渲染隊(duì)列是允許的纹份。將RenderQueueRange.all
傳遞到FilteringSettings
構(gòu)造函數(shù)中。這樣我們就包含了所有的隊(duì)列廷痘。
var filteringSettings = new FilteringSettings(RenderQueueRange.all);
只有使用無(wú)光照著色器的可見(jiàn)對(duì)象會(huì)被繪制蔓涧。所有的繪制調(diào)用都在幀調(diào)試器中列出,并在RenderLoop.Draw
下分組笋额。透明對(duì)象上發(fā)生了一些看起來(lái)很奇怪的事情元暴,但先讓我們看看對(duì)象的繪制順序。下邊這個(gè)由幀調(diào)試器顯示的兄猩,您可以通過(guò)一個(gè)接一個(gè)地選擇或使用箭頭鍵來(lái)逐步執(zhí)行繪制調(diào)用茉盏。
繪制順序看起來(lái)很雜亂鉴未。我們可以通過(guò)設(shè)置SortingSettings
的criteria屬性強(qiáng)制指定繪制順序,讓我們使用SortingCriteria.CommonOpaque
為criteria
賦值鸠姨。
var sortingSettings = new SortingSettings(camera) {
criteria = SortingCriteria.CommonOpaque
};
對(duì)象現(xiàn)在可以基本上從前到后繪制铜秆,這對(duì)于不透明對(duì)象非常理想。如果某些東西被繪制在其他東西的后面讶迁,它的隱藏片元可以被跳過(guò)连茧,這可以加快渲染速度。CommonOpaque
排序選項(xiàng)還考慮到了其他一些標(biāo)準(zhǔn)巍糯,包括渲染隊(duì)列(RenderQueue)和材質(zhì)(Materials)啸驯。
2.7 分別繪制不透明和透明幾何體(Drawing Opaque and Transparent Geometry Separately)
幀調(diào)試器向我們展示了繪制透明對(duì)象的過(guò)程,但是天空盒的繪制會(huì)覆蓋掉所有繪制在不透明對(duì)象后邊的內(nèi)容祟峦。天空盒被繪制在不透明的幾何之后罚斗,所以它的所有被覆蓋的片元可以被跳過(guò)繪制,但它同時(shí)也覆蓋了透明幾何體搀愧。這是因?yàn)橥该髦鞑粚?xiě)入深度緩沖區(qū)(depth buffer
)。它們不會(huì)覆蓋掉它們后邊的內(nèi)容疆偿,因?yàn)槲覀兛梢酝高^(guò)它們看到后邊的內(nèi)容咱筛。解決方法是先畫(huà)不透明的物體,然后畫(huà)天空盒杆故,然后再畫(huà)透明的物體迅箩。
通過(guò)將FilteringSettings
的RenderQueueRange
設(shè)置RenderQueueRange.opaque
,我們可以將其作為DrawRenderers
方法的的一項(xiàng)參數(shù)剔除透明對(duì)象处铛。
var filteringSettings = new FilteringSettings(RenderQueueRange.opaque);
然后饲趋,在繪制天空盒后再次調(diào)用DrawRenderers()
方法。但在這樣做之前撤蟆,改變renderQueueRange
為RenderQueueRange.transparent
奕塑。還要將criteria
更改為SortingCriteria.CommonTransparent
并再次設(shè)置drawingSettings.sortingSettings
。這改變了透明對(duì)象的繪制順序家肯。
context.DrawSkybox(camera);
sortingSettings.criteria = SortingCriteria.CommonTransparent;
drawingSettings.sortingSettings = sortingSettings;
filteringSettings.renderQueueRange = RenderQueueRange.transparent;
context.DrawRenderers(cullingResults, ref drawingSettings, ref filteringSettings);
為什么渲染順序改變了?
.
因?yàn)橥该鲗?duì)象不會(huì)被寫(xiě)入深度緩沖區(qū)龄砰,所以對(duì)它們進(jìn)行前后排序不會(huì)帶來(lái)性能上的好處。但是當(dāng)透明物體在視覺(jué)上互相交叉時(shí)讨衣,它們必須被從后往前繪制以正確地進(jìn)行混合换棚。
.
不幸的是,從后往前排序不能保證正確的混合反镇,因?yàn)榕判蚴轻槍?duì)每個(gè)對(duì)象的固蚤,并且只基于對(duì)象的位置。交叉現(xiàn)象和大型透明對(duì)象仍然可能產(chǎn)生不正確的結(jié)果歹茶。這個(gè)時(shí)候可以通過(guò)將幾何圖形切割成更小的多個(gè)部件來(lái)解決夕玩。
3. 編輯器下的渲染(Editor Rendering)
我們的渲染管線現(xiàn)在可以正確地繪制了無(wú)光照的物體你弦,但我們可以做一些事情來(lái)改善在Unity編輯器中使用它的體驗(yàn)。
3.1 繪制老舊的著色器(Drawing Legacy Shaders)
因?yàn)槲覀兊墓艿乐恢С譄o(wú)光照的pass风秤,使用不同pass的對(duì)象不會(huì)被渲染鳖目,它們將不可見(jiàn)。雖然這種現(xiàn)象是正確的缤弦,但它隱藏了場(chǎng)景中一些對(duì)象使用了錯(cuò)誤的著色器的真相领迈。所以我們還是單獨(dú)的渲染它們吧。
如果有人從默認(rèn)的Unity項(xiàng)目開(kāi)始碍沐,然后切換到我們的渲染管線狸捅,那么他們可能會(huì)在他們的項(xiàng)目中使用錯(cuò)誤的shader。為了覆蓋所有Unity的默認(rèn)shader累提,我們必須為Always, ForwardBase, PrepassBase, Vertex, VertexLMRGBM
和VertexLM
pass使用著色器標(biāo)簽id(shaders tag id
)尘喝。在靜態(tài)數(shù)組中跟蹤這些數(shù)據(jù)。
static ShaderTagId[] legacyShaderTagIds = {
new ShaderTagId("Always"),
new ShaderTagId("ForwardBase"),
new ShaderTagId("PrepassBase"),
new ShaderTagId("Vertex"),
new ShaderTagId("VertexLMRGBM"),
new ShaderTagId("VertexLM")
};
在繪制可見(jiàn)的幾何圖形后邊增加一個(gè)單獨(dú)的方法斋陪,用于繪制所有不支持的shader朽褪,只是使用第一次pass通道。因?yàn)檫@些都是無(wú)效的pass无虚,結(jié)果無(wú)論如何將是錯(cuò)誤的缔赠,所以我們不關(guān)心其他的一些設(shè)置項(xiàng)。我們可以通過(guò)FilteringSettings
.defaultValue屬性獲得默認(rèn)的過(guò)濾設(shè)置友题。
public void Render (ScriptableRenderContext context, Camera camera) {
…
Setup();
DrawVisibleGeometry();
DrawUnsupportedShaders();
Submit();
}
…
void DrawUnsupportedShaders () {
var drawingSettings = new DrawingSettings(
legacyShaderTagIds[0], new SortingSettings(camera)
);
var filteringSettings = FilteringSettings.defaultValue;
context.DrawRenderers(
cullingResults, ref drawingSettings, ref filteringSettings
);
}
我們可以通過(guò)調(diào)用drawingSettings
的SetShaderPassName方法并使用繪制順序索引和標(biāo)簽作為參數(shù)來(lái)繪制多個(gè)pass嗤堰。從第二個(gè)pass開(kāi)始,對(duì)數(shù)組中的所有pass都這樣做度宦,因?yàn)槲覀冊(cè)跇?gòu)造SortingSettings
時(shí)已經(jīng)設(shè)置了第一個(gè)pass踢匣。
var drawingSettings = new DrawingSettings(
legacyShaderTagIds[0], new SortingSettings(camera)
);
for (int i = 1; i < legacyShaderTagIds.Length; i++) {
drawingSettings.SetShaderPassName(i, legacyShaderTagIds[i]);
}
使用標(biāo)準(zhǔn)著色器的物體最終渲染出來(lái)了,但它們現(xiàn)在是純黑色的戈抄,因?yàn)槲覀兊匿秩竟芫€還沒(méi)有為它們?cè)O(shè)置所需的shader屬性离唬。
3.2 錯(cuò)誤材質(zhì)(Error Material)
為了清楚地指出哪些對(duì)象使用了不支持的shader,我們將使用Unity的error shader
繪制它們划鸽。用這個(gè)shader作為參數(shù)創(chuàng)建一個(gè)新材質(zhì)男娄,我們可以通過(guò)調(diào)用Shader.Find()
方法并將Hidden/InternalErrorShader
字符串作為參數(shù)來(lái)找到它。通過(guò)靜態(tài)字段來(lái)緩存這個(gè)材質(zhì)漾稀,這樣我們就不會(huì)每幀都創(chuàng)建一個(gè)新的材質(zhì)模闲。然后將其分配到drawingSettings
的overrideMaterial屬性。
static Material errorMaterial;
…
void DrawUnsupportedShaders () {
if (errorMaterial == null) {
errorMaterial =new Material(Shader.Find("Hidden/InternalErrorShader"));
}
var drawingSettings = new DrawingSettings(legacyShaderTagIds[0], new SortingSettings(camera)) {
overrideMaterial = errorMaterial
};
…
}
現(xiàn)在所有的無(wú)效對(duì)象都是可見(jiàn)的崭捍,而且顯而易見(jiàn)這是錯(cuò)誤的尸折。
3.3 局部類(Partial Class)
在開(kāi)發(fā)中繪制無(wú)效對(duì)象很有幫助,但這不適用于已發(fā)布的應(yīng)用殷蛇。所以讓我們把所有CameraRenderer
的僅編輯器有用的代碼放在一個(gè)單獨(dú)的partial類文件中实夹。首先復(fù)制原來(lái)的CameraRenderer.cs
腳本資源并將其重命名為CameraRenderer.editor
橄浓。
然后將原始的CameraRenderer
轉(zhuǎn)換為一個(gè)partial
類,并從其中移除標(biāo)簽數(shù)組亮航、錯(cuò)誤材質(zhì)和DrawUnsupportedShaders()
方法荸实。
public partial class CameraRenderer { … }
什么是局部類?
.
這是一種將類或結(jié)構(gòu)定義分割為多個(gè)部分、存儲(chǔ)在不同文件中的方法缴淋。唯一的目的是用于組織代碼結(jié)構(gòu)准给。典型的用例是將自動(dòng)生成的代碼與手工編寫(xiě)的代碼分開(kāi)。就編譯器而言,它們都是同一個(gè)類定義一部分。
清理另一個(gè)partial
類文件湘捎,使它只包含我們從另一個(gè)類中刪除的內(nèi)容。
using UnityEngine;
using UnityEngine.Rendering;
partial class CameraRenderer {
static ShaderTagId[] legacyShaderTagIds = { … };
static Material errorMaterial;
void DrawUnsupportedShaders () { … }
}
編輯器的內(nèi)容只需要存在于CameraRender.editor
部分中畔规,所以讓它以UNITY_EDITOR宏為條件。
partial class CameraRenderer {
#if UNITY_EDITOR
static ShaderTagId[] legacyShaderTagIds = { … };
static Material errorMaterial;
void DrawUnsupportedShaders () { … };
#endif
}
然而恨统,在這里編譯將失敗叁扫,因?yàn)榱硪粋€(gè)partial
腳本包含了調(diào)用DrawUnsupportedShaders()
方法,但這個(gè)方法現(xiàn)在只存在于編輯器中畜埋。為了解決這個(gè)問(wèn)題莫绣,我們讓這個(gè)方法也變?yōu)榫植康姆椒ā榇擞缮樱覀冃枰诜椒ê灻懊婕由?code>partial關(guān)鍵字兔综,這與抽象方法聲明類似饿凛。我們可以在類定義的任何部分中這樣做狞玛,所以讓我們把它放在editor
部分。完整的方法聲明也必須用partial
關(guān)鍵字進(jìn)行標(biāo)記涧窒。
partial void DrawUnsupportedShaders ();
#if UNITY_EDITOR
…
partial void DrawUnsupportedShaders () { … }
#endif
編譯現(xiàn)在成功了心肪,編譯器將剔除所有沒(méi)有完整聲明的partial
方法的調(diào)用。
我們可以讓無(wú)效對(duì)象出現(xiàn)在開(kāi)發(fā)版本中嗎?
.
是的纠吴,你可以把條件編譯宏更改為UNITY_EDITOR || DEVELOPMENT_BUILD
硬鞍。那么DrawUnsupportedShaders()
也存在于開(kāi)發(fā)版本中,但還是不會(huì)出現(xiàn)于發(fā)布版本中戴已。
.
在本系列中固该,我將始終分離所有開(kāi)發(fā)相關(guān)與僅編輯器相關(guān)的內(nèi)容。
3.4 繪制線框(Drawing Gizmos)
目前糖儡,無(wú)論是在場(chǎng)景窗口還是在游戲窗口中伐坏,我們的RP都不會(huì)繪制線框,哪怕他是激活的握联。
我們可以通過(guò)調(diào)用UnityEditor
.Handles.ShouldRenderGizmos
來(lái)檢查gizmos是否應(yīng)該被繪制桦沉。如果是的話每瞒,我們必須調(diào)用context
的DrawGizmos方法,將camera
作為它的參數(shù)纯露,第二個(gè)參數(shù)來(lái)指示應(yīng)該繪制哪個(gè)GizmoSubset線框類型剿骨。GizmoSubset
有兩個(gè)子類型,分別用于前后圖像效果埠褪。由于我們暫時(shí)還不支持圖像效果浓利,我們將兩者都調(diào)用。聲明一個(gè)新的僅編輯器生效的DrawGizmos()
方法组橄,并執(zhí)行此操作荞膘。
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
partial class CameraRenderer {
partial void DrawGizmos ();
partial void DrawUnsupportedShaders ();
#if UNITY_EDITOR
…
partial void DrawGizmos () {
if (Handles.ShouldRenderGizmos()) {
context.DrawGizmos(camera, GizmoSubset.PreImageEffects);
context.DrawGizmos(camera, GizmoSubset.PostImageEffects);
}
}
partial void DrawUnsupportedShaders () { … }
#endif
}
線框應(yīng)該在所有指令結(jié)束后被繪制。
public void Render (ScriptableRenderContext context, Camera camera) {
…
Setup();
DrawVisibleGeometry();
DrawUnsupportedShaders();
DrawGizmos();
Submit();
}
3.4 繪制Unity用戶界面(Drawing Unity UI)
我們需要注意的另一件事是Unity的游戲內(nèi)的用戶界面(UI)玉工。例如羽资,通過(guò)GameObject/UI/Button
添加一個(gè)按鈕來(lái)創(chuàng)建一個(gè)簡(jiǎn)單的UI。它將顯示在游戲窗口遵班,而不是場(chǎng)景窗口屠升。
為什么我不能創(chuàng)建一個(gè)UI按鈕?
.
你需要在你的項(xiàng)目中安裝Unity UI包。
幀調(diào)試器告訴我們狭郑,UI是單獨(dú)渲染的腹暖,而不是由我們的自定義RP渲染的。
最起碼翰萨,當(dāng)畫(huà)布(canvas
)組件的渲染模式(Render Mode
)設(shè)置為Screen Space - Overlay
時(shí)是這樣子的脏答,這是默認(rèn)設(shè)置。將其更改為Screen Space - Camera
亩鬼,并將它的Render Camera
屬性設(shè)置為主相機(jī)(main camera
)殖告,這將使它成為透明幾何渲染下的一部分。
當(dāng)UI在場(chǎng)景窗口中渲染時(shí)黄绩,它總是使用World Space
模式,這就是為什么它通常會(huì)顯得非常大玷过。而且爽丹,雖然我們可以通過(guò)在scene
窗口中編輯UI,但它并不會(huì)被繪制辛蚊。
當(dāng)為scene
窗口渲染UI時(shí)粤蝎,我們必須將UI渲染添加到世界中幾何體的渲染中。我們使用camera
作為參數(shù)袋马,通過(guò)調(diào)用ScriptableRenderContext
.EmitWorldGeometryForSceneView()來(lái)實(shí)現(xiàn)這一步初澎。在一個(gè)新的僅editor生效的PrepareForSceneWindow()
方法中執(zhí)行這個(gè)操作。當(dāng)scene camera
的cameraType
屬性等于CameraType.Sceneview
時(shí)飞蛹,我們執(zhí)行上述函數(shù)谤狡。
partial void PrepareForSceneWindow ();
#if UNITY_EDITOR
…
partial void PrepareForSceneWindow () {
if (camera.cameraType == CameraType.SceneView) {
ScriptableRenderContext.EmitWorldGeometryForSceneView(camera);
}
}
因?yàn)檫@可能會(huì)給scene
添加幾何體灸眼,所以必須在剔除操作之前完成。
PrepareForSceneWindow();
if (!Cull()) {
return;
}
4. 多相機(jī)(Multiple Cameras)
在場(chǎng)景中可能存在多個(gè)激活的相機(jī)墓懂。如果是這樣的話焰宣,我們需要確保他們可以共同工作。
4.1 兩個(gè)相機(jī)(Two Cameras)
每個(gè)相機(jī)都有一個(gè)Depth
屬性捕仔,默認(rèn)主相機(jī)為?1
匕积。它們按照深度遞增的順序進(jìn)行渲染。想要看到這個(gè)榜跌,可以復(fù)制Main Camera
闪唆,重命名為Secondary Camera
,并設(shè)置其Depth
為0钓葫。給它設(shè)置另一個(gè)不同的標(biāo)簽(tag
)也是一個(gè)好想法悄蕾,因?yàn)?code>MainCamera標(biāo)簽應(yīng)該只被一個(gè)相機(jī)使用。
場(chǎng)景現(xiàn)在被渲染兩次础浮。但最終顯示的圖像仍然是一樣的帆调,因?yàn)殇秩灸繕?biāo)(render target
)在這之間被清除了。frame debugger
中顯示了這個(gè)豆同,但因?yàn)榫哂邢嗤Q的相鄰采樣被合并了番刊,導(dǎo)致我們最終只得到一個(gè)Render Camera
采樣區(qū)間。
如果每個(gè)相機(jī)都有自己的采樣區(qū)間影锈,那渲染過(guò)程就更清晰可見(jiàn)了芹务。為了實(shí)現(xiàn)這個(gè),添加一個(gè)僅editor生效的PrepareBuffer()
方法鸭廷,使緩沖區(qū)的名稱等于相機(jī)的名稱枣抱。
partial void PrepareBuffer ();
#if UNITY_EDITOR
…
partial void PrepareBuffer () {
buffer.name = camera.name;
}
#endif
在我們?yōu)閟cene窗口做準(zhǔn)備之前調(diào)用它。
PrepareBuffer();
PrepareForSceneWindow();
4.2 處理緩沖區(qū)名稱的變化(Dealing with Changing Buffer Names)
雖然幀調(diào)試器現(xiàn)在為每個(gè)相機(jī)單獨(dú)顯示了一個(gè)的采樣結(jié)構(gòu)列表靴姿,但當(dāng)我們進(jìn)入游戲模式(play mode)時(shí)沃但,Unity的控制臺(tái)將被填充警告(warning
)消息磁滚,它警告我們BeginSample
和EndSample
的計(jì)數(shù)必須匹配佛吓。因?yàn)槲覀儗?duì)采樣和緩沖區(qū)使用了不同的名稱,使它變得混亂垂攘。除此之外维雇,每次訪問(wèn)camera
的name
屬性時(shí),我們都要分配內(nèi)存晒他,所以我們不應(yīng)該在構(gòu)建項(xiàng)目中這樣做吱型。
為了解決這兩個(gè)問(wèn)題,我們將添加一個(gè)SampleName
字符串屬性陨仅。如果我們?cè)?code>editor中津滞,我們?cè)?code>PrepareBuffer()方法中一起設(shè)置它和緩沖區(qū)的名稱铝侵,否則它只是常量bufferName
的字符串的一個(gè)別名常量。
#if UNITY_EDITOR
…
string SampleName { get; set; }
…
partial void PrepareBuffer () {
buffer.name = SampleName = camera.name;
}
#else
const string SampleName = bufferName;
#endif
在Setup()
和Submit()
方法中使用SampleName
采樣触徐。
void Setup () {
context.SetupCameraProperties(camera);
buffer.ClearRenderTarget(true, true, Color.clear);
buffer.BeginSample(SampleName);
ExecuteBuffer();
}
void Submit () {
buffer.EndSample(SampleName);
ExecuteBuffer();
context.Submit();
}
首先運(yùn)行游戲咪鲜,我們可以通過(guò)檢查分析器(Window/Analysis/Profiler
)來(lái)看到區(qū)別。切換到Hierarchy
模式并按GC Alloc
列排序撞鹉。你將看到兩個(gè)GC Alloc
的項(xiàng)∠斫迹總共分配100個(gè)字節(jié)(bytes),這是由檢索相機(jī)名稱引起的孝鹊。再往下看炊琉,你會(huì)看到這些名稱作為采樣項(xiàng)出現(xiàn):Main Camera
和Secondary Camera
。
接下來(lái)又活,選擇File-Build Settings
温自,勾選Development Build
和Autoconnect Profiler
,點(diǎn)擊Build And Run
并確保Profiler
連接并錄制皇钞。在這種情況下悼泌,我們沒(méi)有看到之前100字節(jié)的GC ALLOC
,只有一個(gè)簡(jiǎn)單的的Render Camera
采樣夹界。
其余的48個(gè)字節(jié)分配給什么?
.
這是給相機(jī)數(shù)組用的馆里,我們無(wú)法控制這個(gè)。它的大小取決于有多少相機(jī)被渲染可柿。
通過(guò)將相機(jī)名包裝在名為Editor Only
的分析器采樣中鸠踪,我們可以更清晰地表示出我們只在編輯器中分配內(nèi)存,而不是在構(gòu)建模式中复斥。要實(shí)現(xiàn)這個(gè)营密,我們需要調(diào)用來(lái)自UnityEngine.Profiling
命名空間的Profiler.BeginSample()
和Profiler.EndSample()
方法。只有BeginSample()
方法需要傳遞采樣器名目锭。
…
using UnityEngine.Profiling;
…
partial class CameraRenderer {
…
#if UNITY_EDITOR
…
partial void PrepareBuffer () {
Profiler.BeginSample("Editor Only");
buffer.name = SampleName = camera.name;
Profiler.EndSample();
}
#else
string SampleName => bufferName;
#endif
}
4.3 層(Layer)
相機(jī)也可以配置為只看到特定層上的東西评汰。這是通過(guò)調(diào)整他們的剔除遮罩(Culling Mask
)來(lái)完成的。為了看到實(shí)際的效果痢虹,讓我們把所有使用了標(biāo)準(zhǔn)著色器(standard shader
)的對(duì)象移動(dòng)到Ignore Raycast
層被去。
將該層從Main Camera
的Culling Mask
中剔除。
并使Ignore Raycast
成為Secondary Camera
唯一可以看到的層奖唯。
因?yàn)?code>Secondary Camera最后渲染惨缆,我們最終只看到無(wú)效的對(duì)象。
4.4 清除標(biāo)記(Clear Flags)
我們可以通過(guò)調(diào)整第二個(gè)被渲染的相機(jī)的清除標(biāo)記來(lái)合并兩個(gè)相機(jī)最終的結(jié)果。相機(jī)的清除標(biāo)記由CameraClearFlags枚舉定義坯墨,我們可以通過(guò)相機(jī)的clearFlags
屬性來(lái)獲取寂汇。在Setup()
方法中清除渲染目標(biāo)之前執(zhí)行此操作。
void Setup () {
context.SetupCameraProperties(camera);
CameraClearFlags flags = camera.clearFlags;
buffer.ClearRenderTarget(true, true, Color.clear);
buffer.BeginSample(SampleName);
ExecuteBuffer();
}
CameraClearFlags
枚舉定義了四個(gè)值捣染。從1到4分別是Skybox
, Color
, Depth
和Nothing
健无。這些實(shí)際上不是單獨(dú)的標(biāo)記的值,但代表著清除量的減少液斜。除了Nothing
值累贤,在flags
的值不大于Depth
的所有情況下都必須清除深度緩沖區(qū)(depth buffer
)。
buffer.ClearRenderTarget(
flags <= CameraClearFlags.Depth, true, Color.clear
);
我們只需要在flags
設(shè)置為Color
時(shí)清除顏色緩沖區(qū)少漆,因?yàn)樵谠O(shè)置為Skybox
的情況下臼膏,我們最終會(huì)替換所有之前的顏色數(shù)據(jù)。
buffer.ClearRenderTarget(
flags <= CameraClearFlags.Depth,
flags == CameraClearFlags.Color,
Color.clear
);
如果要清除為純色示损,就必須使用相機(jī)的背景色渗磅。但是因?yàn)槲覀兪窃诰€性顏色空間中渲染的,我們需要將顏色轉(zhuǎn)換成線性空間检访,所以這個(gè)情況下我們需要使用camera.backgroundColor.linear
始鱼。在其他情況下,顏色并不重要脆贵,所以我們用Color.clear
就足夠了医清。
buffer.ClearRenderTarget(
flags <= CameraClearFlags.Depth,
flags == CameraClearFlags.Color,
flags == CameraClearFlags.Color ? camera.backgroundColor.linear : Color.clear
);
因?yàn)?code>Main Camera是第一個(gè)渲染的,它的Clear Flags
應(yīng)該設(shè)置為Skybox
或Color
卖氨。當(dāng)frame debugger
啟用時(shí)会烙,我們總是從一個(gè)清除的緩沖區(qū)開(kāi)始,但通常情況下這是不能保證的筒捺。
Secondary Camera
的Clear Flags
決定了兩個(gè)相機(jī)的渲染如何合并柏腻。在設(shè)置為Sky Box
或Color
的情況下,之前的渲染結(jié)果完全被取代系吭。當(dāng)只有Depth
被清除五嫂,Secondary Camera
渲染正常,除了它不繪制一個(gè)skybox
肯尺,所以以前的結(jié)果顯示為相機(jī)背景沃缘。當(dāng)什么都沒(méi)有被清除時(shí),depth buffer
就會(huì)被保留蟆盹,所以無(wú)光照的對(duì)象最終會(huì)遮擋住無(wú)效的對(duì)象孩灯,就好像它們是由同一個(gè)相機(jī)繪制的一樣闺金。然而逾滥,由前一個(gè)相機(jī)繪制的透明物體沒(méi)有深度信息,所以它們被覆蓋了,就像skybox
之前做的那樣寨昙。
通過(guò)調(diào)整相機(jī)的Viewport Rect
讥巡,也可以將渲染區(qū)域減少到整個(gè)渲染目標(biāo)的一小部分,而渲染目標(biāo)的其余部分則不受影響舔哪。在這種情況下欢顷,清除操作是使用Hidden/InternalClear
著色器進(jìn)行的。模板緩沖區(qū)(stencil buffer
)被用于限制渲染到視口區(qū)域捉蚤。
需要注意的是,每幀渲染多個(gè)相機(jī)意味著需要多次進(jìn)行剔除缆巧、設(shè)置布持、排序等操作。為每個(gè)獨(dú)特的視圖使用一個(gè)相機(jī)通常是最有效的方式陕悬。