每日一语:
程序员都是需要成就感,这个跟其他的行业不一样,是一种很微妙的感觉。一种精神享受。对比物质享受是无法比拟的。如果一个程序员没有这种感觉,或者他已经看淡这些,也许他已经不太适合继续走程序员这条路。也许,你会说,物质比什么都重要,这个当然,但是如果少了这种感觉,做程序员真的没什么意义。为什么,这种感觉重要呢,因为,它是程序员往更深的地方钻研的动力源泉,我们需要这种感觉,在BUG中成长,解决BUG后享受。
正文:
镜面效果:
自然界中的许多表面都像镜子一样,从中我们可看到物体的映像。要想在程序中实现镜面效果,需要解决两个问题。首先,为了能够正确地绘制物体在镜面中的镜像,我们必须了解对于任意平面物体如何成像。其次,必须将某一表面区域“标记”为镜面,接下来我们就可以只绘制处于指定镜面区域中那部分物体的映像。
第一个问题借助向量几何很容易解决。第二个问题则希望借助模板缓存来解决。
阴影:
阴影有助与我们感知场景中光源的位置,并使场景更具有真实感。平面中的阴影只是一种权宜之计,因为其效果不像阴影体那样好。
为了实现平面阴影,我们必须首先找到物体投射到某一平面中的阴影,然后对其进行几何建模,这样就可以对其进行绘制了。接下来,我们绘制描述阴影的多边形,并使用透明度为50%的黑色材质。这类阴影的绘制会产生人工雕琢的痕迹,即二次融合。
阴影矩阵:
由平行光所产生的阴影实质上是物体在平面n.p + d = 0上眼特定方向的平行投影。对于点光源,阴影实质上是以光源为视点,物体在平面n.p + d = 0上的透视投影。
防止二次融合:
我们将模板测试设置为只接受第一次得到绘制的那些像素。即在向后台缓存绘制阴影的像素时,我们对相应的模板缓存值进行标记。然后,如果我们企图将一个像素写入已经被绘制(模板缓存中已被标记的位置)的区域时,模板测试就会失败。这样,我们就防止了重叠像素的写入,由此避免了二次融合的发生。
网格:
在前面的章节中,我们通过使用D3DXCreate*函数已经对ID3DXMesh接口有一定的了解。
注意,ID3DXMesh接口继承了其父接口ID3DXBaseMesh的大部分功能。这一点很重要,因为其他类型的网格接口,如ID3DXPMesh(渐进网格)也继承自接口ID3DXBaseMesh。
ID3DXBaseMesh接口包含有一个顶点缓存(用于存储网格顶点)和一个索引缓存(决定顶点应何种组合方式构成网格的三角形单元)
HRESULT ID3DXMesh::GetVertexBuffer(LPDIRECT3DVERTEXBUFFER9 *ppVB);
HRESULT ID3DXMesh::GetIndexBuffer(LPDIRECT3DINDEXBUFFER9 *ppIB);
下面的例子示范了这些方法如何使用:
IDirect3DVertexBuffer9 *vb = 0;
Mesh->GetVertexBuffer(&vb);
IDirect3DIndexBuffer9 *ib = 0;
Mesh->GetIndexBuffer(&ib);
ID3DXMesh接口仅支持将索引化的三角形单元列表作为其基本类型。
如果你想锁定这些缓存,然后进行读写操作,可使用如下函数,注意,这些方法锁定的是整个顶点缓存或索引缓存。
HRESULT ID3DXMesh::LockVertexBuffer(DWORD Flgas,BYTE** ppData);
HRESULT ID3DXMesh::LockIndexBuffer(DWORD Flags,BYTE**ppData);
其中,Flags参数描述了如何进行锁定。
当对被锁定内存完成操作后
HRESULT ID3DXMesh::UnlockVertexBuffer();
HRESULT ID3DXMesh::UnlockIndexBuffer();
下面是ID3DXMesh接口用于获取几何信息的另外一些方法。
DWORD GetFVF():返回一个描述了顶点格式的DWORD类型值。
DWORD GetNumVertices():返回顶点缓存中的顶点个数。
DWORD GetNumBytesPerVertex():返回每个顶点所占的字节数。
DWORD GetNumFaces():返回网格中(三角形)面片的个数。
子集和属性缓存:
一个网格由一个或多个子集组成。一个子集是网格中一组可用相同属性进行绘制的三角形单元。这里的属性是指材质纹理和绘制状态。
比如房子:我们可以分为
子集0,包含地板。用地板的属性绘制该子集内的三角形。
子集1:用墙的属性绘制该子集内的三角形。
子集2:天花板:用天花板的属性绘制该子集内的三角形。
子集3: 窗户,用窗户的属性绘制该子集内的三角形。
网格中的每个三角形单元都被赋予一个属性ID,这些三角形单元的属性ID被存储在网格的属性缓存中,该属性缓存实际上是一个DWORD类型的数组。由于每个面片(三角形)在属性缓存中都有对应项,所以属性缓存中元素的个数与网格中面片的个数完全相等。而且属性缓存中的那些项与在索引缓存中定义的三角形单元是一一对应的:即属性缓存中的第i项对应于索引缓存中的第i个三角形。三角形单元i是由索引缓存中如下3个索引定义的:
A = i*3;
B = i*3 + 1;
C = i*3 + 2;
要想访问属性缓存,必须首先将其锁定
DWORD* buffer = 0;
Mesh -> LockAttributeBuffer(lockingFlags,&buffer);
Mesh -> UnlockAttributeBuffer();
绘制:
ID3DXMesh接口提供了方法DrawSubset(DWORD Attribld)用于绘制由参数Attribld指定的子集中的三角形单元。例如要绘制子集0中的所有三角形单元:
Mesh -> DrawSubset(0);
由于一个子集都有同一的纹理和材质,所以我们能够用一个简单的循环完成整个网格的绘制:
for(int i = 0; i < numSubsets, i++)
{
Device->SetMaterial( mtrls[i]);
Device->SetTexture(0,textures[i]);
Mesh->DrawSubset(i);
}
网格优化:
为了更高效的绘制一个网格,我们可对网格中的顶点和索引进行重组。这个重组的过程称为网格优化。
HRESULT ID3DXMesh::OptimizeInplace(
DWORD Flags,
CONST DWORD* pAdjacencyIn,
DWORD* pAdjacencyOut,
DWORD* pFaceRemap,
LPD3DXBUFFER* ppVertexRemap
);
下面就其参数进行说明:
Flags:优化选项标记,通知该方法所要实施的优化方案。该参数可取如下一项或多项:
D3DXMESHOPT_COMPACT 从网格中移除那些无用顶点和索引。
D3DXMESHOPT_ATTSORT 依据属性对各三角形进行排序,并生成一个属性表,这样可使DrawSubset获得更高的绘制效率。
D3DXMESHOPT_VERTEXCACHE 提高顶点高速缓存的命中率。
D3DXMESHOPT_STRIPORDER 对索引进行重组,使三角形单元条带尽可能长。
D3DXMESHOPT_IGNOREVERTS 仅对索引进行优化,忽略顶点。
注意D3DXMESHOPT_VERTEXCACHE和 D3DXMESHOPT_STRIPORDER不允许同时使用。
pAdjacencyIn 指向未经优化的网格的邻接数组的指针。
pAdiacencyOut 指向一个DWORD类型数组的指针,该数组被填充了经优化后的网格的邻接信息。该数组的维数必须为ID3DXMesh::GetNumFaces()*3.
pFaceRemap 指向一个DWORD类型数组的指针,该数组填充了网格面片的重绘信息。该数组的维数为ID3DXMesh::GetNumFaces()。当对一个网格面实施优化后,其面片在索引缓存中可能发生了移动。该面片重绘信息表明了原始面片所被一定到的新位置,即pFaceRemap中的第i项保存了表示第i个原始面片被移动到哪些的面片索引。
ppVertexRemap 指向ID3DXMesh对象指针的地址,该对象中保存了顶点的重绘信息。该缓存所包含的顶点数应为ID3DXMesh::GetNumVertices().当网格面经过优化后,其顶点在索引缓存中的位置可能发生了变动。顶点重绘信息表明了原始顶点移动到的新位置;即ppVertexRemap中的第i项保存了表示第i个原始顶点被移动到哪里的顶点索引。
调用示例:
DWORD adjacencyInfo[Mesh->GetNumFaces()*3];
Mesh->GenerateAdjacency(0.0f,adjacencyInfo);
DWORD optimizedAdjacencyInfo[Mesh->GetNumFaces()*3];
Mesh->OptimizeInplace(
D3DXMESHOPT_ATTRSORT|
D3DXMESHOPT_COMPACT|
D3DXMESHOPT_VERTEXCACHE,
adjacencyInfo,
optimizedAdjacencyInfo,0,0
);
与上述方法功能类似的另一个方法是Optimize,该方法将输出调用了该方法的网格对象优化后的版本,但是调用该方法的那个网格对象本身不会发生改变。
HRESULT ID3DMesh::Optimize(
DWORD Flags,
CONST DWORD* pAdjacencyIn,
DWORD* pAdjacencyOut,
DWORD* pFaceRemap,
LPD3DXBUFER* ppVertexRemap,
LPD3DXMESH* ppOptMesh
)
属性表:
如果一个网格对象在优化处理时使用了D3DXMESHOPT_ATTRSORT标记,则构成该网格的三角形面片就会依据其属性进行排序,这样属于特定子集的三角形面片就会保存在顶点缓存或索引缓存中的一个连续存储空间中。
除了可对面片进行排序外,D3DXMESHOPT_ATTRSORT优化选项还将创建一个属性表。该属性表是一个D3DXATTRIBUTERANGE类型的结构数组。属性表中的每一项都对应于网格的一个子集,并指定了该子集中面片的几何信息被存储在顶点缓存或索引缓存的哪一个存储块中。D3DXATTRIBUTERANGE结构的定义如下:
typedef struct _D3DXATRIBUTERANGE
{
DWORD AttribId;
DWORD FaceStart;
DWORD FaceCount;
DWORD VertexStart;
DWORD VertexCount;
}D3DXATTRIBUTERANGE
下面对属性表成员进行说明:
Attribld 子集的ID
FaceStart 一个大小为FaceStart*3的偏移量,表明了属于该子集的三角形单元在索引缓存中的起始位置。
FaceCount 该子集中面片(三角形单元)总数。
VertexStart 一个表明了与子集相关的顶点在顶点缓存中起始位置的偏移值。
VertexCount 该子集中的顶点总数。
属性表创建以后,只要通过一次快速查找,就能找到属于该子集的全部面片,这样就使子集的绘制可以高效地完成。注意,没有属性表时,为绘制某个子集,就只能对整个属性缓存进行若干次线性查找以确定每个属于该子集的面的位置。
为了访问一个网格的属性表,我们可调用:
HRESULT ID3DXMesh::GetAttributTable(
D3DXATTRIBUTERANGE *pAttribTable,
DWORD* pAttribTableSize
)
该方法完成了两项工作;返回属性表中的属性个数和用属性数据填充D3DXATTRIBUTERANGE类型的结构数组。
要想获取属性表中的元素个数,我们可以将上述方法的第一个参数设为0。
DWORD numSubsets = 0;
Mesh-> GetAttributeTable(0,&numSubsets);
一旦得到了属性表中的元素个数,我们就可以用属性数据填充D3DXATTRIBUTERANGE类型的结构数组。
D3DXATRIBUTERANGE table = new D3DXATTRIBUTERANGE (numSubsets);
Mesh -> GetAttributeTable(table,&numSubsets);
我们可直接使用方法ID3DXMesh::SetAttributeTable对属性表进行设置。下面的例子设定了属性表共有12个子集。
D3DXATTRIBUTERANGE attribteTable[12];
//...fill attributeTable aray with data
Mesh->SetAttributeTable(attributeTable,12);
邻接信息:
对于某些网格运算(网格优化),需要知道对任意给定的三角形面片,知道哪些面片与其邻接。这些邻接信息都存储在网格的邻接数组中。
邻接数组的类型为DWORD,其每一项都包含了一个标识网格中某个三角形面片的索引。例如,第i项是指优下列顶点构成的三角形面片。
A = i*3;
B = i*3 + 1;
C = i*3 + 2;
注意,如果邻接数组中某一项等于ULONG_MAX = 4294967295,则表明网格中某一特定边没有邻接面片。我们可以将该项赋为-1来表示这种情形。
由于每个三角形面片有3条边,故每个面片最多可有3个邻接三角形。
所以,邻接数组的维数必须为ID3DXBaseMesh::GetNumFaces()*3,网格中的每个三角形面片都有3个可能的邻接面片。
许多D3DX网格创建函数都能够输出邻接信息,但我们也可使用如下方法。
HRESULT ID3XMesh::GenerateAdjacency(
FLOAT fEpsilon,
DWORD* pAdjacency
)
fEpsilon 一个很小的正数值,指定了在某种距离度量下,两个点接近到何种程度方可认为这点可为同一点。例如,如果两点间距离小于该epsilon值,我们认为这两点为同一点。
DWORD adjacencyInfo(Mesh->GetNumFaces()*3);
Mesh->GenerateAdjacency(0.001f,adjacencyInfo);
克隆:
有时,我们需要生成网格数据的一个副本,这时可使用如下方法。
HRESULT ID3DXMesh::CloneMeshFVF(
DWORD Options,
DWORD FVF,
LPDIRECT3DDEVICE9 pDevice,
LPD3DXMESH *ppCloneMesh
);
下面对其参数进行说明:
Options 创建某网格副本时的创建标记或标记组合。标记选项的完整列表需要参阅SDK文档。下面是常用的标记:
D3DXMESH_32BIT 网格将使用32位索引。
D3DXMESH_MANAGED 网格数据将存储与托管的内存池中。
D3DXMESH_WRITEONLY 指定网格数据为只读。
D3DXMESH_DYNAMIC 网格缓存将使用动态缓存。
FVF 所要创建的克隆网格的灵活顶点格式。
pDevice 所创建的克隆网格关联的设备指针。
ppCloneMesh 输出所创建的克隆网格。
注意,该方法允许目标网格采用与源网格不同的创建选项和灵活的顶点格式。例如,假定有一个灵活顶点格式为FVF_XYZ的网格,我们想要创建该网格的一个副本,但想将其顶点格式指定D3DFVF_XYZ|D3DFVF_NORMAL.可以这样做:
ID3DXMesh * clone = 0;
Mesh -> CloneMeshFVF(
Mesh ->GetOptions(),
D3DFVF_XYZ | D3DFVF_NORMAL,
Device,
&clone
);
创建网格:
到目前为止,我们已经使用了D3DXCreate*系列函数创建了一些网格对象。我们也可使用D3DCreateMeshFVF函数创建一个“空”的网格对象。这里的“空”网格对象是指我们指定了网格的面片总数和顶点总数,然后由D3DXCreateMeshFVF函数为顶点缓存,索引缓存和属性缓存分配大小合适的内存。为网格缓存分配好内存后,我们就可手工填入网格的数据(即,我们必须将顶点,索引以及属性分别写入顶点缓存,索引缓存和属性缓存中)
HRESULT WINAPI D3DXCreateMeshFVF(
DWORD NumFaces,
DWORD NumVerteices,
DWORD Options,
DWORD FVF,
LPDIRECT3DDEVICE9 pDevice,
LPD3DXMESH *ppMesh
)
下面就其参数进行说明:
NumFaces 网格中具有的面片总数,该值必须大于0。
NumVertices 网格中具有的顶点总数,该值必须大于0
Options 创建网格时所使用的创建标记
Options 创建某网格副本时的创建标记或标记组合。标记选项的完整列表需要参阅SDK文档。下面是常用的标记:
D3DXMESH_32BIT 网格将使用32位索引。
D3DXMESH_MANAGED 网格数据将存储与托管的内存池中。
D3DXMESH_WRITEONLY 指定网格数据为只读。
D3DXMESH_DYNAMIC 网格缓存将使用动态缓存。
FVF 存储在该网格的顶点的灵活顶点格式。
pDevice 所创建的克隆网格关联的设备指针。
ppMesh 所创建的网格指针。
另外,也可用D3DXCreateMesh函数创建空网格,这个函数的参数与D3DXCreateMeshFVF类似,只是第4个参数不同。在该函数中,我们并未指定FVF,而是用一个D3DVERTEXELEMENT9类型的结构数组来描述顶点数据的布局方式。
HRESULT WINAPI D3DXCreateMeshFVF(
DWORD NumFaces,
DWORD NumVerteices,
DWORD Options,
CONST LPD3DVERTEXELEMENT9* pDeclaration,
LPDIRECT3DDEVICE9 pDevice,
LPD3DXMESH *ppMesh
)
我们还提一下下面这个函数:
HRESULT D3DXDeclaratorFromFVF(
DWORD FVF,
D3DVERTEXELEMENT9 Declaration[MAX_FVF_DECL_SIZE]
);
该函数在给定FVF时,输出一个D3DVERTEXELEMENT9类型的结构数组。注意MAX_FVF_DECL_SIZE定义如下:
typedef enum{
MAX_FVF_DECL_SIZE = 18
}MAX_FVF_DECL_SIZE