2010年7月9日
縱觀游戲服務器技術框架, 以運行在Linux操作系統上的居多, 為什么要運行在Linux上呢? 相信很多人會給出肯定的答案, Linux操作系統是開源的,而且是免費的,那叫做專業!是的,不錯,是專業。但有一個問題讓我們值得思考的是,雖然是免費的,但Linux工具比較貧乏,在某種程度上大大加在了開發成本及后期調試時間,所以我們不得不研究出一種代碼可以運行在Linux,但又要節省開發成本,提高生產效率的可行性方案。
在Linux上,古老的Linux C程序員一般會采用一個文本編輯工具,比如:VI等,由于我以前是在windows下生活的,對VS.net這一類的工具頗為喜愛,它的開發方便快捷,又有開發輔助插件的支持,使你可以盡情使用智能提示,快捷鍵,代碼模板等高效生產代碼。可以說編碼更多的是依靠可視化工具查錯,快速跳轉代碼等。反正我的意思就是開發相當愉悅。但Linux下的C程序員則不然,我也曾經問過他們為什么要采用VI等編輯工具來編代碼,首先,他們給到我的第一答案是"專業", 其實我也不太懂他們說的專業是什么,是傳統意義上的,還是習慣性必須的;然后他們說:“使用makefile文件來編譯代碼很爽,速度很快”,這一點我是贊同的,但有個問題是程序員的入門門檻比較高,要求大家都要會這東西,還是麻煩,從HR招聘處可以看到,本來投C++程序員的人就比較少了,因為C++應用的領域基礎是應用軟件,游戲啊,電信啊,還是一些MFC界面性的東西,而且要求功底比較好,確實有時候招人還是挺難的,更不用說還要有Linux開發經驗的人了。最后,我問了linux開發不員說那你們的調試用什么工具,他們說:“gdb”, 又是一個"手打牛肉丸",自我感覺很痛苦,心里想“為什么有那么先進的工具,比方說:Eclipse CDT, 你們不用呢?”,其實他們心中也是有答案的,“編碼要養成一種好習慣,而不是依靠于某種工具”,顯然Linux程序員在編碼上大體要比windows上的程序員來得嚴格,也感覺到他們的代碼比較有質量。但很顯然的,由于開發環境的布署比較麻煩,對于大規模生產是否能夠每個人都有那么高素質的編程能力,那就是個"謎"!------ “猜不準!”
針對上面的情況,我也自己分析了一下,軟件開發的三大要素是什么,成本,質量,進度, 只有這三項東西控制得好,那才能控制好項目。
那軟件編程的基礎是什么? 當然是調用操作系統的API了,很顯然的, 不同操作系統有不同的API,除非你有一個跨平臺的開發框架,或者叫類庫也行。
接下來軟件架構在不同領域是否通用?比方說:通信框架,很顯然的,還是有區別的,比方說電信系統與游戲系統,那顯然還是不一樣的。
最后,軟件編碼與接口(API)是不是應該更多人常用的,而且容易上手的(友好第一!),這樣才可以減少開發成本及協調工作。
總結一下,我心中已有答案了,必須采用一種大多數程序員可以接受的,而且是他們熟悉的(不要DIY的),而且開發速度快速的開發方式那才是真道理。
以下是我的基本方案:
一、 跨平臺框架的基礎設施 (組件圖)
1. MySQL數據庫操作組件
2. 線程池 及 讀寫鎖
3. 基礎數據類型,容器,內存池,環形緩沖區
4. IOCP及Epoll跨平臺的面向對象通信框架
5. 集成LuaTinker腳本交互模塊等
二、 開發方式與調試環境
本框架的初步設想是前期在Windows下使用VS.net 2008進行開發調試,爭取在Windows下解決80%左右的邏輯錯誤。之后由主程序員把代碼移植到Linux,并使用Eclipse CDT可視化開發環境進行后期的調試工作。這樣也大大降低招聘人員的知識要求(不用懂Linux),在某一程度降低了人員成本和加快了開發效率。

