Directx3D编程框架
时间:2010-07-06 来源:wilsonwong
微软的编程工具都别扭,但适者生存、小小程序员只能适应。无奈的初学者面对Directx 3D图形接口,有个方法倒是可行,就是先搞通基本的编程框架,算是入门的一种方法。下面就简单介绍D3D的入门途径。
首先,初始化你的D3D设备:
LPDIRECT3DDEVICE9 pd3dDevice;
LPDIRECT3D9 pD3D = Direct3DCreate9( D3D_SDK_VERSION );
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory( &d3dpp, sizeof( d3dpp ) );
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,&d3dpp, &pd3dDevice );
pd3dDevice 就是我们需要的D3D设备。这个 设备是为一个窗口应用程序写的,如果你是想要一个全屏的D3D程序的话,必须要改的就是d3dpp.Windowed 这个属性的设为FALSE。至于其他参数的含义,你最好还是安装DXSDK之后,仔细阅读它的帮助文档中的内容,那 是一份最权威的资料,而且锻炼你的英文阅读。上面的示例中使用的基本是默认参数,足够让你建立起你的第一个D3D程序。
其次,顶点信息:
是3D图形的真正面目——三角形。一个3D对象就是有N个多边形(其实就是三角形)组成的, 多边形的数量越多,对象就越圆滑,反之,则越粗糙。不过,更多的多边形将会导致,计算量增加,性能下降。这就需要我们更好的平衡性能与质量之间的关系。一 个三角形在3D空间里就是由3个顶点组成的。由于3D空间还存在点、线等元素。所以顶点可以认为是D3D中最基本的元素。在D3D中,顶点包含的信息是很 多的;但是并不是每次都会用到所有的信息。所以,我们一般会在程序中自定义顶点结构。在一个程序中是可以存在很多个顶点结构的。
struct CUSTOMVERTEX
{ FLOAT x, y, z;
DOWRD color;
};
#define D3DFVF_CUSTOMVERTEX ( D3DFVF_XYZ | D3DFVF_DIFFUSE )
struct CUSTOMVERTEX1
{ D3DXVECTOR3 position;
FLOAT tu, tv;
};
#define D3DFVF_CUSTOMVERTEX1 ( D3DFVF_XYZ | D3DFVF_TEX1 )
struct CUSTOMVERTEX2
{ D3DXVECTOR3 position;
D3DXVECTOR3 normal;
};
#define D3DFVF_CUSTOMVERTEX2 ( D3DFVF_XYZ | D3DFVF_NORMAL )
以上定义的几个顶点结构还可以合并在一起,定义成一个新的顶点结构。要注意的是每个自定义的 顶点结构后面都应该有一个D3DFVF 与之相对应,对顶点结构进行说明,否则D3D是 不会知道你定义的顶点到底是什么意思的。D3DFVF_XYZ代表顶点的坐标,D3DFVF_DIFFUSE代 表顶点的漫反射颜色,D3DFVF_TEX1代表顶点的贴图坐标,D3DFVF_NORMAL代 表顶点的法线向量。position 在顶点信息中是必须,连坐标都不知道的点对我们来说说明都不是。color 存储的是顶点的颜色信息,用一个32bit的值表示,就像 0xffff0000 表示的是不透明的红色。
第三,基本图形(图元):
在绘图软件中,都存在基本图形这一个说法;D3D同样也有基本图形,这些图元只需要提供顶点 信息,便可以由D3D通过硬件迅速地绘制出来。包括有点表(Point List)、线表(Line List)、线带(List Strip)、三角形表(Triangle List)、三角形带(Triangle Strip)、三角形扇(Triangle Fan)。
示例:说明如果绘制6个单独的顶点。
CUSTOMVERTEX Vertices[] =
{ { -5.0, -5.0, 0.0 },
{ 0.0, 5.0, 0.0 },
{ 5.0, -5.0, 0.0 },
{10.0, 5.0, 0.0},
{15.0, -5.0, 0.0 },
{20.0, 5.0, 0.0 }
};
pd3dDevice->DrawPrimitive( D3DPT_POINTLIST, 0, 6 );
在帮助文档中对每个图元有具体说明。
第四,矩阵:
左手坐标系对于电脑屏幕来说,x轴的正 方向是右,y轴的正方向是上,z轴的正方向是指向屏幕的。
D3D中需要设定的矩阵有3个。
I、 世界 矩阵(World Matrix)
世界矩阵控制对象在3D世界中的位置偏移,偏转角度。这里要引进一个新的概念,Viewport, 视口(翻译好像很土,不过大家都是这样翻译的)。后面再讨论它,先告诉你视口的大小就是等于你的程序窗体大小。世界矩阵的原点(0.0, 0.0, 0.0)就是视口的中央。为一个对象设定世界矩阵就是指定它在3D空间中的位置和偏转状态。
D3DXMATRIX matWorld;
D3DXMatrixIdentity( &matWorld );
pd3dDevice->SetTransForm( D3DTS_WORLD, &matWorld );
这样设定的对象在3D空间的位置就是原点,并且不带偏转。通常为了设定对象的偏移和转动,我 们会定义两个辅助矩阵。
D3DXMATRIX matRotate; // Rotation
D3DXMatrixIdentity( &matRotate );
D3DXMatrixRotationAxis( &matRotate, AxisX, AxisY, fAngle ); // 某方向转动一定角度
D3DXMatrixMultiply( &matWorld, &matWorld, &matRotate );
D3DXMATRIX matTrans; // Translation
D3DXMatrixIdentity( &matTrans );
D3DXMatrixTranslation( &matTrans, fPosX, fPosY, fPosZ );
D3DXMatrixMultiply( &matWorld, &matWorld, &matTrans );
通过上面两个矩阵乘法得到的matWorld 就是得到 了偏移量的对象的世界矩阵。
II、 观察矩阵 (View Matrix)
观察矩阵说得明白一点就是,眼睛在什么地方,在看什么地方,上方是哪个方向。很简单吧。在 D3D中,大家要习惯用矢量思考和计算。
D3DVECTOR3 vEyePt( fPosX, fPosY, fPosZ );
D3DVECTOR3 vLookatPt( fPosX, fPosY, fPosZ );
D3DVECTOR3 vUpVec( fX, fY, fZ );
D3DXMATRIX matView;
D3DXMatrixIdentity( &matView );
D3DXMatrixLookAtLH( &matView, &vEyePt, &vLookatPt, &vUpVec );
pd3dDevice->SetTransForm( D3DTS_VIEW, &matView );
III、 投 影矩阵(Projection Matrix)
投影,就是3D空间中的对象,在2D的屏幕上显示出来的效果。D3D里面的投影方式有两种: 透视投影和正交投影。透视投影受视点的影响,观察者距离对象越远,所观察到的对象就越小,这是与真是世界相符合的。正交投影不受距离的影响,在平面上的投 影就是对象的实际大小。在制图过程中的三视图就是正交投影。
D3DXMATRIX matProj;
D3DXMatrixIdentity( &matProj );
// 透视投影
D3DXMatrixPerspectiveForLH( &matProj, fFovY, fAspect, fNearPlane, fFarPlane );
pd3dDevice->SetTransForm( D3DTS_PROJECTION, &matProj );
// 正交投影
D3DXMatrixOrthoLH( &matProj, width, height, nearplane, farplane );
pd3dDevice->SetTransForm( D3DTS_PROJECTION, &matProj );
以上是基于左手坐标系建立的透视投影和正交投影,DXSDK对于每个函数都有详细的说明;而 且很好理解。D3D中每个和矩阵有关系的函数都是这个形式 D3DXMatrix_____。
在一些情况下,我们可以把观察矩阵和透视矩阵封装成一个类,就是摄像机。在初始化的时候设定 默认值,不需要每次渲染都设定一次。但是世界矩阵还是每次渲染都建立的。
D3D进阶
第五,材质:
关于D3D材质的讨论可以很简单,也可以很复杂。它和灯光是结合在一起的,没有灯光,说什么 材质都是空话。说得明白点,D3D的材质,实际上就是对象对光线的反射方式,对D3D中各种类型的光的反射方式。D3D对真实世界的光效进行了模拟,包括 漫反射、镜面反射,环境反射。
设定材质的过程:
D3DMATERIAL9 mtrl;
ZeroMemory( &mtrl, sizeof( D3DMATERIAL9) );
mtrl.Diffuse.r = 1.0f; // 设 定对光线的漫反射
mtrl.Diffuse.g = 1.0f;
mtrl.Diffuse.b = 1.0f;
mtrl.Diffuse.a = 1.0f;
mtrl.Specular.r = 1.0f; // 设 定对光线的镜面反射
mtrl.Specular.g = 1.0f;
mtrl.Specular.b = 1.0f;
mtrl.Specular.a = 1.0f;
mtrl.Power = 10.0f; // 强 度的值越大,高光的锐化程度越高,也就是高光范围越小,越亮
mtrl.Ambient.r = 1.0f; // 设 定对环境光的反射
mtrl.Ambient.g = 1.0f;
mtrl.Ambient.b = 1.0f;
mtrl.Ambient.a = 1.0f;
pd3dDevice->SetMaterial( &mtrl ); // 每次渲染时调用
以上内容可以参见D3DMATERIAL9 Structure 。漫反射其实就是物体的表面颜色,而镜面反射可以让对象产生高光效果。环境光一般是来控制整个空间的亮度的,还有“气氛”。D3D中,组成一个对象的不同 多边形甚至都可以拥有不同的材质。
因为大多数场景包含的漫反射光比环境光要多,所以在决定最终颜色的过程中漫反射所起的作用最 大。另外,因为漫反射具有方向性,漫反射光的入射角会影响到整个反射光的强度。当光的入射方向与顶点法向平行时,漫反射最强。随着入射方向与顶点法向之间 夹角的增大,漫反射效果逐渐减少。反射光的数量是入射光与顶点法向之间夹角的余弦值。
环境反射和环境光是没有方向性。环境反射对被渲染物体最终的颜色影响较小,但它确实会影响最 终的颜色,最为明显的就是当材质很少甚至不反射漫反射光时。材质的环境反射受场景中的环境光的影响。
材质还有另外一个属性就是放射性,用它渲染的对象看上去就是自己发光一样的效果。D3DMATERIAL9结 构的Emissive成 员就是用来描述物体发出的光的颜色和透明度的。放射会影响物体的颜色,也可以使暗的材质变亮并部分呈现出所发光的颜色。举例:如果想在应用程序中让某个物 体体现出发光效果,又不要对周围的物体产生影响,就可以使用这个属性,这样还可以避免在场景中增加光源所带来的计算消耗。
利用一些其他的3D工具可以建立出你所需要的模型,里面可以包含材质信息。然后导出成.x后 缀的文件,这个格式的文件可以直接由D3D加载。
第六,灯光:
灯光和材质是密不可分,从上一章应该就可以看出一些端倪了。光线和材质的属性都是一一对应 的。如果没有设定材质,场景中的光线不会看出任何效果。
光线所有的类型算起来也就是3种,很容易理解的。建议大家创建一个球体作为渲染对象,各种光 线和材质的组合都可以在球体上得到很明显的反应;这就像小时候上美术课学静物素描,画的第一幅铁定就是球体和柱体。
第一、 方向光;这种光源只有方向和颜色,没有位置。而且不受衰减和范围的影响,所以方向光是D3D中需要计算量最小的一种光源。一般来说,方向光被认为是光源位 于无穷远处的平行光,就像太阳光。
第二、 点光源;特点是有颜色和位置,但是没有方向。它向各个方向发出完全一样的光;但是点光源的光线会受到范围和衰减的影响。如果超出了光线的作用范围,那么该 光线就是不可见的了;同时点光源还受衰减的影响,它有一个衰减数值:
D3DLIGHT9 light;
light.Type = D3DLIGHT_POINT;
light.Position = D3DVECTOR3( 50.0f, 20.0f, -50.0f );
light.Diffuse.r = light.Diffuse.g = light.Diffuse.b = 1.0f;
light.Range = 100.0f;
light.Attenuation0 = 0.0f;
light.Attenuation1 = 0.1f;
light.Attenuation2 = 0.0f;
随着到光源的距离越来越远,光线的强度也会越来越弱。边缘光线强度等于光源处强度的 1/Range。
第三、 聚光灯;是D3D里面最复杂,计算量最大的一种光源。它包括的属性也是最多的有:位置,方向,颜色,内径光圈,外径光圈,范围还有衰减因子。其中衰减因子 对于细微的变化都十分敏感,一般设定为light.Falloff = 1.0f,否则会得到意料之外的效果,但是绝 不会是好事。
第四、 另外,还有一种就是环境光,它也是可以用来控制物体的颜色,和漫反射光结合使用,不过,我感觉更多时候环境光还是在控制整个场景的亮度。
SDK的帮助文档里面有一段“与光照相关的数学”:
Microsoft® Direct3D®光 照模型涵盖了环境光、漫反射光、镜面反射光和放射光,这足以解决绝大部分的光照情况。我们将场景中光的总和称为全局照明(global illumination),并使用以下公式计算:
全局照明 = 环境光 + 漫 反射光 + 镜面反射光 + 放射光
环境光是恒定的光照。它在所有方向上不发生变化,对物体中所有像素产生的作 用也完全相同。它计算起来很快,但得到的物体看起来是平面的,没有真实感。 漫反射光取决于光的方向和表面的法向。由 于光的方向和表面法向量的变化,因此漫反射光会随物体的表面而变化。因为漫反射光随着每个顶点而变化,所以需要更长的时间进行计算,但是使用漫反射光带来 的好处是它使物体呈现出明暗变化和三维深度。 镜面反射光代表了当光线照射到物体表面时反射回摄像机形成的明亮的镜面 反射高光。它比漫反射光更强,但在物体表面也衰减得更快。计算镜面反射光需要比计算漫反射光更长的时间,但是使用镜面反射光带来的好处是它给表面增添了重 要的细节。 放射光是物体发出的光,例如光晕(glow)。 通 过在三维场景中使用这些类型的光,可以得到真实的光照效果。要得到更为真实的光照效果,应用程序可以添加更多的光源,但是,这增加了渲染场景的时间。要达 到(游戏)设计师想要的所有效果,一些游戏使用了超出一般的CPU计 算能力。在这种情况下,一般通过在使用纹理贴图的同时,使用光照贴图和环境贴图给场景加入光照的效果,这样就可以把光照所需的计算量减到最少。环境光、漫 反射光和镜面反射光会受到给定光源的衰减和聚光灯因子的影响。为环境光、放射光和漫反射光成分计算的颜色值被保存在输出顶点的漫反射色中。漫反射和镜面反 射公式都包含了衰减和聚光灯因子属性。
上面的这段话很有说服力,所以我还是说,想真正了解D3D,一定要仔细阅读SDK的帮助文 档。
第七,SetRenderState():
IDirect3Ddevice9::SetRenderState(…)这 个接口有点像3D效果的总开关。
关于SetRenderState()没有什么技巧性的 东西,都是一些固定的设定,为了在程序中实现某个效果,你就必须作出相应正确的设定。所以,把SetRenderState()里 面的每个参数都做一下说明,说白了就是翻译一下SDK的内容.
SetRenderState()的作用是设定单个设备 的渲染状态的参数。该接口的第一个参数是D3DRENDERSTATETYPE的枚举类型;也是这个接口的关键,第二 个参数只是对第一个参数,就是渲染状态的具体设定值。这个枚举类型有100多个值。