欣's profileAries' Matrix Ver#1.0PhotosBlogListsMore Tools Help

Blog


    January 19

    Direct3D9学习笔记--画一个圆锥

    画一个圆锥

    为了便于读者理解,本节及后续章节的例程均不使用索引缓存,因此我们将在第4节例程的基础上进行修改。不过在输入下列代码之前,请先参照第5节的示例,打开Z缓存。

    1 建模

    圆锥由一个曲面和一个底面构成,如图21所示,用两个图元构建实体比较合理,其中每个图元各代表一个面。考虑到圆锥曲面展开后为一个扇面,所以图元格式采用三角扇形,而底面是一个圆,也采用三角扇形,取圆锥外表面做图元的正面。在图21中,A为圆锥的顶点,B为底面的圆心,把底面分成30等份,得到30个分割点C1C2 ... ... C30,则圆锥曲面的图元所对应的顶点序列为AC1C2 ... ... C30C1,共32个顶点,注意在结尾处C1点重复出现了一次,否则画出的圆锥不完整。同理底面也由32个顶点组成,依次为BC1C30C29 ... ... C1

    (图21,见文章末尾)

    设圆锥高为2,底面半径为1,以圆锥中心线AB(图中的蓝线)做X轴,坐标原点设在AB中点,A的坐标为(-100),则B的坐标为(100),C1C30的坐标为(1sinθkcosθk)。

    由于使用灯光照明,还需要提供顶点法线:对于圆锥曲面,A的顶点法线取X轴的负方向,即矢量{-100},C1C30的顶点法线取有向线段BCk,矢量为{0sinθksinθk};对于圆锥底面,顶点法线都设为X轴的正方向,矢量为{100}。请注意,在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_pVB1m_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++)

    { //计算顶点序列C1C2 ... ... C30C1的坐标和法线

    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++)

    { //计算顶点序列C1C30C29 ... ... 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_NORMALIZENORMALSTRUE,确保顶点法线总是单位矢量,不受坐标变换的影响,这有助于提高渲染的精确性,但会增加处理器负担。以前的程序为了使用顶点颜色渲染,把光照处理禁用了,现在要打开。另外,由于这一次的实体是一个封闭图形,不需要渲染背面,因此要把原来允许渲染背面的语句删掉:

    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-10};环境光设为一个亮度很低的灰度光;圆锥曲面的材质设为白色,底面设为黄色,没有镜面反射。

    CD3DWnd添加三个成员函数:SetLightSetMaterial1SetMaterial2,分别用于设置灯光、圆锥曲面材质、圆锥底面材质:

    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是聚光灯的示意图,其中夹角ThetaPhi定义了内、外核的大小。聚光灯有颜色、位置、方向(即光束中心所指方向)、作用范围、衰减(沿光线方向)。
    (图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是效果对比图,其中左边使用了镜面反射,Power10
    (图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引入了索引缓存的概念,把顶点的具体数据和代表图元格式的顶点顺序分开存储:顶点数据仍然放到顶点缓存区中,索引缓存区则按照图元格式,顺序存放顶点的索引。

    以上面的棱锥的为例:首先在顶点缓存中保存ABCD4个顶点的FVF数据项,相应的索引为0123;然后按照三角形列的组成顺序,把顶点索引值存入索引缓存区,4个三角形分别为ACBADCABDBCD(注意顶点排列顺序和可视面的关系),则索引序列为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 bitZ缓存

    m_pD3D->CreateDevice( ... ...

    //启用Z缓存,允许消隐处理

    m_pDevice->SetRenderState( D3DRS_ZENABLE, D3DZB_TRUE );

    ... ...

    然后修改CD3DWnd::Renderm_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的处理函数中不断改变旋转角度。

    对于视角变换,观察点定在(03-5),视线目标点取原点,上方向取Y轴的正向,对应矢量为{010}。

    投影变换的可视角取π/4,高宽比取1,两个裁剪平面的距离分别取1100

    下面是生成变换矩阵的代码:

    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-10)、B010)、C1-10)。选取三角形列作为实体的图元格式,顶点排列顺序为ABC,正面(可视面)朝外。

    (图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,见文章末尾)

    取定坐标系后,空间中的任意一点可以用一组坐标值(xyz)来表示。矢量是空间中的一条有向线段,Direct3D用它来标识空间方向。矢量的表示方法与点坐标类似,也是用{xyz},不过它表示的是从原点指向点(xyz)的有向线段。矢量和起点无关,只要两个矢量同向(平行)且等长,就认为它们相等。在Direct3D中,点和矢量通常使用同一个结构D3DXVECTOR3保存。

    矢量的计算公式很简单:假设矢量的起点为Mx1y1z1),终点为Nx2y2z2),则矢量=x2-x1y2-y1z2-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-16):

    􀂾 点列(Point Lists

     

    由顶点组成的集合;

    􀂾 线列(Line Lists

     

    由直线段组成的集合;

    􀂾 线带(Line Strips

     

    由互相连接的直线段组成的集合;

    􀂾 三角形列(Triangle Lists

     

    由三角形组成的集合,每三个顶点构成一个三角形;

    􀂾 三角形带(Triangle Strips

     

    由相接的三角形组成的集合。在例图中,v1v2v3构成第一个三角形,v2v3v4构成第二个三角形... ...(注意:三角形带的正面由第一个三角形决定,因此第二个三角形顶点的排列顺序实际上应该为v2v4v3);

    􀂾 三角扇形(Triangle Fans

     

    由相接且共点的三角形组成,v1v2v3构成第一个三角形,v1v3v4构成第二个三角形... ...

    (图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轴的正向朝原点看去,旋转方向为顺时针;反之为逆时针,下同),对应的旋转变换阵TZD3DXMatrixRotationZ计算;

    􀂾 把实体沿自身的Y轴旋转一个角度,用D3DXMatrixRotationY求出变换阵TY

    􀂾 把实体沿自身的X轴旋转一个角度,用D3DXMatrixRotationX求出变换阵TX

    􀂾 最后对实体进行缩放,假设三个轴的缩放系数分别为sxsysz,该操作对应的变换阵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

    由于wh用起来不是很直观,因此实际应用中,常用fovaspect代替wh,其中fovY方向上的可视角度,通常取π/4aspect是前裁剪平面的高度与宽度之比,通常取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_BEGINID_D3D_END

    􀂾 为主窗口CMainFrame增加一个CD3DWnd类型的数据成员,代码如下(黑体为用户新输入的部分,下同):

     

    MainFrm.h

    ... ...

    #include "D3DWnd.h"

    class CMainFrame : public CFrameWnd

    {

    protected:

    CD3DWnd m_wndD3D;

    ... ...

    􀂾 利用类向导,为CMianFrame添加工具按钮ID_D3D_BEGINID_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。前者启用硬件三维加速功能,需要显卡支持,如NVIDIATNTGeForce系列;后者则以软件模拟方式完成三维处理,虽然速度慢,但可以在任意显卡上运行,一般用于调试程序,其对应的参数为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程序就算是大功告成了。
    (图二,见文章末尾)