(アーティストさんへ)LDRなブルームの注意点、そしてHDRとLDRについて

f:id:hirasho0:20191003165340p:plain

こんにちは。技術部平山です。

以前LDR(Low Dynamic Range)なブルームエフェクトについて書きましたが、 今回はLDRでブルームをやる時に起こる問題点について、アーティストさん向けに書いてみます。

結論から言うと、「後から足すのはそう簡単じゃない」という事になります。 実際、最近とある製品で「入れたい」という話になったのですが、 保留という結論になってしまいました。

LDRで実装したものには結構な制約があり、 「後からチョロっと入れておしまい」にはしにくい事情があるのです。

0から255しかない

ゲームでは毎フレーム全画面の絵を描き直すわけですが、 その時に書き込むキャンバスには、大きく分けて二種類あります。

  • LDR(Low Dynamic Range。ローダイナミックレンジ)
  • HDR(High Dynamic Range。ハイダイナミックレンジ)

LDRと言えば普通、0から1の範囲の数字を、256段階で書き込みます。 1画素の容量はRGBとアルファそれぞれに1バイトで、4バイトです。

大抵HDRと言う時には、0から65000の範囲の数字を、65000段階で書き込みます。 数字が小さい所では段階が細かくなるように工夫されており、 0から1の間はだいたい2000段階くらいになります。 LDRでは256段階しかないですから、8倍細かいわけです。 その上、10とか100とか1000といった大きな値も書き込めるので、 LDRとは表現力が全く違ってきます。 そのかわり、1画素の容量は倍の8バイトです。

ダイナミックレンジというのは「表現の幅」のことでして、 LDRでは0から1しかないものが、HDRでは0から65000まで広がるわけですから、 HDRの方がいいに決まっているわけです。

しかし、残念ながら、容量が大きいものは遅いのです。 また、古い機械ではそもそもHDRが使えません。 とはいえ、本来、ブルームエフェクトはHDRなキャンバスが前提の技術です。 それをLDRでやるのは基本的に無理をしている、ということを、 知っていただきたいと思います。

では、その無理の詳細に踏み込んでいきましょう。

キャラの光沢をまぶしくしたい

とりあえず冒頭の画面を見てください。 アレなキャラの頭がギラリと光っていますね。 ブルームエフェクトによって、ハイライトがポリゴン境界をはみ出して光っています。 アニメ的な表現を想定していて、背景はQuadにテクスチャをベタ貼りして 照明計算はしていません。 しかし、この絵を作るには若干の手間をかける必要があるのです。

まず、何も考えずにやってみましょう。 背景とキャラを普通に配置します。

f:id:hirasho0:20191003165400p:plain

キャラはメタリックなマテリアルにして、ハイライト以外は暗めにしておきました。

さあ、ブルームを有効化しましょう。

f:id:hirasho0:20191003165348p:plain

白飛びしすぎですね。 入れた設定は、

  • RGBそれぞれが0以上のところを
  • 1倍の強度でぼかして足す

というものです。 黒い所はほとんど広がりませんが、白い所は白が広がり、 赤い所は赤が広がるので、まあこうなります。 すごく明るい所だけを広げたいのであって、 大して明るくない所は広げなくて良いのですから、 これは設定が悪いですね。 では設定を変えてみましょう。

f:id:hirasho0:20191003165350p:plain

マシにはなりましたが、背景は光らなくていいですよね。 この設定は、

  • RGBそれぞれが0.5以上のところだけを取り出してぼかす

というものですが、0.5以上という条件だと 背景がこんなに光ってしまいます。光るのはキャラの頭だけでいいのです。 もっと数字を上げてみましょう。

f:id:hirasho0:20191003165354p:plain

0.9以上の所だけを取り出すことにしましたが、やっぱり背景光ってますね。

まあ当然のことで、背景の画像を見ていただければわかるように、 真っ赤な所や、すごく緑な所があるわけです。 真っ赤な所はRが1くらいあるわけで、 0.9なんて余裕で超えてしまいます。

え、どうすればいいの?ということになるわけです。

HDRならこんな問題は起こらない

問題は「赤かったり緑だったりする所」、つまり背景と、 「すごく光ってる所」、つまりキャラのハイライトの区別がつかないことです。 白は1でして、太陽がガッツリ反射してすごく明るい所も同じく1です。 後者だけを取り出す、ということはそもそもできません。

でも、もしキャンバスがHDRであれば、こんな問題はありません。 65000まで書けるからです。キャラのハイライトは100とか1000とかを書き込み、 背景はたかだか1くらいまでしか書き込まない、 ということになれば、区別は容易につきます。 すごく明るい所だけ取り出してぼかすのは簡単です。

問題は、「1までしか入らない」ということにあるのです。 LDRでブルームをやるのが無茶、ということの意味がおわかりかと思います。

ではどうするか?

手はいくつかあるのですが、一番既存処理に影響を与えないでやれる手は、 「LDRなキャンバスに1より大きいものが入るようにする」です。

え、と思うかもしれません。「LDRなキャンバスは0から1しか入らない」 ってさっき言いましたからね。

しかし、解釈を変える、という手があります。 「1が入っていたらそれは10のことだ」と後で解釈しなおせばいいのです。 つまり、最初に描画する時に1/10の値を書き込みます。 背景の白は1を1/10にして0.1を書き込み、キャラのハイライトは10を1/10にして1 を書き込みます。