1. LoginApp
即登錄服務器, 它主要完成玩家帳號的驗證, 同時它通過BaseAppMgr并向玩家發送一個SessionKey作為基礎服務器(BaseApp)的登錄密鑰;同時LoginServer還向玩家發送服務器列表信息。
2. BaseApp
即基礎服務器, 也稱連接服務器,它還維持著一個客戶端連接列表(用戶列表), 這樣它可以實現區域消息廣播及通過BaseAppMgr實現世界聊天, 玩家信息查看等功能;它還負責消息的分發到CellApp進行處理,并把結果返回到客戶端。
3. CellApp
即游戲服務器,它負責世界數據的加載,游戲邏輯的處理及世界對象的管理。在本架構中當為支線服務器。
4. DBMgr
用戶服務器,它負責用戶相關數據的存取。一般是用戶登錄選擇角色后就獲得角色所有相關數據給到MapServer, 并由MapServer定時保存角色的相關數據。
5. BaseAppMgr
基礎服務器管理器,主要負責分配基礎服務器給到客戶端連接,同時它采用某種策略可以實現用戶的均衡負載等。
6. CellAppMgr
支線服務器管理器, 它主要根據支線ID為基礎代理對象分配支線服務器實體, 這樣就可以實現與客戶端的通信了。
7. DB
數據庫服務器主要分為三個庫來存取,AccountDB為玩家賬戶信息,CharacterDB為玩家角色相關信息,WorldDB為所胡的世界數據。
2009年12月15日
1. 調試快捷鍵
2. 編輯快捷鍵
3. 代碼快捷鍵
4. 窗口快捷鍵
Ctrl+Tab 切換編輯主窗口中的代碼選項頁
Esc鍵關閉當前的非模式窗口 (比方說 查找窗口, 切誤用 Alt + F4, 這樣會連VS.net2005都關掉)
F5: 啟動調試
Ctrl+F5: 開始執行(不調試)
Shift+F5: 停止調試
Ctrl+Shift+F5: 重啟調試
F7: 生成解決方案
Ctrl+U: 生成當前項目
F11: 獨句調試
F10: 過程調試
F9: 打斷點 或 取消斷點
Alt+F9: 顯示斷點窗口
F12: 定位到函數的實現體
???: 定位到函數的聲明, 只能用右鍵--->選擇A
Shift+Alt+Enter: 切換全屏編輯
Ctrl+F: 查找
Ctrl+Shift+F: 在文件中查找
F3: 查找下一個
Shift+F3: 查找上一個
Ctrl+H: 替換
Ctrl+Shift+H: 在文件中替換
Ctrl+左右箭頭鍵: 一次可以移動一個單詞
Ctrl+上下箭頭鍵: 滾動代碼屏幕,但不移動光標位置。
Ctrl+L: 刪除當前行
Ctrl+M,M: 隱藏或展開當前嵌套的折疊狀態
Ctrl+M,L: 將所有過程設置為相同的隱藏或展開狀態 或者 右鍵 + L + O
Ctrl+M,P: 停止大綱顯示 或者再按一次Ctrl+M,L 或者 右鍵 + L + O
Ctrl+G: 轉到指定行
Shift+Alt+箭頭鍵: 選擇矩形文本
Alt+鼠標左按鈕: 選擇矩形文本
Ctrl+Shift+U: 全部變為大寫
Ctrl+U: 全部變為小寫
//有點難度的:
Ctrl+K, Ctrl+C: 注釋
Ctrl+K, Ctrl+U: 解除注釋
Ctrl+Shift+A: 添加新項
//----------------------------------------
VAssistX: 操作
1. 定義標準的代碼段, 并設置快捷鍵
2. Alt+M, 再打上函數名的前幾個字母, 就會過濾掉很多函數, 最后按Enter定位到當前文件所查找的函數體
3. Shift+Alt+O, 查找文件, 輸入文件名的前幾個字母, 就會過濾掉很多文件, 最后按Enter定位到當前文件所查找的文件
4. Alt+O: 切換到.h和.cpp文件
5. 右鍵+查看所有引用 或 VassistX菜單中的Find References, Find References in Files : 查看函數引用的所有地方
6. Shift+Alt+S: 查找全局變量
//----------------------------------------
F7: 查看代碼
Shift+F7: 查看窗體設計器
Windows鍵+E 打開資源管理器。
Windows鍵+D 顯示桌面。
Windows鍵+M 最小化所有被打開的窗口。
Alt+Tab 切換任務欄中的激活窗口
2009年12月11日
SPXG // 標識
// 用到5張圖片:
{
blendTexture = 0
layer0Texture = 1
layer1Texture = 2
layer2Texture = 3
layer3Texture = 4
}
!!ARBfp1.0
// 定義常量:
PARAM c[1] = { { 1, 0.30000001, 0.69999999 } };
// 聲明3個寄存器:
TEMP R0;
TEMP R1;
TEMP R2;
// 開始混合:
// 一.第0層和第1層使用第4張圖的x通道作為alpha進行混合:
TEX R1, fragment.texcoord[0], texture[0], 2D;
TEX R0, fragment.texcoord[1], texture[1], 2D;
ADD R2, R0, -R1;
// r2=Tex1-Tex0
TEX R0, fragment.texcoord[4], texture[4], 2D;
MAD R2, R0.x, R2, R1;
// r2 = Tex4.x*r2+Tex0
// 說明:
// 其中的Tex4.x是對應第1層alpha值, 下面把Tex4.x當a1看
// 即 r2 = a1 * (Tex1-Tex0) + Tex0
// 轉換一下即是: Tex0*(1-a1)+a1*Tex1, 呵呵,看到了吧,這就是混合公式!
// 二.第2層和前面結果使用第4張圖的y通道作為alpha進行混合:
TEX R1, fragment.texcoord[2], texture[2], 2D;
ADD R1, R1, -R2;
// r1=Tex2-r2
MAD R2, R0.y, R1, R2;
// r2 = Tex4.y*r1+r2, 即 a2*r1+r2 = a2*(Tex2-r2)+r2, 即r2*(1-a2)+Tex2*a2, 其中r2即是上次0和1層混合后的結果
// 三.第3層和前面結果使用第4張圖的z通道作為alpha進行混合:
TEX R1, fragment.texcoord[3], texture[3], 2D;
ADD R1, R1, -R2;
// r1=Tex3-r2
MAD R1, R0.z, R1, R2;
// r1=a3*r1+r2 , 即 a3*r1+r2 = a3*(Tex3-r2)+r2, 即r2*(1-a3)+Tex3*a3
// 這樣r1就保存了最終的混合結果
// 四.讓陰影地表光澤系數為0(Tex4.w即a通道代表地形的陰影,0為陰影,1為正常.而每層貼圖中的a通道是光澤通道,所以R1.w保存的是最終的光澤通道值):
MUL R0.x, R1.w, R0.w;
// r0.x = r1.w(光澤)*Tex4.w(陰影值, 是陰影則=0,否則=1)
// 計算削減系數?
MAD R0.w, R0, c[0].y, c[0].z; // r0乘上0.3(削減了30%)再加上一個常量0.69999999
// 反射高光 = secondary_color(反射光)*光澤:
MUL R0.xyz, R0.x, fragment.color.secondary;
// 貼圖最終color = 最終貼圖混出的color*削減系數:
MUL R1.xyz, R1, R0.w;
// 貼圖最終color * primary_color(光照色或是頂點色) + 反射高光:
MAD result.color.xyz, R1, fragment.color.primary, R0;
// alpha:
MOV result.color.w, c[0].x;
END
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/flipcode/archive/2008/03/03/2143452.aspx
2009年12月5日
一直都沒勇氣去寫一個游戲引擎,也許是太懶了,也許是太大了。雖然寫了些零零碎碎的Test Case! 但你知道的,這始終不成氣候!
最近我被安排到一個小組中,做的項目是一個體感游戲,之前考慮過用ogre開源引擎來做,可惜我對這東西又不熟的,聽人家說還要用第三方類庫CEGUI, 還要配置一些東西,一聽頭都大了,光學習理想都用半個月了,我們的工程只給兩個月的時間,唉呀!算了吧,自己不是還有些亂七八糟的代碼可用嗎?狠下決心,決定干它一把!哪怕辛苦一點,做下來一個游戲引擎以后就容易干事了。
經過和另外一個同事兩個月的時間奮斗,一路中雖說遇到些困難,也常加班的,項目終于也做完了。



