1 はじめに
株式会社セガ ゲームコンテンツ&サービス事業本部 技術本部 技術統括室 ソフトシステムセクションの西濱 高志といいます。
現在グラフィックスプログラマとして株式会社セガが開発運営する『PSO2 ニュージェネシス(以下『NGS』)』に携わっています。
今回の記事では『NGS』におけるハイダイナミックレンジ(HDR)ディスプレイ対応について紹介いたします。
2 目次
3 注意事項
この記事において、2点注意することがあります。
3.1 各ゲームにおけるHDRディスプレイ対応についての注意事項
現在、様々なゲームにおいてHDRディスプレイ対応がされています。
しかし、対応方法については各ゲームで異なっている場合や、ユーザーが調整できるパラメータに違いがある場合があります。
この記事ではあくまで『NGS』におけるHDR対応の話になりますのでご注意ください。
3.2 掲載している画像についての注意事項
この記事では随所にSDRディスプレイへの出力結果とHDRディスプレイへの出力結果の比較画像を掲載しています。
しかし、HDRで出力した画像はHDR出力に対応をしているディスプレイやデバイスでないと正しく表示することができません。
そこで、SDRとHDRの違いがわかりやすくなるように加工したSDR画像を掲載しています。
実際の数値や実機上での見た目とは異なりますのでご注意ください。
4 ハイダイナミックレンジ(HDR)とは?
それでは、本題に入っていきたいと思います。
HDRとは一言でいってしまうと「従来の標準ダイナミックレンジ(SDR)と比べて、より広い範囲の明るさを表現できる技術、或いはビデオ映像」のことを言います。
SDRとの違いは表現できる「明るさの幅」、「色の幅」の2点になります。
この2つの幅についてHDRの方が圧倒的に広いため、今まで表現できなかった明るさや色を表現できるようになります。
実際に、同じシーンでHDRとSDRを出力した結果を掲載します。
今回、比較に用いたのはゲームの拠点となる「セントラルシティ」と、ゲームプレイ時に発生する「ナーデレフのライブイベント」になります。
違いとしては、主にハイライト部分の箇所と全体的な色合いになります。
セントラルシティにおける比較(左:SDR出力、右:HDR出力)
ライブイベントにおける比較(左:SDR出力、右:HDR出力)
セントラルシティにおいては、太陽の部分についてHDRの方が白飛びしておらず、色合いについても植物の緑色などが色濃くでているのがわかるかと思います。
ライブイベントにおいては、後ろにあるライトについてHDRでは白飛びせずライトの色が識別できるようになっているのがわかるかと思います。
4.1 明るさの違い
ディスプレイにおける明るさはnit(ニット)という単位面積あたりの明るさ(カンデラ毎平方メートル)で表されます。
出力することが可能な明るさの範囲はディスプレイのスペックによって違いますが、SDRとHDRでおおよそ下記の範囲になります。
SDR:0.1nit以下~数百nit
HDR:0.1nit以下~数千nit
あくまでイメージとなりますが表現できる白色について見た時、下記画像のような違いがあります。
4.2 色の違い
HDRとSDRでは利用されている色空間が違うため、表現できる色の幅についても違いがあります。
具体的にはSDRではRec.709が利用され、HDRではRec.2020が利用されています。
それぞれの色空間についてのグラフは下記になります。
あくまでイメージとなりますが、実際の見た目としては赤色(R)、緑色(G)、青色(B)で下記画像のような違いがあります。
このようにHDRディスプレイ対応を行うことで、明るさの幅と色の幅が広がり、今まで表現することができなかった色や、白飛びしてしまっていたライトの光等を精密に表示することができるようになります。
5 HDRディスプレイ対応について
ここまでの内容でSDRとHDRでどういった所が違うのかについて、説明いたしました。
ここからは『NGS』における対応方法について説明いたします。
5.1 SDR時におけるレンダリングパスについて
レンダリングパスとはグラフィックス処理の順序のことを言います。
『NGS』ではHDR時とSDR時でレンダリングパスは、ほぼ同じ構成になっており、SDR時の出力を基準としてパラメータでHDRへ拡張をするといった対応をしています。
このためSDR時の処理について先に説明します。
下記がSDR時におけるレンダリングパスになります。
注目する点としては、それぞれの処理における「輝度の範囲」、「色空間」、「テクスチャフォーマット」になります。
またオレンジ色はHDRの区間、青色はSDRの区間を表しています。
SDR時のレンダリングパス
今回はHDRディスプレイ対応と関連性がある、「Gbuffer描画からHDRポストプロセスにかけて行うHDRレンダリング処理」、「トーンマッピングとガンマ補正処理」、「カラーグレーディング処理」について簡単に説明いたします。1
5.1.1 HDRレンダリング処理について
HDRレンダリングとは十分な計算精度を保ったまま幅広い諧調を扱うレンダリング方法のことを言います。
SDRで出力する場合でもHDRの範囲で一旦描画を行い、この後の「トーンマッピングとガンマ補正処理」においてSDRに変換することで、幅広い諧調を持った画像を出力できるようになります。
HDRレンダリングの結果が下の画像となります。
HDRレンダリングの結果
5.1.2 トーンマッピングとガンマ補正処理について
トーンマッピングとガンマ補正処理においては、先ほどのHDRレンダリングの結果をSDRディスプレイにおいて適切な見た目となるように変換する処理を行っています。2
5.1.2.1 トーンマッピング
ゲームでは生成したHDRレンダリング画像に対して、トーンカーブを用いることで出力するディスプレイで正確な表現ができるように変換します。
SDRディスプレイに出力する場合、0~1の範囲に変換します。
トーンカーブは様々な種類があるのですが、『NGS』ではACES Filmicを使用しています。
// 使用している係数
// a = 3.0
// b = 0.03
// c = 2.43
// d = 0.59
// e = 0.14
float3 ACESFilmic(float3 x, float a, float b, float c, float d, float e){
return (x*(a*x+b))/(x*(c*x+d)+e);
}
『NGS』において設定しているACES Filimicの係数を当てはめた結果が下記の画像となります。横軸が変換前の値で、縦軸が変換後の値です。
SDR時のトーンカーブ
輝度値が低いところでは変化が大きくなり輝度値が高いところでは変化が緩やかになり、1.0に収束していることがわかるかと思います。
トーンマッピングを行った結果が下記になります。
トーンマッピング後の画像
5.1.2.2 ガンマ補正について
ガンマ補正の説明をする前に、まず電気光伝達関数(Electro-Optical Transfer Function)と光電伝達関数(Opto-Electronic Transfer Function)について説明します。
(以降、電気光伝達関数はEOTF、光電伝達関数はOETFとします。)
EOTFは映像信号をディスプレイの光に変換するための関数、OETFはシーンの光を映像信号に変換するための関数を表しています。
現実の世界でカメラを用いて説明しますと、被写体をカメラで撮影した時、カメラの内部でシーンの光を映像信号に変換するOETF処理が行われます。
次にこのカメラ内の映像信号はカメラに内蔵されているディスプレイ等に送られ、ディスプレイ側で映像信号をディスプレイの光に変換するEOTF処理が行われます。
カメラで撮られた画像はこのようにして、私たちの目に届くといった仕組みになっています。
カメラにおけるEOTFとOETF
ゲーム等のCGでは、カメラにあたる部分がないため生成した画像をそのままディスプレイに渡してしまうと、ディスプレイ側でEOTF処理が行われてしまうため、想定と違う見た目になってしまいます。
このため、あらかじめOETF処理を行うことでディスプレイ側で行われるEOTFの効果を相殺します。
ゲームにおけるEOTFとOETF
SDRディスプレイにおけるEOTFは様々有り、ガンマ1.9、ガンマ2.2、ガンマ2.4等と呼ばれます。
これをOETFで相殺する処理を(特にSDRでは一般に)ガンマ補正と呼びます。
『NGS』ではsRGBのガンマ2.2として扱い、ガンマ補正として画素に対して1/2.2を累乗を行うことでSDRディスプレイにおいて適切な見た目で表示できるようにしています。
ガンマ補正後の画像
5.1.3 カラーグレーディング処理について
次にカラーグレーディング処理について説明いたします。
カラーグレーディング処理とは出来上がった画像に対して色味を変える処理のことを言います。
広義としては色調補正、カラーコレクション等と呼ばれたりします。
ゲームではシチュエーションによっては赤味を強くしたいといった要望や、過去回想などにおいてセピア調にしたいといった要望があると思います。
これらの処理を行うのがカラーグレーディングの役目となります。
具体的にはPhotoshop等を用いて作成したルックアップテーブル(LUT)テクスチャを用いて色の調整を行います。
下記画像がカラーグレーディングの比較画像になります。
カラーグレーディングの比較画像(左:なし、右:あり)
見比べてみるとかなり色味が変わってることがわかるかと思います。
『NGS』では、地域、天候等によってLUTを複数用意しており、ライブイベントの場合4つのLUTを使用して、時間変化で切り替えています。
長くなりましたが、以上がSDR時のレンダリングパスの説明となります。
5.2 HDR時におけるレンダリングパスについて
次に、HDR時におけるレンダリングパスについて説明いたします。
基本的には先ほどのSDR時のレンダリングパスからの追加対応について説明していく形になります。
下記が初期に実装したHDR時のレンダリングパスになります。
初期に実装したHDR時のレンダリングパス
違いとしては、SDR時にトーンマッピングとガンマ補正を掛けていた箇所がHDR用のトーンマッピングになり、出力の手前でOETF処理をしているところになります。
また、オレンジはHDR、赤色は最大輝度で変換したHDR、紫色はOETF処理をしたHDRを表しています。
5.2.1 HDR用のトーンマッピングについて
HDR用のトーンマッピングの説明をする前に、まずHDRで表現できる数値について説明いたします。
SDRでは0~1の範囲に変換をしたと思うのですが、HDRでは1.0以上の値で表示が可能となります。
ディスプレイのスペックによって変わってくるのですが例えば最大輝度(ピーク輝度)が350nitの場合、単純なケースでは3.5までの値が描画可能となります。3
これは、HDRの規格上100nitが標準白レベル(1.0 = 100nit)として取り扱っているためです。
このため、1.0以上の範囲で変換できれば対応することができそうです。
まず、最も簡単な方法としてACES Filimicの係数を変更してみることにします。
ACES Filimicの処理について、「x」が無限大に近づくときの値は「a / c」になります。
このため、「a / c」が最大輝度となるように係数を導出すれば表現できそうですね。
「a = 3.0」とすると、「c = 3.0 / 最大輝度」となるため、これを式に当てはめてみます。
// a = 3.0
// b = 0.03
// c = a / L
// L = 最大輝度
// d = 1.0
// e = 0.14
float3 ACESFilmic(float3 x, float a, float b, float c, float d, float e){
return (x*(a*x+b))/(x*(c*x+d)+e);
}
係数を調整し、1.5に収束するようにした結果が下記になります。
HDR時のトーンカーブ(青:HDR、緑:SDR)
また、この「最大輝度」はパラメータ「HDR設定:最大輝度レベル」としてユーザーが調整できるようにしています。
このカーブを用いれば、ディスプレイの最大輝度に合わせた画像を出力することができます。
しかし、ここで問題が起きました。
5.2.1.1 HDR用のトーンマッピングの問題点
元々『NGS』ではトーンマッピング後はSDR前提で処理が組まれています。
このため、カラーグレーディングやSDRポストプロセス処理で問題が発生しました。
特にカラーグレーディング処理では0.0~1.0の値を前提として作られているため工夫が必要でした。
対策として、最大輝度で除算を行いカラーグレーディング処理を行った後、元に戻すと対応を試してみましたが色合いが不自然になってしまう問題が発生しました。
下の画像がSDR時の画像とHDR時の画像の比較になります。
左:SDR出力、右:HDR出力
今回の対応では「明るさの幅」を拡張したいのですが全体的に青みが強く出てしまっています。
この問題に対処するため、レンダリングパスについて再度見直す必要がありました。
5.2.1.2 HDR時におけるレンダリングパスの改良
先ほどのレンダリングパスについて、改良を加えたものがこちらになります。
改良したHDR時のレンダリングパス
違いとしては、UI描画の手前までSDRで処理を通し、その後HDR用のトーンマッピングをかけているところです。
この対応を行うことでUI描画の手前までの区間はSDR時と同じ処理になるため、色味に不具合が発生しないようにしました。
また、元々のSDR時のレンダリングパスでは8bit精度で処理を通していたのですが16bit精度にしています。
これはSDRに範囲を狭くして、後から引き延ばす都合上精度を落とさないようにするために行っています。
5.2.1.3 HDR用のトーンマッピングの改良
それでは新しくUI描画の手前に移したHDR用のトーンマッピング処理について説明いたします。
HDR用のトーンマッピングではこちらの3つを満たすものを独自実装しました。
SDR用のトーンマッピング後の値を使用すること
輝度の値が低いところについてはSDRと同じにすること
輝度の値が高くなるにつれて既存のHDR用のトーンカーブの変化率に近づくようにすること
これらを満たすトーンマッピングを実装したコードが下記になります。
// ガンマ補正の効果を相殺するため、ガンマ2.2を処理
color.rgb = pow(color.rgb, 2.2f);
// 係数を算出
float3 rate = color.rgb;
rate = pow(rate, 10.0f);
// HDR用のトーンマッピングをかける
// max_nit_level:最大輝度
color.rgb = lerp(color.rgb, color.rgb * max_nit_level, saturate(rate));
// UI素材と色空間を揃えるため、再度ガンマ補正を処理
color.rgb = pow(color.rgb, 1.0f/2.2f);
下記がトーンマッピング適用時のグラフになります。
青:既存のHDR用トーンカーブ、緑:SDR用トーンカーブ、灰:改良したHDR用トーンカーブ
グラフを見ると、輝度の値が低いところはSDRと同じで、輝度が高くなるにつれてHDRに近づいているのがわかるかと思います。
この改良したトーンマッピングを行った結果とSDR時の画像を比較したものが下記になります。
左:SDR出力、右:改良したトーンマッピングによるHDR出力
改良前と比べ全体的な色見は変わらず、ライトの白飛びが改善していることがわかるかと思います。
5.2.2 HDRにおけるOETFについて
次にHDRにおけるOETF処理について説明します。
HDRの場合はOETF処理と合わせて下記の処理を行っています。
ガンマ2.2を処理
色空間の変換処理
輝度の補正
PQ補正
一番最初にガンマ2.2を処理しているのは、ここまでガンマ補正後の画像で処理されているためです。
SDRディスプレイとHDRディスプレイで使用されているEOTFは違うので、ここでガンマ補正前の画像にします。
5.2.2.1 色空間の変換について
『NGS』ではRec.709で描画処理を行っているため、HDRディスプレイにそのまま値を出力すると下記画像のようにSDR時の画像と比べて鮮やかすぎる見た目になってしまいます。
色変換せずに出力した画像
HDRディスプレイではRec.2020で処理するため、Rec.709の値をそのまま渡してしまうと下記の画像のように座標変換で引き延ばされてしまい、想定と違う色味になってしまうのが原因です。
座標変換による引き延ばし
そこで、Rec.709からRec.2020への変換処理を行うことで元の画像と同じ色合いになるようにします。
// Rec.709からRec.2020への変換
float3 ConvertRec709To2020(float3 value){
float3x3 matrix709to2020 =
{
{ 0.627404f, 0.329282f, 0.043314f },
{ 0.069097f, 0.919541f, 0.011362f },
{ 0.016392f, 0.088013f, 0.895595f }
};
return mul(matrix709to2020, value);
}
これで、SDRディスプレイの時と同じ色合いの画像をHDRディスプレイで出力できるようになります。
5.2.2.2 色空間の変換における改善
上述のように色空間の変換を行うことで元の画像と同じ色合いにしましたが、これはHDRなのにも関わらずRec.709の範囲の色しか扱えていないとも言えます。
本来であればゲームの素材をすべてHDR用に調整して、表示すればいいのですが運営しているタイトルですべての素材をHDR用に調整し直すのは現実的ではありません。
そこで、色の範囲を疑似的に拡張しました。
先ほどRec.709の値をそのまま渡すと、色空間がRec.2020に引き伸ばされると言いました。
これはRec.2020の色の範囲を扱えているとも言えます。
なので、色空間の変更後と変更前で線形補間を行い調整できるようにしました。
この「線形補間の係数」はパラメータ「HDR設定:彩度調整」としてユーザーが調整できるようにしています。
float3 rec709 = color.rgb
// rec709の数値をrec2020に変換
float3 rec2020 = ConvertRec709To2020(rec709);
//rateはユーザーが調整できるようにする
color.rgb = lerp(rec2020, rec709, rate);
これで、ユーザーの好みに合わせて色域の拡張を行い鮮やかさを調整できるようにしました。
「HDR設定:彩度調整」の値を50(計算上は0.5)にした結果が下記になります。
係数を0.5にした時の画像
5.2.2.3 輝度の補正
次に輝度の補正について説明します。
HDRでよく問題になるのが、SDRと比べて画像が暗くなるという問題です。
これは、SDRとHDRで標準白レベルの明るさに相違があるため発生します。
一般的なSDRディスプレイでは、1.0を100nitで表示しておらず、おおよそ200~300nitで表示されています。
このため、HDRで1.0を100nitとして表示を行うと暗くなるという問題が起きます。
この問題については輝度の補正をすることで対処します。
具体的には標準白レベルをパラメータで制御することで、明るさを調整できるようにします。
HDRは規格上10000nitまで表現が可能ですので、「標準白レベル ÷ 10000」乗算することで補正します。
この「標準白レベル」はパラメータ「HDR設定:標準白レベル」としてユーザーが調整できるようにしています。
// 指定した輝度に補正する
float3 NormalizeBaseNit(float3 value, float base_nit){
base_nit = max(base_nit, 1.0f);
return value * base_nit / 10000.0f;
}
5.2.2.4 PQ補正
次にPQ補正について説明します。
HDRディスプレイのEOTFではPQ方式が使用されます。
これは、人間の視覚特性に基づいて新しく開発されたガンマカーブになります。
OETFではこの逆関数であるPQ補正を行います。
// PQ補正
float3 LinearToPQ(float3 value){
float m1 = 2610.0f / 16384.f;
float m2 = 2523.0f / 4096.0f * 128.0f;
float c1 = 3424.0f / 4096.0f;
float c2 = 2413.0f / 4096.0f * 32.0f;
float c3 = 2392.0f / 4096.0f * 32.0f;
float3 cp = pow(value, m1);
float3 n = (c1 + c2 * cp);
float3 d = (1 + c3 * cp);
return pow(n / d, m2);
}
以上の処理を行い、描画した結果をSDRディスプレイに表示した結果がこちらになります。
OETF後の画像
SDRとHDRで使用しているEOTFが違うため、白っぽい見た目になりますがこちらの画像をHDRディスプレイで表示することで下の画像のように適切な見た目となります。
ディスプレイに表示される画像
以上がHDR時におけるレンダリングパスの説明になります。
6 調整するパラメータについて
『NGS』で調整できるパラメータは下記3つになります。
HDR設定:標準白レベル(輝度の補正で使用)
HDR設定:最大輝度レベル(HDR用のトーンマッピングで使用)
HDR設定:彩度調整(色域の拡張で使用)
これらのパラメータを用意した理由はディスプレイによって、明るさや色味が違うためです。
『NGS』では調整しやすいように下記のような調整用の画像を用意しました。
調整用の画像
標準白レベルでは250nitが基準となるようにし、最大輝度レベルではそのディスプレイが表現できる明るさに調整できるようになっています。
しかし、ディスプレイの設定やスペックによっては眩しすぎたり、暗くなったりすることがあるため好みに合わせて調整してただくのが適切かと思います。
7 まとめ
長くなってしまいましたが、『NGS』におけるHDR対応についてまとめに入ります。
HDR対応では下の3つのことを行い、SDRの見た目を維持しつつ明るさと色の拡張を行うことができるようにしました。
HDR用のトーンマッピング処理
色の範囲の拡張
HDRにおけるOETF処理
最初に記述したようにこれは『NGS』におけるHDRの対応方法になります。
ゲームによっては「SDRとは違った絵を出したい」といった要望や「HDR用に素材を調整したい」等の要望によって、対応方法に違いが出てくるかと思います。
この記事にかかれていることについては、あくまで参考として活用いただけると幸いです。
8 最後に
汎用ゲームエンジンが流行りつつありますが、自社でゲームエンジンを開発することで、どういった処理が最適なのかを基礎から学び、ひいては独自の実装で特徴ある表現を可能にすることができます。
自社でゲームエンジンを「使う」だけでなく、「作る」ことができる環境。
セガで働くことにご興味を持たれましたら下記サイトにアクセスお願いします。
(C) SEGA
www.sega.co.jp