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

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

Unity はさまざまなプラットフォームで動作し、いくつかのケースでは動作に違いが出ます。ほとんどの場合、Unity は違いを隠してくれますが、それでも表面化する場合があります。

Render Texture の座標

垂直方向のテクスチャ座標の表現方法は、Direct3D と OpenGL のプラットフォームで異なります。

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

ほとんどの場合に影響はありませんが、レンダーテクスチャ に対してレンダリングする場合は影響があります。この場合、Unity は OpenGL 以外でレンダリングするときに意図的にレンダリングを上下逆に反転するので、プラットフォーム間のルールは同じままです。シェーダーで処理する必要のある一般的な例は、イメージエフェクトと、UV空間のレンダリングです。

イメージエフェクト、上下逆のレンダーテクスチャ、MSAA

「非 OpenGL 上で、テクスチャにレンダリングするときに上下逆さまのレンダリング」が起こらないケースは、イメージエフェクト やアンチエイリアスが使用される場合です。この場合、Unty はアンチエイリアスして、後続のイメージエフェクトの処理のためにレンダーテクスチャに対してレンダリングを“溶かし込みます”。結果となるイメージエフェクトのソーステクスチャは Direct3D/Metal で上下逆には なりません (他のレンダーテクスチャと違って)。

もしイメージエフェクトがシンプルである( 1 回にテクスチャを 1 つのみ処理する)場合、Graphics.Blit が処理してくれるため、このことを気にする必要はありません。

しかし、イメージエフェクトで 1 回にレンダーテクスチャを複数まとめる場合、多くのケースでそれぞれが異なる垂直方向に出力されます(ただし Direct3D のようなプラットフォームであり、かつアンチエイリアスが使用されている場合のみ)。頂点シェーダーで手動で画面テクスチャを「上下逆さま」にする必要があります。

// On non-GL when AA is used, the main Texture and scene depth Texture
// will come out in different vertical orientations.
// So flip sampling of the Texture when that is the case (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

Shader Replacement サンプルプロジェクト でエッジ検出を行っているシーンを確認ください。ここでエッジ検出はシーンのテクスチャとカメラの DepthNormals テクスチャ の両方で使用されています。

同様の状況が GrabPass を使用するときに発生します。処理されたレンダーテクスチャは、OpenGL プラットフォーム以外では、実際には上下逆でない場合があります。通常は、GrabPass テクスチャをサンプリングするシェーダーコードは UnityCG の include ファイル から ComputeGrabScreenPos 関数を使用します。

UV スペースでのレンダリング

特別なエフェクトやツールのためにテクスチャコーディネート (UV) スペースでレンダリングを行うとき、 Direct3D と OpenGL に似たシステムの間でのレンダリングの一貫性、また、スクリーンとテクスチャの間でレンダリングの一貫性を保つために、 シェーダーを調整する必要がある場合があります。 ビルトインのシェーダー変数 _ProjectionParams.x は、+1 か –1 の値を保持しています。それは、投影が上下さかさまになっているか、いないかを示します。これに基づいて異なることを行いたい場合は、シェーダーでこの値を確認できます。

float4 vert(float2 uv : TEXCOORD0) : SV_POSITION
{
    float4 pos;
    pos.xy = uv;
    // we're 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;
}

クリップスペースコー​​ディネートの違い

テクスチャ座標と同じように、クリップ空間座標 (post-projection 空間座標としても知られています) は、Direct3D のようなプラットフォームと OpenGL のようなプラットフォームで異なります。

  • Direct3D、Metal、コンソールで、クリップスペースのデプスの範囲は、近平面 0.0 から遠平面 +1.0 までです。
  • OpenGL と OpenGL ES で、クリップスペースのデプスの範囲は、近平面 –1.0 から遠平面 +1.0 までです。

シェーダーコード内で、UNITY_NEAR_CLIP_VALUE マクロ を使用して、プラットフォームに基づいた近平面を取得できます。

スクリプトコード内で GL.GetGPUProjectionMatrix を使用して、 Unity の座標システム (OpenGL に基づく) からプラットフォームに合わせたものに転換できます。

シェーダー計算の精度

PC GPU はすべての浮動小数点型 (floathalffixed) を同様に取り扱い、 すべての計算を完全な32ビット精度を使用して計算しています。 これは、あまり多くのモバイルGPU で行われていません。 そのため、シェーダーをテストして、精度に関する問題を避けることが大切です。 詳細は、 シェーダーのデータタイプと精度 を 参照してください。

シェーダー内の Const (定数) 宣言

HLSL において、 const の意味は、宣言された変数はそのスコープ内で読み込みだけ可能で、ただし、いずれにせよ初期化される、などという面で C# や C++ での const とほとんど同様です。

ただし、GLSL では const は変数は効果的にコンパイル時の定数であり、そのため、それはコンパイル時の定数 - リテラル値か他の定数に関する計算、のいずれかで初期化される必要があります。

GLSL セマンティクスに従い、本当にバリアントでない場合は変数を const として宣言だけ行い、それを機能内のローカル変数のような変更可能な他の値とともに初期化するのを避けるのが得策です。この方法は、HLSL でもよく機能し、いくつかのプラットフォームでのみ発生する混乱によるエラーを避けるのに有効です。

シェーダー内の Const (定数) 宣言

HLSL において、 const の意味は、宣言された変数はそのスコープ内で読み込みだけ可能で、ただし、いずれにせよ初期化される、などという面で C# や C++ での const とほとんど同様です。

