昔懐かしいレンズフレア
少し前にある方々と飲んだ時のこと、VRにおけるポストプロセスはなんとかならないだろうか、という話題が出ました。
VRでは、主に速度的な問題で、全画面処理を複数回行わなければならないポストプロセスはコストが高くなります。
その上立体視なので、左右で画面の描画状態が異なり、派手なポストプロセスほど誤魔化しきれない問題を発生させます。
しかし、主に映像業界の人からすると、ポストプロセスはできるだけ使いたくなるわけです。
派手でかっこいい映像を作ろうと思えば思うほどポストプロセスは重要になります。
エフェクトやモーションももちろん重要ですが、よりかっこよく!と求めていくとポスプロに進まざるを得ません。
ポスプロの速度的なコストは、最悪恐ろしいレベルのハイスペックPCを用意すればなんとかなるかもしれません。
しかし、立体視との相性問題はかなり手強い技術的問題です。
特に飲み会で話題になっていたのはDOFとレンズフレアでした。
この2つはランタイムコストもさることながら、立体視との相性が疑問視されるポスプロです。
DOFについてはエンジニア(自分含む)は比較的肯定的でした。
DOFによって視線誘導がしやすいのではないか、というのが理由でした。
しかしこれに関しては反対意見もありますので、何ら問題がない、とは言えないでしょう。
下手をすると視線誘導が露骨すぎて酔う原因になるかもしれません。
UE4のレンズフレアはやはり描画された画面情報から生成するわけですが、強い光源が左右の画面で同じように出るとは限りません。
つまり、レンズフレアを生成する光源が右目用画面のみに描画されるという状態では、右目画面にはレンズフレアが発生し、左目画面には発生しないという状態になります。
これはもちろんよろしくない状態で、しかも解決が難しい問題になってしまいます。
そこで白羽の矢が立ったのが昔ながらのスプライトを利用したレンズフレアです。
太陽や点光源くらいにしか使えませんが、演出として使うには必要十分でもあります。
さて、ここで問題。
立体視で昔ながらのスプライト式レンズフレアはどのように見えるのが正しいのか?
レンズフレアはレンズ内部での光の再反射によって発生するのでレンズ面(スクリーン面とほぼ同じ?)に張り付いて見えるのが正しい?
しかし、人間の目は左右で場所が違っているので、光の再反射の仕方も変わるはず。左では見えるけど右は見えない、もしくはその逆だっておかしくないのでは?
これらは本来の正しさよりも、人間が自然に見えるのは何か?という感覚的な問題になってくるような気がします。
これはある作品で実装したことがあるのですが、リファレンスとした作品は2作品あります。
1つはPC版バイオハザード5です。
あまり知られていないかもしれませんが、PC版バイオハザード5は立体視に対応していました。
そしてレンズフレアはスクリーン面に張り付いている形、つまり視差なしの状態ですべてのフレアが描画されていたのです。
これを見て思ったのは…開発者の方には大変申し訳無いのですが、これはひどいw でした。
バイオハザード5の立体視はスクリーン面をガラス窓として、その奥でゲームシーンが進行するジオラマのような立体視の使い方でした。
もしかしたらシーンによっては飛び出しも利用していたかもしれませんが、自分が確認した短い時間では確認できませんでした。
つまり、ガラス窓を通して動くジオラマを見ていたら、そのガラス窓にぺたりとレンズフレアっぽい光の輪がいくつも張り付いていたわけです。
残念ながら見たことがある人でなければこの感覚は正確にわからないと思いますが、ある程度想像できるのではないでしょうか?
もう1つの作品は、これは見たことがある人も多いと思われますが、映画トランスフォーマー/ダークサイド・ムーンです。
この作品は当時増えてきていた立体視作品の中でも特に出来が良かったと思います。
レンズフレアやグレアなどの表現もそれまでの作品と比べて非常に多く使われていて、前述の作品を作成している最中に大変参考になった作品です。
実際にレンズフレアが使われているのは予告編でも見られます。
https://www.youtube.com/watch?v=rRIf17ntga0
もちろん、立体視で見なければどうなっているのかわかりませんが。
この作品を立体視で見たのは劇場でのみ1回だけですが、その時にこの手のポスプロは極力チェックするようにしてました。
…まあ、途中から映画が面白くてそちらにばかり集中してしまいましたがw
運が良かったのはレンズフレアがかなり早いうちに出てきてくれたことです。
少なくとも自分が見た限り、レンズフレアには奥行きがありました。
光源からどんどんこちらに向かってきていると感じられたわけです。
自分はそう感じた、というだけで現実にはそうではなかったかもしれませんが、少なくともバイオハザード5のようなものではありませんでした。
申し訳ありませんが、以前関わった作品でどう実装したかについてここでは書けません。
とりあえず言えることは、今回の実装方法とは別、ということだけですね。
着想自体はトランスフォーマーで間違いないです、はい。
今回UE4での実装では、通常は2Dスクリーン上に貼り付けるフレアスプライトを今回は3D空間上に、奥から手前に配置されるようにしてみました。
処理的にも軽いので、VRでも使用できるかもしれません。
残念ながらVRHMDを持っていないので確認できていませんが、どなたか確認していただけると助かります。
とまあ前置きが長くなりましたが、続きからで実装を提示します。
まずは結果から。
見ての通り、非常に単純です。
スプライトのテクスチャは適当に作ったものなので、本格的に使う場合はちゃんとしたテクスチャを用意しましょう。
仕組みはすべてBP化していて、レンズフレアBPを配置、スプライトを出す位置の指定、各位置で使用するスプライトを表示するパーティクルアセットを設定、距離やサイズを調整したら光源方向を指定して完成です。
BPを見ていきましょう。
まずはスプライト位置を設定する構造体 S_FlareSprite です。
この構造体はスプライト1つにつき1つを使用します。
Positionはスプライトの位置ですが、これは-1.0~1.0の範囲で指定します。
レンズフレアでは光源のスクリーン上の座標からスクリーン中心への直線を描き、この直線上にスプライトを配置します。
この際、1.0は光源の位置、0.0はスクリーン中心の位置、-1.0は光源の、スクリーン中心に対する点対称の位置となります。
Sizeはスプライトのサイズで、ただの積算値です。
基本的なサイズはスプライトとなるパーティクルアセット側で指定しますが、微調整用のパラメータとして追加しました。
本当なら画面サイズと3D空間上の座標から適切なサイズに自動計算すべきところなのでしょうが、今回はサボりました。
SpriteEffectは使用するパーティクルアセットです。これを設定しないとスプライトが表示されないので注意。
BP本体です。
BP_LensFlareという名前で、配置はどこに置いても自由です。
必要な時に配置、不要になったら削除してもよいでしょう。
ただ、動的配置の場合はパラメータ設定に注意です。
こちらは変数です。
公開されている変数だけ説明します。
Sprites は S_FlareSprite の配列です。
LightLocation は光源の位置で、平行光源の場合は光源へのベクトルを与えればOKです。
光源からのライトベクトルの逆である点に注意してください。
IsLocationInfinite は光源が無限の先、つまり平行光源かどうかを示すフラグです。
NearLimit と FarLimit はフレアスプライトを配置するビュー空間の深度値の限界です。
Position が 1.0 の場合はスプライトのビュー空間深度は FarLimit になり、-1.0 の場合は NearLimit になります。
この2つのパラメータを調整することでスプライトの視差を調整することが出来る、というわけです。
IsSpriteUse はフレアをスプライトで生成するか、メッシュで生成するかを決定できます。
メッシュの場合は予め用意された球モデルをスプライト代わりに配置します。
通常はスプライトでいいのですが、ただのビルボードであることがVRではバレやすく、それが問題になる場合はこちらを使用してみてください。
もしかしたらうまくいくかも?
スプライトの生成は CreateSprites 関数で行います。
Construction Scriptで呼んでも構わないのですが、レベルエディット中は生成されても邪魔なのでBeginPlayで呼び出しています。
Spritesに登録されている情報に従って、パーティクルかメッシュを生成しているだけです。
パーティクルを使用する際にサイズを100倍していますが、パーティクルのベースサイズを100と仮定しているためです。
パーティクルスプライトのサイズは FlareSize という名前でパラメータ化されているものと仮定しています。
パーティクルは基本、テスト用で作成した P_FlareTest アセットのマテリアルだけ切り替えるのがおすすめです。
次に CalcLightScreenPosition 関数を紹介します。
現在のカメラのパラメータを利用し、光源のスクリーン上での座標を取得します。
ついでにビュー空間上の深度と画面中心の座標を取得しています。
ビュー空間上の深度はフレアのアルファ値に転用するので重要です。
画面中心の座標は画面サイズの半分そのままなのですが、画面サイズを求める方法がパッと見つからなかったのでやっぱりサボりました。
サボりすぎです、すみません。ウボァー。
最後の関数は CalcFlareLocation で、スプライトのスクリーン座標から適切な3D空間上のワールド座標を計算します。
出力されたワールド座標をスプライトコンポーネントに設定するだけです。
ワールド空間からスクリーン空間への変換、及びその逆は PlayerController の ConvertWorldLocationToScreenLocation と ConvertScreenLocationToWorldLocation を使用します。
前者の関数はスクリーン空間上の深度は取得できないので、こちらは別途計算する必要があります。
後者の関数は深度の指定は行わないので、多分ですがニアクリップ面上のワールド座標を計算します。
これはそのまま使用できないので、戻り値の WorldDirection の方を利用して適切な深度に配置するようにしています。
最後は毎フレームの計算処理です。
イベント化していますが、Tickイベントで呼び出しているだけです。
本来であればカメラの計算が完了した後に呼び出すべきなので、必要に応じてイベントを発行するように修正しても構いません。
前半
後半
まあ、適切に関数呼んで、パーティクル、もしくはメッシュの座標設定等を行うだけです。
あとはまあ、マテリアルとかをきちんと作成しましょうね、くらいしか言うことがありません。
一応ですが、マテリアルは深度テスト無効にしておくほうが良いです。
実装したBPは以下のリンクにおいてあります。
https://dl.dropboxusercontent.com/u/39588440/LensFlareTest.zip
Contentフォルダ直下に置けばそのまま使えるかと思います。
UEのバージョンは4.10です。
動画を撮影した際のパラメータはこんな感じ。
Light Locationにはレベル開始時に平行光源から求めた値を入れています。
あと、現在は点光源については多分正常動作しませんのでオススメできません。
深度の問題が解決しづらかったので放置しました。申し訳ない。
出来れば、どなたかにVRでどう見えるか試してもらいたいですね。
暇な方、どうでしょうか?