震源コード分析

画像



私は喜んでQuake Worldのソースコードの研究に没頭し、私が理解したすべてを記事で概説しました。 これが整理したい人に役立つことを願っています。 この記事は4つのパートに分かれています。





建築



Quake Client



Quakeの学習は、 qwcl



(クライアント)プロジェクトから始める価値があります。 WinMain



エントリWinMain



sys_win.cにあります。 要するに、コードは次のようになります。



  WinMain { while (1) { newtime = Sys_DoubleTime (); time = newtime - oldtime; Host_Frame (time) { setjmp Sys_SendKeyEvents IN_Commands Cbuf_Execute /*  */ CL_ReadPackets CL_SendCmd /* // */ CL_SetUpPlayerPrediction(false) CL_PredictMove CL_SetUpPlayerPrediction(true) CL_EmitEntities /*  */ SCR_UpdateScreen } oldtime = newtime; } }
      
      





ここでは、Quake Worldの3つの主要な要素を強調できます。





ネットワーク層(ネットチャンネルとも呼ばれます)は、 frames



情報( frame_t



配列)にワールド情報を出力します。 これらは予測レイヤーに転送され、 cl_visedicts



衝突が処理され、データは可視性領域(POV)の定義とともに可視性表示( cl_visedicts



)の形式で表示されます。 VisEdictは、POV変数( cl.sim*



)とともに視覚化レイヤーで使用され、シーンをレンダリングします。







setjmp







コードに中間点を設定して、何か悪いことが起こった場合、プログラムはここに戻ります。



Sys_SendKeyEvents







Windows OSメッセージの受信、ウィンドウの最小化など。 エンジン変数の対応する更新(たとえば、ウィンドウが最小化されている場合、ワールドはレンダリングされません)。



IN_Commands







ジョイスティックのエントリに関する情報を取得します。



Cbuf_Execute







ゲームの各サイクルで、コマンドはバッファーで実行されます。 コマンドは主にコンソールを介して生成されますが、サーバーまたはキーストロークからも生成できます。



ゲームは、コマンドバッファーのexec quake.rc



から開始します。



CL_ReadPackets



およびCL_SendCmd







エンジンのネットワーク部分の処理。



CL_SendCmd



は、マウスとキーボードの入力をインターセプトし、コマンドを生成して送信します。



Quake WorldはUDPを使用していたため、netChannelパケットヘッダーに設定されたシーケンス/ sequenceACKによって伝送の信頼性が保証されました。 さらに、最後のコマンドは体系的に再送されました。 クライアント側では、パッケージの転送に制限はなく、更新は可能な限り頻繁に送信されました。 サーバー側では、パケットが受信され、送信速度が処理速度よりも遅い場合にのみ、メッセージがクライアントに送信されました。 この制限はクライアントによって設定され、サーバーに送信されました。



「ネットワーク」セクション全体がこのトピック専用です。



CL_SetUpPlayerPrediction



CL_PredictMove



およびCL_EmitEntities







エンジンでの予測と衝突の計算を実行しました。 これらは主に、ネットワーク上の伝送遅延に対処するように設計されています。



このトピックは、「予測」セクション全体で説明されています。



SCR_UpdateScreen







エンジンでの視覚化 。 この部分では、BSP / PVSが積極的に使用されます。 ここでinclude



/ define



基づいてコードが分岐しdefine



。 Quakeエンジンは、プログラムで、またはハードウェアアクセラレーションで世界をレンダリングできます。



「視覚化」セクションはこれに完全に専念しています。



zipアーカイブを開いてコンパイルする



zipを開く:



q1sources.zipアーカイブには、 QW



WinQuake



という2つのフォルダー/ 2つのVisual StudioプロジェクトがありWinQuake









OpenGLレンダリングでQuake Worldを学びました。 このプロジェクトには4つのサブプロジェクトがあります。





コンパイル:



WindowsとDirectX SDKをインストールした後、Visual Studio 2008でのコンパイルで1つのエラーが検出されます。



 .\net_wins.c(178) : error C2072: '_errno' : initialization of a function
      
      





_errno



は現在、他の何かに使用されるMicrosoftマクロです。 これらのエラーを修正するには、変数名を_errno



