Chapter 1. Introducing DirectX

DirectX is a set of APIs for developing game-or graphics-related applications on Microsoft platforms, including Windows, Windows Phone, Xbox 360, and the new Xbox One. DirectX has been evolving since the mid-1990s and is at the leading edge of modern graphics development. Direct3D is the 3D graphics API within DirectX, and this book focuses its attention there. DirectX also includes systems for 2D graphics, input, audio, text rendering, and general-purpose GPU programming. This chapter provides an overview of DirectX and the DirectX 11 graphics pipeline.

A Bit of History

DirectX version 1.0 was released in September 1995, just after the launch of Windows 95. Previously, games were primarily developed on MS-DOS. Microsoft’s release was an attempt to move game developers onto the new operating system. Few early adopters of DirectX stepped forward, a situation exacerbated by rapid-fire releases of the library. By February 2000, Microsoft had produced six additional major releases of DirectX and as many minor revisions. But the library steadily improved, and in 2001, Microsoft released its first game console, the Xbox, which supported a revision of DirectX 8. By that time, DirectX had gained a strong following of developers and Microsoft had emerged as a leader in the game and graphics space.

During this same period, 3D graphics hardware was in a similar state of rapid advancement. Consider that, before the mid-1990s, low-cost, consumer-oriented 3D graphics hardware simply didn’t exist. Early 3D graphics cards offered fixed-function APIs—feature sets that were specific to the graphics card and could not be modified or extended by a graphics developer. With the 2001 release of the Xbox, the nVidia GeForce 3, and DirectX 8, developers were first widely introduced to programmable shaders. Programmable shaders enable developers to manipulate a 3D object as it passes through the graphics processing unit (GPU), also known as a graphics card, and define its output pixel by pixel. Under DirectX 8, shaders were written in assembly language, but in 2002, with the release of DirectX 9, Microsoft introduced the High-Level Shading Language (HLSL), a C-styled programming language for writing shaders.

In November 2005, Microsoft launched the Xbox 360 and a console cycle that lasted until November 2013 (the longest such cycle since the introduction of game consoles). The Xbox 360 employed a flavor of DirectX 9. A year later, in November 2006, Microsoft released Windows Vista and, with it, DirectX 10. However, during this time, consoles dominated the video game market, and the Xbox 360 supported DirectX 9 only. Thus, few developers embraced DirectX 10, and those who did were limited to the PC platform for DirectX 10 features. Additional factors that likely suppressed large-scale adoption of DirectX 10 were the major changes between DirectX 9 and DirectX 10 and the poor adoption of Windows Vista.

Windows 7 launched in July 2009, followed shortly by DirectX 11. This latest release represented relatively minor changes from DirectX 10 (compared with those of the earlier iteration). DirectX 11 introduced DirectCompute, Microsoft’s API for general-purpose GPU (GPGPU) programming, tessellation support, and improved multithreading. Around this time, the PC game market began a resurgence in popularity, due in part to digital distribution platforms like Valve’s Steam, as well as aging consoles. These factors almost certainly contributed to the increasing number of developers who began adopting the updated libraries. Then in fall 2012, Microsoft released Windows 8 and Windows Phone 8, along with DirectX 11.1. This is the version this book focuses on, and all of Microsoft’s most recent platforms, including the new Xbox One, support it.

Although this is a book about DirectX, I would be remiss if I didn’t mention OpenGL, a competing graphics library that evolved during this same time period. OpenGL is a cross-platform rendering API that Silicon Graphics Inc. first released in 1991. As of this writing, the latest version of OpenGL is the 4.4 specification (released in July 2013), and the Kronos Group manages the API. Although they have significant differences in design, modern OpenGL and DirectX generally support the same rendering capability. Thus, the choice of DirectX or OpenGL is largely a question of platform. DirectX is specific to Microsoft platforms, and graphics vendors widely support it, given the dominance of the Microsoft Windows operating system on desktop computers. OpenGL, on the other hand, is not specific to a particular platform and has gained wide adoption in the mobile development space.

And so, with a bit of history under our belt, we begin.

The Direct3D 11 Graphics Pipeline

