Unity-平臺(tái)特定的渲染差異
Unity 在如下各種圖形庫(kù)平臺(tái)上運(yùn)行:Open GL、Direct3D、Metal?和游戲主機(jī)。在某些情況下,平臺(tái)與著色器語(yǔ)言語(yǔ)義之間的圖形渲染行為方式存在差異。大多數(shù)情況下,Unity Editor 會(huì)隱藏這些差異,但在某些情況下,Editor 無(wú)法為您執(zhí)行此操作。在這些情況下,您需要確保消除平臺(tái)之間的差異。下面列出了這些情況以及發(fā)生這些情況時(shí)需要采取的操作。
渲染紋理坐標(biāo)
垂直紋理坐標(biāo)約定在兩種類(lèi)型的平臺(tái)之間有所不同,分別是 Direct3D 類(lèi)和 OpenGL 類(lèi)平臺(tái)。
Direct3D 類(lèi):頂部坐標(biāo)為 0 并向下增加。此類(lèi)型適用于 Direct3D、Metal 和游戲主機(jī)。
OpenGL 類(lèi):底部坐標(biāo)為 0 并向上增加。此類(lèi)適用于 OpenGL 和 OpenGL ES。
除了渲染到渲染紋理的情況下,這種差異不會(huì)對(duì)您的項(xiàng)目產(chǎn)生任何影響。在 Direct3D 類(lèi)平臺(tái)上渲染到紋理時(shí),Unity 會(huì)在內(nèi)部上下翻轉(zhuǎn)渲染。這樣就會(huì)使坐標(biāo)約定在平臺(tái)之間匹配,并以 OpenGL 類(lèi)平臺(tái)約定作為標(biāo)準(zhǔn)。
在著色器中,有兩種常見(jiàn)情況需要您采取操作確保不同的坐標(biāo)約定不會(huì)在項(xiàng)目中產(chǎn)生問(wèn)題,這兩種情況就是圖像效果和 UV 空間中的渲染。
圖像效果
使用圖像效果和抗鋸齒時(shí),系統(tǒng)不會(huì)翻轉(zhuǎn)為圖像效果生成的源紋理來(lái)匹配 OpenGL 類(lèi)平臺(tái)約定。在這種情況下,Unity 渲染到屏幕以獲得抗鋸齒效果,然后將渲染解析為渲染紋理,以便通過(guò)圖像效果進(jìn)行進(jìn)一步處理。
如果您的圖像效果是一次處理一個(gè)渲染紋理的簡(jiǎn)單圖像效果,則?Graphics.Blit?會(huì)處理不一致的坐標(biāo)。但是,如果您在圖像效果中一起處理多個(gè)渲染紋理,則在 Direct3D 類(lèi)平臺(tái)中以及在您使用抗鋸齒時(shí),渲染紋理很可能以不同的垂直方向出現(xiàn)。要標(biāo)準(zhǔn)化坐標(biāo),必須在頂點(diǎn)著色器中手動(dòng)上下“翻轉(zhuǎn)”屏幕紋理,使其與 OpenGL 類(lèi)坐標(biāo)標(biāo)準(zhǔn)匹配。
以下代碼示例演示了如何執(zhí)行此操作:
// 翻轉(zhuǎn)紋理的采樣:
// 主紋理的 // 紋理像素大小將具有負(fù) Y。
# if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0) ? ? ?
?uv.y = 1-uv.y; # endif
請(qǐng)參閱 Unity 的著色器替換示例項(xiàng)目中的邊緣檢測(cè)場(chǎng)景(請(qǐng)參閱?Unity 學(xué)習(xí)資源),了解與此相關(guān)的更詳細(xì)示例。此項(xiàng)目中的邊緣檢測(cè)同時(shí)使用屏幕紋理和攝像機(jī)的深度+法線紋理。
GrabPass?也出現(xiàn)了類(lèi)似的情況。生成的渲染紋理實(shí)際上可能不會(huì)在 Direct3D 類(lèi)(非 OpenGL 類(lèi))平臺(tái)上進(jìn)行上下翻轉(zhuǎn)。如果著色器代碼對(duì) GrabPass 紋理進(jìn)行采樣,請(qǐng)使用?UnityCG include?文件中的?ComputeGrabScreenPos
?函數(shù)。
在 UV 空間中渲染
在紋理坐標(biāo) (UV) 空間中渲染特殊效果或工具時(shí),您可能需要調(diào)整著色器,以便在 Direct3D 類(lèi)和 OpenGL 類(lèi)系統(tǒng)之間進(jìn)行一致渲染。您還可能需要在渲染到屏幕和渲染到紋理之間進(jìn)行渲染調(diào)整。為進(jìn)行此類(lèi)調(diào)整,應(yīng)上下翻轉(zhuǎn) Direct3D 類(lèi)投影,使其坐標(biāo)與 OpenGL 類(lèi)投影坐標(biāo)相匹配。
內(nèi)置變量?ProjectionParams.x
?包含值?+1
?或?–1
。-1
?表示投影已上下翻轉(zhuǎn)以匹配 OpenGL 類(lèi)投影坐標(biāo),而?+1
?表示尚未翻轉(zhuǎn)。 您可以在著色器中檢查此值,然后執(zhí)行不同的操作。下面的示例將檢查是否已翻轉(zhuǎn)投影,如果已翻轉(zhuǎn),則再次進(jìn)行翻轉(zhuǎn),然后返回 UV 坐標(biāo)以便匹配。
float4 vert(float2 uv : TEXCOORD0) : SV_POSITION
{ ?
?float4 pos; ?
?pos.xy = uv; ?
?// 此示例使用上下翻轉(zhuǎn)的投影進(jìn)行渲染, ?
?// 因此也翻轉(zhuǎn)垂直 UV 坐標(biāo) ?
?if (_ProjectionParams.x < 0) ? ?
? ?pos.y = 1 - pos.y; ?
?pos.z = 0; ?
?pos.w = 1; ?
?return pos;
}
裁剪空間坐標(biāo)
與紋理坐標(biāo)類(lèi)似,裁剪空間坐標(biāo)(也稱為投影后空間坐標(biāo))在 Direct3D 類(lèi)和 OpenGL 類(lèi)平臺(tái)之間有所不同:
Direct3D 類(lèi):裁剪空間深度從近平面的 +1.0 到遠(yuǎn)平面的 0.0。此類(lèi)型適用于 Direct3D、Metal 和游戲主機(jī)。
OpenGL 類(lèi):裁剪空間深度從近平面的 –1.0 到遠(yuǎn)平面的 +1.0。此類(lèi)適用于 OpenGL 和 OpenGL ES。
在著色器代碼內(nèi),可使用內(nèi)置宏?UNITY_NEAR_CLIP_VALUE
?來(lái)獲取基于平臺(tái)的近平面值。
在腳本代碼內(nèi),使用?GL.GetGPUProjectionMatrix?將 Unity 的坐標(biāo)系(遵循 OpenGL 類(lèi)約定)轉(zhuǎn)換為 Direct3D 類(lèi)坐標(biāo)(如果這是平臺(tái)所期望的)。
著色器計(jì)算的精度
要避免精度問(wèn)題,請(qǐng)確保在目標(biāo)平臺(tái)上測(cè)試著色器。移動(dòng)設(shè)備和 PC 中的 GPU 在處理浮點(diǎn)類(lèi)型方面有所不同。PC GPU 將所有浮點(diǎn)類(lèi)型(浮點(diǎn)精度、半精度和固定精度)視為相同;PC GPU 使用完整 32 位精度進(jìn)行所有計(jì)算,而許多移動(dòng)設(shè)備 GPU 并不是這樣做。
有關(guān)詳細(xì)信息,請(qǐng)參閱數(shù)據(jù)類(lèi)型和精度的文檔。
著色器中的 const 聲明
const
?的使用在 Microsoft HSL(請(qǐng)參閱?msdn.microsoft.com)和 OpenGL 的 GLSL(請(qǐng)參閱?Wikipedia)著色器語(yǔ)言之間有所不同。
Microsoft 的 HLSL?
const
?與 C# 和 C++ 中的含義大致相同:聲明的變量在其作用域內(nèi)是只讀的,但可按任何方式初始化。OpenGL 的 GLSL?
const
?表示變量實(shí)際上是編譯時(shí)常量,因此必須使用編譯時(shí)約束(文字值或其他對(duì)于?const
?的計(jì)算)進(jìn)行初始化。
最好是遵循 OpenGL 的 GLSL 語(yǔ)義,并且只有當(dāng)變量真正不變時(shí)才將變量聲明為?const
。避免使用其他一些可變值初始化?const
?變量(例如,作為函數(shù)中的局部變量)。這一原則也適用于 Microsoft 的 HLSL,因此以這種方式使用?const
?可以避免在某些平臺(tái)上混淆錯(cuò)誤。
著色器使用的語(yǔ)義
要讓著色器在所有平臺(tái)上運(yùn)行,一些著色器值應(yīng)該使用以下語(yǔ)義:
__頂點(diǎn)著色器輸出(裁剪空間)位置__:
SV_POSITION
。有時(shí),著色器使用 POSITION 語(yǔ)義來(lái)使著色器在所有平臺(tái)上運(yùn)行。請(qǐng)注意,這不適用于 Sony PS4 或有曲面細(xì)分的情況。__片元著色器輸出顏色__:
SV_Target
。有時(shí),著色器使用?COLOR
?或?COLOR0
?來(lái)使著色器在所有平臺(tái)上運(yùn)行。請(qǐng)注意,這不適用于 Sony PS4。
將網(wǎng)格渲染為點(diǎn)時(shí),從頂點(diǎn)著色器輸出?PSIZE
?語(yǔ)義(例如,將其設(shè)置為 1)。某些平臺(tái)(如 OpenGL ES 或 Metal)在未從著色器寫(xiě)入點(diǎn)大小時(shí)會(huì)將點(diǎn)大小視為“未定義”。
有關(guān)更多詳細(xì)信息,請(qǐng)參閱有關(guān)著色器語(yǔ)義的文檔。
Direct3D 著色器編譯器語(yǔ)法
Direct3D 平臺(tái)使用 Microsoft 的?HLSL 著色器編譯器。對(duì)于各種細(xì)微的著色器錯(cuò)誤,HLSL 編譯器比其他編譯器更嚴(yán)格。例如,它不接受未正確初始化的函數(shù)輸出值。
使用此編譯器時(shí),您可能遇到的最常見(jiàn)情況是:
具有?
out
?參數(shù)的表面著色器頂點(diǎn)修改器。按如下方式初始化輸出:
void vert (inout appdata_full v, out Input o) ?
{ ? ?
?**UNITY_INITIALIZE_OUTPUT(Input,o);** ?
? ?// ... ? ?
}
部分初始化的值。例如,函數(shù)返回?
float4
,但代碼只設(shè)置它的?.xyz
?值。如果只需要三個(gè)值,請(qǐng)?jiān)O(shè)置所有值或更改為?float3
。在頂點(diǎn)著色器中使用?
tex2D
。這是無(wú)效的,因?yàn)轫旤c(diǎn)著色器中不存在 UV 導(dǎo)數(shù)。這種情況下,您需要采樣顯式 Mip 級(jí)別;例如,使用?tex2Dlod
?(tex, float4(uv,0,0)
)。此外,還需要添加?#pragma target 3.0
,因?yàn)?tex2Dlod
?是著色器模型 3.0 的功能。
著色器中的 DirectX 11 (DX11) HLSL 語(yǔ)法
表面著色器編譯管線的某些部分不能理解特定于 DirectX 11 的 HLSL(Microsoft 的著色器語(yǔ)言)語(yǔ)法。
如果您正在使用 HLSL 功能(比如?StructuredBuffers
、RWTextures
?和其他非 DirectX 9 語(yǔ)法),請(qǐng)將它們包裹在 DirectX X11 專用的預(yù)處理器宏中,如下例所示。
# ifdef SHADER_API_D3D11
// DirectX11 專用代碼,例如
StructuredBuffer
使用著色器幀緩沖提取
一些 GPU(最明顯的是 iOS 上基于 PowerVR 的 GPU)允許您通過(guò)提供當(dāng)前片元顏色作為片元著色器的輸入來(lái)進(jìn)行某種可編程混合(請(qǐng)參閱?khronos.org?上的?EXT_shader_framebuffer_fetch
)。
可在 Unity 中編寫(xiě)使用幀緩沖提取功能的著色器。要執(zhí)行此操作,請(qǐng)?jiān)谑褂?HLSL(Microsoft 的著色語(yǔ)言,請(qǐng)參閱?msdn.microsoft.com)或 Cg(Nvidia 的著色語(yǔ)言,請(qǐng)參閱?nvidia.co.uk)編寫(xiě)片元著色器時(shí)使用?inout
?顏色參數(shù)。
以下示例采用的是 Cg 語(yǔ)言。
CGPROGRAM
// 只為可能支持該功能的平臺(tái)(目前是 gles、gles3 和 metal)
// 編譯著色器
# pragma only_renderers framebufferfetch
void frag (v2f i, inout half4 ocol : SV_Target) { ?
?// ocol 可以被讀?。ó?dāng)前幀緩沖區(qū)顏色) ?
?// 并且可以被寫(xiě)入(將顏色更改為該顏色) ?
?// ...
} ?
ENDCG
著色器中的深度 (Z) 方向
深度 (Z) 方向在不同的著色器平臺(tái)上不同。
DirectX 11、DirectX 12、PS4、Xbox One、Metal:反轉(zhuǎn)方向
深度 (Z) 緩沖區(qū)在近平面處為 1.0,在遠(yuǎn)平面處減小到 0.0。
裁剪空間范圍是 [near,0](表示近平面處的近平面距離,在遠(yuǎn)平面處減小到 0.0)。
其他平臺(tái):傳統(tǒng)方向
深度 (Z) 緩沖區(qū)值在近平面處為 0.0,在遠(yuǎn)平面處為 1.0。
裁剪空間取決于具體平臺(tái):
在 Direct3D 類(lèi)平臺(tái)上,范圍是 [0,far](表示在近平面處為 0.0,在遠(yuǎn)平面處增加到遠(yuǎn)平面距離)。
在 OpenGL 類(lèi)平臺(tái)上,范圍是 [-near,far](表示在近平面處為負(fù)的近平面距離,在遠(yuǎn)平面處增加到遠(yuǎn)平面距離)。
請(qǐng)注意,使反轉(zhuǎn)方向深度 (Z) 與浮點(diǎn)深度緩沖區(qū)相結(jié)合,可顯著提高相對(duì)于傳統(tǒng)方向的深度緩沖區(qū)精度。這樣做的優(yōu)點(diǎn)是降低 Z 坐標(biāo)的沖突并改善陰影,特別是在使用小的近平面和大的遠(yuǎn)平面時(shí)。
因此,在使用深度 (Z) 發(fā)生反轉(zhuǎn)的平臺(tái)上的著色器時(shí):
定義了 UNITY_REVERSED_Z。
_CameraDepth
?紋理的紋理范圍是 1(近平面)到 0(遠(yuǎn)平面)。裁剪空間范圍是“near”(近平面)到 0(遠(yuǎn)平面)。
但是,以下宏和函數(shù)會(huì)自動(dòng)計(jì)算出深度 (Z) 方向的任何差異:
Linear01Depth(float z)
LinearEyeDepth(float z)
UNITY_CALC_FOG_FACTOR(coord)
提取深度緩沖區(qū)
如果要手動(dòng)提取深度 (Z) 緩沖區(qū)值,則可能需要檢查緩沖區(qū)方向。以下是執(zhí)行此操作的示例:
float z = tex2D(_CameraDepthTexture, uv);
# if defined(UNITY_REVERSED_Z) ?
z = 1.0f - z;
# endif
使用裁剪空間
如果要手動(dòng)使用裁剪空間 (Z) 深度,則可能還需要使用以下宏來(lái)抽象化平臺(tái)差異:
float clipSpaceRange01 = UNITY_Z_0_FAR_FROM_CLIPSPACE(rawClipSpace);
注意:此宏不會(huì)改變 OpenGL 或 OpenGL ES 平臺(tái)上的裁剪空間,因此在這些平臺(tái)上,此宏返回“-near”1(近平面)到 far(遠(yuǎn)平面)之間的值。
投影矩陣
如果處于深度 (Z) 發(fā)生反轉(zhuǎn)的平臺(tái)上,則?GL.GetGPUProjectionMatrix()?返回一個(gè)還原了 z 的矩陣。 但是,如果要手動(dòng)從投影矩陣中進(jìn)行合成(例如,對(duì)于自定義陰影或深度渲染),您需要通過(guò)腳本按需自行還原深度 (Z) 方向。
以下是執(zhí)行此操作的示例:
var shadowProjection = Matrix4x4.Ortho(...);
//陰影攝像機(jī)投影矩陣
var shadowViewMat = ... ? ?
//陰影攝像機(jī)視圖矩陣
var shadowSpaceMatrix = ...
//從裁剪空間到陰影貼圖紋理空間 ? ?
//當(dāng)引擎通過(guò)攝像機(jī)投影計(jì)算設(shè)備投影矩陣時(shí),
//"m_shadowCamera.projectionMatrix"被隱式反轉(zhuǎn)
m_shadowCamera.projectionMatrix = shadowProjection;
//"shadowProjection"在連接到"m_shadowMatrix"之前被手動(dòng)翻轉(zhuǎn),
//因?yàn)樗灰暈橹鞯钠渌仃嚒?
if(SystemInfo.usesReversedZBuffer) { ? ?
?shadowProjection[2, 0] = -shadowProjection[2, 0]; ? ?
?shadowProjection[2, 1] = -shadowProjection[2, 1]; ?
?shadowProjection[2, 2] = -shadowProjection[2, 2]; ?
?shadowProjection[2, 3] = -shadowProjection[2, 3];
} ? ?
m_shadowMatrix = shadowSpaceMatrix * shadowProjection * shadowViewMat;
深度 (Z) 偏差
Unity 自動(dòng)處理深度 (Z) 偏差,以確保其與 Unity 的深度 (Z) 方向匹配。但是,如果要使用本機(jī)代碼渲染插件,則需要在 C 或 C++ 代碼中消除(反轉(zhuǎn))深度 (Z) 偏差。
深度 (Z) 方向檢查工具
使用?SystemInfo.usesReversedZBuffer?可確認(rèn)所在平臺(tái)是否使用反轉(zhuǎn)深度 (Z)。