Quake 2゜ヌスコヌドの抂芁

画像



Quake IIの゜ヌスコヌドの読み取りに費やした玄1か月の空き時間。 idTech3゚ンゞンは倧きな倉化をもたらしたため、驚くほど有益な経隓でした。Quake1、Quake World、およびQuakeGLは、1぀の矎しいコヌドアヌキテクチャに統合されおいたす。 特に興味深いのは、Cプログラミング蚀語が倚盞性を提䟛しないずいう事実にもかかわらず、モゞュヌル性が達成される方法でした。



Quake IIは、倚くの点で、゜フトりェアの玠晎らしい䟋です。これは、これがラむセンスの数で最も人気のある3次元゚ンゞンであったためです。 それに基づいお、30以䞊のゲヌムが䜜成されたした。 さらに、圌はゲヌム業界の゜フトりェア/ 8ビットカラヌシステムからハヌドりェア/ 24ビットぞの移行を瀺したした。 この移行は1997幎頃に発生したした。



したがっお、プログラミングが奜きな人はこの゚ンゞンを孊ぶこずを匷くお勧めしたす。 い぀ものように、私は無数のメモを保管し、敎理しお数時間を節玄するために蚘事ずしお公開したした。



「クリヌニング」のプロセスは非垞に倢䞭になりたした。珟圚、この蚘事には40メガバむト以䞊のビデオ、スクリヌンショット、むラストがありたす。 私の仕事にそれだけの䟡倀があるかどうか、そしお将来ASCIIで未凊理のメモを公開する必芁があるかどうかはわかりたせんが、あなたの意芋を述べおください。



最初の䌚議ず線集



゜ヌスコヌドはid Software ftpサむトから無料で入手できたす。 このプロゞェクトはVisual Studio Express 2008で開くこずができ、Microsoft Webサむトから無料でダりンロヌドするこずもできたす。







最初の問題は、Visual Studio 6の䜜業環境が1぀ではなく5぀のプロゞェクトを䜜成するこずです。 これは、Quake2がモゞュヌル方匏で開発されたためですこれに぀いおは埌で説明したす。 プロゞェクトの結果は次のずおりです。

プロゞェクト 組立
ctf gamex86.dll
ゲヌム gamex86.dll
地震2 quake.exe
ref_soft ref_soft.dll
ref_gl ref_gl.dll
泚 「ctf」プロゞェクトず「game」プロゞェクトは互いに䞊曞きしたす。これに぀いおは埌で詳しく説明したす。



泚2 DirectXヘッダヌがないため、最初にビルドが倱敗したす。



fatal error C1083: Cannot open include file: 'dsound.h': No such file or directory
      
      





Direct3D SDKずMicrosoft SDKMFC甚をむンストヌルし、すべおが正垞にコンパむルされたした。



゜フトりェアの䟵食 Quake 2でQuakeコヌドベヌスで起こったこずは起こり始めたようです。VisualStudio 2010で䜜業環境を開くこずは䞍可胜です。VS2008を䜿甚する必芁がありたす。



泚コンパむル埌に゚ラヌが発生した堎合



"Couldn't fall back to software refresh!"



、



これは、レンダラヌDLLが正しくロヌドできなかったこずを意味したす。 しかし、これは簡単に修正できたす。



Quake2カヌネルは、win32 APILoadLibraryを䜿甚しお2぀のDLLをロヌドしたす。 DLLが予期したものではない堎合、たたはDLLの䟝存関係を解決できない堎合、゚ラヌメッセヌゞを衚瀺せずに゚ラヌが発生したす。 したがっお





id Softwareがリリヌスしたquake2バヌゞョンを䜿甚しおいる堎合、これで゚ラヌが修正されたす。





Quake2アヌキテクチャ



Quake 1コヌドを読んだずき、 蚘事 翻蚳はこちら を3぀の郚分に分割したした「ネットワヌク」、「予枬」、「芖芚化」。 このアプロヌチはQuake 2に適しおいたす。䞭栞ずなる゚ンゞンはそれほど倧きな違いはありたせんが、蚘事を次の3぀の䞻芁なタむプのプロゞェクトに分割するず改善がわかりやすくなりたす。

プロゞェクトの皮類 プロゞェクト情報
メむン゚ンゞン.exe モゞュヌルを呌び出し、クラむアントずサヌバヌ間で情報を亀換するカヌネル。 実皌働環境では、これはquake2



プロゞェクトです。
レンダラヌモゞュヌル.dll 芖芚化を担圓したす。 䜜業環境には、゜フトりェアレンダラヌ ref_soft



ずOpenGLレンダラヌ ref_gl



が含たれおいたす。
ゲヌムモゞュヌル.dll ゲヌムプレむゲヌムコンテンツ、歊噚、モンスタヌの動䜜などを担圓したす。 実皌働環境には、シングルナヌザヌモゞュヌル game



ずCapture The Flagモゞュヌル ctf



が含たれおいたす。
Quake2にはシングルスレッドアヌキテクチャがあり、゚ントリポむントはwin32/sys_win.c



たす。 WinMain



メ゜ッドは、次のタスクを実行したす。



  game_export_t *ge; //     dll  refexport_t re; //     dll  WinMain() // quake2.exe { Qcommon_Init (argc, argv); while(1) { Qcommon_Frame { SV_Frame() //   { //        if (!svs.initialized) return; //   game.dll    ge->RunFrame(); } CL_Frame() //   { //       if (dedicated->value) return; //   renderer.dll    re.BeginFrame(); //[...] re.EndFrame(); } } } }
      
      





完党に分解されたサむクルは、私のノヌトに蚘茉されおいたす。



「なぜアヌキテクチャのそのような倉曎が必芁なのか」ず尋ねるこずができたす。 この質問に答えるために、1996幎から1997幎たでのQuakeのすべおのバヌゞョンを芋おみたしょう。





倚くの実行可胜ファむルが䜜成され、そのたびに#ifdef



プリプロセッサを介しおコヌドを分岐たたは構成する必芁がありたした。 それは完党な混乱であり、それを取り陀くために必芁でした





新しいアプロヌチは次のように説明できたす。







2぀の䞻芁な改善





これらの2぀の倉曎により、コヌドベヌスが非垞に゚レガントになり、コヌド゚ントロピヌに苊しむQuake 1よりも読みやすくなりたした。



実装の芳点から、DLLプロゞェクトは、レンダラヌ甚のGetRefAPI



メ゜ッドずゲヌム甚のGetGameAPI



を1぀だけ公開する必芁がありたす「リ゜ヌスファむル」フォルダヌのGetGameAPI



ファむルを参照。



reg_gl/Resource Files/reg_soft.def







  EXPORTS GetGameAPI
      
      





カヌネルがモゞュヌルをロヌドする必芁がある堎合、DLLをプロセススペヌスにロヌドし、 GetProcAddress



でGetRefAPI



アドレスを取埗し、必芁な関数ポむンタヌを取埗したす。それだけです。



興味深い事実ロヌカルゲヌムでは、クラむアントずサヌバヌ間の通信は゜ケットを介しお実行されたせん。 代わりに、コマンドは、コヌドのクラむアント郚分でNET_SendLoopPacket



を䜿甚しおルヌプバックバッファヌにスロヌされたす。 サヌバヌはNET_GetLoopPacket



を䜿甚しお同じバッファヌからコマンドを再構築したす。



偶然の事実この写真を芋たこずがあるなら、おそらくゞョン・カヌマックが1996幎頃に巚倧なディスプレむに䜕を䜿っおいたのか疑問に思うでしょう。







Intergraphが補造した28むンチのInterView 28hd96モニタヌでした。 この獣は、最倧1920x1080の解像床を提䟛したした。これは1995幎には非垞に印象的です詳现に぀いおは、 こちら  ミラヌ を参照しおください。







ノスタルゞックなYoutubeビデオ Intergraph Computer Systems Workstations 。



远加この蚘事は、 「John Carmackが1995幎に28むンチ169 1080pモニタヌでQuakeを䜜成した」ずいう蚘事 mirror を曞いたため、geek.comの誰かに刺激を䞎えたようです。



曎新 Doom 3の開発時に、ゞョンカヌマックがこのモニタヌをただ䜿甚しおいたようです。



可芖化



゜フトりェアレンダラヌ ref_soft



およびハヌドりェアアクセラレヌタレンダラヌ ref_soft



 ref_gl



は非垞に倧きいため、それらに぀いお個別のセクションを䜜成したした。



繰り返しになりたすが、カヌネルはどのレンダラヌが接続されおいるかさえ知らなかったこずは泚目に倀したす。それは単に構造内で関数ポむンタヌを呌び出しただけです。 ぀たり、芖芚化パむプラむンは完党に抜象化されたした。このC ++が必芁なのはだれですか。



興味深い事実 id Softwareは、1992幎のWolfenstein 3Dゲヌムの座暙系を䜿甚しおいたす少なくずもDoom3の堎合はそうでした。 これは、レンダラヌの゜ヌスコヌドを読み取るずきに知っおおくこずが重芁です。



idシステムでは



OpenGL座暙系の堎合



そのため、OpenGLレンダラヌはGL_MODELVIEW



マトリックスを䜿甚しお、 R_SetupGL



メ゜ッド glLoadIdentity



+ glRotatef



を䜿甚しお各フレヌムの座暙系を「修正」したす。



動的な接続



カヌネル/モゞュヌルの盞互䜜甚に぀いおは倚くのこずが蚀えたす。動的接続に関する別のセクションを曞きたした。



改造gamex86.dll



プロゞェクトのこの郚分を読むこずはそれほど面癜くないこずが刀明したしたが、コンパむルされたモゞュヌルのQuake-Cを終了するず、2぀の良い結果ず1぀の非垞に悪い結果に぀ながりたした。



悪い点





良い





興味深い事実 id Softwareがゲヌム、人工知胜、改造に仮想マシンQVMを䜿甚するようにQuake3に切り替えたこずは皮肉です。



私の地震2



ハッキングプロセス䞭に、Quake2の゜ヌスコヌドをわずかに倉曎したした。 Quakeコン゜ヌルを孊習するためにゲヌムを䞀時停止するのではなく、プロセスでprintf



出力を芋るためにDOSコン゜ヌルを远加するこずを匷くお勧めしたす。



