chap 11 网格(后篇)
如何使用渐近网格接口id3dxpmesh
如何使用d3dx创建外接体
1.ID3DXBuffer
这是一个泛型数据结构,在使用的时候转换成自己需要使用的特定类型。
ID3DXBuffer接口只有两个方法:
使用后要释放(因为他是COM接口)
创建ID3DXBuffer对象:
(1)比较流行的建模工具: 1. 3DS MAX 2. LightWave 3. Maya
(2)加载XFile文件:
(3)XFile材质
(第五个参数)网格的材质数据 包含了一个D3DXMATERIAL类型的 结构,他里面是
一个D3DMATERIAL9结构(MatD3D)和一个字符串指针(PTextureFilename),该指针指定了与网格相关的纹理文件名
D3DXMATERIAL数组结构中的第i项与第i个子集对应,这意味着材质数等于属性子集数
当用D3DXLoadMeshFromX函数加载了一个XFile文件后,必须根据指定的 纹理文件名 加载纹理数据,
(5)生成顶点法线
XFile文件中有可能没有存放顶点的法向量,这就无法使用光照,所以需要手工去计算并添加,
以前是通过自己算,但是还有更为便捷的方法,那就是通过ID3DXBaseMesh(ID3DXMesh的父接口)来解决:
重要的一点是传入的参数pMesh的顶点格式中必须包含顶点标记D3DFVF_NORMAL,
注意:如果一个XFile文件不包含顶点法线数据,则由D3DXLoadMeshFromX创建的ID3DXMesh网格对象在其顶点格式中将不包含D3DFVF_NORMAL标记,
解决的方案是在使用D3DXLoadMeshFromX之前克隆网格,并为他指定一个包含D3DFVF_NORMAL标记的顶点格式
3.渐近网格 (progressive mesh)
渐近网格用ID3DXPMesh接口表示(多了一个P):用来对网格简化,但是简化后就无法获得那些丰富的细节
渐近网格的思路和多级渐近纹理相似,对一个远而小的物体使用高分辨率是浪费的,
所以用尽量少的片面表达这个网格可以节省时间,这就涉及到了渐近网格
(1)生成渐近网格:使用的创建函数是D3DXGeneratePMesh()
(2)顶点属性权值
ID3DXPMesh也继承了ID3DXBaseMesh,他自己还单独具有的方法:
(4)例程:
4.外接体(bound)
两种常见的外接体是 外接球(sphere) 和 外接体(box)
外接体常用于加速 可见性检测 和 碰撞检测,一个网格的外接体(外接球)不可见,称该网格不可见
检测外接体的可见性要比检测网格中的每个面片的可见性代价低得多
D3DX库提供了函数来计算网格的外接体或者外接球:
例程:
这次涉及的内容微多:
如何将x文件中的数据加载到id3dxmesh对象中如何使用渐近网格接口id3dxpmesh
如何使用d3dx创建外接体
1.ID3DXBuffer
这是一个泛型数据结构,在使用的时候转换成自己需要使用的特定类型。
ID3DXBuffer接口只有两个方法:
GetBufferPointer(); //返回指向缓存中数据起始位置的指针,使用时注意要 强制类型转换
GetBufferSize(); //返回缓存的大小
使用后要释放(因为他是COM接口)
创建ID3DXBuffer对象:
ID3DXBuffer *buffer = 0;
D3DXCreateBuffer( 4 * sizeof(int), &buffer ); //创建了一个能容纳4个整数的缓存
(1)比较流行的建模工具: 1. 3DS MAX 2. LightWave 3. Maya
(2)加载XFile文件:
HRESULT D3DXLoadMeshFromX(...);
/*
这里面有8个参数,分别代表加载的文件名,创建网格所用的标记,与网格相关的设备指针,
该网格对象的邻接信息,网格的材质数据,第六个忽略,材质数目,填充了XFile的ID3DXMesh对象,
不必死记,用的时候就懂了
*/
(3)XFile材质
(第五个参数)网格的材质数据 包含了一个D3DXMATERIAL类型的 结构,他里面是
一个D3DMATERIAL9结构(MatD3D)和一个字符串指针(PTextureFilename),该指针指定了与网格相关的纹理文件名
D3DXMATERIAL数组结构中的第i项与第i个子集对应,这意味着材质数等于属性子集数
当用D3DXLoadMeshFromX函数加载了一个XFile文件后,必须根据指定的 纹理文件名 加载纹理数据,
这是因为XFile文件中并未存储纹理数据,只是包含了纹理图像的文件名
//首先是需要用的全局变量们
ID3DXMesh* Mesh = 0;
std::vector<D3DMATERIAL9> Mtrls(0);
std::vector<IDirect3DTexture9*> Textures(0);
//Setup部分
//必要参数
ID3DXBuffer* adjBuffer = 0; //第4个参数,该网格的邻接信息
ID3DXBuffer* mtrlBuffer = 0; //第5个参数,网格的材质数据
DWORD numMtrls = 0; //第7个参数,网格材质的数目
//加载XFile文件
hr = D3DXLoadMeshFromX(
"bigship1.x", //文件名
D3DXMESH_MANAGED,
Device,
&adjBuffer,
&mtrlBuffer,
0,
&numMtrls,
&Mesh //后续操作的主角
);
//加载完成后,遍历D3DXMATERIAL数组中的元素,并加载网格所引用的纹理数据
if( mtrlBuffer != 0 && numMtrls != 0 )
{
D3DXMATERIAL* mtrls = (D3DXMATERIAL*)mtrlBuffer->GetBufferPointer();
for(int i = 0; i < numMtrls; i++)
{
mtrls[i].MatD3D.Ambient = mtrls[i].MatD3D.Diffuse; // 这个材质属性中并没有设置环境光,所以现在设置
Mtrls.push_back( mtrls[i].MatD3D ); // 保存这个材质,把他放进全局的Mtrls中
if( mtrls[i].pTextureFilename != 0 ) // 对这个材质是否绑定了纹理进行检测
{
// 如果有纹理的话就载入纹理
IDirect3DTexture9* tex = 0;
D3DXCreateTextureFromFile( // 读取纹理文件
Device,
mtrls[i].pTextureFilename,
&tex);
Textures.push_back( tex ); // 保存这个纹理,把他放进全局的纹理Textures中
}
else
{
Textures.push_back( 0 ); // 没有纹理的话就什么都不做,进入下一个材质
}
}
}
d3d::Release<ID3DXBuffer*>(mtrlBuffer);
hr = Mesh->OptimizeInplace( // 网格优化,由于前面已经接受了网格的邻接信息,所以这里就直接用
D3DXMESHOPT_ATTRSORT | D3DXMESHOPT_COMPACT | D3DXMESHOPT_VERTEXCACHE,
(DWORD*)adjBuffer->GetBufferPointer(),
0, 0, 0);
d3d::Release<ID3DXBuffer*>(adjBuffer);
//接下来就是 纹理过滤器、多级渐近纹理、灯光、观察矩阵、投影矩阵的设置
//注:这里的push_back是因为那几个全局变量的类型是vector
//Display部分
//世界矩阵的旋转部分代码略过...
for(int i = 0; i < Mtrls.size(); i++) //当然,这里Mtrls.size()写乘Textures.size()也没问题,因为这两个的大小是一样的
{
Device->SetMaterial( &Mtrls[i] );
Device->SetTexture(0, Textures[i]);
Mesh->DrawSubset(i);
}
(5)生成顶点法线
XFile文件中有可能没有存放顶点的法向量,这就无法使用光照,所以需要手工去计算并添加,
以前是通过自己算,但是还有更为便捷的方法,那就是通过ID3DXBaseMesh(ID3DXMesh的父接口)来解决:
HRESULT D3DXComputeNormals(
LPD3DXBASEMESH pMesh,
CONST DWORD * pAdjacency
);
重要的一点是传入的参数pMesh的顶点格式中必须包含顶点标记D3DFVF_NORMAL,
注意:如果一个XFile文件不包含顶点法线数据,则由D3DXLoadMeshFromX创建的ID3DXMesh网格对象在其顶点格式中将不包含D3DFVF_NORMAL标记,
解决的方案是在使用D3DXLoadMeshFromX之前克隆网格,并为他指定一个包含D3DFVF_NORMAL标记的顶点格式
//eg:
if( !(pMesh->GetFVF() & D3DFVF_NORMAL) )
{
ID3DXMesh *pTempMesh = 0;
pMesh -> CloneMeshFVF(
D3DXMESH_MANAGED,
pMesh -> GetFVF() | D3DFVF_NORMAL,
Device,
&pTempMesh
);
D3DXComputeNormals(pTempMesh, 0);
pMesh -> Release();
pMesh = pTempMesh;
}
3.渐近网格 (progressive mesh)
渐近网格用ID3DXPMesh接口表示(多了一个P):用来对网格简化,但是简化后就无法获得那些丰富的细节
渐近网格的思路和多级渐近纹理相似,对一个远而小的物体使用高分辨率是浪费的,
所以用尽量少的片面表达这个网格可以节省时间,这就涉及到了渐近网格
(1)生成渐近网格:使用的创建函数是D3DXGeneratePMesh()
HRESULT D3DXGeneratePMesh(
LPD3DXMESH pMesh, //源网格
CONST DWORD* pAdjacency,
CONST D3DXATTRIBUTEWEIGHTS* pVertexAttributeWeights, //属性权值
CONST FLOAT* pVertexWeights, //指定权值,权值越高,简化时被移除的概率就越小
DWORD MinValue, //网格中顶点或片面被简化的下限,由Options参数决定
DWORD Options, //D3DXMESHSIMP_VERTEX D3DXMESHSIMP_FACE
LPD3DXPMESH* ppMesh //生成的网格
);
(2)顶点属性权值
顶点属性(第三个参数)有多个分量,每个分量都有默认的权值,一般情况下就使用他们的默认权值
ID3DXPMesh也继承了ID3DXBaseMesh,他自己还单独具有的方法:
DWORD GetMaxFaces();
DWORD GetMaxVertices();
DWORD GetMinFaces();
DWORD GetMinVertices();
HRESULT SetNumFaces(); //注意,如果指定的值大于最大,或者小于最小,就自动取最大值或最小值
HRESULT SetNumVertices(); //与上面的注意项类似
HRESULT TrimByFaces(); //重设面片的最大值和最小值
HRESULT TrimByVertices(); //重设顶点的最大值和最小值
(4)例程:
//1.Setup
//从X文件载入网格得到源网格,然后载入每个面片的纹理和材质,通过源网格产生渐近网格
hr = D3DXGeneratePMesh(
SourceMesh,
(DWORD*)adjBuffer->GetBufferPointer(), // adjacency
0, // default vertex attribute weights
0, // default vertex weights
1, // simplify as low as possible
D3DXMESHSIMP_FACE, // simplify by face count
&PMesh);
//现在是最小面片,所以为了绘制出来的美观,把面片设为最大
DWORD maxFaces = PMesh->GetMaxFaces();
PMesh->SetNumFaces(maxFaces);
//纹理过滤器、多级渐近纹理、灯光、观察矩阵、投影矩阵的设置等略过
//2.Display
for(int i = 0; i < Mtrls.size(); i++)
{
// draw pmesh
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);
}
4.外接体(bound)
两种常见的外接体是 外接球(sphere) 和 外接体(box)
外接体常用于加速 可见性检测 和 碰撞检测,一个网格的外接体(外接球)不可见,称该网格不可见
检测外接体的可见性要比检测网格中的每个面片的可见性代价低得多
D3DX库提供了函数来计算网格的外接体或者外接球:
HRESULT WINAPI D3DXComputeBoundingSphere(
const D3DXVECTOR3 * pFirstPosition, //指向顶点数组中的第一个顶点的位置向量的指针
DWORD NumVertices, //顶点数组中的顶点数
DWORD dwStride, //每个顶点的大小
D3DXVECTOR3 * pCenter, //外接球的球心
FLOAT * pRadius //外接球的半径
);
HRESULT WINAPI D3DXComputeBoundingBox(
const D3DXVECTOR3 * pFirstPosition,
DWORD NumVertices,
DWORD dwStride,
D3DXVECTOR3 * pMin, //外接体的最小点
D3DXVECTOR3 * pMax //外接体的最大点
);
例程:
d3d::BoundingSphere boundingSphere;
d3d::BoundingBox boundingBox;
ComputeBoundingSphere(Mesh, &boundingSphere); //这是一个自定义的函数,主要是改变boundingSphere结构的_radius参数
ComputeBoundingBox(Mesh, &boundingBox); //这是一个自定义的函数,主要是改变boundingBox结构的最大点_max和最小点_min
//以上这两个自定义函数的主要部分就是调用D3DXComputeBoundingSphere和D3DXComputeBoundingBox函数
/*创建外接球*/
D3DXCreateSphere(
Device,
boundingSphere._radius,
20,
20,
&SphereMesh,
0); //通过boundingSphere结构把参数传递过来,并成功创建 外接球 的网格
/*创建外接体*/
D3DXCreateBox(
Device,
boundingBox._max.x - boundingBox._min.x,
boundingBox._max.y - boundingBox._min.y,
boundingBox._max.z - boundingBox._min.z,
&BoxMesh,
0); //通过boundingBox结构把参数传递过来,并成功创建 外接体 的网格
//在Display中只要用上融合技术就能把 物体的网格 和 外接体的网格 融合起来