そうすれば、「0.1より大きい所だけ取り出してぼかす」ということができます。 そして全部処理が終わってから、結果を10倍すればいいのです。

「そんなことができるならそうすればいいじゃん」と思われるかもしれません。 しかし、これには犠牲が伴います。 まあやってみましょう。

10分の1で描いて、終わってから10倍

まず、背景のテクスチャの色を1/10します。 テクスチャをいじるのは面倒なので、マテリアルカラーを乗算して1/10にしましょう。

f:id:hirasho0:20191003165337p:plain

キャラの方はライトの強度を1/10にします。

f:id:hirasho0:20191003165334p:plain

環境光(アンビエント)も1/10にする必要があることに注意してください。 Unityの場合LightingSettingsで環境光の強度を下げます。

f:id:hirasho0:20191003165329p:plain

さて、この結果、

f:id:hirasho0:20191003165357p:plain

ブルームなしでこういう絵になります。ほぼ真っ暗ですが、 キャラのハイライトだけは白く残っていますね。期待が持てます。 実は照明計算を行うシェーダでは、計算結果は10とか100とかになっており、 1以上にならなかったのはキャンバスがLDRだからです。 ライトの強さを1/10にしても、100が10になるだけで、まだ十分明るいので、 こうして明るい色が描き込まれることになります。

では、この状態で、0.1以上を取り出してぼかす設定にしてブルームを有効化してみましょう。 私のブルームにはカラーフィルタ機能がついていまして、 処理が終わった後に何倍かすることができるので、それを使います。

f:id:hirasho0:20191003165332p:plain

ColorScaleに10を入れてあり、これで10倍になります。 ブルームの強度はほどよく調整して(この例では4くらい)みました。

f:id:hirasho0:20191003165344p:plain

確かにハイライトだけが光ってはいて、背景はそのままなんですが、 この四角いガタガタしたのは何なんでしょうか。

これが先程言った「犠牲」です。犠牲にしたのは、「精度」です。

元々LDRの場合、0から1を256段階で表していました。 1/10して描き込む場合、本来の0から1の範囲は段階が10分の1になってしまい、 たった25段階になってしまいます。ハイライト以外の所も色が段々になっていて 汚ないのがおわかりでしょうか。たった25段階しかない状態にしたものを、 後から10倍すれば、汚ないのは当然です。

妥協

自然界の物の明るさの幅というのはすごいものがあって、 そのあたりの物と太陽の明るさの比は10倍や100倍どころの話ではなく、 万とかの桁になります。 普通に見えているものと、光っている電球でも100倍くらいは違いがあり、 本当は10とか100とかが入っている所だけをぼかしたいのですが、 精度のせいで10倍すらままなりません。

仕方ないので、2倍で我慢しましょう。 明るさを1/2にして描画し、後で2倍して戻します。 先程0.1を入れた所を0.5にし、 10を入れた所は2にします。

f:id:hirasho0:20191003165340p:plain

実は、これが冒頭のスクリーンショットです。

0から1に128段階しか使えないので結構精度は落ちており、 マッハバンドも出てしまっていますが、 LDRで無理矢理やるなら我慢するしかないでしょう。 もっと凝った手もあるにはありますが、負荷が重くなるので 意味がなくなってしまいます。

まとめ

今回紹介したのは、LDRでブルームをやる一番安い方法だと思います。 「何分の1かの明るさで暗く書いて、いらない所が光らないような値を設定してブルームし、明るさを元に戻す」 という手順です。現実的には精度の問題があって2倍が限度でしょう。

さて、安いとは言え、後付けで導入するとなると問題は簡単ではありません。 最低限、テクスチャをいじらないで描画する明るさを変えられる必要があります。 テクスチャを直すコストは到底許容できません。 標準のUnlit/Textureシェーダは乗算色を指定できないので、これで作っていると シェーダの差し替えから必要になってしまいます。

また、乗算色を指定できるシェーダを使っていてテクスチャを直さずに済むとしても、 今回の例のように「照明計算なし(Unlit)」なものがあると、マテリアルの個別設定が必要になります。 その意味で、アニメ的な背景や、加算で乗せるエフェクトなどは厄介です。 エフェクトについては別カメラに分けて「エフェクトを描画する前にポスプロを済ませる」 という手もありますが、背景はそうも行きません。 まあ背景はそんなに数はないでしょうけれども、やっぱり手間は手間ですよね。

ところで、ここまでLDRでがんばっておいて何ですが、 「HDRにしちゃう」という選択肢は当然あります。 ブルームをやるならそれが一番素直な選択肢です。 高級なメモリを積んでいて、キャンバス(レンダーテクスチャ)の容量が増えても あまり遅くならない機械であれば、それでもいいはずです。 機械の性能を何らかの手段で判定して、 HDRに切り換える手はあるかと思います。 カメラをHDRを使うように設定して、 標準のPostProcessingStackを使えばいいでしょう。 性能が余っているなら妙な工夫は不要です。

ただ、「HDRの時はアンチエイリアスが効かない」といった制限がある機械も 過去にはありました。ある程度の数の機械で試して、 性能や機能制限を見ておくのが無難かと思います。