Version: Unity 6.0 (6000.0)
言語 : 日本語
テクスチャサンプラー
シェーダーでの 16 ビット精度の使用

様々なグラフィックス API のシェーダーの作成

様々なグラフィックス API の間で、グラフィックスレンダリングの動作が異なる場合があります。大抵、Unity エディターはその違いをカバーしてくれますが、エディターでは対応できない状況もあります。これらの状況と、その場合に必要な対処法を説明します。

レンダーテクスチャ座標

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

  • Direct3D 類似: 最上部が 0 の座標位置となり、下方向に行くにしたがって増加します。これは Direct3D、Metal、コンソールに適用されます。
  • OpenGL 類似: 最下部が 0 の座標位置となり、上方向に行くにしたがって増加します。これは OpenGL と OpenGL ES に適用されます。

この違いは、レンダーテクスチャにレンダリングするとき以外は、プロジェクトにまったく影響しない傾向があります。Direct3D のようなプラットフォーム上でテクスチャにレンダリングするとき、Unity は内部的にレンダリングを逆さまに反転させます。これにより、異なる類のプラットフォーム (OpenGL 類) とのきまりを一致させることができます。

UV 空間でのイメージエフェクトとレンダリングでは、自分で処理を行って異なる座標のきまりによる問題が発生しないように気を配る必要があります。

イメージエフェクト イメージエフェクトとアンチエイリアスを使用する場合、イメージエフェクトの結果のソーステクスチャは、OpenGL などのプラットフォームの規則に一致するように反転されません。この場合、Unity はスクリーンに描画してアンチエイリアスを行ってから、レンダーテクスチャにレンダリングしイメージエフェクトでさらに処理します。

イメージエフェクトが一度に 1 つのレンダーテクスチャを処理する単純なものである場合は、Graphics.Blit が不整合な座標を処理します。しかし、イメージエフェクトで 1 回にレンダーテクスチャを複数まとめる場合、多くのケースでそれぞれが異なる垂直方向に出力されます (ただし Direct3D のようなプラットフォームであり、かつアンチエイリアスが使用されている場合のみ)。座標を標準化するためには、頂点シェーダーでスクリーンテクスチャを手動で上下を “反転” させ、OpenGL のような座標標準に一致させる必要があります。

次のコード例は、これを行う方法を示しています。

// Flip sampling of the Texture:
// The main Texture
// texel size will have negative Y).

#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
        uv.y = 1-uv.y;
#endif

同様の状況が GrabPass で発生します。結果のレンダーテクスチャは、Direct3D のような (OpenGL 類似でない) プラットフォームで、実際には逆さまにされない場合もあります。シェーダーコードが GrabPass テクスチャをサンプリングする場合、UnityCG インクルードファイルから ComputeGrabScreenPos 関数を使用します。

UV 空間のレンダリング 特殊効果やツールのためにテクスチャ座標 (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;
    // This example is rendering with upside-down flipped projection,
    // so flip the vertical UV coordinate too
    if (_ProjectionParams.x < 0)
        pos.y = 1 - pos.y;
    pos.z = 0;
    pos.w = 1;
    return pos;
}

クリップスペース座標 テクスチャ座標と同様に、Direct3D のようなプラットフォームと OpenGL のようなプラットフォームでは、クリップスペース座標 (投影後の空間座標とも呼ばれます) は異なります。

  • Direct3D 類似: クリップスペースの深度の範囲は、ニアクリップ面の +1.0 からファークリップ面の 0.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 は、このようには処理しません。

詳細は、シェーダーでの 16 ビット精度の使用を参照してください。

シェーダーのデータ型サポート

Unity でシェーダーを記述する場合、Unity は halffloat または min16float として定義していることに注意してください。シェーダー精度モデル__が__プラットフォームのデフォルト__に設定されている場合、macOS では halffloat になり、iOS/tvOS では min16float になります。シェーダー精度モデル__が Unified に設定されている場合、macOS/tvOS/iOS の halfmin16float になります。

Unity 6 以降、Metal 上の min16float のサイズと整列は、頂点シェーダー入力、定数バッファ、構造化バッファなど、あらゆる CPU で認識可能なバッファでは 4 バイトになります。したがって、half のサイズと整列は、プラットフォームやプロジェクトの設定にかかわらず、常に同じになります。以前のバージョンの Unity では、min16float のサイズと整列が 2 バイトであったため、half を含むバッファのレイアウトは、プラットフォームや選択した__シェーダー精度モデル__の設定によって異なっていました。この問題により、iOS と tvOS のユーザーは、iOS/tvOS の GPU バッファにデータをアップロードする際の回避策として C# コードを追加する必要がありましたが、これは Unity 6 には適用されなくなりました。Unity 6 で FXC を使用してコンパイルする際に以前の動作を有効にするには、シェーダーに #pragma metal_fxc_allow_float16_in_cpu_visible_buffers を追加します。

