• No results found

Part Overview

Chapter 6: The Rendering Pipeline Overview

6.2 Model Representation

An object is represented as a triangle mesh approximation, and consequently, triangles form the basic building blocks of the objects we model. We use the following terms interchangeably to refer to the triangles of a mesh: polygons, primitives, and mesh geometry. As Figure 6.7 implies, we can approximate any real-world 3D object by a triangle mesh. Generally speaking, the greater the triangle density of the mesh, the better the approximation. Of course, the more triangles we use, the more processing power is required, so a balance must be made based on the hardware power of the application's target audience.

Figure 6.7: (Left) A car approximated by a low-density triangle mesh. (Right) A skull approximated by a high-density triangle mesh.

The point where two edges on a polygon meet is called a vertex. To describe a triangle, we specify the three point locations that correspond to the three vertices of the triangle, as shown in Figure 6.8. Then to describe an object, we list the triangles that define it.

Figure 6.8: A triangle defined by its three vertices.

The large number of triangles used in Figure 6.7 makes one thing clear: It would be extremely cumbersome to manually list the triangles of a 3D model. For all but the simplest models, special 3D applications called 3D modelers are used to generate and manipulate 3D objects. These modelers allow the user to build complex and realistic meshes in a visual and interactive environment with a rich tool set, thereby making the entire modeling process much easier. Examples of popular modelers used for game development are 3D Studio MAX (www.discreet.com), LightWave 3D (www.newtek.com), Maya (www.aliaswavefront.com), and Softimage

| XSI (www.softimage.com).

Nevertheless, in Part II of this book, we will generate our 3D models manually by hand or via a mathematical formula (the triangle list for cylinders and spheres, for example, can easily be generated with parametric formulas). In Part III of this book, we show how to load and display 3D models exported to the Direct3D file format (.X).

6.2.1 Vertex Declarations

The previous definition of a vertex is correct mathematically, but it is an incomplete definition when used in the context of Direct3D. This is because a vertex in Direct3D can consist of additional properties besides a spatial location. For instance, a vertex can have a color property as well as a normal property (colors and normals are discussed in Chapters 9 and 10, respectively). Direct3D gives us the flexibility to construct our own vertex formats; in other words, it allows us to define the components (attributes) of a vertex.

To create a custom vertex format we first create a structure that holds the vertex data we choose. For instance, the following illustrates two different kinds of vertex formats; one consists of position and color, and the second consists of position, normal, and texture coordinates (see Chapter 11, "Texturing").

struct ColorVertex

Once we have the vertex structure completed, we need to provide Direct3D with a description of our vertex structure so that it knows what to do with each component. This description is provided to Direct3D in the form of a vertex declaration.

Note The DirectX documentation recommends a 32-byte vertex format for optimal performance

(directx/graphics/programmingguide/gettingstarted/direct3dresources/vertexbuffers/fvfvertexbuffers.htm).

We describe a vertex declaration by an array of D3DVERTEXELEMENT9 elements. Each element in the D3DVERTEXELEMENT9 array describes one component of the vertex. So if the vertex structure has three components (e.g., position, normal, color), then the corresponding vertex declaration will be described by an array of three D3DVERTEXELEMENT9 elements. The D3DVERTEXELEMENT9 structure is defined as:

typedef struct _D3DVERTEXELEMENT9 { BYTE Stream;

BYTE Offset;

(对应龙书一 17.1 顶点声明)

BYTE Type;

BYTE Method;

BYTE Usage;

BYTE UsageIndex;

} D3DVERTEXELEMENT9;

Stream: Specifies the stream with which the vertex component is associated. In this book, we just use one vertex stream — stream 0.

Offset: The offset, in bytes, from the start of the vertex structure to the start of the vertex component. This tells Direct3D where the vertex component is in the vertex structure. For example, if the vertex structure is:

struct Vertex

The offset of the component pos is 0 since it is the first component. The offset of the component normal is 12 because sizeof(pos) == 12 (i.e., the component normal is 12 bytes from the beginning of Vertex ). The offset of the component texCoords is 24 because sizeof (pos) + sizeof (normal) == 24 (i.e., the component texCoords is 24 bytes from the beginning of Vertex ).

Type: Specifies the data type of the vertex element. This can be any member of the D3DDECLTYPE enumerated type — see the documentation for a complete list. Some commonly used types are:

D3DDECLTYPE_FLOAT1: A floating-point scalar.

D3DDECLTYPE_FLOAT2: A 2D floating-point vector.

D3DDECLTYPE_FLOAT3: A 3D floating-point vector.

D3DDECLTYPE_FLOAT4: A 4D floating-point vector.

D3DDECLTYPE_D3DCOLOR: A D3DCOLOR type that is expanded to a 4D floating-point color vector, where each component is normalized to the interval [0, 1].

Returning to the above Vertex structure, the type of pos and normal would be D3DDECLTYPE_FLOAT3 since they are 3D vectors, and the type of texCoords would be D3DDECLTYPE_FLOAT2 since it is a 2D vector.

Method: Specifies the tessellation method. We consider this parameter advanced and thus use the default method, which is specified by the identifier D3DDECLMETHOD_DEFAULT.

