There are several overrides for this method, but for ourneeds, all you have to do is pass in the array of indices: ref-SetDataT[] data; Managing Vertex Data with Index Buffers and Vertex
Trang 1Once the indices have been defined, theSetData()method stores the index erences in the index buffer There are several overrides for this method, but for ourneeds, all you have to do is pass in the array of indices:
ref-SetData<T>(T[] data);
Managing Vertex Data with Index Buffers and Vertex
Buffers
By themselves, theVertexPositionColor,VertexPositionColorTexture,
VertexPositionTexture, and VertexPositionNormalTexture objectsthat you have used will not permit live updates to the position, color, texture, andnormal data.DynamicVertexBufferobjects in combination with index buffers,
on the other hand, will permit updates to large amounts of vertex data You are going
to want a structure like this when creating an effect such as water
When initialized, the constructor for the vertex buffer takes parameters for thecurrent graphics device, vertex type, element count, and buffer usage for memory al-location:
VertexBuffer( GraphicsDevice graphicsDevice,
Type vertexType, int elementCount, BufferUsage usage);
As explained above,BufferUsageoptions areNoneas the default,WriteOnly
for efficient writes to memory and for efficient drawing, andPointsfor point sprites.After the vertex data is loaded into an array, the vertex data is moved into the ver-tex buffer with theVertexBufferobject’sSetData()method There are severalvariations of this method The method used here only passes the array of vertices:SetData(VertexBuffer[] vertexBuffer)
Rendering Vertex Buffers with
an Index Buffer ReferenceThe draw method you use for dynamic vertex buffers—using index buffers—differs
in three ways from the draw methods you have used until now:
1. TheSetSource()method is used to set the vertex buffer that stores thegrid, the starting element, and the size of the vertex type in bytes:
graphics.GraphicsDevice.Vertices[0].SetSource(
VertexBuffer vertexBuffer, int startingElement, int sizeOfVertex );
Trang 22. TheGraphicsDevice’sIndicesobject is set with the corresponding
IndexBufferobject you defined during the program setup:
graphics.GraphicsDevice.Indices = indexBuffer;
3. TheDrawIndexedPrimitives()method is used to reference a series of
vertex subsets that are rendered in succession to draw the entire polygon or
surface.DrawIndexedPrimitives()is called for each vertex subset
graphics.GraphicsDevice.DrawIndexedPrimitives(
PrimitiveType primitiveType, int startingPointInVertexBuffer, int minimumVerticesInBuffer, int totalVerticesInBuffer, int indexBufferStartingPoint, int indexBufferEndPoint);
Grid Using Index Buffer Example
This code will implement an index buffer and dynamic vertex buffer to draw a
rect-angle from a set of vertices that is three vertices wide and five vertices long (see Figure
11-3) Drawing a rectangle with a set of vertices that uses index buffers might seem
like a lackluster chore, but don’t be fooled Index buffers have grit This little
exam-ple serves as the foundation for creating water waves in Chapter 12, creating terrain
with height detection in Chapter 25, and enabling better lighting across primitive
surfaces in Chapter 22
This example begins with either the MGHWinBaseCode or MGH360BaseCode
project in the BaseCode folder on this book’s website
Trang 3To make this vertex reference system work, an index buffer to reference a grid ofvertices is required Also, a vertex buffer object is needed to store the vertices A ver-tex declaration type is used to set up the buffer when it is being initialized Add theseobject declarations to the top of your game class:
private IndexBuffer indexBuffer; // reference vertices
private VertexBuffer vertexBuffer; // vertex storage
The rows and columns used to draw the rectangle will be referenced with ers to help explain how the vertices are arranged Add these identifiers to the top ofthe game class
identifi-const int NUM_COLS = 3;
const int NUM_ROWS = 5;
Indices for referencing the vertex buffer are initialized when the program begins.The index buffer array is sized to store the total number of vertices contained in onesubset of the vertex buffer The code that you need to set up the index reference iscontained in theInitializeIndices()method Add this method to set up yourindex reference:
private void InitializeIndices(){
short[] indices; // indices for 1 subset indices = new short[2 * NUM_COLS]; // sized for 1 subset
indexBuffer = new IndexBuffer(
graphics.GraphicsDevice,// graphics device typeof(short), // data type is short indices.Length, // array size in bytes BufferUsage.WriteOnly); // memory allocation
// store indices for one subset of vertices
// see Figure 11-2 for the first subset of indices
Trang 4C H A P T E R 1 1
AVertexBufferobject is initialized to store the vertices used to build the
primi-tive surface TheVertexBufferobject in this example is static and only stores the
vertices that are used to draw the grid or surface Still, the vertex buffer works in
con-junction with the index buffer as a reference to reduce the total number of vertices
that build the grid or surface This type of static vertex buffer is especially useful for
drawing lit objects or even terrain where many vertices are needed for detail When
setting up a vertex buffer, the vertex data is generated and is stored in a temporary
ar-ray Once all of the vertex values have been assembled, the vertex data is then stored
in the vertex buffer using theSetData()method To set up your vertices in this
effi-cient buffer, addInitializeVertexBuffer()to your game class:
private void InitializeVertexBuffer(){
vertexBuffer = new VertexBuffer(
graphics.GraphicsDevice, // graphics device typeof(VertexPositionColorTexture), // vertex type NUM_COLS * NUM_ROWS, // element count BufferUsage.WriteOnly); // memory use
// store vertices temporarily while initializing them
VertexPositionColorTexture[] vertex
= new VertexPositionColorTexture[NUM_ROWS*NUM_COLS];
// set grid width and height
float colWidth = (float)2 * BOUNDARY/(NUM_COLS - 1);
float rowHeight = (float)2 * BOUNDARY/(NUM_ROWS - 1);
// set position, color, and texture coordinates
for (int row=0; row < NUM_ROWS; row++){
for (int col=0; col < NUM_COLS; col++){
// set X, Y, Z
float X = -BOUNDARY + col * colWidth;
float Y = 0.0f;
float Z = -BOUNDARY + row * rowHeight;
vertex[col + row * NUM_COLS].Position = new Vector3(X, Y, Z);
// set color
vertex[col + row * NUM_COLS].Color = Color.White;
// set UV coordinates to map texture 1:1
float U = (float)col/(float)(NUM_COLS - 1);
float V = (float)row/(float)(NUM_ROWS - 1);
vertex[col + row * NUM_COLS].TextureCoordinate
Trang 5= new Vector2(U, V);
} }
// commit data to vertex buffer
private void DrawIndexedGrid(){
// 5: draw object - select vertex type, vertex source, and indices
Trang 6// draw grid one row at a time
for (int Z = 0; Z < NUM_ROWS - 1; Z++){
graphics.GraphicsDevice.DrawIndexedPrimitives(
PrimitiveType.LineStrip, // primitive
Z * NUM_COLS, // start point in buffer for drawing
0, // minimum vertices in vertex buffer
NUM_COLS * NUM_ROWS, // total vertices in buffer
2 * (NUM_COLS - 1)); // end point in index buffer
When you run the program, the grid appears as shown earlier in Figure 11-3
However, if the grid is drawn with triangle strips, the output will fill in the area
be-tween the vertices and display a rectangle This will happen if you change
LineStriptoTriangleStripinDrawIndexedGrid()
Bystanders might not be impressed that you just created a rectangular surface, but
don’t let that bother you Let’s put this demo on the backburner for now We’ll return
to it in later chapters to let it rip
C HAPTER 11 REVIEW EXERCISES
To get the most from this chapter, try out these chapter review exercises
1. Try the step-by-step example in this chapter, but this time change the
number of rows to 125 and the number of columns to 55 View the
project using line strips and triangle strips
2. Compared to methods that are used in previous chapters for storing vertices
without an index reference, how many vertices are saved when using an
index buffer to draw a grid that is 60 rows high and 35 rows wide?
C H A P T E R 1 1
Trang 73. The example presented in this chapter shows how to use a static
VertexBufferobject with index buffers What is the advantage
of doing this? What type of vertex buffer permits updates to vertices
at run time?
4. List three ways that theDrawIndexedPrimitives()method is differentfrom theDrawUserPrimitives()method
164
Trang 8CHAPTER 12
Combining Images for Better Visual Effects
Trang 9THIS chapter demonstrates various ways of combining images to gen-erate compelling visual effects; more specifically, sprites andmultitexturing will be discussed By the end of the chapter, you will be able to use im-age files that store more than one image frame to create cool heads-up display (HUD)animations in your 2D or 3D games You will also be able to blend two textures to-gether to generate intricate detail for effects such as terrain or a water simulation Al-though games are not defined solely by their aesthetics, no one has ever complainedthat a game’s graphics looked too good Your players will appreciate any effort youput into maximizing your visual effects
S PRITES
As discussed in Chapter 4, a sprite is an animated 2D image You can animate sprites
by moving them in the window Also, if your sprite has more than one frame, you canswap frames to animate them
Image Frame Swapping for 2D Animations
Image frame swapping creates an animation similar to old-style page-flipping tions used to create simple cartoons The sprite is animated at run time by adjustingthe texture’s UV coordinates at fixed time intervals Sprites store multiple imageframes because adjusting UV coordinates at run time—to switch image frames—isfaster than switching to a different image file
anima-For 3D games, a 2D sprite offers a very simple way to customize and animate theheads-up display, or a game dashboard This type of sprite could be used to create ananimated radar scope on the console of a flight simulation game
SpriteBatch
For the 2D effect, a sprite object is created with theSpriteBatchclass:
SpriteBatch spriteBatch = new SpriteBatch(GraphicsDevice);
ASpriteBatchobject is actually already included in the XNA template projectcode that is generated using the New Project dialog box For our purposes this is theonly one you will need
Primitive objects are not needed to display the image when usingSpriteBatch
methods As a result, setting up the sprite is easier than setting up textured primitivesurfaces; theSpriteBatchobject draws the image on its own For the 2D object, alldrawing is done between theSpriteBatchobject’sBegin()andEnd()methods
Trang 10The syntax for theDraw()method is designed for drawing on a 2D window The
first parameter references theTexture2Dobject that stores the image file; the
sec-ond parameter references the position, height, and width of a rectangle in the 2D
window; and the third parameter references the starting pixel’s X and Y position in
the image and the height and width—in pixels—to be drawn The fourth parameter
sets the color of the sprite in case you want to shade it differently from the colors
al-ready in the image
Be aware that there are several other overrides forSpriteBatch.Draw();
how-ever, this is the one that we’ll be using for our examples:
If your 3D game uses 2D sprites, you need to be aware of how this will impact your
drawing routines The 2D S p r i t e B a t c h automatically resets the
GraphicsDevice’s render states to draw 2D graphics in the window While this is
helpful, if the settings are not restored to enable 3D rendering, you may not see your
3D graphics—and if you do, they may not display properly Ideally, when rendering
yourSpriteBatchobjects, you should draw them last in theDraw()method so
that they layer on top of the 3D graphics
Trang 11Your graphics device states should be reset to turn off transparency and to able 3D graphics after drawing with theSpriteBatch You must also reset the cull-ing option back to the default used by your game Culling designates the face of anobject that is not drawn, so it should be the face that is not visible to the user Cullingoptions include CullClockwiseFace, CullCounterClockwiseFace, and
re-en-None The base code usesCullMode.Noneto prevent your surfaces from pearing just in case you mistakenly arrange your vertices in the same order as yourculling option However, for performance gains, you definitely will want to cullnonvisible sides of your surfaces when you are rendering large groups of vertices, so
disap-be aware that you have this option
The following code for resetting the state belongs in theDraw()method right ter you draw withSpriteBatch:
af-graphics.GraphicsDevice.RenderState.CullMode = CullMode.None; // no cull graphics.RenderState.DepthBufferEnable = true; // enable 3D on Z graphics.RenderState.AlphaBlendEnable = false; // end transparent graphics.RenderState.AlphaTestEnable = false; // per pixel test
// enable tiling graphics.SamplerStates[0].AddressU = TextureAddressMode.Wrap;
graphics.SamplerStates[0].AddressV = TextureAddressMode.Wrap;
Instead of manually resetting the render states, you can add
SaveStateMode.SaveStateas a parameter for theSpriteBatchobject’sgin()instruction when drawing it This will restore the render states back to theiroriginal settings before the sprite is drawn It’s important to note that this saves andrestores all the render states, so it’s more costly than restoring the render states byhand:
Be-spriteBatch.Begin(SpriteBlendMode.AlphaBlend,
SpriteSortMode.Immediate,SaveStateMode.SaveState);
Rendering Sprites Within the Title Safe Region
of the WindowWhen running games on the Xbox 360, some televisions will only show 80 percent ofthe game window The PC shows 100 percent of the window, so some adjustmentsmay be needed to account for this platform difference Here is a routine that returnsthe bottom-left pixel in the window for drawing aSpriteBatchobject so that it ispositioned properly in the visible region of the window regardless of where the pro-ject runs:
168
Trang 12Rectangle TitleSafeRegion(Texture2D texture, int numFrames){
int windowWidth = Window.ClientBounds.Width;
int windowHeight = Window.ClientBounds.Height;
// some televisions only show 80% of the window
const float UNSAFEAREA = 0.2f;
const float MARGIN = UNSAFEAREA / 2.0f;
// return bounding margins
int top, left, height, width;
left = (int)(windowWidth * MARGIN);
top = (int)(windowHeight * MARGIN);
width = (int)((1.0f - UNSAFEAREA) * windowWidth - texture.Width);
height = (int)((1.0f - UNSAFEAREA) * windowHeight
- texture.Height/numFrames);
return new Rectangle(left, top, width, height);}
I MAGE FRAME ANIMATIONS
TheSpriteBatchclass is limited to drawing images that are flat To perform image
frame swapping inside the 3D world, you must use textured primitives without the
SpriteBatch class to animate your textures Animating textures through image
frame swapping is useful for effects such as flashing signs and blinking lights inside
your world When a sprite is drawn in a 3D environment, the image frames are
swapped at regular intervals by adjusting the UV coordinates
Sprite on the Heads-Up-Display Example
This example animates a two-frame sprite In this example, theSpriteBatchclass
is used to swap frames within the image so that it appears in the 2D game window as
a blinking light Figure 12-1 shows the sprite image on the right and the warning light
animation on the left The two images on the left will be swapped at each interval To
the gamer, the light appears to blink on and off every 0.5 seconds
This example begins with either the MGHWinBaseCode project or the
MGH360BaseCode project found in the BaseCode folder on this book’s website A
Texture2Dobject is used to load and reference the image To try this example, first
add this declaration to the top of the game class:
private Texture2D spriteTexture;
Trang 13A timer is used to trigger the frame change for the sprite, which creates the ing light animation To implement the timer, class-level declarations are required
blink-to sblink-tore the frame number (frameNum), the time spent in the current timer
inter-v a l (i n t e r v a l T i m e), and the time lapse since the last interval(previousIntervalTime):
int frameNum = 1;
private double intervalTime = 0; // time in current interval
private double previousIntervalTime = 0; // interval time at last frame
Next, theTimer()method is added to the methods section to check for the pletion of each 0.5-second interval TheTimer()method calculates the remainder
com-of the amount com-of time since the interval started, divided by 500 milliseconds Whenthe remainder has increased compared to the remainder calculated for the previousframe, the interval is incomplete When the remainder has decreased since the previ-ous frame, a new interval has been entered, and theTimer() method returns a posi-tive result The positive result triggers a frame swap for the sprite Checking theremainders in this manner prevents the variable from growing beyond the variable’sstorage capacity, because it is reset every interval Even though the remainder is usu-ally positive when a new interval is detected, the overshot from the interval start isminiscule, and tracking the remainder makes this algorithm self-correcting In thismanner, theTimer()implements animations that appear to be synchronized withreal time:
170
An animated sprite in the game window
Trang 14bool Timer(GameTime gameTime){
bool resetInterval = false;
// add time lapse between frames and keep value between 0 & 500 ms
The warninglight.png file is also loaded by code into theTexture2Dobject in the
LoadContent()method The warninglight.png file can be obtained from the
Im-ages folder on this book’s website The image needs to be added to the ImIm-ages folder
in your project so it can be loaded by the content pipeline To reference this in your
project, right-click the Images folder in the Solution Explorer, choose Add, and then
select Existing Item A dialog will appear that allows you to navigate to the image and
select it Once the warninglight.png file is selected, it will appear in your project
within the Solution Explorer, and you can then load it with the following instruction:
spriteTexture = Content.Load<Texture2D>("Images\\warninglight");
To ensure that the sprite is positioned properly in the game window, add the
routine that was discussed earlier to retrieve the starting pixel for drawing in the
window:
Rectangle TitleSafeRegion(Texture2D texture, int numFrames){
int windowWidth = Window.ClientBounds.Width;
int windowHeight = Window.ClientBounds.Height;
// some televisions only show 80% of the window
const float UNSAFEAREA = 0.2f;
const float MARGIN = UNSAFEAREA / 2.0f;
// return bounding margins
int top, left, height, width;
left = (int)(windowWidth * MARGIN);
top = (int)(windowHeight * MARGIN);
Trang 15width = (int)((1.0f - UNSAFEAREA) * windowWidth - texture.Width); height = (int)((1.0f - UNSAFEAREA) * windowHeight
- texture.Height/numFrames);
return new Rectangle(left, top, width, height);
}
The next method to add is DrawAnimatedHud() DrawAnimatedHud()
checks the timer to see if the set interval has completed If the timer returns atrue
value—indicating that it just ticked into a new interval—the frame in the image is cremented or reset TheSpriteBatchobject calls theBegin()method to start thedrawing Begin()allows the developer to set theSpriteBlendModeoption tospecify the type of blending This could include:
in- AlphaBlend For removing masked pixels
Additive For summing source and destination colors
None For standard rendering
If you want to remove the transparent pixels, you can use
SpriteBlendMode.AlphaBlendas a parameter in theSpriteBatch’sBegin()
method The picture in the warninglight.png file was created with a transparent ground, so the pixels will not appear when the image is drawn with alpha blending.TheSpriteBatch’sDraw()method applies four parameters The first parame-ter is thegameTimeobject, which is applied to ensure that the animation runs at aconsistent speed The second parameter is the X and Y position for the starting pixelwhere the sprite is to be drawn ATexture2Dobject is passed as a third parameter
back-to allow you back-to set the width and height of the area back-to be drawn The fourth ter is the total number of frames, which allows you to split up the image so it is drawnone section at a time To draw the sprite so it is positioned properly in the window,addDrawAnimatedHud()to your project:
parame-void DrawAnimatedHUD(GameTime gameTime, Vector2 startPixel,
Texture2D texture, int numFrames){
// get width and height of the section of the image to be drawn
int width = texture.Width; // measured in pixels
int height = texture.Height / numFrames; // measured in pixels
if (Timer(gameTime)){
frameNum += 1; // swap image frame frameNum = frameNum%numFrames; // set to 0 after last frame }
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
spriteBatch.Draw(
172