Quake Engine code review : Rendition (4/4)
Quakeレンダラは、最初の開発中に最も多くの作業をしたモジュールです。 これは、Michael Abrashの本とJohn Carmackの.planファイルによって広範囲に記述されています。
Architecture section
Network section
Prediction section
Rendition section (本記事)
Rendition
シーンのレンダリングプロセスはマップのBSPを中心に行われていきます。 Wikipediaのバイナリ空間分割を紹介しておきます。 一言で言えば、Quakeマップはすごく前処理されています。 ボリュームは次の図のように再帰的にスライスされます。
このプロセスでは、葉のようなBSPが生成されます(ルールは、既存のポリゴンを分割プランとして選択し、より少ないポリゴンを分割するスプリッタを選択することです)。 BSPが生成された後、各リーフについて、PVS(Potentially Visible Set)が計算される。 例として、leaf4は潜在的にleaf7と9を見ることができます:
このリーフの結果として得られるPVSは、ビットベクトルとして格納されます。
Leaf Id | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
PVS for leaf 4 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
これにより、1996年のPCではグローバルなPVSサイズが約5Mbになりました。したがって、PVSはデルタ長圧縮によって圧縮されます。
Compressed PVS for leaf 4 | 3 | 2 | 1 | 7 |
---|
The Run-length encoded PVSは、1の間の0の数のみを含む。 非常に効率的な圧縮技術のようには見えないかもしれませんが、非常に限られた目に見えるleafと組み合わせた高いleaf数(32767)は、PVS全体のサイズを20kBに下げます。
Pre-processing in action
あらかじめ計算されたBPSとPVSが装備されているため、エンジンのMap作成ルーチンは単純に次のようになりました。
- カメラがどのLeafに位置しているかを判断するには、BSPをトラバースします。
- このLeafのPVSを取得して解凍し、PVSを繰り返し、BSPにLeafをマークします。
- BSPを遠くに横切る
- ノードがマークされていない場合は、それをスキップします。
- Camera Frustrumに対してNode Boundary Boxをテストします。
- 現在のLeafをrendition listに追加する
注:BSPは複数回使用されます。例:アクティブなライトごとにマップを遠くに歩き、マップのポリゴンにタグを付けます。
注2:ソフトウェア演出では、BSPは遠くまで歩いている。
Code analysis
The rendition codeは以下のように要約できます:
SCR_UpdateScreen
{
GL_BeginRendering
SCR_SetUpToDrawConsole
V_RenderView
| R_Clear
| R_RenderScene
| | R_SetupFrame
| | Mod_PointInLeaf
| | R_SetFrustum
| | R_SetupGL
| | R_MarkLeaves
| | | Mod_LeafPVS
| | | Mod_DecompressVis
| | R_DrawWorld
| | | R_RecursiveWorldNode
| | | DrawTextureChains
| | | | R_RenderBrushPoly
| | | | DrawGLPoly
| | | R_BlendLightmaps
| | S_ExtraUpdate
| | R_DrawEntitiesOnList
| | GL_DisableMultitexture
| | R_RenderDlights
| | R_DrawParticles
| R_DrawViewModel
| R_DrawAliasModel
| R_DrawWaterSurfaces
| R_PolyBlend
GL_Set2D
SCR_TileClear
V_UpdatePalette
GL_EndRendering
}
SCR_UpdateScreen
Calls:
- GL_BeginRendering(後でR_SetupGLで使用される変数(glx、gly、glwidth、glheight)を設定してviewPortと投影行列を設定する)
- SCR_SetUpToDrawConsole(コンソールの高さを決める:これはなぜ2Dの部分ではないのですか?)
- V_RenderView(レンダー3Dシーン)
- GL_Set2D(オルソ投影(2D)に切り替える)
- SCR_TileClear
- オプションで2Dのもの、コンソール、FPSメトリックなどをたくさん描画します。
- V_UpdatePalette(名前はソフトウェアレンダラーに合わせています。これは、OpenGLの場合、受信したダメージやアクティブボーナスなどに応じてブレンディングモードを設定します。 値はv_blendに格納されます。
- GL_EndRendering(バッファをスワップ(ダブルバッファリング)!!)
V_RenderView
Calls:
- V_CalcRefdef(申し訳ありませんが何なのかわかりませんでした)
- R_PushDlights効果があるすべてのライトがあるポリゴンにマークを付けます(注意を参照)
- R_RenderView
注意:
R_PushDlightsは再帰的メソッド(R_MarkLights)を呼び出します。 BSPはライトの影響を受けるポリゴンを(intビットベクトルを使用して)マークするためにBSPを使用し、BSPは遠くまで(ライトのPOVから)動作しています。 このメソッドは、ライトがアクティブで、範囲内にあるかどうかをチェックします。 R_MarkLightsメソッドは、マイケル・アブラシュがdirect application of distance point-planの記事"Frames of Reference"(dist = DotProduct(light-> origin、splitplane-> normal) - splitplane-> dist;)を直接適用しているので、特に注目に値する。
R_RenderView
Calls:
- R_Clear(必要なものだけをクリアするGL_COLOR_BUFFER_BITやGL_DEPTH_BUFFER_BIT)
- R_RenderScene
- R_DrawViewModel(レンダリングプレーヤーモデルはspectatorモードです)
- R_DrawWaterSurfaces(水を描画するにはGL_BEND / GL_MODULATEモードに切り替え、ワーピングはgl_warp.cのsinとcosのルックアップテーブルで行います)
- R_PolyBlend(V_UpdatePaletteに設定された値をv_blend経由で画面全体にブレンドします。これは、ダメージ(赤色)、水中またはボーナスブースト効果のときに表示されます)
R_RenderScene
Calls:
- R_SetupFrame(カメラがあるBSPリーフを取得し、変数 "r_viewleaf"に格納)
- R_SetFrustum(mplane_t frustum [4]を設定します。
- R_SetupGL(GL_PROJECTION、GL_MODELVIEWの設定、ビューポートとglCullFace側、Y軸とZ軸をQuake z軸として回転させ、x軸をOpenGLで切り替える)
- R_MarkLeaves
- R_DrawWorld
- S_ExtraUpdate(マウスの位置をリセットし、サウンドの問題を処理する)
- R_DrawEntitiesOnList(自己説明)
- GL_DisableMultitexture(Idem)
- R_RenderDlights(ライトバブル、ライトエフェクト)
- R_DrawParticles(爆発、火、静的など)
R_SetupFrame
次の行に注目してください。
r_viewleaf = Mod_PointInLeaf(r_origin、cl.worldmodel);
これは、Quakeエンジンがカメラが現在BSPに配置されているleaf/ノードを取得する場所です。
Mod_PointInLeafはmodel.cにあり、BSPを実行します(BSPルートはmodel-> nodesにあります)。
すべてのノードについて
- ノードがスペースをさらに分割していない場合は、leafであり、したがって現在のノード位置として返されます。
- そうでなければ、BSP分割平面が現在の位置に対してテストされ(単純なドットプロダクトを介してBSPツリーが訪問される通常の方法です)、一致する子が訪問されます。
R_MarkLeaves
BSP(R_SetupFrameで取得)、Lookup(Mod_LeafPVS)、およびPotential Visible Set(PVS。)の解凍(Mod_DecompressVis)でカメラ位置を保持する変数r_viewleaf
次に、ビットベクトルを反復し、BSPのPotentialy可視ノードに:node-> visframe = r_visframecountをマークします。
R_DrawWorld
Calls:
- R_RecursiveWorldNode(BSPの世界を先頭に戻し、先にマークされていないノード(R_MarkLeaves経由)をスキップし、cl.worldmodel-> textures [] - > texturechainリストに適切なポリゴンを設定します)。
- DrawTextureChains(テクスチャチェーンに格納されたポリゴンのリストを描画する:cl.worldmodel-> textures []を繰り返します。この方法では、1つのマテリアルにつき1つのスイッチしかありません。
- R_BlendLightmaps(フレームバッファ内のライトマップをブレンドするために使用される2番目のパス)
注意:
この部分は、おそらく "アートの始まり"と考えられていた時点で、OpenGLの悪名高い "即時モード"を使用します。
R_RecursiveWorldNodeは、サーフェスカリングのほとんどが実行されている場所です。 次の場合、ノードは破棄されます。
- 内容はSolidです。
- LeafはPVS(ノード - > visframe!= r_visframecount)でマークされていませんでした。
- leafはfrustrumクリッピングに失敗します。
MDL format
MDLフォーマットは固定フレームのセットです。Quakeエンジンは頂点の位置を補間してアニメーションを滑らかにしません(フレームレートを上げてもアニメーションが良く見えるわけではありません)。
Some elegant things
エレガントなリーフマーキング
レンダリングされるBSPのリーフをマークする単純なアプローチは、boolean isMarkedVisibleを各フレームの前に使用することです。
- booleanをすべてfalseに設定します。
- PVSを繰り返し、可視のleafをtrueに設定します。
- その後、if(leave.isMarkedVisible)を指定してテストを終了します。
この代わりに、Quakeエンジンはフレームレンダリングされた(r_visframecount変数)の数をカウントするために整数を使用します。 これにより、ステップ1を完全にスキップすることができます。
- PVSを繰り返し、目に見えるleafを設定します。leafframe = r_visframecount
- その後、if(leaf.visframe == r_visframecount)を指定してテストを終了します。
再帰回避
BSPを捜査し、現在の位置を取得するために、素早くダーティな再帰を行うのではなく、R_SetupFrameにあります:whileループが使用されています。
node = model->nodes;
while (1)
{
if (node->contents < 0)
return (mleaf_t *)node;
plane = node->plane;
d = DotProduct (p,plane->normal) - plane->dist;
if (d > 0)
node = node->children[0];
else
node = node->children[1];
}
Minimize texture switchs
OpenGLでは、(glBindTexture(GL_TEXTURE_2D、id))経由のテクスチャのスイッチが非常にコストがかかります。 スイッチ番号を最小にするために、rendition用にマークされたすべてのポリゴンは、ポリゴンのテクスチャマテリアルにインデックスされた配列チェーンに格納されます。
cl.worldmodel->テクスチャ[textureId] - >テクスチャチェーン[]
カリングが行われると、テクスチャチェーンが順番に描画されます。このようにN個のテクスチャスイッチがあり、Nはテクスチャの総数です。
int i;
for ( i = 0; i < cl.worldmodel->textures_num ; i ++)
DrawTextureChains(i);