はじめに
こんばんは、代表の堂前です!
少し間が空きましたが、Unityの5.4&5.5系での新機能の深掘りを行っていきます。
今回はリリースノートに書かれている以下の内容です。
DX9: DX9 half-pixel offset adjustments are now done at shader compilation time. This means that shaders no longer need to do any UNITY_HALF_TEXEL_OFFSET checks, and any possible C# code for coordinate adjustment no longer needs to happen on DX9. However, vertex shader constant register #255 (c255) is reserved by Unity now, and all vertex shaders get two instructions automatically added to do the adjustment.
長い・・・。
これは端的に言うと、シェーダの定義である「UNITY_HALF_TEXEL_OFFSET」についての対応ということになります。
※検証したのはMacのUnity5.4.1f1と5.5.0b8になります。
DirectX9固有のhalf-pixel
今回の説明のため、座標変換の話を少しだけします。
Unityに限らず画面いっぱいに同解像度のテクスチャを描画することを考えます。
座標変換で最終的にはプロジェクション座標系、つまり2Dの座標系(に近いところ)に変換されます。
それは画面の解像度、つまりフルHDなら解像度が1920×1080なので座標系も(0,0)〜(1920,1080)と考えがちですが、(-1,-1)〜(1,1)となります。
左下が(-1,-1)、右上が(1,1)なので、コピーという事なら貼り付けるためのUVも(0,0)〜(1,1)であると推測されます。
シェーダコード的にも以下で行けるだろうと思われます。
v2f vert (appdata_t v)
{
v2f o;
o.vertex = v.vertex;
o.texcoord = TRANSFORM_TEX(v.texcoord.xy, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return tex2D(_MainTex, i.texcoord);
}
しかし、これで処理するとDirectX9では正確にコピーが行えません!
(DirectX11やOpenGLなどでは正確に出ます。)
正しく処理されている状態 | DirectX9での実行例 |
これはDirectX9固有の仕様で、ピクセルの座標系が0.5ピクセルずれている事に起因しています!
(こちらに詳しく書かれています。)
Unityではそれを対応するため、「HLSLSupport.cginc」に「UNITY_HALF_TEXEL_OFFSET」というのがDirectX9の時だけ有効になるように定義されています。
それを使って以下の様に処理することで正しくコピー描画が行えます。
#include "HLSLSupport.cginc"
v2f vert (appdata_t v)
{
v2f o;
o.vertex = v.vertex;
#ifdef UNITY_HALF_TEXEL_OFFSET
o.vertex.xy += (_ScreenParams.zw-1.0) * float2(-1,1) * o.vertex.w;
#endif
o.texcoord = TRANSFORM_TEX(v.texcoord.xy, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return tex2D(_MainTex, i.texcoord);
}
これでDirectX9でもそれ以外でも正しくコピー描画が行えます。
5.5での対応
しかしこの仕様を理解し、「UNITY_HALF_TEXEL_OFFSET」を適切な箇所に入れなければいけないというのは、少々面倒です。
特にスマホでは非DirectX9で、でも開発環境がWindowsのためDirectX9相当である時は間違いの元と言えます。
※ちなみに余談ですが、Graphics.Blitで行う時はこの「UNITY_HALF_TEXEL_OFFSET」は気にしなくて良さそうな仕様になっているみたいです。(マトリクスとかで吸収?)
それが更にややこしくなっている一因ではありますが・・・。
(本検証はGraphics.DrawMeshNowで行ってます。)
それを楽にするのが本件だと思うのですが、少し確認してみました。
5.5系で「UNITY_HALF_TEXEL_OFFSET」を付けずにDirectX9で処理させてみました。
きちんとコピー描画されました!!
これで5.5からは「UNITY_HALF_TEXEL_OFFSET」を気にせずコピー描画ができるということが分かりました。
ただ、本投稿はこれで終わりません。
更に確認をしてみます。
そもそも5.5で「UNITY_HALF_TEXEL_OFFSET」という定義はどうなっているのか?という話です。
5.5はまだベータ段階なのでビルトインシェーダもダウンロードできないので、インストールした中身を覗いてみました。
「Contents/CGIncludes」がシェーダのcginc群になっていると思われますが、検索を掛けても「UNITY_HALF_TEXEL_OFFSET」が出てきませんでした。
詰まるところ、5.5では「UNITY_HALF_TEXEL_OFFSET」は廃止という扱いで良いと思います。
では次に、どう処理をしているのか?
リリースノートを見る限り、頂点シェーダレジスタc255を利用しているとあります。それをコンパイル時にプラットフォームを見て埋め込んでいると。
ということで、下の様にシェーダをUnity上でコンパイルしてコードを見てみましょう。
(DirectX9とDirectX11で比較します。)
まずDirectX9のコード。
vs_2_0
dcl_position v0
dcl_texcoord v1
mad oT0.xy, v1, c0, c0.zwzw
mov r0, v0
mad oPos.xy, r0.w, c255, r0
mov oPos.zw, r0
次にDirectX11のコード。
vs_4_0
dcl_constantbuffer cb0[3], immediateIndexed
dcl_input v0.xyzw
dcl_input v1.xy
dcl_output_siv o0.xyzw, position
dcl_output o1.xy
0: mov o0.xyzw, v0.xyzw
1: mad o1.xy, v1.xyxx, cb0[2].xyxx, cb0[2].zwzz
2: ret
アセンブリコードなので雰囲気で読めればOKなのですが、DirectX9ではc255が利用されている気配があり、DirectX11ではそれが無さそうです。
DirectX11以外、例えばOpenGLとかでもc255はきっと使わないと思うので、あくまでDirectX9のみに留めている気配です。
これは画面コピーに関わらず通常のモデル描画等でも処理されているようなので負荷が気になりますが・・・計算量が少ないのと、あくまでDirectX9のみという感じなので、気にすることは無いでしょう。
本件は良対応であると思います。
【免責事項】
本サイトでの情報を利用することによる損害等に対し、株式会社ロジカルビートは一切の責任を負いません。