📽

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

2022/01/18に公開

はじめに

昨年 12 月より次期バージョンである Unity 2022.1 のベータテストが開始されました。
その中で今回久々に HDR 出力関連で規模の大きいアップデートがされていたので試してみました。

  • Unity 2022.1b2 ~ b3
  • Windows 10 21H2
  • Google Pixel 6

過去記事:

HDRP の改善

HDRP: Added: Added support for HDR output devices.

Bit Depth 10 使用時のディスプレイ色空間変換

映像をディスプレイに出力する際、ディスプレイの色空間に変換してやる必要があります。

従来の Unity の HDR ディスプレイ出力で色空間変換をせずにそのまま出力していたので Bit Depth 10 を選択していた場合は自前で色空間変換をする必要がありました。

  • Bit Depth 16 は scRGB (BT.709 / Linear Gamma) なのでそのまま出力が正しい
  • Bit Depth 10 は BT.2100 PQ (BT.2020 / PQ) なので色空間変換が必要

2022.1 ではこの点が改善され、 HDRP においては Bit Depth 10 に設定しても正しい色で出力されるようになり、自前で色空間変換をする必要はなくなりました。

Windows においては Bit Depth 16 にしておけばよいので Windows だけであればそこまで重要ではないと思いますが、 Windows 以外のプラットフォームでディスプレイ色空間を BT.2100 PQ にしなければならないケースがあると思うので重要な改善と思います。

BuiltIn RP 、 URP は変わらずなので自前での対策が必要です。

Tonemapping の HDR サポート

HDRP に限らず従来 Post Processing Stack の Tonemapping は SDR のみとなっており、 HDR 出力を使用する際はとりあえず Tonemapping は OFF にする、がお約束でした。
2022.1 では HDRP の Tonemapping が HDR 対応をしており、 HDR 出力時でも Tonemapping は有効にできるようになりました。また、 HDR 用のパラメーターとして Tonemapping のプリセット指定と Paper White の輝度が指定できるようになっています。

これに伴ってか、従来 HDRP 使用時は Tonemapping の無効化以外にも 様々な小細工設定 が必要でしたがデフォルトの状態で HDR 出力を ON にしてもおかしな映像にならないようになっています。

Android の HDR 出力サポート

HDRP: Added: Added support for HDR output devices.

Android の HDR 出力は 2022.1b2 では Windows と同じで Project Settings - Player の "Use display in HDR mode" にチェックを入れると有効になります。設定値は Standard Player と共通です (連動する) 。これは Color Space などと同じですね。

Android には "Swap Chain Bit Depth" はなく、 HDR 時は Bit Depth 10 相当になります。よって Windows とクロス開発する場合は Windows も Bit Depth 10 にすべきでしょう。

HDR 出力の状態取得、制御は Windows と同様に SystemInfo.hdrDisplaySupportFlagsHDROutputSettings クラス で行うことができます。 HDROutputSettings.RequestHDRModeChange はきいたりきかなかったりする (?) ような感じでよくわかりません。

https://twitter.com/TANY_FMPMD/status/1480665912814768131?s=20

Android は現在のところ HDRP 非対応ですので BuiltIn RP か URP のどちらかを選択することになります。また、 BitDepth 10 にしなくてはならないため出力前の色空間変換を自前でする必要があります (後述) 。

とりあえずそれっぽいシーンを組んで Pixel 6 で実行してみました。が、輝度のコントロールがちゃんとしていない (?) のか全体的に暗い映像になってしまい、 HDR Off にした方がよっぽど明るい映像になってしまっていました。 Unity の API で HDR metadata の設定ができないからなのかもですが、 HDROutputSettings から取得できる輝度範囲で表示するようにすればいいようにも思うのでちょっと納得のいかない感じでした。

a) HDR 出力 ON で撮影したもの
b) HDR 出力 ON でタスク切り替え画面にしたもの
c) HDR 出力 OFF (SDR) にして撮影したもの

スマホカメラの JPEG 画像ではよくわからなくなってしまっていると思いますが雰囲気だけでも。

