每日一语:
今天看到一篇博文写得蛮好的,讲30岁左右的程序员的发展的。自己目前也处在这个十字路口上。现在越来越多的文章,提到了30岁左右的程序员的现象。其实更多的文章关注,证明30岁左右的程序员越来越多。30岁左右的程序员,一般有两条路。一,转到管理上面,因为自己有技术的积累,可以更好的管理。二,在技术上继续钻研。我们无法评论这两个方向的孰好孰坏。因为这都是因人而异的。他跟每个人的经历,爱好,技术方向等等相关。目前大多的选择是往管理方向发展。因为大家认为30岁后程序员干不动了。其实,我倒不这样认为。中国慢慢的也在跟国际接轨。而国外,30岁的程序员才刚开始,因为在这个阶段,自己的技术积累到一个阶段,可以往更深更高的方面发展。也就是前面的几年磨砺,是跟真正程序员生涯买的一张门票。所以,在目前的状态下,我的目标是,自己现在还应该沉下来,好好的积淀一下技术。在各方面可以更深入一些。为以后打下更深厚的基础。
正文:
网格二:
我们今天看一下网格的应用,当然也是看一下,D3DX库提供的与网格相关的接口,结构和函数。例如,我们从磁盘上面加载一个复杂的3D模型并将该模型绘制出来,或者利用渐进网格接口来控制网格的细节层次。
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);
XFile:
下面我们看一下,关于DIRECT3D如何导入建模工具中的文件,这里我们说的建模工具主要是3DX MAX和Maya,这些建模工具能够将网格数据(几何信息,材质,动画以及其他可能的有用数据)导出到文件中。这样,我们可以编写一个文件读取程序来提取网格数据并在我们的3D应用程序中使用。这当然是一种方案。但是还有一种更简单的方案。有一种特殊的网格文件称为XFile格式(扩展名为.X).许多建模工具可以将模型数据导成这种格式,而且也有许多转换程序可以将其他较流行的网格文件格式转换为.X格式。XFile是DirectX定义的格式。
我们可以使用如下函数加载网格数据并将其存储在XFile文件中,注意,该方法创建了一个ID3DXMesh对象并将XFile中的几何数据加载到该对象中。
HRESULT D3DXLoadMeshFromX(
LPCSTR pFilename,
DWORD Options,
LPDIRECT3DDEVICE9 pDevice,
LPD3DXBUFFER *ppAdjacency,
LPD3DXBUFFER *ppMaterials,
LPD3DXBUFFER *ppEffectInstances,
PDWORD pNumMaterials,
LPD3DXMESH *ppMesh
)
下面就其参数进行说明:
pFileName:所要加载的XFile文件名。
Options: 创建某网格副本时的创建标记或标记组合。标记选项的完整列表需要参阅SDK文档。下面是常用的标记:
D3DXMESH_32BIT 网格将使用32位索引。
D3DXMESH_MANAGED 网格数据将存储与托管的内存池中。
D3DXMESH_WRITEONLY 指定网格数据为只读。
D3DXMESH_DYNAMIC 网格缓存将使用动态缓存。
pDevic 与该网格对象相关的设备指针。
ppAdjacency 返回一个ID3DXBuffer对象,该对象包含了一个描述了该网格对象的邻接信息的DWORD类型的数组。
ppMaterials 返回一个ID3DXBuffer对象,该对象包含了一个描述了该网格对象的材质数据的D3DXMATERIAL类型的结构数组
ppEffectInstances 返回一个ID3DXBuffer对象,该对象包含了一个D3DXEFFECTINSTANCE结构.现在我们可以将其设为0.
pNumMaterials 返回网格中的材质数据(即由ppMaterials输出的D3DXMATERIAL数组中元素的个数).
ppMesh 返回所创建的并已填充了XFile几何数据的ID3DXMesh对象。
XFile材质:
D3DXLoadMeshFromX函数中的第7个参数返回了该网格对象所含的材质数目,第5个参数返回了一个存储了材质数据的D3DXMATRIAL类型的结构数组。D3DXMATERIAL结构的定义如下:
typedef struct D3DXMATERIAL{
D3DMATERIAL9 MatD3D;
LPSTR pTextureFilename;
}D3DXMATERIAL;
该结构较为简单,它包含了D3DMATERIAL9结构和一个指向以NULL结尾的字符串指针,该字符串指定了与网格相关的纹理文件名。XFile文件中并未存储纹理数据,它只包含了纹理图像文件名,该文件名是对实际纹理数据的纹理图像的引用。这样,当用D3DXLoadMeshFromX函数加载一个XFile文件后,我们必须根据指定的纹理文件名加载纹理数据。
值得一提的是,D3DXLoadMeshFromX函数载入XFile数据后,返回的D3DXMATERIAL结构数组中的第i项就与第i个子集相对应。所以,我们将各子集按照0,1,2到n-1的顺序进行标记,其中n是子集和材质的总数。这样,我们就可用一个简单的循环对全部子集进行遍历和绘制,从而完成整个网格的绘制。
生成顶点法向量:
XFile文件中有可能没有存放顶点的法向量。如果出现这种情况,为了使用光照,我们可能需要手工计算每个顶点的法向量。前面,我们简要介绍了顶点法向量的计算。但现在既然我们已经了解了ID3DXMesh接口及其父接口ID3DXBaseMesh,我们可用如下方法来产生任意网格的顶点法向量。
HRESULT D3DXComputeNormals(
LPD3DXBASEMESH pMesh,
CONST DWORD * pAdjacency
);
该函数通过发向量平均的方法生成顶点的法向量。如果提供了邻接信息,重叠的顶点会被剔除。如果没有提供邻接信息,则重叠顶点的法向量由该顶点所依附的各面在该点的局部法向量取平均而得到。很重要的一点是,我们传入的参数pMesh的顶点格式中必须包含标记D3DFVF_NORMAL.
注意,如果一个XFile文件不含顶点法线数据,则由函数D3DXLoadMeshFromX所创建的ID3DXMesh网格对象在其顶点格式中将不含D3DFVF_NORMAL标记。所以,在使用函数D3DComputeNormal之前,我们必须克隆该网格,并为其指定一个包含了D3DFVF_NORMAL标记的顶点格式。下面的例子演示整个过程。
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();
}
渐进网格:
渐进网格用ID3DXPMesh接口表示,它允许我们运用一系列的边折叠变换(ECT)对网格进行简化,每次ECT都移除一个顶点以及一个或两个面。由于每次ECT都是可逆的(其逆运算称为顶点分裂),我们可对过程进行逆转,从而将网格精确回复到初始状态。当然,这也就意味着我们无法获得比原始网格更丰富的细节。我们只能对网格进行简化以及逆运算。
渐进网格的思路与纹理中的多级渐进纹理类似。进行纹理映射时,我们会注意到如果对一个小而远的图元应用高分辨率的纹理实在是一种浪费,因为观察者根本不可能注意到这些细节。对于网格也是同样的道理:一个小而远的网格完全不必像大而近的网格一样使用大量的面片。所以,在满足要求的条件下,我们总是尽量使用少的面片来表达一个网格,以节省宝贵的绘制时间。
生成渐进网格:
我们可用如下函数创建ID3DXPMesh对象:
HRESULT D3DXGeneratePMesh(
LPD3DXMESH pMesh,
CONST DWORD * pAdjacency,
CONST D3DXATTRIBUTEWEIGHTS * pVertexAttributeWeights,
CONST FLOAT * pVertexWeight,
DWORD MinValue,
DWORD Options,
LPD3DXMESH * ppPMesh
}
下面就其参数进行说明:
pMesh: 该输入变量包含了网格数据,渐进网格将根据此网格产生。
pAdjacency :指向包含了pMesh的邻接信息的DWORD类型的数据指针。
pVertexAttributeWeights :指向D3DXATRIBUTEWEIGHTS类型的结构数组的指针,该数组的维数为pMesh->GetNumVertices(),该数组的第i项对应于pMesh中的第i个顶点,并指定了相应顶点的属性权值。顶点属性权值用于决定在简化过程中顶点被移除的概率。可为该参数传入NULL,则每个顶点将被赋予默认的属性权值。
pVertexWeight 指向一个float类型数组的指针,该数组的维数为pMesh -> GetNumVertices(),该数组的第i项对应于pMesh中的第i个顶点,并指定了相应顶点的权值。一个顶点的权值越高,在简化过程中被移除的概率就越小。可将其设置为NULL,这样每个顶点就会被赋予默认的顶点权值1.0.
MinValue 网格中的顶点数或面片数(由下一个Options决定)可被简化到的下限。请注意,该值只是一种期望值,实际还要依赖于顶点的权值或属性权值,所以简化结果可能与该值不一致。
Options 该参数实际上是D3DXMESHSIMP枚举类型的一个成员。
D3DXMESHSIMP_VERTEX 指定了前面的参数MinValue是指顶点树
D3DXMESHSIMP_FACE 指定了前面的参数MinValue是指面片数
ppPMesh 返回所生成的渐进网格。
顶点属性权值:
typedef struct D3DXATTRIBUTEWEIGHTS {
FLOAT Position;
FLOAT Boundary;
FLOAT Normal;
FLOAT Diffuse;
FLOAT Specular;
FLOAT Texcoord[8];
FLOAT Tangent;
FLOAT Binormal;
} D3DXATTRIBUTEWEIGHTS,*LPD3DXATTRIBUTEWEIGHTS;
该顶点权值结构允许我们为顶点的每一个可能的分量指定一个权值。如果某个分量的权值被赋为0.0,则表明该分量无权值。顶点分量的权值越高,在简化过程中该顶点被移除的概率越小,各分量的默认权值为:
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 接口方法:
ID3DXPMesh 接口继承自ID3DXBaseMesh接口。所以前者具有前面所提到的ID3CXMesh接口的全部功能。此外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);
注意,调整后的网格面片数可能与期望的不一致,如果Vertices小于GetMinVertices(),它将取GetMinVertices();类似,如果Vertices大于GetMaxVertices(),它将取GetMaxVertices();
HRESULT TrimByFaces(
DWORD NewFacesMin,
DWORD NewFacesMax,
DWORD * rgiFaceRemap,
DWORD * rgiVertRemap
);
该方法允许我们重新设定面片数的最小值(NewFacesMin)和最大值(NewFacesMax).注意,NewFacesMin和NewFacesMax必须位于区间[GetMinFaces(),GetMaxFaces()].该函数同时也返回了面片和顶点的重绘信息。
HRESULT TrimVertices(
DWORD NewVerticesMin,
DWORD NewVerticesMax,
DWORD * rgiFaceRemap,
DWORD * rgiVertRemap
);
该方法允许我们重新设定顶点数的最小值(NewVerticesMin)和最大值(NewVerticesMax).注意,NewVerticesMin和NewVerticesMax必须位于区间[GetMinVertices(),GetMaxVertices()].该函数同时也返回了面片和顶点的重绘信息。