yag Site Admin
註冊時間: 2007-05-02 文章: 689
2704.11 果凍幣
|
發表於: 2010-2-24, AM 12:36 星期三 文章主題: 3D遊戲程式設計入門第13章心得-1 |
|
|
前言:此乃補丁文。只講解心得,不提供完整教學,有興趣的人請自行購買此書。
代碼: | 書名:3D遊戲程式設計入門-使用DirectX 9.0實作
作者:Frank D. Luna
譯者:黃聖峰
出版社:博碩文化 |
Camera類別
這個範例也有camera
walk函式及strafe函式請依照第12章的心得所述來修改
另外因為這個範例中,會固定把camera設在地表上5單位處
所以fly函式沒有LANDOBJECT型態的處理
在此補充一下一個第12章心得忘了說的東西
關於camera三視軸旋轉的正向與反向的判定
因為dx預設是左手座標系,所以是以左手姆指定律來判斷的
只要將左手姆指指向跟camera的右軸的方向平行
那麼另外四指往掌內彎曲的旋轉方向即為視軸旋轉的正向
因此,pitch時往下轉即為正,往上轉即為負
yaw時往右轉即為正,往左轉即為負
roll時往左轉即為正,往右轉即為負
D3D命名空間
代碼: | float GetRandomFloat(float lowBound, float highBound); |
此函式的註解說其範圍為[lowBound, highBound]
但實際上我們看其實作方式會發現
代碼: | float f = (rand() % 10000) * 0.0001f; |
這行取得的範圍並不像它註解說的是[0, 1]而是[0, 0.9999]才對
也因此經過計算後,真正的範圍該是[lowBound, highBound)
你永遠不可能得到一個隨機數 == highBound
這是要注意的地方
新增的Lerp函式為
代碼: | float d3d::Lerp(float a, float b, float t)
{
return a - (a*t) + (b*t);
} |
但是將它改成
代碼: | float d3d::Lerp(float a, float b, float t)
{
return a + ( b - a ) * t;
} |
這樣較好
一是因為只做一次乘法應該比較快
二是因為這種寫法比較符合其原意
Lerp就是線性內插,所謂的線性內插,就是按比例取個值而已
比如說,A車在高速公路上以固定速度移動
它從標明30公里處開始,移到80公里處,花了30分鐘
請問它開始移動15分鐘後的位置是在幾公里處?
此題a為30,b為80,t就是15/30,所以可得答案為55公里處
簡單來說,15分鐘是30分鐘的一半,而30公里到80公里是50公里遠
按比例,取一半就是25公里,因為是從標明30公里處開始的,所以答案就是55公里處
應該很好理解吧?公式的意思就是 初值+總量*比例
FPSCounter類別
_fpsString屬性需要先初始化
不然render時在第一秒內它的值會是亂七八糟的字串
雖然不會顯示出來,但總是一個隱患
請在FPSCounter::FPSCounter(IDirect3DDevice9* device)建構子的最下面加上:
代碼: | ZeroMemory( _fpsString, sizeof( _fpsString ) );
_fpsString[0] = '0'; |
這會讓它在第一秒內顯示出0
Terrain類別
這個類別要改的就多了
首先在terrain.h檔中,請把
代碼: | int getHeightmapEntry(int row, int col); |
改成
代碼: | float getHeightmapEntry(int row, int col); |
代碼: | void setHeightmapEntry(int row, int col, int value); |
改成
代碼: | void setHeightmapEntry(int row, int col, float value); |
代碼: | std::vector<int> _heightmap; |
改成
代碼: | std::vector<float> _heightmap; |
然後在terrain.cpp檔中,請把
代碼: | int Terrain::getHeightmapEntry(int row, int col)
{
return _heightmap[row * _numVertsPerRow + col];
} |
改成
代碼: | float Terrain::getHeightmapEntry(int row, int col)
{
if( row >= _numVertsPerRow || row < 0 || col >= _numVertsPerCol || col < 0 )
return 0.0f;
return _heightmap[row * _numVertsPerRow + col];
} |
以此程式結構來說,_heightmap不一定都會是整數
所以把_heightmap相關的,都從int類型改成float類型會是比較合理的作法
我不太清楚為什麼作者會把它設成int類型,可能只是為了省事吧
另外getHeightmapEntry函式必須要判斷參數的範圍
否則執行時如果走出邊界外,就會造成fatal error,導致程式被中斷
因此設值時也要做相對應的檢查:
代碼: | void Terrain::setHeightmapEntry(int row, int col, int value)
{
_heightmap[row * _numVertsPerRow + col] = value;
} |
改成
代碼: | void Terrain::setHeightmapEntry(int row, int col, float value)
{
if( row >= _numVertsPerRow || row < 0 || col >= _numVertsPerCol || col < 0 )
return;
_heightmap[row * _numVertsPerRow + col] = value;
} |
接著在computeVertices函式中
有個很嚴重的錯誤
我們先把要改的地方看一下後再來講解:
代碼: | // coordinates to start generating vertices at
int startX = -_width / 2;
int startZ = _depth / 2;
// coordinates to end generating vertices at
int endX = _width / 2;
int endZ = -_depth / 2; |
改成
代碼: | // coordinates to start generating vertices at
int startX = -_width;
int startZ = _depth;
// coordinates to end generating vertices at
int endX = _width;
int endZ = -_depth; |
代碼: | for(int z = startZ; z >= endZ; z -= _cellSpacing)
{
int j = 0;
for(int x = startX; x <= endX; x += _cellSpacing)
{
// compute the correct index into the vertex buffer and heightmap
// based on where we are in the nested loop.
int index = i * _numVertsPerRow + j;
v[index] = TerrainVertex(
(float)x,
(float)_heightmap[index],
(float)z,
(float)j * uCoordIncrementSize,
(float)i * vCoordIncrementSize);
j++; // next column
}
i++; // next row
} |
改成
代碼: | for(int z = startZ; z >= endZ; z -= _cellSpacing * 2)
{
int j = 0;
for(int x = startX; x <= endX; x += _cellSpacing * 2)
{
// compute the correct index into the vertex buffer and heightmap
// based on where we are in the nested loop.
int index = i * _numVertsPerRow + j;
v[index] = TerrainVertex(
x / 2.0f,
_heightmap[index],
z / 2.0f,
j * uCoordIncrementSize,
i * vCoordIncrementSize);
j++; // next column
}
i++; // next row
} |
從上面可以看出來,我把用來當成for迴圈iterator的變數都乘以了2
為什麼要這樣做呢?因為
代碼: | int startX = -_width / 2; |
這種方式在_width不為偶數時,會導致整個地圖都崩潰掉
當_width為奇數時,除以2後應該存在的0.5存放到int中就消失了
雖然原範例中以傳入cellSpacing為10的方式讓_width一定可以被2整除
但這純粹是個案,如果傳入5的話,地圖就崩潰掉了(因為_numCellsPerRow跟_numCellsPerCol都是63)
如果單純把startX等變數改成float型態,因為它們是要拿來做迴圈初值與終值
可能會造成迴圈次數的不確定性,也不適合
所以最好的方式就是把它們乘以2,等要存入VertexBuffer時再除以2.0f就好了
至於bool Terrain::genTexture(D3DXVECTOR3* directionToLight)函式中最後面的
代碼: | hr = D3DXFilterTexture(
_tex,
0, // default palette
0, // use top level as source level
D3DX_DEFAULT); // default filter |
這是拿來產生較低mipmap等級的材質用的
雖然書中有說,但範例中沒有註解標明,所以稍提一下
另外書上對灰階地形高度圖(raw檔)做舉例時,是以256x256的大小在舉例
而範例中的註解是以128x128的大小在舉例
可實際上書附的raw檔的大小只有64x64
不要被那些不明的舉例給騙了…
terrainDriver.cpp
bool Display(float timeDelta)函式中
一樣是把W鍵跟S鍵的方向倒過來:
代碼: | if( ::GetAsyncKeyState('W') & 0x8000f )
TheCamera.pitch(1.0f * timeDelta);
if( ::GetAsyncKeyState('S') & 0x8000f )
TheCamera.pitch(-1.0f * timeDelta); |
改成
代碼: | if( ::GetAsyncKeyState('W') & 0x8000f )
TheCamera.pitch(-1.0f * timeDelta);
if( ::GetAsyncKeyState('S') & 0x8000f )
TheCamera.pitch(1.0f * timeDelta); |
這樣操作pitch比較符合直覺 |
|