MULTIMEDIA

Programming with DirectX : Additional Texture Mapping - Sprites

1/29/2011 6:58:53 PM
A sprite is a 2D image displayed on the screen. Sprites are more common in 2D games, where the images act solely as the various game objects, characters, and environments. In 3D we usually have 3D geometry with texture images applied to them to simulate detail where none exists. In some 3D games, sprites can be seen as screen interface graphics such as health bars, shield bars, and so on.

In the early days of 3D video games, sprites were used more like they are for 2D games, where sprite characters and objects populate the game world. This can be seen in early 3D games such as Id Software’s Doom, Duke Nuke ’Em 3D, and many more. Today sprites are used mostly for particle effects such as snow, rain, smoke, dust, fire, weapon effects, and so forth.

Types of Sprites

There are two main types of sprites: sprites that can orient themselves in 3D space and those that cannot. The sprites that cannot orient in 3D are known as billboard sprites. In other words, a billboard sprite always faces the camera but can be positioned anywhere. Characters in the game Doom were billboard sprites; no matter how the player was oriented, the enemies and many world objects would always face the player.

The decision to use billboard sprites or not depends on the application. For most particle systems the particles are so small that changing their rotation might have a negative effect on the scene or, possibly, no effect at all. In 3D you can draw a textured square to act as a sprite. In this article we will create billboard sprites in Direct3D 10 using the sprite image in Figure 1 since learning that will give us something new to cover rather than drawing yet another textured square.

Figure 1. An example of a sprite.


To create a sprite you simply create a 2D image. If any part of the image is to be transparent or semitransparent, then you can create a 32-bit RGBA image to represent the object. In the image in Figure 1 the black areas are set to transparent in the alpha channel in Adobe Photoshop, while the orb itself is the only visible part of the texture image.

Point Sprites

Direct3D 9 and OpenGL have what are known as point sprites. A point sprite is essentially a sprite that is generated from a point and, in the case of Direct3D 9 and OpenGL, is also hardware accelerated. This means the hardware handles keeping the sprite facing the camera, and all the programmer has to do is enable the point sprite feature, specify the properties (such as size etc.), and supply a list of points to the graphics hardware.

Direct3D 10, however, does not have this point sprite feature. To use hardware-accelerated point sprites in Direct3D 10, we must use the geometry shader to create the sprites from a list of points. Since the geometry shader is doing the work, the effect is technically GPU hardware accelerated since no CPU processing is used for the creation of the geometry.

Sprites Demo

The Billboard Sprites demo builds off of the Alpha Mapping demo and makes several modifications to the original source code.

In the shader effect file, a new global uniform variable is created to represent the size of the sprite. Also added to the effect file is a geometry shader. The geometry shader is set up to accept an input of a single point, and it outputs six vertices because it creates two triangles (three points each) that form the square shape. The body of the geometry shader will use the input point’s position as the center position and will use the global size to create four points around the center point. It outputs each triangle using the stream object’s Append() function, and once a triangle has been fully outputted, the RestartStrip() function is called to mark the start of the next triangle. Keep in mind that each triangle is separated by a RestartStrip() call in a geometry shader.

To ensure that the triangles are always facing the camera, we use the billboard technique. This is done by taking the right and up vectors (i.e., directions) of the view matrix. These vectors act as directions, where -right is left, right is right, up is up, and -up is down. Therefore, to generate the point that goes in the upper left, we add the center position to the -right + up vectors (e.g., pos + [-right + up]) to move the center position to the upper left. We can then pass that position to the triangle stream output using Append(). We do this for the upper-left, upper-right, lower-left, and lower-right positions of the square that will act as the sprite.

The view matrix represents the camera. By taking the first column (right vector) and the first row (up vector), we can use those directions in 3D space to move the center point around in a way that gives the impression that the resulting square is facing the camera. Mathematically, what is happening is that we determine which directions are considered right of and up from the camera, and we generate a square in those directions so that no matter where the camera is facing, we can always generate geometry that looks directly at the camera. You can see this by holding up a pencil and moving it to the left and up (upper left) of where you are facing. Then move it right and up from its original position and so forth. The view matrix uses the same idea. As long as you move the object with respect to what the view (your eyes) considers right and up or down and left, the resulting geometry is always facing you.

The HLSL shader from the Billboard Sprite demo is shown in Listing 1. Note that the geometry shader also generates texture coordinates so that the square can be textured properly. Also, since the size is set every frame, it is added to the constant buffer that is marked for change on a frame-by-frame basis. Since the geometry coming into the geometry shader is the transformed data from the vertex shader, we must use the original transformed W of each point for the pixel shader to get the correct data. Once the data has been transformed by the vertex shader, we don’t have to alter it in a way that changes its meaning and therefore alters our output in ways we don’t expect.