置き換えます(例: qerrno







net_wins.c







  if (ret == -1) { int qerrno = WSAGetLastError(); if (qerrno == WSAEWOULDBLOCK) return false; if (qerrno == WSAEMSGSIZE) { Con_Printf ("Warning: Oversize packet from %s\n", NET_AdrToString (net_from)); return false; } Sys_Error ("NET_GetPacket: %s", strerror(qerrno)); }
      
      





リンカは、qwclプロジェクトのLIBC.libについて文句を言います。 無視されたライブラリ「Ignored Library」のリストに追加するだけで、4つのプロジェクトのアセンブリが完了します。



ツール



Visual Studio Express(フリーウェア)はIDEとして最適でした。 BSP / PVS、Id Software、およびQuakeに基づいてエンジンをよりよく理解したい場合は、いくつかの本を読むことをお勧めします。







Quake Source Code週間の本棚は次のようになりました。







ネットワーク



QuakeWorldのネットワークアーキテクチャは、かつて素晴らしい革新と見なされていました。 後続のすべてのネットワークゲームは同じアプローチを使用しました。



ネットワークスタック



Quakeの情報交換の基本単位は



でした。 それらは、位置、方向、健康、プレーヤーへのダメージなどを更新するために使用されます。 TCP / IPには、リアルタイムシミュレーション(送信制御、信頼性の高い配信、パケット順序の保存)に役立つ多くの優れた機能がありますが、このプロトコルはQuake Worldエンジンでは使用できません(元のQuakeで使用されていました)。 一人称シューティングゲームでは、時間通りに受信されなかった情報は再送する価値がありません。 したがって、UDP / IPが選択されました。 信頼性の高い配信を保証し、パケットの順序を維持するために、抽象化のネットワーク層「 NetChannel



」を作成しました。



OSIの観点から見ると、 NetChannel



UDPの上に便利に配置されています。







要約すると、エンジンは主に



機能し



。 データを送受信する必要がある場合、彼はこのタスクをNetchan_Transmit



およびNetchan_Process



netchan.c



(これらのメソッドはクライアントとサーバーで同じです)。



NetChannelヘッダー



NetChannelヘッダーの構造は次のとおりです。

ビットオフセット ビット0-15 16-31
0 シーケンス
32 ACKシーケンス
64 Qport チーム
94 ...


信頼できるメッセージ



信頼できないコマンドはUDPパケットにグループ化され、最後の発信シーケンス番号でマークされて送信されます。送信者にとって、失われるかどうかは関係ありません。 信頼できるコマンドの処理方法は異なります。 主なことは、送信者と受信者の間に未確認の信頼できるUDPパケットが1つしか存在できないことを理解することです。



各ゲームサイクルでは、新しい信頼できるチームを生成するときに、 message_buf



配列に追加されます( message



変数で制御されます)( 1 )。 信頼できるコマンドのセットは、 message



からreliable_buf



2 )配列に移動されmessage



。 これは、 reliable_buf



空の場合にのみ発生します(空でない場合、別のコマンドセットが以前に送信され、受信がまだ確認されていないことを意味します)。



次に、最終的なUDPパケットが形成されます。NetChannel( 3 )ヘッダーが追加され、次にtrusted_bufの内容と現在のreliable_buf



コマンド(十分なスペースがある場合)が追加されます。



受信側では、UDPメッセージが解析され、着信sequence



番号が発信sequence ACK



4 )に送信されsequence ACK



(パケットに信頼できるデータが含まれていることを示すビットフラグと共に)。



次に受け取るメッセージ:









トランスミッション制御



私が理解しているように、送信制御はサーバー側でのみ実行されます。 クライアントは、可能な限り頻繁にステータスの更新を送信します。



サーバー上でのみアクティブな伝送制御の最初のルール:パケットがクライアントから受信された場合にのみパケットを送信します。 2番目のタイプの伝送制御は「チョーク」です。これは、クライアントがコンソールコマンドrate



設定するパラメーターです。 サーバーは更新メッセージをスキップして、クライアントに送信されるデータの量を削減できます。



重要なコマンド



コマンドには、



に格納されたタイプコードと、それに続く有用なコマンド情報が含まれます。 おそらく最も重要なのは、ゲームの状態に関する情報を提供するコマンド( frame_t



)です。





qportの詳細を読む



