
2011年11月23日idソフトウェアは独自の伝統を維持し、以前のエンジンのソースコードを公開しました。
今回は、Prey、Quake 4、そしてもちろんDoom 3で使用されていた
idTech4が登場し
ました 。わずか数時間で、GitHubに400を超えるリポジトリのフォークが作成され、人々はゲームの内部メカニズムを調べたり、他のプラットフォームに移植したりしました。 また、参加することに決め
、Mac OS X用の
Intelバージョンを作成しました。JohnCarmackが
親切に宣伝しました 。
クリーンさとコメントの観点から、これは、
Doom iPhoneコードベース(後でリリースされたため、コメントアウトしたほうがよい)以来のid Softwareコードの最高のリリースです。 このエンジンを研究し、組み立て、実験することを強くお勧めします。
ここに
私が理解したこと
に関するメモがあります。 いつものように、私はそれらをきれいにしました、彼らが数時間誰かを救って、そして彼らのプログラマースキルを改善するためにコードを学ぶように誰かを奨励することを望みます。
パート1:概要
コードベースを説明するために、ますます多くのイラストを使用し、テキストを減らしていることに気付きました。
以前はこのために
gliffyを使用していましたが、面倒な制限があります(たとえば、アルファチャネルの欠如)。 SVGとJavascriptに基づいて、3Dエンジンのイラスト専用に独自のツールを作成することを検討しています。 似たようなものが既にあるのでしょうか? まあ、コードに戻って...
はじめに
このような素晴らしいエンジンのソースコードにアクセスできることは非常に素晴らしいことです。 2004年のリリースの時点で、Doom IIIはリアルタイムエンジンの新しいグラフィックスとサウンドの標準を設定しました。その中で最も注目すべきものは、Unified Lighting and Shadowsでした。 テクノロジーにより、アーティストはハリウッドのファッションで自分自身を表現できるようになりました。 8年後でも、Delta-Labs-4でのHellKnightとの最初の会議は依然として信じられないほど壮観です。
最初の連絡
ソースコードはGithubを介して配布されるようになりました。これは、IDソフトウェアFTPサーバーがほぼ常に置かれているか、過負荷になっていたためです。
オリジナルの TTimo
リリースは 、Visual Studio 2010 Professionalを使用して正常にコンパイルされます。 残念ながら、Visual Studio 2010 "Express"にはMFCがないため、使用できません。 リリース後、これは少し残念ですが、
依存関係は削除されました 。
Windows 7 :
===========
git clone https://github.com/TTimo/doom3.gpl.git

コードの読み取りと調査には、Mac OS XでXcode 4.0を使用することを好みます。SpotLightからの検索速度、変数の強調表示、適切な場所に移動するための「コマンドクリック」により、Visual Studioよりも作業が便利になります。 Xcodeプロジェクトはリリース中に破損しましたが
、修正するのは
非常に簡単であり、Mac OS X Lionで正常に動作するユーザー「不良セクター」のGithubリポジトリがあります。
MacOS X :
=========
git clone https://github.com/badsector/Doom3-for-MacOSX-
注: Visual Studio 2010 Productivity Power Toolsをインストールした後、Visual Studio 2010で変数を強調表示して「Control-Click」をクリックすることもでき
ます 。 これが「バニラ」インストールパッケージに含まれていない理由がわかりません。
両方のコードベースが最適な状態になりました
。1回クリックするだけで実行可能ファイルをビルドできます。- コードをダウンロードします。
- F8 / Command-Bを押します。
- 走れます!
興味深い事実:ゲームを開始するには、Doom 3リソースを含む
base
フォルダーが必要です。Doom3 CDからそれらを抽出して更新するのに時間を無駄にしたくなかったので、Steamからバージョンをダウンロードしました。 公開されたVisual Studioプロジェクトのデバッグ設定に
"+set fs_basepath C:\Program Files (x86)\Steam\steamapps\common\doom 3"
がまだあるため、id Softwareの人たちも同じことをしたようです!
興味深い事実:このエンジンはVisual Studio .NET(
ソース )で開発されました。 ただし、コード内のC#には単一行がなく、コンパイル用に公開されたバージョンにはVisual Studio 2010 Professionalが必要です。
興味深い事実: Id SoftwareチームはMatrixフランチャイズのファンであるようです。QuakeIIIのワーキングネームは「Trinity」で、Doom IIIのワーキングネームは「Neo」です。 これは、すべてのソースコードが
neo
サブフォルダーにある理由を説明しています。
建築
ゲームは、エンジンの全体的なアーキテクチャを反映するプロジェクトに分割されます。
プロジェクト
| 組立
| 注釈
|
---|
| 窓
| Mac OS X
| |
---|
ゲーム
| gamex86.dll
| gamex86.so
| Doom3ゲームプレイ
|
Game-d3xp
| gamex86.dll
| gamex86.so
| ゲームプレイDoom3 eXPension(Ressurection)
|
MayaImport
| MayaImport.dll
| - | リソース作成ツールチェーンの一部:Mayaファイルを開き、モンスター、カメラルート、マップをインポートするために実行時にロードされます。
|
運命3
| Doom3.exe
| Doom3.app
| ドゥーム3エンジン
|
Typeinfo
| TypeInfo.exe
| - | RTTI内部ヘルパーファイル: GameTypeInfo.h 生成します。 GameTypeInfo.h は、すべてのタイプのDoom3クラスと各要素のサイズのマップです。 これにより、TypeInfoクラスを使用してメモリをデバッグできます。
|
カールリブ
| Curllib.lib
| - | ファイルのダウンロードに使用されるHTTPクライアント(gamex86.dllおよびdoom3.exeに静的にリンク)。
|
idLib
| idLib.lib
| idLib.a
| IDソフトウェアライブラリ。 パーサー、レキシカルアナライザー、辞書...(静的にgamex86.dllおよびdoom3.exeにリンク)が含まれています。
|
idTech2から始まる他のすべてのエンジンと同様に、1つのクローズドソースバイナリファイル(doom.exe)と1つのオープンソースダイナミックライブラリ(gamex86.dll)が表示されます。