Listing 1. The Billboard Sprite’s HLSL Effect Shader
/*
Billboard Sprite Demo's Shader
Ultimate Game Programming with DirectX 2nd Edition
Created by Allen Sherrod
*/

uniform Texture2D decal;

SamplerState DecalSampler
{
Filter = MIN_MAG_MIP_LINEAR;
AddressU = Wrap;
AddressV = Wrap;
};


BlendState AlphaBlending
{
AlphaToCoverageEnable = FALSE;
BlendEnable[0] = TRUE;
SrcBlend = SRC_ALPHA;
DestBlend = INV_SRC_ALPHA;
BlendOp = ADD;
SrcBlendAlpha = ZERO;
DestBlendAlpha = ZERO;
BlendOpAlpha = ADD;
RenderTargetWriteMask[0] = 0×0F;
};


cbuffer cbChangesEveryFrame
{
matrix World;
matrix View;
float size;
};

cbuffer cbChangeOnResize
{
matrix Projection;
};

struct VS_INPUT
{
float4 Pos : POSITION;
};

struct GS_INPUT
{
float4 Pos : SV_POSITION;
};

struct PS_INPUT
{
float4 Pos : SV_POSITION;
float2 Tex : TEXCOORD0;
};

GS_INPUT VS(VS_INPUT input)
{
GS_INPUT output = (GS_INPUT)0;

output.Pos = mul(input.Pos, World);
output.Pos = mul(output.Pos, View);
output.Pos = mul(output.Pos, Projection);

return output;
}


[maxvertexcount(6)]
void GS(point GS_INPUT input[1],
inout TriangleStream<PS_INPUT> triStream)
{
PS_INPUT output = (PS_INPUT)0;

matrix modelView = World * View;

// Used for generating billboard geometry aligned to the camera.

float3 right = float3(modelView._m00, modelView._m10,
modelView._m20);

float3 up = float3(modelView._m01, modelView._m11,
modelView._m21);

float3 pos = input[0].Pos.xyz;


// Must use transformed vertex W. Start first triangle.

output.Pos = float4(pos + (-right + up) * size, input[0].Pos.w);
output.Tex = float2(0.0, 1.0);
triStream.Append(output);

output.Pos = float4(pos + (right + up) * size, input[0].Pos.w);
output.Tex = float2(0.0, 0.0);
triStream.Append(output);

output.Pos = float4(pos + (right - up) * size, input[0].Pos.w);
output.Tex = float2(1.0, 0.0);
triStream.Append(output);


// Start next triangle.

triStream.RestartStrip();
output.Pos = float4(pos + (right - up) * size, input[0].Pos.w);
output.Tex = float2(1.0, 0.0);
triStream.Append(output);

output.Pos = float4(pos + (-right - up) * size, input[0].Pos.w);
output.Tex = float2(1.0, 1.0);
triStream.Append(output);

output.Pos = float4(pos + (-right + up) * size, input[0].Pos.w);
output.Tex = float2(0.0, 1.0);
triStream.Append(output);

triStream.RestartStrip();
}

float4 PS(PS_INPUT input) : SV_Target
{
return decal.Sample(DecalSampler, input.Tex);
}

technique10 Billboard
{
pass P0
{
SetBlendState(AlphaBlending, float4(0.0f, 0.0f, 0.0f, 0.0f),
0xFFFFFFFF);

SetVertexShader(CompileShader(vs_4_0, VS()));
SetGeometryShader(CompileShader(gs_4_0, GS()));
SetPixelShader(CompileShader(ps_4_0, PS()));
}
}

On the application side, the Billboard Sprite demo sends four points to Direct3D 10. Each of these points acts as the center of a sprite, and, when rendered, the geometry shader generates squares around each of these points. Since the geometry shader also generates the texture coordinates, the only thing we need to render is a list of positions. The global section of the demo’s main.cpp source file is shown in Listing 2, where an effect variable for the sprite’s size has been added to the mix. Also in Listing 2, you can see the modified InitializeDemo() function and RenderScene() function. Notice that in the RenderScene() function the flag D3D10_PRIMITIVE_TOPOLOGY_POINTLIST is used to specify that we are sending points, not triangles, to the Direct3D 10 API. Even though the geometry shader generates triangles, technically, we are rendering points with this function call, and therefore Direct3D 10 should expect points as the input, not triangle primitives. Listing 7.4 shows the code that was modified to create the Billboard Sprite demo and was built from the Alpha Mapping demo. To avoid listing too much redundant code, we have listed the InitializeDemo() function up until the point at which the vertex buffer is created.

Listing 2. The Modified Code of the Billboard Sprite Demo’s Main Source File
/*
Billboard Sprites
Ultimate Game Programming with DirectX 2nd Edition
Created by Allen Sherrod
*/

