欣's profileAries' Matrix Ver#1.0PhotosBlogListsMore ![]() | Help |
|
|
January 19 Direct3D9学习笔记--画一个圆锥画一个圆锥 为了便于读者理解,本节及后续章节的例程均不使用索引缓存,因此我们将在第4节例程的基础上进行修改。不过在输入下列代码之前,请先参照第5节的示例,打开Z缓存。 1 建模 圆锥由一个曲面和一个底面构成,如图21所示,用两个图元构建实体比较合理,其中每个图元各代表一个面。考虑到圆锥曲面展开后为一个扇面,所以图元格式采用三角扇形,而底面是一个圆,也采用三角扇形,取圆锥外表面做图元的正面。在图21中,A为圆锥的顶点,B为底面的圆心,把底面分成30等份,得到30个分割点C1、C2 ... ... C30,则圆锥曲面的图元所对应的顶点序列为A、C1、C2 ... ... C30、C1,共32个顶点,注意在结尾处C1点重复出现了一次,否则画出的圆锥不完整。同理底面也由32个顶点组成,依次为B、C1、C30、C29 ... ... C1。 (图21,见文章末尾) 设圆锥高为2,底面半径为1,以圆锥中心线AB(图中的蓝线)做X轴,坐标原点设在AB中点,A的坐标为(-1,0,0),则B的坐标为(1,0,0),C1至C30的坐标为(1,sinθk,cosθk)。 由于使用灯光照明,还需要提供顶点法线:对于圆锥曲面,A的顶点法线取X轴的负方向,即矢量{-1,0,0},C1至C30的顶点法线取有向线段BCk,矢量为{0,sinθk,sinθk};对于圆锥底面,顶点法线都设为X轴的正方向,矢量为{1,0,0}。请注意,在Direct3D中,要求顶点法线为单位矢量。 现在开始输入代码,首先定义两个图元的FVF,因为使用灯光渲染,顶点颜色不再需要,新的FVF包括坐标和顶点法线: (D3DWnd.cpp) ... ... #include "D3DWnd.h" //圆锥曲面的FVF格式:坐标、顶点法线 struct CUSTOMVERTEX1 { D3DXVECTOR3 position; //顶点坐标 D3DXVECTOR3 normal; //顶点法线 }; #define D3DFVF_CUSTOMVERTEX1 (D3DFVF_XYZ | D3DFVF_NORMAL) //圆锥底面的FVF格式:坐标、顶点法线 struct CUSTOMVERTEX2 { D3DXVECTOR3 position; //顶点坐标 D3DXVECTOR3 normal; //顶点法线 }; #define D3DFVF_CUSTOMVERTEX2 (D3DFVF_XYZ | D3DFVF_NORMAL) ... ... 为CD3DWnd增加两个数据成员m_pVB1和m_pVB2,分别保存圆锥曲面和底面的顶点缓存区接口指针,原有的m_pVB予以删除: (D3DWnd.h) ... ... void SetupMatrices(); LPDIRECT3DVERTEXBUFFER9 m_pVB1; //圆锥曲面的顶点缓存区接口指针 LPDIRECT3DVERTEXBUFFER9 m_pVB2; //圆锥底面的顶点缓存区接口指针 ... ... 修改CD3DWnd::InitGeometry,建立圆锥的几何模型: (D3DWnd.cpp) void CD3DWnd::InitGeometry() { //建立圆锥曲面的数学模型 CUSTOMVERTEX1 vertices1[32]; vertices1[0].position = D3DXVECTOR3( -1.0f, 0.0f, 0.0f ); //点A的坐标 vertices1[0].normal = D3DXVECTOR3( -1.0f, 0.0f, 0.0f ); //点A的法线矢量 for (int i = 1; i < 32; i++) { //计算顶点序列C1、C2 ... ... C30、C1的坐标和法线 float theta = (i-1)*12*D3DX_PI/180; vertices1[i].position = D3DXVECTOR3( 1.0f, sin(theta), cos(theta) ); vertices1[i].normal = D3DXVECTOR3( 0.0f, sin(theta), cos(theta) ); } //创建圆锥曲面的顶点缓存区,填入顶点数据 m_pDevice->CreateVertexBuffer( sizeof(vertices1), 0, D3DFVF_CUSTOMVERTEX1, D3DPOOL_DEFAULT, &m_pVB1, NULL ); void* pVertices; m_pVB1->Lock( 0, sizeof(vertices1), (void**)&pVertices, 0 ); memcpy( pVertices, vertices1, sizeof(vertices1) ); m_pVB1->Unlock(); //建立圆锥底面的数学模型 CUSTOMVERTEX2 vertices2[32]; vertices2[0].position = D3DXVECTOR3( 1.0f, 0.0f, 0.0f ); //点B的坐标 vertices2[0].normal = D3DXVECTOR3( 1.0f, 0.0f, 0.0f ); //点B的法线矢量 for (i = 1; i < 32; i++) { //计算顶点序列C1、C30、C29 ... ... C1的坐标和法线 vertices2[i].position = vertices1[32-i].position; vertices2[i].normal = D3DXVECTOR3( 1.0f, 0.0f, 0.0f ); } //创建圆锥底面的顶点缓存区,填入顶点数据 m_pDevice->CreateVertexBuffer( sizeof(vertices2), 0, D3DFVF_CUSTOMVERTEX2, D3DPOOL_DEFAULT, &m_pVB2, NULL ); m_pVB2->Lock( 0, sizeof(vertices2), (void**)&pVertices, 0 ); memcpy( pVertices, vertices2, sizeof(vertices2) ); m_pVB2->Unlock(); } 上述代码为每个图元定义了各自的FVF和顶点缓存区,其实如果顶点格式相同,完全可以把所有顶点放到同一个缓存区,然后在调用DrawPrimitive时,给出所绘图元的第一个顶点在缓存区中的偏移量即可。现在之所以分开存储,是出于对后续章节的考虑,在下一节中,两个图元将使用不同的顶点格式。 修改函数CD3DWnd::Cleanup,释放新增加的接口指针,代码如下: (D3DWnd.cpp) void CD3DWnd::Cleanup() { m_pVB1->Release(); //释放圆锥曲面的顶点缓存区 m_pVB2->Release(); //释放圆锥底面的顶点缓存区 m_pDevice->Release(); //释放设备对象 m_pD3D->Release(); //释放Direct3D对象 } 在初始化函数CD3DWnd::InitD3D中设置渲染状态D3DRS_NORMALIZENORMALS为TRUE,确保顶点法线总是单位矢量,不受坐标变换的影响,这有助于提高渲染的精确性,但会增加处理器负担。以前的程序为了使用顶点颜色渲染,把光照处理禁用了,现在要打开。另外,由于这一次的实体是一个封闭图形,不需要渲染背面,因此要把原来允许渲染背面的语句删掉: (D3DWnd.cpp) ... ... m_pD3D->CreateDevice( ... ... m_pDevice->SetRenderState( D3DRS_ZENABLE, D3DZB_TRUE ); //打开光照处理 m_pDevice->SetRenderState( D3DRS_LIGHTING, TRUE ); //自动对法线矢量进行归一化处理 m_pDevice->SetRenderState( D3DRS_NORMALIZENORMALS, TRUE ); } 2 添加灯光和材质 我们使用一个白色的平行光进行照明,灯光方向指向左下方,矢量为{-1,-1,0};环境光设为一个亮度很低的灰度光;圆锥曲面的材质设为白色,底面设为黄色,没有镜面反射。 为CD3DWnd添加三个成员函数:SetLight、SetMaterial1、SetMaterial2,分别用于设置灯光、圆锥曲面材质、圆锥底面材质: (D3DWnd.h) ... ... LPDIRECT3DVERTEXBUFFER9 m_pVB2; void SetLight(); //该函数用于设置灯光 void SetMaterial1(); //该函数用于设置圆锥曲面的材质 void SetMaterial2(); //该函数用于设置圆锥底面的材质 ... ... (D3DWnd.cpp) void CD3DWnd::SetLight() { //创建一个白色的平行光 D3DLIGHT9 light; ::ZeroMemory( &light, sizeof(D3DLIGHT9) ); light.Type = D3DLIGHT_DIRECTIONAL; //灯光类型 light.Diffuse.r = 1.0f; light.Diffuse.g = 1.0f; light.Diffuse.b = 1.0f; light.Direction = D3DXVECTOR3( -1.0f, -1.0f, 0.0f ); light.Range = 1000.0f; //灯光的作用范围 m_pDevice->SetLight( 0, &light ); //设置灯光,参数1为灯光的索引号 m_pDevice->LightEnable( 0, TRUE );//打开灯光,参数1为灯光的索引号 //设置环境光 m_pDevice->SetRenderState( D3DRS_AMBIENT, D3DCOLOR_RGBA(32,32,32,0) ); } void CD3DWnd::SetMaterial1() { //创建一个白色的材质 D3DMATERIAL9 mtrl; ::ZeroMemory( &mtrl, sizeof(D3DMATERIAL9) ); mtrl.Diffuse.r = mtrl.Ambient.r = 1.0f; mtrl.Diffuse.g = mtrl.Ambient.g = 1.0f; mtrl.Diffuse.b = mtrl.Ambient.b = 1.0f; mtrl.Diffuse.a = mtrl.Ambient.a = 1.0f; m_pDevice->SetMaterial( &mtrl ); //设置材质 } void CD3DWnd::SetMaterial2() { //创建一个黄色的材质 D3DMATERIAL9 mtrl; ::ZeroMemory( &mtrl, sizeof(D3DMATERIAL9) ); mtrl.Diffuse.r = mtrl.Ambient.r = 1.0f; mtrl.Diffuse.g = mtrl.Ambient.g = 1.0f; mtrl.Diffuse.b = mtrl.Ambient.b = 0.0f; mtrl.Diffuse.a = mtrl.Ambient.a = 1.0f; m_pDevice->SetMaterial( &mtrl ); //设置材质 } 修改渲染函数CD3DWnd::Render,加入圆锥的绘制代码: (D3DWnd.cpp) ... ... m_pDevice->BeginScene(); SetupMatrices(); //设置变换矩阵 SetLight(); //设置灯光 //绘制圆锥曲面的图元 SetMaterial1(); //使用白色的材质 m_pDevice->SetFVF( D3DFVF_CUSTOMVERTEX1 ); m_pDevice->SetStreamSource( 0, m_pVB1, 0, sizeof(CUSTOMVERTEX1) ); m_pDevice->DrawPrimitive( D3DPT_TRIANGLEFAN, 0, 30 ); //绘制圆锥底面的图元 SetMaterial2(); //使用黄色的材质 m_pDevice->SetFVF( D3DFVF_CUSTOMVERTEX2 ); m_pDevice->SetStreamSource( 0, m_pVB2, 0, sizeof(CUSTOMVERTEX2) ); m_pDevice->DrawPrimitive( D3DPT_TRIANGLEFAN, 0, 30 ); m_pDevice->EndScene(); ... ... 编译运行程序,效果如图22所示。如果显示不正确,看是不是忘了打开Z缓存。
(图22,见文章末尾)
以上所画的圆锥,在由多个图元构成的实体中,只能算是一个比较简单的例子。绘制多图元实体的一般方法是:首先为图元设置各自的世界变换矩阵、材质和纹理(另外两个变换矩阵和灯光都属于全局性参数,设置一次即可);然后对该图元调用IDirect3DDevice9::DrawPrimitive方法。 Direct3D9学习笔记--灯光(Light)和材质(Material)灯光(Light)和材质(Material) 6.1基本概念 在前面的例程中,通过对顶点颜色进行插值来获取实体表面的颜色,这种简化的计算模型无法如实地反映真实世界。在自然界中,我们所看到的一切都是由光线产生的:光由光源出发,沿直线传播;当光线遇到物体时,一部分被吸收,剩余的被反射,该过程反复进行,直至光线能量耗尽,或者被人眼接收从而产生视觉。 在Direct3D中,用灯光和材质来模拟这个过程。灯光用于照亮实体,可分为环境光(Ambient Light)和直射光(Direct Light):前者均匀充满整个场景,为所有实体提供一个恒定的照明,没有方向性;后者一般由光源产生,具有方向性。材质则定义了实体表面对光线的反射属性。 Direct3D用结构D3DCOLORVALUE描述直射光和材质的颜色,它有4个浮点分量,分别代表红、绿、蓝、Alpha混合,正常取值范围0.0-1.0。其中Alpha混合用来产生透明效果,仅用于材质:0.0表示完全透明;1.0为不透明。 环境光颜色用一个4字节的整数D3DCOLOR描述,每个字节依次代表红、绿、蓝、Alpha混合,取值范围0-255,可以借助宏D3DCOLOR_RGBA来简化计算。和直射光一样,环境光也不使用Alpha混合。 虽然灯光和材质都有颜色,但其含义并不相同。灯光的颜色定义了光线中三原色的“数量”,红=绿=蓝=1.0为白光,都取0.0表示没有光。材质的颜色代表了在光线发生反射时,三原色被反射的“数量”,红=绿=蓝=Alpha=1.0表示所有光线都被反射,也就是说,材质看上去为白色,而红=绿=0.0,蓝=Alpha=1.0则表示只有蓝光被反射,即材质为蓝色。 6.2 灯光 环境光的使用比较简单,Direct3D把它作为一个渲染状态,通过调用IDirect3DDevice9::SetRenderState进行设置,对应的状态常数为D3DRS_AMBIENT。以下着重介绍直射光的应用。 按光源划分,直射光可分为三种: 1)点光源 点光源(Point Light)从一个点向周围均匀地发射光线,如图16所示,家用的白炽灯就是一个点光源。点光源有颜色、位置、作用范围,光强随距离而衰减,没有方向(因为向全部方向发射)。
(图16,17,见文章末尾)
2)平行光 平行光(Directional Light)由相互平行的光线组成,如图17所示,最常见的例子就是阳光。平行光只有颜色和方向,没有位置,也没有作用范围和衰减,因此不论实体位于场景的何处,所受到的光照都相同。
3)聚光灯 聚光灯(Spotlight)是三种直射光中最复杂的一种,常见的例子有手电筒、探照灯。它的光束是一个圆锥,其截面如图18所示,分内、外核两部分:内核最亮,且亮度保持不变;外核较暗,沿径向有一个衰减。图19是聚光灯的示意图,其中夹角Theta和Phi定义了内、外核的大小。聚光灯有颜色、位置、方向(即光束中心所指方向)、作用范围、衰减(沿光线方向)。
(图18,19,见文章末尾)
在Direct3D中,用结构D3DLIGHT9来描述直射光,它的定义如下: typedef struct _D3DLIGHT9 { D3DLIGHTTYPE Type; //类型:只能是点光源、平行光或聚光灯 D3DCOLORVALUE Diffuse; //灯光的漫反射颜色 D3DCOLORVALUE Specular; //灯光的镜面反射颜色 D3DCOLORVALUE Ambient; //灯光的环境光颜色 D3DVECTOR Position; //光源在世界坐标系的位置 D3DVECTOR Direction; //灯光的方向,建议使用单位矢量 float Range; //灯光的作用范围 float Falloff; //聚光灯内核到外核的衰减系数,通常取1.0,表示均匀过渡 float Attenuation0; //距离衰减系数之一:通常取0.0 float Attenuation1; //距离衰减系数之二:通常取一个大于0的常数 float Attenuation2; //距离衰减系数之三:通常取0.0 float Theta; //聚光灯的内核大小 float Phi; //聚光灯的外核大小 } D3DLIGHT9; 上述数据项中,最不好理解的恐怕要算灯光的颜色了,竟然有三种。在Direct3D的光照模型中,灯光效果由三部分组成:漫反射、镜面反射和环境光照。Direct3D以灯光的漫反射颜色和材质的漫反射颜色为输入参数,计算最终的漫反射效果,镜面反射与此类似。而灯光的环境光颜色则参于计算整个场景的环境光照,此前以渲染状态方式设置的环境光相当于公式中的常数项。 以上只是一个粗略的解释,读者在编程时,不妨试着分别改变这三种颜色,看看每种颜色所起的作用。如果对Direct3D的光照模型感兴趣,推荐阅读SDK中“Mathematics of Lighting”这篇文章,其中给出了详细的计算公式。 设置好D3DLIGHT9的各个成员后,调用IDirect3DDevice9::SetLight把直射光加入场景,然后还要执行IDirect3DDevice9::LightEnable激活它。 使用灯光会增加渲染的计算量,按从小到大排序,依次为:环境光、平行光、点光源、聚光灯。因此在编程时,要少用聚光灯,多用平行光和点光源。 6.3 材质 前面已经提过,材质用于描述实体的反光性能,Direct3D使用结构D3DMATERIAL9保存材质,它有如下成员: typedef struct _D3DMATERIAL9 { D3DCOLORVALUE Diffuse; //材质的漫反射颜色 D3DCOLORVALUE Ambient; //材质的环境光颜色 D3DCOLORVALUE Specular; //材质的镜面反射颜色 D3DCOLORVALUE Emissive; //材质的发射颜色 float Power; //材质的镜面反射强度 } D3DMATERIAL9; 漫反射颜色定义了材质对灯光中漫反射分量的反射性能,环境光颜色定义了材质对环境光照的反射性能,这二者结合在一起,决定了实体的外观颜色。在编程中,它们通常取相同的值。 镜面反射颜色定义了材质对灯光中镜面反射分量的反射性能,一般用于在材质表面产生高光部分,使实体看上去有光泽,通常取白或亮灰。Power决定镜面反射的强度,或者说,实体的光滑程度。Power越大,反光越强。图20是效果对比图,其中左边使用了镜面反射,Power为10。
(图20,见文章末尾)
发射颜色用于定义自身可以发光的材质,这种光只是让实体看上去更明亮,不能用来照明。 在D3DMATERIAL9中设置好材质的各项属性后,调用IDirect3DDevice9::SetMaterial把它加入场景。Direct3D9学习笔记--画一个三棱锥画一个三棱锥-索引缓存和Z缓存 本节将通过画一个三棱锥,介绍索引缓存(Index Buffer)和Z缓存(Z-Buffer)的用法。 5.1 什么是索引缓存 在Direct3D中,实体模型中的一个点可能被多个三角形面所共用,如图13所示的三棱锥,虽然只有4个顶点,却由4个三角形面组成。 (图13,见文章末尾) 如果象上一节那样,把顶点数据按对应图元的格式,直接放进顶点缓存区,该棱锥使用三角形列,4个锥面共需要4 x 3 = 12个顶点,也就是说,有8个顶点是重复的。如果实体比较复杂,重复的顶点会更多,造成资源浪费。 为此Direct3D引入了索引缓存的概念,把顶点的具体数据和代表图元格式的顶点顺序分开存储:顶点数据仍然放到顶点缓存区中,索引缓存区则按照图元格式,顺序存放顶点的索引。 以上面的棱锥的为例:首先在顶点缓存中保存A、B、C、D这4个顶点的FVF数据项,相应的索引为0、1、2、3;然后按照三角形列的组成顺序,把顶点索引值存入索引缓存区,4个三角形分别为△ACB、△ADC、△ABD、△BCD(注意顶点排列顺序和可视面的关系),则索引序列为0 2 1 0 3 2 0 1 3 1 2 3。这样原本要用12个顶点数据构建一个三棱锥,使用索引缓存后,只需要4个。当然了,索引缓存本身也要占用一些资源,不过和节约的顶点缓存相比少多了。 5.2 创建索引缓存 打开上一节的例程,为CD3DWnd添加一个数据成员,用来保存索引缓存区的接口指针: (D3DWnd.h) ... ... void SetupMatrices(); LPDIRECT3DINDEXBUFFER9 m_pIB; //索引缓存区的接口指针 ... ... 修改函数CD3DWnd::InitGeometry中的建模部分,并添加索引缓存区的创建代码: (D3DWnd.cpp) void CD3DWnd::InitGeometry() { //三棱锥的数学模型 CUSTOMVERTEX vertices[] = //FVF顶点数据 {{ 0.0f, 1.0f, 0.0f, D3DCOLOR_XRGB(0,255,0) }, //点A,绿色 { -1.0f, -1.0f, -0.577f, D3DCOLOR_XRGB(255,0,0) }, //点B,红色 { 1.0f, -1.0f, -0.577f, D3DCOLOR_XRGB(0,255,255) }, //点C,浅蓝 { 0.0f, -1.0f, 1.155f, D3DCOLOR_XRGB(255,0,255) }}; //点D,粉红 WORD indices[] = { 0, 2, 1, 0, 3, 2, 0, 1, 3, 1, 2, 3 }; //索引序列 //创建顶点缓存区,并获取接口IDirect3DVertexBuffer9的指针 m_pDevice->CreateVertexBuffer( sizeof(vertices), //缓存区尺寸 0, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &m_pVB, NULL ); //把顶点数据填入顶点缓存区 void* pVertices; m_pVB->Lock( 0, sizeof(vertices), (void**)&pVertices, 0 ); memcpy( pVertices, vertices, sizeof(vertices) ); m_pVB->Unlock(); //创建索引缓存区,并获取接口LPDIRECT3DINDEXBUFFER9的指针 m_pDevice->CreateIndexBuffer( sizeof(indices), //缓存区尺寸 0, D3DFMT_INDEX16, //使用16 bit的索引值 D3DPOOL_DEFAULT, &m_pIB, NULL ); //把索引值填入索引缓存区 void *pIndices; m_pIB->Lock( 0, sizeof(indices), (void**)&pIndices, 0 ); memcpy( pIndices, indices, sizeof(indices) ); m_pIB->Unlock(); } 还要在CD3DWnd::Cleanup中添加索引缓存区的释放代码: (D3DWnd.cpp) ... ... m_pIB->Release(); //释放索引缓存区 m_pVB->Release(); ... ... 5.3 渲染索引缓存 由于使用了索引缓存,因此函数CD3DWnd::Render中的渲染部分也要进行相应修改: (D3DWnd.cpp) ... ... m_pDevice->SetStreamSource( 0, m_pVB, 0, sizeof(CUSTOMVERTEX) ); //绑定索引缓存区 m_pDevice->SetIndices( m_pIB ); //从索引缓存区绘制图元,参数1为图元格式,参数4为顶点数,参数6为三角形数 m_pDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, 0, 0, 4, 0, 4 ); m_pDevice->EndScene(); ... ... 编译运行程序,然后弹出Direct3D窗口,可以看到一个旋转的三棱锥。细心的读者可能会发现,棱锥看上去好像有点透明,这是因为没有打开Z缓存的缘故,此时Direct3D只是简单地按图元格式,顺序渲染三角形,没有考虑平面之间的遮挡关系,从而导致问题的出现。 5.4 打开Z缓存 在Direct3D中,使用深度缓存区(Depth Buffer)来进行消隐处理(隐藏面消除),以确保实体被遮挡的部分不被显示。Z缓存是最常用的一种深度缓存,它因为用Z坐标作为判断深度(远近)的依据而得名,其工作原理如图14所示,图中的渲染表面相当于Direct3D窗口,Z缓存用来保存窗口中各个像素的深度。在消隐时,Direct3D先用背景色(或纹理)填充渲染表面,Z缓存则统一设置成最大深度,即投影变换中后裁剪平面的距离,然后逐像素处理渲染表面:对于任意一个像素,Direct3D逐一测试所有与该像素重叠的三角形,如果三角形中像素对应点的Z坐标小于Z缓存中的数值,也就是说,此三角形离观察者较近,则像素取该点的颜色,同时像素在Z缓存中的深度也设为该点的Z坐标,然后继续测试下一个三角形... ... (图14,见文章末尾) Z缓存的工作原理说起来有些拗口,不过使用还是很容易的。首先在初始化函数CD3DWnd::InitD3D中添加如下代码,以便在创建设备对象的同时生成Z缓存: (D3DWnd.cpp) ... ... d3dpp.BackBufferFormat = D3DFMT_UNKNOWN; d3dpp.EnableAutoDepthStencil = TRUE; d3dpp.AutoDepthStencilFormat = D3DFMT_D16; //生成16 bit的Z缓存 m_pD3D->CreateDevice( ... ... //启用Z缓存,允许消隐处理 m_pDevice->SetRenderState( D3DRS_ZENABLE, D3DZB_TRUE ); ... ... 然后修改CD3DWnd::Render中m_pDevice->Clear的调用参数,在清除后备缓存区的同时,把Z缓存统一设置为最大深度1.0: (D3DWnd.cpp) ... ... m_pDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0,0,255), 1.0f, 0); ... ... 此时再编译运行程序,显示效果如图15所示。 (图15,见文章末尾) 除Z缓存外,Direct3D中还有一种深度缓存-W缓存(W-Buffer),用于变换后的坐标空间(w的全称是Reciprocal Homogeneous W,简写为RHW),限于篇幅,这里就不介绍了。 虽然本节使用了索引缓存绘制三棱锥,但它并不适合画多边形,因为索引缓存会产生公共顶点,其顶点法线不好确定。只不过该例程是使用顶点颜色进行渲染,没使用光照,因此不需要顶点法线。 Direct3D9学习笔记--画一个三角形(二)画一个三角形(二) [ 接Direct3D9学习笔记--画一个三角形(一)] 3 设置变换矩阵 在本例中,我们把实体放到世界坐标系的原点,让它绕Y轴做顺时针旋转,为此要用到一个定时器,在WM_TIMER的处理函数中不断改变旋转角度。 对于视角变换,观察点定在(0,3,-5),视线目标点取原点,上方向取Y轴的正向,对应矢量为{0,1,0}。 投影变换的可视角取π/4,高宽比取1,两个裁剪平面的距离分别取1和100。 下面是生成变换矩阵的代码: (D3DWnd.h) ... ... void InitGeometry(); int m_nRotateY; //实体的旋转角度(单位:度) void SetupMatrices(); //该函数用于设置三个变换矩阵 ... ... (D3DWnd.cpp) void CD3DWnd::SetupMatrices() { float angle = m_nRotateY * D3DX_PI / 180; //把旋转角换算成弧度 D3DXMATRIX matWorld; //计算世界变换矩阵 ::D3DXMatrixRotationY( &matWorld, angle ); //把世界变换矩阵设置到渲染环境 m_pDevice->SetTransform( D3DTS_WORLD, &matWorld ); D3DXVECTOR3 eye( 0.0f, 3.0f,-5.0f ); //观察点 D3DXVECTOR3 lookat( 0.0f, 0.0f, 0.0f ); //视线目标点 D3DXVECTOR3 up( 0.0f, 1.0f, 0.0f ); //上方向 D3DXMATRIX matView; //计算视角变换矩阵 ::D3DXMatrixLookAtLH( &matView, &eye, &lookat, &up ); //把视角变换矩阵设置到渲染环境 m_pDevice->SetTransform( D3DTS_VIEW, &matView ); D3DXMATRIXA16 matProj; //计算透视投影变换矩阵 ::D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4, 1.0f, 1.0f, 100.0f ); //把投影变换矩阵设置到渲染环境 m_pDevice->SetTransform( D3DTS_PROJECTION, &matProj ); } 在CD3DWnd::OnCreate中添加定时器的初始化语句: (D3DWnd.cpp) ... ... InitD3D(); InitGeometry(); m_nRotateY = 0; SetTimer( 1, 40 ,NULL ); //定时间隔设为40毫秒 ... ... 用类向导为CD3DWnd添加WM_TIMER的消息处理函数OnTimer,在其中累加旋转角度: (D3DWnd.cpp) void CD3DWnd::OnTimer(UINT nIDEvent) { m_nRotateY += 2; //每次旋转2度 CWnd::OnTimer(nIDEvent); } 4 渲染 修改CD3DWnd::Render,在其中加入三角形的绘制语句: (D3DWnd.cpp) ... ... m_pDevice->BeginScene(); SetupMatrices(); //设置变换矩阵 //设置自定义的FVF m_pDevice->SetFVF( D3DFVF_CUSTOMVERTEX ); //绑定顶点缓存区至设备数据源 m_pDevice->SetStreamSource( 0, m_pVB, 0, sizeof(CUSTOMVERTEX) ); //绘制图元,其中参数1为图元格式,参数3为三角形数目 m_pDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, 1 ); m_pDevice->EndScene(); ... ... 因为要让三角形旋转,所以Render的调用改放在时钟消息处理函数OnTimer中,原来的OnPaint函数用类向导予以删除: (D3DWnd.cpp) ... ... Render(); //渲染 m_nRotateY += 2; ... ... 最后,要禁用光照处理(缺省是打开的)。因为本例程使用顶点颜色进行渲染,如果不禁用的话,将会看到一个黑糊糊的三角形。另外,缺省情况下Direct3D只挑选三角形的正面进行渲染,当旋转到一定角度,背面朝向观察者时,图像会消失,因此要关闭该项特性。相关代码加在函数CD3DWnd::InitD3D中: (D3DWnd.cpp) ... ... m_pD3D->CreateDevice( ... ... //因为使用顶点颜色渲染,所以要禁用光照处理 m_pDevice->SetRenderState( D3DRS_LIGHTING, FALSE ); //关闭“挑选”功能,允许渲染背面 m_pDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE ); ... ... 编译运行程序,点击工具按钮ID_D3D_BEGIN,将会出现一个旋转的彩色三角形,如图12所示。请读者妥善备份本节生成的例程,因为后面要用到它。 (图12,见文章末尾)January 18 Direct3D9学习笔记--画一个三角形(一)画一个三角形(一) 前面讲了一大堆抽象的理论,现在让我们理论联系实际,来画一个最简单的三角形。 1 建模 该三角形在本地坐标系的数学模型如图11所示,三个顶点的本地坐标分别为A(-1,-1,0)、B(0,1,0)、C(1,-1,0)。选取三角形列作为实体的图元格式,顶点排列顺序为A、B、C,正面(可视面)朝外。 (图11,见文章末尾) 2 创建顶点缓存区 首先要定义顶点格式,Direct3D采用了一种被称之为“可变形顶点格式 Flexible Vertex Format((FVF)”的技术,除顶点坐标外,还可以包括顶点的法线、颜色、纹理等数据。在本节中,用到了坐标和颜色。通常情况下,实体的外观由材质、光照和纹理决定,不需要再另外为顶点定义颜色,但目前还没有讲到这些内容,因此要给出顶点的颜色。Direct3D在渲染时,将使用顶点颜色,通过插值算法来填充三角形。 打开上一节生成的例程d3d001,在D3DWnd.cpp的开始部分中加入FVF的定义: (D3DWnd.cpp) ... ... #include "D3DWnd.h" //定义FVF的顶点结构 struct CUSTOMVERTEX { float x, y, z; //顶点坐标 DWORD color; //顶点颜色 }; //定义FVF用到的数据项:坐标 颜色 #define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ | D3DFVF_DIFFUSE) ... ... 在Direct3D中,FVF顶点数据按图元的格式,顺序存放在顶点缓存区(Vertex Buffer),它是一个COM对象,通过接口IDirect3DVertexBuffer9访问。以下是创建顶点缓存区的代码: (D3DWnd.h) ... ... void Cleanup(); LPDIRECT3DVERTEXBUFFER9 m_pVB; //顶点缓存区的的接口指针 void InitGeometry(); //该函数用于建模 ... ... (D3DWnd.cpp) void CD3DWnd::InitGeometry() { //三角形实体的数学模型 CUSTOMVERTEX vertices[] = {{ -1.0f, -1.0f, 0.0f, D3DCOLOR_XRGB(255,0,0) }, //点A,红色 { 0.0f, 1.0f, 0.0f, D3DCOLOR_XRGB(0,255,0) }, //点B,绿色 { 1.0f, -1.0f, 0.0f, D3DCOLOR_XRGB(0,255,255) }};//点C,浅蓝 //创建顶点缓存区,并获取接口IDirect3DVertexBuffer9的指针 m_pDevice->CreateVertexBuffer( sizeof(vertices), //缓存区尺寸 0, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &m_pVB, NULL ); //把顶点数据填入顶点缓存区 void* pVertices; m_pVB->Lock( 0, sizeof(vertices), (void**)&pVertices, 0 ); memcpy( pVertices, vertices, sizeof(vertices) ); m_pVB->Unlock(); } 修改CD3DWnd::OnCreate,加入对InitGeometry的调用: (D3DWnd.cpp) ... ... InitD3D(); InitGeometry(); //进行建模 ... ... 由于顶点缓存区是COM对象,还要在CD3DWnd::Cleanup中添加它的释放代码: (D3DWnd.cpp) ... ... m_pVB->Release(); //释放顶点缓存区 m_pDevice->Release(); ... ...
[ 剩余部分见Direct3D9学习笔记--画一个三角形(二)] Direct3D9学习笔记--3D的数学基础3D的数学基础 1 一些数学概念 在开始编程之前,读者有必要了解一些有关三维坐标系的基本概念。 1.1 三维坐标系、点、矢量 按坐标轴之间的相互关系划分,三维坐标系可分为左手坐标系和右手坐标系,如图3所示。在左手坐标系中,坐标轴的定义符合左手法则:左手四个手指的旋转方向从X轴到Y轴,大拇指的指向就是Z轴。右手坐标系依次类推。Direct3D使用左手坐标系,其中X轴表示左右,Y轴表示上下,Z轴表示远近(深度)。 (图3,见文章末尾) 取定坐标系后,空间中的任意一点可以用一组坐标值(x,y,z)来表示。矢量是空间中的一条有向线段,Direct3D用它来标识空间方向。矢量的表示方法与点坐标类似,也是用{x,y,z},不过它表示的是从原点指向点(x,y,z)的有向线段。矢量和起点无关,只要两个矢量同向(平行)且等长,就认为它们相等。在Direct3D中,点和矢量通常使用同一个结构D3DXVECTOR3保存。 矢量的计算公式很简单:假设矢量的起点为M(x1,y1,z1),终点为N(x2,y2,z2),则矢量={x2-x1,y2-y1,z2-z1}。 矢量除了方向属性外,也有大小(长度),但是Direct3D一般不用。为了避免矢量的大小给计算带来误差,可以用函数D3DXVec3Normalize把它变换成单位矢量(长度为1)。 1.2 三角形、平面法线、顶点法线 在Direct3D中,三角形是构成实体的基本单位,因为一个三角形正好是一个平面,以三角形面为单位进行渲染效率最高。大量的三角形组合在一起,构成复杂的多边形或者曲面。图4是一个球面的例子。 (图四,见文章末尾) 一个三角形由三个点构成,习惯上把这些点称为顶点(Vertex)。三角形平面有正、反面之分,由顶点的排列顺序决定:顶点按顺时针排列的表面是正面,如图5所示。其中与三角形平面垂直、且指向正面的矢量称为该平面的法线(Normal)。在Direct3D中,为了提高渲染效率,缺省条件下只有正面可见,不过可以通过IDirect3DDevice9::SetRenderState来改变设置,其对应的渲染状态常数为D3DRS_CULLMODE,具体用法请参阅SDK文档。 (图五,见文章末尾) 顶点法线(Vertex Normal)是过顶点的一个矢量,用于在高洛德着色(Gouraud Shading)中计算光照和纹理效果。在生成曲面时,通常令顶点法线和相邻平面的法线保持等角,如图6-1所示,这样进行渲染时,会在平面接缝处产生一种平滑过渡的效果。如果是多边形,则令顶点法线等于该点所属平面(三角形)的法线,如图6-2所示,以便在接缝处产生突出的边缘。 (图6-1,图6-2,见文章末尾) 1.3 Direct3D设备支持的图元(Primitive)格式 在Direct3D中,三维实体都是由一些基本图元组合而成的,总共有6种图元格式(示例参见图7-1-6): 点列(Point Lists)
由顶点组成的集合; 线列(Line Lists)
由直线段组成的集合; 线带(Line Strips)
由互相连接的直线段组成的集合; 三角形列(Triangle Lists)
由三角形组成的集合,每三个顶点构成一个三角形; 三角形带(Triangle Strips)
由相接的三角形组成的集合。在例图中,v1、v2、v3构成第一个三角形,v2、v3、v4构成第二个三角形... ...(注意:三角形带的正面由第一个三角形决定,因此第二个三角形顶点的排列顺序实际上应该为v2、v4、v3); 三角扇形(Triangle Fans)
由相接且共点的三角形组成,v1、v2、v3构成第一个三角形,v1、v3、v4构成第二个三角形... ...; (图7-1,7-2,7-3,7-4,7-5,7-6,见文章末尾) 上述图元中,后三种以三角形为单位的图元比较常用。其中三角形列适用范围较广,既能用于多边形,也可用于曲面;而三角形带和三角扇形由于存在公共顶点,如果用来创建多边形,其公共顶点的法线不好确定,因此通常只用于曲面,不过在三角形数目相同的情况下,它俩使用的顶点数目要比前者少得多。 1.4 坐标变换 1)世界变换 我们在建立三维实体的数学模型时,通常以实体的某一点为坐标原点,比如一个球体,很自然就用球心做原点,这样构成的坐标系称为本地坐标系(Local Coordinates)。实体总是位于某个场景(World Space)中,而场景采用世界坐标系(World Coordinates),如图8所示,因此需要把实体的本地坐标变换成世界坐标,这个变换被称为世界变换(World Transformation)。 (图八,见文章末尾) 在Direct3D中,坐标变换通过一个4x4矩阵来实现,对于世界变换,只要给出实体在场景中的位置信息,就可以借助Direct3D函数得到变换矩阵,具体的计算步骤如下: 首先把实体放置在在世界坐标系原点,使两个坐标系重合; 在世界空间中,对实体进行平行移动,其对应的平移变换阵TT可由函数D3DXMatrixTranslation求得; 把平移后的实体沿自身的Z轴旋转一个角度(角度大于0,表示从Z轴的正向朝原点看去,旋转方向为顺时针;反之为逆时针,下同),对应的旋转变换阵TZ用D3DXMatrixRotationZ计算; 把实体沿自身的Y轴旋转一个角度,用D3DXMatrixRotationY求出变换阵TY; 把实体沿自身的X轴旋转一个角度,用D3DXMatrixRotationX求出变换阵TX; 最后对实体进行缩放,假设三个轴的缩放系数分别为sx、sy、sz,该操作对应的变换阵TS可由函数D3DXMatrixScaling求得; 最终的世界变换矩阵TW = TS·TX·TY·TZ·TT ,在Direct3D中,矩阵乘法用函数D3DXMatrixMultiply实现,注意相乘顺序为操作的逆序。
从以上描述中,我们很容易得出:实体的运动可以通过不断改变世界变换矩阵来实现。 2)视角变换 实体确定后,接下来要确定观察者在世界坐标系中的方位,换句话说,就是在世界坐标系中如何放置摄像机。观察者(摄像机)所看到的景象,就是Direct3D窗口显示的内容。 确定观察者需要三个量: 观察者的点坐标; 视线方向,为一个矢量,不过Direct3D用视线上的一个点来替代,此时视线方向就是从观察者指向该目标点,这样表示更直观一些; 上方向,通俗地说,就是观察者的头顶方向,用一个矢量表示。
确定后,以观察者为原点,视线为Z轴,上方向或它的一个分量为Y轴(X轴可由左手法则得出,为右方向),构成了视角坐标系,如图9所示。我们需要把实体从世界空间转换到视角空间,这个坐标变换被称为视角变换(View Transformation)。 (图九,见文章末尾) 与世界变换相比,视角变换矩阵的获取要容易得多,只需调用一个函数D3DXMatrixLookAtLH,其输入参数就是决定观察者的那三个量。 3)投影变换 实体转换到视角空间后,还要经过投影变换(Projection Transformation),三维的实体才能显示在二维的计算机屏幕上。打个比方,如果把屏幕看做照相机中的胶卷,那么投影变换就相当于照相机的镜头。 Direct3D使用透视投影变换(Perspective Transformation),此时在视角空间中,可视区域是一个以视线为轴心的棱台(Viewing Frustum),如图10所示。想象一下你处在一个伸手不见五指的房间里,面前有一扇窗户,你可以透过窗户看到各种景物。窗户就是棱台的前裁剪平面,天空、远山等背景是后裁剪平面,其间的可视范围是景深。投影变换把位于可视棱台内的景物投影到前裁剪平面,由于采用透视投影,距离观察者远的对象会变小,从而更具有真实感。在Direct3D中,前裁剪平面被映射到程序窗口,最终形成了我们在屏幕上看到的画面。 (图十,见文章末尾) 透视投影变换由四个量决定: 前裁剪平面的宽度w; 前裁剪平面的高度h; 前裁剪平面到原点的距离z1; 后裁剪平面到原点的距离z2。 由于w、h用起来不是很直观,因此实际应用中,常用fov和aspect代替w、h,其中fov是Y方向上的可视角度,通常取π/4;aspect是前裁剪平面的高度与宽度之比,通常取1(由三角函数定义,易知h=2·z1·tg(fov/2),w=h/aspect)。用这四个量来调用函数D3DXMatrixPerspectiveFovLH,即可获得投影变换矩阵。 得到三个变换矩阵后,还需要调用方法IDirect3DDevice9::SetTransform把它们设置到渲染环境中,具体用法参见后面的例程。 最后,可以用三句话来概括这些变换的作用:世界变换决定实体的位置;视角变换决定观察者的位置;投影变换决定观察者的可视区域。 至此,相关的数学部分终于讲完了,可能枯燥了点,但却是掌握Direct3D的关键。作为程序员,虽然不需要我们了解这些算法的来历、推导等,但一定要知道它们是干什么用的以及如何用。
Direct3D9学习笔记--初始化Direct3D第一个程序-初始化Direct3D 在本节中,我们将编写一个简单的Direct3D程序,它在MFC单文档程序的基础上,生成一个蓝色背景的Direct3D窗口。通过该例程,我们可以了解Direct3D的初始化过程。 1 创建程序框架 进入VC,新建一个工程d3d001,工程类型选“MFC AppWizard (exe)”,即MFC应用程序,然后把程序类型设置为“Single document”-单文档,其余选项使用缺省设置即可。之所以选择单文档程序,是为了借用MFC的界面和消息处理机制,至于文档和视图,这里用不上。 打开类向导,以CWnd为基类,派生一个窗口类CD3DWnd。我们把它作为Direct3D窗口,这意味着绘制好的三维图形将显示在该窗口,而所有和Direct3D相关的代码也都放在该类中。 接下来添加一些代码,以便在程序运行时显示该窗口: 编辑工具条资源IDR_MAINFRAME,增加两个按钮,命令ID分别设为ID_D3D_BEGIN和ID_D3D_END; 为主窗口CMainFrame增加一个CD3DWnd类型的数据成员,代码如下(黑体为用户新输入的部分,下同):
(MainFrm.h) ... ... #include "D3DWnd.h" class CMainFrame : public CFrameWnd { protected: CD3DWnd m_wndD3D; ... ... 利用类向导,为CMianFrame添加工具按钮ID_D3D_BEGIN和ID_D3D_END的消息处理函数,前者用于创建并显示一个CD3DWnd窗口,后者则用来销毁它。代码如下:
(MainFrm.cpp) void CMainFrame::OnD3dBegin() { m_wndD3D.CreateEx( 0, AfxRegisterWndClass(0,NULL,NULL,NULL), "Direct3D窗口", WS_POPUP | WS_CAPTION | WS_VISIBLE, CRect(100,100,500,500), this, 0); } void CMainFrame::OnD3dEnd() { m_wndD3D.DestroyWindow(); } 编译并运行程序,然后点击按钮ID_D3D_BEGIN,会弹出一个窗口,点击另一个按钮,窗口消失。当然了,目前为止它还只是一个普通的MFC窗口,下面我们为它添加Direct3D功能。 请读者注意,本文中的程序均忽略了出错情况下的处理,这样做的目的是为了简化代码。在正式编程中,还是应该考虑进行适当的出错处理。 2 初始化Direct3D 对于Direct3D程序来说,第一步要做的就是创建Direct3D对象,然后用该对象创建设备对象。设备对象可以说是Direct3D中最重要的部件,几乎所有的3D绘图功能都要通过它实现。 为类CD3DWnd添加下列成员: (D3DWnd.h) ... ... #include <d3d9.h> #include <d3dx9math.h> class CD3DWnd : public CWnd { protected: LPDIRECT3D9 m_pD3D; //Direct3D对象的接口指针 LPDIRECT3DDEVICE9 m_pDevice; //设备对象的接口指针 void InitD3D(); //该函数用于初始化Direct3D ... ... 输入初始化函数InitD3D的代码: (D3DWnd.cpp) void CD3DWnd::InitD3D() { //创建Direct3D对象,并获取接口IDirect3D9的指针, //我们将通过该指针操作Direct3D对象。 m_pD3D = ::Direct3DCreate9(D3D_SDK_VERSION); D3DPRESENT_PARAMETERS d3dpp; ::ZeroMemory(&d3dpp, sizeof(d3dpp)); d3dpp.Windowed = TRUE; //创建窗口模式的Direct3D程序 d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.BackBufferFormat = D3DFMT_UNKNOWN; //调用方法IDirect3D9::CreateDevice创建设备对象,并获取 //接口IDirect3DDevice9的指针,我们将通过该指针操作设备对象 m_pD3D->CreateDevice( D3DADAPTER_DEFAULT, //使用缺省的显卡 D3DDEVTYPE_HAL, //指定设备类型为HAL m_hWnd, //Direct3D窗口的句柄 D3DCREATE_SOFTWARE_VERTEXPROCESSING,//软件顶点处理 &d3dpp, &m_pDevice); } 作为Direct3D中的渲染部件,设备对象用于顶点变换、光照处理以及矢量图形的光栅化。Direct3D目前支持两种设备类型:HAL(硬件抽象层 Hardware Abstraction Layer)和Reference。前者启用硬件三维加速功能,需要显卡支持,如NVIDIA的TNT、GeForce系列;后者则以软件模拟方式完成三维处理,虽然速度慢,但可以在任意显卡上运行,一般用于调试程序,其对应的参数为D3DDEVTYPE_REF。 上述代码使用了软件顶点处理方式,如果显卡支持硬件T/L,如GeForce系列,可以选用硬件方式以提高效率,调用参数为: D3DCREATE_HARDWARE_VERTEXPROCESSING。 用类向导为CD3DWnd添加WM_CREATE的消息处理函数OnCreate,在其中调用InitD3D,以便在创建窗口的同时完成初始化工作: (D3DWnd.cpp) int CD3DWnd::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CWnd::OnCreate(lpCreateStruct) == -1) return -1; InitD3D(); //初始化Direct3D return 0; } 3 渲染 初始化完成后,就可以开始执行渲染操作,即前面所说的绘图。 在Direct3D中,一个设备对象至少包含两个显示缓存区:当前缓存区(Front Buffer)和后备缓存区(Back Buffer),前者可以看成Direct3D窗口的映射。当我们渲染图形时,实际上并不是直接在窗口上输出,而是在后备缓存区上绘图。渲染完毕后,交换两个缓存区,使原来的后备缓存区变成当前缓存区,从而实现窗口刷新,如图1所示。快速重复此过程,就会在屏幕上形成连续的动画。
(图一,见文章末尾)
现在来编写渲染部分的代码,给CD3DWnd添加一个成员函数Render: (D3DWnd.h) ... ... void InitD3D(); void Render(); //该函数用于渲染 ... ... (D3DWnd.cpp) void CD3DWnd::Render() { //用指定颜色清除后备缓存区 m_pDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,255), //指定使用蓝色 1.0f, 0); //Direct3D规定在渲染前必须调用方法IDirect3DDevice9::BeginScene, //结束时要调用IDirect3DDevice9::EndScene。 m_pDevice->BeginScene(); //实际的渲染代码放在此处。因为本节只是为了演示如何初始化Direct3D, //所以这里为空,生成的Direct3D窗口将是一个蓝色背景的空白窗口 m_pDevice->EndScene(); //交换当前/后备缓存区,刷新窗口 m_pDevice->Present(NULL, NULL, NULL, NULL); } 打开类向导,为CD3DWnd添加WM_PAINT的消息处理函数OnPaint,在其中调用Render: (D3DWnd.cpp) void CD3DWnd::OnPaint() { CPaintDC dc(this); Render(); //渲染 } 3.4 释放接口 为CD3DWnd添加成员函数Cleanup,输入“释放”代码: (D3DWnd.h) ... ... void Render(); void Cleanup(); //该函数用于释放接口 ... ... (D3DWnd.cpp) void CD3DWnd::Cleanup() { m_pDevice->Release(); //释放设备对象 m_pD3D->Release(); //释放Direct3D对象 } 用类向导为CD3DWnd添加WM_DESTROY的消息处理函数OnDestroy,在其中调用Cleanup。这样当窗口关闭时,就会自动释放接口: (D3DWnd.cpp) void CD3DWnd::OnDestroy() { CWnd::OnDestroy(); Cleanup(); //释放接口 } 现在代码已经全部输入完毕,不过在编译前,还需要为连接器指定Direct3D的导入库:选择VC的菜单项“Project/Settings...”,然后选中“Link”标签,在“Object/library modules”栏输入“d3d9.lib d3dx9.lib”(本节中的例程只用到了d3d9.lib,另外一个库文件是为后面程序准备的)。 编译运行程序,点击按钮ID_D3D_BEGIN,会弹出一个蓝色的Direct3D窗口(见图2),点击另一个按钮,窗口消失。至此,我们的第一个Direct3D程序就算是大功告成了。
(图二,见文章末尾)
|
|
|