ただし、GLSL では const は変数は効果的にコンパイル時の定数であり、そのため、それはコンパイル時の定数 - リテラル値か他の定数に関する計算、のいずれかで初期化される必要があります。

GLSL セマンティクスに従い、本当にバリアントでない場合は変数を const として宣言だけ行い、それを機能内のローカル変数のような変更可能な他の値とともに初期化するのを避けるのが得策です。この方法は、HLSL でもよく機能し、いくつかのプラットフォームでのみ発生する混乱によるエラーを避けるのに有効です。

シェーダーに使用されるセマンティクス

シェーダーをすべてのプラットフォーム上で動作させるために、いくつかの特殊なシェーダーの値は以下のようなセマンティクスを使用するべきです。

  • 頂点シェーダーの出力する (クリップスペース) 位置: SV_POSITION。シェーダーでは ‘POSITION’ セマンティクスを使用することもありますが、これは Sony PS4 上の場合やテッセレーションが使用されている場合は動作しません。
  • フラグメントシェーダーの出力する色: SV_Target。シェーダーでは COLORCOLOR0 を使用することもありますが、こちらも PS4 上では動作しません。

Point としてメッシュをレンダリングするとき、頂点シェーダーから PSIZE 出力セマンティクスを出力することを確認してください(例えば 1 に設定)。プラットフォーム(例えば OpenGL ES や Metal)によっては、シェーダーから、またはシェーダーへの書き込みでない場合、 undefined (未定) としてポイントサイズを処理するものもあります。

詳細は シェーダーセマンティクス を参照してください。

Direct3D 9/11 シェーダーコンパイラは文法に厳格

Direct3D プラットフォームは Microsoft のHLSL シェーダーコンパイラーを使用します。HLSL コンパイラーは他のコンパイラーに比べると、さまざまな微細なシェーダーエラーに関してより厳格です。例えば、HLSL コンパイラーは適切に初期化されていない関数の出力値を受けいれません。

この現象にもっとも頻繁に遭遇するのは以下のような場面です。

  • サーフェスシェーダー 頂点モディファイアが「出力」パラメーターを持つ場合。以下のように必ず出力を初期化するようにしてください。
      void vert (inout appdata_full v, out Input o) 
      {
        **UNITY_INITIALIZE_OUTPUT(Input,o);**
        // ...
      }
  • 部分的に初期化された値。 例えば float4 を戻す関数があるが、コードが .xyz 値しか設定しない場合。すべての値を設定できるようにするか、float3 でよいのならばそのように変更します。
  • ** tex2D を頂点シェーダーで使用するとき。** 頂点シェーダーでは UV デリバティブが存在しないため無効です。その代わりに明示的にミップレベルのサンプリングを追加する必要があり、例えば tex2Dlod (tex、float4(uv, 0, 0)) を使用します。tex2Dlod はシェーダーモデル 3.0 機能なので #pragma target 3.0 を追加する必要もあります。

DirectX 11 HLSL 文法およびサーフェイスシェーダー

現在 サーフェスシェーダー の一部のコンパイルパイプラインでは DirectX 11 特有の HLSL 文法が解釈されません。もし StructuredBuffersや RWTextures、その他の非 DirectX 9 文法の HLSL 機能を使用する場合、DirectX 11 専用のプリプロセッサーマクロにラッピングする必要があります。

#ifdef SHADER_API_D3D11
// DX11-specific code, e.g.
StructuredBuffer<float4> myColors;
RWTexture2D<float4> myRandomWriteTexture;
#endif

シェーダーのフレームバッファフェッチ

一部の GPU(iOS 上で最も顕著な PowerVR ベースのもの) では、フラグメントシェーダーへのインプットとして現在のフラグメントカラーを渡して、プログラマブルブレンディングの形にすることができます。(EXT_shader_framebuffer_fetch を参照してください)。

framebuffer fetch 機能を使うUnityでシェーダーをプログラムすることが可能です。 HLSL/Cg でフラグメントシェーダーをプログラミングするとき、color の引数の inout を使用するだけです。以下の例を参照してください。

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

反転 float Z バッファ

デプスバッファの方向は現在一般的に使用されている多くのグラフィックス APIs (DX11/12, PS4, XboxOne, Metal) で反転されます。これらの APIs では、近距離では Z バッファの値は 1.0、遠距離では 0.0 です。これにより、浮動小数点のデプスバッファと組み合わせて、デプスバッファの精度を大幅に上げる効果があります (特に小さな近距離面と大きな遠距離面を使用している場合は、Z fighting が少なくなりシャドウの品質も良くなります)。

実際的には、シェーダーレベルにほとんど変化がないことを意味します。

  • UNITY_REVERSED_Z が定義されます
  • クリップスペースの範囲は [near, 0] です。
  • _CameraDepthTexture テクスチャの範囲は [1,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

クリップスペースデプスに関しては、以下のマクロを使用できます。

    float clipSpaceRange01 = UNITY_Z_0_FAR_FROM_CLIPSPACE(rawClipSpace);

注意 このマクロは、OpenGL/ES プラットフォームではクリップスペースを変えません。つまり、OpenGL/ES プラットフォームでは [-near, far] のままです。

ネイティブコードレンダリングプラグインを使う場合は、マッチングするプラットフォームでもデプスバイアスを無効にする必要があります。

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