はじめに
サムザップの『呪術廻戦 ファントムパレード(以下、ファンパレ)』開発チームに所属している二宮です。リリースしてから半年が経ち、新コンテンツ「夢幻廻楼」をリリースしました。このコンテンツでは、3Dを使用してライティングを施したリッチなグラフィックを提供しています。
以前は、ファンパレ「夢幻廻楼」技術の裏側 ~ 天候変化編~として、無限に登れる塔の仕組みや天候変化の実装方法について触れましたが、この記事ではUI表現の工夫点として、階層オブジェクトがどうできているかについてお話ししようと思います。
夢幻廻楼の特徴と、階層オブジェクトが光る表現が必要な理由
夢幻廻楼の塔の外観は、見た目をしっかりと作り込んでいるため、階層と塔が十分に馴染んだものになっています。しかし、操作するユーザーにとっては、選択している階層が分かりづらくなるという問題がありました。さらに、一見どこまでもスクロールできる仕組み上、どの階層まで選択可能なのかも分かりづらくなってしまいます。そこで、選択している階層がどれか、選択可能な階層はどれかを分かりやすく可視化する必要がありました。
デザイナーチームからの要望は、階層オブジェクトの形に合わせて、色、アウトライン、グローがをかけるものでした。初めは、これならオブジェクトの色調整と、Bloomで光らせることである程度はそれらしくならないかな...と考えましたが、手前のエフェクトあることもあって塔に馴染んでしまい、光る建物がくっついた塔に見えてしまいました。これでは、UIとしては適切ではありません。
そこで、通常のUIのようにエフェクトを描画した後で、階層オブジェクトの形に合わせて光を載せる専用機能を作ることになりました。
階層オブジェクトが光る表現の作り方
さっそく、階層が光る表現の仕組みについて、概要を書いてみます。
- 階層オブジェクトのシルエット画像を作成。
- シルエットを小さくぼかした画像を用意。ボケが入った範囲をアウトラインとして扱う。
- シルエットを大きくぼかした画像を用意。そこから、元のシルエットを抜いて、グロー表現の範囲をとして扱う。
- 1~3それぞれの範囲を色付けしていくことで、オブジェクトの発光、アウトライン、グローを載せる。
階層オブジェクトのシルエット画像を撮影する方法
まずは、特定の階層オブジェクトだけを撮影する方法について書いてみます。
また、選択可能な階層オブジェクトは他のオブジェクトの影になって隠れることがあるため、「選択可能な階層オブジェクトが見えている範囲のみ光ること」が必要でした。
ファンパレ開発チームではUniversal Render Pipelineを採用しており、シルエットの撮影および効果適用のために、ポストエフェクトの直前タイミングで一つのScriptableRenderPassを仕込んでいます。
選択可能な階層オブジェクトは複数あるため、RenderingLayerを階層オブジェクトに設定しておき、対象レイヤーのみを描画するようにUnityEngine.Rendering.ScriptableRenderContext.DrawRenderers
を叩いています。
さらに、白黒のシルエット画像を作りたいので、DrawRenderersを叩く際に専用のシェーダーを指定してシルエット描画を行なっています。該当シェーダーでは、次の処理をさせることで、白黒のシルエット画像を作り出すことに成功しています。
- あらかじめRenderTextureは黒でクリアしておく。
- 見えている範囲だけ描画するために、背景やエフェクトを描画したZBufferを再利用した上で、ZTestには
CompareFunction.Equal
を使う。 - 色を白で描画する。
シルエット画像を使ったUI効果
シルエット画像の撮影についてお話したので、ぼかしたシルエット画像を使った、アウトラインやグローの付け方について、書いていきます。
アウトラインの擬似的なシェーダーコードを載せておきます。小さくぼかしたシルエット画像を使って、ぼかしがかかった範囲をアウトラインの範囲として使っています。
// 元のシルエット画像は 何かが書き込まれていれば1 (白), そうでなければ 0 (黒) のテクスチャにしておきます。 // ぼかしをかけたシルエット画像のぼけが効いている範囲は 0 ~ 1の間の値域のピクセルとなり、十分にぼかす強さを抑えることで、これをアウトラインの範囲として定義できます。 const half small_blured_value = tex2D(_SmallBlurredSilhouetteBuffer, i.uv).r; half4 outline = 0 < small_blured_value && small_blured_value < 0.99 ? _OutlineColor : half4(0, 0, 0, 0);
グロー処理の擬似的なシェーダーコードを載せておきます。ぼかしたシルエット画像と元のシルエット画像からグロー範囲を決めています。
// 元のシルエット画像を取得 const half silhouette_value = tex2D(_SilhouetteBuffer, i.uv).r; // ぼかしたシルエット画像から元のシルエット画像を型抜きして、glowのアルファ値を取得 const half glowValue = saturate(tex2D(_BlurredSilhouetteBuffer, i.uv).r - silhouette_value); // グローの色を決定 half4 glow = _GlowColor * glowValue; glow.a = _GlowColor.a * _GlowPower;
階層が光る表現の工程をまとめ
以上が階層が光る表現の作り方になります。さいごに、実際のシーンを使って階層が光る表現ができるところを制作工程に沿ってを並べてみます。
パフォーマンス担保のための工夫点
「夢幻廻楼、天候変化編」でもパフォーマンスチューニングの話がありましたが、この画面普通に作ると演出モリモリなためにとても重くなってしまいます。UI表現も例外なくパフォーマンスを気にして実装を調整しています。
課題だったのは、アウトラインとグロー表現のぼかし処理です。ぼかす処理は周辺ピクセルの平均値を取る必要があるために、サンプリング数が多くなってしまって重くなりがちです。
アウトライン表現の軽量化
アウトライン表現は、十分に細くてよかったので、実はバイリニア補完でぼかしています。つまり、小さいRenderTextureを獲得して、Blitを行い、そこを参照してアウトラインをフレームバッファに書き込みます。
テクスチャのサイズ変更に伴ってバイリニア補完がかかるため、わずかなぼかし処理がかかります。
バイリニア補完によるぼかしはハードウェアのサポートによって比較的軽い処理になるため、パフォーマンス向上につながっています。
グローのためのぼかし処理
グローのためのぼかし処理は、広いぼかしになるためアウトラインと同じ手法は使えません。代わりに、サンプリング数をなるべく落とすために縮小バッファを活用する工夫をしています。
テクスチャを約 1/4 に縮小して、これをぼかすことでグローのためのぼかし画像を作っています。縦横 1/4なので、負荷はほぼ1/16になる計算です。
おわりに
この記事では、「夢幻廻楼」における階層オブジェクトが光る表現の実装方法と、その背景について詳しく説明しました。この機能は、ユーザーが選択している階層や選択可能な階層を視覚的に明確にするために必要なものでした。実装にあたっては、見た目の美しさにも十分にこだわりながら、パフォーマンスも考慮することが重要でした。
今回紹介したアウトラインやグロー表現の工夫は、リッチなグラフィックと快適な操作性を両立するための一例です。これからも、「ファンパレ」を通じて、ユーザーに楽しんでいただけるコンテンツを提供し続けていきたいと考えています。今後のアップデートにもご期待ください。読んでいただき、ありがとうございました。
©芥見下々/集英社・呪術廻戦製作委員会 ©Sumzap, Inc./TOHO CO., LTD.