How to: Draw Points, Lines, and Other 3D Primitives
In the XNA Framework, a 3D primitive is a special type of 3D shape that describes how the graphics device interprets vertices stored in a vertex array or vertex stream. This example demonstrates how to use the point, line, and triangle primitive types that are the basis for all low-level drawing calls in the XNA Framework.
Note
To render primitives, it is necessary to create an effect and transformation matrix. Here, the steps described in How to: Use BasicEffect are used to create an instance of BasicEffect. The complete source code including the creation of the BasicEffect object is available at the end of the page (see Complete Example).
To draw a point list
Create a list of vertices in 3D space that represent the points to draw. In this example, eight points along the edge of a circle and the center of the circle are drawn along the plane z = 0. The following code calculates these eight points and stores them in an array of type VertexPositionNormalTexture. This results in an array with the following vertex positions.
double angle = MathHelper.TwoPi / points; pointList = new VertexPositionNormalTexture[points + 1]; pointList[0] = new VertexPositionNormalTexture( Vector3.Zero, Vector3.Forward, Vector2.One); for (int i = 1; i <= points; i++) { pointList[i] = new VertexPositionNormalTexture( new Vector3( (float)Math.Round(Math.Sin(angle * i), 4), (float)Math.Round(Math.Cos(angle * i), 4), 0.0f), Vector3.Forward, new Vector2()); }
The size of a point rendered from a point list is controlled by setting the property RenderState.PointSize. Here, the point size is specified as 10, so at each point in the list a 10-pixel square is drawn.
graphics.GraphicsDevice.RenderState.PointSize = 10;
Render the points by calling DrawUserPrimitives, specifying PrimitiveType.PointList to determine how the data in the vertex array is interpreted.
graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionNormalTexture>( PrimitiveType.PointList, pointList, 0, // index of the first vertex to draw 9 // number of primitives );
To draw a line list
This example uses the sample vertex list created in step 1 of "To draw a point list". Here, an index array is created that indexes into that vertex buffer to identify a series of lines.
// Initialize an array of indices of type short. lineListIndices = new short[(points * 2)]; // Populate the array with references to indices in the vertex buffer for (int i = 0; i < points; i++) { lineListIndices[i * 2] = (short)(i + 1); lineListIndices[(i * 2) + 1] = (short)(i + 2); } lineListIndices[(points * 2) - 1] = 1;
This is equivalent to setting lineListIndices to the following array, consisting of a series of lines between pointList[1] and pointList[2], pointList[2] and pointList[3], and so forth.
lineListIndices = new short[16]{ 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 1 };
Render the lines by calling DrawUserIndexedPrimitives, specifying PrimitiveType.LineList to determine how the data in the vertex array is interpreted.
graphics.GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionNormalTexture>( PrimitiveType.LineList, pointList, 0, // vertex buffer offset to add to each element of the index buffer 9, // number of vertices in pointList lineListIndices, // the index buffer 0, // first index element to read 8 // number of primitives to draw );
To draw a line strip
This example uses the same point list and renders the same output as "To draw a line list", but it uses a line strip primitive type when identifying the indices of the vertex array to draw. Because a line list is used, fewer indices are stored.
Create a list of indices that identify the order in which to draw the points in the specified point list. Here, we need only half the number of indices as were used to draw with a line list, because the data consists of a series of connected lines.
// Initialize an array of indices of type short. lineStripIndices = new short[points + 1]; // Populate the array with references to indices in the vertex buffer. for (int i = 0; i < points; i++) { lineStripIndices[i] = (short)(i + 1); } lineStripIndices[points] = 1;
This is equivalent to setting lineStripIndices to the following array, consisting of a series of connected lines between pointList[1], pointList[2], and pointList[3], and so forth.
lineStripIndices = new short[9]{ 1, 2, 3, 4, 5, 6, 7, 8, 1 };
Render the lines by calling DrawUserIndexedPrimitives, specifying PrimitiveType.LineStrip to determine how the data in the vertex array is interpreted. Note that here there are fewer vertices used to render the same number of primitives rendered when using a line list.
graphics.GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionNormalTexture>( PrimitiveType.LineStrip, pointList, 0, // vertex buffer offset to add to each element of the index buffer 9, // number of vertices to draw lineStripIndices, 0, // first index element to read 8 // number of primitives to draw );
To draw a triangle list
Like a line list, a triangle list is a primitive type that indicates that the vertices in the vertex buffer are to be interpreted as a series of separately drawn triangles.
Create an array to hold the list of indices that identify a series of triangles to draw from the specified point list.
// Initialize an array of indices of type short. triangleListIndices = new short[points * 3]; // Populate the array with references to indices in the vertex buffer for (int i = 0; i < points; i++) { triangleListIndices[i * 3] = 0; triangleListIndices[(i * 3) + 1] = (short)(i + 1); triangleListIndices[(i * 3) + 2] = (short)(i + 2); } triangleListIndices[(points * 3) - 1] = 1;
This is equivalent to setting triangleListIndices to the following array, consisting of a series of triangles between pointList[1], pointList[2], and pointList[3], and so forth.
triangleListIndices = new short[24]{ 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 5, 6, 0, 7, 8, 0, 8, 1 };
Render the lines by calling DrawUserIndexedPrimitives, specifying PrimitiveType.TriangleList to determine how the data in the vertex array is interpreted.
graphics.GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionNormalTexture>( PrimitiveType.TriangleList, pointList, 0, // vertex buffer offset to add to each element of the index buffer 9, // number of vertices to draw triangleListIndices, 0, // first index element to read 8 // number of primitives to draw );
To draw a triangle fan
A triangle fan is a series of triangles that share a single point. This example shows how to render an object that looks the same as the object rendered with a triangle list, but with fewer vertices needed because the triangles share a point.
Create an array to hold the list of indices identifying a series of connected triangles that share a central point. Note here that the center point is the first item identified in the list of indices
// Initialize an array of indices of type short. triangleFanIndices = new short[points + 2]; // Populate the array with references to indices in the vertex buffer. for (int i = 0; i < points + 1; i++) { triangleFanIndices[i] = (short)i; } triangleFanIndices[points + 1] = 1;
This is equivalent to setting triangleFanIndices to the following array, consisting of a series of connected triangles between pointList[1], pointList[2], and pointList[3], and so forth, all sharing one point at the center of the triangle.
triangleFanIndices = new short[10]{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 1 };
Render the lines by calling DrawUserIndexedPrimitives, specifying PrimitiveType.TriangleFan to determine how the data in the vertex array is interpreted. Note that here there are fewer vertices used to render the same number of primitives rendered when using a triangle list.
graphics.GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionNormalTexture>( PrimitiveType.TriangleFan, pointList, 0, // vertex buffer offset to add to each element of the index buffer 9, // number of vertices to draw triangleFanIndices, 0, // first index element to read 8 // number of primitives to draw );
Complete Example
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using Microsoft.Xna.Framework.Content;
public class Game1 : Microsoft.Xna.Framework.Game
{
Matrix viewMatrix;
Matrix projectionMatrix;
BasicEffect basicEffect;
VertexDeclaration vertexDeclaration;
VertexPositionNormalTexture[] pointList;
VertexBuffer vertexBuffer;
PrimitiveType typeToDraw = PrimitiveType.PointList;
int points = 8;
short[] lineListIndices;
short[] lineStripIndices;
short[] triangleListIndices;
short[] triangleFanIndices;
GraphicsDeviceManager graphics;
ContentManager content;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
content = new ContentManager(Services);
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadGraphicsContent(bool loadAllContent)
{
InitializeTransform();
if (loadAllContent)
{
InitializeEffect();
InitializePointList();
InitializeLineList();
InitializeLineStrip();
InitializeTriangleList();
InitializeTriangleFan();
}
}
private void InitializeTransform()
{
viewMatrix = Matrix.CreateLookAt(
new Vector3(0, 0, 5),
Vector3.Zero,
Vector3.Up
);
projectionMatrix = Matrix.CreatePerspectiveFieldOfView(
MathHelper.ToRadians(45),
(float)graphics.GraphicsDevice.Viewport.Width /
(float)graphics.GraphicsDevice.Viewport.Height,
1.0f, 100.0f);
}
private void InitializeEffect()
{
vertexDeclaration = new VertexDeclaration(
graphics.GraphicsDevice,
VertexPositionNormalTexture.VertexElements);
basicEffect = new BasicEffect(graphics.GraphicsDevice, null);
basicEffect.DiffuseColor = new Vector3(1.0f, 1.0f, 1.0f);
basicEffect.View = viewMatrix;
basicEffect.Projection = projectionMatrix;
}
private void InitializePointList()
{
vertexDeclaration = new VertexDeclaration(
graphics.GraphicsDevice,
VertexPositionNormalTexture.VertexElements);
double angle = MathHelper.TwoPi / points;
pointList = new VertexPositionNormalTexture[points + 1];
pointList[0] = new VertexPositionNormalTexture(
Vector3.Zero, Vector3.Forward, Vector2.One);
for (int i = 1; i <= points; i++)
{
pointList[i] = new VertexPositionNormalTexture(
new Vector3(
(float)Math.Round(Math.Sin(angle * i), 4),
(float)Math.Round(Math.Cos(angle * i), 4),
0.0f),
Vector3.Forward,
new Vector2());
}
// Initialize the vertex buffer, allocating memory for each vertex.
vertexBuffer = new VertexBuffer(graphics.GraphicsDevice,
VertexPositionNormalTexture.SizeInBytes * (pointList.Length),
ResourceUsage.None,
ResourceManagementMode.Automatic);
// Set the vertex buffer data to the array of vertices.
vertexBuffer.SetData<VertexPositionNormalTexture>(pointList);
}
private void InitializeLineList()
{
// Initialize an array of indices of type short.
lineListIndices = new short[(points * 2)];
// Populate the array with references to indices in the vertex buffer
for (int i = 0; i < points; i++)
{
lineListIndices[i * 2] = (short)(i + 1);
lineListIndices[(i * 2) + 1] = (short)(i + 2);
}
lineListIndices[(points * 2) - 1] = 1;
}
private void InitializeLineStrip()
{
// Initialize an array of indices of type short.
lineStripIndices = new short[points + 1];
// Populate the array with references to indices in the vertex buffer.
for (int i = 0; i < points; i++)
{
lineStripIndices[i] = (short)(i + 1);
}
lineStripIndices[points] = 1;
}
private void InitializeTriangleList()
{
// Initialize an array of indices of type short.
triangleListIndices = new short[points * 3];
// Populate the array with references to indices in the vertex buffer
for (int i = 0; i < points; i++)
{
triangleListIndices[i * 3] = 0;
triangleListIndices[(i * 3) + 1] = (short)(i + 1);
triangleListIndices[(i * 3) + 2] = (short)(i + 2);
}
triangleListIndices[(points * 3) - 1] = 1;
}
private void InitializeTriangleFan()
{
// Initialize an array of indices of type short.
triangleFanIndices = new short[points + 2];
// Populate the array with references to indices in the vertex buffer.
for (int i = 0; i < points + 1; i++)
{
triangleFanIndices[i] = (short)i;
}
triangleFanIndices[points + 1] = 1;
}
protected override void UnloadGraphicsContent(bool unloadAllContent)
{
if (unloadAllContent == true)
{
content.Unload();
}
}
protected override void Update(GameTime gameTime)
{
// Allows the default game to exit on Xbox 360 and Windows.
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
CheckInput();
base.Update(gameTime);
}
private void CheckInput()
{
KeyboardState newState = Keyboard.GetState();
if (newState.IsKeyDown(Keys.NumPad0))
{
typeToDraw = PrimitiveType.PointList;
}
else if (newState.IsKeyDown(Keys.NumPad1))
{
typeToDraw = PrimitiveType.LineList;
}
else if (newState.IsKeyDown(Keys.NumPad2))
{
typeToDraw = PrimitiveType.LineStrip;
}
else if (newState.IsKeyDown(Keys.NumPad3))
{
typeToDraw = PrimitiveType.TriangleList;
}
else if (newState.IsKeyDown(Keys.NumPad4))
{
typeToDraw = PrimitiveType.TriangleFan;
}
}
protected override void Draw(GameTime gameTime)
{
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
graphics.GraphicsDevice.VertexDeclaration = vertexDeclaration;
// The effect is a compiled effect created and compiled elsewhere
// in the application.
basicEffect.Begin();
foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
{
pass.Begin();
switch (typeToDraw)
{
case PrimitiveType.PointList:
DrawPoints();
break;
case PrimitiveType.LineList:
DrawLineList();
break;
case PrimitiveType.LineStrip:
DrawLineStrip();
break;
case PrimitiveType.TriangleList:
DrawTriangleList();
break;
case PrimitiveType.TriangleFan:
DrawTriangleFan();
break;
}
pass.End();
}
basicEffect.End();
base.Draw(gameTime);
}
private void DrawPoints()
{
graphics.GraphicsDevice.RenderState.PointSize = 10;
graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionNormalTexture>(
PrimitiveType.PointList,
pointList,
0, // index of the first vertex to draw
9 // number of primitives
);
}
private void DrawLineList()
{
graphics.GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionNormalTexture>(
PrimitiveType.LineList,
pointList,
0, // vertex buffer offset to add to each element of the index buffer
9, // number of vertices in pointList
lineListIndices, // the index buffer
0, // first index element to read
8 // number of primitives to draw
);
}
private void DrawLineStrip()
{
graphics.GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionNormalTexture>(
PrimitiveType.LineStrip,
pointList,
0, // vertex buffer offset to add to each element of the index buffer
9, // number of vertices to draw
lineStripIndices,
0, // first index element to read
8 // number of primitives to draw
);
}
private void DrawTriangleList()
{
graphics.GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionNormalTexture>(
PrimitiveType.TriangleList,
pointList,
0, // vertex buffer offset to add to each element of the index buffer
9, // number of vertices to draw
triangleListIndices,
0, // first index element to read
8 // number of primitives to draw
);
}
private void DrawTriangleFan()
{
graphics.GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionNormalTexture>(
PrimitiveType.TriangleFan,
pointList,
0, // vertex buffer offset to add to each element of the index buffer
9, // number of vertices to draw
triangleFanIndices,
0, // first index element to read
8 // number of primitives to draw
);
}
}