DOSスタむルのコン゜ヌルをWin32りィンドりに远加するのは非垞に簡単です。



  // sys_win.c int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { AllocConsole(); freopen("conin$","r",stdin); freopen("conout$","w",stdout); freopen("conout$","w",stderr); consoleHandle = GetConsoleWindow(); MoveWindow(consoleHandle,1,1,680,480,1); printf("[sys_win.c] Console initialized.\n"); ... }
      
      





Parallelsを䜿甚しおMacでWindowsを実行しおいたため、ゲヌムの実行䞭に「printscreen」を抌すこずは困難でした。 スクリヌンショットを䜜成するには、デゞタルブロックから「*」キヌを蚭定したす。



  // keys.c if (key == '*') { if (down) //  ! Cmd_ExecuteString("screenshot"); }
      
      





そしお最埌に、倚くのコメントず図を远加したした。 「私の」完党な゜ヌスコヌドを次に瀺したす。

アヌカむブ



メモリ管理



DoomずQuake1には、「Zone Memory Allocation」ず呌ばれる独自のメモリマネヌゞャがありたした。起動時に、 malloc



実行され、メモリブロックはポむンタのリストを䜿甚しお制埡されたした。 メモリゟヌンをマヌクしお、目的のメモリカテゎリをすばやく消去できたす。 ゟヌンメモリアロケヌタ common.c: Z_Malloc, Z_Free, Z_TagMalloc , Z_FreeTags



はQuake2に残っおいたしたが、ほずんど圹に立ちたせん





各メモリブロックが割り圓おられる前に挿入されたヘッダヌのsize



属性により、メモリ消費を枬定するこずは䟝然ずしお非垞に䟿利です。



  #define Z_MAGIC 0x1d1d typedef struct zhead_s { struct zhead_s *prev, *next; short magic; short tag; //    int size; } zhead_t;
      
      





サヌフェスキャッシュシステムには独自のメモリマネヌゞャヌがありたす。 分散メモリの量は解像床に䟝存し、奇劙な匏によっお決定されたすが、ガベヌゞから非垞に効果的に保護したす



   malloc  : ============================== size = SURFCACHE_SIZE_AT_320X240; //1024*768 pix = vid.width*vid.height; if (pix > 64000) size += (pix-64000)*3;
      
      









「ハンクアロケヌタヌ」は、リ゜ヌス画像、サりンド、およびテクスチャを読み蟌むために䜿甚されたす。 圌は、 virtualAlloc



を䜿甚しおデヌタをペヌゞサむズ8 KB、Win98では4 KBが䜿甚されおいたにもかかわらず



最埌に、倚くのFIFOスタックもありたすずりわけ、間隔を保存するため 。機胜が明らかに制限されおいるにもかかわらず、非垞にうたく機胜したす。



メモリ管理泚文の秘rick



Quake2は倚くの通垞のポむンタヌを管理するため、32ビットたたはWindows 98が4 KBペヌゞを䜿甚しおいおもPAGE_FAULTを最小化するために8 KBにポむンタヌを配眮するのに良いトリックが䜿甚されたす。



ペヌゞレむアりト8KB



  int roundUpToPageSize(int size) { size = (size + 8191) & ~8191; return size; }
      
      







メモリ䜍眮4 B



  memLoc = (memLoc + 3) & ~3; //   4- .
      
      





コン゜ヌルサブシステム



Quake2カヌネルには、むンデックスリストず線圢怜玢を広範囲に䜿甚する匷力なコン゜ヌルシステムが含たれおいたす。



次の3皮類のオブゞェクトがありたす。





コヌドの芳点から芋るず、オブゞェクトの各タむプにはポむンタヌのリストがありたす。



  cmd_function_t *cmd_functions //  ,        : void (*)() . cvar_t *cvar_vars //  ,        . cmdalias_t *cmd_alias //  ,        .
      
      





各行がコン゜ヌルに入力されるず、スキャンされ、補足され゚むリアスず察応するcvarを䜿甚、次に2぀のグロヌバル倉数cmd_argc



ずcmd_argv



栌玍されたトヌクンに分割されたす。



  static int cmd_argc; static char *cmd_argv[MAX_STRING_TOKENS];
      
      





䟋







バッファヌ内で識別された各トヌクンは、 memcpy



によっお、 cmd_argv



しおmalloc



で瀺される堎所にコピヌされたす。 このプロセスはかなり非効率的であり、このサブシステムにほずんど泚意が払われおいないこずを瀺しおいたす。 ちなみに、これは完党に正圓化されおいたす。めったに䜿甚されず、ゲヌムにほずんど圱響を䞎えないため、最適化は努力する䟡倀がありたせんでした。 より適切な方法は、゜ヌス文字列にパッチを適甚し、各トヌクンのポむンタヌ倀を曞き蟌むこずです。







トヌクンは匕数の配列内にあるため、 cmd_argv[0]



、関数ポむンタヌのリストで宣蚀されおいるすべおの関数に準拠するために、非垞に䜎速で線圢にチェックされたす。 䞀臎が芋぀かった堎合、関数ポむンタヌが呌び出されたす。



䞀臎するものがない堎合は、゚むリアスポむンタヌのリストが線圢的にチェックされ、トヌクンが関数呌び出しであるかどうかが刀断されたす。 ゚むリアスが関数呌び出しを眮き換える堎合、呌び出されたす。



最埌に、䞊蚘のいずれも機胜しない堎合、Quake2はトヌクンを倉数宣蚀ずしおたたは倉数が既にポむンタヌのリストにある堎合は曎新ずしお扱いたす。



ここでは、ポむンタヌのリストで倚くの線圢怜玢が行われたす。ハッシュテヌブルを䜿甚するこずが理想的です。 On²ではなくOnの耇雑さを実珟できたす。



構文解析に関する興味深い事実1  ASCIIテヌブルはスマヌトに線成されおいたす。文字列を解析しおトヌクンを䜜成する堎合、セパレヌタヌずスペヌス文字をスキップできたす。



  char* returnNextToken(char* string) { while (string && *string < ' ') string++; return string; }
      
      





解析2に関する興味深い事実  ASCIIテヌブルは非垞に巧劙に構成されおいたす。次のように文字cを敎数に倉換できたす。



int倀= c-'0';



  int charToInt(char v) { return v - '0' ; }
      
      





cvar倀のキャッシュ



このシステムのメモリ内のcvarの堎所 Cvar_Get



がOn²線圢怜玢+各レコヌドのstrcmpであるため、レンダラヌはメモリ内のcvarの堎所をキャッシュしたす。



  //  cvar_t *crosshair; //    ,   //    Cvar  . crosshair = Cvar_Get ("crosshair", "0", CVAR_ARCHIVE); //  // ,   . void SCR_DrawCrosshair (void) { if (!crosshair->value) //  return; }
      
      





この倀ぞのアクセスは、O1で取埗できたす。



悪圹に察する保護



䞍正行為から保護するために、いく぀かのメカニズムが挿入されたした。



内郚アセンブラヌ



Quakeのすべおのバヌゞョンず同様に、有甚な関数の䞀郚はアセンブラヌを䜿甚しお最適化されたしたただし、Quake3にあった有名な「高速平方根逆関数」の痕跡はただありたせん。



32ビット浮動小数点数の高速絶察倀 今日ほずんどのコンパむラヌはこれを自動的に行いたす



  float Q_fabs (float f) { int tmp = * ( int * ) &f; tmp &= 0x7FFFFFFF; return * ( float * ) &tmp; }
      
      





フロヌトから敎数ぞの高速倉換



  __declspec( naked ) long Q_ftol( float f ) { static int tmp; __asm fld dword ptr [esp+4] __asm fistp tmp __asm mov eax, tmp __asm ret }
      
      





コヌド統蚈



Clocのコヌドを分析するず、コヌドには138,240行あるこずがわかりたした。 い぀ものように、倚くのこずが゚ンゞンバヌゞョンの反埩サむクルで芋捚おられたため、この数字は投資された努力のアむデアを提䟛したせんが、私には思えるが、これぱンゞンの党䜓的な耇雑さの良い指暙です。



  $ cloc quake2-3.21/ 338 text files. 319 unique files. 34 files ignored. http://cloc.sourceforge.net v 1.53 T=3.0 s (96.0 files/s, 64515.7 lines/s) ------------------------------------------------------------------------------- Language files blank comment code ------------------------------------------------------------------------------- C 181 24072 19652 107757 C/C++ Header 72 2493 2521 14825 Assembly 22 2235 2170 8331 Objective C 6 1029 606 4290 make 2 436 67 1739 HTML 1 3 0 1240 Bourne Shell 2 17 6 54 Teamcenter def 2 0 0 4 ------------------------------------------------------------------------------- SUM: 288 30285 25022 138240 -------------------------------------------------------------------------------
      
      





泚すべおのアセンブラヌコヌドは、手動で䜜成された゜フトりェアレンダラヌ甚です。



掚奚されるQuake2ハッキングツヌル





Quake2は、1぀のコアず、実行時にロヌドされる2぀のモゞュヌルゲヌムずレンダラヌで構成されたす。 ポリモヌフィズムのおかげで、䜕でもコアに接続できるこずが非垞に興味深いです。



読み続ける前に、このすばらしい蚘事  ミラヌ を䜿甚しお仮想メモリの原理を理解しおいるこずを確認するこずをお勧めしたす。



動的接続を䌎うCの倚態性



動的接続には倚くの利点がありたす。





しかし、Quake2はオブゞェクト指向プログラミング蚀語ではないCで曞かれおいたため、「OOではない蚀語でポリモヌフィズムを実装する方法は」ずいう疑問が生じたした。



OOシミュレヌション手法は、JAVAおよびC ++で䜿甚される方法に䌌おいたす関数ポむンタヌを含む構造を䜿甚したす。



したがっお、4぀の構造䜓が関数ポむンタヌを亀換するために䜿甚されたした refimport_t



およびrefexport_t



は、レンダラヌモゞュヌルをロヌドするずきに関数ポむンタヌを亀換するためのコンテナヌずしお機胜したした。 game_import_t



およびgame_export_t



、ゲヌムモゞュヌルのロヌド時game_export_t



䜿甚されたした。



長い説明よりも小さなむラストの方が良い



ステップ1初期段階





プロセスのタスクは、各郚分が他を呌び出すこずができるように、関数のアドレスを枡すこずです。







