zoukankan      html  css  js  c++  java
  • 47. 模型加载

    在现代游戏中,人物和模型由针对每个对象的成千上万个多边形组成。对纹理映射而言这非常复杂,因为它需要为每个多边形的每个顶点设置一对TU和TV纹理坐标。假定有一个包含了5000个三角形的模型。假定不涉及加索引的几何图形(索引),而只使用三角形,那么在整个模型中将有15000个顶点。至此,除了内置的Direct3D以外,还得手动指定几何图形数据。如果一个模型包含5000个多边形,那么试着指定这些多边形中的每个几何图形数据,就会是一场噩梦。同时还要为顶点手动设置纹理坐标,从时间角度而言,这也是不可以接受的,并且绝对不现实。即使设法对所有模型都做了这一点,那么也会存在将这些信息硬编码到源代码中,以及其他游戏不可使用相同资源的问题。所要做的工作就是可以比较简单地开发资源,并能够从游戏程序外获取这些资源。幸运的是,本章将介绍这些内容。

      模型加载介绍
           
    本章旨在介绍将不同类型文件中的几何图形数据加载到Direct3D程序中的方法。关于这方面,最好的内容就是可以在场景中包含复杂的和施加了纹理的对象,而这只需添加几行加载和显示数据的代码。然后可以在多个程序中使用这些对象,将几何图形信息从一个游戏中复制粘贴到另一个游戏中。

    加载模型的方法有很多。现实中有许多不同类型的模型文件格式。文件格式只是在特定文件中保存信息的方式。某些文件格式保存顶点、法线和纹理坐标。某些文件格式除了保存前面所有的内容,还包括三角形索引和材质信息。使用或创建的文件格式将取决于对游戏的要求。本章将介绍Direct3D中三种不同的模型文件格式。这三种模型分别是Direct(X)模型、OBJ模型和最终模型格式(Ultimate Model Format,UMF)。最终所要做的全部工作就是在从文件加载模型时了解信息在文件中的存储方式。如果文件格式先是保存了所有的顶点,然后是所有的纹理坐标,那么同样要以这样的顺序加载它。换句话说,为了加载顶点和纹理坐标,就必须了解它们在文件中的位置。有许多可用于开发模型的软件。这些软件包括3D Studio Max、LightWave、Maya、TrueSpace和MilkShape3D。

    模型文件
           下面将要介绍的第一种模型文件格式是X模型文件。该文件是以X为扩展名的DirectX模型,它们既可以是文本文件,也可以是二进制文件。本书介绍的第二种模型是OBJ模型。通常可以从诸如3D Studio Max这样的建模软件导出这些常用的文本文件。这意味着可以在操作系统附带的记事本程序中打开OBJ模型,并查看和编辑模型数据。本书接下来介绍的最后一类模型是在www.UltimateGameProgramming.com上开发的名为UMF的通用模型文件格式。该类文件中的每一项都和其他的完全不同。这样可以在加载不同的可用模型格式时积累经验。

        对Direct3D中的X文件格式而言,这里将使用内置的Direct3D函数加载、渲染和释放X模型。目前,也许读者已猜到,Direct3D提供了大量有用的函数,可以方便地处理从模型渲染到光照到文本以及更多的图形学方面的不同内容。

    不同的建模软件可以加载、导入或导出不同类型的模型文件。一些软件也许支持某些其他软件不支持的文件类型。在寻找适合自己使用的建模软件时一定要牢记这一点。MilkShape3D提供了大量的模型文件格式,包含了大多数主要的游戏模型,如雷神之锤3(Quake3)、彩虹6号(Rainbow6)、魔兽争霸3(Warcraft3)、Unreal、英雄本色(Max Rayne)、半条名(Half Life)、英雄萨姆(Serious Sam)和毁灭战士3(Doom3)。

    GMAX是一款可以下载的免费编辑软件,它是由3D Studio Max开发的。GMAX可用于开发模型或级别,并提供一套指导学习该软件的指南。但要注意的是,GMAX除非是开发了游戏包,否则并不能导出模型。为了开发软件包,就要给Discreet公司支付费用。所以虽然它可以免费下载,但最终还是要花银子的。

    使用X文件
           
    DirectX模型或X模型是在Direct3D中使用的模型,可以通过几个内置函数直接加载和渲染到API。X模型最大的优点就是它们十分易用,可以直接和Direct3D交互使用。动画模拟X模型要做大量的工作,但对静态网格X而言,这是个很好的选择。同样,有许多导出程序可以将许多不同的文件格式导出为X格式,反之亦然。X模型格式很受欢迎,并且在不久的将来会得到广泛的使用。

    X文件格式介绍
           X文件格式基于模板格式。基于模板就可以根据定义在文件中的模板来定义X文件中的内容。这意味着可以对所有想用的内容使用X文件,还意味着并不止是可以存储几何图形数据。由于为某些对象使用X文件而未存储几何图形数据已经超出了本书的讨论范围,因此对此介绍很少,读者甚至没有必要了解这方面的内容。

           Direct3D有一些内置模板,可用于定义X文件中的网格。这些标准模板包括外观、纹理坐标和法线。因为X文件格式基于模板格式,所以Direct3D需要知道一些存储在文件中用于某种目的的信息。

           X模型既可以被保存为文本文件,也可以被保存为二进制文件。这意味着可以在任意的文本编辑器中打开X文本文件,并修改其中的内容。同样可以创建一个新文件,并手动填充其中的信息。对复杂模型而言,诸如前面提到的有5000个多边形的模型,就不想手动这样做,可以使用如3D Studio Max这样的建模软件来保存该模型。下面介绍一下由两个三角形组成的方形X模型。将为该模板施加纹理,并为网格添加简单的材质。方形X模型示例如程序清单12.1所示。

    保存纹理方形网格的X文件示例

    xof 0302txt 0032

    Material UgpMat {   // Material 0

       1.0;1.0;1.0;1.0;;       // Face color

       0.0;                    // Power

       0.0;0.0;0.0;;           // Specular color

       0.0;0.0;0.0;;           // Emissive color

       TextureFilename{"ugp.bmp";}   // Texture file name.

    }

    // The square mesh data.

    Mesh Square {

    4;                      // Number of vertices.

    1.0; 1.0; 0.0;,         // Vertice 1

    -1.0; 1.0; 0.0;,        // Vertice 2

    -1.0;-1.0; 0.0;,        // Vertice 3

    1.0;-1.0; 0.0;          // Vertice 4

    2;                      // Number of triangles

    3;0,1,2;,               // Triangle indices 1

    3;0,2,3;,               // Triangle indices 2

    MeshMaterialList {

    1;                      // Number of materials

    2;                      // Number of faces

    0,                      // Face 0 use material 0

    0,                      // Face 1 use material 0

    {UgpMat} // Reference the material.

    }

    MeshTextureCoords {

    4;                // Number of vertices

    0.0; 0.0;,        // Vertex 1 tex coord.

    0.0; 1.0;,        // Vertex 2 tex coord.

    1.0; 1.0;,        // Vertex 3 tex coord.

    1.0; 0.0;;        // Vertex 4 tex coord.

    }

    } // End of Mesh Square

        

    X模型看上去非常像一组C结构。如果分解整个模型文件,那么在最后可以看到,这里并没有包含大量复杂的信息。该文件由多个模板组成,这些模板包括针对网格的模板、针对材质的模板、针对纹理坐标的模板。这些模板都是Direct3D可以理解的标准模板,所以要做的全部工作就是在模型文件中定义这些模板,并设置这些模板。同样还可以在文件中为X文件格式添加注释。这些注释就像C/C++中的一样,其目的只是为了增加模板的可读性。如程序清单12.1所示,很容易描述文件每一部分的工作。

           每个X文件的开头先是一个标识符。这个标识符告诉程序正在读取X文件,而且是一个有效的X文件,这里还包含了文件版本。

    X文件头示例

    xof 0302txt 0032

    文件头开始先是xof。xof意味着程序正在加载某个版本的X文件。后面是文件的主版本号和次版本号:本例中是3.2。版本后面是txt,这意味着程序正在加载的是一个X模型的文本文件,而不是一个二进制文件。文件头最后一部分表示的是程序加载的文件使用的浮点数位数,在本示例文件是32(0032)。

           这里不需要创建任何模板,因为本书正使用的是Direct3D中的标准模板。程序清单12.1中的X示例文件中已经指定了标准模板Material、Mesh、MeshMaterialList和MeshTextureCoords。这里包含大量模板,可以在DirectX SDK文档中的X File Format Reference(X 文件格式参考)一节可以找到。唯一要用到的其他模板是MeshNormals和MeshVertexColors。

    Material 模板
           X文件中的Material标准模板用于指定可以施加在单个表面(多边形)的材质。针对材质的标准模板定义了环境颜色、发射能量、镜面颜色、反射量、与材质相关的纹理图像文件名。读者可以创建许多不同类型的材质,可以在模板中让不同的外观引用它们。这样可以使用包含完全不同材质和/或纹理的部分模型,而不是相同模型网格的所有其他部分。

    从X示例文件中提取的Material模板

    Material UgpMat {

      // Material 0

       1.0;1.0;1.0;1.0;;       // Diffuse color

       0.0;                    // SpecularPower

       0.0;0.0;0.0;;           // Specular color

       0.0;0.0;0.0;;           // Emissive color

       TextureFilename{"ugp.bmp";}   // Texture file name.

    }

     Mesh 模板

           Mesh模板定义X文件中的完整网格。X模型可以包含多个网格。例如,可以在单个模型文件中使用不同的mesh模板将网格分成头网格、上身网格、下身网格。X文件中的这些网格由多个小模板组成,它们嵌入在一个大模板中。网格模板结构开始先定义顶点总数,然后是顶点的x、y、z坐标。定义完顶点之后,接下来必须定义网格的外观或多边形。开始先是网格中的外观数,后面是每个外观的三角形索引。每个三角形索引行开始是代表外观使用的三角形数目,后面才是索引号。定义完顶点和三角形之后,就可以随意定义其他属性了,如每一外观的材质、纹理坐标等。程序清单12.4给出了X示例文件完整的正方形网格模板。

    // The square mesh data.

    Mesh Square {

    4;                      // Number of vertices.

    1.0; 1.0; 0.0;,         // Vertice 1

    -1.0; 1.0; 0.0;,        // Vertice 2

    -1.0;-1.0; 0.0;,        // Vertice 3

    1.0;-1.0; 0.0;          // Vertice 4

    2;                      // Number of triangles

    3;0,1,2;,               // Triangle indices 1

    3;0,2,3;,               // Triangle indices 2

    MeshMaterialList {

    1;                      // Number of materials

    2;                      // Number of faces

    0,                      // Face 0 use material 0

    0,                      // Face 1 use material 0

    {UgpMat} // Reference the material.

    }

    MeshTextureCoords {

    4;                // Number of vertices

    0.0; 0.0;,        // Vertex 1 tex coord.

    0.0; 1.0;,        // Vertex 2 tex coord.

    1.0; 1.0;,        // Vertex 3 tex coord.

    1.0; 0.0;;        // Vertex 4 tex coord.

    }

    } // End of Mesh Square

        

    MeshMaterialList 模板
           MeshMaterialList模板指明网格中的哪个外观使用哪种材质。在X模型示例文件中只定义了一种材质,所以将该材质施加给所有的外观。材质链表结构开始先定义材质数目,然后是将材质施加到外观总数。这之后,每个外观占一行。每行定义一个值,用该值引用要用的材质。该结构的最后一行是涉及到的所有材质链表。第一个材质索引号为0,第二个为1,依此类推。所以对于X示例文件中的每个外观都指定材质索引号为0,因为这里只有一个材质可用。程序清单12.5给出了X示例文件中的MeshMaterialList模板。

    MeshMaterialList {

    1;                      // Number of materials

    2;                      // Number of faces

    0,                      // Face 0 use material 0

    0,                      // Face 1 use material 0

    {UgpMat} // Reference the material.

    }

       

    MeshTextureCoords 模板
           要介绍的最后一个标准模板是MeshTextureCoords模板。有了该模板就可以在Direct3D中将纹理映射到网格上。该模板很容易理解,非常明了。模板开始先指定网格中的索引数。然后用逗号隔开,简单地列出每个顶点的纹理坐标。程序清单12.6给出了X示例文件中的MeshTextureCoords模板。

    MeshTextureCoords {

    4;                // Number of vertices

    0.0; 0.0;,        // Vertex 1 tex coord.

    0.0; 1.0;,        // Vertex 2 tex coord.

    1.0; 1.0;,        // Vertex 3 tex coord.

    1.0; 0.0;;        // Vertex 4 tex coord.

    }

    加载和渲染X模板
           Direct3D不涉及动画时,加载和渲染X模型很简单。所有的X模型都保存在相同的名为LPD3DXMESH的结构中。第3章中的Direct3D内置对象使用了该结构。为了从文件加载X模型,调用D3DXLoadMeshFromX()函数即可。该函数可将X模型加载到LPD3DXMEH对象中,并在Direct3D中使用该模型。D3DXLoadMeshFromX()函数原型如程序清单12.7所示。
    HRESULT D3DXLoadMeshFromX(
    LPCTSTR pFilename,
    // 要加载的X文件的文件名
    DWORD Options, // 创建网格时所使用的创建标记
    LPDIRECT3DDEVICE9 pD3DDevice, // 与该网格对象相关的设备指针
    LPD3DXBUFFER * ppAdjacency, // 返回一个ID3DXBuffer对象,该对象包含了一个
    // 描述该网格对象的邻接信息的DWORD类型的数组
    LPD3DXBUFFER * ppMaterials, // 返回一个ID3DXBuffer对象,该对象包含了一个
    // 存储该网格的材质数据的D3DXMATERIAL类型的结构数组
    LPD3DXBUFFER * ppEffectInstances, // 返回一个ID3DXBuffer对象,该对象包含了一个
    // D3DXEFFECTINSTANCE结构
    DWORD * pNumMaterials, // 返回网格中的材质数目(即有ppMaterials参数输出的D3DXMATERIAL数
    // 组中元素的个数)
    LPD3DXMESH * ppMesh // 返回所创建的并已填充了X文件几何数据的ID3DXMesh对象.
    );

      D3DXLoadMeshFromX()函数的参数包括X文件的文件名、加载网格的选项标识符、Direct3D设备对象、存储邻近数据(三角形索引)的LPD3DXBUFFER、存储定义在文件中的材质的缓存、存储在文件中使用的效果(阴影器)实例的缓存、指向材质总数的指针以及该函数调用创建的网格对象的地址。本书中,该函数只涉及前三个参数和最后一个参数。其他参数可选。本书并不使用涉及效果实例的参数,因为效果文件,即DirectX高级编程阴影器,涉及到高级图形学的内容,超出了本书的讨论范围。如果该函数返回D3D_OK,则表示加载成功。否则,加载过程出现错误,模型没有被加载到内存中。

           使用内置的Direct3D对象渲染X模型,同样的方法在第3章已经用过。调用LPD3DXMESH对象的DrawSubset()函数可以绘制模型。DrawSubset()的函数原型如程序清单12.8所示。该函数只有一个参数,即要绘制的网格索引。记住:在X模型文件中可以指定多个网格。在渲染X模型时,如果想在屏幕上渲染整个模型,就要渲染所有的网格。虽然第3章只涉及到一个网格模型,但对X模型而言,可以有多个网格。

    HRESULT DrawSubset(

      UINT AttribId     // 要绘制的网格索引

    );

        

    就像在第3章所做的工作一样,必须牢记一点:一定要调用对象的Release()函数释放所有用到的内存。

    Model Loading 演示程序


       首先,从main源文件的全局部分入手。这一部分包含了常用的对象,还添加了Direct3D光照对象、网格、材质总数、LPD3DXBUFFER缓存、材质链表以及模型使用的纹理链表等内容。光照对象和网格对象很容易理解,而且在前面的章节已经做过介绍。用于保存材质总数的全局变量可以得到定义在模型文件中的材质数量。LPD3DXBUFFER对象从X模型文件中获取材质数据,这样就可以在代码中施加材质。材质链表指明了从该模型文件可以创建的真正材质,而纹理链表指明了施加给每个网格的纹理对象。X Model Loading 演示程序全局部分完整代码如程序清单12.9所示。

           程序清单12.9 X Model Loading 演示程序完整的全局部分

    #include<d3d9.h>

    #include<d3dx9.h>

    #pragma comment(lib, "d3d9.lib")

    #pragma comment(lib, "d3dx9.lib")

    #define WINDOW_CLASS    "UGPDX"

    #define WINDOW_NAME     "X Model Loading"

    #define WINDOW_WIDTH    640

    #define WINDOW_HEIGHT   480

    #define FULLSCREEN      0

    // Function Prototypes...

    bool InitializeD3D();

    bool InitializeObjects();

    void RenderScene();

    void Shutdown();

    // Global window handle.

    HWND g_hwnd = 0;

    // Direct3D object and device.

    LPDIRECT3D9 g_D3D = NULL;

    LPDIRECT3DDEVICE9 g_D3DDevice = NULL;

    // Matrices.

    D3DXMATRIX g_projection;

    D3DXMATRIX g_worldMatrix;

    D3DXMATRIX g_ViewMatrix;

    // Display object.

    LPD3DXMESH g_model = NULL;

    DWORD g_numMaterials;

    LPD3DXBUFFER g_matBuffer = NULL;

    D3DMATERIAL9* g_matList = NULL;

    LPDIRECT3DTEXTURE9* g_textureList = NULL;

    // Scene light source.

    D3DLIGHT9 g_light;

        

    该演示程序中出现改动的三个函数中的第一个是InitializeObjects()。该函数的工作方式除了加载X模型的代码之外,和前面章节演示程序中的类似。为了在演示程序中加载X模型,首先调用D3DXLoadMeshFromX()函数。然后使用CloneMesh()函数将D3DVERTEXELEMENT9对象复制到网格中。这样做是因为想通过该对象添加比其在文件中多许多的信息而更改网格。在文件中并未指明法线,但使用D3DXComputeNormals()函数就可以计算网格的法线,而不必在文件中指明那些法线。当然,这对大量多边形而言就很慢。但对只有绝对需要使用法线的情况而言,这样做可以节省磁盘空间。由于原始模型不包含法线,因此必须调用包含法线信息的CloneMesh()函数更新对象。

    #include<d3d9.h>
    #include<d3dx9.h>

    #pragma comment(lib, "d3d9.lib")
    #pragma comment(lib, "d3dx9.lib")


    #define WINDOW_CLASS "UGPDX"
    #define WINDOW_NAME "X Model Loading"
    #define WINDOW_WIDTH 640
    #define WINDOW_HEIGHT 480
    #define FULLSCREEN 0

    // Function Prototypes...
    bool InitializeD3D();
    bool InitializeObjects();
    void RenderScene();
    void Shutdown();


    // Global window handle.
    HWND g_hwnd = 0;


    // Direct3D object and device.
    LPDIRECT3D9 g_D3D = NULL;
    LPDIRECT3DDEVICE9 g_D3DDevice = NULL;


    // Matrices.
    D3DXMATRIX g_projection;
    D3DXMATRIX g_worldMatrix;
    D3DXMATRIX g_ViewMatrix;


    // Display object.
    // 网格对象
    LPD3DXMESH g_model = NULL;
    // 材质总数
    DWORD g_numMaterials;
    // LPD3DXBUFFER缓存,用于从X模型文件中获取材质数据,这样就可以在代码中施加材质.
    LPD3DXBUFFER g_matBuffer = NULL;
    // 材质链表,指明了从该模型文件可以创建的真正材质.
    D3DMATERIAL9* g_matList = NULL;
    // 纹理链表,指明了施加给每个网格的纹理对象.
    LPDIRECT3DTEXTURE9* g_textureList = NULL;

    // Scene light source.
    D3DLIGHT9 g_light;


    LRESULT WINAPI MsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
    {
    switch(msg)
    {
    case WM_DESTROY:
    case WM_CLOSE:
    PostQuitMessage(0);
    return 0;
    break;

    case WM_KEYUP:
    if(wParam == VK_ESCAPE) PostQuitMessage(0);
    break;
    }

    return DefWindowProc(hWnd, msg, wParam, lParam);
    }


    int WINAPI WinMain(HINSTANCE hInst, HINSTANCE prevhInst, LPSTR cmdLine, int show)
    {
    // Register the window class
    WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L,
    GetModuleHandle(NULL), NULL, NULL, NULL, NULL,
    WINDOW_CLASS, NULL };
    RegisterClassEx(&wc);

    // Create the application's window
    HWND hWnd = CreateWindow(WINDOW_CLASS, WINDOW_NAME, WS_OVERLAPPEDWINDOW,
    100, 100, WINDOW_WIDTH, WINDOW_HEIGHT,
    GetDesktopWindow(), NULL, wc.hInstance, NULL);

    // Show the window
    ShowWindow(hWnd, SW_SHOWDEFAULT);
    UpdateWindow(hWnd);

    // Record for global.
    g_hwnd = hWnd;

    // Initialize Direct3D
    if(InitializeD3D())
    {
    // Enter the message loop
    MSG msg;
    ZeroMemory(&msg, sizeof(msg));

    while(msg.message != WM_QUIT)
    {
    if(PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE))
    {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
    }
    else
    RenderScene();
    }
    }

    // Release any and all resources.
    Shutdown();

    // Unregister our window.
    UnregisterClass(WINDOW_CLASS, wc.hInstance);
    return 0;
    }


    bool InitializeD3D()
    {
    D3DDISPLAYMODE displayMode;

    // Create the D3D object.
    g_D3D = Direct3DCreate9(D3D_SDK_VERSION);
    if(g_D3D == NULL) return false;

    // Get the desktop display mode.
    if(FAILED(g_D3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &displayMode)))
    return false;

    // Set up the structure used to create the D3DDevice
    D3DPRESENT_PARAMETERS d3dpp;
    ZeroMemory(&d3dpp, sizeof(d3dpp));

    if(FULLSCREEN)
    {
    d3dpp.Windowed = FALSE;
    d3dpp.BackBufferWidth = WINDOW_WIDTH;
    d3dpp.BackBufferHeight = WINDOW_HEIGHT;
    }
    else
    d3dpp.Windowed = TRUE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.BackBufferFormat = displayMode.Format;
    d3dpp.BackBufferCount = 1;
    d3dpp.EnableAutoDepthStencil = TRUE;
    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;


    // Create the D3DDevice
    if(FAILED(g_D3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, g_hwnd,
    D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_PUREDEVICE,
    &d3dpp, &g_D3DDevice))) return false;

    // Initialize any objects we will be displaying.
    if(!InitializeObjects()) return false;

    return true;
    }


    bool InitializeObjects()
    {
    // Set default rendering states.
    g_D3DDevice->SetRenderState(D3DRS_LIGHTING, TRUE);
    g_D3DDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
    g_D3DDevice->SetRenderState(D3DRS_ZENABLE, TRUE);


    // Setup the g_light source and material.
    g_light.Type = D3DLIGHT_DIRECTIONAL;
    g_light.Direction = D3DXVECTOR3(0.0f, 0.0f, 1.0f);

    D3DCOLORVALUE white;
    white.a = white.r = white.g = white.b = 1;

    g_light.Diffuse = white;
    g_light.Specular = white;

    g_D3DDevice->SetLight(0, &g_light);
    g_D3DDevice->LightEnable(0, TRUE);

    /*
    使用CloneMesh()函数将D3DVERTEXELEMENT9对象复制到网格中。
    这样做是因为想通过该对象添加比其在文件中多许多的信息而更改网格。
    在文件中并未指明法线,但使用D3DXComputeNormals()函数就可以计算网格
    的法线,而不必在文件中指明那些法线。当然,这对大量多边形而言就很慢。
    但对只有绝对需要使用法线的情况而言,这样做可以节省磁盘空间。由于原
    始模型不包含法线,因此必须调用包含法线信息的CloneMesh()函数更新对象。

    加载完模型后,就可以得到材质信息并加载纹理。通过将LPD3DXBUFFER对象的
    材质复制到材质链表中即可完成该工作。因为LPD3DXBUFFER对象还包含了纹理名称,
    所以也可以加载纹理,这些纹理就在缓存中每种材质的后面。有了这些内容,
    InitializeObjects()函数的其他部分跟往常一样继续下去。程序清单12.10给出了完整
    的InitializeObjects()函数。

    在创建顶点元素(D3DVERTEXELEMENT9)时,偏移的字节就是顶点结构中标识数据开始位置
    的字节数。所以如果第一个元素是顶点位置,那么该偏移量就是0,因为该元素前面没有
    任何元素。如果第二个元素是法线,那么偏移量为12,因为第一个元素占用了12个字节。
    如果第三个元素是一组纹理坐标,那么偏移量为24,这是因为每个浮点数占4个字节。
    由于在处理位置时有三个浮点值,因此总共需要12个字节。例如,如果在纹理坐标后面是颜色,
    那么偏移量为32,因为第一个元素占12个字节,法线占12个字节,纹理坐标占8个字节。

    D3DVERTEXELEMENT9
    Defines the vertex data layout. Each vertex can contain one or more data types, and each data
    type is described by a vertex element.

    typedef struct D3DVERTEXELEMENT9
    {
    WORD Stream;
    WORD Offset;
    BYTE Type;
    BYTE Method;
    BYTE Usage;
    BYTE UsageIndex;
    } D3DVERTEXELEMENT9, *LPD3DVERTEXELEMENT9;

    Members
    Stream
    Stream number.
    Offset
    Offset from the beginning of the vertex data to the data associated with the particular data type.
    Type
    The data type, specified as a D3DDECLTYPE. One of several predefined types that define the data size.
    Some methods have an implied type.
    Method
    The method specifies the tessellator processing, which determines how the tessellator interprets
    (or operates on) the vertex data. For more information, see D3DDECLMETHOD.
    Usage
    Defines what the data will be used for; that is, the interoperability between vertex data layouts
    and vertex shaders. Each usage acts to bind a vertex declaration to a vertex shader. In some cases,
    they have a special interpretation. For example, an element that specifies D3DDECLUSAGE_NORMAL or
    D3DDECLUSAGE_POSITION is used by the N-patch tessellator to set up tessellation. See D3DDECLUSAGE
    for a list of the available semantics. D3DDECLUSAGE_TEXCOORD can be used for user-defined fields
    (which don't have an existing usage defined).
    UsageIndex
    Modifies the usage data to allow the user to specify multiple usage types.

    */
    // 新mesh的顶点声明
    D3DVERTEXELEMENT9 elements[] =
    {
    { 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 }, // 位置
    { 0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0 }, // 法线
    { 0, 24, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 }, // 纹理坐标
    D3DDECL_END()
    };

    // Load mesh into temp object then copy over (to set elements).
    LPD3DXMESH temp = NULL;
    if(FAILED(D3DXLoadMeshFromX("Model.x", D3DXMESH_SYSTEMMEM,
    g_D3DDevice, NULL, &g_matBuffer, NULL,
    &g_numMaterials, &temp))) return false;
    //elements:顶点声明
    //g_model:Address of a pointer to an ID3DXMesh interface, representing the cloned mesh.
    temp->CloneMesh(D3DXMESH_SYSTEMMEM, elements, g_D3DDevice, &g_model);
    if(temp) temp->Release();

    // Calculate normals for lighting.
    D3DXComputeNormals(g_model, NULL);

    // Allocate the lists for materials and textures.
    g_matList = new D3DMATERIAL9[g_numMaterials];
    g_textureList = new LPDIRECT3DTEXTURE9[g_numMaterials];

    // Get a pointer to the buffer
    D3DXMATERIAL* mat = (D3DXMATERIAL*)g_matBuffer->GetBufferPointer();

    // Loop and load each textture and get each material.
    for(DWORD i = 0; i < g_numMaterials; i++)
    {
    // Copy the materials from the buffer into our list.
    g_matList[i] = mat[i].MatD3D;

    // Load the textures into the list.
    if(FAILED(D3DXCreateTextureFromFile(g_D3DDevice,
    mat[i].pTextureFilename,
    &g_textureList[i])))
    g_textureList[i] = NULL;
    }


    // Set the projection matrix.
    D3DXMatrixPerspectiveFovLH(&g_projection, D3DX_PI / 4,
    WINDOW_WIDTH/WINDOW_HEIGHT, 0.1f, 1000.0f);

    g_D3DDevice->SetTransform(D3DTS_PROJECTION, &g_projection);


    // Define camera information.
    D3DXVECTOR3 cameraPos(0.0f, 0.0f, -10.0f);
    D3DXVECTOR3 lookAtPos(0.0f, 0.0f, 0.0f);
    D3DXVECTOR3 upDir(0.0f, 1.0f, 0.0f);

    // Build view matrix.
    D3DXMatrixLookAtLH(&g_ViewMatrix, &cameraPos,
    &lookAtPos, &upDir);

    return true;
    }


    void RenderScene()
    {
    // Clear the backbuffer.
    g_D3DDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
    D3DCOLOR_XRGB(0,0,0), 1.0f, 0);

    // Begin the scene. Start rendering.
    g_D3DDevice->BeginScene();

    // Apply the view (camera).
    g_D3DDevice->SetTransform(D3DTS_VIEW, &g_ViewMatrix);

    // g_numMaterials个网格 每个网格对应一个材质,、
    // 对每个mesh应用材质和纹理,然后渲染
    // Draw the model.
    for(DWORD i = 0; i < g_numMaterials; i++)
    {
    g_D3DDevice->SetMaterial(&g_matList[i]);
    g_D3DDevice->SetTexture(0, g_textureList[i]);

    g_model->DrawSubset(i);
    }

    // End the scene. Stop rendering.
    g_D3DDevice->EndScene();

    // Display the scene.
    g_D3DDevice->Present(NULL, NULL, NULL, NULL);
    }


    void Shutdown()
    {
    if(g_D3DDevice != NULL) g_D3DDevice->Release();
    g_D3DDevice = NULL;

    if(g_D3D != NULL) g_D3D->Release();
    g_D3D = NULL;

    if(g_model != NULL) g_model->Release();
    g_model = NULL;

    for(DWORD i = 0; i < g_numMaterials; i++)
    {
    if(g_textureList[i] != NULL)
    {
    g_textureList[i]->Release();
    g_textureList[i] = NULL;
    }
    }

    if(g_matList != NULL)
    {
    delete[] g_matList;
    g_matList = NULL;
    }

    if(g_textureList != NULL)
    {
    delete[] g_textureList;
    g_textureList = NULL;
    }

    if(g_matBuffer != NULL)
    {
    g_matBuffer->Release();
    g_matBuffer = NULL;
    }
    }

    /*
    一个X文件不包含顶点法线数据,这是很有可能的。假如是这种情况,
    那么手动计算顶点法线以便我们能够使用灯光这是很有必要的。现在
    知道了ID3DXMesh接口和它的父接口ID3DXBaseMesh,我们能够使用下面
    的函数来产生任何mesh的顶点法线:

    Computes unit normals for each vertex in a mesh. Provided to support legacy applications.
    Use D3DXComputeTangentFrameEx for better results.

    HRESULT D3DXComputeNormals(
    LPD3DXBASEMESH pMesh,
    CONST DWORD * pAdjacency
    );
    */



  • 相关阅读:
    动词 + to do、动词 + doing
    图像直线检测——霍夫线变换
    x=min(x, y)
    x=min(x, y)
    算法 Tricks(三)—— 数组(序列)任意区间最小(大)值
    算法 Tricks(三)—— 数组(序列)任意区间最小(大)值
    分治法求解切割篱笆
    分治法求解切割篱笆
    GMM的EM算法实现
    秒杀多线程第二篇 多线程第一次亲热接触 CreateThread与_beginthreadex本质差别
  • 原文地址:https://www.cnblogs.com/kex1n/p/2179520.html
Copyright © 2011-2022 走看看