#include<d3d10.h>
#include<d3dx10.h>

#pragma comment(lib, "d3d10.lib")
#pragma comment(lib, "d3dx10.lib")

#define WINDOW_NAME "Billboard Sprites"
#define WINDOW_CLASS "UPGCLASS"
#define WINDOW_WIDTH 800
#define WINDOW_HEIGHT 600
// Global window handles.
HINSTANCE g_hInst = NULL;
HWND g_hwnd = NULL;

// Direct3D 10 objects.
ID3D10Device *g_d3dDevice = NULL;
IDXGISwapChain *g_swapChain = NULL;
ID3D10RenderTargetView *g_renderTargetView = NULL;

struct DX10Vertex
{
D3DXVECTOR3 pos;
};

ID3D10InputLayout *g_layout = NULL;
ID3D10Buffer *g_pointsVB = NULL;
ID3D10ShaderResourceView *g_spriteDecal = NULL;

ID3D10Effect *g_shader = NULL;
ID3D10EffectTechnique *g_billboardTech = NULL;
ID3D10EffectShaderResourceVariable *g_decalEffectVar = NULL;
ID3D10EffectScalarVariable *g_pointSpriteSize = NULL;
ID3D10EffectMatrixVariable *g_worldEffectVar = NULL;
ID3D10EffectMatrixVariable *g_viewEffectVar = NULL;
ID3D10EffectMatrixVariable *g_projEffectVar = NULL;

D3DXMATRIX g_worldMat, g_viewMat, g_projMat;


bool InitializeDemo()
{
// Load the shader.
DWORD shaderFlags = D3D10_SHADER_ENABLE_STRICTNESS;

#if defined( DEBUG ) || defined( _DEBUG )
shaderFlags |= D3D10_SHADER_DEBUG;
#endif

HRESULT hr = D3DX10CreateEffectFromFile(
"BillboardSpritesShader.fx",
NULL, NULL, "fx_4_0", shaderFlags, 0, g_d3dDevice, NULL,
NULL, &g_shader, NULL, NULL);

if(FAILED(hr))
return false;

g_billboardTech = g_shader->GetTechniqueByName("Billboard");
g_worldEffectVar = g_shader->GetVariableByName(
"World")->AsMatrix();

g_viewEffectVar = g_shader->GetVariableByName(
"View")->AsMatrix();

g_projEffectVar = g_shader->GetVariableByName(
"Projection")->AsMatrix();

g_decalEffectVar = g_shader->GetVariableByName(
"decal")->AsShaderResource();

g_pointSpriteSize = g_shader->GetVariableByName(
"size")->AsScalar();


// Load the texture.
hr = D3DX10CreateShaderResourceViewFromFile(g_d3dDevice,
"sprite.dds", NULL, NULL, &g_spriteDecal, NULL);
if(FAILED(hr))
return false;


// Create the geometry.

D3D10_INPUT_ELEMENT_DESC layout[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,
D3D10_INPUT_PER_VERTEX_DATA, 0 },
};

unsigned int numElements = sizeof(layout) / sizeof(layout[0]);
D3D10_PASS_DESC passDesc;

g_billboardTech->GetPassByIndex(0)->GetDesc(&passDesc);

hr = g_d3dDevice->CreateInputLayout(layout, numElements,
passDesc.pIAInputSignature, passDesc.IAInputSignatureSize,
&g_layout);

if(FAILED(hr))
return false;

DX10Vertex vertices[] =
{
{ D3DXVECTOR3( 0.5f, 0.5f, 3.0f) },
{ D3DXVECTOR3( 0.5f, -0.5f, 3.0f) },
{ D3DXVECTOR3(-0.5f, -0.5f, 3.0f) },
{ D3DXVECTOR3(-0.5f, -0.5f, 3.0f) },
{ D3DXVECTOR3(-0.5f, 0.5f, 3.0f) },
{ D3DXVECTOR3( 0.5f, 0.5f, 3.0f) }
};


return true;
}

void RenderScene()
{
float col[4] = { 0, 0, 0, 1 };

g_d3dDevice->ClearRenderTargetView(g_renderTargetView, col);

g_worldEffectVar->SetMatrix((float*)&g_worldMat);
g_decalEffectVar->SetResource(g_spriteDecal);
g_pointSpriteSize->SetFloat(0.3f);

unsigned int stride = sizeof(DX10Vertex);
unsigned int offset = 0;

g_d3dDevice->IASetInputLayout(g_layout);

g_d3dDevice->IASetVertexBuffers(0, 1, &g_pointsVB, &stride,
&offset);

g_d3dDevice->IASetPrimitiveTopology(
D3D10_PRIMITIVE_TOPOLOGY_POINTLIST);

D3D10_TECHNIQUE_DESC techDesc;
g_billboardTech->GetDesc(&techDesc);

for(unsigned int i = 0; i < techDesc.Passes; i++)
{
g_billboardTech->GetPassByIndex(i)->Apply(0);
g_d3dDevice->Draw(6, 0);
}

g_swapChain->Present(0, 0);
}


