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

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

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

Render Texture の座標

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

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

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

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

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

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

しかし、イメージエフェクトで 1 回に Render Texture を複数まとめる場合、多くのケースでそれぞれが異なる垂直方向に出力されます(ただし 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 サンプルプロジェクト でエッジ検出を行っているシーンを確認ください。ここでエッジ検出はシーンのテクスチャとカメラ カメラの深度テクスチャ の両方で使用されています。

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 で行われていません。 そのため、シェーダーをテストして、精度に関する問題を避けることが大切です。 詳細は、 シェーダーのデータタイプと精度 を 参照してください。

同様に、シェーダーコード内のすべてのサンプラーとテクスチャは “low precision” (低精度) に初期設定されています。これは、PC GPU にとっては問題ありませんが、モバイル GPU のなかには sampler2D_half (半精度テクスチャ) や sampler2D_float (フル精度テクスチャ) などを使用したほうがよい場合があります。

シェーダー内の 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 (未定) としてポイントサイズを処理するものもあります。

詳細は shader semantics を参照してください。

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

Direct3D 9 や OpenGL は NVIDIA の Cg を使用してシェーダーをコンパイルしますが、Direct3D 11 (および Xbox 360)は Microsoft の HLSL シェーダーコンパイラを使用します。HLSL コンパイラーはさまざまなところでシェーダーのわずかなエラーに対して厳格です。例をあげると、正しく初期化されていない関数の出力値があると受付をしてくれません。

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

  • Surface Shader 頂点モディファイアが「出力」パラメーターを持つ場合。 次のように必ず出力を初期化するようにしてください。
      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 文法およびサーフェイスシェーダー

現在 Surface Shader の一部のコンパイルパイプラインでは 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
カメラの深度テクスチャ
シェーダー LOD