プロジェクトセカイ 3周年グラフィックスアップデート解説(DoF,ディフュージョン)
はじめに
この記事は Colorful Palette アドベントカレンダー 12/1 の記事です。
株式会社Colorful Paletteでクライアントエンジニアをしている、いちです。主に3D、グラフィックス分野の開発をしています。
今回は、「プロジェクトセカイ カラフルステージ! feat. 初音ミク」(以下、「プロセカ」)の3周年グラフィックスアップデートでおこなった内容のうち
DoF機能の改良、最適化
ディフュージョン機能の改良
を紹介させていただこうと思います。
DoF機能の改良、最適化
ディフュージョン機能
これらの機能は「プロジェクトセカイ ワンダショちゃんねる 3周年スペシャル」にて紹介された内容ですので、ぜひ生放送のアーカイブも見ていただければと思います。
開発環境について
3周年アップデート時のプロセカの開発環境は、以下になります。
ゲームエンジン:Unity
描画パイプライン:URP
DoF機能の改良
DoFとは”Depth of Field(被写界深度)”の略で、撮影においてピントを合わせた部分の、ピントが合っているようにみえる範囲を指します。いわゆるピントが合わないボケを表現するときに使われる専門用語です。この記事では、ボケを表現する機能の名称として”DoF機能”と呼ぶことにします。
DoF機能の改良前後比較を画像として記載します。
DoF機能自体は、アプリリリースから存在していました。ただし、今までのDoF機能では前景のみ綺麗にぼかしたり、カメラ構造に従った制御ができませんでした。今回開発した内容は、前景が綺麗にボケてくれ、かつカメラ構造に従ったパラメータをもとにDoF値を決定することができるようになりました。
開発前段の話
まず上記機能を開発するにあたって、Unity URP標準のDoFを試してみました。URPのDoF機能は2つのモードが用意されており、手前ボケ等は表現できないが、処理が比較的軽い「Gaussian」と、Gaussianと比べて処理は重いが、カメラ構造に従ったパラメータをもとにDoF値を決定でき、かつ前景のボケを綺麗に表現できる「Bokeh」というモードがあります。
表現したい内容を達成するために、Bokehモードを導入してみました。Bokehの詳細な処理に関して昨年のアドベントカレンダーにて解説していますので、興味のある方は合わせて読んでみてください。
結論から申しますと、Bokehの処理がミドルレンジ端末では処理負荷の懸念から扱えず、カスタムのDoF処理を用意することにしました。実際Bokehを導入した際、ミドルレンジ端末で60fps安定していたものが40fpsまで低下し、そのまま導入は難しいという判断になりました。
カスタムのDoFでは、Gaussianベースのアルゴリズムに、カメラ構造に従ったDoF値の決定処理と、前景ボケができるようシステムを改良しました。
実装
概要
概要を図示してみました。処理自体は簡易的で、ボケの度合いを示すCoC(錯乱円)マップを用いて、前景ボケと後景ボケ、そしてピントが合っている状態のカラーをブレンドすることで表現できます。CoCマップを変化させることによって、ピントが合う位置や範囲が変化します。
CoC(錯乱円)値の決定
CoCについて、昨年のアドベントカレンダーから抜粋します。
正規化CoCマップでは、灰色(正規化したカラー値では0.5)に近い場所がピントが合っている場所とし、白(1)に近いほど後景ボケが生じ、黒(0)に近い場所ほど前景ボケが生じると定義します。CPU処理は、前述した昨年のアドベントカレンダーの「DoBokehDepthOfField - CoCの算出」に記載した計算を、GPU処理は「実装 : Shader > pass0 : FragCoC」に記載した計算をおこなっています。ここでピクセルごとの「ボケ」のサイズを決定します。錯乱円はアルファ値に格納します。
前景後景ブラー処理とブレンド率の決定
前景と後景でダウンサンプリング+ブラーをおこないつつ、アルファ値に格納した錯乱円をもとにブレンド率を算出していきます。ブラーの重みづけは前景と後景で異なるため、ブラー処理は前景後景それぞれでおこないます。
特徴的な部分として、前景と後景でブレンド率の計算方法が異なります。ここで、Gaussianモードによる問題点に触れます。
Gaussianモードでなぜ綺麗に前景ボケが表現できないかというと、「前景ボケの対象となるオブジェクトの周辺もぼける必要がある」という点を解消できないからです。CoCはオブジェクトのDepth情報を参照して計算しているため、前景オブジェクトの周辺は奥のオブジェクトのDepthが参照され、結果周辺はぼけてくれません。
そこで、ダウンサンプリングしつつ、アルファ値に格納したCoCマップを周囲に広げることで周辺のブレンド率を前景オブジェクトに重み付けします。これにより、前景オブジェクトの周辺がぼけてくれるようになります。
この他に、ブラーのフィルタや最終ブレンド時にウェイトをつけることでボケ具合が変化するので、それぞれ調整をおこないます。
実装結果
Bokehモードを使用した場合40fpsだったDoF表現手法が、60fps出るような処理に落とし込むことができました。ただし、Bokehモードで表現できた絞り羽根を考慮したボケ形状等は今回開発した手法だと表現できません。これに関してはプロジェクトごとに表現と負荷コストを加味し、判断する必要があります。
ディフュージョン機能の拡張
ディフュージョン(diffusion)とは、言葉のとおり拡散を示し、光を拡散させることでやわらかい空気感が演出できます。
今回の改良版では、拡散する箇所を明るい場所のみにとどめるような処理にすることで、シャープさを残しつつ、かつ2Dイラスト表現に近い、柔らかいタッチを再現できるようになりました。
イラストの合成モードで考えていただくと分かりやすいかと思います。レイヤーごとに加工し、重ねていくことで表現できます。
ベースに乗算します。
ベースと乗算したレイヤーを結合し、結合したレイヤーにブラーをかけ、合成モードをスクリーンにします。
2とベースカラーを比較(明)で最終カラーを決定します。
乗算やスクリーンの強度を調整することで、ディフュージョン効果の度合いを変化させることができます。
実装
上記処理をShaderでおこなえば表現できます。擬似コードを下記に記載します。なお、最適化の観点からブラー済みカラーは、DoFでも使用した一時テクスチャを流用してサンプリングしたものになります。
half3 Diffusion(half3 baseColor, half3 blurredColor)
{
// baseColor ... ベースカラー
// blurredColor ... (ブラー後の)ベースカラー
// ブラーカラーの乗算.
half3 mulBlurColor = blurredColor * blurredColor;
// ベースカラーの乗算.
half3 mulColor = color * color;
// スクリーン結果.
// スクリーンカラー = (加算カラー) - (乗算カラー)で求められる.
half3 screenColor = mulColor + mulBlurColor - mulColor * mulBlurColor;
// 比較(明).
half3 finColor = max(screenColor, color);
return finColor;
}
これをベースとして、追加で彩度補正をおこない、ディフュージョンの出力カラー値を決定します。
既存のディフュージョン表現にも利点はあるので、どちらの機能も使用できるよう選択式にしています。MVごとにどちらかの機能のみ使用できる制限付きですが、デザイナーさんが表現したいイメージをもとに、任意に選択して使用することができます。
おわりに
グラフィックスアップデートは今後もおこなっていく予定ですので、MVを見る際にどういった部分がアップデートされたのかにも注目していただけると面白いかもしれません。