Unity 2020.1 の HDR ディスプレイ出力機能を試す

はじめに

この記事は以前書いた記事の続きです。

Unity 2020.1 では HDR ディスプレイ出力機能が強化されています。

https://blogs.unity3d.com/jp/2020/03/17/unity-2020-1-beta-is-now-available-for-feedback/

上記記事によると

  • PS4 / Xbox OneHDR 出力に対応 (Desktop PC を含めて Single API で対応)
  • Unity Editor 上の HDR プレビューに対応
  • HDR サポートの C# API

といった機能が追加されたとのこと。 PS4 / XONE は試しようもないですが、それ以外について一つずつ見ていこうと思います。

この記事では Unity 2020.1b5 で確認しています。

HDR 出力 / Unity Editor 上の HDR プレビュー対応

We have added HDR display support for the Editor, allowing developers who use displays that support HDR to take advantage of it. This change supports DX12 and Metal.

との記載があり Unity Editor 上で HDR 出力が行えるようになっています。

Windows では DX12 モードで起動する必要があります。 現時点ではデフォルトでは DX11 なので -force-d3d12 オプションをつけて Unity Editor を起動する必要があります。ビルドしたアプリケーションについては DX11 でも HDR は機能しますので Graphics API を Direct3D11 にしてビルドしても OK です (DX12 は Editor 上での条件) 。

2019.3 より Project Settings に下記設定をすることで HDR ディスプレイ出力が有効にできます。

  • "Player - Rendering - Color Space" を "Linear" に設定する
  • "Player - Rendering - Use display in HDR mode" のチェックを ON にする

上記で HDR ディスプレイ出力が有効になりますが、条件を満たしている環境では Unity Editor 上でもそのまま HDR ディスプレイ出力が行われます。

f:id:tan-y:20200412105511p:plain

HDR mode ではさらに "Swap Chain Bit Depth" という指定があり、これが "Bit Depth 10" か "Bit Depth 16" で挙動が違うことが確認できています。この点については前回記事でも触れていて、 "Bit Depth 10" では色がおかしいと書きました。

前回記事では検証が不十分だったのですが Unity としては次のような動作になっているようです。

Mac で DisplayP3 の設定をした時の挙動は環境がないので未確認

Unity のドキュメントによると

  • Bit Depth 10 → Unity will use the R10G10B10A2 buffer format and Rec2020 primaries with ST2084 PQ encoding.
  • Bit Depth 16 → Unity will use the R16G16B16A16 buffer format and Rec709 primaries with linear color (no encoding).

と書かれています。これは Windows の DXGI の仕様に準じています (DXGI はこの 2 つのフォーマットで HDR 出力ができる) 。

これに先に記述した "HDR ディスプレイに出力 → 最終レンダリング結果をそのまま出力" を合わせて考えると、 Unity のエンジン側でなく、アプリの方で指定の Bit Depth に対応した色空間に変換する必要がある ということになります。

従来、 Unity で言う色空間 (Color Space) は ドキュメントの説明 によると

  • Color Space は Gamma と Linear の 2 種類である
  • 標準的な Gamma は sRGB である

ということになっているので、まあ次のような事だろうという事は想定できます。というか Unity に限らず暗黙的にそのような扱いだったはずと思います。

色域 伝達特性
Gamma sRGB BT.709 sRGB
Linear scRGB BT.709 Linear

色空間については下記記事もご覧ください。

ちなみに scRGB は有効な値の範囲が仕様として定義されている範囲がせまい (-0.5~7.5) ですが処理中においては特に気にする必要はないと思います。

HDR 出力有効時の注意点として (少なくとも 2020.1 時点では) Unity 標準の Post Process (PPS, URP, HDRP) の Tone mapping を適用すると 0.0~1.0 の範囲に丸め込まれるので HDR じゃなくなります。従って Tone mapping は標準 Post Process 以外の方法で行う必要があります。

Bit Depth 10 の場合

Bit Depth 10 は "Rec.2020 (BT.2020) かつ "ST 2084(PQ)" となっています。これは Unity の Color Space と互換性がありません。実は私は 2019.3 で試していた時は Unity 側で色空間変換をしてくれるものと思っていたのですがそうではありませんでした。

