一、主要分享和學(xué)習(xí)的內(nèi)容
????????文章是學(xué)習(xí)入門級(jí)別的文章,定位偏向于個(gè)人學(xué)習(xí)筆記,文章內(nèi)有錯(cuò)誤的點(diǎn)希望大家理性指出,感謝各位。因?yàn)閁RP本身是一個(gè)比較雜的東西,涉及到的東西也非常的多,下面主要是對(duì)于Unity URP的創(chuàng)建、簡(jiǎn)單使用(資深級(jí)別使用起來可能會(huì)設(shè)計(jì)到渲染方方面面的知識(shí),各種渲染效果,后處理等等),相關(guān)概念,原理,源碼解析,以及URP自帶Shader腳本的解析(主要是Lit腳本)。
簡(jiǎn)單枚舉一下學(xué)習(xí)的內(nèi)容:
1.URP的概念,創(chuàng)建和使用
2.URP的運(yùn)行邏輯和源碼解析
3.URP自帶Shader腳本和Lit腳本涉及的渲染知識(shí)整理
二、URP的概念,創(chuàng)建和使用
1.URP的概念
I.URP是什么
URP全稱,Universal Render Pipeline 通用渲染管線,是SRP(Scriptable Render Pipeline,可編輯渲染管線)的一個(gè)模型,它的前身是LWRP(Light Weight Render Pipeline,輕量渲染管線),在Unity的2019.3版本中,正式將LWRP改名為URP,LWRP名字也正式退出了歷史的舞臺(tái)。URP主要是應(yīng)用在移動(dòng)端上面。
II.使用URP渲染管線的優(yōu)勢(shì)
可擴(kuò)展性
Unity中最早提出SRP就是為了提高渲染管線的可編輯性,能夠給用戶提供管線自定義的空間以致
滿足不同項(xiàng)目的需求。而URP是SRP的一個(gè)模型,本身就對(duì)功能進(jìn)行模塊化了。這樣我們可以添加不同的功能模塊來滿足對(duì)應(yīng)的需求。
性能對(duì)比
性能上最大的區(qū)別就是URP是單Pass前向渲染管線,而內(nèi)置管線是多Pass前向渲染管線。所謂的前向渲染,就是在渲染物體受點(diǎn)光光照的時(shí)候,分別對(duì)每個(gè)點(diǎn)光對(duì)該物體產(chǎn)生的影響進(jìn)行計(jì)算,最后將所有光的渲染結(jié)果相加得到最終物體的顏色。內(nèi)置管線的做法是,用多個(gè)pass來渲染光照,第一個(gè)pass只渲染主光源,然后多出來的光每個(gè)光用一個(gè)pass單獨(dú)渲染。這也是為什么我們?cè)谧鍪钟蔚臅r(shí)候很少會(huì)用點(diǎn)光源。因?yàn)閷?duì)于內(nèi)置管線來說,每多一盞光,整個(gè)場(chǎng)景的drawcall就會(huì)翻倍,這個(gè)性能開銷基本是無法接受的。
URP的做法則是,在一個(gè)pass當(dāng)中,對(duì)這個(gè)物體受到的所有光源通過一個(gè)for循環(huán)一次性計(jì)算。這么做的好處有:
一個(gè)物體的光照可以在一次DrawCall中計(jì)算完畢
省去了多個(gè)Pass的上下文切換以及光柵化等開銷
但是這么做的壞處也很明顯:
只支持1盞直光
單個(gè)物體最多支持4盞點(diǎn)光
單個(gè)相機(jī)最多支持16盞燈光
因此,有了URP之后,只要控制好點(diǎn)光的范圍,我們?cè)谑钟卫锩嬉部梢宰龆帱c(diǎn)光照明了,例如釋放一個(gè)火球照亮周圍物件,這在內(nèi)置管線里基本是可以放棄的功能
支持SRP Batch
在所有SRP管線中,都可以使用SRP Batcher,這個(gè)功能可以將沒有進(jìn)行靜態(tài)合并,也沒法通過Instancing渲染的使用相同Shader的物體,通過CBuffer去保存每個(gè)物體材質(zhì)球的參數(shù),進(jìn)而在不進(jìn)行SetPassCall的情況下完成繪制。這個(gè)功能的效果是,可以大幅降低相同DrawCall情況下單個(gè)DrawCall的開銷,當(dāng)這個(gè)功能開啟的時(shí)候,你會(huì)發(fā)現(xiàn),也許你的場(chǎng)景有500個(gè)DrawCall,但實(shí)際上SetPassCall只有不到100,在相同情況下的渲染性能是要高于內(nèi)置管線不少的。不過Shader要支持SRP Batcher還是有些條件的,詳細(xì)的大家去參考SRP Batcher的文檔吧。
容易對(duì)渲染功能的更新
打比方,我們使用內(nèi)置渲染管線的功能而且要更新內(nèi)置渲染管線的東西時(shí)候,需要對(duì)Unity版本的管線,但是我們使用URP的時(shí)候,只需要管線一下URP包就可以了。
2.Universal的安裝,創(chuàng)建流程記錄
步驟一,點(diǎn)擊Unity菜單欄的window,打開下面的Package Manager
步驟二,打開Package Manager面板后搜索Universal RP,并且進(jìn)行安裝。等待安裝結(jié)束即可
步驟三,點(diǎn)擊Unity菜單欄Assets->Create->Rendering->URP Asset(with Universal Renderer),點(diǎn)擊之后就可以創(chuàng)建出來了一個(gè)Asset文件和一個(gè)Data文件。關(guān)于Asset文件和Data文件的關(guān)系需要放到下面提一嘴。
步驟四,點(diǎn)擊Unity菜單欄Project Setting。點(diǎn)擊打開Project Setting面板的Graphics,把上個(gè)步驟創(chuàng)建好的URP的Asset資源文件放到下面截圖的Scriptable Render Pipeline Setting上面,Unity會(huì)自動(dòng)生成Universal RP的Global Setting文件和內(nèi)置管線自帶的Shader不能用的時(shí)候就表明成功了。
打開Framedebugger(window->analysis->framedebugger)就能看到對(duì)應(yīng)的調(diào)用接口和自定義管線了。
3.Universal RP Asset和Universal RP Data
Universal RP Asset也就是拖拽進(jìn)Project Setting面板的Graphics選項(xiàng)里面的Scriptable Render Pipeline Settings的資源文件,它是URP管線資產(chǎn),也就是存放設(shè)置數(shù)據(jù)的地方可以進(jìn)行各種設(shè)置。通過Unity的官方文檔給出來的解釋,我們可以認(rèn)為Universal RP Asset是一個(gè)可編輯腳本的對(duì)象,其實(shí)是一個(gè)handler。比如說一個(gè)handler(Asset)打開陰影的設(shè)置,另外一個(gè)handler關(guān)閉陰影的設(shè)置,我們可以透過選擇不同的handler(Asset)來實(shí)現(xiàn)不同需求,比如說移動(dòng)端和PC端來說,移動(dòng)端的性能較弱,實(shí)時(shí)陰影的性能開銷高,那么就選擇關(guān)閉陰影的handler(Asset)放置到Scriptable Render Pipeline Setting設(shè)置中。
這里主要整理一下URP的Asset文件設(shè)置的東西,當(dāng)然這些東西可以通過Unity的URP文檔中去查詢。
I.Rendering項(xiàng)
這一項(xiàng)主要是設(shè)置控制渲染幀的核心部分。下面為該項(xiàng)設(shè)置的截圖
Depth Texture 使URP可以創(chuàng)建_CameraDepthTexture。然后,URP為場(chǎng)景中所有攝像機(jī)都默認(rèn)使用此深度紋理??梢栽贑amera的Inspector中為單個(gè)攝像機(jī)覆蓋此項(xiàng)內(nèi)容。
Opaque Texture啟動(dòng)此選項(xiàng)可為場(chǎng)景中所有攝像機(jī)都創(chuàng)建一個(gè)_CameraOpaqueTexture作為默認(rèn)設(shè)置。此設(shè)置的功能很像內(nèi)置渲染管線中的GrabPass。Opaque Texture在URP渲染任何透明網(wǎng)格之前立即提供場(chǎng)景的快照。您可以在透明著色器使用它來創(chuàng)建毛玻璃、水折射或熱浪等效果。
Opaque Downsampling 將不透明紋理上的采樣模式設(shè)置為一下三個(gè)選項(xiàng)之一:
None:使用與攝像機(jī)相同的分辨率生成不透明通道的副本。
2x Bilinear:使用雙線性濾波生成二分之一分辨率圖像。
4x Box:使用盒狀濾波生成四分之一分辨率圖像。這會(huì)產(chǎn)生柔和模糊的副本。
4x Bilinear:使用雙線性濾波生成四分之一分辨率圖像。
Terrain Holes如果禁用此選項(xiàng),URP 會(huì)在您針對(duì) Unity Player 進(jìn)行構(gòu)建時(shí)移除所有地形孔洞著色器變體,從而減少構(gòu)建時(shí)間。
II.Quality項(xiàng)
HDR啟用此選項(xiàng)可以默認(rèn)為場(chǎng)景中的每個(gè)攝像機(jī)以高動(dòng)態(tài)范圍 (HDR) 執(zhí)行渲染。使用 HDR 時(shí),圖像中最亮的部分可以大于 1。這提供了更廣泛的光強(qiáng)度,使光照看起來更逼真。有了它,即使在明亮的光線下,您仍然可以看到細(xì)節(jié)并獲得更少的飽和度。如果需要多種光照或使用泛光效果,這非常有用。如果目標(biāo)硬件是低端硬件,可以禁用此屬性以便跳過 HDR 計(jì)算,從而獲得更好的性能。
MSAA 默認(rèn)情況下,在渲染時(shí)為場(chǎng)景中的每個(gè)攝像機(jī)使用多重采樣抗鋸齒 (Multi Sample Anti-aliasing) 技術(shù)。這樣可以柔化幾何體的邊緣,使它們不會(huì)出現(xiàn)鋸齒狀或閃爍現(xiàn)象。在下拉菜單中,選擇每個(gè)像素使用的樣本數(shù):2x、4x 或 8x。選擇的樣本越多,對(duì)象邊緣越平滑。如果想跳過 MSAA 計(jì)算,或者在 2D 游戲中不需要此類計(jì)算,請(qǐng)選擇 Disabled。注意:在不支持 StoreAndResolve 存儲(chǔ)操作的移動(dòng)平臺(tái)上,如果在 URP 資源中選擇了 Opaque Texture,Unity 會(huì)在運(yùn)行時(shí)忽略 Anti Aliasing (MSAA) 屬性(如同 Anti Aliasing (MSAA) 設(shè)置為 Disabled 一樣)。
Render Scale 此滑動(dòng)條用于縮放渲染目標(biāo)分辨率(而不是當(dāng)前設(shè)備的分辨率)。如果出于性能原因要以較小的分辨率進(jìn)行渲染或需要升級(jí)渲染來提高質(zhì)量,請(qǐng)使用此屬性。這只會(huì)縮放游戲渲染。UI 渲染保留采用設(shè)備的原始分辨率。
Lighting
這些設(shè)置會(huì)影響場(chǎng)景中的光源。
如果禁用其中某些設(shè)置,則會(huì)從著色器變量中剝離相關(guān)的關(guān)鍵字。如果您確定不會(huì)在游戲或應(yīng)用程序中使用某些設(shè)置,則可以禁用它們來提高性能并縮短構(gòu)建時(shí)間。
Main Light 這些設(shè)置會(huì)影響場(chǎng)景中的主方向光。為選擇此項(xiàng),可以在 Lighting Inspector 中將其指定為 Sun Source。如果不指定太陽光源 (Sun Source),URP 會(huì)將場(chǎng)景中最亮的方向光視為主光源。可以在 Pixel Lighting 和 None 選項(xiàng)之間進(jìn)行選擇。如果選擇 None,即使設(shè)置了太陽光源,URP 也不會(huì)渲染主光源。
** Cast Shadows** 選中此復(fù)選框可以使主光源在場(chǎng)景中投射陰影。
Shadow Resolution 此屬性可以控制主光源的陰影貼圖紋理的大小。高分辨率可提供更清晰、細(xì)節(jié)更多的陰影。如果內(nèi)存或渲染時(shí)間受限,請(qǐng)嘗試降低分辨率。
Additional Lights 在此處可以選擇附加的光源來補(bǔ)充主光源。選項(xiàng)包括 Per Vertex、Per Pixel 和 Disabled。
關(guān)于陰影故障排除我會(huì)另外寫一篇學(xué)習(xí)文章。
Per Object Limit 此滑動(dòng)條可以設(shè)置影響每個(gè)游戲?qū)ο蟮母郊庸庠磾?shù)量限制。
III.Shadows項(xiàng)
這些設(shè)置可讓您配置陰影的外觀和行為方式,并在視覺質(zhì)量和性能之間找到良好的平衡。
Max Distance Unity 渲染陰影時(shí)與攝像機(jī)之間的最大距離。Unity 不會(huì)渲染超出此距離的陰影。
注意:此屬性采用公制單位,無論 Working Unit 屬性中的值如何,均是如此。
Working Unit Unity 度量陰影級(jí)聯(lián)距離的單位。
Depth Bias 使用此設(shè)置可減輕陰影暗斑。
Normal Bias 使用此設(shè)置可減輕陰影暗斑。
Cascade Count 陰影級(jí)聯(lián)的數(shù)量。使用陰影級(jí)聯(lián)可以避免靠近攝像機(jī)的陰影過于粗糙,并使陰影分辨率保持在合理的較低值。有關(guān)更多信息,請(qǐng)參閱陰影級(jí)聯(lián)頁面。增加級(jí)聯(lián)數(shù)會(huì)降低性能。級(jí)聯(lián)設(shè)置僅影響主光源。
Soft Shadows 選中此復(fù)選框可啟用對(duì)陰影貼圖的額外處理,以使它們看起來更平滑。
啟用后,Unity 使用以下陰影貼圖過濾方法:
桌面平臺(tái):5x5 帳篷過濾器,移動(dòng)平臺(tái):4 抽頭過濾器。
性能影響:高。
禁用此選項(xiàng)后,Unity 會(huì)使用默認(rèn)的硬件過濾方法對(duì)陰影貼圖進(jìn)行一次采樣。
IV.Post-processing項(xiàng)
此部分用于微調(diào)全局后期處理設(shè)置。
Post Processing 此復(fù)選框?yàn)楫?dāng)前 URP 資源開啟(選中復(fù)選框)或關(guān)閉(清除復(fù)選框)后期處理。
如果清除此復(fù)選框,Unity 會(huì)從構(gòu)建中排除后期處理著色器和紋理,除非以下條件之一成立:
構(gòu)建中的其他資源是指與后期處理相關(guān)的資源。
另一個(gè) URP 資源啟用了 Post Processing 屬性。
Post Process Data 該資源引用了供渲染器用于后期處理的著色器和紋理。
注意:只有高級(jí)自定義用例才需要更改此屬性。
Grading Mode 選擇要用于項(xiàng)目的顏色分級(jí)模式。
? High Dynamic Range:此模式最適合類似于電影制作工作流程的高精度分級(jí)。Unity 在色調(diào)映射之前應(yīng)用顏色分級(jí)。
? Low Dynamic Range:此模式遵循更經(jīng)典的工作流程。Unity 在色調(diào)映射之后應(yīng)用有限范圍的顏色分級(jí)。
LUT Size 設(shè)置通用渲染管線用于顏色分級(jí)的內(nèi)部和外部查找紋理 (LUT) 的大小。更大的大小提供更高的精度,但有潛在的性能和內(nèi)存使用成本。不能混合和搭配 LUT 大小,因此請(qǐng)?jiān)陂_始顏色分級(jí)過程之前確定好大小。
默認(rèn)值為 32,可以確保速度與質(zhì)量之間的良好平衡。
三、URP的運(yùn)行邏輯,原理和源碼解析記錄
1.關(guān)于SRP的自定義管線
在閱讀URP的代碼之前,需要對(duì)SRP進(jìn)行一遍熟悉。
首先我們可以通過一下代碼進(jìn)行SRP的Asset文件的創(chuàng)建
[CreateAssetMenu(menuName = "Rendering/Custom Render Pipeline")]
public class CustomRenderPipelineAsset : RenderPipelineAsset
{
protected override RenderPipeline CreatePipeline()
{
return new CutomRenderPipeline();
}
}
public class CutomRenderPipeline : RenderPipeline
{
protected override void Render(ScriptableRenderContext context, Camera[] cameras)
{
}
}
創(chuàng)建好以上的兩個(gè)腳本之后,就可以直接創(chuàng)建一個(gè)CustomRenderPipeline的一個(gè)Asset文件了,點(diǎn)擊Unity的工具欄里面的Assets/Create/Rendering/CustomRenderPipeline進(jìn)行對(duì)Asset文件進(jìn)行創(chuàng)建,創(chuàng)建好再把創(chuàng)建好的Asset文件配置到Project Settings面板的Graphics選項(xiàng)里面的Scriptable Render Pipeline Settings里面。如下圖:
切換Scriptable Render Pipeline Settings成功之后,Unity的Scene面板和Game面板什么都沒有就說明切換成功了,這個(gè)時(shí)候需要我們?nèi)ゾ帉懸幌翪utomRenderPipeline 腳本里面的Render方法,對(duì)渲染指令進(jìn)行提交等操作?,F(xiàn)在我們看一下CutomRenderPipeline重寫的Render方法,參數(shù)為ScriptableRenderContext和Camera數(shù)組,其實(shí)我可以理解為這里的ScriptableRenderContext 是一個(gè)配置,Camera數(shù)組里面的所有相機(jī)都會(huì)使用ScriptableRenderContext的對(duì)象進(jìn)行渲染流程。更改CutomRenderPipeline的Render方法之后,代碼如下:
public class CutomRenderPipeline : RenderPipeline
{
protected override void Render(ScriptableRenderContext context, Camera[] cameras)
{
//把當(dāng)前攝像機(jī)的屬性設(shè)置到全局的Shader屬性中(比如view矩陣,project矩陣等數(shù)據(jù))
context.SetupCameraProperties(cameras[0]);
//配置完攝像機(jī)屬性后,利用攝像機(jī)屬性對(duì)天空盒子的繪制
context.DrawSkybox(cameras[0]);
//把當(dāng)前進(jìn)行過的命令進(jìn)行一次提交
context.Submit();
}
}
這樣我們就能把天空盒子給繪制出來了。
2.關(guān)于CommandBuffer
CommandBuffer->命令緩沖,其實(shí)CommandBuffer對(duì)象就是用來收集CPU向GPU發(fā)送的渲染指令,把需要進(jìn)行的指令在一個(gè)合適的時(shí)間點(diǎn)里面對(duì)GPU進(jìn)行發(fā)送。CommandBuffer的對(duì)象一般是通過CommandBufferPool進(jìn)行實(shí)例化的,這里稍微修改一下CustomPipeline的Render方法,代碼如下:
public class CutomRenderPipeline : RenderPipeline
{
private string m_commandBufferName = "我是一個(gè)測(cè)試用的CommandBuffer";
protected override void Render(ScriptableRenderContext context, Camera[] cameras)
{
CommandBuffer cmd = CommandBufferPool.Get(m_commandBufferName);
//把當(dāng)前攝像機(jī)的屬性設(shè)置到全局的Shader屬性中(比如view矩陣,project矩陣等數(shù)據(jù))
context.SetupCameraProperties(cameras[0]);
//對(duì)RT的深度緩存,顏色緩存進(jìn)行清理
cmd.ClearRenderTarget(true,true,Color.clear);
//執(zhí)行buffer
context.ExecuteCommandBuffer(cmd);
//清理
cmd.Clear();
//配置完攝像機(jī)屬性后,利用攝像機(jī)屬性對(duì)天空盒子的繪制
context.DrawSkybox(cameras[0]);
//把當(dāng)前進(jìn)行過的命令進(jìn)行一次提交
context.Submit();
}
}
上面僅僅是使用CommandBuffer對(duì)象進(jìn)行了清理render target原理顏色上的清理。
3.繪制當(dāng)前相機(jī)觀察的物體
下面會(huì)對(duì)場(chǎng)景上的物體進(jìn)行渲染處理,我們首先得對(duì)攝像機(jī)不能看到的東西進(jìn)行剔除,雖然GPU也會(huì)方面剔除,在程序這邊剔除能減少一下CPU和GPU之間的通訊帶寬壓力,故編寫如下代碼
private bool Cull(ScripttableRenderContext context,Camera camera){
if(camera.tryGetCullingParameters(out ScriptableCullingParameters p)){
m_cullingResults = context.Cull(ref p);
return true;
}
return false;
}
修改后CustomRenderPipeline類的整體代碼為
public class CutomRenderPipeline : RenderPipeline
{
private string m_commandBufferName = "我是一個(gè)測(cè)試用的CommandBuffer";
private static ShaderTagId m_unlitShaderTagId = new ShaderTagId("SRPDefaultUnlit");
private CullingResults m_cullingResults;
protected override void Render(ScriptableRenderContext context, Camera[] cameras)
{
if (!Cull(context, cameras[0]))
{
return;
}
Setup(context,cameras[0]);
DrawVisibleGeometry(context, cameras[0]);
DrawSkyBox(context,cameras[0]);
context.Submit();
}
private bool Cull(ScriptableRenderContext context, Camera camera)
{
if (camera.TryGetCullingParameters(out ScriptableCullingParameters p))
{
m_cullingResults = context.Cull(ref p);
return true;
}
return false;
}
private void Setup(ScriptableRenderContext context, Camera camera)
{
context.SetupCameraProperties(camera);
CommandBuffer cmd = CommandBufferPool.Get(m_commandBufferName);
cmd.ClearRenderTarget(true, true, Color.clear);
context.ExecuteCommandBuffer(cmd);
cmd.Clear();
}
//繪制圖像
private void DrawVisibleGeometry(ScriptableRenderContext context, Camera camera)
{
var sortingSetting = new SortingSettings(camera);
var drawingSetting = new DrawingSettings(m_unlitShaderTagId, sortingSetting);
var filteringSetting = new FilteringSettings(RenderQueueRange.all);
context.DrawRenderers(m_cullingResults, ref drawingSetting, ref filteringSetting);
}
//繪制天空盒子
private void DrawSkyBox(ScriptableRenderContext context, Camera camera)
{
context.DrawSkybox(camera);
}
}
由上面的代碼,因?yàn)檫@個(gè)時(shí)候點(diǎn)擊打開Frame Debug,發(fā)現(xiàn)確實(shí)會(huì)對(duì)物體進(jìn)行了渲染。
但是這里發(fā)現(xiàn)物體的材質(zhì)Shader出現(xiàn)物體了。
這是因?yàn)槲疑厦娴拇a選擇使用SRPDefaultUnlit的pass進(jìn)行著色,而因?yàn)榘惭b的URP的包,所以目前場(chǎng)景默認(rèn)使用的材質(zhì)都是Lit,Lit的pass的名字叫ForwardLit,因?yàn)槟壳耙褂肧PRDefaultUnlit進(jìn)行著色的話,需要我們重新制作一個(gè)材質(zhì),那就是,我們選擇無光照的Color/Unlit即可。如下兩張圖:
到這里SRP腳本就完成了簡(jiǎn)單的編寫學(xué)習(xí)。當(dāng)然更詳細(xì)的可以去參考Catlike Coding里面查看,里面說明更加詳細(xì),會(huì)更加詳細(xì)說明多相機(jī)處理,多個(gè)層級(jí)剔除,半透明渲染和不透明渲染等等的說明介紹。
3.慢慢剖析URP運(yùn)行邏輯和原理
這一小節(jié)主要是記錄一下個(gè)人在閱讀URP源代碼的時(shí)候的一些心得。
先來點(diǎn)看開一下Unity下載的包,里面有Editor文件和Runtime文件還有其他的shader文件、shader庫等等。其實(shí)這里主要是查看一下Runtime文件里面的代碼,Editor其實(shí)是對(duì)Unity進(jìn)行自定義編輯進(jìn)行擴(kuò)展的代碼。
在啟動(dòng)游戲之后打開Frame Debuger,會(huì)看到我抓了一幀之后他所執(zhí)行的東西
我們可以看到我們只有一個(gè)主相機(jī)所以我們的URP執(zhí)行了主相機(jī)相關(guān)的渲染命令,添加另外一個(gè)相機(jī)的時(shí)候會(huì)發(fā)現(xiàn)URP會(huì)多執(zhí)行另外一個(gè)攝像機(jī)的渲染命令,這里肯定是對(duì)所有相機(jī)都執(zhí)行了一次遍歷。首先,我們?cè)诘诙?jié)簡(jiǎn)單的學(xué)習(xí)了一下SRP擴(kuò)展的編寫,我們首先找到URP里面繼承了RenderPipeline的類,因?yàn)榛旧献远x渲染管線以繼承RenderPipelineAsset為入口創(chuàng)建RenderPipeline類的子類對(duì)象,再通過重寫Render方法去進(jìn)行提交渲染指令。這里我們先找到UniversalRenderPipelineAsset類,再看看里面的CreatePipeline()方法如下:
protected override RenderPipeline CreatePipeline()
{
if (m_RendererDataList == null)
m_RendererDataList = new ScriptableRendererData[1];
// If no default data we can't create pipeline instance
if (m_RendererDataList[m_DefaultRendererIndex] == null)
{
// If previous version and current version are miss-matched then we are waiting for the upgrader to kick in
if (k_AssetPreviousVersion != k_AssetVersion)
return null;
if (m_RendererDataList[m_DefaultRendererIndex].GetType().ToString()
.Contains("Universal.ForwardRendererData"))
return null;
Debug.LogError(
$"Default Renderer is missing, make sure there is a Renderer assigned as the default on the current Universal RP asset:{UniversalRenderPipeline.asset.name}",
this);
return null;
}
DestroyRenderers();
var pipeline = new UniversalRenderPipeline(this);
CreateRenderers();
// Blitter can only be initialized after renderers have been created and ResourceReloader has been
// called on potentially empty shader resources
foreach (var data in m_RendererDataList)
{
if (data is UniversalRendererData universalData)
{
Blitter.Initialize(universalData.shaders.coreBlitPS, universalData.shaders.coreBlitColorAndDepthPS);
break;
}
}
return pipeline;
}
可以看到里面都是一些獲取UniversalURP data的一些操作。這里往下查看一下創(chuàng)建的UniversalRenderPipeline,主要是再看這個(gè)方法的Render方法都做了些什么操作。
#if UNITY_2021_1_OR_NEWER
/// <inheritdoc/>
protected override void Render(ScriptableRenderContext renderContext, List<Camera> cameras)
#else
/// <inheritdoc/>
protected override void Render(ScriptableRenderContext renderContext, Camera[] cameras)
#endif
{
#if RENDER_GRAPH_ENABLED
useRenderGraph = asset.enableRenderGraph;
#else
useRenderGraph = false;
#endif
SetHDRState(cameras);
// When HDR is active we render UI overlay per camera as we want all UI to be calibrated to white paper inside a single pass
// for performance reasons otherwise we render UI overlay after all camera
SupportedRenderingFeatures.active.rendersUIOverlay = HDROutputIsActive();
// TODO: Would be better to add Profiling name hooks into RenderPipelineManager.
// C#8 feature, only in >= 2020.2
using var profScope = new ProfilingScope(null, ProfilingSampler.Get(URPProfileId.UniversalRenderTotal));
#if UNITY_2021_1_OR_NEWER
using (new ProfilingScope(null, Profiling.Pipeline.beginContextRendering))
{
BeginContextRendering(renderContext, cameras);
}
#else
using (new ProfilingScope(null, Profiling.Pipeline.beginFrameRendering))
{
BeginFrameRendering(renderContext, cameras);
}
#endif
GraphicsSettings.lightsUseLinearIntensity = (QualitySettings.activeColorSpace == ColorSpace.Linear);
GraphicsSettings.lightsUseColorTemperature = true;
GraphicsSettings.defaultRenderingLayerMask = k_DefaultRenderingLayerMask;
SetupPerFrameShaderConstants();
XRSystem.SetDisplayMSAASamples((MSAASamples)asset.msaaSampleCount);
#if UNITY_EDITOR
// We do not want to start rendering if URP global settings are not ready (m_globalSettings is null)
// or been deleted/moved (m_globalSettings is not necessarily null)
if (m_GlobalSettings == null || UniversalRenderPipelineGlobalSettings.instance == null)
{
m_GlobalSettings = UniversalRenderPipelineGlobalSettings.Ensure();
if(m_GlobalSettings == null) return;
}
#endif
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (DebugManager.instance.isAnyDebugUIActive)
UniversalRenderPipelineDebugDisplaySettings.Instance.UpdateFrameTiming();
#endif
SortCameras(cameras);
#if UNITY_2021_1_OR_NEWER
for (int i = 0; i < cameras.Count; ++i)
#else
for (int i = 0; i < cameras.Length; ++i)
#endif
{
var camera = cameras[i];
if (IsGameCamera(camera))
{
RenderCameraStack(renderContext, camera);
}
else
{
using (new ProfilingScope(null, Profiling.Pipeline.beginCameraRendering))
{
BeginCameraRendering(renderContext, camera);
}
#if VISUAL_EFFECT_GRAPH_0_0_1_OR_NEWER
//It should be called before culling to prepare material. When there isn't any VisualEffect component, this method has no effect.
VFX.VFXManager.PrepareCamera(camera);
#endif
UpdateVolumeFramework(camera, null);
RenderSingleCameraInternal(renderContext, camera);
using (new ProfilingScope(null, Profiling.Pipeline.endCameraRendering))
{
EndCameraRendering(renderContext, camera);
}
}
}
s_RenderGraph.EndFrame();
#if UNITY_2021_1_OR_NEWER
using (new ProfilingScope(null, Profiling.Pipeline.endContextRendering))
{
EndContextRendering(renderContext, cameras);
}
#else
using (new ProfilingScope(null, Profiling.Pipeline.endFrameRendering))
{
EndFrameRendering(renderContext, cameras);
}
#endif
#if ENABLE_SHADER_DEBUG_PRINT
ShaderDebugPrintManager.instance.EndFrame();
#endif
}
根據(jù)上面代碼首先對(duì)HDR的狀態(tài)進(jìn)行設(shè)置,進(jìn)行一些狀態(tài)上的設(shè)置。Render最核心的內(nèi)容無非就是BeginRender(開始渲染)—>遍歷攝像機(jī)數(shù)組---->EndRendering(結(jié)束渲染)
而遍歷攝像機(jī)里面主要又是把主相機(jī)和堆疊相機(jī)分開處理(堆疊相機(jī)可以看下面相關(guān)的資料官方的手冊(cè)描述的很清楚),如果是堆疊相機(jī)就正常走完流程 BeginCamera -> RenderingCamera ->EndCamera,RenderingCamera 這過程基本上都是發(fā)生在RenderCameraStack方法里面,這里主要是研究一下攝像機(jī)內(nèi)部循環(huán)里面這段代碼判斷。上面這段代碼可以看到,只要是游戲的相機(jī)基本上都會(huì)進(jìn)入RenderCameraStack方法里面,下面是RenderCameraStack方法代碼片段,下面主要要做的東西基本是找到堆疊相機(jī)的最后一個(gè)相機(jī),讓相機(jī)堆疊里面的最后一個(gè)相機(jī)為最終輸出屏幕的相機(jī),其他的相機(jī)都輸出到,毫無疑問里面底層都是使用CommandBuff收集命令,通過CommadBuff和Context提交命令,使用一個(gè)RT放前面相機(jī)的輸入顏色,最終把最后一個(gè)相機(jī)作為輸出的最終相機(jī)。
/// <summary>
/// Renders a camera stack. This method calls RenderSingleCamera for each valid camera in the stack.
/// The last camera resolves the final target to screen.
/// </summary>
/// <param name="context">Render context used to record commands during execution.</param>
/// <param name="camera">Camera to render.</param>
static void RenderCameraStack(ScriptableRenderContext context, Camera baseCamera)
{
using var profScope = new ProfilingScope(null, ProfilingSampler.Get(URPProfileId.RenderCameraStack));
baseCamera.TryGetComponent<UniversalAdditionalCameraData>(out var baseCameraAdditionalData);
// Overlay cameras will be rendered stacked while rendering base cameras
if (baseCameraAdditionalData != null && baseCameraAdditionalData.renderType == CameraRenderType.Overlay)
return;
// Renderer contains a stack if it has additional data and the renderer supports stacking
// The renderer is checked if it supports Base camera. Since Base is the only relevant type at this moment.
var renderer = baseCameraAdditionalData?.scriptableRenderer;
bool supportsCameraStacking = renderer != null && renderer.SupportsCameraStackingType(CameraRenderType.Base);
List<Camera> cameraStack = (supportsCameraStacking) ? baseCameraAdditionalData?.cameraStack : null;
bool anyPostProcessingEnabled = baseCameraAdditionalData != null && baseCameraAdditionalData.renderPostProcessing;
int rendererCount = asset.m_RendererDataList.Length;
// We need to know the last active camera in the stack to be able to resolve
// rendering to screen when rendering it. The last camera in the stack is not
// necessarily the last active one as it users might disable it.
int lastActiveOverlayCameraIndex = -1;
if (cameraStack != null)
{
var baseCameraRendererType = baseCameraAdditionalData?.scriptableRenderer.GetType();
bool shouldUpdateCameraStack = false;
cameraStackRequiresDepthForPostprocessing = false;
for (int i = 0; i < cameraStack.Count; ++i)
{
Camera currCamera = cameraStack[i];
if (currCamera == null)
{
shouldUpdateCameraStack = true;
continue;
}
if (currCamera.isActiveAndEnabled)
{
currCamera.TryGetComponent<UniversalAdditionalCameraData>(out var data);
// Checking if the base and the overlay camera is of the same renderer type.
var currCameraRendererType = data?.scriptableRenderer.GetType();
if (currCameraRendererType != baseCameraRendererType)
{
Debug.LogWarning("Only cameras with compatible renderer types can be stacked. " +
$"The camera: {currCamera.name} are using the renderer {currCameraRendererType.Name}, " +
$"but the base camera: {baseCamera.name} are using {baseCameraRendererType.Name}. Will skip rendering");
continue;
}
var overlayRenderer = data.scriptableRenderer;
// Checking if they are the same renderer type but just not supporting Overlay
if ((overlayRenderer.SupportedCameraStackingTypes() & 1 << (int)CameraRenderType.Overlay) == 0)
{
Debug.LogWarning($"The camera: {currCamera.name} is using a renderer of type {renderer.GetType().Name} which does not support Overlay cameras in it's current state.");
continue;
}
if (data == null || data.renderType != CameraRenderType.Overlay)
{
Debug.LogWarning($"Stack can only contain Overlay cameras. The camera: {currCamera.name} " +
$"has a type {data.renderType} that is not supported. Will skip rendering.");
continue;
}
cameraStackRequiresDepthForPostprocessing |= CheckPostProcessForDepth();
anyPostProcessingEnabled |= data.renderPostProcessing;
lastActiveOverlayCameraIndex = i;
}
}
if (shouldUpdateCameraStack)
{
baseCameraAdditionalData.UpdateCameraStack();
}
}
// Post-processing not supported in GLES2.
anyPostProcessingEnabled &= SystemInfo.graphicsDeviceType != GraphicsDeviceType.OpenGLES2;
bool isStackedRendering = lastActiveOverlayCameraIndex != -1;
// Prepare XR rendering
var xrActive = false;
var xrRendering = baseCameraAdditionalData?.allowXRRendering ?? true;
var xrLayout = XRSystem.NewLayout();
xrLayout.AddCamera(baseCamera, xrRendering);
// With XR multi-pass enabled, each camera can be rendered multiple times with different parameters
foreach ((Camera _, XRPass xrPass) in xrLayout.GetActivePasses())
{
if (xrPass.enabled)
{
xrActive = true;
UpdateCameraStereoMatrices(baseCamera, xrPass);
}
using (new ProfilingScope(null, Profiling.Pipeline.beginCameraRendering))
{
BeginCameraRendering(context, baseCamera);
}
// Update volumeframework before initializing additional camera data
UpdateVolumeFramework(baseCamera, baseCameraAdditionalData);
InitializeCameraData(baseCamera, baseCameraAdditionalData, !isStackedRendering, out var baseCameraData);
RenderTextureDescriptor originalTargetDesc = baseCameraData.cameraTargetDescriptor;
#if ENABLE_VR && ENABLE_XR_MODULE
if (xrPass.enabled)
{
baseCameraData.xr = xrPass;
// Helper function for updating cameraData with xrPass Data
// Need to update XRSystem using baseCameraData to handle the case where camera position is modified in BeginCameraRendering
UpdateCameraData(ref baseCameraData, baseCameraData.xr);
// Handle the case where camera position is modified in BeginCameraRendering
xrLayout.ReconfigurePass(baseCameraData.xr, baseCamera);
XRSystemUniversal.BeginLateLatching(baseCamera, baseCameraData.xrUniversal);
}
#endif
// InitializeAdditionalCameraData needs to be initialized after the cameraTargetDescriptor is set because it needs to know the
// msaa level of cameraTargetDescriptor and XR modifications.
InitializeAdditionalCameraData(baseCamera, baseCameraAdditionalData, !isStackedRendering, ref baseCameraData);
#if VISUAL_EFFECT_GRAPH_0_0_1_OR_NEWER
//It should be called before culling to prepare material. When there isn't any VisualEffect component, this method has no effect.
VFX.VFXManager.PrepareCamera(baseCamera);
#endif
#if ADAPTIVE_PERFORMANCE_2_0_0_OR_NEWER
if (asset.useAdaptivePerformance)
ApplyAdaptivePerformance(ref baseCameraData);
#endif
// update the base camera flag so that the scene depth is stored if needed by overlay cameras later in the frame
baseCameraData.postProcessingRequiresDepthTexture |= cameraStackRequiresDepthForPostprocessing;
RenderSingleCamera(context, ref baseCameraData, anyPostProcessingEnabled);
using (new ProfilingScope(null, Profiling.Pipeline.endCameraRendering))
{
EndCameraRendering(context, baseCamera);
}
// Late latching is not supported after this point
if (baseCameraData.xr.enabled)
XRSystemUniversal.EndLateLatching(baseCamera, baseCameraData.xrUniversal);
if (isStackedRendering)
{
for (int i = 0; i < cameraStack.Count; ++i)
{
var currCamera = cameraStack[i];
if (!currCamera.isActiveAndEnabled)
continue;
currCamera.TryGetComponent<UniversalAdditionalCameraData>(out var currAdditionalCameraData);
// Camera is overlay and enabled
if (currAdditionalCameraData != null)
{
// Copy base settings from base camera data and initialize initialize remaining specific settings for this camera type.
CameraData overlayCameraData = baseCameraData;
overlayCameraData.camera = currCamera;
overlayCameraData.baseCamera = baseCamera;
UpdateCameraStereoMatrices(currAdditionalCameraData.camera, xrPass);
using (new ProfilingScope(null, Profiling.Pipeline.beginCameraRendering))
{
BeginCameraRendering(context, currCamera);
}
#if VISUAL_EFFECT_GRAPH_0_0_1_OR_NEWER
//It should be called before culling to prepare material. When there isn't any VisualEffect component, this method has no effect.
VFX.VFXManager.PrepareCamera(currCamera);
#endif
UpdateVolumeFramework(currCamera, currAdditionalCameraData);
bool lastCamera = i == lastActiveOverlayCameraIndex;
InitializeAdditionalCameraData(currCamera, currAdditionalCameraData, lastCamera, ref overlayCameraData);
xrLayout.ReconfigurePass(overlayCameraData.xr, currCamera);
RenderSingleCamera(context, ref overlayCameraData, anyPostProcessingEnabled);
using (new ProfilingScope(null, Profiling.Pipeline.endCameraRendering))
{
EndCameraRendering(context, currCamera);
}
}
}
}
if (baseCameraData.xr.enabled)
baseCameraData.cameraTargetDescriptor = originalTargetDesc;
}
if (xrActive)
{
CommandBuffer cmd = CommandBufferPool.Get();
XRSystem.RenderMirrorView(cmd, baseCamera);
context.ExecuteCommandBuffer(cmd);
context.Submit();
CommandBufferPool.Release(cmd);
}
XRSystem.EndLayout();
}
四、URP自帶Shader腳本和Lit腳本涉及的渲染知識(shí)整理
后面整理。文章來源:http://www.zghlxwxcb.cn/news/detail-751567.html
五、相關(guān)資料閱讀鏈接
Unity官方閱讀Universal RP文檔
Unity Universal RP Manual手冊(cè)
Catlike Coding的Scriptable RP教程
走進(jìn)LWRP(Universal RP)的世界文章來源地址http://www.zghlxwxcb.cn/news/detail-751567.html
到了這里,關(guān)于【unity基礎(chǔ)】關(guān)于學(xué)習(xí)通用渲染管線(UniversalRenderPipeline)入門級(jí)的分享筆記的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!