『呪術廻戦 ファントムパレード』メモリークエストの解放演出におけるグラフィック表現

はじめに

サムザップで『呪術廻戦 ファントムパレード』の主にアウトゲームの開発・マネージメントやグラフィック関連の開発をしています、クライアントエンジニアの木田です。

弊社が提供する『呪術廻戦 ファントムパレード(以下、ファンパレ)』は2023年11月21日にリリース。
この度、半周年を迎え、TVアニメ第2期のストーリーを追体験できる新イベントとしてメモリークエスト「呪術廻戦 懐玉・玉折」をリリースしました。

メモリークエスト「呪術廻戦 懐玉・玉折」

メモリークエストは、クエストを進めてピースを獲得し、全てのピースを集める事でイベント限定報酬を獲得出来るものになります。

本記事では、メモリークエストのピース獲得時演出などで使用されている技術的な部分を一部紹介しようと思います。

概要

メモリークエストのピース獲得時演出などで使用されている技術的な部分を紹介する前に、最終的にリリースされた演出について触れておきます。

ピース獲得時演出

このように、ピースを白く飛ばすような演出となっており、ピースの形は異なります。
また、追加アップデートする度にイベント限定報酬として、別の回想残滓が分割されたピースを埋めていく形になります。

そのピースを分割する際、回想残滓によってキャラの顔の位置が異なり、キャラの顔を分割しないようにする為には、分割の仕方が回想残滓ごとで変わります。

そこで最初に検討した実装方法は、

  • 全体のアニメーション制御はTimelineで制御
  • ピースごとにPreFab化し、それぞれがTimeline制御でアニメーションを行う
  • ピースごとに個別制御するために、回想残滓の分割やそれに応じたマスクや演出用のテクスチャなどのリソース制作

しかし、回想残滓の画像分割、Timeline制御を個別で対応するとなると、大量のリソース制作が必要となり、このイベントを開催する度にかかる運用コストが上がります。

そこで、なるべく制作するリソースを減らす形で実現したかった為、専用のShaderを用意しました。

実装したもの

実際に使用されている専用Shaderについて説明します。

マテリアルパラメータ

マテリアルの設定パラメータは以下になります。

  • サブテクスチャ
  • 制御テクスチャ(R : トランジション、G : エッジ)
  • トランジションのSmoothness
  • エッジマスクのThreshold
  • ピース数
  • 対象ピース
  • ディゾルブテクスチャ
  • ディゾルブのThreshold

設定パラメータの画像

ShaderGUIで補完している部分や結果的にアニメーション制御で変更する必要の無いパラメータは見えないようにしています。
例えば、

  • ステンシル関連やテクスチャのTilingとOffsetなど弄って欲しくないパラメータを隠蔽
  • ピース数や対象ピースの設定によって、内部的に解放対象ピースを計算して、ディゾルブをかける為の対象マスク計算に使用

細かい処理の流れについての説明は、後述します。

Shaderパラメータ

実際のShaderパラメータは、画像のようになります。

Shaderパラメータ

CustomShaderを切って、ShaderGUIを通さない場合のパラメータが上の画像になります。

処理の流れ

処理の流れは、以下のようになります。 また、画像は分かりやすいように、2ピース目の解放時にしています。

1.必要なテクスチャをサンプリング

必要なテクスチャをサンプリングします。
この時、ImageのSourceに入っているテクスチャとカラー設定の計算も行います。

// Mainテクスチャ
const half4 mainTexture = tex2D(_MainTex, IN.texcoord);
half4 mainColor = mainTexture * IN.color;

// Baseテクスチャ
const half4 baseTexture = tex2D(_SubTex, IN.texcoord);
                
// コントロールテクスチャ
half4 controlTex = tex2D(_ControlTex, IN.texcoord);

// ディゾルブテクスチャ
half4 dissolveTex = tex2D(_DissolveTex, IN.texcoord);

2.トランジションマスクの計算

コントロールテクスチャのRチャネルがトランジションを制御するものになっています。
これを用いて、トランジションマスクの計算を行います。

// トランジションマスク
const float transitionMask = saturate(GetTransitionMask(controlTex.r, mainTexture.a, _TransitionSmoothness, 1 - _TransitionThreshold));