シェーダーでの Const 宣言 const の使用は、Microsoft HLSL (msdn.microsoft.com を参照) と OpenGL の GLSL (Wikipedia を参照) シェーダー言語で異なります。

  • Microsoft の HLSL の const は、宣言された変数がそのスコープ内では読み取り専用ですが、どのような方法でも初期化できる点で、C# や C++ とほとんど同じ意味を持ちます。

  • OpenGL の GLSL const は、変数が実際コンパイル時の定数であることを意味するため、コンパイル時の制約 (リテラル値、または他の const の計算) で初期化する必要があります。

OpenGL の GLSL セマンティクスに従い、変数が本当に不変である場合にのみ const として宣言することをお勧めします。const 変数を他の可変値 (関数のローカル変数など) で初期化しないでください。これは Microsoft の HLSL でも機能するため、この方法で const を使用すると、一部のプラットフォームで混乱を招くエラーを回避できます。

GPU バッファでのバッファの使用

バッファを使用してシェーダーで変数を宣言し、GPU 演算バッファまたはグラフィックスバッファからのデータを使用して値を設定する場合、グラフィックス API によってはデータレイアウトが一致しない場合があります。これは、データを上書きしたり、変数を間違った値に設定したりする可能性があることを意味します。

例えば、cbuffer または Unity の定数バッファマクロを使用する場合、定数バッファのデータレイアウトとグラフィックス API によって、float3float4 になったり、floatfloat2 になったりします。

すべてのグラフィックス API が同じデータレイアウトでバッファをコンパイルするようにするには、以下の方法を使用します。

  • float3float3x3 の代わりに float4float4x4 を使用します。なぜなら、float4 変数はすべてのグラフィックス API で同じサイズですが、一部のグラフィックス API では float3 変数のサイズが異なる可能性があるためです。
  • すべてのグラフィックス API でデータが同じ方法で構造化されるように、例えば、float4float2float のように、サイズが大きいものから順に変数を宣言します。

例:

cbuffer myConstantBuffer {
    float4x4 matWorld;
    float4 vObjectPosition; // Uses a float4 instead of a float3
    float arrayIndex;
}

シェーダーに使用されるセマンティクス すべてのプラットフォーム上でシェーダーを動作させるためには、一部のシェーダー値に以下のようなセマンティクスを使用する必要があります。

  • 頂点シェーダーの出力する (クリップスペース) 位置: 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 のシェーダー言語) 構文を理解しません。

StructuredBuffersRWTextures、その他の DirectX 9 以外の構文などの HLSL 機能を使用する場合は、以下の例のように DirectX X11 だけのプリプロセッサーマクロでそれらをラップします。

#ifdef SHADER_API_D3D11
// DirectX11-specific code, for example
StructuredBuffer<float4> myColors;
RWTexture2D<float4> myRandomWriteTexture;
#endif

シェーダーのフレームバッファフェッチの使用一部の GPU (最も顕著なものは iOS の PowerVR ベースのもの) では、フラグメントシェーダーへの入力として現在のフラグメントカラーを渡して、プログラマブルなブレンディング処理を行うことができます (khronos.orgEXT_shader_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, 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);

ノート: このマクロは、OpenGL または OpenGL ES プラットフォームのクリップスペースを変更しないため、これらのプラットフォームでは “-near”1 (ニア) から far (ファー) の範囲内で返します。

射影行列 GL.GetGPUProjectionMatrix() は、深度 (Z) が反転したプラットフォーム上では、Z が反転した行列を返します。GL.GetGPUProjectionMatrix() は、深度 (Z) が反転したプラットフォーム上では、Zが反転した行列を返します。ただし、投影行列から手動で作成する場合は (例えば、カスタムの影や深度レンダリングなど)、スクリプトを通して適用するために、深度 (Z) の方向を手動で反転する必要があります。

以下はその例です。

var shadowProjection = Matrix4x4.Ortho(...); //shadow camera projection matrix
var shadowViewMat = ...     //shadow camera view matrix
var shadowSpaceMatrix = ... //from clip to shadowMap texture space

//'m_shadowCamera.projectionMatrix' is implicitly reversed
//when the engine calculates device projection matrix from the camera projection
m_shadowCamera.projectionMatrix = shadowProjection;

//'shadowProjection' is manually flipped before being concatenated to 'm_shadowMatrix'
//because it is seen as any other matrix to a Shader.
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 を使用します。
テクスチャサンプラー
シェーダーでの 16 ビット精度の使用