ShaderTips

シェーダーTips

主にUnityシェーダーについての記事を書いています。

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

今回やること

Unityでシェーダーを書いていると、プラットフォームごとで結果が異なることがあります。 これはグラフィックスAPIに違いがあることが原因であることがある為、理解しておく必要があります。

公式リファレンスがありますが、分かり辛かったのでまとめました。 docs.unity3d.com

グラフィックスAPIとは?

描画する作業はWindowsmacOSLinuxといったOSが管理しています。 私達はグラフィックのプログラムを書くときはOSに「こんな画面描いて」と指示を出す必要があります。 ところが、各OSはグラフィック周りの処理を独自に実装しているので、OSへの指示の出し方も異なってきます。 OSごとに描画プログラムを書き直すのは非常に手間がかかる作業です。 そこで共通して利用できる描画指示の規格が作られました。 これらがグラフィックスAPIです。

グラフィックスAPIの種類

皆さんお馴染みのDirectXOpenGL以外にもMetal、OpenGL ESなどがあります。 OpenGL ESはモバイル端末に組み込まれるAPIIosでもAndroidでも使用できます。

MetalはAppleバイス用の標準的なグラフィックス API です。 OpenGL ES に比べると Apple プラットフォーム上でより多くの機能を提供します。

Metal - Unity マニュアル

APIごとの違い一覧

APIの種類 API 座標系 UV 正規デバイス座標のZ値
OpneGL類 OpenGL,OpenGL ES 左手系(Z軸前方マイナス) 上が1で下が0 -1 〜1
Direct3D DirectX,Metal,Unityコンソール 右手系(Z軸前方プラス) 上が0で下1 0〜1

座標系

OpenGL類は左手系なので奥に行くほどZ値がマイナスになります。 Direct3D類はその逆で奥に行くほどZ値がプラスになります。

f:id:Ny_Program:20210515153559j:plain

極性ベクトルと軸性ベクトル - 科学のおもちゃ箱@Hatena引用

これは射影行列によって制御されています。 下の画像はDIrectXのヘルパー関数でD3DXMatrixPerspectiveFovLHは左手座標系、D3DXMatrixPerspectiveFovRHは右手系の射影行列を作るヘルパー関数です。 行列の3行目の正負が反転しています。 正負の反転により、掛け算の最終結果のZ値の値を反転させています。

f:id:Ny_Program:20210515155517p:plain

https://blog.natade.net/2017/06/04/directx-opengl-perspective/引用

UV

f:id:Ny_Program:20210515154351p:plain

https://www.charlezz.com/?p=1007引用

OpenGL類はUV値のV座標が上が1で下に行くほど0に近づきます。 Direct3D類は上が0で下に行くほど1に近づいていくので、逆さまになっています。

基本的にはDirect3D類ではテクスチャにレンダリングするとき、Unity が内部的にレンダリングを反転させて、OpenGL類に合わせているので、問題ないのですが、スクリーンポストエフェクトで複数のRenderTextureが同時に処理される場合、それらは異なる垂直方向に表示される可能性があります。(Direct3Dなどのプラットフォームで、アンチエイリアシングオプションを使用する場合のみ)。 その為、頂点シェーダーで画面テクスチャを手動で「反転」する必要があります。

MainTex_TexelSize、この変数は文字通りメインテクスチャ_MainTexのピクセルサイズを意味します。その値はVector4(1 / width、1 / height、width、height)です。

Direct3Dプラットフォームでは、アンチエイリアスをオンにすると、xxx_TexelSize.yが負の値になります。 xxx_TexelSize.yは縦のサイズの逆数ですがこれが負の値になるというのは数学的に根拠があるわけではありません。 Unityの方がそういうモノ明言しています。

f:id:Ny_Program:20210517222629p:plain _MainTex_TexelSize what's the meaning? - Unity Forum引用

したがって、if(_MainTex_TexelSize.y <0)の関数は、現在アンチエイリアシングが有効になっているかどうかを判断しています。

// 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

この例ではUV値をそのまま頂点の座標として出力していますが、DirectXはUVが上が0で下が1なので、出力結果が逆さまになってしまいます。 DIrectX系か見極めるためには射影行列が反転しているかで判定します。 これはDirextX系は右手系なので射影行列が反転しているからです。

float4 vert(float2 uv : TEXCOORD0) : SV_POSITION
{
    float4 pos;
    //uvを座標として扱う。
    pos.xy = uv;
    // この例は、逆さまの反転投影でレンダリングしています。
    //  垂直UV座標も反転します   
    //  基本は1だが射影行列が反転しているなら-1  
    if (_ProjectionParams.x < 0)
        pos.y = 1 - pos.y;
    pos.z = 0;
    pos.w = 1;
    return pos;
}