カメラの深度テクスチャ
シェーダー LOD

プラットフォーム特有のレンダリングの違い

Unity は、OpenGLDirect3DMetal、ゲームコンソールなど、さまざまなグラフィックスライブラリプラットフォームで使用することができます。ある状況では、プラットフォームとシェーダー言語のセマンティクス間で、グラフィックスレンダリングの挙動の相違があります。ほとんどの場合、Unity エディターはその違いを表面に出しませんが、隠せない場合もあります。このような状況では、プラットフォーム間の違いを無効にする必要があります。そのような状況と、それが発生した場合に行うべき処理を以下に説明します。

レンダーテクスチャ座標

垂直のテクスチャ座標の規則は 2 つのプラットフォーム、Direct3D 類似のものと OpenGL 類似のものとでは異なります。

  • Direct3D 類: 座標の一番上が 0 で、下に行くにつれ数値が増加します。これは Direct3D、Metal、コンソールに当てはまります。
  • OpenGL 類: 座標の一番下が 0 で、上に行くにつれ数値が増加します。これは OpenGL と OpenGL ES に当てはまります。

この違いは、レンダーテクスチャ にレンダリングするとき以外は、プロジェクトにまったく影響しない傾向があります。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 空間のレンダリング

特殊効果やツールのためにテクスチャ座標 (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 宣言

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。ときおり、シェーダーは COLORCOLOR0 を使用し、シェーダーをすべてのプラットフォームで使えるようにします。これは、Sony PS4 ではうまく挙動しません。

メッシュを点としてレンダリングするときは、頂点シェーダから PSIZE セマンティクスを出力します (たとえば、1 に設定します)。OpenGL ES や Metal などの一部のプラットフォームでは、シェーダーから書き込まれないときにポイントサイズを「未定義」として扱います。

詳細については、シェーダーセマンティクス に関するドキュメントを参照してください。

Direct3D シェーダーコンパイラーシンタックス

Direct3D プラットフォームは、Microsoft の HLSL シェーダーコンパイラー を使用します。HLSL コンパイラーは、さまざまな微妙なシェーダーエラーについて他のコンパイラーよりも厳密です。例えば、正しく初期化されていない関数出力値は受け入れません。

これを使用して実行する可能性のある最も一般的な状況は以下のとおりです。

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 (DX11) HLSL シンタックス

サーフェスシェーダー コンパイルパイプラインの一部は、DirectX 11 特有の HLSL (Microsoft’s shader language) シンタックスを理解しません。

StructuredBuffersRWTextures、その他の非 DirectX 9 シンタックスのような HLSL 関数を使用する場合は、この例のように DirectX X11 だけのプリプロセッサーマクロでそれらをラップします。

#ifdef SHADER_API_D3D11
// DirectX11 だけのコード。例えば
StructuredBuffer<float4> myColors;
RWTexture2D<float4> myRandomWriteTexture;
#endif

Shader framebuffer fetch の使用

一部の GPU (特に iOS の PowerVR ベースのもの) では、現在のフラグメントカラーをフラグメントシェーダーに入力することによって、プログラム可能なブレンディングを行うことができます (khronos.orgEXT_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) の向き

深度 (Z) の向きはシェーダープラットフォームによって異なります。

DirectX 11, DirectX 12, PS4, Xbox One, Metal: 逆方向

  • 深度 (Z) バッファーは、ニアクリップ面で 1.0、ファークリップ面の 0.0 まで減少します。

  • クリップ空間範囲は、 [near,0] (つまり、ニアクリップ面の距離から減少し、ファークリップ面の 0.0 まで)。

他のプラットフォーム: 伝来の方向

  • 深度 (Z) バッファーは、ニアクリップ面で 0.0、ファークリップ面では 1.0 。

  • クリップ空間は特定のプラットフォームで異なります。

  • Direct3D のようなプラットフォームでは、範囲は [0,far] (つまり、ニアクリップ面の 0.0 から増加し、ファークリップ面の距離まで)。

  • OpenGL のようなプラットフォームでは、範囲は [-near,far] (つまり、マイナスの “ニアクリップ面の距離” から増加し、ファークリップ面の距離まで)。

浮動小数点深度バッファーと組み合わせた逆方向深度 (Z) は、元来の方向での深度バッファー精度を大幅に向上させます。 これの利点は、特に小さなニアクリップ面と大きなファークリップ面を使用する場合、Z 座標と影の矛盾が少なくなります。

そのため、逆方向深度 (Z) を使ったプラットフォームのシェーダーを使用する場合は

  • UNITY_REVERSED_Z が定義されます。
  • _CameraDepth Texture のテクスチャ範囲は 1 (ニア) から 0 (ファー) です。
  • クリップ空間範囲は “near” (ニア) から 0 (ファー) の範囲内です。

ただし、以下のマクロと関数は、自動的に深度 (Z) 方向の違いをすべて処理します。

  • Linear01Depth(float z)
  • LinearEyeDepth(float z)
  • UNITY_CALC_FOG_FACTOR(coord)

深度バッファーのフェッチ

深度 (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;

深度 (Z) バイアス

Unity は自動的に深度 (Z) バイアスを処理して、Unity の深度 (Z) 方向に一致するようにします。ただし、ネイティブコードレンダリングプラグインを使用している場合は、C または C++ コードの深度 (Z) バイアスを打ち消す (反転) 必要があります。

深度 (Z) 方向を確認するツール

プラットフォームが逆の深度 (Z) 方向を使用しているかどうかを調べるには、SystemInfo.usesReversedZBuffer を使用します。

カメラの深度テクスチャ
シェーダー LOD