Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
88 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

  Contents

 Preface
 3D File Formats
 The Framework
 X File Class

 Down to the Code

 Source code
 Printable version

 


  The Series

 The Basics
 First Steps to
 Animation

 Multitexturing
 Building Worlds
 With X Files


 

3D File Formats

3D scene file formats present two fundamental problems: how to store the objects that will make up the scene and how to store the relationship between these objects.

There have to be user-defined data types. Scene data requires hierarchical relationships such as parent, child and sibling relationships and associational relationships such as meshes being attached to frames, materials being attached to meshes and so on. In addition, a file format design should be robust enough to handle forward and backward compatibility.

The X file format handles all this tasks, so it's a good solution to show the loading of 3D models into a game engine in a tutorial.

X file format

The Direct3D X file format was built for the legacy retained mode of Direct3D and expanded with the advent of DirectX 6 for immediate mode. Let's see on an abstract level how the X file format solves the problems described above:

  • User-defined data types. X files are driven by templates, which can be defined by the user. A template is a definition on how a data object should be stored. The predefined templates are contained in the "rmxftmpl.h" and "rmxftmpl.x" headers (try to pronounce it :-) ) and their indentification signatures in "rmxfguid.h" which are included in d3dfile.cpp, one of the Framework files. See "The Basics" tutorial for more information on the Direct3D 7 IM Framework.
  • Hierarchical relationships. Data types allowed by the template are called optional members. These optional members are saved as children of the data object. The children can be another data object, a reference to an earlier data object, or a binary object.

Ok let's dive into the X file format with a sample: a square. It's located in square.x. Use the DirectX 7 SDK sample app Xfile.exe to play around with the files:

xof 0302txt 0064 Header { 1; 0; 1; } // square Mesh Square { // front face and back face 8; // number of vertices 1.0; 1.0; 0.0;, // vertice 0 -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 1.0;-1.0; 0.0;, // vertice 5 -1.0;-1.0; 0.0;, // vertice 6 -1.0; 1.0; 0.0;; // vertice 7 4; // number of triangles 3;0,1,2;, // triangle #1 3;0,2,3;, // triangle #2 3;4,5,6;, // triangle #3 3;4,6,7;; // triangle #4 MeshMaterialList { 1; // one material 2; // two faces 0, // face #0 use material #0 0;; // face #1 use material #0 Material { // material #0 0.0;1.0;0.0;1.0;; // face color 0.0; // power 0.0;0.0;0.0;; // specular color 0.0;0.0;0.0;; // emissive color } } }

Header

xof is the magic 4-byte number. The major version number is 03 and the minor version number is 02. The file format is txt for text or bin for a binary format.

You can convert between the txt and bin format with the help of convx.exe, which is located in d:\mssdk\DXUtils\Xfiles.

Other possible file formats are tzip and bzip, which stands for MSZip compressed text or bin files. The floating point data type is set to the default 0064 instead of 0032. You will find the first template named Header in the third row of square.x. Its declaration is located in the rmxftmpl.x file, like all other templates.

template Header { <3D82AB43-62DA-11cf-AB39-0020AF71E433> WORD major; WORD minor; DWORD flags; }

This template defines the application-specific header for the Direct3D Retained Mode usage of the DirectX file format. Retained Mode uses the major and minor flags to specify the current major and minor versions. We're not using RM here, so we can skip that by commenting it out or deleting it. You might use the Header template for your graphics version control system. Comments are set, as you might see above, with the double slash like in C++ or the hash symbol (#).

Templates consist of four parts. The unique name, which consists of numbers, characters and the underscore. It shouldn't start with a number. The second part is the UUID (the Universally Unique Identifier) and the third part consists of the data types of the entries. The last part regulates the degree of restriction. A template could be open, closed or restricted. Open templates can contain every other data type, closed templates no other data types and restricted templates can only integrate specific other data types.

Mesh

Most of this sample file consists of a mesh template and its integrated templates:

