遊戲開發基礎(十一)

 

第十一章
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);

Mesh對象存儲從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//返回外接體的最大點
     );

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章