Catlike Coding CustomSRP部分的練習筆記,記錄了工程思路拧烦、知識點和一些注意事項忘闻。跟隨的中文翻譯版本。英文原教程頁面這里
1.自定義渲染管線
- CustomRenderPipeAsset.cs:配置管線的Asset,并返回自定義的管線實例對象恋博,用于創(chuàng)建管線資產(chǎn)齐佳。
- CustomRenderPipeline.cs:自定義渲染管線類,負責渲染债沮。渲染方式是調(diào)用攝像機的渲染方法炼吴。Unity每一幀都會調(diào)用Render方法,此方法是SRP的入口方法疫衩。
- CameraRenderer:用于管理RenderPipeline中的Camera渲染的輔助類硅蹦,方便為每個Camera調(diào)用渲染方法。分為兩個partial闷煤。
- CameraRenderer.cs: 管理發(fā)行版與開發(fā)版中通用的渲染的部分童芹。由Render()方法作為入口開始渲染
- void Cull(): 剔除.相機獲取剔除參數(shù),context執(zhí)行剔除返回剔除結(jié)果鲤拿。
- void SetUp(): 1.為攝像機添加必要的屬性假褪,比如VP變換矩陣 2.清除渲染對象:獲取相機的clear flags,以便根據(jù)相機的clear flag確定清除不清除frame buffer近顷。3.開啟性能分析器生音,采樣程序執(zhí)行過程。
- void Submit(): 提交渲染命令:ScriptableRenderContext發(fā)送的命令都是進入緩沖的幕庐,需要顯式提交命令久锥,發(fā)送了緩沖區(qū)渲染命令才會進行渲染。有的任務(wù)比如天空盒可以直接通過ScriptableRenderContext發(fā)送渲染命令异剥,而其他指令需要單獨通過CommandBuffer->ScriptableRenderContext的方式間接發(fā)出瑟由。Commandbuffer是個容器保存了渲染命令。調(diào)用
Graphics.ExecuteCommandBuffer()
方法可以立即執(zhí)行CommandBuffers中的指令而不通過ScriptableRenderContext。1.結(jié)束性能分析器采樣 2.ScriptableRenderContext.submit()
提交并執(zhí)行ScriptableRenderContext中的指令歹苦,在調(diào)用submit之前不會執(zhí)行任何指令青伤。 - void ExecuteBuffer(): Commandbuffer和ScriptableRenderContext的橋梁,從Commandbuffer向ScriptableRenderContext復制指令殴瘦,并清除Commandbuffer中的指令緩沖狠角。
- void DrawVisibleGeometry(): 繪制可見物體:需要注意繪制順序。因為透明物體渲染會關(guān)閉深度測試蚪腋。所以先渲染all過濾對象丰歌,然后渲染天空盒,會導致渲染天空盒的時候打開了深度測試屉凯,于是覆蓋了透明物體立帖。所以注意繪制順序,不透明物體-天空盒-透明物體悠砚。就不會讓天空盒覆蓋透明物體了晓勇。
- CameraRenderer.Editor.cs: 管理編輯器下的開發(fā)版的渲染的部分
- void PrepareBuffer(): 為Commandbuffer采樣準備SampleName。如果buffername相同的話灌旧,兩個相機渲染的條目會被合并在默認buffername下绑咱,為了區(qū)分兩個相機,使用相機名字定義緩沖區(qū)枢泰。為了避免在發(fā)布環(huán)境下運行時描融,每幀都訪問camera.name,造成開銷大宗苍,所以在編輯器環(huán)境下可以訪問camera name稼稿,而在發(fā)布環(huán)境下使用固定字符串。于是給buffername套個皮讳窟。只在編輯器下才會為其分配內(nèi)存。使性能分析器在層級視圖和時間軸視圖中顯示樣本敞恋。在非開發(fā)版中部署時開銷為零丽啡。
- void DrawGizmos(): 在編輯器狀態(tài)下繪制Gizmos
- void PrepareForSceneWindow(): 負責在Scene視圖中顯示UI。默認情況下在Scene視圖中看不到UI硬猫,此方法調(diào)用了ScriptableRenderContext.EmitWorldGeometryForSceneView(camera)补箍,在Scene視圖下,往Scene場景中發(fā)送UI幾何體進行渲染啸蜜,所以需要在剔除前繪制坑雅。我們將Canvas的Rendermode設(shè)置為了主相機,默認情況下這樣做會讓相機在渲染透明物體時渲染UI衬横,這就出現(xiàn)了渲染順序問題裹粤。一般情況都是用一個額外的相機獨立渲染UI的。
- void DrawUnsupportedShaders(): 默認SRP里大部分Shader都不能用蜂林,但是保留了Unlit遥诉。所以其他不支持的Shader需要以“錯誤提示”的方式繪制拇泣。
- CameraRenderer.cs: 管理發(fā)行版與開發(fā)版中通用的渲染的部分童芹。由Render()方法作為入口開始渲染
2.Draw Calls
ShaderLibrary目錄
- UnityInput.hlsl: 定義了必要的變換矩陣,這些矩陣會在運行時由Unity傳入矮锈。其中在CBUFFER的UnityPerDraw緩沖區(qū)中霉翔,定義了每個實例不同的屬性。
- Common.hlsl: 引用了UnityInput.hlsl和其他必要的hlsl庫文件苞笨,為UnityInput.hlsl的矩陣命名和庫文件中的命名提供了宏替換的橋梁债朵。
Shaders目錄
- UnlitShader.shader: UnityShader,為材質(zhì)提供供引用的著色器瀑凝,內(nèi)部定義必要的屬性葱弟、設(shè)置和參數(shù),引用UnlitPass.hlsl
- UnlitPass.hlsl: 著色器主要的代碼所在
- struct Attributes:頂點著色器輸入結(jié)構(gòu)體猜丹,內(nèi)部定義有頂點屬性芝加。特別地,為了使用GPU Instancing(后面解釋)射窒,定義有
UNITY_VERTEX_INPUT_INSTANCE_ID
來標記實例的索引藏杖。 - struct Varyings:頂點著色器的輸出結(jié)構(gòu)體和片元著色器的輸入結(jié)構(gòu)體。其中也定義有
UNITY_VERTEX_INPUT_INSTANCE_ID
- UNITY_INSTANCING_BUFFER:為了使用GPU Instancing而使用的Unity實例緩沖空間脉顿,在UnityPerMaterial緩沖區(qū)中保存了每個實例所不同的信息蝌麸。
- Varyings UnlitPassVertex(Atrributes input):頂點著色器主體函數(shù)。1.內(nèi)部對頂點進行MVP變換艾疟;2.使用
UNITY_SETUP_INSTANCE_ID(input)
提取輸入頂點數(shù)據(jù)中的實例對象索引數(shù)據(jù)来吩,并存儲到全局靜態(tài)變量中,并使用UNITY_TRANSFER_INSTANCE_ID(input, output)
將索引數(shù)據(jù)復制到輸出結(jié)構(gòu)體中蔽莱;3.提取紋理縮放和平移弟疆,并處理紋理縮放平移,傳遞到片元函數(shù)中盗冷。 - float4 UnlitPassFragment(Varyings input):片元著色器主體函數(shù)怠苔。1.提取實例索引數(shù)據(jù);2.訪問UNITY_INSTANCEING_BUFFER中 該實例的材質(zhì)其他屬性仪糖;3.透明度測試
- 其他的工作:定義2D紋理柑司,并使用SAMPLER(sampler+紋理名)為其指定采樣器。
- struct Attributes:頂點著色器輸入結(jié)構(gòu)體猜丹,內(nèi)部定義有頂點屬性芝加。特別地,為了使用GPU Instancing(后面解釋)射窒,定義有
Examples目錄
- PerObjectMaterialProperties.cs:允許每個物體可以單獨設(shè)置材質(zhì)屬性(設(shè)置本腳本后锅劝,此時SRP Batcher失效了攒驰,因為無法處理每個對象的材質(zhì)屬性,緩沖位置都不同了)
- MeshBall.cs:用來生成多個Mesh和多個小球?qū)ο蠊示簦故竞芏鄠€實例用GPU Instancing合批的效果玻粪。繪制1023個小球產(chǎn)生了3個Draw Call,每個Draw Call的最大緩沖區(qū)不一樣,需要幾個是不同平臺決定的奶段。本機每個Draw call最大511實例饥瓷。
其他修改
為CustomRenderPipeAsset.cs,CustomRenderPipeline.cs痹籍,CameraRenderer.cs類增加了設(shè)置批處理的開關(guān)呢铆。
- CameraRenderer.cs:為
void DrawVisibleGeometry()
添加了控制動態(tài)批處理和GPU Instancing的開關(guān)(修改drawingSettings傳入相應(yīng)參數(shù)即可) - CustomRenderPipeAsset.cs:增加了在管線資產(chǎn)的查看面板的選項控件,以控制開啟三種批處理蹲缠。
筆記
批處理
CPU與GPU即獨立又并行工作的橋梁是Command Buffer棺克,CPU需要渲染時就向CommandBuffer添加指令,GPU完成上一次渲染任務(wù)后就從Command buffer中取一個命令執(zhí)行线定,添加和讀取是獨立的娜谊。Command buffer又許多種類,其中包括Draw Call和Set Pass Call斤讥。Set Pass Call代表了改變渲染狀態(tài)纱皆,當切換材質(zhì)或同一材質(zhì)中Shader的不同Pass進行渲染時會觸發(fā)Set Pass Call。比如渲染1000相同物體和1000不同物體芭商,DrawCall都是1000派草,但是前者Set Pass Call是1,后者是1000铛楣。切換渲染狀態(tài)往往比Draw Call更耗時
CPU發(fā)送Draw Call需要發(fā)送很多內(nèi)容近迁,Draw Call前CPU的準備工作相比于GPU的執(zhí)行更耗時,所以如果Draw Call數(shù)量過多簸州,則會影響CPU執(zhí)行速度鉴竭,拖慢渲染進程。所以要通過批處理(Batching)降低DrawCall數(shù)量岸浑。相關(guān)技術(shù)有動態(tài)批處理和GPU Instancing搏存。降低Set Pass Call的技術(shù)有SRP Batcher。
- SRP Batcher:
一個Drawcall被一個新的材質(zhì)使用的時候助琐,需要準備進行渲染設(shè)置工作祭埂,這部分耗時是一個drawcall的主要耗時點,所以如果場景有越多的materials兵钮,就會有越多的CPU必須使用去設(shè)置GPU 數(shù)據(jù)。傳統(tǒng)的優(yōu)化做法是減少drawcall數(shù)量去提升CPU渲染性能舌界,而實際上真正的CPU消耗來自那些設(shè)置工作掘譬,而不是GPU drawcall本身。
SRPBatcher.png
SRP Batcher不會減少Draw Call數(shù)量呻拌,但可以減少Set Pass Call數(shù)量并減少繪制調(diào)用命令的開銷葱轩。CPU不需要每幀都給GPU發(fā)送數(shù)據(jù),如果這些數(shù)據(jù)不變,則會保存在GPU內(nèi)存中靴拱,每次繪制調(diào)用只需要一個指向正確內(nèi)存位置的偏移量垃喊。SRP Batcher是否被打斷依據(jù)是是不是相同的Shader變種,如果材質(zhì)不同袜炕,但Shader變種相同也不會被打斷(以往的批處理方法是要求材質(zhì)相同的)本谜。SRP Batcher會將主存中的坐標、材質(zhì)偎窘、主光源陰影參數(shù)乌助、非主光源陰影參數(shù)等分別保存在不同的CBUFFER(常量緩沖區(qū))中只有CBUFFER發(fā)生變化才會重新向GPU提交。
默認不兼容SRP Batcher陌知,設(shè)置方法:1.設(shè)置必要的CBUFFER 2.在管線構(gòu)造函數(shù)中啟用SRPBatcher
結(jié)果:不減少Draw Call他托,優(yōu)化了序列,減少了Batch仆葡,節(jié)省了CPU的渲染準備時間
- GPU Instancing:將數(shù)據(jù)一次性發(fā)到GPU赏参,然后使用一個繪制函數(shù)讓流水線利用數(shù)據(jù)繪制多個相同的物體。GPU Instancing能在一次調(diào)用中渲染多個相同網(wǎng)格的物體沿盅,CPU收集每個物體的材質(zhì)屬性和變換把篓,放入數(shù)組發(fā)到GPU,GPU遍歷數(shù)組按順序渲染嗡呼。GPU Instancing會把每個實例不同的信息存儲在緩沖區(qū)(可能是頂點緩沖區(qū)纸俭,可能是著色器Uniform變量的常量緩沖區(qū)中),然后直接操作緩沖區(qū)的數(shù)據(jù)來設(shè)置南窗。假設(shè)渲染100個相同模型揍很,每個模型有256個面。那么需要兩個緩沖區(qū)万伤,一個描述頂點信息(儲存256個三角形)窒悔,一個描述各個實例不同的變換信息。
使用方法:1.在Shader的Pass中添加應(yīng)用GPU Instancing的指令#pragma multi_compile_instancing
敌买。(材質(zhì)球上能看到開關(guān)简珠,此時Unity會為Shader生成兩種變體) 2.引入UnityInstancing.hlsl,它重新定義了宏去訪問實例的數(shù)據(jù)數(shù)組虹钮,需要知道當前渲染對象的索引聋庵,該索引是通過頂點數(shù)據(jù)提供。3.SRP Batcher優(yōu)先級比較高芙粱,不能實現(xiàn)每個物體分別的實例數(shù)據(jù)祭玉,所以把CBUFFER取消,使用UNITY_INSTANCING_BUFFER
保存每個物體的不同材質(zhì)數(shù)據(jù)春畔。4.頂點著色器從輸入中解析實例索引數(shù)據(jù)脱货,傳入片元著色器岛都。在片元著色器中解析實例索引信息,訪問UNITY_INSTANCING_BUFFER
獲取保存在其中的該實例的材質(zhì)數(shù)據(jù)振峻。
結(jié)果:同一材質(zhì)的多個不同實例減少到一個Draw Call
注意:GPU Instancing 和SRP Batcher二者只能存在其一臼疫,SRPBatcher優(yōu)先級最高,但是建議直接支持GPU Instancing而不支持SRP Batcher
- 動態(tài)批處理:每一幀把可以合批的網(wǎng)格模型進行合并扣孟,再把合并好的數(shù)據(jù)傳給CPU烫堤,然后使用同一個材質(zhì)渲染,好處是合批的物體仍可以移動哈打,這是由于Unity每幀都會合并塔逃。
限制:使用逐對象的材質(zhì)屬性會失效(因為畢竟網(wǎng)格都合批了,沒法逐對象)料仗,頂點規(guī)模也有限制湾盗,最大不超過900,適用于共享材質(zhì)的小型網(wǎng)格立轧。
使用方法:為DrawingSettings
設(shè)置屬性即可格粪。
透明
在處理透明時,渲染順序是很重要的氛改。渲染不透明物體時帐萎,由于深度緩沖,在不考慮渲染順序時也能得到正確的渲染結(jié)果胜卤,此時既開啟深度測試Z-test疆导,也開啟了深度寫入Z-write。而在渲染透明物體時葛躏,因為使用透明度混合澈段,我們會關(guān)閉深度寫入(不更新深度)。
- 透明度測試:片元沒有通過測試就會被丟棄舰攒,不會對顏色緩沖產(chǎn)生影響败富。通過測試就按照處理不透明物體的方式處理,正常進行深度測試和深度寫入摩窃。會使Early-z失效兽叮。
使用方法:1.定義閾值屬性,將閾值屬性放進GPU Instancing緩沖 2.在片元著色器中使用clip函數(shù)進行測試透明度 - 透明度混合:使用當前片元的透明度作為混合因子猾愿,與顏色緩沖中的顏色進行混合鹦聪,得到新的顏色。透明度混合需要關(guān)閉深度寫入(不關(guān)閉深度測試蒂秘,相當于深度緩沖是只讀的)椎麦,所以需要特別注意渲染順序。
使用方法:1.設(shè)置Blend SrcFactor DstFactor指令:properties中設(shè)置混合模式材彪,在Pass中定義混合模式观挎。2.增加控制是否寫入深度 3.將不透明物體的渲染隊列設(shè)置為Transparent.
其他
- 使用紋理:1.定義紋理屬性,2.在shader中定義紋理并為紋理分配采樣器段化,3.GPU Instancing定義處理紋理的縮放和平移的緩沖 4.頂點著色器中計算縮放和平移嘁捷,片元著色器中采樣紋理顏色。
- Shader Feature:可以讓Unity根據(jù)不同定義條件或關(guān)鍵字編譯多次显熏,生成多個著色器變體雄嚣,然后通過外部代碼或面板設(shè)置開關(guān)某個關(guān)鍵字加載對應(yīng)的著色器變種來執(zhí)行對應(yīng)的功能,在開發(fā)中比較常用喘蟆。
使用方法:1.增加是否開啟的屬性 2.使用#pragma為該屬性添加shader_feature缓升,如#pragma shader_feature _CLIPPING
3.在shader中通過#if defined()
判斷是否被定義,以決定執(zhí)行什么分支內(nèi)容
第2章演示代碼下載
3.平行光下的材質(zhì)
使shader受光照影響:1.修改shader的tag蕴轨,并在DrawSettings中加入對此tag的支持港谊。2.著色器輸入加入法線屬性。3.新增surface struct橙弱,使片元著色器的處理以表面為單位歧寺。4.新增Lighting文件,用于根據(jù)物體表面信息和光線信息計算最終光照棘脐,首先是固定數(shù)值光照斜筐。
燈光的處理:上面一節(jié)是固定數(shù)值的表面顏色。現(xiàn)在加入燈光處理使表面受到場景燈光的影響蛀缝。在本章處理方向光顷链。1.新增Light文件保存燈光和獲取燈光屬性。2.Lighting中新增光照函數(shù)屈梁,計算一根光線在表面的顏色分量嗤练。
cos(normal,lightdir)*lightcolor
3.新增光線表面著色函數(shù),計算表面本身顏色在一根光線影響下呈現(xiàn)的顏色俘闯。lightcolor*surfacecolor
潭苞。4.新增表面著色函數(shù),計算所有的光線影響下表面的最終顏色真朗。sum_all(lightcolor*surfacecolor)
此疹。5.向GPU發(fā)送燈光數(shù)據(jù):GPU開辟緩沖區(qū)保存燈光,CPU向GPU發(fā)送方向光燈光數(shù)量遮婶、燈光顏色蝗碎、燈光方向,GPU從緩沖區(qū)中獲取燈光數(shù)據(jù)并使用旗扑。(此時只可以發(fā)送一個燈光)6.處理可見光蹦骑,多個光源:從裁剪結(jié)果獲取可見光,分離出不同種類的燈光臀防,GPU緩沖區(qū)以數(shù)組方式保存燈光屬性眠菇,CPU向GPU發(fā)送特定種類的燈光數(shù)據(jù)边败,GPU遍歷計算燈光,得出最終顏色捎废。(其他:使管線支持線性光強笑窜,設(shè)置著色器編譯目標級別以不支持舊的圖形API)BRDF:Unity內(nèi)置管線中支持兩種基于物理的工作流:金屬工作流和高光反射工作流。其中金屬工作流是默認的工作流登疗,對應(yīng)的Shader是Standard Shader排截。如果想使用高光反射工作流,需要在Shader的下拉框選擇Standard(Specular setup)辐益。需要注意的是断傲,不同的工作流可以實現(xiàn)相同的效果,只是參數(shù)不同而已智政。實際工程中认罩,可以選擇偏好的工作流也可以混合著用。這里使用金屬工作流女仰。 本章BRDF只是通過材質(zhì)的金屬度和光滑度來控制.Metallic定義了表面更像金屬或非金屬猜年,1金屬0非金屬。Smoothness是Metallic的附屬值疾忍,定義了光滑程度乔外,1完全光滑鏡面反射明顯,0完全粗糙一罩。
想知道物體表面一點是如何與光交互的杨幼,使用BRDF定量分析,大多數(shù)情況下聂渊,BRDF可以使用來表示差购,
是入射光線方向,
是觀察方向汉嗽。BRDF有兩種理解方式欲逃。第一種是當給出入射角后,BRDF可以給出所有出射方向上的反射和散射光線的相對分布情況饼暑;第二種是稳析,當給定觀察方向后,即出射方向弓叛,BRDF可以給出所有入射方向到該出射方向的光線分布彰居。更直觀的理解是,當一束光線沿入射方向l到達表面某點時撰筷,
表示有多少能量被反射到觀察方向
上陈惰。
做法:我們使用表面的屬性計算BRDF,這是漫反射和鏡面反射的組合毕籽,我們需要把表面顏色分為漫反射和鏡面反射兩個部分抬闯,還需知道表面粗糙度井辆。
- 添加BRDF文件,規(guī)定BRDF画髓,根據(jù)表面屬性獲取表面BRDF的函數(shù)掘剪。
- Lighting中計算光線著色的方法中傳入BRDF數(shù)據(jù)。
- 計算漫反射奈虾,BRDF的漫反射就是表面color,替換一下漫反射的計算即可廉赔。
- 表面反射計算肉微。當使用金屬工作流時,物體表面對光線的反射率會受到金屬度的影響蜡塌,金屬度越大碉纳,自身反照率(albedo)顏色越不明顯,對周邊環(huán)境的反射越清晰馏艾。所以修改獲取漫反射BRDF函數(shù)劳曹,
得到不反射率,表面自身的漫反射需要乘不反射率加權(quán)(合理)琅摩√酰考慮到一些絕緣體也會有光從表面反射出來(高低也得有一點反射率),所以沒有完美的絕對不反射的物體房资,所以不反射率是有上限的蜕劝。鏡面反射:考慮到能量守恒,表面顏色 = 漫反射+鏡面反射轰异。同時考慮到岖沛,金屬度影響鏡面反射顏色,非金屬鏡面反射是白色(單通道)搭独,所以要在最小反射率和表面顏色之間用金屬度(鏡面反射率)插值婴削。
- 粗糙度和光滑度相反,1-smoothness牙肝,通過源碼的方法用光滑度得到粗糙度唉俗,然后用源碼的方法將粗糙度平方,得到真實粗糙度惊奇,與迪士尼的模型匹配互躬。
- 獲取相機位置,與世界坐標的表面計算颂郎,得到視角方向吼渡。使視角方向成為表面的屬性。7.鏡面反射強度:取決于視角方向和完美反射方向的對齊程度乓序。計算公式:
,
粗糙度,
表面法線,
光照方向,
視角方向,
中間向量寺酪,
歸一化項.
注:這里BRDF使用金屬工作流坎背,只有金屬度和粗糙度
兩個參數(shù),計算采用了cook-Torrance BRDF的簡略版寄雀。
主要得到反射的顏色得滤,即光照項,包括漫反射和鏡面反射盒犹;
主要得到反射率懂更,即BRDF項。
具體細節(jié)請見:基于物理的渲染:微平面理論(Cook-Torrance BRDF推導)
透明度:當前隨alpha變小急膀,鏡面反射也會消失沮协,整體都會變得透明,這是不對的卓嫂,實際情況下鏡面反射不會消失】对荩現(xiàn)在還不能做到這一點。我們想要的結(jié)果是調(diào)整alpha只改變漫反射的顏色晨雳,鏡面反射不變行瑞。通過預乘alpha:把RGB通道也乘上alpha比例,變成
餐禁,好處是可以讓兩個像素之間線性插值后顏色更加合理血久,使帶透明通道圖片的紋理可以正常的線性插值。
ShaderGUI:我們的材質(zhì)支持多種模式坠宴,切換材質(zhì)模式需要同時修改一些參數(shù)洋魂,使用ShaderGUI對材質(zhì)面板拓展,一鍵配置參數(shù)完成調(diào)節(jié)喜鼓。
- 使用CustomEditor拓展材質(zhì)面板
- 創(chuàng)建子文件夾Editor副砍,創(chuàng)建腳本CustomShaderGUI.cs,用于便利地調(diào)整屬性
Reference:
[1] 基于物理的渲染:微平面理論(Cook-Torrance BRDF推導) - 知乎 (zhihu.com)