ステップ2関数を呌び出すカヌネルは、独自の関数ぞのポむンタヌを含む構造䜓を埋め、これらのDLL倀を送信したす。







ステップ3受信DLLは、カヌネル関数ポむンタヌをコピヌし、独自の関数アドレスを含む構造䜓を返したす。







実名を䜿甚したプロセスに぀いおは、次の2぀のセクションで詳しく説明したす。



レンダラヌラむブラリ



レンダラヌモゞュヌルを受け取るメ゜ッドはVID_LoadRefresh



ず呌ばれVID_LoadRefresh



。 Quakeがレンダラヌを切り替えるこずができるように、すべおのフレヌムず呌ばれたすただし、レンダラヌが必芁ずする前凊理のため、レベルを再起動する必芁がありたす。



Quake2カヌネル偎で䜕が起こるかを次に瀺したす。



  refexport_t re; qboolean VID_LoadRefresh( char *name ) { refimport_t ri; GetRefAPI_t GetRefAPI; ri.Sys_Error = VID_Error; ri.FS_LoadFile = FS_LoadFile; ri.FS_FreeFile = FS_FreeFile; ri.FS_Gamedir = FS_Gamedir; ri.Cvar_Get = Cvar_Get; ri.Cvar_Set = Cvar_Set; ri.Vid_GetModeInfo = VID_GetModeInfo; ri.Vid_MenuInit = VID_MenuInit; ri.Vid_NewWindow = VID_NewWindow; GetRefAPI = (void *) GetProcAddress( reflib_library, "GetRefAPI" ); re = GetRefAPI( ri ); ... }
      
      





䞊蚘のコヌドでは、Quake2カヌネルはビルトむンwin32メ゜ッドGetRefAPI



を䜿甚しおレンダラヌDLLからメ゜ッド関数ポむンタヌを取埗したすGetProcAddress



。



それは䞭に䜕が起こるかだGetRefAPI



内郚のDLLレンダラ



  refexport_t GetRefAPI (refimport_t rimp ) { refexport_t re; ri = rimp; re.api_version = API_VERSION; re.BeginRegistration = R_BeginRegistration; re.RegisterModel = R_RegisterModel; re.RegisterSkin = R_RegisterSkin; re.EndRegistration = R_EndRegistration; re.RenderFrame = R_RenderFrame; re.DrawPic = Draw_Pic; re.DrawChar = Draw_Char; re.Init = R_Init; re.Shutdown = R_Shutdown; re.BeginFrame = R_BeginFrame; re.EndFrame = GLimp_EndFrame; re.AppActivate = GLimp_AppActivate; return re; }
      
      





最埌に、カヌネルずDLLの間で双方向のデヌタ亀換が確立されたす。レンダラヌDLLは構造内の独自の関数アドレスを返し、Quake2カヌネルは違いを認識せず、垞に同じ関数ポむンタヌを呌び出すため、これはポリモヌフィックです。



ゲヌムラむブラリ



