HN 日本語サマリー

← 一覧へ戻る
プログラミング

ソニック06の逃した機会

The Missed Opportunities of Sonic 06 (isaacmarovitz.com)

59 pointsby ibobev18 コメント

要約

ソニック06のレンダリングシステムを深く掘り下げ、ゲームのビジュアルがなぜ期待外れだったのかを分析します。多くのカットされた機能がパフォーマンス上の理由で無効化されていたことが判明し、シェーダー、シャドウマップ、ポイントライト、プランナーリフレクションといった要素の技術的な問題点を解説しています。

全文翻訳

ソニック06の数多くの逃した機会

Marathon Recompiledを開発する過程で、レンダリングを含むソニック06の多くのコアシステムを深く掘り下げることになりました。06の様々なエリアの外観は、Crisis Cityのようにかなり良いものから、Castle Townのように完全に平坦で憂鬱なものまで様々です。

[画像: Crisis City]
Crisis Cityには、反射、パーティクルエフェクト、ドラマチックなライティングがあります。

[画像: Castle Town]
Castle Townは私を悲しくさせます。

一体何がうまくいかなかったのか、本当に不思議に思いますか?ベータコンテンツの限られた映像を見ると、最終ゲームには存在しない多くのエフェクトが見られます。Kingdom Valleyには完全なバンプマップがあり、Castle Townにはより複雑なジオメトリと植物があり、New Cityのコリジョンは開発のある時点で非常に異なる建物のレイアウトを示唆しています。

[画像: Beta Castle Town]
ベータ版のCastle Townには、適切なライティングとシャドウがありました。出典: TCRF.

カットされたコンテンツの多くは実際にはゲーム内に残っていますが、バニラではパフォーマンス上の理由で有効になっていないだけであることが判明しました。視覚効果がカットバックされていても、ゲームは実際のハードウェアでターゲットFPSに達するのに苦労します。ソニックチームがゲームをリリースするために受けていたクランチを考えると、これらの機能が最初に犠牲になったのも無理はありません。

シェーダー

これらの機能は具体的にどのように無効化されたのでしょうか?それは、使用されている多くの異なるシェーダーバリアントにかかっています。ソニック06のほとんどのものは、TerrainまたはBillboardシェーダー(名前にもかかわらず、Billboardは実際にはビルボードではありません)のいずれかでレンダリングされます。これらはいずれも多くのバリアントを持ち、それぞれ00から07までの異なる機能レベルを持っています。一般的に、数字が高いほど機能セットは優れています。これらのシェーダーはまだ完全に逆コンパイルされていないため、すべての違いを100%確実に説明することはできませんが、Billboardバリアントのラフな機能テーブルを以下に示します。CSMはカスケードシャドウマップの略で、06で使用されているシャドウ技術です。

ライティングCSMポイントライトデュアルテクスチャブレンド
00なしいいえいいえいいえ
01頂点いいえはいいいえ
02頂点いいえいいえいいえ
03頂点はいはいいいえ
04ピクセルはいいいえいいえ
05ピクセルいいえいいえはい
06ピクセルいいえはいはい
07ピクセルはいはいはい

ゲームで最も使用されているシェーダーがどれか推測したいですか?その通り、Billboard02です。最悪というわけではありませんが、間違いなく最悪のものの一つです。つまり、ゲームで最も使用されているシェーダーは、頂点ごとのライティングのみで、CSMサポートはなく、ポイントライトもありません。多くのものがなぜあんなに鈍く見えるのか疑問に思っているなら、これが理由です。ゲームのほとんどは、最も基本的なアルベドテクスチャとアンビエントライティング以外のものなしでレンダリングされています。

各シェーダーがどれだけ、どこで使用されているか興味がある場合は、便利なテーブルを作成しました。各シェーダーの説明はHedgeDocsで入手できます。

残念ながら、バリアント間のI/Oレイアウトの違いにより、それらを交換するのは期待するほど簡単ではありません。私の現在の試みはほとんど成功しておらず、ほとんどの場合、様々なテクニックが何らかの形で壊れるだけです。さらに、各マテリアルに使用されるテクスチャは、オブジェクトの.xnoファイルで定義されています。一部の.xnoが、ゲームファイル内にまだ存在しているにもかかわらず、ノーマルマップやバンプマップを参照していないだけで、シェーダーを交換するだけでは不十分な場合があります。

[画像: Billboard07を使用するCastle Town]
Billboard07シェーダーを使用したCastle Town。シャドウは建物に正しく適用されません。

シャドウマップ

シャドウマップについても、問題がないわけではありません。ゲームは最大4つのカスケードに対応するように設定されています。各カスケードは、カメラのファストラムに沿ったビューの一部に使用され、距離全体でシャドウを解決するために使用されるピクセル量を比較的均一に保ちます。しかし、CSMも直前のパフォーマンスハックの標的になったようです。

