プログラミング
小さな携帯ゲーム機のための小さな3Dレンダラーの構築
Building a Tiny 3D Renderer for a Tiny Handheld (saffroncr.itch.io)
要約
この記事は、Playdateという小さな携帯ゲーム機向けに3Dソフトウェアレンダラーを開発した経験について述べています。開発者はまず、デバイスの3D処理能力と画面描画性能を評価するためにシンプルなレイキャスターを作成しました。その結果、初期の3Dアクセラレーターや初期のプレイステーションには及ばないものの、Playdateの低解像度・1ビットディスプレイという特性を活かせば、3DOやセガサターン時代のゲームのような体験を提供できる可能性を見出しました。
全文翻訳
今日は、Playdateという小さな携帯ゲーム機のために3Dソフトウェアレンダラーを書いた私の旅についてお話ししたいと思います。
最初、パフォーマンスの基準値は何もありませんでした。私のアイデアがどれほど実現可能か全く分からなかったので、シンプルなテストから始めました。それはレイキャスターです。
私は何年も前に、Ken Silvermanのウェブサイトのコード例を基にレイキャスターを書いたことがあり、それ以来、低電力デバイスの3D処理能力と画面描画性能をテストしたいときには、基本的にこれを作成しています。
この種のテストを書く、コンパイルする、実行するために多くのものは必要ありません。そして、それはデバイスの処理能力のおおよそのアイデアを与えてくれます。
より具体的には、3Dレンダラーにとって最も重要な領域をベンチマークできます。それは、浮動小数点数とベクトル演算をどれだけ速く処理できるか、メモリ操作をどれだけ速く実行できるか、画面への描画をどれだけ速くできるか、そしてフレームバッファの設定と使用に問題がないかどうかです。
最初の結果は予想よりも悪かったです。パフォーマンスは悪く、このプロジェクトが簡単ではないことが明らかになりました。
はっきりさせておくと、私のレイキャスターは最高のパフォーマンスを念頭に置いて書かれたものではありませんでした。
これは決してPlaydateでレイキャスティングエンジンを動作させることについての話ではありません。
十分な最適化を行えば、それが可能であると私は確信しています。
ポイントは、このデバイスがどれほどパワフル(あるいはそうでないか)であるかを感じ取ることでした。
このテストは、携帯ゲーム機の能力をより良く理解するのに役立ちました。
初期の3Dアクセラレーター(3dfx Voodooなど)のレベルで3Dシーンを描画するのに十分なパワーがないことは明らかでしたし、オリジナルのPlayStationでできることと競うこともできませんでした。
しかし、希望はありました。
Playdateは小さく、低解像度の画面を持っており、1ビットディスプレイを使用しているため、メモリの節約が大きく期待できます。
結果から、プレイヤーの視点から見ると、3DOやセガサターン時代に近いものを作れると確信していました。
それが何を意味するのかを明確にする必要があります。
3DOやSaturnのようなゲームに似たものと言うとき、私は画面上の知覚される結果を意味します。
技術的な意味ではありません。それらのコンソールのハードウェアはPlaydateとは非常に異なるからです。
3DOとSaturnの両方には、ポリゴン(つまりクワッド)を描画するためのカスタムハードウェアと控えめなCPUがありました。
グラフィックスハードウェアがほとんどの重労働を行うことになっていたため、メーカーはより安価なCPUで済ませることができました。
欠点は、これらのマシンがあまり柔軟ではなかったことです。
それらは、ハードウェアが設計された特定の方法でグラフィックスをレンダリングするのに適していましたが、ゲームが異なるアプローチを必要とするときには苦労しました。
これが、それらのDoomのポートのパフォーマンスが悪かった理由の1つです。
Doomのレンダラーは、それらのコンソールが3Dを描画する方法とは自然に適合しませんでした。
一方、Playdateには3Dハードウェアがありません。
3Dグラフィックスをレンダリングしたい場合、すべてCPUで行う必要があります。
ポリゴンソートはありません。
ラスタライゼーションハードウェアはありません。
デプスバッファはありません。
ビューフラスタムに対する三角形の自動クリッピングはありません。
テクスチャマッピング、フィルタリング、パースペクティブ補正はありません。
何もありません。
3Dグラフィックスについて話すとき、実際に何を意味するのかを説明するのは価値があります。
私たちは3次元の世界に生きています。
物体には奥行き、高さ、幅があります。
それらは私たちから遠いか近いか、私たちの上か下か、左か右かです。
私たちはこれらの3つの空間次元を認識し、その中を移動することができます。
しかし、私たちの目は網膜に投影された平坦な2次元画像しか受け取りません。
私たちの脳は、パースペクティブ、動き、スケール、影、オクルージョンなどの手がかりを通して3D世界を推測します。
コンピュータグラフィックスも同様のトリックを使用します。
3Dゲームは実際の3Dで世界を描画するわけではありません。
画面は平坦です。
いわゆる3Dスクリーンでさえ、平坦な2D画像を表示することによって機能します。
私たちは、3Dシーンを記述する情報を処理し、その空間内にカメラを配置し、次に実際の3D世界が私たちの目の2次元平面に投影されるのと同様の方法で、シーンを2次元平面に投影します。
ラスタライザーは、投影されたジオメトリを受け取り、それをピクセルに変換するレンダラーの一部です。これは、コンピュータ画面に描画するために使用するものです。
テクスチャのペイント方法、重なり合うポリゴンの処理、クリッピングなどを判断し、結果をフレームバッファに書き込みます。
そのフレームバッファには、フレームと呼ばれるものの2D画像が含まれており、それが画面に表示されるものです。
最新のハードウェアでは、この作業のほとんどはGPUによって行われます。
Playdateでは、GPUがないため、CPUがポリゴンの頂点を変換し、投影し、クリッピングし、ソートし、シェーディング/テクスチャリングし、結果のピクセルをフレームバッファに書き込む必要があります。
そして、毎秒数回新しいフレームを生成するのに十分な速さで行う必要があります。
それが課題です。
問題は単に「3Dを描画できるか?」ということではありません。
十分な時間があれば、ZX Spectrumでさえ3Dを描画できます。
本当の質問は、「リアルタイムでプレイ可能になるほど速く実行できるか?」ということです。
私のレンダラーはQuakeのBSPマップファイルをロードします。
このフォーマットを選んだ主な理由は2つあります。
第一に、レベルエディターとマップコンパイラをゼロから書く必要がなかったことです。
TrenchBroomをレベルデザインに、ericw-toolsをマップ、可視性データ、ライティングのコンパイルに使用できます。
これにより、膨大な時間を節約できます。
第二に、QuakeのBSPフォーマットは、レンダリングを高速化するためにすべてを事前にコンパイルするように設計されており、まさに私が求めていたものでした。
BSPはBinary Space Partitioningの略です。
基本的な考え方は、平面で空間を再帰的に分割することです。
3Dでは、各分割は空間を2つの半空間に分割します。
そのプロセスを繰り返すとツリーが作成されます。
内部ノードには分割平面が含まれ、リーフには空間の領域が含まれます。
QuakeスタイルのBSPでは、それらのリーフは世界の凸状のチャンクを表し、各リーフはそれに接する面と、そこから見える可能性のある他のリーフを知っています。
マップコンパイラは、事前に多くのコストのかかる作業を行います。
ブラシジオメトリを取得し、BSPツリーに分割し、リーフ間の可視性を計算し、その可視性をPVS(potentially visible set)として保存します。
実行時には、レンダラーはカメラを含むリーフを見つけ、そのPVSを使用して、ラスタライゼーションが開始される前にマップの大部分を拒否します。
これは膨大な時間の節約になります。
欠点は、このアプローチが静的な世界を好むことです。
また、レベルは可視性を考慮して設計する必要があります。
例えば、2つの大きな部屋をつなぐまっすぐな廊下は一度に多くのジオメトリを露出させる可能性があるため、視線を遮り、PVSが目に見えないジオメトリを削除する機会を増やすL字型の廊下の方がしばしば優れています。
BSPを使用することで、このプロジェクトははるかに現実的になりました。
ツールの再利用は時間を節約してくれました。
既存のツールをチェックし、車輪の再発明ではなく、それらを再利用して自分のニーズに適応させることができるかどうかを確認することは常に良い考えです。
マッピング、コンパイル、可視性、ライティングのための成熟した、実証済みのツールを使用することで、プロジェクトの最も重要な部分に時間を費やすことができました。
私が完全にゼロから作ったのは、実際の3Dソフトウェアレンダラーでした。
私は単にQuakeを移植したり、誰かのコードを使いたくありませんでした。
自分で作りたかったので、それを理解していることを確信でき、改良でき、あらゆる種類のアイデアをテストでき、ゲームのルックアンドフィールを見つけることができました。
z-bufferを使用するかしないか
z-bufferはデプスバッファです。
標準的なデプスバッファは、現在描画されているピクセルがカメラからどれだけ離れているかを格納します。
レンダラーが新しいピクセルを描画しようとするとき、そのピクセルの深度をz-bufferに既に格納されている値と比較します。
これが、レンダラーが遠くのオブジェクトが近くのオブジェクトの上に描画されるのを防ぐ方法です。
私の最初のバージョンではz-bufferを使用しませんでした。私は古典的なペインター