エラーを修正するために、QportがNetChannelヘッダーに追加されました。 qportの前に、Quakeサーバーは「リモートIPアドレス、リモートUDPポート」の組み合わせを使用してクライアントを識別しました。 ほとんどの場合、これはうまく機能しましたが、一部のNATルーターはポート変換スキーム(リモートUDPポート)を任意に変更できます。 UDPポートは信頼できなくなり、John Carmackは、「リモートIPアドレス、NetChannelヘッダーのQport」によってクライアントを識別することにしたと説明しました。 これによりエラーが修正され、サーバーは宛先UDP応答ポートを即座に変更できました。



遅延計算



Quakeエンジンは、 frame_t



とともに、最近送信された64個のコマンド( frame_t



frames



配列)をsenttime



ます。 それらは、送信に使用されたシーケンス番号( outgoing_sequence



)によって直接アクセスできます。



  frame = &cl.frames[cls.netchan.outgoing_sequence & UPDATE_MASK]; frame->senttime = realtime; //  
      
      





サーバーから確認を受け取った後、コマンドを送信する時間はsequenceACK



から取得されsequenceACK



。 遅延は次のように計算されます。



  //    frame = &cl.frames[cls.netchan.incoming_acknowledged & UPDATE_MASK]; frame->receivedtime = realtime; latency = frame->receivedtime - frame->senttime;
      
      





エレガントなソリューション



配列インデックスのループ

エンジンのネットワーク部分には、最後に受信した64個のUDPパケットが格納されます。 配列をループする単純な解決策は、整数除算の剰余演算子を使用することです。



 arrayIndex = (oldArrayIndex+1) % 64;
      
      





代わりに、UPDATE_MASKのバイナリAND演算を使用して新しい値が計算されます。 UPDATE_MASKは64-1です。



 arrayIndex = (oldArrayIndex+1) & UPDATE_MASK;
      
      





実際のコードは次のようになります。



  frame_t *newpacket; newpacket = &frames[cls.netchan.incoming_sequence&UPDATE_MASK];
      
      





更新:これは、剰余除算操作の最適化に関してディートリッヒEppから受け取ったコメントです。



     ,        "".          :   file.c: unsigned int modulo(unsigned int x) { return x % 64; } unsigned int and(unsigned int x) { return x & 63; }  gcc -S file.c      file.s. ,    ,    !     ""    << 5  *32.      ,    ,   ,     << 5  & 63 "",    *32  %64  . --Dietrich .globl modulo .type modulo, @function modulo: pushl %ebp movl %esp, %ebp movl 8(%ebp), %eax andl $63, %eax popl %ebp ret .size modulo, .-modulo .globl and .type and, @function and: pushl %ebp movl %esp, %ebp movl 8(%ebp), %eax andl $63, %eax popl %ebp ret .size and, .-and
      
      





予測



ネットワーク通信のためのNetChannel抽象化を見ました。 次に、遅延が予測によってどのように相殺されるかを調べます。 学習する資料は次のとおりです。





予測



予測はおそらく、Quake Worldエンジンの中で最も難しく、文書化されておらず、最も重要な部分です。 予測の目的は、待ち時間を無効にすること、つまり、情報を送信するために媒体が必要とする遅延を補償することです。 予測はクライアント側で実行されます。 このプロセスは「クライアント側予測」と呼ばれます。 サーバー側では、遅延補正手法は適用されません。



問題:







ご覧のとおり、ゲームの状態はレイテンシの半分の値だけ「古い」状態です。 チームを送信する時間を追加する場合、アクションの結果を確認するためにフルサイクル(待機時間)を待つ必要があります。







Quakeの予測システムを理解するには、NetChannelがframes



変数(array frame_t



)をどのように取り込むかを理解する必要があります。







サーバーに送信される各コマンドは、 netchannel.outgoingsequence



インデックスでsenttime



とともにframes



に保存されます。



サーバーがsequenceACK



を使用してコマンドの受信を確認すると、送信されたコマンドを受け入れて待機時間を計算できます。



 latency = senttime-receivedtime;
      
      





この段階では、 遅延/ 2秒前の世界がわかります。 NATでは、待ち時間は非常に短い(<50ミリ秒)が、インターネットでは非常に大きく(> 200ミリ秒)、 現在の世界の状態をシミュレートするには予測が必要です。 このプロセスは、ローカルプレーヤーと他のプレーヤーで異なる方法で実行されます。