Figure 2 shows a screenshot of the Billboard Sprite demo.

Figure 2. Screenshot from the Billboard Sprite demo.

Other  
  •  Programming with DirectX : Additional Texture Mapping - Alpha Mapping
  •  Microsoft XNA Game Studio 3.0 : Writing Your First Program (part 2) - Running the Same XNA Game on Different Devices
  •  Microsoft XNA Game Studio 3.0 : Writing Your First Program (part 1)
  •  Programming with DirectX : Shading and Surfaces - Additional Texturing Topics
  •  iPhone 3D Programming : Adding Textures to ModelViewer (part 4) - Enabling Textures with ES2::RenderingEngine
  •  iPhone 3D Programming : Adding Textures to ModelViewer (part 3) - Enabling Textures with ES1::RenderingEngine
  •  iPhone 3D Programming : Adding Textures to ModelViewer (part 2) - Generating Texture Coordinates
  •  iPhone 3D Programming : Adding Textures to ModelViewer (part 1) - Enhancing IResourceManager
  •  Programming with DirectX : Shading and Surfaces - Implementing Texture Mapping (part 2) - Multi Texture Demo
  •  Programming with DirectX : Shading and Surfaces - Implementing Texture Mapping (part 1) - 2D Texture Mapping Demo
  •  Building Out Of Browser Silverlight Applications - Using COM Interoperability and File System Access
  •  Building Out Of Browser Silverlight Applications - Controlling the Application Window
  •  iPhone 3D Programming : Adding Depth and Realism - Loading Geometry from OBJ Files
  •  iPhone 3D Programming : Adding Depth and Realism - Better Wireframes Using Polygon Offset
  •  Programming with DirectX : Textures in Direct3D 10 (part 2)
  •  Programming with DirectX : Textures in Direct3D 10 (part 1) - Textures Coordinates
  •  Programming with DirectX : Shading and Surfaces - Types of Textures
  •  iPhone 3D Programming : Adding Shaders to ModelViewer (part 2)
  •  iPhone 3D Programming : Adding Shaders to ModelViewer (part 1) - New Rendering Engine
  •  iPhone 3D Programming : Adding Depth and Realism - Shaders Demystified
  •  
    Video
    Top 10
    Nikon 1 J2 With Stylish Design And Dependable Image And Video Quality
    Canon Powershot D20 - Super-Durable Waterproof Camera
    Fujifilm Finepix F800EXR – Another Excellent EXR
    Sony NEX-6 – The Best Compact Camera
    Teufel Cubycon 2 – An Excellent All-In-One For Films
    Dell S2740L - A Beautifully Crafted 27-inch IPS Monitor
    Philips 55PFL6007T With Fantastic Picture Quality
    Philips Gioco 278G4 – An Excellent 27-inch Screen
    Sony VPL-HW50ES – Sony’s Best Home Cinema Projector
    Windows Vista : Installing and Running Applications - Launching Applications
    Most View
    Bamboo Splash - Powerful Specs And Friendly Interface
    Powered By Windows (Part 2) - Toshiba Satellite U840 Series, Philips E248C3 MODA Lightframe Monitor & HP Envy Spectre 14
    MSI X79A-GD65 8D - Power without the Cost
    Canon EOS M With Wonderful Touchscreen Interface (Part 1)
    Windows Server 2003 : Building an Active Directory Structure (part 1) - The First Domain
    Personalize Your iPhone Case
    Speed ​​up browsing with a faster DNS
    Using and Configuring Public Folder Sharing
    Extending the Real-Time Communications Functionality of Exchange Server 2007 : Installing OCS 2007 (part 1)
    Google, privacy & you (Part 1)
    iPhone Application Development : Making Multivalue Choices with Pickers - Understanding Pickers
    Microsoft Surface With Windows RT - Truly A Unique Tablet
    Network Configuration & Troubleshooting (Part 1)
    Panasonic Lumix GH3 – The Fastest Touchscreen-Camera (Part 2)
    Programming Microsoft SQL Server 2005 : FOR XML Commands (part 3) - OPENXML Enhancements in SQL Server 2005
    Exchange Server 2010 : Track Exchange Performance (part 2) - Test the Performance Limitations in a Lab
    Extra Network Hardware Round-Up (Part 2) - NAS Drives, Media Center Extenders & Games Consoles
    Windows Server 2003 : Planning a Host Name Resolution Strategy - Understanding Name Resolution Requirements
    Google’s Data Liberation Front (Part 2)
    Datacolor SpyderLensCal (Part 1)