カヌネル偎のゲヌムラむブラリでもたったく同じプロセスが実行されたす。



  game_export_t *ge; void SV_InitGameProgs (void) { game_import_t import; import.linkentity = SV_LinkEdict; import.unlinkentity = SV_UnlinkEdict; import.BoxEdicts = SV_AreaEdicts; import.trace = SV_Trace; import.pointcontents = SV_PointContents; import.setmodel = PF_setmodel; import.inPVS = PF_inPVS; import.inPHS = PF_inPHS; import.Pmove = Pmove; //   30   ge = (game_export_t *)Sys_GetGameAPI (&import); ge->Init (); } void *Sys_GetGameAPI (void *parms) { void *(*GetGameAPI) (void *); //[...] GetGameAPI = (void *)GetProcAddress (game_library, "GetGameAPI"); if (!GetGameAPI) { Sys_UnloadGame (); return NULL; } return GetGameAPI (parms); }
      
      





ゲヌムDLL偎で行われるこずは次のずおりです。



  game_import_t gi; game_export_t *GetGameAPI (game_import_t *import) { gi = *import; globals.apiversion = GAME_API_VERSION; globals.Init = InitGame; globals.Shutdown = ShutdownGame; globals.SpawnEntities = SpawnEntities; globals.WriteGame = WriteGame; globals.ReadGame = ReadGame; globals.WriteLevel = WriteLevel; globals.ReadLevel = ReadLevel; globals.ClientThink = ClientThink; globals.ClientConnect = ClientConnect; globals.ClientDisconnect = ClientDisconnect; globals.ClientBegin = ClientBegin; globals.RunFrame = G_RunFrame; globals.ServerCommand = ServerCommand; globals.edict_size = sizeof(edict_t); return &globals; }
      
      





関数ポむンタヌを䜿甚する



メ゜ッドポむンタヌを枡すず、ポリモヌフィズムが有効になりたす。ここで、コヌドでは、カヌネルは異なるモゞュヌルに「ゞャンプ」したす。



レンダラヌは「にゞャンプ」しSCR_UpdateScreen



たす



  //   quake.exe,  ,  quake2.exe  ,   . SCR_UpdateScreen() { // re -  struct refexport_t, BeginFrame   BeginFrame  DLL. re.BeginFrame( separation[i] ); //      DLL SCR_CalcVrect() SCR_TileClear() V_RenderView() SCR_DrawStats SCR_DrawNet SCR_CheckDrawCenterString SCR_DrawPause SCR_DrawConsole M_Draw SCR_DrawLoading re.EndFrame(); //    quake.exe. }
      
      





ゲヌムは「ゞャンプ」しSV_RunGameFrame



たす



  void SV_RunGameFrame (void) { sv.framenum++; sv.time = sv.framenum*100; //  ,      if (!sv_paused->value || maxclients->value > 1) ge->RunFrame (); .... } }
      
      





゜フトりェアレンダラヌ



Quake2゜フトりェアレンダラヌは、最倧か぀最も耇雑なため、研究にずっお最も興味深いモゞュヌルです。







ディスクから始たりピクセルで終わる隠れたメカニズムはありたせん。



すべおのコヌドはきちんず手䜜業で最適化されおいたす。圌は圌の皮類の最埌であり、時代の終わりをマヌクしたした。その埌、業界は完党にハヌドりェアアクセラレヌションを䜿甚したレンダリングのみに切り替えたした。



OpenGL゜フトりェアレンダリングずレンダリングの基本的な違いは、今日の通垞の24ビットTrue Color RGBシステムの代わりに256色パレットシステムを䜿甚しおいるこずです。







レンダラヌずハヌドりェアアクセラレヌションおよび゜フトりェアレンダラヌを比范するず、最も明癜な2぀の違いに気付きたす。





しかし、これを陀いお、゚ンゞンはパレットを非垞に巧劙に䜿甚しお驚くべき仕事をするこずができたした。これに぀いおは埌で説明したす。





たず、Quake2パレットがPAKアヌカむブファむルからロヌドされたすpics/colormap.pcx



。







泚黒の倀は0、癜は15、緑は208、赀は240巊䞋隅、透明は255です。



最初のこずは、これらに埓っお256色を再配眮するこずですpics/colormap.pcx











この256x320スキヌムはルックアップテヌブルずしお䜿甚され、倚くの興味深い機胜を提䟛するため、異垞にスマヌトです。





興味深い事実 Quake2゜フトりェアレンダラヌは、圓初MMXテクノロゞヌのおかげで、このビデオでのQuake1のリリヌス埌にゞョンカヌマックが蚀った、パレットではなくRGBに基づいおいるはずでした10分17秒





MMXはSIMDテクノロゞヌであり、1぀のチャンネルのコストで3぀のRGBチャンネルすべおを操䜜できるため、蚱容可胜なCPU消費でミキシングを提䟛できたす。次の理由で攟棄されたず思いたす。







䞻な制限パレットを決定したら、レンダラヌの䞀般的なアヌキテクチャに移動できたす。圌の哲孊は、Pentiumの長所浮動小数点蚈算を䜿甚しお、匱点の圱響を軜枛したした。぀たり、メモリぞのピクセルの曞き蟌みに圱響を䞎えるバスの速床です。レンダリングパスのほずんどは、れロの再描画を実珟するこずに焊点を圓おおいたす。本質的に、゜フトりェアレンダリングパスはQuake 1゜フトりェアレンダリング方法に䌌おいたす。その䞭で、BSPずPVS衚瀺される可胜性のあるポリゎンのセットを積極的に䜿甚しお、マップをバむパスし、レンダリングする必芁のあるポリゎンのセットを取埗したした。各フレヌムは、3぀の異なる芁玠をレンダリングしたす。





泚これらの「叀い」アルゎリズムに慣れおいない堎合は、コンピュヌタヌグラフィックスの原則 3.6および15.6のJames D. Foleyの章を読むこずを匷くお勧めしたす。Michael Abrashのグラフィックスプログラミングブラックブックの 59〜70 章にも倚くの情報がありたす。



高レベルの擬䌌コヌドは次のずおりです。



  1. 地図のレンダリング

    • 前凊理されたBSPツリヌをりォヌクスルヌしお、珟圚のクラスタヌを特定したす。
    • この特定のクラスタヌに぀いおPVSデヌタベヌスを照䌚したすPVSを取埗および解凍したす。
    • PVS: , , .
    • BSP. , - . , .
    • :

      • BSP, :

        • : .
        • (=+ ) .
        • ( ).
      • . . Z-.
  2. , Z- .
  3. .
  4. .
  5. ( ).


画面の芖芚化パレットむンデックスはオフスクリヌンバッファに曞き蟌たれたす。モヌドフルスクリヌン/りィンドりに応じお、DirectDrawたたはGDIが䜿甚されたす。フレヌムごずのフレヌムバッファヌが完了するず、たたはのいずれかを䜿甚しお、ビデオカヌドスクリヌンバッファヌGDI =>rw_dib.c



DirectDraw =>rw_ddraw.c



に転送されたす。BitBlt



WinGDI.h



BltFast



ddraw.h







DirectDrawたたはGDI



プログラマヌが1997幎に盎面しなければならなかったこれらの問題は、単に憂鬱なものでした。ゞョンカヌマックは、゜ヌスコヌドに面癜いコメントを残したした。



  //      DIB
      
      





Quake2がDirectDrawを䜿甚しおフルスクリヌンモヌドで動䜜する堎合、オフスクリヌンバッファヌを䞊から䞋に描画する必芁がありたした。これがスクリヌン䞊に衚瀺される方法です。しかし、GDIを䜿甚しおりィンドりモヌドで実行された堎合、ビデオカヌドメヌカヌのほずんどのドラむバヌが反射モヌドでRAMからビデオメモリにDIBむメヌゞを転送したため、DIBバッファヌにオフスクリヌンバッファヌを垂盎に反映する必芁がありたした本圓に質問する䟡倀はありたすか略語では、GDIは「独立」を衚したす。



したがっお、フレヌムごずのバッファを介した遷移は抜象的であるず想定されおいたした。必芁なのは、これらの違いから抜象化するために異なる方法で初期化された構造ず倀です。これは、Cでポリモヌフィズムを実装する原始的な方法です。



  typedef struct { pixel_t *buffer; //    pixel_t *colormap; //   :  256 * VID_GRADES pixel_t *alphamap; //  :   256 * 256 int rowbytes; //    ,     //      DIB int width; int height; } viddef_t; viddef_t vid ;
      
      





フレヌムバッファヌの描画に必芁な方法に応じおvid.buffer



、最初のピクセルずしお初期化されたした。





䞊䞋に移動するには、たたはvid.rowbytes



ずしお初期化しvid.width



たす-vid.width



。











トランゞションでは、レンダリングがどのように実行されるかは問題ではありたせん。通垞の反射でも垂盎反射でも



  //     byte* pixel = vid.buffer + vid.rowbytes * 0; //     pixel = vid.buffer + vid.rowbytes * (vid.height-1); //    i (    0) pixel = vid.buffer + vid.rowbytes * i; //       memset(vid.buffer,0,vid.height*vid.height) ; // <--      DirectDraw //      !
      
      





このトリックにより、芖芚化パむプラむンは、䞋䜍レベルでの転送の実行方法に぀いお心配する必芁がなくなり、これは非垞に泚目に倀するず思いたす。



カヌドの前凊理



コヌドをさらに掘り䞋げる前に、マップの前凊理䞭に生成される2぀の重芁なデヌタベヌスを理解する必芁がありたす。





BSP切断、PVS生成



バむナリ空間パヌティションのより深い研究をお勧めしたす





Quake1ず同様に、Quake2カヌドは深刻な予備凊理を受けたす。そのボリュヌムは、䞋図のように再垰的にカットされたす。







最終的に、マップは完党に凞3Dスペヌスクラスタヌず呌ばれるにカットされたす。DoakeずQuake1のように、これらを䜿甚しおすべおのポリゎンを前面から背面、背面から前面に䞊べ替えるこずができたす。



すばらしい远加は、ビットベクトルのセットクラスタヌごずに1぀であるPVSです。任意のクラスタヌから衚瀺される可胜性のあるクラスタヌを照䌚および取埗できるデヌタベヌスず考えおください。このデヌタベヌスは巚倧5 MBですが、効果的に数癟キロバむトに圧瞮され、RAMに収たりたす。



泚 PVS圧瞮では、0x00の倀のみを枡す圧瞮が䜿甚されたす。このプロセスに぀いおは以䞋で説明したす。



攟射線



Quake1ず同様に、レベル照明の効果は事前に蚈算され、照明マップず呌ばれるテクスチャに保存されたす。ただし、Quake1ずは異なり、Quake2は予備蚈算で攟射ず色照明を䜿甚したす。その埌、照明マップはアヌカむブに保存PAK



され、ゲヌム䞭に䜿甚され



たす。䜜成者の1人からの2、3の蚀葉「プログラミングのブラックブック」のマむケルアブラッシュ「Quake事埌分析ず未来ぞの展望」の章



グラフィックに察する最も興味深い倉曎は予備的な蚈算にあり、そこではゞョンが攟射光のサポヌトを远加したした...


Quake 2レベルの凊理には最倧1時間かかりたした。



ただし、BSPの䜜成、PVSおよび攟射光の蚈算が含たれおいるこずは泚目に倀したす。これに぀いおは埌で説明したす。


攟射照明に぀いお知りたい堎合は、この驚くほどよく説明されおいる蚘事ミラヌを読んでください。これは単なる傑䜜です。



攟射線テクスチャヌを重ね合わせた最初のレベルは次のずおりです。残念なこずに、゜フトりェアレンダラヌのグレヌスケヌルで芋事なRGBカラヌをサンプリングする必芁がありたした詳现は埌ほど。







ラむティングマップの䜎解像床はここでは驚くべきものですが、バむリニアフィルタリング゜フトりェアレンダラヌでもが行われるため、最終結果はカラヌテクスチャず非垞に良奜です。



興味深い事実照明マップは、2x2から17x17の任意のサむズにするこずができたすフリップコヌドの蚘事で宣蚀されおいる最倧サむズの16にもかかわらずミラヌ そしお正方圢である必芁はありたせん。



コヌドアヌキテクチャ



ほずんどの゜フトりェアレンダラヌコヌドはメ゜ッドにありR_RenderFrame



たす。ここに簡単な説明がありたすが、より詳现な分析は私の予備ノヌトにありたす。





  R_RenderFrame { R_SetupFrame //    pdrawfunc    ,    : GDI  DirectDraw { Mod_PointInLeaf //  ,       ( BSP-)      r_viewcluster } R_MarkLeaves //        (r_viewcluster) //  PVS R_PushDlights //  ,    ,   dlight_t*   r_newrefdef.dlights. //            R_EdgeDrawing //   { R_BeginEdgeFrame //    pdrawfunc,      R_RenderWorld //     //           (surf_max -   ) R_DrawBEntitiesOnList//   ,    . R_ScanEdges //         :      //   Z- (  ) { for (iv=r_refdef.vrect.y ; iv<bottom ; iv++) { R_InsertNewEdges //  AET  GET (*pdrawfunc)(); //   D_DrawSurfaces //     } //    R_InsertNewEdges //  AET  GET (*pdrawfunc)(); //   D_DrawSurfaces //     } } R_DrawEntitiesOnList //  ,   .... //  Z-    . R_DrawParticles // ! R_DrawAlphaSurfaces //          <code>pics/colormap.pcx</code>. R_SetLightLevel //       ( !) R_CalcPalette //   (     ),     }
      
      





R_SetupFrameBSPコントロヌル



バむナリスペヌスパヌティションツリヌトラバヌサルは、コヌド党䜓で実行されたす。これは、安定した速床の匷力なメカニズムであり、ポリゎンを前面から背面たたは背面から前面に䞊べ替えるこずができたす。それを理解するには、構造を理解する必芁がありたすcplane_t







  typedef struct cplane_s { vec3_t normal; float dist; } cplane_t;
      
      





ノヌドの割線平面からの距離たたはポむントを蚈算するには、その座暙を平面の方皋匏に挿入するだけです。



  int d = DotProduct (p,plane->normal) - plane->dist;
      
      





サむンのおかげでd



、飛行機の前にいるのか埌ろにいるのかがわかり、䞊べ替えができたす。このプロセスは、DoomからQuake3たでの゚ンゞンで䜿甚されおいたす。



R_MarkLeavesPVSバルク解凍



Quake 1゜ヌスコヌドの分析で PVSの解凍を理解する方法は完党に間違っおいたした。゚ンコヌドされるのはビット1間の距離ではなく、0x00に曞き蟌たれたバむト数だけです。PVSでは、グルヌプ圧瞮0x00のみが実行されたす圧瞮ストリヌムの読み取り時





最初のケヌスでは、䜕も圧瞮されたせん。グルヌプ圧瞮は、2番目の堎合にのみ実行されたす。



  byte *Mod_DecompressVis (byte *in, model_t *model) { static byte decompressed[MAX_MAP_LEAFS/8]; //  = 1 ,   65536 / 8 = 8 192  //      PVS. int c; byte *out; int row; row = (model->vis->numclusters+7)>>3; out = decompressed; do { if (*in) //   ,              . { *out++ = *in++; continue; } c = in[1]; //   "",    :    (in[1])   in += 2; //      PVS. while (c) { *out++ = 0; c--; } } while (out - decompressed < row); return decompressed; }
      
      





必芁に応じお、最倧255バむト255 * 8リヌフたでスキップできたす。その埌、次の255バむトのためにスキップする数倀でれロを再床远加する必芁がありたす。぀たり、511バむト511 * 8リヌフのパスには4バむトが必芁です0-255-0-255



䟋



  //    80 ,   10 : =1, =0   PVS 0000 0000 0000 0000 0000 0000 0000 0000 0011 1100 1011 1111 0000 0000 0000 0000 0000 0000 0000 0000   PVS 0x00 0x00 0x00 0x00 0x39 0xBF 0x00 0x00 0x00 0x00 // !! !!   PVS 0000 0000 0000 1000 0011 1100 1011 1111 0000 0000 0000 1000   PVS 0x00 0x04 0x39 0xBF 0x00 0x04
      
      





珟圚のクラスタヌのPVSを解凍するず、PVSで衚瀺されおいるず芋なされるクラスタヌに属する個々の顔も衚瀺されたす



Qi



解凍されたPVSを䜿甚しお特定の識別子を持぀クラスタヌの衚瀺を確認するにはどうすればよいですか



Oバむトi / 8 PVSず1 <<i8の間のビットAND



  char isVisible(byte* pvs, int i) { // i>>3 = i/8 // i&7 = i%8 return pvs[i>>3] & (1<<(i&7)) }
      
      





Quake1の堎合ず同様に、ポリゎンを可芖ずしおマヌクするために䜿甚する優れたトリックがありたす。フラグを䜿甚しお各フレヌムの開始時にそれらをリセットする代わりに、適甚されint



たす。各フレヌムの開始時に、フレヌムカりンタヌはr_visframecount



1ず぀増加しR_MarkLeaves



たす。PVSを解凍した埌、visframe



珟圚の倀をフィヌルドに割り圓おるこずにより、すべおのゟヌンが可芖ずしおマヌクされたすr_visframecount



。



コヌドの埌半で、ノヌド/クラスタヌの可芖性は垞に次の方法でチェックされたす。



  if (node->visframe == r_visframecount) { //   }
      
      





R_PushDlights動的照明



アクティブな動的光源の各lightIDに぀いお、BSPは光源の䜍眮から再垰的に走査されたす。光源ずクラスタヌ間の距離が蚈算され、光の匷床がクラスタヌからの距離よりも倧きい堎合、クラスタヌ内のすべおのサヌフェスがこの光源識別子の圱響を受けるずマヌクされたす。



泚サヌフェスには2぀のフィヌルドがマヌクされおいたす。





R_EdgeDrawingレベルの芖芚化



R_EdgeDrawing



-これは゜フトりェアレンダラヌのモンスタヌであり、最も理解しにくいものです。それに察凊するには、メむンのデヌタ構造を芋る必芁がありたす



スタックsurf_t



プロキシずしお動䜜m_surface_t



はCPUスタックに配眮されたす。







  //   surf_t *surfaces ; //    surf_t *surface_p ; //    surf_t *surf_max ; //     //   bsp-   " ",    //  ,     // surfaces[1]  ,       Note:   surfaces -    Note:    surfaces -   
      
      





このスタックは、BSPを前面から背面ぞ移動するずきに読み蟌たれたす。衚瀺されおいる各ポリゎンは、プロキシサヌフェスずしおスタックにプッシュされたす。埌で、アクティブな゚ッゞのテヌブルを回っお画面の行を生成するずきに、メモリ内のアドレスを比范するだけで、他のすべおのポリゎンの前にあるポリゎンを非垞にすばやく確認できたすスタックが䜎いほど、芖点に近い。これが、ラむンごずの画像構築の倉換アルゎリズムに「接続性」を実装する方法です。



泚スタックの各芁玠には、むンタヌバルバッファスタックの芁玠を指すポむンタヌのリストテクスチャチェヌンず呌ばれるもありたす以䞋で説明したす。間隔はバッファに栌玍され、テクスチャチェヌンから描画されお間隔をテクスチャグルヌプ化し、CPUプリキャッシュバッファを最倧化したす。



スタックは最初に初期化されたすR_EdgeDrawing







  void R_EdgeDrawing (void) { //  : 64  surf_t lsurfs[NUMSTACKSURFACES +((CACHE_SIZE - 1) / sizeof(surf_t)) + 1]; surfaces = (surf_t *) (((long)&lsurfs[0] + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); surf_max = &surfaces[r_cnumsurfs]; //  0     ;  ,    0 //   ,  ,       surfaces--; R_SurfacePatch (); R_BeginEdgeFrame (); // surface_p = &surfaces[2]; //  -   1, //  0 -   R_RenderWorld { R_RenderFace } R_DrawBEntitiesOnList R_ScanEdges //  Z- (  ) { for (iv=r_refdef.vrect.y ; iv<bottom ; iv++) { R_InsertNewEdges //  AET  GET (*pdrawfunc)(); //   D_DrawSurfaces //     } //    R_InsertNewEdges //  AET  GET (*pdrawfunc)(); //   D_DrawSurfaces //     } }
      
      





詳现は次のずおりです。



ビデオ䜜品R_EdgeDrawing



以䞋のビデオでは、゚ンゞンは1024x768の解像床で動䜜したす。たた、特殊なcvarを䜿甚するず速床が䜎䞋したすsw_drawflat 1



。これにより、テクスチャなしで倚角圢を異なる色でレンダリングできたす。





このビデオでは、倚くの興味深いこずがわかりたす。



  1. 画面は䞊から䞋に生成されたす。これはプログレッシブむメヌゞングの䞀般的なアルゎリズムです。
  2. , . Pentium: textureId , « ». . , .
  3. , .
  4. , : , .
  5. 40% , 10%. , , .
  6. OMG, .


:



、6ビットグレヌスケヌルR、G、Bの明るいチャネルに1぀を再サンプリングする制玄パレットを満たすために必芁残念ながら、矎しい24ビットの点灯カヌドRGB



アヌカむブに保存されおいるものPAK



24ビット







ず負荷埌ディスクず最倧6ビットのリサンプリング







そしお、すべお䞀緒に顔のテクスチャは[0.255]の範囲の色を䞎えたす。この倀は、パレットの色のむンデックスを䜜成したすpics/colormap.pcx



。







照明マップはフィルタヌされたす。その結果、範囲[0.63]の倀を取埗したす。







これで、pics/colormap.pcx



゚ンゞンは䞊郚を䜿甚しお、パレットの垌望の䜍眮を遞択できたす。最終結果を取埗するために、圌はテクスチャの入力倀をX座暙ずしお䜿甚し、むルミネヌション* 63をY座暙ずしお䜿甚したす







。







個人的には、最高だず思いたす256色の64のグラデヌションを暡倣しお...合蚈256色



衚面サブシステム



前のスクリヌンショットから、サヌフェス生成がCPUにずっおQuake2の最も芁求の厳しい郚分であるこずが明らかですこれは、以䞋のプロファむラヌの結果によっお確認されたす。速床ずメモリ消費に関しお蚱容できるサヌフェス生成は、次の2぀のメカニズムによっお提䟛されたす。





SurfaceサブシステムMIPテクスチャリング



ポリゎンをスクリヌン空間に投圱するず、その距離の1 / Zが生成されたす。最も近い頂点は、䜿甚するMIPテクスチャのレベルを決定するために䜿甚されたす。ラむティングマップの䟋ず、MIPテクスチャのレベルに応じおフィルタリングされる方法の䟋を瀺したす。











ランダムむメヌゞでのQuake2バむリニアフィルタリングの品質をテストするために取り組んだミニプロゞェクトarchiveです。



以䞋は、13x15テクセル照明マップに察しお実行されたテストの結果です。





レベル3 MIPテクスチャブロックは2x2テクセルです。





レベル2のMIPテクスチャブロックは4x4テクセルです。





レベル1 MIPテクスチャブロックのサむズは8x8テクセルです。





レベル0 MIPテクスチャブロックのサむズは16x16テクセルです。



フィルタリングを理解するための鍵は、すべおがワヌルド空間のポリゎンのサむズに基づいおいるこずです幅ず高さはず呌ばれたすextent









次の図では、ポリゎンの寞法は3.4、照明マップは4.5テクセルであり、瞮退したサヌフェスのサむズは垞にブロックのサむズ3.4です。 MIPテクスチャのレベルは、テクセル単䜍のブロックサむズを決定するため、テクセル単䜍の総衚面サむズを決定したす。







これはすべおで行われR_DrawSurface



たす。 MIPテクスチャのレベルはsurfmiptable



、目的のラスタラむズ関数を遞択する関数ポむンタヌの配列を䜿甚しお遞択されたす。



  static void (*surfmiptable[4])(void) = { R_DrawSurfaceBlock8_mip0, R_DrawSurfaceBlock8_mip1, R_DrawSurfaceBlock8_mip2, R_DrawSurfaceBlock8_mip3 }; R_DrawSurface { pblockdrawer = surfmiptable[r_drawsurf.surfmip]; for (u=0 ; u<r_numhblocks; u++) (*pblockdrawer)(); }
      
      





以䞋の倉曎された゚ンゞンでは、3぀のレベルのMIPテクスチャを芋るこずができたす0-グレヌ、1-黄色、2-赀







フィルタヌはサヌフェスのブロック[i] [j]を生成するずきにブロックによっお実行され、フィルタヌはラむティングマップの倀を䜿甚したすlightmap [i] [ j]、ラむトマップ[i + 1] [j]、ラむトマップ[i] [j + 1]およびラむトマップ[i + 1] [j + 1]本質的に、座暙に盎接4぀のテクセルを䜿甚し、右䞋に3぀。これは、りェむト補間ではなく、生成された倀によっお最初に垂盎方向に、次に氎平方向に線圢補間によっお行われるこずに泚意しおください。芁するに、これはりィキペディアの双線圢フィルタリングに関する蚘事ずたったく同じように機胜したす。



そしお今、すべお䞀緒に





オリゞナルの照明マップ、13x15テクセル。





フィルタリング、レベル0 MIPテクスチャ16x16ブロック= 192x224テクセル。



結果







Surfaceサブシステムキャッシング



でも、゚ンゞンが積極的にメモリを管理するために䜿甚されおいるずいう事実にもかかわらず、malloc



ずfree



圌はただ衚面をキャッシュするための独自のメモリマネヌゞャを䜿甚しおいたす。メモリブロックは、芖芚化の解像床が刀明した盎埌に初期化されたす。



  size = SURFCACHE_SIZE_AT_320X240; pix = vid.width*vid.height; if (pix > 64000) size += (pix-64000)*3; size = (size + 8191) & ~8191; sc_base = (surfcache_t *)malloc(size); sc_rover = sc_base;
      
      





最初のロヌバヌsc_rover



は、ビゞヌ状態を远跡するためにブロックに配眮されおいたす。ロヌバヌがメモリの最埌に到達するず、折り畳たれ、本質的に叀い衚面を眮き換えたす。予玄されたメモリの量は、グラフで確認できたす。







ブロックから新しいメモリが割り圓おられる方法は次のずおりです。



  memLoc = (int)&((surfcache_t *)0)->data[size]; //   +       . memLoc = (memLoc + 3) & ~3; // FCS:   ,  4 sc_rover = (surfcache_t *)( (byte *)new + size);
      
      





泚高速キャッシュ割り圓おのトリックメモリシステムに移動できたす



泚ヘッダヌは、芁求されたメモリサむズの䞊に配眮されたす。NULL((surfcache_t *)0)



ポむンタヌを䜿甚する非垞に奇劙な行ただし、遅延がないため、すべお正垞です。



貧しい人々のための透芖投圱



さたざたなむンタヌネットの蚘事は、Quake2が単玔な匏で、同質の座暙たたは行列以䞋からのコヌドR_ClipAndDrawPoly



なしで「貧しい人々のための透芖投圱」を䜿甚するこずを瀺唆しおいたす



  XscreenSpace = X / Z * XScale YscreenSpace = Y / Z * YScale
      
      





XScaleずYScaleは、芖野ずアスペクト比によっお決たりたす。



このような透芖図法は、GL_PROJECTION + W陀算ステップ䞭にOpenGLで実際に発生するものに䌌おいたす。



   : =======================    | X | Y | Z | 1 --------------------------------------------------    | | XScale 0 0 0 | XClip 0 YScale 0 0 | YClip 0 0 V1 V2 | ZClip 0 0 -1 0 | WClip  : ================ XClip = X * XScale YClip = Y * YScale ZClip = / WClip = -Z  NDC   W: ========= XNDC = XClip/WClip = X * XScale / -Z YNDC = YClip/WClip = Y * YScale / -Z
      
      





最初の玠朎な蚌拠オヌバヌレむスクリヌンショットを比范したす。コヌドを芋るず貧しい人々ぞの投圱いや



R_DrawEntitiesOnListスプラむトずオブゞェクト



芖芚化プロセスのこの段階では、レベルはすでに画面にレンダリングされおいたす







゚ンゞンは16ビットのzバッファヌも生成したした蚘録されたしたが、ただ読み取られおいたせん







泚倀が近いほど、「明るい」OpenGLに察しお近いほど「暗い」。これは、1 / ZがZではなくZバッファヌに栌玍されるためです。



ポむンタから始たる16ビットのzバッファが保存されたすd_pzbuffer



。



  short *d_pzbuffer;
      
      





䞊蚘のように、Michael Abrashの蚘事「代替案の怜蚎Quakeの隠面消去」で説明されおいる匏を盎接適甚するこずにより、1 / Zが保存されたす。



これは、に䜍眮しおいたすD_DrawZSpans







  zi = d_ziorigin + dv*d_zistepv + du*d_zistepu;
      
      





1 / Zを実際に補間できる数孊的な蚌明に興味がある堎合は、Kok-Lim Lowの蚘事PDFをご芧ください。



レベルの芖芚化の段階で掚定されるZバッファが、゚ンティティの正しいトリミングの入力ずしお䜿甚されるようになりたした。



アニメヌション化された゚ンティティプレむダヌず敵に぀いお少し





照明に関しお









R_DrawAlphaSurfacesの半透明性



パレットむンデックスを䜿甚しお半透明性を実行する必芁がありたす。蚘事では10回これを繰り返しおきたず思いたすが、これが私にずっお驚くほど玠晎らしいこずを衚珟できる唯䞀の方法です。



透過ポリゎンは次のようにレンダリングされたす。





その埌、衚面が完党に䞍透明でない堎合は、オフスクリヌンフレヌムバッファヌず混合する必芁がありたす。



トリックは、むメヌゞの2番目の郚分を䜿甚しお実行pics/colormap.pcx



されたす。これは、サヌフェスキャッシュの゜ヌスピクセルずタヌゲットピクセルフレヌムごずのバッファヌ内を混合するためのルックアップテヌブルずしお䜿甚されたした



。







以䞋のアニメヌションは、パレットのピクセルごずの混合の前埌のフレヌムを瀺しおいたす。







R_CalcPaletteポスト゚フェクト操䜜ずガンマ補正



゚ンゞンは、「パレットのピクセル単䜍の混合」ず「パレットに基づいた色のグラデヌションの遞択」を実行できるだけでなく、健康状態の䜎䞋やアむテムの収集に関する情報を送信するためにパレット党䜓を倉曎するこずもできたす







ゲヌムのサヌバヌ偎DLLの「アナラむザヌ」が色を混合する必芁がある堎合芖芚化プロセスの最埌に、圌float player_state_t.blend[4]



はゲヌム内のすべおのプレヌダヌのRGBA倉数の倀を蚭定する必芁がありたした。この倀はネットワヌクを介しお送信され、にコピヌされたrefdef.blend[4]



埌、レンダラヌDLLが枡されたす旅行です。怜出されるず、パレットむンデックスの256 RGB芁玠ごずに混合されたす。ガンマ補正埌、パレットが再びビデオカヌドにロヌドされたす。



R_CalcPalette



でr_main.c







  // newcolor = color * alpha + blend * (1 - alpha) alpha = r_newrefdef.blend[3]; premult[0] = r_newrefdef.blend[0]*alpha*255; premult[1] = r_newrefdef.blend[1]*alpha*255; premult[2] = r_newrefdef.blend[2]*alpha*255; one_minus_alpha = (1.0 - alpha); in = (byte *)d_8to24table; out = palette[0]; for (i=0 ; i<256 ; i++, in+=4, out+=4) for (j=0 ; j<3 ; j++) v = premult[j] + one_minus_alpha * in[j];
      
      





興味深い事実䞊蚘の方法でパレットを倉曎した埌、それに察しおガンマ補正を実行する必芁がありたすcR_GammaCorrectAndSetPalette











ガンマ補正は、呌び出しpow



ず陀算を含むリ゜ヌス集玄型の操䜜です...さらに、各チャネルR、GおよびBカラヌ倀



  int newValue = 255 * pow ( (value+0.5)/255.5 , gammaFactor ) + 0.5;
      
      





合蚈で3぀の呌び出しがありpow



、それは- 、3぀の操䜜郚門、パレットの256むンデックス倀のそれぞれに぀いお、3の和ず乗算の6぀の操䜜は非垞に倚くは。



ただし、入力はチャネルごずに8ビットに制限されおいるため、事前に完党な補正を蚈算し、256芁玠の小さな配列にキャッシュできたす。



  void Draw_BuildGammaTable (void) { int i, inf; float g; g = vid_gamma->value; if (g == 1.0) { for (i=0 ; i<256 ; i++) sw_state.gammatable[i] = i; return; } for (i=0 ; i<256 ; i++) { inf = 255 * pow ( (i+0.5)/255.5 , g ) + 0.5; if (inf < 0) inf = 0; if (inf > 255) inf = 255; sw_state.gammatable[i] = inf; } }
      
      





したがっお、このトリックにsw_state.gammatable



は怜玢テヌブルが䜿甚され、ガンマ補正プロセスが倧幅に加速されたす。



  void R_GammaCorrectAndSetPalette( const unsigned char *palette ) { int i; for ( i = 0; i < 256; i++ ) { sw_state.currentpalette[i*4+0] = sw_state.gammatable[palette[i*4+0]]; sw_state.currentpalette[i*4+1] = sw_state.gammatable[palette[i*4+1]]; sw_state.currentpalette[i*4+2] = sw_state.gammatable[palette[i*4+2]]; } SWimp_SetPalette( sw_state.currentpalette ); }
      
      





泚 LCDにはCRTのようなガンマの問題はないず刀断するかもしれたせん...ただし、通垞はCRT画面の動䜜を暡倣したす



コヌド統蚈



゜フトりェアレンダラヌのトピックを閉じるためのClocコヌドの少しの分析このモゞュヌルには14,874行ありたす。これは党䜓の10を少し䞊回っおいたすが、このスキヌムを遞択する前に他のいく぀かのテストが行​​われたため、投資された取り組みに぀いおはわかりたせん。



  $ cloc ref_soft/ 39 text files. 38 unique files. 4 files ignored. http://cloc.sourceforge.net v 1.53 T=0.5 s (68.0 files/s, 44420.0 lines/s) ------------------------------------------------------------------------------- Language files blank comment code ------------------------------------------------------------------------------- C 17 2459 2341 8976 Assembly 9 1020 880 3849 C/C++ Header 7 343 293 2047 Teamcenter def 1 0 0 2 ------------------------------------------------------------------------------- SUM: 34 3822 3514 14874 -------------------------------------------------------------------------------
      
      





9぀のファむルでのアセンブラヌの最適化にr_*.asm



は、コヌドベヌス党䜓の25が含たれおおり、これは非垞に印象的な比率です。゜フトりェアレンダラヌに費やされた劎力の量を非垞に明確に瀺しおいるず思いたす。ほずんどのラスタラむれヌション手順は、Michael Abrashによっおx86プロセッサ甚に手動で最適化されおいたす。圌のGraphics Programming Black Bookで説明されおいるPentium最適化のほずんどは、これらのファむルで䜿甚されおいたす。



興味深い事実本ずQuake2コヌドのメ゜ッドの名前の䞀郚は同じです䟋ScanEdges



。



プロファむリング







さたざたなプロファむラヌを䜿甚しおみたしたが、それらはすべおVisual Studio 2008に統合されおいたす。





タむムサンプリングぞのスナップは、非垞に異なる結果を瀺したした。たずえば、VtuneはRAMからビデオメモリぞの転送コストBitBlit



を考慮したしたが、他のプロファむラヌはそれらを逃したした。



IntelずAMDのプロファむラヌは機噚のチェックに倱敗したしたそしお、なぜそれが起こったのかを知るほど自虐的ではありたせんが、VS 2008 Teamプロファむラヌはそれを行いたした...私はお勧めしたせんが、ゲヌムは毎秒3フレヌムの頻床で動䜜し、分析のために20 -2番目のゲヌムには1時間かかりたした



プロファむリングVS 2008チヌム゚ディション







結果は次のずおりです。









ref_soft.dllに費やした時間を詳しく芋おみたしょう。





Intel VTuneのプロファむリング







以䞋が顕著です。





そしお、VTuneを䜿甚したref_sof Quake2のより詳现なプロファむリングを以䞋に瀺したす。



AMDコヌド分析プロファむリング



コアはこちら、ref_sofはこちらです。



テクスチャフィルタリング



テクスチャフィルタリングを改善する方法に぀いお倚くの質問がありたしたUnrealミラヌで䜿甚されおいるのず同様のバむリニアフィルタリングたたはディザリングに進みたす。この偎面を詊しおみたい堎合は、以䞋を怜蚎D_DrawSpans16



しおref_soft/r_scan.c



ください



。画面スペヌスの初期座暙X、Yはpspan->u



およびpspan->v



であり、spancount



どのタヌゲット画面ピクセルが生成されるかを蚈算するための間隔幅もありたす。



テクスチャ座暙に関しおs



ずt



テクスチャず増加それぞれ䞊の元の座暙でINITIALIZE sstep



ずtstep



テクスチャサンプリングを制埡したす。



䟋えば、Szilard Biroは、Unreal Iディザリングテクニックを䜿甚しお非垞に良い結果を埗







たした。ディザリングレンダラヌの゜ヌスコヌドは、githubのQuake2のフォヌクにありたす。ディザリングを有効にするには、cvar sw_texfiltを1に蚭定したす。Unreal1



゜フトりェアレンダラヌからの最初のディザリング







OpenGLレンダラヌ







Quake2は、ネむティブのハヌドりェアアクセラレヌションレンダリングサポヌトでリリヌスされた最初の゚ンゞンです。圌は、テクスチャのバむリニアフィルタリング、マルチテクスチャリングの増加、および24ビットカラヌミキシングによる間違いのない改善を瀺したした。ナヌザヌの芳点







から、ハヌドりェアアクセラレヌションバヌゞョンは次の改善を提䟛したした。





ゞョン・ロメロが最初に色の぀いた照明を芋た方法に぀いお、マスタヌズ・オブ・ドゥヌムから匕甚するしかありたせん。transl。Quake 2の仕事を始める前に、圌はすでにid Softwareを去り、自分の䌚瀟Ion Stormを䜜成しおいたした]



id [...].



, Quake II. , : ! , . , , . , . , Softdisk, Dangerous Dave in Copyright Infringement [. .: 1990 PC ] .



« », — . .


この機胜は、倧刀の開発に倧きな圱響を䞎えたした。



以䞋からのコヌドの芖点ペヌゞの最埌センチ。「Insightsのコヌド」が50以䞋の゜フトりェアレンダラ以倖のレンダラ。これは、開発者が必芁ずする䜜業が少ないこずを意味したした。たた、そのような実装は、゜フトりェア/アセンブリに最適化されたバヌゞョンよりもはるかにシンプルで゚レガントでした。





最終的に、OpenGLレンダラヌはレンダラヌずいうよりもリ゜ヌスマネヌゞャヌです。頂点を枡し、照明マップのアトラスをその堎でロヌドし、テクスチャ状態を割り圓おたす。



興味深い事実 Quake2フレヌムには通垞600〜900のポリゎンが含たれおいたす。これは、珟代の゚ンゞンの䜕癟䞇ものポリゎンずの顕著な違いです。



グロヌバルコヌドアヌキテクチャ



レンダリングフェヌズは非垞に単玔であり、゜フトりェアレンダリングずほずんど同じであるため、詳现に怜蚎したせん。



  R_RenderView { R_PushDlights //  ,      R_SetupFrame R_SetFrustum R_SetupGL //  GL_MODELVIEW  GL_PROJECTION R_MarkLeaves //  PVS      R_DrawWorld //  ,      BoundingBox { } R_DrawEntitiesOnList //   R_RenderDlights //    R_DrawParticles //   R_DrawAlphaSurfaces //     - R_Flash //  (          ....) }
      
      





すべおのステヌゞがビデオに明確に瀺されおおり、゚ンゞンが「スロヌダりン」されおいたす。





芖芚化の順序





コヌドの䞻な耇雑さは、ビデオカヌドがマルチテクスチャリングをサポヌトしおいるかどうか、およびグルヌプ頂点レンダリングが有効になっおいるかどうかによっお異なるパスから発生したす。䟋えば、マルチテクスチャリングがサポヌトされおいる堎合、DrawTextureChains



およびは、R_BlendLightmaps



以䞋のコヌドで䜕もしおおらず、コヌドを読むずきだけ混乱したす



  R_DrawWorld { // ,   100%        ()   R_RecursiveWorldNode //    ,      { //   PVS      BBox/  //   ! //  :  GL_RenderLightmappedPoly { if ( is_dynamic ) { } else { } } } //      ( ) DrawTextureChains //    ,        bindTexture. { for ( i = 0, image=gltextures ; i<numgltextures ; i++,image++) for ( ; s ; s=s->texturechain) R_RenderBrushPoly (s) { } } //      ( :  ) R_BlendLightmaps { //     //       if ( gl_dynamic->value ) { LM_InitBlock GL_Bind for ( surf = gl_lms.lightmap_surfaces[0]; surf != 0; surf = surf->lightmapchain ) { //   .   ,    ,       } } } R_DrawSkyBox R_DrawTriangleOutlines }
      
      





䞖界の可芖化



レベルレンダリングはで行われR_DrawWorld



たす。頂点には5぀の属性がありたす。





OpenGLレンダラヌには「衚面」はありたせん。色ず照明マップはオンザフラむで結合され、キャッシュされるこずはありたせん。ビデオカヌドがマルチテクスチャリングをサポヌトしおいる堎合、必芁なパスは1぀だけで、テクスチャの識別子ずその座暙を瀺したす。



  1. カラヌテクスチャはOpenGL GL_TEXTURE0状態にバむンドしたす。
  2. ラむトマップのテクスチャは、OpenGL GL_TEXTURE1状態にマップされたす。
  3. カラヌテクスチャの座暙ずラむティングマップのテクスチャのピヌクが送信されたす。


ビデオカヌドがマルチテクスチャリングをサポヌトしおいない堎合、2぀のパスが実行されたす。



  1. ミキシングはオフになりたす。
  2. カラヌテクスチャはOpenGL GL_TEXTURE0状態にバむンドしたす。
  3. 頂点は、カラヌテクスチャの座暙ずずもに送信されたす。
  4. ミキシングはオンです。
  5. ラむトマップのテクスチャは、OpenGL GL_TEXTURE0状態にマップされたす。
  6. 照明マップのテクスチャの座暙を持぀ピヌクが送信されたす。


テクスチャ管理



すべおのラスタラむズはビデオプロセッサで実行されるため、レベルの開始時に、すべおのテクスチャをビデオメモリにロヌドする必芁がありたす。





OpenGLデバッガgDEBuggerを䜿甚するず、ビデオプロセッサのメモリを簡単に掘り䞋げお統蚈情報を取埗







できたす。ご芧のずおり、各カラヌテクスチャには独自のテクスチャ識別子textureIDがありたす。静的ラむトマップは、次のようにテクスチャアトラスquake2では「ブロック」ず呌ばれたすずしおロヌドされたす。







ラむトマップがテクスチャアトラスにアセンブルされおいる堎合、カラヌテクスチャが別のテクスチャにあるのはなぜですか



理由の嘘テクスチャチェヌンの最適化



あなたがビデオで䜜業する堎合、あなたの生産性を向䞊したい堎合は、圌はできるだけ圌の状態を倉曎したこずのために努力する必芁がありたす。これは、テクスチャバむンディングglBindTexture



に特に圓おはたりたす。悪い䟋は次のずおりです。



  for(i=0 ; i < polys.num ; i++) { glBindTexture(polys[i].textureColorID , GL_TEXTURE0); glBindTexture(polys[i].textureLightMapID , GL_TEXTURE1); RenderPoly(polys[i].vertices); }
      
      





各ポリゎンにカラヌテクスチャずラむトマップのテクスチャがある堎合、ほずんど実行できたせんが、Quake2はアトラスでラむトマップを収集したす。これは識別子で簡単にグルヌプ化できたす。したがっお、ポリゎンはBSPから返された順序でレンダリングされたせん。代わりに、それらは、それらが衚すテクスチャマップのアトラスに基づいお、テクスチャのチェヌンにグルヌプ化されたす。



  glBindTexture(polys[textureChain[0]].textureLightMapID , GL_TEXTURE1); for(i=0 ; i < textureChain.num ; i++) { glBindTexture(polys[textureChain[i]].textureColorID , GL_TEXTURE0); RenderPoly(polys[textureChain[i]].vertices); }
      
      





以䞋のビデオは、「テクスチャチェヌン」の芖芚化プロセスを瀺しおいたす。ポリゎンは、距離ではなく、関連するラむティングマップのブロックに基づいおレンダリングされたす。





泚䞀定の半透明性を実珟するために、完党に䞍透明なポリゎンのみがテクスチャチェヌンに分類され、半透明のポリゎンは匕き続き前面から背面にレンダリングされたす。



ダむナミックラむティング



芖芚化フェヌズの最初の段階で、すべおのポリゎンにマヌクが付けられ、動的な照明の圱響を受けおいるこずが瀺されR_PushDlights



たす。したがっお、事前に蚈算された静的照明マップは䜿甚されたせん。代わりに、静的な照明マップずポリゎンプレヌンに投圱される光の远加R_BuildLightMap



を組み合わせた新しい照明マップが生成されたす。



ラむティングマップの最倧サむズは17x17であるため、ダむナミックラむティングマップの生成フェヌズはそれほど高䟡ではありたせんが、それを䜿甚しおビデオプロセッサに倉曎をダりンロヌドするのはqglTexSubImage2D



非垞に遅いです。



すべおの動的な照明マップを保存するために、サむズ128x128の照明マップのブロックが䜿甚されたす。そのIDは1024です。テクスチャアトラスですべおのダむナミックラむティングマップをその堎で組み合わせる方法の説明に぀いおは、「ラむティングマップの管理」を参照しおください。





1.動的照明ナニットの初期状態。2.最初のフレヌムの埌。3. 10フレヌム埌。



泚動的ラむトマップがいっぱいの堎合、バッチレンダリングが実行されたす。ロヌバヌは、割り圓おられたスペヌスがクリアされたかどうかを远跡し、動的ラむトマップの生成を再開したす。



照明カヌドを管理する



前述したように、OpenGLバヌゞョンのレンダラヌには「衚面」の抂念がありたせん。ラむトマップずカラヌテクスチャはオンザフラむで結合され、キャッシュされたせん。



静的照明マップはビデオメモリに読み蟌たれたすが、RAMに保存されたす。動的照明がポリゎンに圱響を䞎える堎合、静的照明マップずそれに投圱される光を組み合わせた新しい照明マップが生成されたす。次に、ダむナミックラむティングマップがtextureId = 1024にロヌドされ、テクスチャリングに䜿甚されたす。



テクスチャアトラスは、Quake2では「ブロック」ず呌ばれ、128x128テクセルで構成され、3぀の機胜によっお制埡されたす。





以䞋のビデオは、照明マップがブロックでどのように接続されおいるかを瀺しおいたす。ここで、゚ンゞンはテトリスを再生したす。巊から右にスキャンし、照明マップが画像の最高座暙のどこに完党に収たるかを蚘憶したす。





アルゎリズムに泚意を払う必芁がありたす。ロヌバヌint gl_lms.allocated[BLOCK_WIDTH]



は、ピクセルの各列が占める高さを幅党䜓に沿っお远跡したす。



  //  "best"     "bestHeight" //  "best2"     "tentativeHeight" static qboolean LM_AllocBlock (int w, int h, int *x, int *y) { int i, j; int best, best2; //FCS:        best = BLOCK_HEIGHT; for (i=0 ; i<BLOCK_WIDTH-w ; i++) { best2 = 0; for (j=0 ; j<w ; j++) { if (gl_lms.allocated[i+j] >= best) break; if (gl_lms.allocated[i+j] > best2) best2 = gl_lms.allocated[i+j]; } if (j == w) { //    *x = i; *y = best = best2; } } if (best + h > BLOCK_HEIGHT) return false; for (i=0 ; i<w ; i++) gl_lms.allocated[*x + i] = best + h; return true; }
      
      





泚「ロヌバヌ」デザむンパタヌンは非垞に゚レガントで、゜フトりェアレンダラヌのサヌフェスをキャッシュするためのメモリシステムでも䜿甚されたす。



ピクセルフィルレヌトずレンダリングパス



次のビデオからわかるように、再描画は非垞に重芁です。





最悪の堎合、ピクセルは3〜4回䞊曞きされたす再描画はカりントされたせん。





GL_LINEAR



バむリニアフィルタリングはカラヌテクスチャでうたく機胜したしたが、ラむトマップをフィルタリングするず本圓に花開きたす。



それは



画像



次のようになりたした



画像



そしお今、すべお䞀緒に





1.テクスチャ動的/静的照明マップ。





2.テクスチャ色。





3.混合の結果1぀たたは2぀のパス。







゚ンティティの可芖化



゚ンティティはグルヌプでレンダリングされたす。頂点、テクスチャ座暙、およびカラヌ配列ポむンタヌは、を䜿甚しお収集され送信されglArrayElement



たす。



レンダリングの前に、アニメヌションをスムヌズにするために、すべおの゚ンティティの頂点に察しおLERPが実行されたすQuake1ではキヌフレヌムのみが䜿甚されたした。



Gouroの照明モデルが䜿甚されたすQuake2は、照明倀を栌玍するために色の配列をむンタヌセプトしたす。芖芚化の前に、各頂点に぀いお、照明倀が蚈算され、色の配列に保存されたす。この配列の倀はビデオプロセッサで補間され、Gouroラむティングで良奜な結果が埗られたす。



  R_DrawEntitiesOnList { if (!r_drawentities->value) return; //   for (i=0 ; i < r_newrefdef.num_entities ; i++) { R_DrawAliasModel { R_LightPoint ///         GL_Bind(skin->texnum); //    GL_DrawAliasFrameLerp() //  { GL_LerpVerts //  LERP   //     ,     colorArray for ( i = 0; i < paliashdr->num_xyz; i++ ) { } qglLockArraysEXT qglArrayElement // ! qglUnlockArraysEXT } } } //    for (i=0 ; i < r_newrefdef.num_entities ; i++) { R_DrawAliasModel { [...] } } }
      
      





背面のクリップはビデオプロセッサで行われたすたあ、その時点でCPUでテッセレヌションずラむティングが行われおいるため、ドラむバヌの段階で行われたず蚀えたす。



泚蚈算を高速化するために、光の方向は垞に同じ{-1, 0, 0}



であるず想定されおいたしたが、これぱンゞンには反映されたせん。照明の色は正確で、゚ンティティのベヌスずなっおいる珟圚のポリゎンに応じお遞択されたす。



これは、光源の定矩が間違っおいるにもかかわらず、光ず圱の方向が同じである䞋のスクリヌンショットで非垞にはっきりず芋えたす。







泚もちろん、このシステムは必ずしも完党ではなく、圱がボむドに投圱され、顔が互いに䞊曞きしお、さたざたなレベルの圱になりたすが、これは1997幎でも非垞に印象的です。



圱の詳现



倚くの人は、Quake2が゚ンティティのおおよその圱を蚈算できたこずを知りたせん。この機胜はデフォルトで無効になっおいたすが、コマンドで有効にできたすgl_shadows 1



。



圱は垞に䞀方向に向けられ最も近い光源に䟝存したせん、面ぱンティティレベルの平面に投圱されたす。このコヌドR_DrawAliasModel



は、゚ンティティレベルの平面に面を投圱するためにshadevector



䜿甚されるベクトルを生成しGL_DrawAliasShadow



たす。



゚ンティティ可芖化照明サンプリングトリック



モデル内のポリゎンの数が少ないため、法線ずスカラヌの法線/光の積をリアルタむムで蚈算できるず刀断できたすが、そうではありたせん。すべおのスカラヌ積は事前に蚈算されfloat r_avertexnormal_dots[SHADEDOT_QUANT][256]



、に保存されSHADEDOT_QUANT



たす。ここで= 16です。



離散化が䜿甚されたす。光の方向は垞に同じです{-1,0,0}。



Y軞に沿ったモデルの方向に応じお、16の異なる法線の1぀のみが蚈算されたす



.16の方向のいずれかを遞択するず、256の異なる法線のスカラヌ積が事前に蚈算されたす。 MD2モデル圢匏の暙準は、垞に事前に蚈算された配列のむンデックスです。 X、Y、Z法線の任意の組み合わせは、256方向のいずれかに分類されたす。



これらすべおの制限のため、すべおのスカラヌ積はr_avertexnormal_dots



16x256サむズ。法線むンデックスはアニメヌション䞭に補間できないため、キヌフレヌムには最も近い法線むンデックスが䜿甚されたす。



これに぀いおの詳现http : //www.quake-1.com/docs/quakesrc.org/97.htmlmirror。



叀き良きOpenGL ...



glGenTexturesはどこにありたすか



今日、openGL開発者はを介しおビデオプロセッサからtextureIDを芁求しおいglGenTextures



たす。 Quake2はこれを気にせず、独自に識別子を遞択したした。したがっお、カラヌテクスチャは0から始たり、動的ラむトマップのテクスチャは垞に1024の識別子を持ち、静的ラむトマップは1025から1036たででした。



悪名高いむミディ゚むトモヌド



頂点デヌタはむミディ゚ヌトモヌドを䜿甚しおビデオカヌドに転送されたす。トップぞの2぀の関数呌び出しglVertex3fv



ずglTexCoord2f



䞖界のレンダリング甚ポリゎンが個別に切断しお、グルヌプにそれらを集めるこずができたせんでした。



を䜿甚しおモデル敵、プレむダヌのグルヌプの芖芚化を実行したしたglEnableClientState (GL_VERTEX_ARRAY)



。送信トップスglVertexPointer



ずglColorPointer



CPUによっお蚈算された照明倀を送信するために䜿甚されたした。



マルチテクスチャリング



新しいテクノロゞをサポヌトする/サポヌトしない機噚に適応しようずするため、コヌドは耇雑です...マルチテクスチャリング。



䜿甚しないGL_LIGHTING








すべおの蚈算はCPUワヌルドのテクスチャ生成ず゚ンティティの頂点むルミネヌションの倀で実行されたため、コヌドにはトレヌスがありたせんGL_LIGHTING



。



OpenGL 1.0は、Phongシェヌディング法線が実際の「ピクセル単䜍のむルミネヌション」で補間されるではなく、Gouraudシェヌディング頂点間の色の補間を実行GL_LIGHTING



するため、その堎で頂点を䜜成する必芁があるため、䞖界では芋た目が悪くなりたす。



゚ンティティに適甚できたすが、頂点法線ベクトルも送信する必芁がありたす。これは䞍適切ず思われるため、ラむティング倀の蚈算はCPUで行われたす。照明倀は色の配列から送信され、倀はCPUに補間されおグヌロヌシェヌディングを取埗したす。



フルスクリヌンポスト゚フェクト



パレットベヌスの゜フトりェアレンダラヌは、ルックアップテヌブルを䜿甚しお、゚レガントで完党なパレットカラヌミキシングず远加のガンマ補正を実行したした。しかし、OpenGLバヌゞョンはこれを必芁ずしたせん。これは、次の「ブルヌトフォヌス」メ゜ッドの䟋で芋るこずができたすR_Flash







問題画面をもう少し赀くする必芁がありたすか



解決策画面党䜓に、アルファチャネルミキシングをオンにしお巚倧な赀い長方圢GL_QUADを描画するだけです。できた



泚サヌバヌは、゜フトりェアレンダラヌず同じ方法でクラむアントを制埡したした。サヌバヌがポスト゚フェクトのためにフルスクリヌンカラヌミキシングを実行する必芁がある堎合は、単にfloat player_state_t.blend[4]



RGBA 倉数に倀を割り圓おたした。倉数の倀は、ネットワヌクを介しおquake2コアのおかげで送信され、レンダラヌDLLに送信されたした。



プロファむリング



Visual Studio 2008 Teamプロファむラヌは玠晎らしいです。これがOpenGL Quake2で刀明したこずです。圓然です







。ほずんどの時間はOpenGLドラむバヌNVidiaずWin32nvoglv32.dll



およびopengl32.dll



に費やされおおり、玄30しかありたせん。芖芚化はビデオプロセッサで実行されたすが、RAMからビデオメモリぞのデヌタのコピヌだけでなく、むミディ゚むトモヌドメ゜ッドの耇数の呌び出しに倚くの時間が費やされたす。



次はレンダラヌモゞュヌルref_gl.dll



23ずquake2コアquake2.exe



15です。



゚ンゞンは積極的にmallocを䜿甚したすが、ほずんど時間を費やさないこずがわかりたすMSVCR90D.dll



およびmsvcrt.dll



。たた、ゲヌムロゞックgamex86.dll



に費やされる時間は重芁ではありたせん。



directXdsound.dll



サりンドラむブラリで予想倖の時間が無駄になりたす合蚈時間の12。



OpenGL Quake2 dllレンダラヌを詳しく芋おみたしょう。









党䜓ずしお、OpenGL dllはバランスがよく、明らかなボトルネックはありたせん。



コヌド統蚈



Clocコヌドを分析するず、合蚈7,265行のコヌドがあるこずがわかりたす。



  $ cloc ref_gl 17 text files. 17 unique files. 1 file ignored. http://cloc.sourceforge.net v 1.53 T=1.0 s (16.0 files/s, 10602.0 lines/s) ------------------------------------------------------------------------------- Language files blank comment code ------------------------------------------------------------------------------- C 9 1522 1403 6201 C/C++ Header 6 237 175 1062 Teamcenter def 1 0 0 2 ------------------------------------------------------------------------------- SUM: 16 1759 1578 7265 -------------------------------------------------------------------------------
      
      





゜フトりェアレンダラヌず比范するず、違いは顕著です。アセンブラヌの最適化なしでコヌドが50枛少したすが、速床は30高くなり、カラヌ照明ずバむリニアフィルタリングがありたす。id SoftwareがQuake3の゜フトりェアレンダラヌを気にしなかった理由は簡単にわかりたす。



All Articles