ローカルプレイヤー



ローカルプレーヤーの場合、サーバーの状態を推定するため、レイテンシはほぼ0に減少します。 これは、サーバーから受信した最後のステータスを使用して、その瞬間から送信されたすべてのコマンドを「再生」します。







したがって、クライアントは、サーバー上のその位置が時間t +遅延/ 2になると予測します。



コードの観点から、これはCL_PredictMove



メソッドを使用して行われます。 まず、Quakeエンジンは「再生可能な」コマンドのセンチメント制限を選択します。



 cl.time = realtime - cls.latency - cl_pushlatency.value*0.001;
      
      





注: cl_pushlatency



は、値がクライアント側で設定されるコンソール変数(cvar)です。 これは、ミリ秒単位のクライアントの負の遅延に相当します。 これから、 cl.time = realtime



と結論付けるのは簡単です。



その後、他のすべてのプレーヤーはCL_SetSolidPlayers (cl.playernum);



定義されCL_SetSolidPlayers (cl.playernum);



(衝突をテストできるようにするため)ソリッドオブジェクトとして、最後に受信した状態からその瞬間に送信された「再生」コマンド: cl.time <= to->senttime



(衝突はCL_PredictUsercmd



を使用して各反復でテストされます)。



他のプレイヤー



他のプレイヤーの場合、Quakeエンジンには「送信済みですが、まだ確認されていないチーム」がないため、代わりに補間が使用されます。 最後の既知の位置から開始して、 cmd



結果の位置を予測するために補間されます。 角回転なしで位置のみが予測されます。



Quake Worldでは、他のプレイヤーのレイテンシも考慮されます。 各プレイヤーのレイテンシーは、ワールドの更新とともに送信されます。



コード



衝突予測および計算コードは、次のように要約できます。



  CL_SetUpPlayerPrediction(false) CL_PredictMove | /*    */ | CL_SetSolidPlayers | | CL_PredictUsercmd | | PlayerMove |   CL_SetUpPlayerPrediction(true) CL_EmitEntities CL_LinkPlayers | /*    */ |    | | CL_SetSolidPlayers | | CL_PredictUsercmd | | PlayerMove CL_LinkPacketEntities CL_LinkProjectiles CL_UpdateTEnts
      
      





Quake Worldはプレイヤーの予測を実行するだけでなく、予測に基づいて衝突を認識するため、この部分は複雑です。



CL_SetUpPlayerPrediction(false)







最初の呼び出しは予測を実行せず、サーバーから受信した位置にプレイヤーを配置するだけです(つまり、tレイテンシ/ 2の遅延で)。



CL_PredictMove()







これは、ローカルプレーヤーが移動する場所です。





位置と速度の更新の詳細:





CL_SetUpPlayerPrediction(true)







2番目のサーバー側の呼び出しでは、現在の時点での他のプレーヤーの位置が予測されます(ただし、移動はまだ実行されていません)。 位置は、最後の既知のチームと最後の既知の位置に基づいて推定されます。



注:ここで小さな問題が発生します。Valveは( cl_pushlatency



)サーバー側のローカルプレーヤーのステータスをt +レイテンシ/ 2で予測することを推奨します。 ただし、他のプレーヤーの位置は、時間tでサーバー側で予測されます。 おそらく、QWのcl_pushlatency



の最適な値は-latency / 2でしたか?



CL_EmitEntities







可視性ガイドラインはここで生成されます。 次に、レンダラーに渡されます。





可視化



オリジナルのゲームを開発するとき、ほとんどの努力はQuakeレンダラーモジュールに費やされました。 これについては、Michael Abrashの本とJohn Carmackの.planファイルに詳しく説明されています。



可視化



シーンの視覚化プロセスは、BSPカードと密接にリンクしています。 ウィキペディアのバイナリスペースパーティション分割について詳しく読むことをお勧めします。 要するに、Quakeカードは多くの前処理を経ました。 ボリュームは次のように再帰的にカットされました。







このプロセスにより、リーフを含むBSPが作成されました(作成ルールは次のとおりです。既存のポリゴンを切断面として選択し、より少ないポリゴンを切断するセパレータを選択します)。 BSPを作成した後、PVS(潜在的に可視のセット、潜在的に可視のセット)が各シートに対して計算されました。 例:シート4は、リーフ7および9を潜在的に見ることができます。