In your computer are generally two processors you’ll write code for: the central processing unit (CPU) and the GPU. These components have entirely different architectures and instruction sets. In graphics programming, you write software for both—in a general-purpose language, such as C++, for the application (CPU) side and in HLSL for the GPU side. DirectX is the bridge between the systems. Most texts on the subject of graphics programming focus on either the CPU side or the GPU side, yet they are very much intertwined. In this book, you learn about both.

Direct3D is the API within DirectX that we’re primarily concerned with. Put simply, Direct3D is the system you use to draw 3D graphics, and it defines a sequence of steps you use to present graphics to the screen. These steps are known as the Direct3D graphics pipeline (see Figure 1.1). In this figure, the arrows depict the flow of data from one stage to the next. The large rectangle spanning the right of the figure designates resources within GPU memory, and bi-directional arrows indicate read/write capability between a stage and these resources. Stages that are programmable (using HLSL) are shown as rounded rectangles. The following sections describe each stage in the pipeline.

Image

Figure 1.1 The Direct3D 11 graphics pipeline.

The Input-Assembler Stage (IA)

The input-assembler stage is the entry point of the graphics pipeline, where you supply vertex and index data for the objects you want to render. The IA stage “assembles” this data into primitives (such as points, lines, and triangles) and sends the corresponding output to the vertex shader stage. So what exactly is a vertex?

Vertex Buffers

A vertex is at least a position in three-dimensional space. When considering a line, a single vertex marks one of the endpoints of the line: one of three points for a triangle (see Figure 1.2). But I say a vertex is “at least” a position because it’s often much more than that. A vertex might also contain a color, a normal (useful for lighting calculations), texture coordinates, and more. All this data is made available to the input-assembler stage through a vertex buffer. Aside from a position, Direct3D leaves the definition of a vertex entirely in the hands of the programmer. You define what your vertices contain and then inform Direct3D of the vertex format through an input layout. Part III, “Rendering with DirectX,” describes the specific calls to set up vertex buffers and input layouts; all you need to know right now is the terminology.

Image

Figure 1.2 3D primitives: point (left), line (middle), triangle (right).

Index Buffers

Index buffers are the (optional) second type of input into the IA stage. Indices identify specific vertices within your vertex buffer and are employed to reduce duplication of reused vertices. Consider the following scenario: You want to render a rectangle (more generically, a quadrilateral). A quad can be minimally defined by four vertices. However, Direct3D doesn’t support quads as a primitive type (but doesn’t particularly need to because all polygonal shapes can be decomposed into triangles). To render the quad, you can split it into two triangles composed of three vertices each (see Figure 1.3). So now you have six total vertices instead of four, with two of the vertices duplicates. With an index buffer, you can instead specify just the four unique vertices and six indices into the vertex buffer.

Image

Figure 1.3 Vertices and indices for a 3D quadrilateral.

Now, you might be thinking, “How does adding an index buffer decrease the total size of the data I’m using?” Well, if your vertex data is composed of just a 3D position (x, y, z), where each component is a 32-bit floating point number (4 bytes per component), then each vertex is 12 bytes. Without an index buffer, your vertex buffer will be filled with 72 bytes of data (6 vertices × 12 bytes/vertex = 72 bytes). With an index buffer, your vertex buffer becomes 48 bytes (4 vertices × 12 bytes/vertex). If you use 16-bit integers for your indices, then your index buffer is a total of 12 bytes (6 indices × 2 bytes/index = 12 bytes). Combined, the total size of the vertex and index buffers is 60 bytes (48-byte vertex buffer + 12-byte index buffer). You see hardly any savings at all. But consider passing additional vertex data—perhaps a 16-byte color, a 12-byte normal, and 8 bytes of texture coordinates. That’s an additional 36 bytes per vertex saved for each vertex shared between two triangles. This really adds up when loading meshes with hundreds or thousands of vertices. Furthermore, you’re not concerned with just memory—you also need to consider the bus between the CPU and the GPU. Every bit of data that you employ must be transferred from the application to the video card over the graphics bus (such as PCI Express). This bus is slow (compared to CPU to RAM or GPU to VRAM), so reducing the amount of data you transmit is vital.

Primitive Types

When you supply a vertex buffer to the input-assembler stage, you must define the topology of those vertices—that is, how the pipeline interprets those vertices. Direct3D supports the following basic primitive types:

Image Point list

Image Line list

Image Line strip

Image Triangle list

Image Triangle strip

A point list is a collection of vertices that are rendered as individual, unconnected points. A line list, by contrast, connects pairs of points into line segments. However, these line segments are not connected as they are in a line strip. Furthermore, a line strip specifies its vertices not in pairs, but as a connect-the-dots-style sequence of points. Figure 1.4 illustrates these topologies.

Image

Figure 1.4 Point list (left), line list (middle), and line strip (right).

A triangle list is the most common topology we’ll be working with. In a triangle list, each set of three vertices is interpreted as an isolated triangle. Any shared vertices are repeated (not withstanding our discussion of index buffers). By contrast, a triangle strip interprets vertices as a series of connected triangles in which shared vertices are not repeated. Figure 1.5 depicts both topologies.

Image

Figure 1.5 Triangle list (top) and triangle strip (bottom).

Primitives with Adjacency

Since version 10, Direct3D has included support for primitives with adjacency data. For primitives with adjacency, you specify not only vertices for the base primitive, but also adjacent vertices “surrounding” the base primitive. Figure 1.6 illustrates this concept.

Image

Figure 1.6 Triangle list with adjacency.

Control Point Patch Lists

Direct3D 11 added control point patch lists as a supported topology for use with the tessellation stages of the pipeline. We discuss patch lists in Chapter 21, “Geometry and Tessellation Shaders.”

The Vertex Shader Stage (VS)

The vertex shader stage processes the primitives interpreted by the input-assembler stage. It does this processing on a per-vertex basis. This is the first stage in the pipeline that is programmable. In fact, a vertex shader must always be supplied to the VS stage. So what exactly is a shader?

A shader is a small program—a function, if you’d like—that you write and the GPU executes. A vertex shader operates on every vertex passing through the pipeline (the input to the shader), performing a series of instructions and passing output to the next active stage. As mentioned before, the input to the vertex shader is at least the position of the vertex. Generally, the vertex shader transforms the vertex in some way and outputs the modified or newly computed data. Listing 1.1 shows perhaps the simplest vertex shader we can create.

Listing 1.1 Your First Vertex Shader


float4 vertex_shader(float3 objectPosition : POSITION) : SV_Position
{
    return mul(float4(objectPosition, 1), WorldViewProjection);
}



Note

Did you say simple? Don’t stress over the syntax of this vertex shader. As a C/C++ programmer, this should look vaguely like a function, but clearly there’s some special sauce in the mix. We cover all of this syntax in Part II.


Tessellation Stages

New to Direct3D 11, hardware tessellation is a process that adds detail to objects directly on the GPU. Generally, more geometric detail (that is, more vertices) yields a better-looking render. Consider the images in Figure 1.7.

Image

Figure 1.7 A 3D model with low, medium, and high levels of detail. (3D Model by Nick Zuccarello, Florida Interactive Entertainment Academy.)

This image shows a 3D model with low, medium, and high levels of detail (LODs). Traditionally, LODs are authored by an artist, and a particular LOD is rendered based on the distance of the object from the camera.


Note

Little reason exists for rendering a high-resolution object that is far away because all that added detail is lost from the perspective of the viewer. You choose a level of detail corresponding to the object’s distance—the farther away, the lower the detail.

The fewer vertices your vertex shaders have to process, the faster your rendering is.


With a traditional LOD system, you have a fixed amount of detail (polygon count) in your models. Hardware tessellation enables you to subdivide an object dynamically and without the cost of additional geometry passed to the input-assembler stage. This allows for a dynamic LOD system and less utilization of the graphics bus (both good things). In Direct3D 11, these three stages correspond to tessellation:

Image The hull shader stage (HS)

Image The tessellator stage

Image The domain shader stage (DS)

The hull and domain shader stages are programmable; the tessellator stage is not. We cover the topic of tessellation in more detail in Chapter 21.

The Geometry Shader Stage (GS)

Unlike vertex shaders, which operate on individual vertices, geometry shaders operate on complete primitives (such as points, lines, and triangles). Moreover, geometry shaders have the capability to add or remove geometry from the pipeline. This feature can provide some interesting applications. For example, you could create a particle effects system, in which a single vertex represents each particle. In the geometry shader, you can create quads around those central points, to which you can map textures. Such objects are commonly known as point sprites.