一個游戲引擎原型基本蛋生了,我們命名它為 RedLight,它基本實現了
(0) Win32程序渲染框架
(1) UI的基本消息交互流程,XML窗體配置管理,UI皮膚配置管理,基本的UI控件庫,多分辯率無縫UI拼圖
(2) 室外場景管理
(3) 攝相機路徑攝像
(4) 3D Max8模型及骨骼動畫導出插件
(5) 模型渲染, 關鍵幀動畫及骨骼動畫控制
(6) 基本的水面反射效果
(7) 簡單的面粒子系統
(8) 聲音控制接口
這是一個單機游戲引擎的原型,功能有限,但它總算不辱使命完成了一個項目了,以后再擴展使它日益強大吧!
2009年12月1日
我喜歡玩WOW的UI風格,尤其是它的高清字體-------任務窗口的內容總感覺十分舒暢。這是從魔獸世界中任務窗口的游戲截圖

經研究, 其實,上面的字體是用方正楷體(不是windows楷體)字體來做的, 用Freetype類庫得到每個字符的對應圖,并組裝排版到一張預先創建好的空紋理中(256*256), 組成一張灰度圖,這樣可以防止重復字符貼圖兩遍,提高效率,就像活字印刷術一樣靈活組合。
渲染的時候,采用字體顏色RGB + 灰度圖合成最終效果。
也許你會發現,左圖為什么這么清晰,右圖好像稍差一點。對了,其實人的眼睛很奇怪,對顏色的識別是通過對比的,wow正是利用這一點,背景色采用以黃色為主色調,夾雜一些噪聲,然后再渲上黑色的文字,這樣可以給人感覺到很清晰。
其實wow中還是其他一些文字效果,比方說:陰影文字,原理是渲染兩次同一字符,并在第二次遍渲染作偏移即可。當然,還有那種字體外邊包起來的效果,不過這種我還沒研究它的算法。
怎么說呢?一個游戲世界中,字體我覺得相當重要,豐富的字體表現會給人相當美滿的效果,帶給人美好的印象,所以還是相當重要的。我也在做我的文字配置系統,祝福我早日完成吧! 謝謝!
2009年11月14日
作為一個3D程序員, 我用了OpenGL兩年多, 最近在搞一個項目, 從OpenGL轉到D3D, 雖然工程外在的框架都封裝得不錯, 但想完全地從OpenGL轉換到D3D, 看起來還是有難度的, 花了我兩個星期的時間, 我終于轉換過來了。
D3D與OpenGL的幾點比較明顯不同的地方:
(一)、正交投影時:OpenGL以屏幕左上角為(0,0), 而D3D卻以屏幕中心為(0,0)
(二)、OpenGL使用右手坐標系, 而D3D使用左手坐標系
(三)、OpenGL使用旋轉操作等轉入的角度參數是 角度, 而D3D是 弧度,所以注意要PI * Angle / 180
下面我把具體地API對照關系列出來(不是很全,以后添加中.......)
1. 坐標變換
pos = D3DXVECTOR3(0,2,-1.5);
at = D3DXVECTOR3(0,0,0);
up = D3DXVECTOR3(0,1,0);
D3DXMatrixLookAtLH(&view,&pos,&at,&up);
pd3dDevice->SetTransform(D3DTS_VIEW,&view);
2. 繪制
pd3dDevice->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME);
DrawPrimitive()
DrawIndexedPrimitive()
DrawPrimitiveUP()
DrawIndexedPrimitiveUP()
3. 顏色
4. 片段測試
(1) 深度測試
g_pDevice->SetRenderState(D3DRS_ZENABLE, TRUE); //glEnable(GL_DEPTH_TEST);
g_pDevice->SetRenderState(D3DRS_ZFUNC, D3DCMP_LESSEQUAL); //glDepthFunc(GL_LEQUAL);
//--------------------------------------------------------------------------------------------------------
g_pDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW); //glEnable(GL_CULL_FACE);
(2) Alpha測試
//--------------------------------------------------------------------------------------------------------
g_pDevice->SetRenderState(D3DRS_ALPHATESTENABLE, TRUE); //glEnable(GL_ALPHA_TEST);
g_pDevice->SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATER); //glAlphaFunc(GL_GREATER, 0.1f);
g_pDevice->SetRenderState(D3DRS_ALPHAREF, 0.1 * 255); //取值范圍 0 ~ 255
(3) 剪裁測試 (平面剪切)
//--------------------------------------------------------------------------------------------------------
// Enable clip plane for reflection map
CMatrix44f pWorldViewProjIT=m_pWorldViewProj;
//pWorldViewProjIT.Transpose();
pWorldViewProjIT.Invert();
// Transform plane to clip-space
float pClipSpacePlane[4];
float pClipPlane[]= { 0, 0, 1, 0};
// Check if camera is below water surface, if so invert clip plane
CVector3f pEye=(CVector3f)m_pCamera.GetPosition();
if(-pEye.m_fZ<0.0)
{
pClipPlane[2]=-pClipPlane[2];
}
MatrixTransformPlane(pClipSpacePlane, pClipPlane, pWorldViewProjIT);
// enable clip plane now
g_pDevice->SetClipPlane(0, pClipSpacePlane);
g_pDevice->SetRenderState(D3DRS_CLIPPLANEENABLE, 1);
(4) 模板測試
//--------------------------------------------------------------------------------------------------------
g_pDevice->SetRenderState(D3DRS_STENCILENABLE, TRUE);
g_pDevice->SetRenderState(D3DRS_STENCILFUNC, 3DCMP_ALWAYS);
g_pDevice->SetRenderState(D3DRS_STENCILREF, 0x1); //取值范圍 0 ~ 255
Device->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_KEEP);
5. 紋理操作
g_pDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
g_pDevice->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
g_pDevice->SetSamplerState( 0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);
g_pDevice->SetSamplerState( 0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
g_pDevice->SetSamplerState( 0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);
6. 緩沖區操作
(1) 顏色緩沖
//--------------------------------------------------------------------------------------------------------
g_pDevice->SetRenderState(D3DRS_COLORWRITEENABLE, D3DCOLORWRITEENABLE_ALPHA);
g_pDevice->SetRenderState(D3DRS_COLORWRITEENABLE, 0x000000F);
(2) 深度緩沖
//--------------------------------------------------------------------------------------------------------
g_pDevice->SetRenderState(D3DRS_ZENABLE, TRUE); //glEnable(GL_DEPTH_TEST);
g_pDevice->SetRenderState(D3DRS_ZWRITEENABLE, TRUE); //glDepthMask(GL_TRUE);
(3) 模板緩沖
//--------------------------------------------------------------------------------------------------------
(4) 渲染到紋理
//--------------------------------------------------------------------------------------------------------
// Render targets
IDirect3DSurface9 *m_plD3DBackbufferSurf,
*m_plD3DDepthStencilSurfAA,
*m_plD3DDepthStencilSurf;
CRenderTarget *m_pRTRefraction, *m_pRTReflection; //(自定義紋理類)
//-----------------------------------------------------------------------------------
// Get backbuffer
g_pDevice->GetRenderTarget(0, &m_plD3DBackbufferSurf);
// Get depthstencil
g_pDevice->GetDepthStencilSurface(&m_plD3DDepthStencilSurfAA);
// Restore previous states
g_pDevice->SetRenderTarget(0, m_plD3DBackbufferSurf);
g_pDevice->SetDepthStencilSurface(m_plD3DDepthStencilSurfAA);
// (1)折射圖--------------------------------------------------------------------------
//下面的語句調用了 g_pDevice->CreateRenderTarget(iWidth, iHeight, (D3DFORMAT) iFormat, (D3DMULTISAMPLE_TYPE)iAASamples, 0, 0, &m_plD3Surf, 0));
if(FAILED(m_pRTRefraction->Create(m_fWidth>>1, m_fHeight>>1, D3DFMT_A8R8G8B8)))
{
return APP_ERR_INITFAIL;
}
// Create depthstencil withouth multisampling
g_pDevice->CreateDepthStencilSurface(m_fWidth, m_fHeight, D3DFMT_D24X8, (D3DMULTISAMPLE_TYPE)0, 0, 0, &m_plD3DDepthStencilSurf, 0);
g_pDevice->SetRenderTarget(0, m_pRTReflection->GetSurface());
g_pDevice->StretchRect(m_plD3DBackbufferSurf, 0, m_pRTRefraction->GetSurface(), 0, D3DTEXF_NONE);
// (2)反射圖-----------------------------------------------------------------------------------
m_pRTReflection=new CRenderTarget;
if(FAILED(m_pRTReflection->Create(m_fWidth>>2, m_fHeight>>2, D3DFMT_A8R8G8B8)))
{
return APP_ERR_INITFAIL;
}
g_pDevice->SetRenderTarget(0, m_pRTReflection->GetSurface());
//-----------------------------------------------------------------------------------
g_pDevice->SetRenderTarget(0, m_pRTReflection->GetSurface());
g_pDevice->SetDepthStencilSurface(m_plD3DDepthStencilSurf);
g_pDevice->Clear(0, 0, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_ARGB(255, 0, 0, 128), 1.0f, 0);
SetViewport(m_pRTReflection->GetWidth(), m_pRTReflection->GetHeight());
//-----------------------------------------------------------------------------------
D3DXSaveTextureToFile("imageTex.jpg",D3DXIFF_JPG,(IDirect3DTexture9*)m_pWavesBump->GetTexture(),NULL);
7. 混合操作
g_pDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE); //glDisable(GL_BLEND);
g_pDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); //glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
g_pDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
8. 燈光與材質
g_pDevice->SetRenderState(D3DRS_LIGHTING, FALSE); //glDisable(GL_LIGHTING);
D3DMATERIAL9 mtrl;
mtrl.Ambient = a;
mtrl.Diffuse = d;
mtrl.Specular = s;
mtrl.Emissive = e;
mtrl.Power = p;
Device->SetMaterial(&mtrl); //在設置紋理前設定
//設置當前使用的紋理
2009年9月25日
wmo(wow map object) research
The wmo是一個非常有趣的設計,wow中比較小的物體使用doodad,而building使用wmo,
這里的building可以是橋梁、了望臺、簡單的小房子、復雜點的旅館這樣的房屋、非常
復雜的建筑群(例如地下城場景),本文對wmo場景文件進行簡單的介紹,關于wmo文件的具
體信息請參考wowmapview的source code,這里非常感謝ufoz所做的貢獻。
1、命名規則
wmo保存在以.wmo結尾的文件中,這個文件使用數據塊來保存數據。一個wmo通常由一個或多個
group組成,而group數據也保存在以.wmo結尾的文件中,不過文件名稱存在不同,例如一個wmo
保存在name.wmo文件中,那么group文件名就為name_001.wmo name_002.wmo......
2、結構
wmo就組織結構來說包含兩個層次,group和batch。一個group通常包含多個batch,其中group包含
一個AABB。batch是wmo最小的渲染單元,它保存了頂點索引列表,可以直接調用DP進行渲染。group
內保存一個標志位,可以將group分為indoor/outdoor兩類,這一個信息非常重要,通過它wmo就將
building分成了內外兩部分,outdoor group就是building的外殼,他通過portal與室內場景連接
在一起。
3、portal
wmo文件中保存了portal信息,在wmo中規定group必須通過portal進行連接,portal由PVS和PRS
兩部分組成,PVS記錄portal頂點信息,PRS記錄portal和group的連接信息,PRS結構如下:
struct WMOPR {
int portal;
int group;
int dir;
};
需要注意的是dir,這個成員只有兩個值-1或1,由于portal的頂點信息按照順時針記錄,因此group
位于portal的正面時dir為1,否則為-1,通過dir可以快速確定group到底位于portal的哪一側。
通過wmo中記錄的portal信息可以使用portal culling來檢查group的可見性,但是這里還是有一些
難度,主要是指portal的記錄方式。由于一個group可能有多個portal,而查找連接的portal只能
通過PRS,這樣在大的場景中非常不方便。而且在wmo中竟然沒有記錄portal的plane信息,如何確定
camera到底是位于portal的正面還是反面呢?(現在wmo文件由于沒有完全破解,存在一些wowmapview
未讀入的數據塊,例如MVER、MOPT、MOVV、MOVB等,其中MVER應該是wmo文件的版本號,MOPT懷疑是保
存所有plane信息,而MOVV可能是保存包圍體頂點信息,而MOVB保存包圍體信息,MOVV、MOVB應當用于
碰撞檢測,這些暫時沒有驗證)我的做法是在載入時計算portal的plane信息,并將PRS信息轉換為類似
Q3 BSP中portal的結構。
struct portal_t {
int othergroup;
int pvs;//pvs index
int dir;
};
struct group_t {
int firstportal;
int numportals;
};
4、碰撞檢測
在wmo中并沒有使用BSP、OC TREE這樣的結構來進行場景管理,可能所有人都感覺非常困惑。
場景管理的功能主要是為了加速渲染和方便碰撞檢測,由于存在portal,這樣第一個功能已經完成。
而對于碰撞檢測,我的想法應當是AABB TREE。仔細觀察WOW的場景可以發現在indoor場景中曲面、斜面
這樣的幾何物體非常少,大多數是規則物體,因此可以判斷在wmo中所有的物體都是嚴格按照軸對齊
方式進行建模,也就是對規則性物體AABB=OBB。由于MOVV和MOVB信息并沒有完全研究透徹,因此關于
這一部分只能是我的猜測。
5、渲染
對wmo的渲染由于batch的存在從而變的簡單化,但還有可以優化的地方。由于wmo中使用portal將其分割
成group,因此有大量的材質相同的model被分割成不同的batch,在渲染時將材質相同的batch合并到一起
渲染可以避免一些無謂的DP調用。wmo一個令人詬病的地方是使用vertex light,為了減少圖元數量從而
使頂點數量降低,造成渲染的時候出現色帶效果,應當加入lightmap,由于wmo的場景通常不大,預處理
時做radiosity的時間也不會太長。
6、動態載入
對于只包含幾個group的小場景的wmo,由于載入時間不是太長,在動態載入時一次性載入對程序影響
并不會太大。但是對于超大場景的wmo就需要考慮載入策略,這樣場景典型的就是wow中的地下城場景,
它一個wmo中包含了幾百個group,一次性載入時間非常長,需要分段進行載入。此時就顯示出wmo分
文件保存group的優勢了,為了實現動態載入wmo場景,一種可能的做法是在載入wmo后需要根據camera
所在的group快速的建立group連接層次圖,這個圖通過PRS數據建立,建立流程如下:
一、將camera所在group作為當前group,獲得所有相連的protal;
二、將protal連接的group保存到第一層列表中,遍歷第一層列表中所有的group;
三、獲得第一層列表中group的portal,檢查portal所連接的group是否保存在第一層列表中,如果沒有
將其保存到第二層列表中;
四、重復上述過程,直到整個層次圖建立。
這個層次圖可以預先建立然后保存到文件中運行時載入,這樣wmo就是分層載入而不需要一次性載入。
(這里我考慮是否在wmo中也可以建立類似bsp的pvs數據呢?雖然pvs現在已經開始淘汰,但是如果
存在pvs就可以方便確定哪些group需要立即載入,只是不知被portal分割后的group到底是不是convex
hull,如果是的話可以建立pvs,但對建模時限制更加明顯,兩難的選擇!!!)
7、建模
由于wmo是按照group對場景進行保存,因此為了建立wmo需要設計一個強力的模型構建工具,這個工具
主要功能就是對從建模工具(3DS MAX)中建立的場景模型進行分組和處理。美工在制作模型時需要非常
小心,所有的模型要嚴格的軸對齊(軸對齊的原因是需要模型的AABB=OBB),然后將模型導入工具中。
模型構建工具有以下功能:分組(group)功能、group選擇、group顯示/隱藏、指定portal,portal對
齊(考慮門、窗戶這樣天然的portal,手動指定portal時肯定無法與外表墻壁對齊,需要程序自動對齊)
、batch操作(分割、選擇、顯示/隱藏等)、圖元級操作(triangle揀選,用于batch分割)、光照運算
(產生vertex light數據)、放置光源、放置doodad(場景中的道具,如桌子、椅子等)。可能還需要其
他一些功能,但是對比其他引擎的場景建模工具(hammer、sandbox)明顯簡單化許多。
8、優勢及不足
當前處理室內場景的主流技術依然是bsp,但是隨著硬件的發展bsp的優勢在慢慢地喪失,bsp賴以生存的
預處理PVS現在已經完全被實時的portal culling所取代,bsp優勢只剩下對圖元排序(用于透明物體的
渲染)和基于brush的快速碰撞檢測上,但是對比建模工具的復雜化和場景的限制,采用bsp的開銷確實
顯得太大。而基于純portal引擎的結構開始流行,例如cryengine中處理室內場景時就完全拋棄bsp,
場景完全由一塊塊固定大小的墻壁組成,一塊墻壁基本和bsp中brush類似,這樣做的好處是建模工具變
的簡單(不需要進行CSG運算),而且非常容易的產生portal,同時由于場景使用brush構成也兼具了
bsp方便進行碰撞檢測的優勢。wmo有些類似cryengine,但是在某些方面更具優勢。
首先在建模方面,wmo的場景完全可以通過成熟的建模工具來構建,這樣對于美工不需要重新學習新的
建模工具,可以節約大量的時間。其次模型構建工具需要的功能非常少,減少了程序的復雜性,縮短了
編寫相關工具的時間。再次,場景管理簡單化,相應代碼量大幅度減少,同時由于portal的存在,可以方便
的與其他引擎相對接。可以說wmo是一種可以進行快速開發的場景結構。