Unity は、OpenGL、Direct3D、Metal、ゲームコンソールなど、さまざまなグラフィックスライブラリプラットフォームで使用することができます。ある状況では、プラットフォームとシェーダー言語のセマンティクス間で、グラフィックスレンダリングの挙動の相違があります。ほとんどの場合、Unity エディターはその違いを表面に出しませんが、隠せない場合もあります。このような状況では、プラットフォーム間の違いを無効にする必要があります。そのような状況と、それが発生した場合に行うべき処理を以下に説明します。
垂直のテクスチャ座標の規則は 2 つのプラットフォーム、Direct3D 類似のものと OpenGL 類似のものとでは異なります。
この違いは、レンダーテクスチャ にレンダリングするとき以外は、プロジェクトにまったく影響しない傾向があります。Direct3D のようなプラットフォーム上でテクスチャにレンダリングするとき、Unity は内部的にレンダリングを逆さまに反転させます。これにより、異なる類のプラットフォーム (OpenGL 類) とのきまりを一致させることができます。
UV 空間でのイメージエフェクトとレンダリングでは、自分で処理を行って異なる座標のきまりによる問題が発生しないように気を配る必要があります。
イメージエフェクト とアンチエイリアシングを使用する場合、イメージエフェクトの結果のソーステクスチャは、OpenGL などのプラットフォームの規則に一致するように反転されません。この場合、Unity はスクリーンに描画してアンチエイリアシングを行ってから、レンダーテクスチャにレンダリングしイメージエフェクトでさらに処理します。
イメージエフェクトが一度に 1 つのレンダーテクスチャを処理する単純なものであるときは、Graphics.Blit は一貫性のない座標を扱うことができます。ただし、イメージエフェクト で複数の レンダーテクスチャ を一緒に処理するときは、Direct3D などのプラットフォームで使用する場合やアンチエイリアスを使用する場合、レンダーテクスチャの垂直座標の方向が異なってしまう傾向があります。座標を標準化するには、頂点シェーダー内でスクリーンのテクスチャを手動で上下反転させます。すると、OpenGL のような座標基準に一致します。
次のコード例は、これを行う方法を示しています。
// テクスチャを上下反転させる例
// 主要テクスチャの
// テクセルサイズはネガティブの 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 のような投影座標と一致するように調整します。
ビルトイン変数 ProjectionParams.x
には +1
か -1
の値が含まれています。-1
は投影が OpenGL のような投影座標に一致するように上下を逆さまに反転したことを示し、+1
は反転していないことを示します。
シェーダーでこの値をチェックして、異なる対応を取れます。以下の例では、投影が反転しているかどうかを確認し、反転している場合は、UV 座標を反転して一致させます。
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
//潜在的に可能なプラットフォーム (現在 gles、gles3、metal) の
//シェーダーのみをコンパイルします。
#pragma only_renderers framebufferfetch
void frag(v2f i、inout half4 ocol:SV_Target)
{
//ocol は現在のフレームバッファカラーの読み込みと
//... への書き込み (その色に変更します) が可能です。
}
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() は、深度 (Z) が反転したプラットフォーム上では、Zが反転した行列を返します。ただし、投影行列から手動で作成する場合は (例えば、カスタムの影や深度レンダリングなど)、スクリプトを通して適用するために、深度 (Z) の方向を手動で反転する必要があります。
以下はその例です。
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 を使用します。