このシートの最終PVSはビットベクトルとして保存されました。



Id 葉っぱ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
シート4のPVS 0 0 0 1 0 0 1 0 1 0 0 0 0 0 0 0
結果は、サイズが約5MBのグローバルPVSです。 1996年のPCには大きすぎました。 したがって、PVSは長さの差の圧縮によって圧縮されました。



シート4の圧縮PVS 3 2 1 7


エンコードされたPVSには、ユニット間のゼロの数のみが含まれていました。 これは非常に効果的な圧縮技術のようには見えませんが、多数の葉(32767)と非常に限られた可視の葉のセットを組み合わせることで、PVS全体のサイズが20KBに縮小されました。



アクションの前処理



事前に計算されたBPSおよびPVSが存在するため、エンジンでマップを視覚化するプロセスは簡単でした。





注: BSPは複数回使用されます。たとえば、マップを最も近いポイントから各アクティブな光源の距離にバイパスし、マップ上のポリゴンにマークを付ける場合。



注2:ソフトウェアレンダリングでは、BSPツリーは遠いポイントから最も近いポイントまでトラバースされました。



コード分​​析



つまり、視覚化コードは次のように表すことができます。

 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



呼び出し:



  1. GL_BeginRendering



    glx,gly,glwidth,glheight



    後でR_SetupGL



    ビューポートと投影行列を設定するために使用される変数()の値を設定します
  2. SCR_SetUpToDrawConsole



    (コンソールの高さを決定します:2Dに関連する部分ではなく、なぜここにあるのですか?!)
  3. V_RenderView



    (3Dシーンレンダリング)
  4. GL_Set2D



    (正射影への切り替え(2D))
  5. SCR_TileClear



    (多くの2Dオブジェクト、コンソール、FPSメトリックなどの追加レンダリング)
  6. V_UpdatePalette



    (名前はソフトウェアレンダラーに対応します。openGLでは、メソッドは受け取ったダメージまたはアクティブなボーナスに応じてミキシングモードを設定し、画面を赤くしたり、明るくしたりします。)値はに格納されますv_blend



  7. GL_EndRendering



    (バッファスイッチング(ダブルバッファリング)!)


V_RenderView

呼び出し:



  1. V_CalcRefdef



    (すみません、私はこの部分を理解していませんでした)
  2. R_PushDlights



    各光源でポリゴンをマークして、効果を適用します(注を参照)
  3. R_RenderView





注: R_PushDlightsは、再帰メソッド(R_MarkLights



)を呼び出しますBSPを使用して、光源の影響を受けるポリゴンを(整数ビットベクトルを使用して)マークします。BSPは(光源の観点から)近い点から遠い点に移動します。このメソッドは、光源がアクティブで手の届く範囲にあるかどうかを確認します。この方法はR_MarkLights



特に注目に値します。なぜなら、ここではポイントとプレーン間の距離に関するMichael Abrashによる記事の直接的な実装が見られるからです(「参照フレーム」dist = DotProduct (light->origin, splitplane->normal) - splitplane->dist;



))。



R_RenderViewの