トランジションマスクの状態

また、トランジションのマスク計算をメソッド化して利用しています。

float GetTransitionMask(float transitionMaskValue, float colorAlpha, float smoothness, float threshold)
{
    return saturate(lerp(0, colorAlpha, smoothstep(0,smoothness,clamp(lerp(0, 1 + smoothness, threshold) - transitionMaskValue, 0, smoothness))));
}

3.対象ピースのマスクを計算

トランジションマスクの計算を利用して、対象ピースのマスクを計算します。
この時、コントロールテクスチャのGチャネルが境界線のマスクになっているので、減算して境界線には掛からないようにします。

// Piece
half4 pieceMask = saturate(GetTransitionMask(controlTex.r, mainTexture.a, _TransitionSmoothness, 1 - _PieceMaskThreshold) - transitionMask - controlTex.g);

pieceMaskの状態

4.ディゾルブの計算

対象ピースのマスクを使って、ディゾルブの計算を行います。

#define USE_DISSOLVE _DissolveThreshold != 0
float pieceMainMask = pieceMask;
if(USE_DISSOLVE)
{
    const half dissolveValue = _DissolveThreshold < dissolveTex.r ? 1 : 0;
    pieceMainMask = pieceMask * dissolveValue;
}

ディゾルブ計算

5.対象ピースのカラー計算

ディゾルブ込みで、対象ピースのカラー計算を行います。

fixed4 pieceColor = saturate(mainTexture * pieceMainMask + saturate(baseTexture) * (pieceMask - pieceMainMask));

対象ピースのカラー計算結果

5.残りのカラー計算

境界線部分も考慮したマスク計算で、解放済みと未解放のピースでカラーの出し分けを行います。

// エッジマスク
const float edgeMask = saturate(saturate(controlTex.g - (1 - _EdgeMaskThreshold))+ transitionMask);
#define USE_PIECE_MASK pieceMask.a != 0
if(USE_PIECE_MASK)
{
    mainColor.rgb = lerp(mainColor.rgb, pieceColor.rgb, pieceMask);
}
half4 color = baseTexture * edgeMask + mainColor * (1 - edgeMask);

#ifdef UNITY_UI_CLIP_RECT
half2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(IN.mask.xy)) * IN.mask.zw);
color.a *= m.x * m.y;
#endif

#ifdef UNITY_UI_ALPHACLIP
clip (color.a - 0.001);
#endif

color.a *= IN.color.a;
color.rgb *= color.a;

return color;

その結果が図のようになります。

最終結果

こだわりとコストを天秤に掛けてオミットしたもの

演出のこだわりと運用する上での制作コストを天秤に掛けた時に、オミットした処理は以下になります。

ピース解放前のベーステクスチャをShader制御

ピース解放前のベーステクスチャを用意しなくても済むように、回想残滓のテクスチャから下記のように対応していました。

  1. ピース解放後のテクスチャをグレースケール
  2. それに対して指定カラーを乗算

こうする事で、必須で制作するテクスチャに対して処理を行うため、ピース解放前のベーステクスチャは用意しなくて済みます。 しかし、ピース解放前の状態での色味にこだわりたいとの事で、オミットしました。

最後に

自身含め、『呪術廻戦』のいちファンとして、こだわりの強いメンバーがコミュニケーションしながら、運用・開発を行なっています。
その中でやはり、演出のこだわりと運用する上でのコストは、天秤に掛ける場面がよくあり、その落とし所を模索しています。

演出をこだわりつつ、運用コストを減らせるような仕組み作りをこれからも意識して、運用・開発に携わっていきたいなと思います。


©芥見下々/集英社・呪術廻戦製作委員会 ©Sumzap, Inc./TOHO CO., LTD.

木田 敬也

株式会社サムザップ クライアントエンジニア 2017年サイバーエージェント入社。
『戦国ASURA』『戦国炎舞 -KIZNA-』などの開発や3Dグラフィックスの研究開発に従事。
現在は『呪術廻戦 ファントムパレード』のクライアントリーダーを務める。
また、プロジェクトを超えたサムザップ全体のクライアント技術判断などにも携わる。