Usage: This member tells Direct3D what the component is going to be used for — does the 3D vector represent a spatial location, a normal, or something else? Valid usage identifiers are of the D3DDECLUSAGE enumerated type. Some common usages are:

D3DDECLUSAGE_POSITION: Vertex component used to store position.

D3DDECLUSAGE_NORMAL: Vertex component used to store a normal.

D3DDECLUSAGE_TEXOORD: Vertex component used to store a texture coordinate.

D3DDECLUSAGE_COLOR: Vertex component used to store color information.

UsageIndex: Used to identify multiple vertex components of the same usage. The usage index is an integer in the interval [0, 15]. For example, suppose we have three vertex components of usage D3DDECLUSAGE_NORMAL. We would specify a usage index of 0 for the first, a usage index of 1 for the second, and a usage index of 2 for the third. In this way we can identify each particular normal by its usage index. For example, the following vertex declaration describes a vertex structure with a position component and three normals:

D3DVERTEXELEMENT9 decl[] = {

{0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0}, {0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0 }, {0, 24, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 1 }, {0, 36, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 2 }, D3DDECL_END()

};

The D3DDECL_END macro is used to mark the end of a D3DVERTEXELEMENT9 array. The vertex structure that decl describes looks like this:

struct Vertex

Once we have described a vertex declaration by a D3DVERTEXELEMENT9 array, we can obtain a pointer to an IDirect3DVertexDeclaration9 interface, which represents a vertex declaration using the method:

HRESULT IDirect3DDevice9::CreateVertexDeclaration(

CONST D3DVERTEXELEMENT9* pVertexElements, IDirect3DVertexDeclaration9** ppDecl);

pVertexElements: Array of D3DVERTEXELEMENT9 elements describing the vertex declaration we want created.

ppDecl: Used to return a pointer to the created IDirect3DVertexDeclaration9 interface.

For example, suppose that decl is a D3DVERTEXELEMENT9 array; then we create an IDirect3DVertexDeclaration9 like so:

IDirect3DVertexDeclaration9* d3dVertexDecl = 0;

HR(gd3dDevice - >CreateVertexDeclaration(decl, &d3dVertexDecl));

Note Do not get bogged down in the details of vertex declarations at this point. You will see plenty of easy examples over the next few chapters of this book, and some more advanced examples in the last part of this book.

Note For a large program, we typically work with numerous vertex structures. It is convenient to define all of these vertex structures in a single location. As we progress through this book, we add all of our vertex structure definitions to the source code files Vertex.h and Vertex. cpp.

6.2.2 Triangles

Triangles are the basic building blocks of 3D objects. To construct an object, we create a triangle list that describes its shape and contours. A triangle list contains the data for each individual triangle we wish to draw. For example, to construct a quad we break it into two triangles, as shown in Figure 6.9a, and specify the vertices of each triangle. Likewise, to construct a rough circle approximation (octagon), we break it up into triangles again and list the vertices of each triangle, as shown in Figure 6.9b. For example, to define the quad we define the vertex list as follows:

Vertex quad[6] = {

v0, v1, v2, // Triangle 0 v0, v2, v3 // Triangle 1 };

Figure 6.9: (a) A quad built from two triangles. (b) A rough circle approximation built from several triangles.

And we can do the same thing for the circle approximation:

Vertex circle[24] = {

Note The order in which you specify the vertices of a triangle is important and is called the winding order. See §6.4.5 for details.

6.2.3 Indices

As Figure 6.9 illustrates, the triangles that form a 3D object share many of the same vertices. More specifically, each triangle of the quad in Figure 6.9a shares the vertices v0 and v2. While duplicating two vertices is not too bad, the duplication is worse in the circle example (Figure 6.9b), as every triangle duplicates the center vertex v0, and each vertex on the exterior of the circle is shared by two triangles. In general, the number of duplicate vertices increases as the detail and complexity of the model increases.

There are two reasons why we do not want to duplicate vertices:

1. Increased memory requirements. (Why store the same vertex data more than once?)

2. Increased processing by the graphics hardware. (Why process the same vertex data more than once?)

To solve this problem, we introduce the concept of indices. It works like this: We create a vertex list and an index list. The vertex list consists of all the unique vertices and the index list contains values that index into the vertex list to define how the vertices are to be put together to form triangles. Returning to the shapes in Figure 6.9, the vertex list of the quad would be constructed as follows:

Vertex v[4] = {v0, v1, v2, v3};

Then the index list needs to define how the vertices in the vertex list are to be put together to form the two triangles.

WORD indexList[6] = {0, 1, 2, // Triangle 0 0, 2, 3}; // Triangle 1

In the index list, every three elements define a triangle. So the above index list says, "form triangle 0 by using the vertices v[0], v[1], and v[2], and form triangle 1 by using the vertices v[0], v[2], and v[3] ."

Similarly, the vertex list for the circle would be constructed as follows:

Vertex v [9] = {v0, v1, v2, v3, v4, v5, v6, v7, v8};

After the unique vertices in the vertex list are processed, the graphics card can use the index list to put the vertices together to form the triangles. Observe that we have moved the "duplication" over to the index list, but this is not bad since:

Indices are simply integers and do not take up as much memory as a full vertex structure (and vertex structures can get big as we add more components to them).

With good vertex cache ordering (see §14.5), the graphics hardware won't have to process duplicate vertices (too often).