Connected to the GS stage is the stream-output stage (SO). This stage streams in the vertices output from the geometry shader and stores them in memory. For multipass rendering, this data can be read back into the pipeline in a subsequent pass, or the CPU can read the data. As with the tessellation stages, the geometry shader stage is optional. We discuss geometry shaders and multipass rendering in more detail in Part IV, “Intermediate-Level Rendering Topics.”

The Rasterizer Stage (RS)

Up to this point in the pipeline, we’ve mostly been discussing vertices and the interpretation of those vertices into primitives. The rasterizer stage converts those primitives into a raster image, otherwise known as a bitmap. A raster image is represented as a two-dimensional array of pixels (colors) and generally makes up some area of your computer screen.

The rasterizer stage determines what, if any, pixels should be rendered and passes those pixels to the pixel shader stage. Along with the pixels to render, the rasterizer passes per-vertex values interpolated across each primitive. For example, a triangle primitive has three vertices, each of which contains at least a position and potentially additional data such as color, normal, and texture coordinates. This vertex data is interpolated for the in-between pixels the rasterizer computes. Figure 1.8 illustrates this concept for vertex colors. In this image, the three points of the triangle are supplied with a red, green, and blue vertex color, respectively. Notice how the pixels within the triangle change color relative to their proximity to the three vertices. The rasterizer stage produces those interpolated colors.

Image

Figure 1.8 Rasterizer interpolation for a triangle with red, green, and blue colors specified for the three vertices.

The Pixel Shader Stage (PS)

Although technically optional, you almost always provide a shader to the pixel shader stage. This stage executes your shader code against each pixel input from the rasterizer stage and (typically) outputs a color. This gives the programmer control over every pixel that’s rendered to the screen. The pixel shader uses interpolated per-vertex data, global variables, and texture data to produce its output. Listing 1.2 presents a shader that outputs pure red for each pixel.

Listing 1.2 Your First Pixel Shader


float4 pixel_shader() : SV_Target
{
    return float4(1, 0, 0, 1);
}


The Output-Merger Stage (OM)

The output-merger stage produces the final rendered pixel. This stage isn’t programmable (you don’t write a shader for the OM stage), but you do control how it behaves through customizable pipeline states. The OM stage generates the final pixel through a combination of these states, the output from the pixel shader stage, and the existing contents of the render target. This allows for, among other interesting effects, the implementation of transparent objects through color blending. We discuss blending in Chapter 8, “Gleaming the Cube.”

The OM stage also determines which pixels are visible in the final render through processes known as depth testing and stencil testing. Depth testing uses data that has previously been written to the render target to determine whether a pixel should be drawn. Consider the scenario in Figure 1.9. In this figure, a number of objects are closer to the camera than others, but they otherwise represent the same screen space. These objects occlude the ones behind them either entirely or in part. Depth testing makes use of the distance between the object and the camera for each corresponding pixel written to the render target. Most commonly, if the pixel already in the render target is closer to the camera than the pixel being considered, the new pixel is discarded.

Image

Figure 1.9 A scene illustrating depth testing, with some objects occluding others.

Stencil testing uses a mask to determine which pixels to update. This is conceptually similar to a cardboard or plastic stencil you might use to paint or print a design on a physical surface. We discuss depth and stencil testing in more detail in Part III, “Rendering with DirectX.”


Note

The rasterizer stage also plays a role in determining which pixels are rendered to the screen in a process known as clipping. Any pixels that the rasterizer determines to be “off-screen” aren’t sent to the pixel shader stage and are discarded from further processing in the pipeline.


Summary

In this chapter, you discovered that DirectX is a set of APIs that covers a range of game-related topics, including 2D and 3D rendering, input, audio, and even general-purpose GPU programming. You learned that DirectX has been evolving since the mid-1990s and is now in its 11th major revision. The chapter provided an overview of the Direct3D graphics pipeline, from the input-assembler stage to the output-merger stage. You were introduced to topics including vertex and index buffers, primitive topologies, vertex and pixel shaders, and tessellation.

This is just the beginning—the coming chapters expand upon all these topics and much more.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.10.201