a) と b) を比べると b) の方が明るくなっているのが分かりますが実機では a) がそのまま明るく (明るい部分がより明るく) なったような感じで白飛びもしていない「らしい」映像になっています。

b) と c) はこの写真では同じような感じですが、 c) は実機でもこんな感じの白飛び映像です。

その他

2022.1 とは直接的に関係のない HDR 出力に関する事です。

Bit Depth 10 における色空間変換

Bit Depth 10 (HDR10) にする場合、 Unity の Gamma/Linear 空間と互換性がないため色空間変換が必要になります。

先に書きましたが、 HDRP の場合は自動で適切な変換処理が行われますが、 Android の場合は HDRP が使えないため BuiltIn RP か URP を選択しなければならず、これらを使用する場合は必然的に色空間変換処理を実装する必要があります。

ただ BulitIn RP で Bit Depth 10 を選択してもぱっと見た目そんなにおかしな色になりません。輝度が高くなっているようですが、色域的には正しいように見えます。

これは HDROutputSettings.automaticHDRTonemapping が原因でした。これが true の場合は色域の変換が行われるようです。が、中途半端にやるなら正直余計なお世話であるという感じしかしないので false にして全て自前で行った方がよいでしょう。

また、このプロパティは Editor 上で設定できないようですが、コードで変更すると実行を停止しても変更状態が維持されます。それが気になる場合は OnDestroy で戻す、といった小細工をするとよいかもしれません。

private void Awake()
{
    HDROutputSettings.main.automaticHDRTonemapping = false;
}

private void OnDestroy()
{
    HDROutputSettings.main.automaticHDRTonemapping = true;
}

ちなみに URP は (正しく) おかしい色のまま、 HDRP は HDRP 内で対応してくれるので Scriptable RP 系には影響を与えないプロパティなのかもしれません。

実際の色空間変換は shader を用いて PostProcess 上で適用します。 Microsoft の D3D12HDR サンプル を参考に (MIT)

float3 Rec709ToRec2020(float3 color)
{
    static const float3x3 conversion =
    {
        0.627402, 0.329292, 0.043306,
        0.069095, 0.919544, 0.011360,
        0.016394, 0.088028, 0.895578
    };
    return mul(conversion, color);
}

float3 LinearToST2084(float3 color)
{
    float m1 = 2610.0 / 4096.0 / 4;
    float m2 = 2523.0 / 4096.0 * 128;
    float c1 = 3424.0 / 4096.0;
    float c2 = 2413.0 / 4096.0 * 32;
    float c3 = 2392.0 / 4096.0 * 32;
    float3 cp = pow(abs(color), m1);
    return pow((c1 + c2 * cp) / (1 + c3 * cp), m2);
}

float4 frag (v2f i) : SV_Target
{
    const float standardNits = 80.0;
    const float st2084max = 10000.0;
    const float hdrScalar = standardNits / st2084max;

    float4 color = tex2D(_MainTex, i.uv);
    return float4(LinearToST2084(Rec709ToRec2020(color.rgb) * hdrScalar), 1.0);
}

上記を OnRenderImage などで適用してください。

Unity Editor で HDR 出力を有効にする

Windows の Unity Editor で HDR 出力を有効にするには DX12 モードにする必要があります。
これは -force-d3d12 オプションを付けて起動する他に、 Graphics API を手動で Direct3D 12 を最優先設定にする事でも可能です。

-force-d3d12 オプション付けて起動するの何気に面倒なので Project Settings を変えておいた方がよいように思います。 DX11 にこだわる理由はほとんどないと思いますし。

おわりに

HDRP に対する拡張、改善はかなりインパクトが大きく、 HDR 出力が既存機能でまともに使えるようになってきたという感じがします。従来から考えればデフォルトの状態で HDR 出力がとりあえず機能するというのは大進歩という印象です。

一方で Android の HDR 対応ですが、有効化できるようになったというのはものすごく大きいのですが、現状では環境が伴っていないように思うので使うのはまだ厳しそうです。特になぜか輝度が上がらないのが厳しい (端末の問題かもしれませんが) 。せっかく強化された HDRP も使えないですし。 URP にも HDRP と同様の拡張が欲しいですね。

Discussion