コードベースのほとんどは、2004年10月以降
Doom3 SDKで利用可能になりましたが、Doom3実行可能ファイルのソースコードのみが欠落していました。 Modderは
idlib.a
および
gamex86.dll
ビルドできましたが、エンジンコアはまだ閉じられていました。
注:エンジンは標準C ++ライブラリを使用しません。すべてのコンテナー(マップ、ポインター付きリスト...)は新たに実装されますが、
libc
が積極的に使用されます。
注: Gameモジュールでは、各クラスはidClassを継承します。 これにより、エンジンは内部RTTIを実行し、クラス名でクラスをインスタンス化できます。
興味深い事実:この図を見ると、必要なフレームワーク(
Filesystem
など)の一部がDoom3.exeプロジェクトにあることがわかります。 gamex86.dllもリソースをロードする必要があるため、これは問題を引き起こします。 これらのサブシステムは、doom3.exeからgamex86.dllライブラリによって動的にロードされます(これが図の矢印の意味です)。 PEエクスプローラーでDLLを開くと、gamex86.dllが1つのメソッドGetGameAPIをエクスポートしていることがわかります。

オブジェクトにポインターを渡すことで
、Quake2がレンダラーとゲームのddlをロードしたのとまったく同じように
機能します。
Doom3.exeがロードされると、次のようになります。
LoadLibrary
を使用してDLLをプロセスメモリ空間にロードします。GetProcAddress
win32を使用して、dllのGetGameAPI
アドレスを取得します。GetGameAPI
呼び出しGetGameAPI
。
gameExport_t * GetGameAPI_t( gameImport_t *import );
この「接続セットアップ」Doom3.exeの最後には、
idGame
オブジェクトへのポインターがあり、Game.dllには、欠落しているすべてのサブシステムへの追加リンク(例:
idFileSystem
を含む
gameImport_t
オブジェクトへのポインターがあります。
Gamex86がDoom 3実行可能オブジェクトを認識する方法:
typedef struct { int version;
Doom 3がGame / Moddオブジェクトを認識する方法:
typedef struct { int version;
注:各サブシステムをよりよく理解するための優れたリソースは、
Doom3 SDKドキュメントページです。2004年にコードを深く理解した人(つまり、開発チームの1人)によって書かれたようです。
コード
解析する前に、
cloc
からの統計を以下に示します。
./cloc-1.56.pl neo
2180 text files.
2002 unique files.
626 files ignored.
http://cloc.sourceforge.net v 1.56 T=19.0 s (77.9 files/s, 47576.6 lines/s)
-------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
C++ 517 87078 113107 366433
C/C++ Header 617 29833 27176 111105
C 171 11408 15566 53540
Bourne Shell 29 5399 6516 39966
make 43 1196 874 9121
m4 10 1079 232 9025
HTML 55 391 76 4142
Objective C++ 6 709 656 2606
Perl 10 523 411 2380
yacc 1 95 97 912
Python 10 108 182 895
Objective C 1 145 20 768
DOS Batch 5 0 0 61
Teamcenter def 4 3 0 51
Lisp 1 5 20 25
awk 1 2 1 17
-------------------------------------------------------------------------------
SUM: 1481 137974 164934 601047
-------------------------------------------------------------------------------
通常、コードの行数で明確なことを言うことはできませんが、ここではエンジンを理解するために必要な作業を評価するのに非常に役立ちます。 コードには601,047行あります。つまり、エンジンはQuake IIIの2倍「理解」が困難です。 コードの行数におけるidソフトウェアエンジンの履歴に関する統計:
コードの行 | 運命 | idTech1 | idTech2 | idTech3 | idTech4 |
エンジン | 39079 | 143855 | 135788 | 239398 | 601032 |
ツール | 341 | 11155 | 28140 | 128417 | - |
合計 | 39420 | 155010 | 163928 | 367815 | 601032 |
注: idTech3のボリュームの大幅な増加は、lccコードベースのツールによるものでした(Cコンパイラーを使用してQVMバイトコードを生成しました)。
注: Doom3の場合、ツールはエンジンコードベースに含まれているため、考慮されません。
高レベルでは、いくつかの面白い事実に気付くことができます。
- id Softwareの歴史の中で初めて、コードはCではなくC ++で書かれました。JohnCarmack はこれをインタビューで説明しました。
- コードは、抽象化とポリモーフィズムを積極的に使用しています。 ただし、興味深いトリックを使用すると、一部のオブジェクトでvtableのパフォーマンスが低下することを回避できます。
- すべてのリソースは、人間が読めるテキスト形式で保存されます。 これ以上のバイナリはありません。 コードは、字句解析器/パーサーを積極的に使用します。 ジョン・カーマックはこれについてインタビューで話しました。
- テンプレートは低レベルのヘルパークラス(主にidLib)で使用されますが、上位レベルでは使用されないため、コードはGoogle V8とは異なり、目を出血させません。
- コメントの観点からは、これは2番目に高品質のidソフトウェアコードベースであり、 Doom iPhoneの方が優れています。 コメントの30%は依然として傑出した結果であり、そのような十分に文書化されたプロジェクトを見つけることは非常にまれです! コードの一部(dmapセクションを参照)には、コードよりも多くのコメントがあります。
- OOPカプセル化により、コードがより簡潔になり、読みやすくなりました。
- 低レベルのアセンブリ最適化の時代は過ぎました。
idMath::InvSqrt
と空間ローカリゼーションの最適化などのいくつかのトリックは保持されましたが、基本的にコードは使用可能なツール(GPUシェーダー、OpenGL VBO、SIMD、Altivec、SMP、L2最適化(モデル処理用のR_AddModelSurfaces
))のみを使用しています。
John Carmackによって書かれ
たidTech4 Code Writing Standard (
Mirror )を見るのも興味深いです(特に、
const
の場所に関するコメントに感謝します)。
サイクルを拡大する
エンジンの最も重要な部分でのメインサイクルの分析は次のとおりです。
idCommonLocal commonLocal;
完全に分解されたサイクルの詳細については、
こちらをご覧ください 。 コードを読むとき、私はそれをマップとして使用しました。
これはidソフトウェアエンジンの標準メインループです。
Sys_StartAsyncThread
除きます。これは、Doom3がマルチスレッドであることを意味します。 このストリームの目的は、エンジンがフレームレートによって制限してはならないタイムクリティカルな機能を管理することです。
興味深い事実:すべてのidTech4トップレベルオブジェクトは仮想メソッドを持つ抽象クラスです。 通常、これはパフォーマンスを低下させます。各仮想メソッドのアドレスは、実行時に呼び出す前にvtableで見つける必要があるためです。 しかし、これを回避する「トリック」があります。 すべてのオブジェクトのインスタンスは、次のように静的に作成されます。
idCommonLocal commonLocal;
データセグメントに静的に配置されたオブジェクトは既知の型であるため、コンパイラは
commonLocal
メソッドを呼び出すときにvtableの検索を最適化できます。 接続(ハンドシェイク)を確立するとき、インターフェイスポインターが使用されるため、
doom3.exe
はオブジェクト参照を
gamex86.dll
と交換できますが、この場合、vtableでの検索のコストは最適化されません。
興味深い事実: id Softwareエンジンのほとんどを研究した結果、doom1エンジン以来、メソッド名が変更されていないことは注目に値します。マウス入力とジョイスティックを読み取るメソッドは
IN_frame()
です。
レンダラー
2つの重要な部分:
- Doom3はポータルシステムを使用するため、
dmap
従来のbspリンカーとdmap
まったく異なります。 関連するセクションで以下を確認しました。

- ランタイムレンダラーには非常に興味深いアーキテクチャがあります。 フロントエンドとバックエンドの2つの部分に分かれています(これについては、以下のレンダラーセクションで詳しく説明します)。

プロファイリング
Xcodeの
Instrumentsを使用して、CPUサイクルが何をしているかを確認しました。 結果と分析については、以下の「プロファイリング」セクションを参照してください。
スクリプトと仮想マシン
各製品では、idTech VMとスクリプト言語が完全に変更されました...そしてidが再度変更しました(詳細については、「スクリプトVM」のセクションを参照してください)
インタビュー
コードを読んでいる間、私はいくつかの革新に戸惑っていたので、ジョン・カーマックに手紙を書きました。
- C ++。
- レンダラーを2つの部分に分割します。
- テキストリソース。
- 解釈されたバイトコード。
さらに、私は1ページにすべてのビデオとidTech4についてのマスコミとのインタビューを集めました。 それらはすべて
インタビューページで収集され
ます 。
パート2:Dmap
すべてのid Softwareエンジンと同様に、設計チームによって作成されたカードは、ユーティリティによる強力な予備処理を受けて、実行時のパフォーマンスを向上させます。
idTech4では、このユーティリティは
dmap
と呼ばれ、その目的は
.map
ファイルから多面体からスープを読み取り、ポータルによって接続された領域を特定し、
.proc
ファイルに保存することです。
このツールの目的は、
doom3.exe
ランタイムポータルシステムを使用すること
doom3.exe
。 Seth Tellerによる1992年の驚くべき記事があり
ます 。
「密集した多面体環境での可視性の計算」 idTech4エンジンがどのように機能するかについて、多くの図とともに詳細に説明します。
エディター
デザイナーは、CSG(Constructive Solid Geometry)を使用してレベルマップを作成します。通常、6つの面を持つ多面体を使用して、マップ上に配置します。
これらのブロックは「ブラシ」と呼ばれます。 次の図では8つのブラシが使用されています(同じマップを使用して各
dmap
ステップを説明します)。
デザイナーは「内部」(最初の図)をよく理解しているかもしれませんが、
dmap
はブラシからスープのみを受け取り、内部または外部には何もありません(2番目の図)。
デザイナーが見るもの | .map ファイルからブラシを読み取るときにDmap が表示するもの。 |

| 
|

ブラシは、面ではなく、平面を通して定義されます。 面の代わりに平面を定義するのは非効率に思えるかもしれませんが、後で2つのサーフェスが同じ平面上にあるかどうかを確認するときに非常に役立ちます。 プレーンは「同じように」方向付けられていないため、内部または外部の部品はありません。 平面の向きは、ボリュームの内部と外部で異なる場合があります。
コードレビュー
Dmapのソースコードは非常によくコメントアウトされています。その番号を見てください。コードよりもコメントが多いです!
bool ProcessModel( uEntity_t *e, bool floodFill ) { bspface_t *faces;
0.レベルジオメトリの読み込み
.map
ファイルは、エンティティのリストです。 レベルは、worldspawnクラスを持つファイル内の最初のエンティティです。 エンティティには、ほとんど常にブラシであるプリミティブのリストが含まれています。 残りのエンティティは、光源、モンスター、プレイヤーのスポーンポイント、武器などです。
Version 2
各ブラシは複数のプレーンとして記述されます。 ブラシの側面は面(またはベンド)と呼ばれ、各面はブラシの他のすべての面で面をトリミングすることによって取得されます。
注:読み込み段階では、非常に興味深い高速のプレーンハッシングシステムが使用されます(Plane Hashing System):
idHashIndex
idPlaneSet
上に作成され、一見の価値があります。
1. MakeStructuralBspFaceListおよびFaceBSP
最初のステップは、バイナリスペースパーティションを使用してマップをカットすることです。 マップの各不透明な面は、分離面として使用されます。
次のセパレータ選択ヒューリスティックが使用されます。
1:マップに5,000ユニット以上ある場合:スペースの中央で軸指向平面(軸整列平面)を使用してカットします。 以下の画像では、6000x6000のスペースが3回カットされています。
2: 5000ユニットを超えるパーツが残っていない場合
: 「ポータル」とマークされた面を使用します(マテリアル
textures/editor/visportal
)。 以下の図では、ポータルブラシは青でマークされています。
3:残りのエッジを使用します。 他のほとんどと同一線上にある面を選択し、最小の面をカットします。 軸スペーサも好ましい。 分離面は赤でマークされています。


使用可能な面がない場合、プロセスは終了します。BSPツリーのシート全体が凸部分空間です。

2. MakeTreePortals
現在、マップは凸部分空間に分割されていますが、これらの部分空間はお互いについて何も知りません。 このステップの目標は、ポータルを自動的に作成することにより、各リーフをその隣接ノードに接続することです。 アイデアは、マップを制限する6つのポータルから始めることです。「外部」を「内部」に(BSPのルートで)接続します。 次に、BSPの各ノードについて、ノード内の各ポータルを分割し、分離面をポータルとして追加し、再帰的に繰り返します。

6つのソースポータルは分割され、リーフに展開されます。ノードが分割されるたびに、これに接続されている各ポータルも分割する必要があるため、これは見かけほど簡単ではありません。左の図では、1つのポータルがBSPツリー内の2つの「兄弟」ノードを接続しています。左の子シートをたどると、その分割面はポータルを2つに分割します。他のノードのポータルも更新して、「兄弟」や「ne」に接続しないようにする必要があることがわかります。プロセスの最後に、6つのソースポータルが数百のポータルに分割され、新しいポータルが分離面に作成されます。BSPの各シートは、共通のエッジを持つリーフに接続するポータルのリンクリストのおかげで、隣接を認識します:
3. FilterBrushesIntoTree
このステージは、BSPがボードで、ブラシがシェイプであるシェイプの選択がある子供向けゲームに似ています。不透明な葉を検出するために、各ブラシがBSPに送られます。この方法は、よく説明されたヒューリスティックのおかげで機能します。ブラシが分離平面をわずかに横切るが、EPSILONを超えない場合、代わりに、ブラシの他のすべての要素が配置されている平面の側面に完全に行きます。これで、「内部」部分と「外部」部分が表示され始めます。ブラシが接触したシートは不透明(固体)と見なされ、それに応じてマークが付けられます。
4. FloodEntitiesおよびFillOutside
プレイヤーのスポーンエンティティを使用して、塗りつぶしアルゴリズムが各シートに対して実行されます。彼は、葉がエンティティから到達可能であるとマークします。
FillOutsideの最終段階は各シートを通過し、到達できない場合は不透明としてマークします。
各サブスペースが達成可能または不透明なレベルを取得しました。リーフポータルを介したナビゲーションは均一になり、ターゲットリーフの不透明度をチェックすることで実行されます。5. ClipSidesByTree
ブラシの不要な部分を破棄する時が来ました。ブラシの各ソース側がBSPを下っていきます。側面が不透明なスペース内にある場合、破棄されます。それ以外の場合は、visibleHull
関係者のリストに追加されます。その結果、「スキン」レベルが取得され、可視部分のみが保持されます。
これ以降、残りの操作については、visibleHull
関係者のリストのみが考慮されます。6. FloodAreas
今dmap
のグループの葉の識別子エリア:各シートの塗りつぶしアルゴリズムのため。彼は、シートに関連付けられているポータルを通じてすべてを埋めようとします。ここで、デザイナーの作業は非常に重要です。エリアは、ポータル(手順1で言及したポータルのブラシ)を手動でマップ上に配置した場合にのみ識別できます。それらがなければ、それdmap
は1つの領域のみを識別し、ビデオプロセッサの各フレームはカード全体に送信されます。再帰塗りつぶしアルゴリズムは、エリアポータルと不透明ノードによってのみ停止されます。以下の図では、自動生成されたポータル(赤)は引き続き塗りつぶされますが、デザイナーによって配置されたvisportal(青、areaportalとも呼ばれます)は、2つの領域を作成することで停止します。
プロセスの最後に、各非連続シートはリージョンに属し、リージョン間ポータル(青)が定義されます。
7. PutPrimitivesInAreas
この段階で、別のゲーム「形状を見つける」で、ステップ6で定義された領域とステップ5で計算されたvisibleHullが結合されます。今回はボードが領域であり、ピースがvisibleHullです。領域の配列が選択され、各ブラシの各visibleHullがBSPに送信されます。インデックスareaIDにより、表面が領域の配列に追加されます。注:かなりスマートな動き-この段階では、エンティティの生成も最適化されます。一部のエンティティが「func_static」としてマークされている場合、それらのインスタンスはすぐに作成され、エリアにバインドされます。したがって、ボックス、樽、椅子をその領域に「接着」することができます(それらの影を事前に生成することによっても)。8.プリライト
静的光源ごとdmap
に、影のボリュームのジオメトリを事前に計算します。これらのボリュームは後で保存されます.proc
。唯一の秘isは"_prelight_light"
、エンジンがファイルの光源.map
とファイルの影のボリュームを一致させることができるように、影のボリュームが光源の識別子に接続された名前で保存されることです.proc
。 shadowModel { "_prelight_light_2900" 24 72 84 96 5 ( -1008 976 183.125 ) ( -1008 976 183.125 ) ( -1013.34375 976 184 ) ( -1013.34375 976 184 ) ( -1010 978 184 ) ( -1008 976 184 ) ( -1013.34375 976 168 ) ( -1013.34375 976 168 ) ( -1008 976 168.875 ) ( -1008 976 168.875 ) ( -1010 978 168 ) ( -1008 976 167.3043518066 ) ( -1008 976 183.125 ) ( -1008 976 183.125 ) ( -1010 978 184 ) ( -1008 976 184 ) ( -1008 981.34375 184 ) ( -1008 981.34375 184 ) ( -1008 981.34375 168 ) ( -1008 981.34375 168 ) ( -1010 978 168 ) ( -1008 976 167.3043518066 ) ( -1008 976 168.875 ) ( -1008 976 168.875 ) 4 0 1 4 1 5 2 4 3 4 5 3 0 2 1 2 3 1 8 10 11 8 11 9 6 8 7 8 9 7 10 6 7 10 7 11 14 13 12 14 15 13 16 12 13 16 13 17 14 16 15 16 17 15 22 21 20 22 23 21 22 18 19 22 19 23 18 20 21 18 21 19 1 3 5 7 9 11 13 15 17 19 21 23 4 2 0 10 8 6 16 14 12 22 20 18 }
9. FixGlobalTjunctions
Tジョイントの修正は、通常、視覚的なアーティファクトを取り除くために重要ですが、idTech4ではさらに重要です。テンプレートバッファーへの書き込み時にジオメトリを使用してシャドウを生成することもできます。Tジョイントには2つの問題があります。10.データ出力
最後に、前処理されたすべてのデータがファイルに保存されます.proc
:- 各領域について、サーフェスの多くの面がマテリアルごとにグループ化されます。
- 葉のareaIDを持つBSPツリー。
- さまざまな地域間ポータル。
- 影のボリューム。
物語
コードの多くのセグメントは、Quake()、Quake 2()、およびQuake 3()の前処理ツールで使用されるコードにdmap
似ています。この理由は、潜在的な可視セット(PVS)が一時的なポータルシステムを使用して生成されるためです。qbsp.exe
q2bsp.exe
q3bsp.exe
qbsp.exe
BSPポータル内のリーフ間の関係に関する情報を含む.map
ファイル.prt
を読み取って生成しました(「MakeTreePortals」のステップ2とまったく同じ)。vis.exe
.prt
入力として使用されます。各シートについて:
- ポータルはバインドされたリーフにキャストされました。
- シートに記入する前に、可視性のピラミッドに関連する2つの前のポータルで次のポータルを切断することにより、可視性をチェックしました(可視性は何千もの光線の放射によって決定されると考えられていますが、これは多くの人々がまだ信じている神話です)。
図は常に優れています。たとえば、qbsp.exe
ポータルで接続された6つのリーフを見つけ、vis.exe
PVSを生成するために実行されるようになりました。このプロセスはシートごとに実行されますが、この例ではシート1のみを考慮します。
シートは常にそれ自体から見えるため、シート1の初期PVSは次のようになります。シートID | 1 | 2 | 3 | 4 | 5 | 6 |
ビットベクトル(シート1のPVS) | 1 | ? | ? | ? | ? | ? |
充填アルゴリズムが開始されます。ルールには、パスに2つのポータルはないが、シートは開始点から見えると見なされます。これは、次のPVSでシート3に到達することを意味します。シートID | 1 | 2 | 3 | 4 | 5 | 6 |
ビットベクトル(シート1のPVS) | 1 | 1 | 1 | ? | ? | ? |
シート3では、実際に可視性を確認できます。ポータルn-2とポータルn-1から2つのポイントを取得して、クリッピングプレーンを生成し、潜在的な可視性について次のポータルをテストできます。図から、リーフ4と6につながるポータルはテストに合格せず、シート5へのポータルは合格することがわかります。次に、シート6のフィルアルゴリズムが再帰的に実行され、シート1のPVSの終了時に次のようになります。シートID | 1 | 2 | 3 | 4 | 5 | 6 |
ビットベクトル(シート1のPVS) | 1 | 1 | 1 | 0 | 1 | 0 |
IdTech4はPVSを生成せず、代わりにポータルデータが保存されます。各エリアの可視性は、投影時にポータルを画面スペースに曲げて、互いに対してトリミングすることにより計算されます。興味深い事実: Michael Abrashは、GDC Vaultからのこの素晴らしいビデオのマーカーを使用して、10秒でプロセス全体を説明しました(写真はクリック可能です):
。
パート3:レンダラー
idTech4レンダラーは、3つの重要な革新を行いました。- ユニファイドライティングとシャドウ:レベルフェースとエンティティフェースは、同じパイプラインとシェーダーを通過します。
- 可視表面の決定:ポータルシステムでは、実行時にVSDを実行できます-PVSは不要です。
- 「マルチパスレンダリング」。
idTech4で最も重要なのはマルチパスレンダラーです。視界内の各光源の影響は、加算混合を使用してビデオプロセッサのフレームバッファに蓄積されます。 Doom 3は、フレームバッファーのカラーレジスタが転送ではなく飽和を実行するという事実を最大限に活用します。加算ミキシングを説明するために、独自のレベルを作成しました。以下のスクリーンショットは、3つのパスが実行される3つの光源を示しています。各パスの結果はフレームバッファに蓄積されます。すべての光源が混合されている画面中央の白色光に注目してください。各通路を分離するようにエンジンを変更しました:通路1:青色光源通路2:緑色光源通路3:赤色光源() :
============================
1111 1111
+ 0000 0100
---------
= 0000 0011
() :
==========================
1111 1111
+ 0000 0100
---------
= 1111 1111



エンジンに他の変更を加えて、光源の各パスの後にフレームバッファーの状態を確認しました:
最初のパス
の 後のビデオプロセッサーバッファー2番目のパス
の 後のビデオプロセッサーフレームバッファー3番目のパスの後のビデオプロセッサーフレームバッファー興味深い事実:光源の各パスの結果を取得し、それらを手動で混合することができますPhotoshop(OpenGL加算ミキシングをシミュレートする線形回避)とまったく同じ結果を取得します。シャドウのサポートとバンプマッピングを組み合わせたアディティブブレンドは、2012年の基準でも、エンジンに非常に優れた画像を作成します。
建築
以前のidTechエンジンとは異なり、レンダラーはモノリシックではありませんが、2つの部分(フロントエンドとバックエンド)に分割されています。- フロントエンド:
- , .
- (
def_view_t
) / VBO . - RC_DRAW_VIEW.
- :
- RC_DRAW_VIEW .
- VBO.
レンダラーのアーキテクチャは、Quake3仮想マシンのバイトコードを生成するために使用されるLCCクロスコンパイラーに非常に似ています。
当初、レンダラーの設計はLCCの設計に影響されると考えましたが、SMPシステムでマルチスレッドになるため、レンダラーは2つの部分に分割されました。フロントエンドは1つのコアで実行され、バックエンドは別のコアで実行されることになっています。残念ながら、一部のドライバーは不安定であるため、別のスレッドを無効にする必要があり、両方の部分が同じスレッドで実行されました。起源に関する興味深い事実:コードを使用すると、考古学的な調査を実行することもできます-レンダラーの詳細なコードをよく見ると、エンジンがC ++からC(オブジェクトから静的メソッド)に移行していることが明確にわかります。これはコード履歴が原因で発生しました。idTech4レンダラーは、Quake3エンジン(Cコードベース)に基づいてJohn CarmackがC ++スペシャリストになるまで作成しました。レンダラーは後にC ++のidtech4コードベースに統合されました。Doom3にはQuakeがどれだけ残っていますか?言うのは難しいですが、Mac OS Xの主な方法は次のとおりです。 - (void)quakeMain;
フロントエンド、バックエンド、ビデオプロセッサとの相互作用
この図は、フロントエンド、バックエンド、およびビデオプロセッサ間の相互作用を示しています。
- フロントエンドは世界の状態を分析し、2つの結果を生成します。
- 視野に影響を与える各光源のリストを含む中間ビュー。各光源には、それと相互作用するエンティティサーフェスのリストが含まれています。
- , , . VBO .
- . OpenGL , . VBO .
- OpenGL .
Doom3
フロントエンドは困難なタスクを実行します:可視サーフェスの定義(可視サーフェス決定、VSD)。その目的は、視野に影響を与える光源とエンティティのすべての組み合わせを見つけることです。このような組み合わせは、相互作用と呼ばれます。すべてのインタラクションが見つかると、フロントエンドはバックエンドが必要とするすべてをビデオプロセッサのRAMにロードします(「インタラクションテーブル」を使用してすべてを追跡します)。最後のステップは、OpenGLコマンドを生成するためにバックエンドによって読み取られる中間ビューを生成することです。コードでは次のようになります。
- idCommon::Frame - idSession::UpdateScreen - idSession::Draw - idGame::Draw - idPlayerView::RenderPlayerView - idPlayerView::SingleView - idRenderWorld::RenderScene - build params - ::R_RenderView(params)
注: CからC ++への移行はここでは明らかです。例を理解するのは常に簡単なので、ここにレベルがあります。配設されたデザイナーのvisplanesエンジンは、次の4つの領域を見ている:
ロードすると.proc
エンジンはまた、アップロードされた.map
すべての光源を決定し、実体を移動備えます。エンジンは、光源ごとに、影響を受けるすべての領域のリストを作成しました。実行時に、プレイヤーの位置とモンスターが影を落とすようになりました。シーンを正確にするには、すべてのモンスターと影を見つける必要があります。プロセスは次のとおりです。
1 :
=========
- 0
- 1
2 :
=========
- 1
- 2
- 3

- プレーヤーがBSPツリーをバイパスしているエリアを見つけます
PointInArea
。 FlowViewThroughPortals
: . . Realtime rendering :

, , , :
( /) :
==================================
1 - 0
1 - 1
1 - 1
2 - 1
2 - 1
: « 2 — 2», 2 .R_AddLightSurfaces
, , .
( /) :
==================================
1 - 0
1 - 1
1 - 1
2 - 1
2 - 1
2 - 2
R_AddModelSurfaces
:すべてのインタラクションが見つかりました。頂点とインデックスがまだない場合は、ビデオプロセッサのVBOに読み込みます。これにより、アニメーションモンスターのジオメトリのインスタンス(モデルとシャドウのボリューム)が作成されます。- すべての「知的」作業が完了しました。ヘルプを使用して、バックエンドに画面へのレンダリングを開始
R_AddDrawViewCmd
させるコマンドが発行されRC_DRAW_VIEW
ます。
Doom3バックエンドレンダラー
バックエンドは、ビデオプロセッサの制限を考慮して、中間ビューをレンダリングします。Doom3は、ビデオプロセッサをレンダリングする5つの方法をサポートしました。- R10(GeForce256)
- R20(GeForce3)
- R200(Radeon 8500)
- ARB(OpenGL 1.X)
- ARB2(OpenGL 2.0)
2012年には、ARB2のみが最新のビデオプロセッサでサポートされています。この規格は、移植性を提供するだけでなく、ゲームの寿命を延ばしました。ビデオカードがバンプマッピング(数年前に書いたHellknightの使用方法に関するチュートリアル)とリフレクションマップをサポートしている場合、idtech4はそれらをオンにしましたが、次の操作でピクセルフィルレートの保存に全力を尽くしました:- OpenGLシザーテスト(フロントエンドによって作成された各光源に対して個別に)
- 最初の段階でZバッファを埋めます。
詳細なバックエンドコードは次のとおりです。 idRenderSystemLocal::EndFrame R_IssueRenderCommands RB_ExecuteBackEndCommands RB_DrawView RB_ShowOverdraw RB_STD_DrawView { RB_BeginDrawingView
バックエンドステージを段階的に進めるために、有名な画面をDoom3レベル
から取得し、視覚化の各段階でエンジンを停止しました.Doom3は拡散テクスチャの上にバンプマッピングと反射マップを使用するため、サーフェスレンダリングでは3つのテクスチャで検索を使用できます。ピクセルは5〜7個の光源の影響を受ける可能性があるため、再描画を行わなくても、ピクセルあたり最大21個のテクスチャ検索の可能性を想定するのは狂気ではありません。再描画を0にするには、バックエンドの最初の段階が必要です。すべてのシェーダーを無効にし、深度バッファーのみに書き込み、すべてのジオメトリをレンダリングします:
深度バッファーがいっぱいになりました。この時点から、深度記録は無効になり、深度テストがオンになります。主にzバッファへのレンダリングは逆効果のように思えるかもしれませんが、実際には、フィルレートの保存に非常に役立ちます。カラーバッファはクリーニングされ、黒で塗りつぶされていることに注意してください。自然な形では、Doom3の世界は完全に黒です。「アンビエント」ライティングがないためです-表示するには、ポリゴン/サーフェスが光源と相互作用する必要があります。これが、Doom3が非常に暗い理由です。その後、エンジンは11パスを実行します(各光源に1つ。レンダリングプロセスを部分に分割しました。以下の図は、光源の個々の通過を示しています。
光源の
効果 1 光源の
効果 2 光源の
効果 3 光源の
効果 4光源の効果5
光源の
効果 6光源の効果7
光源の 効果8光源の
効果9
光源の
効果 10光源の効果11
ラストパス:環境光の通過そして、ビデオプロセッサのフレームバッファーで何が起こるか:
光源
の通過後 1 光源
の通過後 2 光源
の通過後 3 光源
の通過後 4光源の通過後5
光源6を通過した後、
光源7を通過後に
光源8を通過後に
光源9を通過後に
光源10を通過した後
、光源11の通過後に
光源12の通過後に
R光源13が通過する次のテンプレートは、テストバッファとはさみ:光源によって影が落とされる場合、各光源が通過する前にテンプレートテストを実行する必要があります。深度失敗/深度パスの矛盾と悪名高いCreative Labsの動きについては詳しく説明しません。公開されたソースコードでは、高品質のシャドウを構築する必要があるため、深度パスアルゴリズムが遅くなります。誰かが深さ失敗アルゴリズムをソースコードに返すことができましたが、これはヨーロッパでのみ合法であることに注意してください!充填率を節約するために、フロントエンドは、OpenGLのシザーテストに使用する画面スペースの長方形を生成します。これにより、光源までの距離が原因で表面が黒のままになるピクセルのシェーダーが不要になります。テンプレートバッファは、光源8が通過する直前に適用されます。黒以外の領域はすべて塗りつぶされます。他の領域では、フレームバッファへの書き込みが制限されます。
テンプレートバッファーは、光の通路7の直前にあります。フィルレートを節約するために、はっきりと見えるハサミです。<img src = " fd.fabiensanglard.net/doom3/renderer/DOOM3-Context3-Static-StencilBuffer2.png "インタラクティブな表面
レンダリングの最終段階は次のRB_STD_DrawShaderPasses
とおりです。光を必要としないすべてのサーフェスをレンダリングします。これらには、画面とグラフィカルユーザーインターフェイスの見事なインタラクティブサーフェスが含まれます。エンジンのこの部分、ジョン・カーマックは最も誇りに思っていました。彼女は彼女が負っているすべての尊敬を得たとは思わない。2004年、最初のスプラッシュスクリーンは通常フルスクリーンビデオでした。ビデオが完成した後、レベルがロードされ、エンジンがケースに入りましたが、Doom IIIにはまったく異なるストーリーがありました。ステージ:- レベルローディング。
- クリップの再生が始まります。
- 5分目の5秒で、カメラが引き戻されます。
- 今見たビデオは、ゲームエンジンのスクリーンです!
これを初めて見たとき、私はそれが何らかのトリックであると決めたことを覚えています。ビデオプレーヤーが中断され、デザイナーがディスプレイ画面にテクスチャを挿入し、カメラの位置がビデオの最後のフレームに対応すると考えました。私は間違っていました。idTech4は、ユーザーインターフェイスサーフェスのインタラクティブな要素でビデオを本当に再生できます。RoQはこれに使用されました。GrahamDevineがid Softwareに来たときに彼にもたらしたテクノロジーです。興味深い事実:イントロで使用されるRoQは2005年には印象的で、ゲーム内の画面で使用することは大胆な動きでした。- ビデオは1秒あたり30フレームで再生されます。
- 各フレームの解像度は512x512です。当時はかなり高いです
- 各フレームは
idCinematicLocal::ImageForTime
CPU内で生成され、その場でOpenGLテクスチャとしてビデオプロセッサにロードされました。
ただし、スクリプトとネイティブメソッドを呼び出す機能のおかげで、インタラクティブサーフェスはさらに多くの機能を備えています。誰かが非常に興味を持っていて、彼らはどうにかしてDoom 1をローンチしました!
興味深い事実:インタラクティブサーフェスの技術は、すべてのDoom3メニュー(設定、メイン画面など)の作成にも使用されました。より多くの興味深いもの...
このセクションは、レンダリングの氷山の一角にすぎず、さらに深く理解することができます。パート4:プロファイリング
Xcodeには、優れたプロファイリング方法であるInstrumentsがあります。ゲーム中、サンプリングモードで使用しました(ゲームの読み込みを完全に削除し、ビデオプロセッサでレベルを予備キャッシュします)。復習
高レベルのループでは、プロセスで実行されている3つのスレッドがあります。- ゲームロジックと視覚化が実行されるメインスレッド。
- ユーザー入力が蓄積され、効果音が混合される補助ストリーム。
- CoreAudioが作成し、定期的に呼び出す音楽ストリーム(リソースの8%を消費)
idAudioHardwareOSX
(注:サウンドエフェクトはOpenALを使用して提供されますが、独自のストリームでは実行されません)。
メインストリーム
メインのDoom 3スレッドは... QuakeMain
!驚いたことに、Mac OS XにQuake 3を移植したチームは古いコードを再利用したに違いありません。時間の再配布は内部で実行されます:- グラフィックスのレンダリングに65%が与えられています(
UpdateScreen
)。 - 25%がゲームロジックに与えられます。idSoftwareによって作成されたゲームの場合、驚くほど多くです。
ゲームロジック
ゲームロジックは、スペースgamex86.dll(またはMac OS Xのgame.dylib)で実行されます。ゲームロジックは、メインスレッド時間の25%を必要としますが、これは異常に大量です。これには2つの理由があります。- インテリジェントエージェント(IA):仮想マシンが実行されており、エンティティに思考を許可しています。すべてのバイトコードが解釈され、スクリプト言語があまりにも積極的に使用されているようです。
- 物理エンジンはより複雑(線形相補性問題のソルバー)であるため、以前のゲームと比較して高価です。各オブジェクトに対して実行され、ラグドールモデリングと相互作用の解決が含まれます。
レンダラー
上記のように、レンダラーは2つの部分で構成されています。- フロントエンド(
idSessionLocal::Draw
)は、レンダリングプロセスの43.9%を占めます。Draw
フロントエンドは1つのOpenGLレンダリング呼び出しを行わないため、これはかなり不適切な名前であることに注意してください!
- バックエンド(
idRenderSessionLocale:EndFrame
)は、レンダリングプロセスの55.9%を占めます。
負荷分散は十分にバランスが取れており、これは驚くことではありません。- フロントエンドは、可視サーフェスの定義に関連する多くの計算を実行します。
- また、フロントエンドはモデルのアニメーション化と影のシルエットの検索に取り組んでいます。
- フロントエンドは、ピークをビデオプロセッサにロードします。
- バックエンドは、シェーダーパラメーターの調整とビデオプロセッサとのデータ交換に多くの時間を費やします(たとえば、エンボス加工のテクスチャの三角形インデックスまたは頂点法線マトリックスを送信します
glDrawElements
)。
レンダラー:フロントエンド
フロントエンドレンダラー:驚くことではありませんが、ほとんどの時間(91%)がVBOからビデオプロセッサへのデータの読み込みに費やされています(R_AddModelSurfaces
)。時間のごく一部(4%)が、地域間の移動とすべての相互作用の検索に費やされています(R_AddLightSurfaces
)。最小時間(2.9%)は、目に見える表面の決定に費やされます:BSPをバイパスし、ポータルシステムを通過します。レンダラー:バックエンド
レンダラーバックエンド:明らかに、バックエンドはバッファーを変更し(GLimp_SwapBuffers
)、画面との同期(10%)に時間を費やしています。これは、ゲームがダブルバッファー環境で実行されるためです。5%は、最初のパスで完全な再描画を回避するコストです。これは、主にZバッファー(RB_STS_FillDepthBuffer
)を満たします。裸の統計
Instrumentsトレースをダウンロードし、自分で探査を開始するように設定されている場合、プロファイルファイルは次のとおりです。パート4:スクリプト化された仮想マシン
idTech1からidTech3に毎回完全に変更された唯一の部分は、スクリプトシステムでした。- idTech1:仮想マシンで実行されているQuakeC。
- idTech2:x86共有ライブラリにコンパイルされたC(仮想マシンなし)。
- idTech3:C、LCCを使用してバイトコードにコンパイルされ、QVM(Quake Virtual Machine)で実行されます。x86プラットフォームでは、バイトコードはブート時にネイティブコマンドに変換されました。
idTech4も例外ではなく、すべてが再び変更されました。- スクリプトは、C ++に似たオブジェクト指向言語を使用して行われます。
- 言語はかなり制限されています(typedefなし、5つの基本型)。
- これは常に仮想マシンで解釈されます。idTech3のように、ネイティブコマンドへのJIT変換はありません(ジョンカーマックはインタビューでこれについて詳しく説明しました)。
Doom3 Scripting SDKについての注意事項から始めるのが良いでしょう。建築
全体像は次のとおりです。コンパイル:idCompiler
1つの定義済みファイルがブート時に転送されます.script
。一連のディレクティブ#include
は、すべてのスクリプトの行とすべての関数のソースコードを含むスクリプトスタックを作成します。スキャンされidLexer
、基本トークン(トークン)が生成されます。トークンが入りidParser
、1つの巨大なバイトコードが生成されます。これはシングルトンに保存されidProgram
ます。仮想マシンのRAMを表し、VM .text
とのセグメントが含まれます.data
。
仮想マシン:実行時idThread
に、リンクリストの最後に達するまで、エンジンは各スレッドに実際のCPUの時間を(次々に)割り当てます。それぞれidThread
が含まれていますidInterpreter
仮想マシンのCPUの状態を保存します。インタプリタが非常識になり、500万を超える命令に従わない限り、CPUはそれを空にすることはできません。それは共同マルチタスクです。コンパイラ
コンパイルパイプラインは、プリプロセッサがないことを除けば、Google V8やClangなどの他のコンパイラーを読むときに表示されるものと似ています。したがって、「コメントスキップ」、マクロ、ディレクティブ(#include、#if)などの機能は、字句解析プログラムとパーサーの両方で実行する必要があります。以来idLexer
、広くすべてのテキストリソース(マップ、エンティティのルートカメラ)を解析するためにエンジン周りに使用、それは非常に原始的です。たとえば、合計5種類のトークンを返します。- TT_STRING
- TT_LITERAL
- TT_NUMBER
- TT_NAME
- TT_PUNCTUATION
したがって、パーサーは「標準」コンパイラーパイプラインよりも多くの作業を行います。ゲームが開始されると、idCompilerは最初のスクリプトをロードします。script/doom_main.script
シリーズ#include
は、1つの巨大なスクリプトに接続されたスクリプトのスタックを作成します。エンジンパーサーは、再帰下降を備えた標準の下降パーサーのようです。スクリプト言語の文法はLL(1)であり、0のリターンが必要なようです(レキシカルアナライザーには単一のトークンの読み取りをキャンセルする機能があります)。すでにこの本を読んでいれば、迷うことはありません...しかし、そうでないなら、これは理解を始める良い理由です。通訳
実行時に、イベントは作成をトリガーしますidThread
。これは、オペレーティングシステムスレッドではなく、仮想マシンスレッドです。 CPUは一定の時間を与えます。それぞれidThread
にidInterpreter
コマンドポインターモニターと2つのスタック(1つはデータ/パラメーター用、もう1つは関数呼び出しの追跡用)があります。実行はidInterpreter::Execute
、インタープリターが仮想マシンの制御を停止するまで行われます。これは、共同マルチタスクです。 idThread::Execute bool idInterpreter::Execute(void) { doneProcessing = false; while( !doneProcessing && !threadDying ) { instructionPointer++; st = &gameLocal.program.GetStatement( instructionPointer );
idInterpreter
制御を渡した後、idThread::Execute
ランタイムを必要とするスレッドがなくなるまで次のメソッドが呼び出されます。全体的なアーキテクチャは、Another World仮想マシンの図を強く思い出させました。興味深い事実:バイトコードは、アクティブな使用を目的としていないため、x86コマンドに変換されません。しかし、その結果、多くのことがスクリプトを介して行われるため、Doom3は、Quake3のように、JITからx86コマンドへの変換から大きな利益を得ます。