こんにちは
この記事は KLab Engineer Advent Calendar 2023 の17日目の記事です。
ゲーム「Portal」の仕組みについて
Valve社が開発した大ヒットパズルアクションゲーム「Portal」に登場するポータルガンの簡易的な解説になります。
あくまで個人で調査・実装したものであって実際の仕組みとは異なる箇所があると思いますのでご了承ください。
PortalはValve Corporationの商標または登録商標です。
Portalとはどういうゲームなの?
ゲーム内に登場するポータルガンを駆使してパズルを解くゲームです。ポータルガンとは、異なる場所を繋げる二つのポータルを生成するデバイスで、この記事ではその仕組みについて触れようと思います。
Portalの描画の仕組み
方法としてはレンダーテクスチャに書き込む方法とステンシルを使って描画する方法の2パターンがあります。(Portalではステンシルが使われているっぽい)
仮想カメラの位置は下のような式で計算できます(青のポータルを描画する例)
playerCamera.localToWorldMatrix * bluePortal.worldToLocalMatrix * redPortal.localToWorldMatrix
レンダーテクスチャを使う場合
レンダーテクスチャは実装が楽だが複数のレンダーテクスチャを用意する分メモリーが増える、負荷もやや高め
描画手順
- ポータルの視点からシーンを描画してテクスチャに書き込む
- 1で書き込んだテクスチャをポータルに貼り付けて、ポータルも含めシーンを描画する
- 1-2をもう一つのポータルで繰り返す
- 1-3で出来上がったテクスチャをポータルに貼ってシーンを普通に描画する
ステンシルで描画する場合
ステンシルはステンシルバッファーの管理がややこしいがテクスチャーが不要、負荷も低い
描画手順
- ポータルをステンシルバッファのみに書き込む
- デプスバッファをクリア
- 1でステンシルに書き込んだ場所だけにポータルのViewとProjection行列でシーンをバックバッファに書き込む
- ステンシルとデプスバッファをクリアして二つ目のポータルで1−2を繰り返す
- 再度ステンシルとデプスバッファをクリアしてポータルをデプスバッファのみに書き出す(ここでポータルだけがバックバッファに描画されている状態)
- シーンを普通に描画する(ポータル以外をデプスバッファに書き込む)
ポータル間の移動
ポータル間の移動は内積の性質を利用して実現することができます。
ポータルの前方向ベクトルとポータルからプレイヤーの方向ベクトル(player.xyz - portal.xyz)の内積を計算して、マイナスであればポータルを通過している、つまり繋がっているポータルへワープさせてあげれば完成です。
ただ、このままではポータルを通過しようとしても壁の当たり判定にぶつかります。ポータル付近にいる際は周囲の壁の当たり判定をOFFにすることで解決できます。
ポータルと仮想カメラの間に物があると写り込んでしまう
ポータルの中身は仮想カメラを適切な場所に移動して描画した物を表示する仕組みのため間に物があると写ってしまいます。
この問題を解決してくれるのが斜投影(Oblique Projection)という技術です。
斜投影はカメラのニアクリップをポータルの平面に移動して傾けてくれるので、ポータルから後ろにあるオブジェクトは全てカリングされます。
UnityではCamera.CalculateObliqueMatrixで取得できます。
終わりに
今回は座学中心の記事になりましたが、需要あればサンプルコードを含めた深掘りする記事も書いてみようと思います。