使用されるカスケードのレベルは、GenerateCSMLevelsとGenerateCSMObjectsによって定義されます。Levelsは一般的に地形/静的要素を指し、Objectsは…まあ、他のオブジェクトを指します。ほとんどのエリアは完全な4つのカスケードを使用しますが、twn_aといくつかのテストレベルではGenerateCSMLevelsが3に、GenerateCSMObjectsが2にダウングレードされ、シャドウがレンダリングされる距離が大幅に短縮されます。マルチプレイヤーでは、このカスケード数がどこでも削減されています。

シャドウが正しくレンダリングされる場合でも、これらの種類のテクニックを使用する際によく発生するアーティファクトである、深刻なシャドウアクネ(shadow acne)が発生しやすいです。これは通常、シャドウマップサンプルに小さなイプシロン値を追加することで解決されますが、ソニックチームはこれを行っておらず、私はまだシェーダーに関する知識がないため、これを追加しようと試みることができません。

[画像: Shadow Acne]
ソニックの耳のシャドウアクネ。

バニラでは、シャドウが時折不鮮明になることもあります。オブジェクトはシャドウマップに寄与するかどうかを設定できますが、時々これが間違っており、ソニックのCrisis Cityスノーボードにシャドウがない、またはソニックのストーリーカットシーンの1つでEliseのスカートにシャドウがないといった問題を引き起こします。これらは、オブジェクト自体への直接編集、またはレンダリングコード内のオブジェクトを見つけて修正するための複雑なフックが必要になる可能性が高いため、修正は容易ではありません。

ポイントライト

これは私の意見では、最も悲しいカットの1つです。ソニック06は、理論上はかなり柔軟なライティングシステムを持っています。すべてのオブジェクトに対して定義されたいくつかの固定アンビエントライトと、いくつかのポイントライトがあります。これらは、トーチのようなオブジェクトの追加の小さなディテールを提供するもので、シャドウをキャストせず、レンダリングコストも比較的安価です。

shared struct
{
    float4 Position;
    float4 Color;
    float4 Attenuation;

} g_PointLights[2]

ソニックチームは、オブジェクトあたりのポイントライトの数を2に制限しました。つまり、いつでもレンダリングできるシェーダー入力のスペースがあるライトは2つだけです。通常、このような制限は問題ありません。ほとんどのフォワードレンダラーにはこのような制限がありますが、デザイナーがステージを設計する際にその制限を認識していれば問題ありません。しかし、彼らはそうしませんでした。

Kingdom Valleyのような一部のステージは制限をほぼ忠実に守っていますが、Flame CoreやDusty Desertのようなステージは制限をはるかに超え、オブジェクトごとに8つ以上のライトをレンダリングしようとします。言うまでもなく、これはうまくいきません。レンダラーは混乱し、ライトは優先順位を「争う」ため、一部が表示されたり消えたりすることが半ランダムに発生します。

[画像: Flame Coreのポイントライト]
Flame Coreの過負荷なポイントライト。パスの左側にあるライトのみがレンダリングされます。

ソニックチームは、ポイントライトの一般的な最適化を提供し、それらを近くのオブジェクトに効果的に割り当てようとしましたが、このチェックのどこかで問題が発生しました。私は、開発後半で制限が急激に引き下げられたことと、ピクセルごとのライティングオブジェクトの多くが頂点ごとのライティングに切り替えられたことの組み合わせが、ポイントライトの寄与範囲を大きく変えたと考えています。その結果、ポイントライトはバニラではほとんど機能しません。制限を守っているステージでさえそうです。

リコンパイルでは、それらを壊す最適化のほとんどをパッチアウトできるため、バニラよりもはるかに安定してポイントライトをレンダリングできますが、それでも2ライトの制限があります。ポイントライトの寄与を追加するライティング計算はすべて、最終的なシェーダーバイトコードで展開/インライン化されているため、シェーダーごとに異なる場合があるため、このコードを特定して変更するのは困難です。これは将来的に私が取り組みたい分野です。

プランナーリフレクション

ソニック06にリアルタイムのプランナーリフレクションがあったことを知っていましたか?ヒント:先ほどCrisis Cityで見ました。ここではあまり不満はありません。一般的に、それらは非常によく機能し、シーンに多くのリアリズムを追加します。もちろん、バニラではフレームバッファの解像度の1/4に解像度が厳しく制限されていますが、これは簡単にパッチできます。ゲームのビジュアルが台無しになる領域が1つあります。それはマルチプレイヤーです。

[画像: Kingdom Valleyの修正されたキューブマップ]
Kingdom Valleyのぼやけた修正されたキューブマップリフレクション。

