執行個體原文
Unity通用渲染管線(URP)系列(二)——Draw Calls(Shaders&Batches) - 知乎 (zhihu.com)
Draw Calls (catlikecoding.com)
編寫簡單的HLSL Shader
建立Shader: Custom RP/Unlit
聲明其頂點着色器函數和片元着色器函數,聲明顔色屬性_BaseColor
Shader "Custom RP/Unlit"
{
Properties
{
_BaseColor("Color", Color) = (1.0, 1.0, 1.0, 1.0)
}
SubShader
{
Pass
{
HLSLPROGRAM
#pragma vertex UnlitPassVertex
#pragma fragment UnlitPassFragment
#include "UnlitPass.hlsl"
ENDHLSL
}
}
}
在UnlitPass.hlsl中編寫頂點着色器和片元着色器的具體定義
#ifndef CUSTOM_UNLIT_PASS_INCLUDE
#define CUSTOM_UNLIT_PASS_INCLUDE
#include "../ShaderLibrary/Common.hlsl"
float4 _BaseColor;
float4 UnlitPassVertex(float3 positionOS : POSITION) : SV_POSITION
{
float3 positionWS = TransformObjectToWorld(positionOS.xyz);
return TransformWorldToHClip(positionWS);
}
float4 UnlitPassFragment() : SV_TARGET
{
return _BaseColor;
}
#endif
其中在頂點着色器中做的是頂點位置空間變換,從模型空間轉換到世界空間,再從世界空間轉換到裁剪空間。
片元着色器傳回定義的片元顔色。
在做空間變換時用到了unity_ObjectToWorld,unity_MatrixVP等矩陣及其逆矩陣,在UnityInput.hlsl中定義:
#ifndef CUSTOM_UNITY_INPUT_INCLUDED
#define CUSTOM_UNITY_INPUT_INCLUDED
float4x4 unity_ObjectToWorld;
float4x4 unity_WorldToObject;
real4 unity_WorldTransformParams;
float4x4 unity_MatrixVP;
float4x4 unity_MatrixV;
float4x4 glstate_matrix_projection;
#endif
在Common.hlsl中包含對實際調用轉換方法的定義:
#ifndef CUSTOM_COMMON_INCLUDED
#define CUSTOM_COMMON_INCLUDED
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
#include "UnityInput.hlsl"
#define UNITY_MATRIX_M unity_ObjectToWorld
#define UNITY_MATRIX_I_M unity_WorldToObject
#define UNITY_MATRIX_V unity_MatrixV
#define UNITY_MATRIX_VP unity_MatrixVP
#define UNITY_MATRIX_P glstate_matrix_projection
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/SpaceTransforms.hlsl"
#endif
可以定位到對應的Core檔案中看到頂點空間變換調用的函數代碼SpaceTransform.hlsl:
float3 TransformObjectToWorld(float3 positionOS)
{
return mul(GetObjectToWorldMatrix(), float4(positionOS, 1.0)).xyz;
}
float4x4 GetObjectToWorldMatrix()
{
return UNITY_MATRIX_M;
}
float4 TransformWorldToHClip(float3 positionWS)
{
return mul(GetWorldToHClipMatrix(), float4(positionWS, 1.0));
}
float4x4 GetWorldToHClipMatrix()
{
return UNITY_MATRIX_VP;
}
批處理
SRP批處理器
它在GPU上緩存了材質屬性,所有材質屬性都需要在具體的存儲緩沖區内定義,而不是在全局級别上定義。它通過将指定屬性放入特定的常量記憶體緩沖區中來隔離它,盡管它仍可在全局級别通路。
并非所有平台(例如OpenGL ES 2.0)都支援常量緩沖區,是以,我們可以使用核心RP庫中包含的CBUFFER_START和CBUFFER_END宏,而不是直接使用cbuffer。
在這個示例中,如果我們使用特定的一組值,則需要全部定義它們。對于轉換組,即使我們不使用它,我們也需要包括float4 unity_LODFade。順序無關緊要,但是Unity會将其直接放在unity_WorldToObject之後,是以我們也要這樣做。
在渲染管線中開啟SRP批處理程式,調用:
GraphicsSettings.useScriptableRenderPipelineBatching設定為true
唯一的限制是每種材質的記憶體布局需要相同,這是因為我們對所有材質都使用相同的着色器,每個着色器僅包含一個顔色屬性。Unity不會比較材質的确切記憶體布局,它隻是僅批處理使用完全相同的着色器變體的繪制調用。
GPU Instancing
還有一種合并DrawCall的方法,該方法适用于逐對象的材質屬性。這就是所謂的GPU執行個體化(GPUInstancing),其工作原理是一次對具有相同網格物體的多個對象發出一次繪圖調用。CPU收集所有每個對象的變換和材質屬性,并将它們放入數組中,然後發送給GPU。然後,GPU周遊所有條目,并按提供順序對其進行渲染。
在Unlit.shader檔案中添加預編譯指令,生成具有GPU執行個體化支援和不具有GPU執行個體化支援的兩個變體。
在Common.hlsl中包括對GPU執行個體化的支援:
UnityInstancing.hlsl的作用是重新定義這些宏來通路執行個體資料數組。将輸入結構修改為結構用作函數的輸入參數,并将UNITY_VERTEX_INPUT_INSTANCE_ID放在屬性中來添加它,然後在輸入時,添加UNITY_SETUP_INSTANCE_ID(input); 在UnlitPassVertex的開頭。這将從輸入中提取索引,并将其存儲在其他執行個體化宏所依賴的全局靜态變量中。
struct Attribute
{
float3 positionOS : POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
float4 UnlitPassVertex(Attribute input) : SV_POSITION
{
UNITY_SETUP_INSTANCE_ID(input);
float3 positionWS = TransformObjectToWorld(input.positionOS);
return TransformWorldToHClip(positionWS);
}
現在尚不支援逐執行個體的材質資料。如果要添加的話,通過用UNITY_INSTANCING_BUFFER_START替換CBUFFER_START以及用UNITY_INSTANCING_BUFFER_END替換CBUFFER_END來完成。
然後,将_BaseColor的定義替換為UNITY_DEFINE_INSTANCED_PROP(Float 4,_BaseColor)。
UNITY_INSTANCING_BUFFER_START(UnityPerMaterial)
UNITY_DEFINE_INSTANCED_PROP(float4, _BaseColor)
UNITY_INSTANCING_BUFFER_END(UnityPerMaterial)
使用執行個體化時,我們現在還需要在UnlitPassFragment中提供執行個體索引。為了簡單起見,我們将使用一個結構,通過UNITY_TRANSFER_INSTANCE_ID(input,output)使UnlitPassVertex輸出位置和索引。複制索引(如果存在)。我們像Unity一樣命名此結構Varying,因為它包含的資料在同一三角形的片段之間可能會有所不同。
将此結構作為參數添加到UnlitPassFragment。然後像以前一樣使用UNITY_SETUP_INSTANCE_ID來使索引可用。現在需要通過UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterial,_BaseColor)通路material屬性。
struct Varyings {
float4 positionCS : SV_POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
Varyings UnlitPassVertex(Attributes input)
{
Varyings output;
UNITY_SETUP_INSTANCE_ID(input);
UNITY_TRANSFER_INSTANCE_ID(input, output);
float3 positionWS = TransformObjectToWorld(input.positionOS);
output.positionCS = TransformWorldToHClip(positionWS);
return output;
}
float4 UnlitPassFragment(Varyings input) : SV_TARGET
{
UNITY_SETUP_INSTANCE_ID(input);
return UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterial, _BaseColor);
}
請注意,基于目标平台以及每個執行個體需要提供的資料量,批處理大小是有限制的。如果超過此限制,那麼最終将導緻一批以上。此外,如果使用多種材質,分類仍可以拆分批次。
配置批處理
那種方式更好可能取決于很多因素,是以把它們處理成可配置的選項會更好。首先,添加布爾參數以控制是否将動态批處理和GUI執行個體化用于DrawVisibleGeometry,而不是對其進行寫死。
相關代碼如下:
建立透明和裁切的材質
Blend模式
不透明渲染和透明渲染之間的主要差別是,我們是替換之前繪制的任何内容還是與之前的結果結合以産生透視效果。可以通過設定源和目标混合模式來控制。這裡的源是指現在繪制的内容,目标是先前繪制的内容,以及最終産生的結果。為此添加兩個着色器屬性:_SrcBlend和_DstBlend。它們是blend modes的枚舉,我們可以使用的最佳類型是Float,預設情況下将源設定為1,将目标設定為零。
為了簡化編輯,我們可以将Enum添加到properties中,并使用完全限定的UnityEngine.Rendering.BlendMode枚舉類型作為參數。
可以在Pass塊中使用Blend語句和兩個模式來定義混合模式。想使用着色器屬性,可以通過将其放在方括号内來通路它們。這是可程式設計着色器之前的遠古文法。
不寫入深度
紋理化
向着色器添加_BaseMap紋理屬性
在UnlitPass.hlsl中的着色器屬性之前執行:
使用名為TEXTURE2D的宏參數,将紋理傳到GPU記憶體。
通過SAMPLER宏實作,在名稱前添加了sampler。用來比對Unity自動提供的采樣器狀态。
TEXTURE2D(_BaseMap);
SAMPLER(sampler_BaseMap);
在UnityPerMaterial緩沖區中添加提供紋理的平鋪和偏移屬性,但附加了_ST,代表縮放和平移等。
定義紋理坐标:
在片元着色器函數中,添加baseUV的定義
修改頂點着色器中複制紋理坐标,
float4 baseST = UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterial, _BaseMap_ST);
output.baseUV = input.baseUV * baseST.xy + baseST.zw;
在片元着色器中,通過使用SAMPLE_TEXTURE2D宏對紋理,采樣器狀态和坐标作為參數,對紋理進行采樣。最終顔色是通過乘法相結合的紋理和單一顔色。
Alpha裁剪
完成此操作的通常方法是定義一個截止門檻值。alpha值低于此門檻值的片段将被丢棄,而所有其他片段将保留。添加一個_Cutoff屬性,預設情況下将其設定為0.5。由于alpha始終位于零和1之間,是以我們可以使用Range(0.0,1.0)作為其類型。
同樣将其添加到UnlitPass.hlsl的材質屬性中。
通過調用UnlitPassFragment中的clip函數來丢棄片段。如果我們傳遞的值為零或更小,它将中止并丢棄該片段。是以,将最終的alpha值(可通過a或w屬性通路)減去截止門檻值傳遞給它。
添加一個控制着色器裁剪的關鍵字的Toggle屬性,我們将使用_CLIPPING。屬性本身的名稱無關緊要,是以隻需使用_Clipping。
将#pragma shader_feature _CLIPPING添加到其Pass中的指令中。
它将生成一個或兩個變體,可以使代碼以定義為條件: