Unity runs on various graphics library platforms: Open GL, Direct3D, Metal, and games consoles. In some cases, there are differences in how graphics rendering behaves between the platforms and Shader language semantics. Most of the time the Unity Editor hides the differences, but there are some situations where the Editor cannot do this for you. In these situations, you need to ensure that you negate differences between the platforms. These situations, and the actions you need to take if they occur, are listed below.
垂直のテクスチャ座標の規則は 2 つのプラットフォーム、Direct3D 類似のものと OpenGL 類似のものとでは異なります。
この違いは、レンダーテクスチャ にレンダリングするとき以外は、プロジェクトにまったく影響しない傾向があります。Direct3D のようなプラットフォーム上でテクスチャにレンダリングするとき、Unity は内部的にレンダリングを逆さまに反転させます。これにより、異なる類のプラットフォーム (OpenGL 類) とのきまりを一致させることができます。
UV 空間でのイメージエフェクトとレンダリングでは、自分で処理を行って異なる座標のきまりによる問題が発生しないように気を配る必要があります。
When you use Image Effects and anti-aliasing, the resulting source Texture for an Image Effect is not flipped to match the OpenGL-like platform convention. In this case, Unity renders to the screen to get anti-aliasing and then resolves rendering into a Render Texture for further processing with an Image Effect.
If your Image Effect is a simple one that processes one Render Texture at a time, Graphics.Blit deals with the inconsistent coordinates. However, if you’re processing more than one Render Texture together in your Image Effect, the Render Textures are likely to come out at different vertical orientations in Direct3D-like platforms and when you use anti-aliasing. To standardise the coordinates, you need to manually “flip” the screen Texture upside down in your Vertex Shader so that it matches the OpenGL-like coordinate standard.
次のコード例は、これを行う方法を示しています。
// テクスチャを上下反転させる例
// 主要テクスチャの
// テクセルサイズはネガティブの Y になります)
# if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
uv.y = 1-uv.y;
# endif
より詳細な例は、Unity の Shader Replacement サンプルプロジェクトの Edge Detection のシーンを参照してください ( Unity の Learn リソース を参照)。 このプロジェクトのエッジ検出では、画面のテクスチャとカメラの 深度テクスチャと法線テクスチャ の両方を使用します。
同様の状況が GrabPass で発生します。 結果のレンダーテクスチャは、Direct3D のような (OpenGL 類でない) プラットフォームで、実際には逆さまにされない場合もあります。シェーダーコードが GrabPass テクスチャをサンプリングする場合、UnityCG インクルードファイルから ComputeGrabScreenPos
関数を使用します。
特殊効果やツールのためにテクスチャ座標 (UV) 空間でレンダリングする場合、レンダリングが Direct3D 類と OpenGL 類の座標系で一貫するように、シェーダーの調整が必要な場合があります。また、画面にレンダリングするときとテクスチャにレンダリングするときで、レンダリングの調整が必要な場合があるかもしれません。このような場合は、Direct3D のような投影を上下反転させて、その座標が OpenGL のような投影座標と一致するように調整します。
The built-in variable ProjectionParams.x
contains a +1
or –1
value. -1
indicates a projection has been flipped upside down to match OpenGL-like projection coordinates, while +1
indicates it hasn’t been flipped.
You can check this value in your Shaders and then perform different actions. The example below checks if a projection has been flipped and, if so, flips and then returns the UV coordinates to match.
float4 vert(float2 uv : TEXCOORD0) : SV_POSITION
{
float4 pos;
pos.xy = uv;
// この例は上下反転した投影で描画されます
// ですから垂直の UV 座標も反転させます
if (_ProjectionParams.x < 0)
pos.y = 1 - pos.y;
pos.z = 0;
pos.w = 1;
return pos;
}
テクスチャ座標と同様に、Direct3D のようなプラットフォームと OpenGL のようなプラットフォームでは、クリップ空間座標 (ポストプロジェクション空間の座標とも呼ばれます) は異なります。
Direct3D 類:クリップ空間の深度の範囲は、ニアクリップ面の 0.0 からファークリップ面の +1.0 までになります。これは Direct3D、Metal、コンソールに適用されます。
OpenGL 類:クリップ空間の深度の範囲は、ニアクリップ面の –1.0 からファークリップ面の +1.0 までになります。これは OpenGL と OpenGL ES に適用されます。
シェーダーコードの内部では、UNITY_NEAR_CLIP_VALUE
ビルトインマクロ を使用して、プラットフォームに基づいてニアクリップ面の値を取得できます。
プラットフォームに適切な場合は、スクリプトコードの中で、GL.GetGPUProjectionMatrix を使用して、Unity の座標系 (OpenGL のような規則に従っています) から Direct3D 類の座標に変換します。
精度の問題を回避するには、必ずターゲットプラットフォームでシェーダをテストしてください。モバイルデバイスと PC の GPU は、浮動小数点型の処理方法が異なります。PC の GPU は、すべての浮動小数点型 (float、half、fixed) を同じものとして扱います。つまり、すべての演算を完全な 32 ビット精度で行います。一方、多くのモバイルデバイス GPU はこのようには行いません。
詳しくは シェーダーのデータタイプと精度 を参照してください。
const
の使用は、Microsoft HSL (msdn.microsoft.com 参照) と OpenGL の GLSL (Wikipedia 参照) シェーダー言語で異なります。
Microsoft の HLSL の const
は、宣言された変数がそのスコープ内では読み取り専用ですが、どのような方法でも初期化できる点で、C# や C++ とほとんど同じ意味を持ちます。
OpenGL の GLSL const
は、変数が実際コンパイル時の定数であることを意味するため、コンパイル時の制約 (リテラル値、または他の const
の計算) で初期化する必要があります。
OpenGL の GLSL のセマンティクスに従うことが最善であり、本当に不変であるときにだけ変数を const
として宣言します。const
変数を他の変更可能な値 (例えば、関数のローカル変数など) で初期化することは避けてください。これは Microsoft の HLSL でも有効です。このように const
を使用すると、一部のプラットフォームで混乱しやすいエラーを避けることができます。
シェーダーをすべてのプラットフォームで使えるようにするためには、シェーダー値のいくつかはこれらのセマンティクスを使う必要があります。
頂点シェーダーの出力する (クリップスペース) 位置: SV_POSITION
。ときおり、シェーダーは POSITION セマンティクスを使用し、シェーダーをすべてのプラットフォームで使えるようにします。これは、Sony PS4 やテッセレーションを使う場合にはうまく挙動しません。
フラグメントシェーダーの出力する色: SV_Target
。ときおり、シェーダーは COLOR
や COLOR0
を使用し、シェーダーをすべてのプラットフォームで使えるようにします。これは、Sony PS4 ではうまく挙動しません。
メッシュを点としてレンダリングするときは、頂点シェーダから PSIZE
セマンティクスを出力します (たとえば、1 に設定します)。OpenGL ES や Metal などの一部のプラットフォームでは、シェーダーから書き込まれないときにポイントサイズを「未定義」として扱います。
詳細については、シェーダーセマンティクス に関するドキュメントを参照してください。
Direct3D プラットフォームは、Microsoft の HLSL シェーダーコンパイラー を使用します。HLSL コンパイラーは、さまざまな微妙なシェーダーエラーについて他のコンパイラーよりも厳密です。例えば、正しく初期化されていない関数出力値は受け入れません。
これを使用して実行する可能性のある最も一般的な状況は以下のとおりです。
out
パラメーターをもつ サーフェスシェーダー 頂点モディファイアー。出力をこのように初期化します。 void vert (inout appdata_full v, out Input o)
{
**UNITY_INITIALIZE_OUTPUT(Input,o);**
// ...
}
部分的に初期化された値。例えば、関数は float4
を返しますが、コードはその .xyz
値のみを設定します。3 つの値だけが必要な場合は、すべての値を設定するか、float3
に変更します。
頂点シェーダで tex2D
を使用する場合。UV デリバティブが頂点シェーダーに存在しないため、これは無効です。代わりに、明示的なミップレベルをサンプリングする必要があります。例えば、 tex2Dlod
(tex, float4(uv,0,0)
) を使用します。また、tex2Dlod
はシェーダーモデル 3.0 の機能なので #pragma target 3.0
を加える必要があります。
サーフェスシェーダー コンパイルパイプラインの一部は、DirectX 11 特有の HLSL (Microsoft’s shader language) シンタックスを理解しません。
StructuredBuffers
、RWTextures
、その他の非 DirectX 9 シンタックスのような HLSL 関数を使用する場合は、この例のように DirectX X11 だけのプリプロセッサーマクロでそれらをラップします。
#ifdef SHADER_API_D3D11
// DirectX11 だけのコード。例えば
StructuredBuffer<float4> myColors;
RWTexture2D<float4> myRandomWriteTexture;
#endif
一部の GPU (特に iOS の PowerVR ベースのもの) では、現在のフラグメントカラーをフラグメントシェーダーに入力することによって、プログラム可能なブレンディングを行うことができます (khronos.org の EXT_shader_framebuffer_fetch
を参照)。
framebuffer fetch 機能を使用するシェーダーを Unity で書くことは可能です。これを行うには、フラグメントシェーダーを HLSL (Microsoft のシェーディング言語 - msdn.microsoft.com を参照) または Cg (Nvidia のシェーディング言語 - nvidia.co.uk を参照) のいずれかに書き込むときに inout
の色の引数を使用します。
以下の例は Cg の場合です。
CGPROGRAM
// only compile Shader for platforms that can potentially
// do it (currently gles,gles3,metal)
#pragma only_renderers framebufferfetch
void frag (v2f i, inout half4 ocol : SV_Target)
{
// ocol can be read (current framebuffer color)
// and written into (will change color to that one)
// ...
}
ENDCG
深度 (Z) の向きはシェーダープラットフォームによって異なります。
DirectX 11, DirectX 12, PS4, Xbox One, Metal: 逆方向
深度 (Z) バッファーは、ニアクリップ面で 1.0、ファークリップ面の 0.0 まで減少します。
クリップ空間範囲は、 [near,0] (つまり、ニアクリップ面の距離から減少し、ファークリップ面の 0.0 まで)。
他のプラットフォーム: 伝来の方向
深度 (Z) バッファーは、ニアクリップ面で 0.0、ファークリップ面では 1.0 。
浮動小数点深度バッファーと組み合わせた逆方向深度 (Z) は、元来の方向での深度バッファー精度を大幅に向上させます。 これの利点は、特に小さなニアクリップ面と大きなファークリップ面を使用する場合、Z 座標と影の矛盾が少なくなります。
そのため、逆方向深度 (Z) を使ったプラットフォームのシェーダーを使用する場合は
_CameraDepth
Texture のテクスチャ範囲は 1 (ニア) から 0 (ファー) です。ただし、以下のマクロと関数は、自動的に深度 (Z) 方向の違いをすべて処理します。
Linear01Depth(float z)
LinearEyeDepth(float z)
深度 (Z) バッファーの値を手動でフェッチしている場合は、バッファーの方向を確認してください。以下は、その例です。
float z = tex2D(_CameraDepthTexture, uv);
#if defined(UNITY_REVERSED_Z)
z = 1.0f - z;
#endif
クリップ空間の (Z) 深度を手動で使用している場合は、以下のマクロを使ってプラットフォームの違いを抽出してください。
float clipSpaceRange01 = UNITY_Z_0_FAR_FROM_CLIPSPACE(rawClipSpace);
Note: このマクロは、OpenGL または OpenGL ES プラットフォームのクリップ空間を変更しないため、これらのプラットフォームでは “-near”1 (ニア) から far (ファー) の範囲内で返します。
GL.GetGPUProjectionMatrix() returns a z-reverted matrix if you are on a platform where the depth (Z) is reversed. However, if you’re composing from projection matrices manually (for example, for custom shadows or depth rendering), you need to revert depth (Z) direction yourself where it applies via script.
以下はその例です。
var shadowProjection = Matrix4x4.Ortho(...); //shadow カメラプロジェクション行列
var shadowViewMat = ... //shadow カメラビュー行列 matrix
var shadowSpaceMatrix = ... //クリップから shadowMap テクスチャ空間へ
//エンジンが、カメラのプロジェクションからデバイスのプロジェクション行列を計算する場合、
//'m_shadowCamera.projectionMatrix' は暗示的に反転されます
m_shadowCamera.projectionMatrix = shadowProjection;
//'m_shadowMatrix' に追加される前に、'shadowProjection' は手動で反転されます
//なぜなら、それはシェーダにとって他のマトリックスとして見られるからです
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;
Unity は自動的に深度 (Z) バイアスを処理して、Unity の深度 (Z) 方向に一致するようにします。ただし、ネイティブコードレンダリングプラグインを使用している場合は、C または C++ コードの深度 (Z) バイアスを打ち消す (反転) 必要があります。
プラットフォームが逆の深度 (Z) 方向を使用しているかどうかを調べるには、SystemInfo.usesReversedZBuffer を使用します。