様々なグラフィックス API の間で、グラフィックスレンダリングの動作が異なる場合があります。大抵、Unity エディターはその違いをカバーしてくれますが、エディターでは対応できない状況もあります。これらの状況と、その場合に必要な対処法を説明します。
垂直のテクスチャ座標の規則は 2 つのプラットフォーム、Direct3D 類似のものと OpenGL 類似のものとでは異なります。
この違いは、レンダーテクスチャにレンダリングするとき以外は、プロジェクトにまったく影響しない傾向があります。Direct3D のようなプラットフォーム上でテクスチャにレンダリングするとき、Unity は内部的にレンダリングを逆さまに反転させます。これにより、異なる類のプラットフォーム (OpenGL 類) とのきまりを一致させることができます。
UV 空間でのイメージエフェクトとレンダリングでは、自分で処理を行って異なる座標のきまりによる問題が発生しないように気を配る必要があります。
イメージエフェクトが一度に 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 関数を使用します。
ビルトイン変数 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 類似: クリップスペースの深度の範囲は、ニアクリップ面の +1.0 からファークリップ面の 0.0 までになります。これは Direct3D、Metal、コンソールに適用されます。
OpenGL 類似: クリップスペースの深度の範囲は、ニアクリップ面の –1.0 からファークリップ面の +1.0 までになります。これは OpenGL と OpenGL ES に適用されます。
シェーダーコードの内部では、UNITY_NEAR_CLIP_VALUE ビルトインマクロを使用して、プラットフォームに基づいてニアクリップ面の値を取得できます。
プラットフォームに適切な場合は、スクリプトコードの中で、GL.GetGPUProjectionMatrix を使用して、Unity の座標系 (OpenGL のような規則に従っています) から Direct3D 類の座標に変換します。
詳細は、シェーダーでの 16 ビット精度の使用を参照してください。
Unity でシェーダーを記述する場合、Unity は half を float または min16float として定義していることに注意してください。シェーダー精度モデル__が__プラットフォームのデフォルト__に設定されている場合、macOS では half は float になり、iOS/tvOS では min16float になります。シェーダー精度モデル__が Unified に設定されている場合、macOS/tvOS/iOS の half は min16float になります。
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 の使用は、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 演算バッファまたはグラフィックスバッファからのデータを使用して値を設定する場合、グラフィックス API によってはデータレイアウトが一致しない場合があります。これは、データを上書きしたり、変数を間違った値に設定したりする可能性があることを意味します。
例えば、cbuffer または Unity の定数バッファマクロを使用する場合、定数バッファのデータレイアウトとグラフィックス API によって、float3 が float4 になったり、float が float2 になったりします。
すべてのグラフィックス API が同じデータレイアウトでバッファをコンパイルするようにするには、以下の方法を使用します。
float3 と float3x3 の代わりに float4 と float4x4 を使用します。なぜなら、float4 変数はすべてのグラフィックス API で同じサイズですが、一部のグラフィックス API では float3 変数のサイズが異なる可能性があるためです。float4、float2、float のように、サイズが大きいものから順に変数を宣言します。例:
cbuffer myConstantBuffer {
float4x4 matWorld;
float4 vObjectPosition; // Uses a float4 instead of a float3
float arrayIndex;
}
頂点シェーダーの出力する (クリップスペース) 位置: SV_POSITION。ときおり、シェーダーは POSITION セマンティクスを使用し、シェーダーをすべてのプラットフォームで使えるようにします。これは、Sony PS4 やテッセレーションを使う場合にはうまく挙動しません。
フラグメントシェーダーの出力色: SV_Target。ときおり、シェーダーは COLOR や COLOR0 を使用し、シェーダーをすべてのプラットフォームで使えるようにします。これは、Sony PS4 ではうまく挙動しません。
メッシュを点としてレンダリングするときは、頂点シェーダーから PSIZE セマンティクスを出力します (例えば、1 に設定します)。OpenGL ES や Metal などの一部のプラットフォームでは、シェーダーから書き込まれないときにポイントサイズを “未定義” として扱います。
詳細については、シェーダーセマンティクスに関するドキュメントを参照してください。
これを使用して実行する可能性のある最も一般的な状況は以下のとおりです。
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 のシェーダー言語) 構文を理解しません。
StructuredBuffers、RWTextures、その他の DirectX 9 以外の構文などの HLSL 機能を使用する場合は、以下の例のように DirectX X11 だけのプリプロセッサーマクロでそれらをラップします。
#ifdef SHADER_API_D3D11
// DirectX11-specific code, for example
StructuredBuffer<float4> myColors;
RWTexture2D<float4> myRandomWriteTexture;
#endif
EXT_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) の向きはシェーダープラットフォームによって異なります。
DirectX 11, DirectX 12, 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)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 または OpenGL ES プラットフォームのクリップスペースを変更しないため、これらのプラットフォームでは “-near”1 (ニア) から far (ファー) の範囲内で返します。
以下はその例です。
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;