DirectXの話 第169回
OpenColorIOを使ってみる
20/03/31 up
今回はOpenColorIOを使用した色変換を実際に試してみたので使い方について解説します。
しかしまだ私は色空間とかに詳しいわけではないので、解説や使い方に間違いがあるかもしれません。
そのような場合はメールかTwitterでお伝えいただけるとありがたいです。
…マサカリは…マサカリは勘弁してや…
OpenColorIOとは?
OpenColorIO(以下 OCIO)は Sony Pictures Imageworks が開発したカラーマネジメントのソリューションで、現在は Academy Software Foundation が基本的に管理しているものです。
名前の通りオープンソースとして公開されており、ライセンスもBSDライセンスとなっているので使いやすいと思います。
主な用途は前述にもある通りカラーマネジメントです。
現在の映像作品というのは入出力デバイスが多様化していて、入力であればカメラのメーカーや型番、年代によってセンサー能力は変わってくるわ映像エンジンはアップデートされているわという感じで変化があります。
それでもゲームの場合はほぼリニア空間でのレンダリング結果で固定されると思いますが、出力先はユーザーが用意したデバイスによって変わってくるので割と困りものです。
HDRに対応してるのかとか入力端子の種類とバージョンはどうかとか…
今後も出力周りは種類が増えていく可能性もありますし、まあ、これは入力側もそうですが…
で、OCIOはそんな面倒なカラーマネジメントを取り扱うためのデータ構造と取り回しを提供するソリューションということになります。
基本的に公開されているデータ構造に則ってデータを作れば色空間の変換などは簡単に扱えるようになります。
また、データを作成しなくとも、いくつかのコンフィグデータがフリーで公開されています。
最近では当たり前に使用されている ACES(Academy Color Encoding System)のコンフィグデータも公開されていますし、このコンフィグデータをDCCツールで使うことも可能なのでDLしておくと便利かもしれません。
以下は OCIO の公開されているソースコードとコンフィグデータへのリンクです。
色空間周りは結構面倒で、リニア空間のレンダリング結果にUIをブレンドしたらPhotoshopやAfterEffectsと結果が違って見えるんだけど!と文句を言われた人も多いのではないかと思います。
色空間が違うんだからそりゃそうでしょって話ではあるのですが、PS2の頃とかだと全く関係なかった部分でもあるので困ると言われればわからないでもないわけです。
しかし、今後は出力先の色空間がRec2020になったりとかも当然あるわけで、アーティストさんにもある程度の色空間の知識が必要になります。
ただ、それはそれとして、実機側の色空間を作業ツールで確認できないってのは困るので、OCIOを使って環境を整えるのは悪くない選択肢なんじゃないかと思います。
OpenColorIOをビルドする
以下はWindows10環境でOpenColorIOをビルドする方法についてです。
LinuxやMac環境はわからないので、そちらでビルドしたい方は頑張って!
まあ、やり方はWindows版より簡単なんじゃないかとは思いますが…
まず必要なソフトウェアは以下のとおりです。
・Visual Studio 2017
・CMake 3.1.2.2
Visual Studioは2015、2019でもOKかもしれませんが、私は2017で試していますので一応2017と指定しておきます。
CMakeについても特にこのバージョンでなければならないということはないと思います。
少なくともこれより上のバージョンなら大丈夫ではないかと思います。
まずはGitHubからOCIOのソースコードをダウンロードします。
次にCMakeを使ってVS2017のx64版のプロジェクトを生成します。ちなみに、私はGUIを使って生成しました。
ちょっとエラーっぽい赤文字表示が出るかもしれませんが放置で構いません。OCIOのライブラリを生成する分には問題ないはずです。
プロジェクトが作成されたら "OpenColorIO.sln" を開いてビルドします。
そして多分エラーが出ます。
エラーの元は "src/OpenColorIO/version.rc" で、afxres.h が存在しないと言われると思います。
このエラーが出た場合は当該ファイルを開いて afxres.h -> winres.h に書き換えてください。
私の環境ではこれの変更で x64 Release版のビルドは通りました。Debug版はビルドが通らなかったのでこちらは使用していません。
多分すべてのビルドは通らないと思いますが(自環境では1つのプロジェクトでビルドが通らなかった)、"src/OpenColorIO/Release/" フォルダ内に "OpenColorIO.lib", "OpenColorIO_2_0.dll" が作成されていれば成功です。
実際のアプリケーションで使用するのはこのライブラリとヘッダファイルなので、これさえ出来てればOKです。
OpenColorIOを使う
OpenColorIOを使用するプロジェクトを用意しましょう。
今回私が使用したのはいつものDirectX12用サンプルです。
以下のリンクにある Sample023 が今回使用したサンプルです。
プロジェクトの設定でヘッダファイルと静的ライブラリへのリンクを設定します。
ヘッダファイルのリンクを指定する際に注意が必要な点としては、"include/OpenColorIO" と "include/OpenColorIO/OpenColorIO" を設定しておくことです。
"include/OpenColorIO/OpenColorIO/OpenColorABI.h" をパス指定なしでインクルードしている箇所があり、ここでエラーとなるのに対応するためです。
実行時にはDLLを使用することになりますので、DLLが置いてあるフォルダへのパスを通しておくか、実行ファイルやプロジェクトファイルの直下にDLLをコピーしておくことをオススメします。
ライブラリのリンク設定を行い、"OpenColorIO.h" をインクルードしてビルドが通れば使用が可能になっているはずです。
今回はOCIOによる色変換をGPUで実行する方法についてを記載します。
CPUでも変換は行なえますが、ゲームのようなリアルタイムアプリケーションではGPUでの処理がメインになるのでCPUについては記載しません。
まずはOCIOコンフィグファイルを読み込みます。
サンプルでは公開されている ACES1.0.3 のコンフィグファイルを使用しています。
OCIOはこのコンフィルファイルありきです。ほとんどの処理はコンフィグを起点としています。
次に出力先のView情報を取得します。View情報と言ってもViewの名前です。
ここで取得しているDisplayは色変換の種別?的なものだと思います。ちなみに取得できるのは文字列で、今回のコンフィグではACESとなっています。
ViewはこのDisplay設定内に登録されている色空間の種類になるのかと思います。DCI-P3とかRec2020とかです。
今回のサンプルではこれらを切り替えられるようにしていますので、文字列のVectorを初期化段階で作成しています。
こちらは実際の取り回し部分となります。
最初にTransformを作成します。
Transformは色空間の変換情報を指定したもので、setInputColorSpaceName() で入力色空間を、setDisplay() と setView() で出力色空間を指定します。
また、今回は行っていませんが、Transformには追加の変換マトリクスを設定することも出来ます。
わかりやすい例でいうと入力されるレンダリング画像などが露出の影響を受けている場合です。
昨今のリアルタイムレンダリングではライティング時に予め露出のEV値を適用した状態で計算を行うことがあり、この場合は最終結果がEV値の影響を受けた状態になってしまっています。
EV値が固定であるならTransform情報に対応したマトリクスを設定しておくことで、生成されるシェーダ内でこの変換が行われた上で色空間の変換が行われ、逆変換して出力されるようです。
ゲームの場合は自動露出によってEV値が変化するため、それに合わせてシェーダコードを生成してしまうと毎フレームシェーダを生成、コンパイル、パイプライン作成を行わなければならないため、実際には使わないのではないかと思います。
Transformの設定が終わったらそれを元にしてProcessorを取得します。
そしてShaderDescを作成して設定したら extractGpuShaderInfo() 命令でシェーダコードとLUTを生成します。
ShaderDescの setLanguage() 命令でシェーダコードの言語を指定できます。今回はHLSLですが、GLSLとCgが指定できます。
また、setFunctionName() では変換命令の関数名を指定できます。
GPUで変換を行う場合はここで指定した命令にカラーを引数として与えると変換後のカラーを取得することが出来るようになっていますので、使う側は大変簡単です。
ここで注意が必要な点としては生成するShaderDescです。
CreateShaderDesc() 命令を使用するとOCIOのバージョン2.0のGPUリソースを生成することが出来ますが、CreateLegacyShaderDesc() 命令を使用するとバージョン1.0のGPUリソースを生成することが出来ます。
この違いは何かというと、LUTの形状とシェーダコード、そして扱い方です。
バージョン1.0ではLUTは指定したサイズの3Dテクスチャのみが生成され、バージョン2.0では基本的に1枚の3Dテクスチャと1枚の2Dテクスチャが生成されます。
LUTは常にこの構成になっているとは保証されないようなので、実際に使用される場合は必ず何枚のどんなサイズのテクスチャが必要か確認するようにしましょう。
また、バージョン1.0の場合は引数として指定するカラーは0.0~1.0の範囲になければなりません。
実際に使用する場合は例えばPQカーブで正規化した後に変換を行い、逆変換して対応するということが必要になるでしょう。
このため、バージョン1.0で変換した場合はCPUでの変換より大きな誤差が出てきますので注意してください。
ここまでくれば必要なシェーダコードとLUTが取得できるようになります。
取得したシェーダコードは必要なLUTも織り込んだものとなっているので、インクルードファイルとして扱ってもいいですし、ピクセルシェーダのコードにコピペしてもOKです。
LUTは基本的に32ビットの浮動小数点数として取得できるので、シェーダで使用可能なテクスチャリソースにコピーして使用すれば問題ありません。
以下は各リソースの取得方法です。
LUTはRGBチャンネルの32ビット浮動小数点で取得できます。
2D LUTについてはRチャンネルのみの場合もあるようなので、取得したテクスチャタイプに応じて処理を変更しましょう。
今回はコンフィグ的に不要だったようなので特にテクスチャタイプによる処理変更はしていません。
シェーダコードは本当にただのシェーダコードが文字列で取得できるだけですので好きなように処理しましょう。
今回はランタイムコンパイルするようにしていますが、入力の色空間がリニアのみ、出力の色空間が数個のみという場合は出力したコードを適当なヘッダファイルで出力してしまってもいいでしょう。
まあ、運用についてはそれぞれのワークフローに対応しやすい形を採用すればいいのではないかと思います。
今回のサンプルではランタイム上でパイプラインステートとLUTの作り直しを行っていますので、同様の取り回しをしたい方は参考にしてみてください。
最後に
サンプルを実行して出力色空間を変更してもらうとわかるかと思いますが、色空間によって見た目がだいぶ変わります。
まあ、サンプルは出力ディスプレイがHDRだったら、などの対応は行っていませんし、自宅環境もHDR対応ディスプレイではないのでRec2020とかさっぱりな感じですが、こんなに見た目変わるようならUI作る人は大変だな、と思いますね。
そしてサンプル作ってて思ったのですが、OpenColorIOはコンフィグさえあれば取り回しは非常に簡単です。
実際のゲームで使用するのも十分ありだなと思いました。
色空間周りでワークフローをどうしようか困っている方は一度試してみてもいいのではないかと思います。