unity烘培单个物体_Unity可编程渲染管线(SRP)教程:二、自定义着色器
本文翻譯自Catlike Coding,原作者:Jasper Flick。
本文經原作者授權,轉載請說明出處。
原文鏈接在下:
https://catlikecoding.com/unity/tutorials/scriptable-render-pipeline/custom-shaders/?catlikecoding.com本章內容如下:
- 編寫HLSL著色器
- 定義常量緩沖區
- 使用unity渲染管線核心庫
- 支持動態批處理和GPU實例化
這是Unity可編程渲染管線系列教程的第二章。這章主要介紹了如何用HLSL編寫一個著色器,以及怎么在單個draw call中批量高效地渲染多個物體。
本教程使用Unity 2018.3.0f2完成。
一、自定義無光著色器(Unlit Shader)
雖然我們可以使用默認的unlit shader來測試我們的渲染管線,但是要充分利用自定義渲染管線的強大功能,我們需要為它創建自定義著色器。因此,我們將創建一個自己的著色器來取代Unity的默認unlit shader。
1.1 創建一個著色器
可以通過“Assets / Create / Shader/Unlit Shader”創建一個著色器。刪除創建的文件中所有的默認代碼,并命名為Unlit。
Unlit Shader關于著色器的基礎知識在 Rendering 2, Shader Fundamentals 章節中。如果你對此不熟悉,請仔細閱讀。一個能夠正常運行的著色器至少要定義一個Shader語塊,其中必須包含一個Properties語塊和一個SubShader語塊,而SubShader語塊中必須包含一個Pass語塊。Shader后面的字符串會展現在材質球的shader下拉菜單中,我們用"My Pipeline/Unlit"來命名它。
Shader "My Pipeline/Unlit" {Properties {}SubShader {Pass {}} }把Unlit Opaque材質球的shader屬性設置成我們剛剛創建的shader。
使用自定義著色器的Unlit opaque材質球1.2 HLSL
自定義著色器的代碼要寫在著色器的pass語塊中。Unity支持GLSL或HLSL,雖然GLSL用于默認著色器,但是Unity新的渲染管線著色器是使用HLSL編寫的,因此我們也是用HLSL。這意味著我們必須將所有代碼放在HLSLPROGRAM和ENDHLSL語句之間。
Pass {HLSLPROGRAMENDHLSL }Unity著色器至少需要包含一個頂點著色器和一個片元著色器,每個著色器都用一個pragma編譯器指令定義。我們把頂點著色器命名為UnlitPassVertex,片元著色器命名為UnlitPassFragment。但是我們不會直接把這些函數放在著色器文件中。我們把HLSL代碼放在一個單獨的包含文件中,我們也將其命名為Unlit,但使用.hlsl擴展名。把它放在與Unlit.shader相同的文件夾中,然后在pragma指令之后包含HLSL程序。
HLSLPROGRAM#pragma vertex UnlitPassVertex#pragma fragment UnlitPassFragment#include "Unlit.hlsl"ENDHLSL不幸的是,Unity沒有方便的菜單項來創建HLSL文件。您必須自己創建它,先復制Unlit.shader文件,然后把文件擴展名更改為hlsl并刪除其中的著色器代碼。
在文件被包含多次的情況下,為了防止出現重復代碼,我們要在包含文件中以包含防護開頭。雖然這種情況永遠不會發生,但最好始終為每個包含文件執行此操作。
#ifndef MYRP_UNLIT_INCLUDED #define MYRP_UNLIT_INCLUDED#endif // MYRP_UNLIT_INCLUDED在頂點著色器中我們至少需要知道頂點的坐標,另外它必須輸出一個齊次裁剪坐標中的位置。因此,我們在頂點著色器的輸入和輸出結構中都要定義一個float4類型的位置變量。
#ifndef MYRP_UNLIT_INCLUDED #define MYRP_UNLIT_INCLUDEDstruct VertexInput {float4 pos : POSITION; };struct VertexOutput {float4 clipPos : SV_POSITION; };#endif // MYRP_UNLIT_INCLUDED接下來,我們定義頂點著色器的函數UnlitPassVertex。目前,我們將直接使用模型空間的頂點位置作為剪輯空間中的頂點位置作為輸出。雖然這不正確,但是可以馬上得到一個可以正確編譯的著色器。我們稍后會添加正確的坐標轉換代碼。
struct VertexOutput {float4 clipPos : SV_POSITION; };VertexOutput UnlitPassVertex (VertexInput input) {VertexOutput output;output.clipPos = input.pos;return output; }#endif // MYRP_UNLIT_INCLUDED目前我們仍然使用默認的白色作為片元著色器的輸出,所以在片元著色器中簡單地返回float4類型的1。頂點著色器的輸出經過柵化器插值后輸入到片元著色器,因此將其作為片元著色器的參數,即便我們尚未使用它。
VertexOutput UnlitPassVertex (VertexInput input) {VertexOutput output;output.clipPos = input.pos;return output; }float4 UnlitPassFragment (VertexOutput input) : SV_TARGET {return 1; }#endif // MYRP_UNLIT_INCLUDED1.3 轉換矩陣
此時我們擁有了一個能夠正確編譯的著色器,即使它還不能產生合理的結果。下一步是要把頂點坐標轉換為正確的空間。如果我們有一個模型-觀察-投影矩陣,那么我們可以直接從模型空間轉換到裁剪空間,但Unity不會為我們創建這樣的矩陣。它提供了一個可用的模型矩陣,我們可以使用它從模型空間轉換為世界空間。我們需要一個float4x4 unity_ObjectToWorld變量來存儲這個矩陣。當我們使用HLSL時,我們必須自己定義該變量。然后在頂點著色器中把模型頂點坐標轉換到時間空間。
float4x4 unity_ObjectToWorld;struct VertexInput {float4 pos : POSITION; };struct VertexOutput {float4 clipPos : SV_POSITION; };VertexOutput UnlitPassVertex (VertexInput input) {VertexOutput output;float4 worldPos = mul(unity_ObjectToWorld, input.pos);output.clipPos = worldPos;return output; }接下來,我們需要將世界空間轉換為裁剪空間。這是通過觀察-投影矩陣完成的,Unity是通過float4x4 unity_MatrixVP變量提供的。添加它然后完成坐標轉換。
float4x4 unity_MatrixVP; float4x4 unity_ObjectToWorld;…VertexOutput UnlitPassVertex (VertexInput input) {VertexOutput output;float4 worldPos = mul(unity_ObjectToWorld, input.pos);output.clipPos = mul(unity_MatrixVP, worldPos);return output; }我們的著色器現在可以正常工作了,所有使用unlit材質球的對象再次可見了,但是全是白色的。由于著色器內部使用轉換矩陣和四維的坐標向量相乘,所以我們現在的轉換乘法效率不高。坐標向量的第四個分量總是1,通過明確這一點,我們可以使編譯器優化計算。
float4 worldPos = mul(unity_ObjectToWorld, float4(input.pos.xyz, 1.0));1.4 常量緩沖區
Unity沒有為我們提供模型-觀察-投影矩陣,因為這樣可以避免模型矩陣和觀察-投影矩陣的乘法。除此之外,觀察-投影矩陣可以重復用于在同一幀中使用相同相機繪制的所有內容。因為這一點,Unity的著色器將這些矩陣放在不同的常量緩沖區中。雖然我們將它們定義為變量,但它們的數據在繪制單個物體時保持不變,并且還不知如此。我們把觀察-投影矩陣放入per-frame緩沖區,而模型矩陣放入per-draw緩沖區。
雖然并不嚴格要求將著色器變量放在常量緩沖區中,但這樣做可以更有效地更改同一緩沖區中的所有數據。至少,在圖形API支持的情況下是這樣。但是OpenGL并不支持。
為了盡可能高效,我們要使用常量緩沖區。Unity將VP矩陣放在UnityPerFrame緩沖區中,將模型矩陣放在UnityPerDraw緩沖區中。還有更多的數據放在這些緩沖區中,但我們還不需要它,因此不需要包含。除了cbuffer關鍵字以及變量仍然可以像以前一樣訪問,常量緩沖區的定義和結構體一樣。
cbuffer UnityPerFrame {float4x4 unity_MatrixVP; };cbuffer UnityPerDraw {float4x4 unity_ObjectToWorld; }1.5 核心庫
由于常量緩沖區不是在所有平臺都能提高效率,因此Unity著色器通過宏控制,在需要的時候打開它們。需要使用帶有name參數的CBUFFER_START宏而不是直接只用cbuffer關鍵字,并且在常量緩沖區末尾使用CBUFFER_END宏。
CBUFFER_START(UnityPerFrame)float4x4 unity_MatrixVP; CBUFFER_ENDCBUFFER_START(UnityPerDraw)float4x4 unity_ObjectToWorld; CBUFFER_END由于這兩個宏未定義,會導致編譯器報錯。我們將利用Unity渲染管道核心庫來解決這個報錯,而不需要弄清楚應該何時使用常量緩沖區并自己定義宏。它可以通過包管理器窗口添加到我們的項目中。切換到All Packages列表并在Advanced下啟用Show preview packages,然后選擇Render-pipelines.core并安裝它。我正在使用版本4.6.0預覽版,這是Unity 2018.3可使用的最高版本。
現在我們可以通過路徑"Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"來包含公共庫功能。它定義了多個有用的函數和宏,包括常量緩沖區宏,因此在使用它們之前包含它。
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"CBUFFER_START(UnityPerFrame) float4x4 unity_MatrixVP; CBUFFER_END1.6 編譯目標等級
對于大多數平臺而言,我們的著色器又能正常工作。在包含公共庫之后,我們的著色器在OpenGL ES 2平臺下會編譯報錯。這是因為默認情況下,對于OpenGL ES 2平臺而言, Unity著色器編譯器不支持核心庫。我們可以通過添加"#pragma prefer_hlslcc gles"編譯指令到我們的著色器來解決這個問題,這正是Unity的輕量級渲染管線的做法。但是,我們根本不打算支持OpenGL ES 2,因為它僅在老舊的移動設備上才使用。我們通過使用"#pragma target"編譯指令把著色器的編譯目標等級改成3.5而不是默認的2.5。
#pragma target 3.5#pragma vertex UnlitPassVertex#pragma fragment UnlitPassFragment1.7 文件結構
核心庫的所有HLSL包含文件都位于ShaderLibrary文件夾中。我們也要這樣做,所以在My Pipeline文件夾中創建兩個文件夾ShaderLibrary和Shaders,將Unlit.hlsl放在ShaderLibrary文件夾中,將Unlit.shader放在Shaders文件夾中。
同時我們要把色器中包含文件的路徑"Unlit.hlsl"改為"../ShaderLibrary/Unlit.hlsl"。
#include "../ShaderLibrary/Unlit.hlsl"二、動態批處理
現在我們有了一個最簡單的自定義著色器,我們可以用它來進一步研究我們的管線如何渲染。一個重要的問題是它的效率如何。我們在場景中創建大量的使用我們自定義著色器渲染的球體來測試一下。值得注意的是要保持他們縮放一致。
我們可以通過幀調試器來研究場景是如何被繪制的,你會注意到每個球體都需要單獨的draw call。這不是很有效,因為每次draw call中CPU和GPU都需要進行通信,這會導致開銷。理想情況下,多個球體可以通過一次draw call一起繪制。雖然這是可能的,但目前還沒有發生。當您選擇其中一個繪制調用時,幀調試器會給我們一個提示。
2.1 啟用批處理
通過幀調試器,我們發現沒啟用動態批處理,因為它被禁用了或者是深度排序會干擾它。如果您檢查播放器設置,那么您將看到確實禁用了動態批處理選項。但是,即使啟用它,也無效。這是因為播放器設置適用于Unity的默認渲染管線,而不是我們的自定義管線。
要為我們的管線啟用動態批處理,我們必須在MyPipeline.Render中把draw settings的flags設置為DrawRendererFlags.EnableDynamicBatching。
var drawSettings = new DrawRendererSettings(camera, new ShaderPassName("SRPDefaultUnlit"));drawSettings.flags = DrawRendererFlags.EnableDynamicBatching;drawSettings.sorting.flags = SortFlags.CommonOpaque;修改之后,我們發現仍然沒有使用動態批處理,但是原因以及變了。動態批處理意味著Unity在繪制之前將物體合并在一個mesh中,這需要消耗每幀的CPU時間并且物體的頂點數不能超過300。
球體頂點數太多,立方體頂點數少可以使用動態批處理。因此,把所有物體改成立方體網格。你可以全選,并一次調整它們的網格過濾器。
2.2 顏色
動態批處理適用于使用相同材質繪制的小網格(頂點較少的物體)。但是當涉及多種材質球時,事情變得更加復雜。為了說明這一點,我們需要更改無光著色器的顏色。將color屬性添加到它的Properties語塊中,并命名為_Color,把"Color"作為標簽, 白色作為默認值。
Properties {_Color ("Color", Color) = (1, 1, 1, 1)}現在我們可以調整材質的顏色了,但它還不會影響繪制。需要把一個float4 _Color變量添加到Unlit.hlsl文件中并在片元著色器中返回該變量,而不是固定值。顏色是根據材質球定義的,因此可以放在一個常量緩沖區中,只需要在切換材質球時更改。我們將緩沖區命名為UnityPerMaterial。
CBUFFER_START(UnityPerDraw)float4x4 unity_ObjectToWorld; CBUFFER_ENDCBUFFER_START(UnityPerMaterial)float4 _Color; CBUFFER_ENDstruct VertexInput {float4 pos : POSITION; };…float4 UnlitPassFragment (VertexOutput input) : SV_TARGET {return _Color; }復制材質球并設置成不同的顏色,以便我們區分它們。然后選擇一些物體并讓它們使用新材質球。
動態批處理的批次變多了。因為不同的材質球需要不同的per-material數據,所以每種材質球至少需要一個批次。但是通常會有更多的批次,因為Unity會在空間上對物體進行分組以減少重復繪制。
2.3 批處理選項
動態批處理可能產生好處,但也可能最終并沒有什么用,甚至拖慢速度。如果場景中不包含許多共享相同材質球的小網格(頂點較少的物體),則禁用動態批處理可能是有意義的,因此Unity就不必每幀判斷是否使用動態批處理。因此,我們將添加一個選項,以便為我們的管線啟用或禁用動態批處理。我們不能依賴播放器設置,而是需要添加了一個配置字段到我們的MyPipelineAsset,因此我們可以通過編輯器中的管線資源文件對其進行配置。
[ SerializeField ]bool dynamicBatching;當MyPipeline實例被創建時,我們要告訴它是否使用動態批處理。我們將在調用其構造函數時將此信息作為參數傳過去。
protected override IRenderPipeline InternalCreatePipeline () {return new MyPipeline(dynamicBatching); }因此,我們不能再依賴于MyPipeline的默認構造函數了。需要為MyPipeline添加一個公共構造函數,使用布爾參數來傳遞動態批處理設置,然后添加一個DrawRendererFlags類型的變量drawFlag。在構造函數設置為drawFlag賦值。
DrawRendererFlags drawFlags;public MyPipeline (bool dynamicBatching) {if (dynamicBatching) {drawFlags = DrawRendererFlags.EnableDynamicBatching;}}將drawFlag復制給Render函數中的drawSettings.flags。
drawSettings.flags = drawFlags;當我們在編輯器中切換管線資源的動態批處理選項時,Unity的批處理行為會立即發生變化。每次我們調整管線資源時,都會創建一個新的管線實例。
三、GPU實例化
動態批處理不是我們可以減少每幀draw call次數的唯一方法。另一種方法是使用GPU實例化,在這種情況下,CPU通過單個調用告訴GPU需要多次繪制指定的網格和材質的組合。這使得可以對使用相同網格和材質的對象進行分組,而無需構造新網格。這也消除了網格大小的限制。
3.1 GPU實例化選項
默認情況下GPU實例化是開啟的,但我們要自定義一個標記來控制它。讓GPU實例化成為可選項,這樣可以很容易地比較GPU實例化在啟用和禁用下的不同結果。在MyPipelineAsset中添加一個可配置字段,并將其傳遞給MyPipeline的構造函數。
[SerializeField]bool instancing;protected override IRenderPipeline InternalCreatePipeline () {return new MyPipeline(dynamicBatching, instancing);}在MyPipeline的構造函數中,我們需要設置GPU實例化標志。在這種情況下,標志值是DrawRendererFlags.EnableInstancing,把該值和之前的drawFlags做邏輯或運算,如此可以同時啟用動態批處理和GPU實例化。當它們都被啟用時,Unity傾向于使用GPU實例化。
public MyPipeline (bool dynamicBatching, bool instancing) {if (dynamicBatching) {drawFlags = DrawRendererFlags.EnableDynamicBatching;}if (instancing) {drawFlags |= DrawRendererFlags.EnableInstancing;}}3.2 材質球支持
當我們的渲染管線啟用GPU實例化時,并不意味著渲染的物體就能實現GPU實例化,它還要求渲染的物體所使用的材質球支持GPU實例化。因為并不總是需要GPU實例化,所以它是一個可選項,這需要兩個著色器變量:一個支持實例化,另一個不支持實例化。我們可以把#pragma multi_compile_instancing指令添加到著色器來創建所必需的變量。在我們的例子中,會產生兩個著色器變量,一個啟用INSTANCING_ON宏,另一個禁用INSTANCING_ON宏。
#pragma target 3.5#pragma multi_compile_instancing#pragma vertex UnlitPassVertex#pragma fragment UnlitPassFragment添加新的指令之后,我們的材質球中會出現一個新的可配置選項:啟用GPU實例化。
3.3 著色器支持
啟用GPU實例化時,會告訴GPU使用相同的常量數據多次繪制相同的網格。但模型矩陣是該數據的一部分。這意味著我們最終還是不能在一個draw call中多次渲染相同的網格。要解決該問題,必須將包含所有對象的模型矩陣的數組放入常量緩沖區中。每個實例都使用自己的索引繪制,通過該索引可以從數組中獲取正確的模型矩陣。
在禁用實例化時需要使用unity_ObjectToWorld矩陣,在啟用實例化時需要使用矩陣數組。為了使頂點著色器在兩種情況下的的代碼保持一致,我們將為模型矩陣定義一個宏:UNITY_MATRIX_M。我們之所以這樣命名,是因為核心庫的包含文件中已經定義了該宏來支持GPU實例化。
CBUFFER_START(UnityPerDraw)float4x4 unity_ObjectToWorld; CBUFFER_END#define UNITY_MATRIX_M unity_ObjectToWorld…VertexOutput UnlitPassVertex (VertexInput input) {VertexOutput output;float4 worldPos = mul(UNITY_MATRIX_M, float4(input.pos.xyz, 1.0));output.clipPos = mul(unity_MatrixVP, worldPos);return output; }在我們把模型矩陣定義為UNITY_MATRIX_M之后,添加包含文件:UnityInstancing.hlsl,因為它可以在需要的情況下重新定義宏來支持GPU實例化。
#define UNITY_MATRIX_M unity_ObjectToWorld#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/UnityInstancing.hlsl"當使用實例化時,當前正在繪制的對象的索引會被GPU添加到其頂點數據。UNITY_MATRIX_M依賴于索引,所以我們必須把它添加到VertexInput結構中。我們可以使用UNITY_VERTEX_INPUT_INSTANCE_ID宏來幫助我們獲取索引。
struct VertexInput {float4 pos : POSITION;UNITY_VERTEX_INPUT_INSTANCE_ID };最后,在使用UNITY_MATRIX_M 宏之前,我們必須通過使用UNITY_SETUP_INSTANCE_ID宏(該宏該收input作為參數)來設置索引ID。
VertexOutput UnlitPassVertex (VertexInput input) {VertexOutput output;UNITY_SETUP_INSTANCE_ID(input);float4 worldPos = mul(UNITY_MATRIX_M, float4(input.pos.xyz, 1.0));output.clipPos = mul(unity_MatrixVP, worldPos);return output; }現在立方體實現了GPU實例化。像動態批處理一樣,我們最終還是有多個批次,那是因為我們使用了不同的材質球。需要確保使用的所有材質球都啟用了GPU實例化。
除了模型-世界的矩陣之外,世界-模型矩陣也被默認添加在實例化緩沖區中。這些是模型矩陣的逆矩陣,在我們只使用統一縮放的情況下,我們不需要這些額外的矩陣。我們可以通過把#pragma instancing_options assumeuniformscaling指令添加到我們的著色器來從緩沖區中去掉世界-模型矩陣。
#pragma multi_compile_instancing#pragma instancing_options assumeuniformscaling如果需要支持非統一縮放,則必須使用未啟用此選項的著色器。
3.4 不同顏色
如果我們想在場景中使用多種顏色,我們需要制作不同的材質球,這意味著批次會增加。既然矩陣可以放在數組中,那么顏色應該也可以。所以可以在一個draw call中繪制不同顏色的對象。
第一步,我們需要為每個物體單獨設置顏色。我們不能通過材質球來做到這一點,因為這是所有物體共享的資源。我們需要創建一個組件,命名為InstancedColor,給它一個可配置的顏色字段。由于它不是自定義渲染管線的一部分,所以把該文件放在My Pipeline文件夾之外。
using UnityEngine;public class InstancedColor : MonoBehaviour {[SerializeField]Color color = Color.white; }為了覆蓋材質球的顏色,我們需要創建一個MaterialPropertyBlock對象,通過SetColor方法設置它的"_Color "屬性,然后通過調用MeshRenderer的SetPropertyBlock方法將它傳遞給物體的材質球。我們假設顏色在播放模式中不會改變,所以在Awake方法中進行這些操作。
void Awake () {var propertyBlock = new MaterialPropertyBlock();propertyBlock.SetColor("_Color", color);GetComponent<MeshRenderer>().SetPropertyBlock(propertyBlock);}把我們的組件添加到場景中的一個對象上。在進入播放模式后你能看到它的顏色發生變化。
要在編輯模式下立即查看場景中的顏色更改,需要把設置顏色的代碼移動到OnValidate方法中,然后在Awake中調用OnValidate方法。
void Awake () {OnValidate();}void OnValidate () {var propertyBlock = new MaterialPropertyBlock();propertyBlock.SetColor("_Color", color);GetComponent<MeshRenderer>().SetPropertyBlock(propertyBlock);}把組件添加到所有物體上,同時確保他們使用相同的材質球,然后給不同的物體設置不同的顏色,注意不能在物體上重復添加該組件。
每次通過MaterialPropertyBlock設置顏色時,我們都會創建一個新實例。這不是必需的,因為每個MeshRenderer在內部會生成一個屬性塊的拷貝來追蹤被覆蓋的屬性。這意味著我們可以重復使用MaterialPropertyBlock實例,因此生成一個靜態的屬性塊,僅在需要時創建它。
static MaterialPropertyBlock propertyBlock;…void OnValidate () {if (propertyBlock == null) {propertyBlock = new MaterialPropertyBlock();}propertyBlock.SetColor("_Color", color);GetComponent<MeshRenderer>().SetPropertyBlock(propertyBlock);}此外,我們可以通過Shader.PropertyToID方法來獲取屬性ID,從而略微加快color屬性的匹配。每個著色器屬性名稱都會生成一個全局整數標識。這些標識在單個會話期間始終保持不變,即播放和編譯之間。所以我們獲取一次,把它賦值給一個靜態字段。
static int colorID = Shader.PropertyToID("_Color");…void OnValidate () {if (propertyBlock == null) {propertyBlock = new MaterialPropertyBlock();}propertyBlock.SetColor(colorID, color);GetComponent<MeshRenderer>().SetPropertyBlock(propertyBlock);}3.5 Per-Instance不同顏色
為每個物體設置不同的顏色會影響GPU實例化。雖然我們使用的是相同的材質球,但是用于渲染的數據(顏色)卻不同。當使用不同顏色時,會導致每個物體被單獨繪制。
為了使GPU實例化再次起作用,需要把顏色數據放在一個數組中,就像模型矩陣一樣處理。在這種情況下,我們需要自己完成,因為核心庫不會為自定義屬性重新定義宏。我們通過UNITY_INSTANCING_BUFFER_START宏和對應的結束宏手動創建一個用于GPU實例化的常量緩沖區,并且命名為PerInstance。在緩沖區內,我們將顏色定義為UNITY_DEFINE_INSTANCED_PROP(float4, _Color),當禁用GPU實例化時該宏就相當于float4 _Color,當啟用GPU實例化時我們會得到一組實例數據。
//CBUFFER_START(UnityPerMaterial)//float4 _Color; //CBUFFER_ENDUNITY_INSTANCING_BUFFER_START(PerInstance)UNITY_DEFINE_INSTANCED_PROP(float4, _Color) UNITY_INSTANCING_BUFFER_END(PerInstance)為了訪問顏色值的代碼在啟用或者禁用GPU實例化時能保持一致,我們通過UNITY_ACCESS_INSTANCED_PROP宏來獲取顏色值,把緩沖區和屬性的名稱傳給該宏。
float4 UnlitPassFragment (VertexOutput input) : SV_TARGET {return UNITY_ACCESS_INSTANCED_PROP(PerInstance, _Color); }現在我們必須在片元著色器中使用實例索引。把UNITY_VERTEX_INPUT_INSTANCE_ID添加到到VertexOutput,然后使用UNITY_SETUP_INSTANCE_ID宏來設置索引ID。最后,我們使用UNITY_TRANSFER_INSTANCE_ID宏把索引從頂點輸入復制到頂點輸出。
struct VertexInput {float4 pos : POSITION;UNITY_VERTEX_INPUT_INSTANCE_ID };struct VertexOutput {float4 clipPos : SV_POSITION;UNITY_VERTEX_INPUT_INSTANCE_ID };VertexOutput UnlitPassVertex (VertexInput input) {VertexOutput output;UNITY_SETUP_INSTANCE_ID(input);UNITY_TRANSFER_INSTANCE_ID(input, output);float4 worldPos = mul(UNITY_MATRIX_M, float4(input.pos.xyz, 1.0));output.clipPos = mul(unity_MatrixVP, worldPos);return output; }float4 UnlitPassFragment (VertexOutput input) : SV_TARGET {UNITY_SETUP_INSTANCE_ID(input);return UNITY_ACCESS_INSTANCED_PROP(PerInstance, _Color); }所有物體最終在一個draw call中完成來繪制,即便它們都使用不同的顏色。但是,在常量緩沖區中可以放入多少數據是有限制的。GPU實例化批處理的限制取決于每個實例的數據變化量,除此之外,緩沖區的最大值因平臺而異。當然仍然只能在使用相同的網格和材質球的情況下才是實現GPU實例化批處理。例如,當同時使用立方體和球體網格時,會分批次。
至此,我們有一個最簡單的著色器,并且能夠盡可能高效地繪制多個對象。接下來,我們將在此基礎上構建更高級的著色器。
還有一點要說明一下,所有常量緩沖的命名都是自定義的,只需要定義和引用保持一致就像行。
下一章我們實現光照。
本章教程項目倉庫
總結
以上是生活随笔為你收集整理的unity烘培单个物体_Unity可编程渲染管线(SRP)教程:二、自定义着色器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: excel 电阻并联计算_电路分析基础(
- 下一篇: 幻塔蓝色的花怎么打开