template Mesh { <3D82AB44-62DA-11cf-AB39-0020AF71E433> DWORD nVertices; array Vector vertices[nVertices]; DWORD nFaces; array MeshFace faces[nFaces]; [...] } template Vector { <3D82AB5E-62DA-11cf-AB39-0020AF71E433> FLOAT x; FLOAT y; FLOAT z; } template MeshFace { <3D82AB5F-62DA-11cf-AB39-0020AF71E433> DWORD nFaceVertexIndices; array DWORD faceVertexIndices[nFaceVertexIndices]; }

The first number is the number of vertices used. After that, the vertices are set.

The front face uses four vertices consisting of two triangles. There's a front and a back face.

In Direct3D, only the front side of a face is visible. A front face is one in which vertices are defined in clockwise order. Any face that is not a front face is a back face. Direct3D does not always render back faces; therefore, back faces are said to be culled = Backface culling.

You can use triangles or quad info in MeshFace:

// Instead of .. 4; // number of triangles 3;0,1,2;, // triangle #1 3;0,2,3;, // triangle #2 3;4,5,6;, // triangle #3 3;4,6,7;; // triangle #4 // we could use this 2; // number of quads 4;0,1,2,3;, // quad #1 4;4,5,6,7;; // quad #2

The Mesh template is an open template, because it uses open brackets [...] at the end.

MeshMaterialList

The MeshMaterialList is a child object of the Mesh Object and is incorporated in the Mesh object. The DirectX 7 SDK sample Xfile.exe wouldn't load the file square.x without it. With the MeshMaterialList, you can reference on more than one material. It needs the number of faces and materials and concatenates one face with a material. The template in rmxftmpl.x looks like:

