第十一章
ID3DXBuffer介面是一種泛型資料結構,該介面為D3DX庫所使用,並可將資料存放區在一個連續的記憶體塊中
該介面只有兩個方法
LPVOID GetBufferPointer();返回指向緩衝中資料起始位置的指標
DWORD GetBufferSize();返回緩衝的大小,單位為位元組
為了保持該介面的通用性,該介面使用了void類型的指標。所以必須由我們來實現被儲存的資料類型
例:
D3DXLoadMeshFromX函數用ID3DXBuffer類型的指標返回一個網格對象的鄰接資訊
由於鄰接資訊被儲存在一個DWORD類型的數組中,所以當我們想要使用該介面的鄰接資訊時,我們必須對該緩衝進行強制類型轉換
例:
DWORD *info = (DWORD*)adjacencyInfo->GetBufferPointer();
D3DXMATERIAL* mtrls = (D3DXMATRIAL*)mtrlBuffer->GetBufferPointer();
由於ID3DXBuffer 是一個COM對象,該介面使用完畢之後必須將其釋放,以防記憶體泄露
adjacencyInfo->Release();
mtrlBuffer->Release();
我們可用如下函數建立一個空的ID3DXBuffer對象
HRESULT D3DXCreateBuffer(DWORD NumBytes,LPD3DXBUFFER* ppBuffer);
建立一個能容納4個資料的緩衝
ID3DXBuffer* buffer = 0;
D3DXCreateBuffer(4*sizeof(int),&buffer);
.x格式是DirectX定義的格式,該格式得到了D3DX庫的有力支援,D3DX庫提供了對XFile格式的檔案進行載入和儲存的函數
該法建立一個ID3DXMesh對象並將XFile中的幾何資料載入到該對象中
HRESULT D3DLoadMeshFromX(LPCSTR pFileName,
DWORD Options,
LPDIRECT3DDEVICE9 pDevice,
LPD3DXBUFFER* ppAdjacency,
LPD3DXBUFFER* ppMaterials,
LPD3DXBUFFER* ppEffectInstances,
PDWORD pNumMaterials,
LPD3DXMESH *ppMesh);
#pFileName XFile檔案名稱
#Options 建立網格時所使用的建立標記
*D3DXMESH_32BIT 網格將使用32位索引
*D3DXMESH_MANAGED 網格資料將被儲存於託管記憶體池中
*D3DXMESH_WRITEONLY 指定網格資料位元唯讀
*D3DXMESH_DYNAMIC 網格緩衝將使用動態記憶體
#pDevice 與該網格對象相關的裝置指標
#ppAdjacency返回一個ID3DXBuffer對象,該對象包含了一個描述了該網格對象的鄰接資訊的DWORD類型數組
#ppMaterials返回一個ID3DXBuffer對象,該對象包含了一個儲存了該網格的材質資料的D3DXMATERIAL類型的結構數組
#ppEffectInstances返回一個ID3DXBuffer對象,該對象包含了一個D3DXEFFECTIN-STANCE結構
#pNumMaterials返回網格中的材質數目(即由ppMaterials輸出的D3DXMATERIAL數組中的元素個數)
#ppMesh返回所建立的並已填充了XFile幾何資料的ID3DXMesh對象
D3DXLoadMeshFromX函數中的第7個參數返回了該網格對象所含的材質數目,第5個參數返回了一個儲存了材質資料的D3DXMATRIAL類型的結構數組
typedef struct D3DXMATERIAL{
D3DMATERIAL9 MatD3D;
LPSTR pTextureFileName;//指定了網格相關紋理的檔案名稱
}D3DXMATERIAL;
XFile檔案中並未儲存紋理資料,只包含了紋理影像檔名,該檔案名稱是對包含了實際紋理資料的紋理映像的引用,當用D3DXLoadMeshFromX函數載入了一個XFile檔案後,必鬚根據指定的紋理檔案名稱載入紋理資料。
D3DXLoadMeshFromX函數載入XFile資料後,返回的D3DXMATERIAL結構數組中的第i項與第i個子集想對應,所有我們將各子集按照0,1,2,...n-1的順序進行標記,其中n是子集和材質的總數,這樣可用一個簡單的迴圈對全部子集進行遍曆和繪製,從而完成整個網格的繪製
例:
ID3DXMesh* Mesh = 0;
std::vector<D3DMATERIAL9> Mtrls(0);
std::vector<IDirect3DTexture9*> Textures(0);
MeshObject Storage Service從XFile中載入的網格資料
Setup函數,記載XFile檔案
bool Setup()
{
HRESULT hr = 0;
ID3DXBuffer* adjBuffer = 0;
ID3DXBuffer* mtrlBuffer = 0;
DWORD numMtrls = 0;
hr = D3DXLoadMeshFromX("bigship1.x",
D3DXMESH_MANAGED,
Device,
&adjBuffer,
&mtrlBuffer,
0,
&numMtrls,
&Mesh);
if(FAILED(hr))
{
::MessageBox(0,"D3DXLoadMeshFromX()-FAILED",0,0);
return false;
}
//載入XFile資料後,必須遍曆D3DXMATERIAL數組中的元素,並載入該網格所引用的紋理資料
if(mtrlBuffer != 0 && numMtrls != 0)
{
D3DXMATERIAL* mtrls = (D3DXMATERIAL*) mtrlBuffer->GetBufferPointer();
for(int i = 0;i < numMtrls;i++)
{
//the MatD3D property does't have an ambient value set
// when its loaded ,so set it now
mtrls[i].MatD3D.Ambient = mtrls[i].MatD3D.Diffuse;
//save the ith material
Mtrls.push_back(mtrls[i].MatD3D);
//check if the ith material has an associative texture
if (mtrls[i].pTextureFileName != 0)
{
//yes load the texture for the ith subset
IDirect3DTexture9* tex = 0;
D3DXCreateTextureFromFile(Device,mtrls[i].pTextureFileName,&tex);
//save the loaded texture
Textures.push_back(tex);
}
else
{
//no texture for the ith subset
Textures.push_back(0);
}
}
}
d3d::Release<ID3DXBuffer*>(mtrlBuffer);
return true;
}
在Display函數中,在每一幀映像中對網格做了略微的旋轉,以使其呈現旋轉的動畫效果,由於網格的個子集已按0,1,2.。。n-1的順序標記(n為子集總數),整個網格的繪製通過一個簡單的迴圈即可完成
bool Display(float timeDelta)
{
if (Device)
{
static float y = 0.0f;
D3DXMATRIX yRot;
D3DXMatrixRotationY(&yRot,y);
y += timeDelta;
if (y >= 6.28f)
y = 0.0f;
D3DXMATRIX World = yRot;
Device->SetTransform(D3DTS_WORLD,&World);
Device->Clear(0,0,D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,0xffffffff,1.0f,0);
Device->BeginScene();
for (int i = 0; i < Mtrls.size();i++)
{
Device->SetMaterial(&Mtrls[i]);
Device->SetTexture(0,Textures[i]);
Mesh->DrawSubset(i);
}
Device->EndScene();
Device->Present(0,0,0,0);
}
return true;
}
我們可用如下方法來產生任意網格的頂點法向量
HRESULT D3DXComputeNormals(LPD3DXBASEMESH pMesh,//Mesh to compute the normals of
CONST DWORD *pAdjacency //Input adjacency info
);
該函數通過法向量平均的方法產生頂點的法向量,如果提供了鄰接資訊,重疊的頂點就會被剔除,如果沒有提供鄰接資訊,則重疊頂點的法向量由該頂點所依附的各面在該點的局部法向量取平均而得到。很重要的一點是,傳入的參數pMesh的頂點格式中必須包含標記D3DFVF_NORMAL
如果XFile文庫不含頂點法線資料,則由函數D3DXLoadMeshFromX所建立的ID3DXMesh網格對象在其頂點格式中將不含D3DFVF_NORMAL標記,所以在使用函數D3DXComputeNormals之前,必須複製該網格,並為其指定一個包含了D3DFVF_NORMAL標記的頂點格式
例:
if(!(pMesh->GetFVF() & D3DFVF_NORMAL))
{
ID3DXMesh* pTempMesh = 0;
pMesh->CloneMeshFVF(
D3DXMESH_MANAGED,
pMesh->GetFVF() | D3DFVF_NORMAL,// add it here
Device,
&pTempMesh);
//compute the normals
D3DXComputeNormals(pTempMesh,0);
pMesh->Release();//get rid of the old mesh
pMesh = pTempMesh;// save the new mesh with normals
}
漸進網格用ID3DXPMesh介面來表示,它允許運用一系列的邊摺疊變換(Edge Collapse Transformations,ECT)對網格進行簡化,每次ECT都移除一個頂點以及一個或兩個面,由於每次ECT都是可逆的(其逆運算稱為頂點分裂),可對簡化過程進行逆轉,從而將網格精確恢複到初始狀態,當然,意味著我們無法獲得比原始網格更豐富的細節,只能對網格進行簡化及其逆運算。
漸進網格的思路與紋理中的多級漸進紋理類似
HRESULT D3DXGeneratePMesh(
LPD3DXMESH pMesh,
CONST DWORD *pAdjacency,
CONST D3DXATTRIBUTEWIGHTS* pVertexAttributeWeights,
CONST FLOAT * pVertexWeights,
DWORD MinValue,
DWORD Options,
LPD3DXPMESH *ppPMesh);
#pMesh 該輸入變數包含了網格資料,漸進網格將根據此網格產生
#pAdjacency 指向包含了pMesh的鄰接資訊的DWORD類型的數組指標
#pVertexAttributeWeights 指向D3DXATTRIBUTEWEIGHTS 類型的結構數組指標,該數組的維數為pMesh->GetNumVertices(),該數組的第i項對應於pMesh中的第i個頂點,並指定了相應頂點的屬性權值,頂點屬性權值用於決定在簡化過程中頂點被移除的機率,可以為參數傳入NULL,則每個頂點將被賦予預設的屬性權值。
#pVertexWeights指向一個float類型數組的指標,該數組的維數為pMesh->GetNumVertices(),該數組的第i項對應於pMesh中的第i個頂點,並指定了相應頂點的權值,一個頂點權值越高,在簡化過程中被移除的機率就越小,可將該參數賦為NULL,這樣每個頂點就會被賦予預設的頂點權值1.0
#MinValue 網格中的頂點數或面片數(有下一個參數Options決定)可被簡化到的下限,該值只是一種期望值,實際還有依賴頂點權值或屬性權值,所以簡化結構可能與該值不一致
Options該參數實際上是D3DXMESHSIMP枚舉類型的一個成員
*D3DXMESHSIMP_VERTEX 指定了前面的參數MinValue是指頂點數
*D3DXMESHSIMP_FACE指定了前面參數MinValue是指面片數
ppPMesh返回產生的漸進網格
頂點權值結構允許為頂點的每個可能的分量指定一個權值,如果某個分量的權值被賦為0.0,則表明該分量無權值,頂點分量的權值越高,在簡化過程中該頂點被移除的機率越小
頂點權值結構:
typedef struct D3DXATTRIBUTEWEIGHTS{
FLAOT Position;
FLOAT Boundary;
FLOAT Normal;
FLOAT Diffuse;
FLOAT Specular;
FLOAT Texcoord[8];
FLOAT Tangent;
FLOAT Binormal;
}D3DXATTRIBUTEWEIGHTS,*LPD3DXATTRIBUTEWEIGHTS;
D3DXATTRIBUTEWEIGHTS AttributeWeights;
AttributeWeights.Position = 1.0;
AttributeWeights.Boundary = 1.0 ;
AttributeWeights.Normal = 1.0;
AttributeWeights.Diffuse = 0.0;
AttributeWeights.Specular = 0.0;
AttributeWeights.Tex[8] = {0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0};
建議使用預設值
ID3DXPMesh介面:
DWORD GetMaxFaces(VOID);返回漸進網格面片數可被指定的上限
DWORD GetMaxVertices(VOID);返回漸進網格頂點數可被指定的上限
DWORD GetMinFaces(VOID);返回漸進網格面片數可被指定的下限
DWORD GetMinVertices(VOID);返回漸進網格頂點數可被指定的下限
HRESULT SetNumFaces(DWORD Faces);允許我們設定網格面片數可被簡化或細化到的個數
例: 網格目前有50個面片,將其簡化到30個
pMesh -> SetNumFaces(30);
調整後,網格面片數可能與期望的面片數不一致,如果Faces小於GetMinFaces(),將取GetMinFaces(),類似地,Faces大於GetMaxFaces,將取GetMaxFaces();
HRESULT SetNumVertices(DWORD Vertices);該方法允許我們設定網格的頂點數可被簡化或細化到的個數
例 網格當前有20個頂點,將其增加細節,將頂點數增加到40個
pMesh->SetNumVertices(40)
調整後,網格頂點數可能與期望的頂點數不一致,規則類似SetNumFaces規則
HRESULT TrimByFaces(
DWORD NewFacesMin,
DWORD NewFacesMax,
DWORD *rgiFaceRemap,//Face remap info
DWORD *rgiVertRemap//Vertex remap info
);
該方法允許重新設定面片數的最小值(NewFacesMin)和最大值(NewFacesMax),區間必須在[GetMinFaces(),GetMaxFaces()]內,該函數同時也返回了面片和頂點的重繪資訊
HRESULT TrimByVertices(
DWORD NewVerticesMin,
DWORD NewVerticesMax,
DWORD *rgiFaceRemap,
DWORD *rgiVertRemap);
該方法允許重新設定頂點數的最小值(NewVerticesMin)和最大值(NewVerticesMax),區間必須在[GetMinVertices(),GetMaxVertices()]內,該函數同時也返回了面片和頂點的重繪資訊
SetNumFaces和SetNumVertices方法允許調整一個網格的LOD
下面該例是建立和繪製的網格是由ID3DXPMesh介面表示的漸進網格
ID3DXMesh* SourceMesh = 0;
ID3DXPMesh* pMesh = 0;
vector<D3DMATERIAL9> Mtrls(0);
vector<IDirect3DTexture9*> Textures(0);
產生漸進網格,必須傳入一個"源"網格,該網格包含了所建立漸進網格將包含的資料,所以,首先將XFile資料載入到一個ID3DXMesh對象SourceMesh中,然後再產生漸進網格
bool Setup()
{
HRESULT hr = 0;
//Load the XFile data
//Extracting materials and textures snipped
// 產生漸進網格
//Generate the progressive mesh
hr = D3DXGeneratePMesh(
SourceMesh,
(DWORD*)adjBuffer->GetBufferPointer(),
0,
0,
1,
D3DXMESHSIMP_FACE,
&PMesh);
d3d::Release<ID3DXMesh*>(SourceMesh);
d3d::Release<ID3DXBuffer*>(adjBuffer);
if(FAILED(hr))
{
::MessageBox(0,"D3DXGeneratePMesh()-FAILED",0,0);
return false;
}
}
如果想將網格面簡化為只有一個面片,則由於頂點/屬性權值的存在,這將不可能實現
但是,將期望的面片數指定為1可將網格簡化至其所能達到的最低解析度
由於首先以最大分辯率將該網格繪製出來,這樣設定:
DWORD maxFaces = PMesh->GetMaxFaces();
PMesh->SetNumFaces(maxFaces);
bool Display(float timeDelta)
{
if (Device)
{
// Update :Mesh resolution
//Get the current number of faces the pmesh has
int numFaces = PMesh->GetNumFaces();
// add a face ,note the SetNumFaces() will automatically
//clamp the specified value if it goes out of bounds
if (::GetAsyncKeyState('A') & 0x8000f)
{
PMesh->SetNumFaces(numFaces + 1);
if (PMesh ->GetNumFaces() == numFaces)
PMesh->SetNumFaces(numFaces + 2);
}
// Remove a face ,note the SetNumFaces() will automatically
// clamp the specified value if it goes out of bounds
if (::GetAsyncKeyState('S') & 0x8000f)
PMesh->SetNumFaces(numFaces -1);
Device->Clear(0,0,D3DCLREAR_TARGET | D3DCLEAR_ZBUFFER,0xffffffff,1.0f,0);
Device->BeginScene();
for (int i = 0 ; i < Mtrls.size(); i++ )
{
Device->SetMaterial(&Mtrls[i]);
Device->SetTexture(0,Textures[i]);
PMesh->DrawSubset(i);
//draw wireframe outline
Device->SetMaterial(&d3d::YELLOW_MTRL);
Device->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME);
PMesh->DrawSubset(i);
Device->SetRenderState(D3DRS_FILLMODE,D3DFILL_SOLID);
}
Device->EndScene();
Device->Present(0,0,0,0);
}
return true;
}
// 增加面片時,有時必須增加兩個面片以保證ECT的逆變換能夠順利進行
計算一個網格的外界體,兩種常見的外接體是外界球和外接體,其他的外接體還有圓柱體,橢球體,棱形體以及膠囊狀容器
外接球或外接體常用於加速可見度檢測和碰撞,如,一個網格的外接體或外接球不可見,就可認為該網格不可見。
檢測外接體的可見度要比檢測網格中每個面片的可見度的代價低得多,例,情境中有一個發射物,要確定該發射物是否會擊中情境中的某一物體,由於物體是由三角形面片構成的,所以需要遍曆每個物體的每個面片,並檢測發射物(數學模型為射線)是否會擊中某一面片,該方法需要進行大量的射線/三角形相交測試,情境中每個物體的每個三角形面片都需要進行一次,一種更高效的途徑是計算出每個網格的(物體)的外接體,然後對每個物體進行射線/外接體相交測試,如果射線與某一物體的外接體相交,就認為該物體被擊中,這是一種很好的近似,如果希望提高檢測精度,可藉助外接體快速排除那些顯然不能被擊中的物體,然後再對那些極有可能擊中的物體使用更精確的方法來檢測
D3DX庫提供一些函數用來計算一個網格的外接球和外接體,這些函數接收一個數組,然後依據這些頂點計算出外接體或外接體。
HRESULT WINAPI D3DXComputeBoundingSphere(
const D3DXVECTOR3 *pFirstPosition,
DWORD NumVertices,
DWORD dwStride,
D3DXVECTOR3 *pCenter,
FLOAT* pRadius);
#pFirstPosition指向頂點數組(該數組的每個元素都描述了對應頂點)中第一個頂點的位置向量的指標
#NumVertices該頂點數組中頂點的個數
#dwStride每個頂點的大小,單位為位元組。該值很重要,因為一種頂點結構可能包含了許多該函數所不需要的附件資訊
如法向量和紋理座標等,這樣該函數就需要知道應該跳過多少位元組才能找到下一個頂點的位置
#pCenter返回外接球的球心位置
#pRadius返回外接球的半徑
HRESULT WINAPI D3DXComputeBoundingBox(
const D3DXVECTOR3 *pFirstPosition,
DWORD NumVertices,
DWORD dwStride,
D3DXVECTOR3 *pMin,//返回外接體的最小點
D3DXVECTOR3 *pMax//返回外接體的最大點
);