How to: Draw a Textured Quad
This example demonstrates how to create and draw a simple quad using DrawUserIndexedPrimitives.
A quad is two triangles that form a rectangle or square. This sample introduces the Quad class, which constructs a quad with a list of vertices and indices suitable for drawing with DrawUserIndexedPrimitives. This sample also demonstrates how to use BasicEffect to apply a texture to the primitive.
Note
To render the quad, this sample uses the BasicEffect class. For a discussion of BasicEffect see How to: Use BasicEffect.
To create a Quad
First, determine the location of each of the four vertices in the quad. The Quad constructor calculates the four corners given the origin, width, height and facing information supplied by the caller.
public Quad( Vector3 origin, Vector3 normal, Vector3 up, float width, float height )
{ Vertices = new VertexPositionNormalTexture[4]; Indices = new int[6]; Origin = origin; Normal = normal; Up = up;
// Calculate the quad corners
Left = Vector3.Cross( normal, Up );
Vector3 uppercenter = (Up * height / 2) + origin;
UpperLeft = uppercenter + (Left * width / 2);
UpperRight = uppercenter - (Left * width / 2);
LowerLeft = UpperLeft - (Up * height);
LowerRight = UpperRight - (Up * height);
FillVertices();
}
Second, determine the UV coordinates for the texture you want to apply to the quad. The Quad constructor calls FillVertices, which assigns UV coordinates ranging from (0,0) to (1,1). This will make the entire texture appear on the quad.
private void FillVertices()
{ // Fill in texture coordinates to display full texture // on quad Vector2 textureUpperLeft = new Vector2( 0.0f, 0.0f ); Vector2 textureUpperRight = new Vector2( 1.0f, 0.0f ); Vector2 textureLowerLeft = new Vector2( 0.0f, 1.0f ); Vector2 textureLowerRight = new Vector2( 1.0f, 1.0f );
Third, determine the normals for your vertices. FillVertices copies the normal vector provided to the constructor to each vertex.
// Provide a normal for each vertex
for (int i = 0; i < Vertices.Length; i++) { Vertices[i].Normal = Normal; }
Fourth, copy the position and texture coordinate data to your vertex array.
Lastly, fill out the index buffer to determine what order your vertices are drawn by the graphics hardware. Drawing two triangles requires four vertices, but six index entries if you are using PrimitiveType.TriangleList. The indices are specified in clockwise order - because XNA is a right-handed system, triangles drawn in counter-clockwise order are assumed to be facing away from the camera, and are automatically culled by default.
// Set the index buffer for each vertex, using // clockwise winding Indices[0] = 0; Indices[1] = 1; Indices[2] = 2; Indices[3] = 2; Indices[4] = 1; Indices[5] = 3;
}
To draw a Quad
In your game's Initialize method, create a new Quad object specifying the location, the normal (the direction the Quad faces), the Up vector, and the width and height of the quad. You can also create a View and Projection matrix to use when rendering.
Quad quad;
VertexDeclaration quadVertexDecl;
Matrix View, Projection;
protected override void Initialize()
{
// TODO: Add your initialization logic here
quad = new Quad( Vector3.Zero, Vector3.Backward, Vector3.Up, 1, 1 );
View = Matrix.CreateLookAt( new Vector3( 0, 0, 2 ), Vector3.Zero, Vector3.Up );
Projection = Matrix.CreatePerspectiveFieldOfView( MathHelper.PiOver4, 4.0f / 3.0f, 1, 500 );
base.Initialize();
}
In your game's LoadGraphicsContent method, load the texture you want to apply to the quad using the ContentManager, and create an initialize a new BasicEffect object. Set TextureEnabled to true and assign the texture to draw to the Texture property.
Texture2D texture;
BasicEffect quadEffect; protected override void LoadGraphicsContent( bool loadAllContent ) { if (loadAllContent) { // TODO: Load any ResourceManagementMode.Automatic content texture = content.Load<Texture2D>( "Glass" ); quadEffect = new BasicEffect( graphics.GraphicsDevice, null ); quadEffect.EnableDefaultLighting();
quadEffect.World = Matrix.Identity;
quadEffect.View = View;
quadEffect.Projection = Projection;
quadEffect.TextureEnabled = true;
quadEffect.Texture = texture;
}</pre>
Also in LoadGraphicsContent, create a VertexDeclaration for the vertex type used to define the quad - in this case, VertexPositionNormalTexture.
// TODO: Load any ResourceManagementMode.Manual content quadVertexDecl = new VertexDeclaration(graphics.GraphicsDevice, VertexPositionNormalTexture.VertexElements );
}
In your game's Draw method, set the VertexDeclaration on the GraphicsDevice to the VertexDeclaration object you created in LoadGraphicsContent.
protected override void Draw( GameTime gameTime )
{ graphics.GraphicsDevice.Clear( Color.CornflowerBlue );
graphics.GraphicsDevice.VertexDeclaration = quadVertexDecl;</pre>
Call Begin on the effect, then in each EffectPass use DrawUserIndexedPrimitives to draw the quad. Use the Vertices and Indices properties on the Quad structure to supply the primitive data, and specify two triangles to draw. Then End the effect.
quadEffect.Begin(); foreach (EffectPass pass in quadEffect.CurrentTechnique.Passes) { pass.Begin(); graphics.GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionNormalTexture>( PrimitiveType.TriangleList, quad.Vertices, 0, 4, quad.Indices, 0, 2 ); pass.End(); } quadEffect.End();
}
The Complete Example
public struct Quad
{
public Vector3 Origin;
public Vector3 UpperLeft;
public Vector3 LowerLeft;
public Vector3 UpperRight;
public Vector3 LowerRight;
public Vector3 Normal;
public Vector3 Up;
public Vector3 Left;
public VertexPositionNormalTexture[] Vertices;
public int[] Indices;
public Quad( Vector3 origin, Vector3 normal, Vector3 up, float width, float height )
{
Vertices = new VertexPositionNormalTexture[4];
Indices = new int[6];
Origin = origin;
Normal = normal;
Up = up;
// Calculate the quad corners
Left = Vector3.Cross( normal, Up );
Vector3 uppercenter = (Up * height / 2) + origin;
UpperLeft = uppercenter + (Left * width / 2);
UpperRight = uppercenter - (Left * width / 2);
LowerLeft = UpperLeft - (Up * height);
LowerRight = UpperRight - (Up * height);
FillVertices();
}
private void FillVertices()
{
// Fill in texture coordinates to display full texture
// on quad
Vector2 textureUpperLeft = new Vector2( 0.0f, 0.0f );
Vector2 textureUpperRight = new Vector2( 1.0f, 0.0f );
Vector2 textureLowerLeft = new Vector2( 0.0f, 1.0f );
Vector2 textureLowerRight = new Vector2( 1.0f, 1.0f );
// Provide a normal for each vertex
for (int i = 0; i < Vertices.Length; i++)
{
Vertices[i].Normal = Normal;
}
// Set the position and texture coordinate for each
// vertex
Vertices[0].Position = LowerLeft;
Vertices[0].TextureCoordinate = textureLowerLeft;
Vertices[1].Position = UpperLeft;
Vertices[1].TextureCoordinate = textureUpperLeft;
Vertices[2].Position = LowerRight;
Vertices[2].TextureCoordinate = textureLowerRight;
Vertices[3].Position = UpperRight;
Vertices[3].TextureCoordinate = textureUpperRight;
// Set the index buffer for each vertex, using
// clockwise winding
Indices[0] = 0;
Indices[1] = 1;
Indices[2] = 2;
Indices[3] = 2;
Indices[4] = 1;
Indices[5] = 3;
}
}
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
ContentManager content;
public Game1()
{
graphics = new GraphicsDeviceManager( this );
content = new ContentManager( Services );
}
Quad quad;
VertexDeclaration quadVertexDecl;
Matrix View, Projection;
protected override void Initialize()
{
// TODO: Add your initialization logic here
quad = new Quad( Vector3.Zero, Vector3.Backward, Vector3.Up, 1, 1 );
View = Matrix.CreateLookAt( new Vector3( 0, 0, 2 ), Vector3.Zero, Vector3.Up );
Projection = Matrix.CreatePerspectiveFieldOfView( MathHelper.PiOver4, 4.0f / 3.0f, 1, 500 );
base.Initialize();
}
Texture2D texture;
BasicEffect quadEffect;
protected override void LoadGraphicsContent( bool loadAllContent )
{
if (loadAllContent)
{
// TODO: Load any ResourceManagementMode.Automatic content
texture = content.Load<Texture2D>( "Glass" );
quadEffect = new BasicEffect( graphics.GraphicsDevice, null );
quadEffect.EnableDefaultLighting();
quadEffect.World = Matrix.Identity;
quadEffect.View = View;
quadEffect.Projection = Projection;
quadEffect.TextureEnabled = true;
quadEffect.Texture = texture;
}
// TODO: Load any ResourceManagementMode.Manual content
quadVertexDecl = new VertexDeclaration(graphics.GraphicsDevice,
VertexPositionNormalTexture.VertexElements );
}
protected override void Draw( GameTime gameTime )
{
graphics.GraphicsDevice.Clear( Color.CornflowerBlue );
// TODO: Add your drawing code here
DrawQuad();
base.Draw( gameTime );
}
private void DrawQuad()
{
graphics.GraphicsDevice.VertexDeclaration = quadVertexDecl;
quadEffect.Begin();
foreach (EffectPass pass in quadEffect.CurrentTechnique.Passes)
{
pass.Begin();
graphics.GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionNormalTexture>(
PrimitiveType.TriangleList, quad.Vertices, 0, 4, quad.Indices, 0, 2 );
pass.End();
}
quadEffect.End();
}