MeshMaterialList { 1; // one material 2; // two faces 0, // face #0 use material #0 0;; // face #1 use material #0 Material { // material #0 0.0;1.0;0.0;1.0;; // face color 0.0; // power 0.0;0.0;0.0;; // specular color 0.0;0.0;0.0;; // emissive color } } ... template MeshMaterialList { <f6f23f42-7686-11cf-8f52-0040333594a3> DWORD nMaterials; DWORD nFaceIndexes; array DWORD faceIndexes[nFaceIndexes]; [Material] }

The first variable holds the number of materials used by this sample. The second variable holds the number of faces. The square uses the front and back face, so the variable has to be set to 2. The concatenation of the materials with the faces happens with the faceIndexes array. Every face is concatenated with a material by naming the material number. The X file reader knows the proper face by counting the number of faces provided.

Beneath that array, Material templates could be integrated. So the MeshMaterialList template is a restricted template, because only specified templates could be integrated.

template Material { <3D82AB4D-62DA-11cf-AB39-0020AF71E433> ColorRGBA faceColor; FLOAT power; ColorRGB specularColor; ColorRGB emissiveColor; [...] } template ColorRGBA { <35FF44E0-6C7C-11cf-8F52-0040333594A3> FLOAT red; FLOAT green; FLOAT blue; FLOAT alpha; } template ColorRGB { FLOAT red; FLOAT green; FLOAT blue; }

The Material data object holds the material color, power, specular color and emissive color. The open brackets show that it can integrate other data objects. The Material data object could be referenced by MeshMaterialList. The file square2.x shows a referenced Material Data Object and the use of quads:

xof 0302txt 0064 Material GreenMat { // material #0 0.0;1.0;0.0;1.0;; 0.0; 0.0;0.0;0.0;; 0.0;0.0;0.0;; } // square Mesh Square { // front face and back face 8; // number of vertices 1.0; 1.0; 0.0;, // vertice 0 -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 1.0;-1.0; 0.0;, // vertice 5 -1.0;-1.0; 0.0;, // vertice 6 -1.0; 1.0; 0.0;; // vertice 7 2; // number of quads 4;0,1,2,3;, // quad #1 4;4,5,6,7;; // quad #2 MeshMaterialList { 1; // one material 2; // tow faces 0, // face #0 use material #0 0;; // face #1 use material #0 {GreenMat} } }

{GreenMat} is the reference for the Material Object.

Normals

Even though Direct3D (since version 7) performs lighting calculations on all vertices, even those without vertex normals, nevertheless you should integrate normals with the MeshNormals template to get the desired lighting, as you'll see in a few seconds:

MeshNormals { 2; // 2 normals for every face 0.0; 0.0; 1.0;, // normal #1 0.0; 0.0;-1.0;; // normal #2 2; // two faces 4;0,0,0;, // connect face #1 with normal #1 4;1,1,1;; // connect face #2 with normal #2 } template MeshNormals { <f6f23f43-7686-11cf-8f52-0040333594a3> DWORD nNormals; array Vector normals[nNormals]; DWORD nFaceNormals; array MeshFace faceNormals[nFaceNormals]; }

The vertex normal vector is used in Gouraud shading mode (default shading mode since Direct3D 6), to control lighting and made some texturing effects. So what's a shading mode? Shading is the process of performing lighting computations and determining pixel colors from them. These pixel colors will later be drawn on the object. Flat shading lights per polygon or face, Gouraud shading lights per vertex:


Teapot as a wireframe model, flat shaded model and as a Gouraud shaded model

As you might see, the viewer might see the faces of the teapot with flat shading, but will get the illusion of a round teapot by using the Gouraud model. How does that work? Let's build a more simple example.

This will appear as a flat triangle when it's shaded with Gouraud shading, because all of the vertex normals point the same way, so all the points in the face in between the vertices get the same lighting. It would look the same with flat shading, because flat shading shades per face. Now we'll change the normals to be non-perpendicular:

With flat shading, nothing would have been changed, because the face hasn't changed. With Gouraud shading, the face appears curved, because the direction of the normals varies from vertex to vertex in a way that suggests the face is curled down at the corners. This is how the teapot is rounded up. And this is also how the object is rounded up. You don't have to do anything in Direct3D to choose the Gouraud shading mode, because it's the default shading mode. So ... no code :-).

Textures

Textures could be referenced in a TextureFilename Data Object as child objects of the Material Object:

template TextureFilename { STRING filename; }

The reference to the texture filename has to be implemented in the Material Object like this:

Material GreenMat { // material #0 0.0;1.0;0.0;1.0;; 0.0; 0.0;0.0;0.0;; 0.0;0.0;0.0;; TextureFilename{"wall.bmp";} }

Additionally you need to specify texture coordinates:

template MeshTextureCoords { <f6f23f40-7686-11cf-8f52-0040333594a3> DWORD nTextureCoords; array Coords2d textureCoords[nTextureCoords]; } template Coords2d { <f6f23f44-7686-11cf-8f52-0040333594a3>FLOAT u; FLOAT v; }

The first variable holds the number of vertices which have to be used in conjunction with the texture coordinates. The following array holds the tu/tv pairs of the textures. To map a texture to that square, we could use the following texture coordinates:

If you'd like to map one texture on the whole square you have to use

MeshTextureCoords { 4; // 4 texture coords 1.0; 1.0;, // coord 0 1.0; 0.0;, // coord 1 0.0; 0.0;, // coord 2 0.0; 1.0;; // coord 3 }

The bottom right corner is (1.0f, 1.0f) and the upper left corner is (0.0f, 0.0f) regardless of the actual size of the texture. Even if the texture is wider than it is tall.

To get four textures, use the following coordinates

MeshTextureCoords { 4; // four textures 1.0; 1.0;, // coord 0 -1.0; 1.0;, // coord 1 -1.0;-1.0;, // coord 2 1.0;-1.0;; // coord 3 }

The complete file square3.x looks like this:

xof 0302txt 0064 Material GreenMat { // material #0 0.0;1.0;0.0;1.0;; 0.0; 0.0;0.0;0.0;; 0.0;0.0;0.0;; TextureFilename{"wall.bmp";} } // square Mesh Square { // front face and back face 8; // number of vertices 1.0; 1.0; 0.0;, // vertice 0 -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 1.0;-1.0; 0.0;, // vertice 5 -1.0;-1.0; 0.0;, // vertice 6 -1.0; 1.0; 0.0;; // vertice 7 2; // number of quads 4;0,1,2,3;, // quad #1 4;4,5,6,7;; // quad #2 MeshMaterialList { 1; // one material 2; // two faces 0, // face #0 use material #0 0;; // face #1 use material #0 {GreenMat} } MeshTextureCoords { 8; // 8 texture coords 1.0; 1.0;, // coord 0 1.0; 0.0;, // coord 1 0.0; 0.0;, // coord 2 0.0; 1.0;; // coord 3 1.0; 1.0;, // coords 4 1.0;-1.0;, // coords 5 -1.0;-1.0;, // coords 6 -1.0; 1.0;; // coords 7 } MeshNormals { 2; // 2 normals for every face 0.0; 0.0; 1.0;, // normal #1 0.0; 0.0;-1.0;; // normal #2 2; // two faces 4;0,0,0;, // connect face #1 with normal #1 4;1,1,1;; // connect face #2 with normal #2 } }

Xfile.exe blends the material and the texture, so that the texture looks green. This alpha blending effect is described in the Multitexturing tutorial. Textures with the same texture names won't be loaded twice. A flag will handle internally duplicated textures. Ok let's start an exercise: You produce a X file out of the specs of the object in the second tutorial First Steps to Animation. NO cheating ... finished? Ok ... here's my version:

xof 0302txt 0064 Header { 1; 0; 1; } // boid Mesh boid { 7; // number of vertices 0.00; 0.00; 0.50;, // vertice 0 0.50; 0.00;-0.50;, // vertice 1 0.15; 0.15;-0.35;, // vertice 2 -0.15; 0.15;-0.35;, // vertice 3 0.15;-0.15;-0.35;, -0.15;-0.15;-0.35;, -0.50; 0.00;-0.50;; 10; // number of triangles 3;0,1,2;, // triangle #1 3;0,2,3;, // triangle #2 3;0,3,6;, // triangle #3 3;0,4,1;, // ... 3;0,5,4;, 3;0,6,5;, 3;1,4,2;, 3;2,4,3;, 3;3,4,5;, 3;3,5,6;; MeshMaterialList { 1; // one material 10; // ten faces 0,0;; // face #1 use material #0 0,0;; // face #2 use material #0 0,0;; // face #3 use material #0 0,0;; // face #4 use material #0 0,0;; // face #5 use material #0 0,0;; // face #6 use material #0 0,0;; // face #7 use material #0 0,0;; // face #8 use material #0 0,0;; // face #9 use material #0 0,0;; // face #10 use material #0 Material { // material #0 1.0;0.92;0.0;1.0;; 1.0; 0.0;0.0;0.0;; 0.0;0.0;0.0;; } } MeshNormals { 9; // 9 normals 0.2; 1.0; 0.0;, // normal #1 0.1; 1.0; 0.0;, // normal #2 0.0; 1.0; 0.0;, // normal #3 -0.1; 1.0; 0.0;, // normal #4 -0.2; 1.0; 0.0;, // normal #5 -0.4; 0.0;-1.0;, // normal #6 -0.2; 0.0;-1.0;, // normal #7 0.2; 0.0;-1.0;, // normal #8 0.4; 0.0;-1.0;; // normal #9 9; // two faces 4;0,0,0;, // connect face #1 with normal #1 4;1,1,1;, // connect face #2 with normal #2 4;2,2,2;, // connect face #3 with normal #3 4;3,3,3;, // connect face #4 with normal #4 4;4,4,4;, // connect face #5 with normal #5 4;5,5,5;, // connect face #6 with normal #6 4;6,6,6;, // connect face #7 with normal #7 4;7,7,7;, // connect face #8 with normal #8 4;8,8,8;; // connect face #9 with normal #9 } }

Transformation Matrices

To transform, for example, parts of an object independently from each other, you have to divide the model into different frames. For example a tank could turn its cannon up and down and to the left or right side. Its chains and wheels will turn when it drives through the wild enemy terrain. So there is a frame for the hull, for the turret, the cannon and one for the chains/wheels. Every frame will hold the matrix for the specified part of the tank. A Frame data object is expected to take the following structure:

Frame Hull { FrameTransformMatrix { 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, -0.000000, 1.000000, 0.000000, 206.093353, -6.400993, -31.132195, 1.000000;; } Mesh Hull { 2470; 41.310013; -26.219450; -113.602348;, ... Frame Wheels_L { FrameTransformMatrix { 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000, -0.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, -56.020325, -31.414078, 3.666503, 1.000000;; } Mesh Wheels_L { 2513; -4.642166; -11.402874; -98.607910;, ... Frame Wheels_R { FrameTransformMatrix { 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000, -0.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 56.687805, -31.414078, 3.666503, 1.000000;; } Mesh Wheels_R { 2513; 4.642181; -11.402874; -98.607910;, Frame Turret { FrameTransformMatrix { 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, -2.077148, 84.137527, 29.323750, 1.000000;; } Mesh Turret { 2152; 52.655853; -36.225544; -16.728998;, ...

The templates used in this sample in rmxftmpl.x are:

template Frame { <3D82AB46-62DA-11cf-AB39-0020AF71E433> [...] } template FrameTransformMatrix { Matrix4x4 frameMatrix; } template Matrix4x4 { array FLOAT matrix[16]; }

As an old Direct3D.net tutorial freak, you might notice the use of 4x4 Matrices as described in the second tutorial First Steps to Animation. The whole sample is provided in the source package, which you may download with this tutorial. Another simple sample you should look at is the file car.x in the DirectX 7 SDK. And here's an advanced version of the X file of the sample program boidsy2.x:

xof 0302txt 0064 Header { 1; 0; 1; } Frame BOID_Root { FrameTransformMatrix { 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000;; } // boid Mesh boid { 7; // number of vertices 0.00; 0.00; 0.50;, // vertice 0 0.50; 0.00;-0.50;, // vertice 1 0.15; 0.15;-0.35;, // vertice 2 -0.15; 0.15;-0.35;, // vertice 3 0.15;-0.15;-0.35;, -0.15;-0.15;-0.35;, -0.50; 0.00;-0.50;; 10; // number of triangles 3;0,1,2;, // triangle #1 3;0,2,3;, // triangle #2 3;0,3,6;, // triangle #3 3;0,4,1;, // ... 3;0,5,4;, 3;0,6,5;, 3;1,4,2;, 3;2,4,3;, 3;3,4,5;, 3;3,5,6;; MeshMaterialList { 1; // one material 10; // ten faces 0,0;; // face #1 use material #0 0,0;; // face #2 use material #0 0,0;; // face #3 use material #0 0,0;; // face #4 use material #0 0,0;; // face #5 use material #0 0,0;; // face #6 use material #0 0,0;; // face #7 use material #0 0,0;; // face #8 use material #0 0,0;; // face #9 use material #0 0,0;; // face #10 use material #0 Material { // material #0 1.0;0.92;0.0;1.0;; 1.0; 0.0;0.0;0.0;; 0.0;0.0;0.0;; } } MeshNormals { 9; // 9 normals 0.2; 1.0; 0.0;, // normal #1 0.1; 1.0; 0.0;, // normal #2 0.0; 1.0; 0.0;, // normal #3 -0.1; 1.0; 0.0;, // normal #4 -0.2; 1.0; 0.0;, // normal #5 -0.4; 0.0;-1.0;, // normal #6 -0.2; 0.0;-1.0;, // normal #7 0.2; 0.0;-1.0;, // normal #8 0.4; 0.0;-1.0;; // normal #9 9; // two faces 4;0,0,0;, // connect face #1 with normal #1 4;1,1,1;, // connect face #2 with normal #2 4;2,2,2;, // connect face #3 with normal #3 4;3,3,3;, // connect face #4 with normal #4 4;4,4,4;, // connect face #5 with normal #5 4;5,5,5;, // connect face #6 with normal #6 4;6,6,6;, // connect face #7 with normal #7 4;7,7,7;, // connect face #8 with normal #8 4;8,8,8;; // connect face #9 with normal #9 } } }



Next : The Framework X File Class