色空間変換をしてくれない、ということは自前でなんとかする必要があります。

Bit Depth 16 の場合

Bit Depth 16 は "Rec.709 (BT.709)" かつ "Linear" となっています。つまり Color Space = Linear に設定した際の中間レンダリングフォーマットそのもの ということになります。従ってこの設定にした場合は特に意識しないで正しい色になります。

HDR サポート の C# API

HDR の対応状況は SystemInfo.hdrDisplaySupportFlags より取得できます。

HDR 出力に関する詳細な設定は HDROutputSettings クラス で行えます。設定できるものは下記の 2 つです (SystemInfo.hdrDisplaySupportFlags の状況で設定の可否があると思います) 。

  • active → HDR が有効かどうか
  • automaticHDRTonemapping → 自動 HDR トーンマッピングを有効にするかどうか

active についてはプロパティ自体は get のみですが RequestHDRModeChange メソッド により切り替えができます。

上記以外のプロパティは available プロパティ が true じゃないと例外が出るので、まずこのプロパティを確認してから操作をするようにすべきでしょう。 avalilable プロパティは Player Settings の "Use display in HDR mode" が ON になっていると (かつ HDR が有効にできるプラットフォーム) true になります。 OFF にしていると対応環境でも available = false なので「Editor 上では SDR で作業したい」と OFF にしてそのままビルドするとはまりそうなので注意が必要です。

BitDepth は実行時に変更することはできません (まあ実行時に切り替えるようなものではないですが) 。 BitDepth に基づいた設定は displayColorGamut, format, graphicsFormat から判断できます。これらの情報を元に必要に応じて色空間変換処理を適用すればよいかと思います。

その他、ディスプレイの性能 (最大、平均輝度など) を取得するプロパティも存在します。が、経験上この値の信頼性はあまりない (ディスプレイが正しい値を返す保証がない) と思いますので参考程度に留め、性能に応じてトーンマッピングのパラメーターを調整するのであればキャリブレーション機能があった方が望ましいと思います。

automaticHDRToneMapping は具体的にどういった挙動をするのか試した感じではよくわかりませんでした・・・ (らしい絵を作ってみないとだめかも)

トーンマッピングと色空間変換

automaticHDRToneMapping のドキュメント によるとトーンマッピングを自前でやる場合には PostEffect や OnRenderImage で、と書いてあります (これがカスタムトーンマッピングの事?) 。 URP や HDRP で OnRenderImage は使えないので別の手段で対応する必要があります。ただ私があれこれ試してみたところ URP 8.0.1 では URP 組み込み PostEffect の後に独自の PostEffect を差し込むことはできません でした。前だったら差し込めましたが、トーンマッピングと色空間変換は一番最後に適用すべきなのであまり意味がないですね。私の調べ方が不十分なだけかもしれませんが。

BT.709 / Linear → BT.2020 / PQ の変換は Microsoft の D3D12 HDR sample にそのものずばりの PixelShader のコードがあるので、それを利用するのが手っ取り早いでしょう。

表示例

Bit Depth 設定と Post Process による色空間変換の適用によってレンダリング結果にどのような違いが生じるかを見ていきます。

画像は適当な Sphere に強めの Light をあてただけのものです。 Tone mapping はしていません。

画像 説明
1
f:id:tan-y:20200411141437p:plain
適切に sRGB 変換されたもの
2
f:id:tan-y:20200411141649p:plain
Bit Depth 10 にレンダリング結果を無変換でそのまま表示したもの
3
f:id:tan-y:20200411141737p:plain
レンダリング結果を BT.2020 / PQ に変換したものをそのまま SDR で表示したもの

1 はあからさまにおかしく見えますが、これは強い Light をあてて白飛びしているためです。

  • SDR (sRGB)
  • Bit Depth 16
  • Bit Depth 10 かつ Post Process で BT.709 / Linear → BT.2020 / PQ 変換したもの

を SDR キャプチャしたもので全て同じ表示になります。

f:id:tan-y:20200411142120p:plain
Bit Depth 16 出力映像の波形モニター