呼び出し:



  1. R_Clear



    (必要であればGL_COLOR_BUFFER_BITおよび/またはGL_DEPTH_BUFFER_BITのクリーニング)
  2. R_RenderScene



  3. R_DrawViewModel



    (オブザーバーモードでのプレーヤーモデルのレンダリング)
  4. R_DrawWaterSurfaces



    (GL_BEND / GL_MODULATEモードに切り替えて水を引く。変形は、ルックアップテーブルsinおよびcosを使用して実行されますgl_warp.c



  5. R_PolyBlend



    V_UpdatePalette



    変数に設定された値を使用して画面全体を混合しますv_blend



    。これは、ダメージを受ける(赤)、水中にいる、またはボーナスを適用することを示すために使用されます


R_RenderScene



呼び出し:

  1. R_SetupFrame



    (カメラが配置されているBSPシートを抽出し、変数「r_viewleaf」に保存します)
  2. R_SetFrustum



    (インストール mplane_t[4]



    。近くと遠い飛行機なし。
  3. R_SetupGL



    (GL_PROJECTION、GL_MODELVIEW、glCullFaceのビューポートと側面の設定、およびY軸とZ軸の回転。QuakeのX軸とZ軸はopenGLとは異なる位置にあるため。)
  4. R_MarkLeaves



  5. R_DrawWorld



  6. S_ExtraUpdate



    (マウスの位置をリセットし、オーディオの問題を解決します)
  7. R_DrawEntitiesOnList



    (リスト内のオブジェクトの描画)
  8. GL_DisableMultitexture



    (マルチテクスチャリングを無効にする)
  9. R_RenderDlights



    (ライトドメインと照明効果)
  10. R_DrawParticles



    (爆発、火、電気など)


R_SetupFrame



興味深い行:



 r_viewleaf = Mod_PointInLeaf (r_origin, cl.worldmodel);
      
      





その中で、Quakeエンジンは、カメラが現在指しているBSPのシート/ノードを取得します。



Mod_PointInLeafはmodel.cにあり、BSPを介して実行されます(BSPツリーのルートはmodel->ノードにあります)。



各ノードに対して:





R_MarkLeaves BSPのカメラの場所を



変数r_viewleaf



(に取得R_SetupFrame



)に格納し、潜在的な可視セット(PVS)を検索(Mod_LeafPVS



)、および解凍(Mod_DecompressVis



)します。次に、ビットベクトルを反復処理し、潜在的に表示されるBSPノードをマークします:node-> visframe = r_visframecount。



R_DrawWorldの



課題:



  1. R_RecursiveWorldNode



    (BSPワールドを前から後ろに走査し、以前にマークされていないノードをスキップし(c R_MarkLeaves



    )、cl.worldmodel->textures[]->texturechain



    適切なポリゴンでリストに入力します。)
  2. DrawTextureChains



    (texturechainに保存されているポリゴンのリストを描画する:cl.worldmodel-> textures []を反復処理します。これにより、マテリアルへのスイッチを1つだけ取得できます。悪くはありません。)
  3. R_BlendLightmaps



    (フレームバッファーでライトマップを混合するために使用される2番目のパス)。


注:



この部分では、悪名高いopenGLの「イミディエイトモード」モードが使用されます。このモードは「技術の最後の言葉」と見なされていました。



R_RecursiveWorldNode



クリッピング表面のほとんどの操作が実行されます。次の場合、ノードは切断されます。





画像



MDL形式



MDL形式は、固定フレームのセットです。Quakeエンジンは、頂点の位置を補間してアニメーションを滑らかにしません(したがって、フレームレートが高くてもアニメーションは改善されません)。



エレガントなソリューション



エレガントなリーフ



タギングレンダリングのためのBSP リーフタギングの素朴なアプローチは、ブール変数を使用することですisMarkedVisible



各フレームの前に必要なもの:



  1. すべてのブール変数の値をfalseに設定します。
  2. PVSを繰り返しバイパスし、表示されている各シートにtrueを指定します。
  3. 次に、シートをテストします if (leave.isMarkedVisible)





代わりに、Quakeエンジンは整数を使用して、レンダリングされたフレーム(r_visframecount



変数)の数を計算します。これにより、最初のステップを取り除くことができます。



  1. 反復PVSトラバーサルおよび可視シートセットごと leaf.visframe = r_visframecount



  2. 次に、シートをテストします if (leaf.visframe == r_visframecount)





再帰を取り除く



にはR_SetupFrame



代わりにBSPのための「間に合わせ」トラバーサル再帰を実行しながらサイクルを利用して現在位置を取得します。



  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]; }
      
      





テクスチャの切り替えの最小化



openGLでは、(glBindTexture(GL_TEXTURE_2D,id)



)を使用したテクスチャの切り替えは非常に高価です。テクスチャの切り替え回数を最小限に抑えるため、レンダリング用にマークされた各ポリゴンは、ポリゴンテクスチャマテリアルによってインデックス付けされた配列のチェーンに格納されます。



 cl.worldmodel->textures[textureId]->texturechain[]
      
      





クリッピングが完了すると、テクスチャチェーンが順番に描画されます。したがって、合計N個のテクスチャスイッチが実行されます。ここで、Nは可視テクスチャの総数です。



  int i; for ( i = 0; i < cl.worldmodel->textures_num ; i ++) DrawTextureChains(i);
      
      






All Articles