マルチプレイヤーで高価なプランナーリフレクションを2回レンダリングするのを避けるために、ソニックチームはそれらを完全に削除し、静的なキューブマップに置き換えることを選択しました。問題は、これらのキューブマップがかなり平凡であることです。Kingdom Valleyは素晴らしい見た目から、より落ち込んだ沼のようになります。問題ありません、パッチで修正できますよね?残念ながら、レンダリングコードの仕組みにより、これはそれほど簡単ではありません。

ソニック06はLuaスクリプトを使用してレンダグラフを構築しており、異なるモードは異なるスクリプトを使用します。これは非常にクールで柔軟なシステムですが、機能すればの話です。予想通り、シングルプレイヤーとマルチプレイヤーはそれぞれ独自のレンダースクリプトを持っています。render_gamemode.luaとrender_gamemode_multi.luaです。

function Build(_ARG_0_)
  CreateCSM(_ARG_0_)
  RenderCSM(_ARG_0_, GenerateCSMLevels, GenerateCSMObjects)
  ApplySceneParams(_ARG_0_)
  --
  RenderReflection(_ARG_0_)
  --
  PrepareColorRender(_ARG_0_)
  SetFrameBufferObjectOnce(_ARG_0_, "framebuffer0", "depthstencil", "all", 0, 0, 0, 0)
  SetViewport(_ARG_0_, 0, 0, GetSurfaceWidth(_ARG_0_, "framebuffer0"), (GetSurfaceHeight(_ARG_0_, "framebuffer0")))
  SetCurrentCamera(_ARG_0_, "main")
  AutoSetAspect(_ARG_0_)
  Zpass(_ARG_0_, "depthbuffer0", "depthstencil", "main")
  RenderMain(_ARG_0_, "framebuffer0", "depthstencil")
  PostColorRender(_ARG_0_)
  Capture(_ARG_0_, "framebuffer0")
  MakeBloom(_ARG_0_)
  ApplyBloom(_ARG_0_)
  SetCurrentCamera(_ARG_0_, "mainglare")
  AutoSetAspect(_ARG_0_)
  RenderWorld(_ARG_0_, "glare")
  RenderPostprocess(_ARG_0_, "framebuffer0")
  SetFrameBufferObject(_ARG_0_, "postprocess_output", "depthstencil", "none", 0, 0, 0, 0)
  SetCurrentCamera(_ARG_0_, "mainafterpp")
  AutoSetAspect(_ARG_0_)
  RenderWorld(_ARG_0_, "afterpp", "all", "transparent")
  SetFrameBufferObjectOnce(_ARG_0_, "backbuffer", "null", "color", 0, 0, 0, 0)
  SetFrameBufferObject(_ARG_0_, "backbuffer", "null", "none", 0, 0, 0, 0)
  ResetViewport(_ARG_0_)
  if EnableRasterizeBlur then
    RasterizeBurnoutBlur(_ARG_0_, "postprocess_output", "main", 1)
  else
    Rasterize(_ARG_0_, "postprocess_output")
  end
  RenderAfterPostprocess(_ARG_0_)
  Render2D(_ARG_0_)
end

render_gamemode.luaでは、重要なRenderReflection()の呼び出しが見られますが、これは残念ながらrender_gamemode_multi.luaには完全に欠落しています。その結果、マルチプレイヤーでリアルタイムリフレクションを有効にしようとすると、きれいなリフレクションの代わりに黒い虚空が残るだけです。

render_gamemode_multi.luaをrender_gamemode.luaに置き換えることもできません。マルチプレイヤーレンダラーには、スプリットスクリーンを作成するための特別なステップ(実質的に2つのフレームバッファ)があります。そのため、マルチプレイヤーが正しくレンダリングするには、提供されたレンダグラフが両方のフレームバッファをレンダリングできる必要があります。たとえマルチプレイヤーレンダラーをRenderReflectionを持つように変更したとしても、このスプリットスクリーン環境で動作するように設定されておらず、再び意図したとおりには機能しません。現状では、これは簡単に解決できない迷宮入りになります。将来、Luaパッチを適用する簡単な方法があれば、再検討するかもしれません。

修正できるか?

おそらく。これらの問題のいくつかは少なくとも部分的に解決されていますが、ベータ映像の良さを得るために必要なシェーダーの編集はまだ見つけられていません。これらの修正のいくつかは、リコンパイルの範囲を大きく超えていますが、修正が実現するかどうかに関わらず、このゲームのバックエンドでの動作方法については常に多くのことを学んでいます。それは将来の改善とパッチに直接貢献します。

このゲームにはまだまだ多くの可能性があります。ソニックチームに完成させる時間が与えられなかったのは、正直悲しいことです。この記事では触れていない、キャラクターシェーダーの狂気や、オクルージョンカリングの欠如など、多くのことがあります。しかし、すでに十分な長さです。このおかしなゲームを楽しんで、早い20周年おめでとうございます。

ファクトチェックはGordon Ramsay氏に感謝します。