これは Bit Depth 16 でレンダリングしたものを Windows 10 の機能で JPEG XR に HDR キャプチャしたものを波形モニターで見たものです。 1.0 オーバーの部分は SDR 下では全て飛びます。 HDR 環境では綺麗な描画で見ることができます。

2 は単純に設定を Bit Depth 10 にしただけのものです。これだとわかりにくいですが色空間が合っていないので 1 と色合いが変わってしまっています。

3 は PQ のソースをそのまま sRGB に表示した場合の見栄えです。傾向として色合いが大分浅く見えます。これも色空間が合っていないので色は正しくありません。が 0~1 の範囲に値が収まるので SDR 環境で HDR のグラデーションのかかり具合が視認できます。

Unity Editor ではウィンドウ内の描画が丸ごと指定の色空間になります。

f:id:tan-y:20200411143847p:plain
Bit Depth 10 の UI
f:id:tan-y:20200411143922p:plain
Bit Depth 16 の UI

UI は BT.709 / Linear でレンダリングしていると思われます。そのため Bit Depth 16 の UI は通常の SDR と同じに見えますが、 Bit Depth 10 は白っぽくなってしまっています。これは UI 部分の色空間変換をせず、そのまま出力してしまっているためでしょう。せめて UI の色くらいはなんとかして欲しいですね・・・

Unity でのレンダリング色空間を考える

Unity の Player Settings で設定する "Color Space" は実際のところ "伝達特性" の定義であって色域については規定がされてなく、最終出力の sRGB から BT.709 を色域とする、といった形になっているのではないかと思います。実際のところは最終的な出力時に色空間変換を自前ですれば (Bit Depth 10 出力有効時はそもそも必要) レンダリング時の色空間はなんでもよいわけです。

まず前提として scRGB (BT.709 / Linear) は負の値を許容することで広色域になっていますが、そもそも負の値を扱えるバッファが必要になります。 Frame Debugger で観察してみるとレンダリングバッファは次のフォーマットのようです。 (ColorSpace = Linear)

GraphicsFormat
Legacy R16G16B16A16_SFloat
URP B10G11R11_UFloatPack32
HDRP B10G11R11_UFloatPack32 or R16G16B16A16_SFloat

B10G11R11_UFloatPack32 は 1 ピクセルあたり 32bit ですが符号ビットがないので負の値を持てません。一方 R16G16B16A16_SFloat は負の値が持てるものの 1 ピクセルあたり 64bit となりメモリ負荷が高いのと URP では選択できないようです。

BT.709 より広い色域のものにすれば 0~1 の範囲で扱うことが可能になります。一方で BT.709 と全く互換性がない事からワークフローも見直す必要が出てくると思います。もしも違う色域にするなら BT.2020 が現在の HDR コンテンツが採用している標準的な色域で表現範囲も一番広いのでよいのではないかと思います。

以上の事から次のように考えられます。

  • BT.709 は sRGB と色域が同じなので従来ワークフローと互換性が高いが、広色域にする場合は負の値が表現できる必要がある
    • 負の値が表現できる R16G16B16A16_SFloat はメモリ負荷が高めなのでそれを許容する必要がある
    • 色域については BT.709 の範囲で留め、 HDR 表現のみ採用するという判断も考えられる
  • BT.2020 などより広色域のものにすれば負の値を扱わずに広色域にすることが可能になるが、ワークフローの見直しが必要になる
    • RGB 値が BT.709 と異なるものになる (BT.709 下で色選択しても正しくなくなる)
    • テクスチャのインポート時にもあらかじめ色域を変換しておく必要がある
    • Editor 上でのプレビューが難しくなるかもしれない

BT.2020 は Unity 自体にサポートが入ってほしいかなあと思います。

おわりに

HDR 出力制御の C# API が追加されたことにより、 Unity でも現実的に HDR 出力に対応したアプリの開発が可能になってきたと思います。一方でレンダリング色空間や Unity Editor の UI の色空間の扱いなどアプリ開発者側的に不便なところもまだあると思います。 今後の拡張にも期待していきたいところです。