These pages of the book discuss how to define, reference and draw geometric Objects using OpenGL.
Focus is on storing the Geometry directly in Vertex Buffer Objects (VBO), for using Immediate Mode please refer to the red book.
A Vertex (pl. Vertices) specifies a number of Attributes associated with a single Point in space. In the fixed-function environment a Vertex commonly includes Position, Normal, Color and/or Texture Coordinates. The only Attribute that is not optional and must be specified is the Vertex's Position, usually consisting of 3 float.
In Shader Program driven rendering it is also possible to specify custom Vertex Attributes which are previously unknown to OpenGL, such as Radius or Bone Index and Weight for Skeletal Animation. For the sake of simplicity we'll re-create one of the Vertex formats OpenGL already knows, namely InterleavedArrayFormat.T2fN3fV3f. This format contains 2 float for Texture Coordinates, 3 float for the Normal direction and 3 float to specify the Position.
Thanks to the included Math-Library in OpenTK, we're allowed to specify an arbitrary Vertex struct for our requirements, which is much more elegant to handle than a float[] array.
[StructLayout(LayoutKind.Sequential)] struct Vertex { // mimic InterleavedArrayFormat.T2fN3fV3f public Vector2 TexCoord; public Vector3 Normal; public Vector3 Position; }
This leads to a Vertex consisting of 8 float, or 32 byte. We can now declare an Array of Vertices to describe multiple Points and allow easy indexing/referencing them.
Vertex[] Vertices;
The Vertex-Array Vertices can now be created and filled with data. Addressing elements is as convenient as in the following example:
Vertices = new Vertex[ n ]; // -1 < i < n (Remember that arrays start at Index 0 and end at Index n-1.) // examples how to assign values to the Vector's components: Vertices[ i ].Position = new Vector3( 2f, -3f, .4f ); // create a new Vector and copy it to Position. Vertices[ i ].Normal = Vector3.UnitX; // this will copy Vector3.UnitX into the Normal Vector. Vertices[ i ].TexCoord.X = 0.5f; // the Vectors are structs, so the new keyword is not required. Vertices[ i ].TexCoord.Y = 1f; // Ofcourse this also works the other way around, using the Vectors as the source. Vector2 UV = Vertices[ i ].TexCoord;
An Index is simply a byte, ushort or uint, referencing an element in the Vertices Array. So if we decide to draw a single Vertex 100 times at the same spot, instead of storing 100 times the same Vertex in Vertices, we can reference it 100 times from the Indices Array:
uint[] Indices;
Basically the Indices Array is used to describe the primitives and the Vertex Array is used to declare the corner points.
We can also use collections to store our Vertices, but it's recommended you stick with a simple Array to make sure your Indices are valid at all times.
Now the Vertices and Indices Arrays can be used to describe the edges of any Geometric Pritimitve Type.
Once the Arrays are filled with data it can be drawn in Immediate Mode, as Vertex Array or sent into a Vertex Buffer Object.
OpenGL requires you to specify the Geometric Primitive Type of the Vertices you wish to draw. This is usually expected when you begin drawing in either Immediate Mode (GL.Begin), GL.DrawArrays or GL.DrawElements.

Fig. 1: In the above graphic all valid Geometric Primitive Types are shown, their winding is Clockwise (irrelevant for Points and Lines).
This is important, because drawing a set of Vertices as Triangles, which are internally set up to be used with Quads, will result only in garbage being displayed.
Examine Figure 1, you will see that v3 in a Quad is used to finish the shape, while Triangles uses v3 to start the next shape. The next drawn Triangle will be v3, v4, v5 which isn't something that belongs to any surface, if the Vertices were originally intended to be drawn as Quads.
However Points and Lines are an Exception here. You can draw every other Geometric Primitive Type as Points, in order to visualize the Vertices of the Object. Some more possibilities are:
The smallest common denominator for all filled surfaces (i.e. no Points or Lines) is the Triangle. This Geometric Primitive Type has the special attribute of always being planar and is currently the best way to describe a 3D Object to GPU hardware.
While OpenGL allows to draw Quads or Polygons aswell, it is quite easy to run into lighting problems if the surface is not perfectly planar. Internally, OpenGL breaks Quads and Polygons into Triangles, in order to rasterize them.
When looking at the graphic, Triangle- and Quad-strips might look quite appealing due to their low memory usage. They are beneficial for certain tasks, but Triangles are the best primitive type to represent an arbitrary mesh, because it's not restricting locality and allows further optimizations. It's just not realistic that you can have all your 3D Objects in Quads and OpenGL will split them internally into Triangles anyway. 3 ushort per Triangle isn't much memory, and still allows to index 64k unique Vertex in a mesh, the number of Triangles can be much higher. Don't hardwire BeginMode.Triangles into your programs though, for example Quads are very commonly used in orthographic drawing of UI Elements such as Buttons, Text or Sprites.
Should TriangleStrip get an core/ARB command to start a new strip within the begin/end block (only nVidia driver has such an Extension to restart the primitive) this might change, but currently the smaller data structure of the strip does not make up for the performance gains a Triangle List gets from Vertex Cache optimization. Ofcourse you can experiment with the GL.MultiDraw Extension mentioned above, but using it will break using other Extensions such as DirectX 10 instancing.
Introduction
The advantage of VBO (Vertex Buffer Objects) is that we can tell OpenGL to store information used for drawing - like Position, Colors, Texture Coordinates and Normals - directly in the Video-card's Memory, rather than storing it in System Memory and pass it to the graphics Hardware every time we wish to draw it. While this has been already doable with Display Lists before, VBO has the advantage that we're able to retrieve a Pointer to the data in Video Memory and read/write directly to it, if necessary. This can be a huge performance boost for dynamic meshes and is for years the best overall solution for storing - both, static and dynamic - Meshes.
Creation
Handling VBOs is very similar to handling Texture objects, we can generate&delete handles, bind them or fill them with data. For this tutorial we will need 2 objects, one VBO containing all Vertex information (Texture, Normal and Position in this example case) and an IBO (Index Buffer Object) referencing Vertices from the VBO to form Triangles. This has the advantage that, when we have uploaded the data to the VBO/IBO later on, we can draw the whole mesh with a single GL.DrawElements call.
First we acquire two Objects to use:
uint[] VBOid = new uint[ 2 ]; GL.GenBuffers( 2, out VBOid );
Although it is unlikely, OpenGL could complain that it ran out of memory or that the extension is not supported, it should be checked with GL.GetError. If everything went smooth we have 2 objects to work with available now.
Delete
The OpenGL driver should clean up all our mess when it deletes the render context, it's always a good idea to clean up on your own where you can. We remove the objects we reserved at the buffer creation by calling:
GL.DeleteBuffers( 2, ref VBOid );
Binding
To select which object you currently want to work with, simply bind the handle to either BufferTarget.ArrayBuffer or BufferTarget.ElementArrayBuffer. The first is used to store position, uv, normals, etc. (named VBO) and the later is pointing at those vertices to define geometry (named IBO).
GL.BindBuffer( BufferTarget.ArrayBuffer, VBOid[ 0 ] ); GL.BindBuffer( BufferTarget.ElementArrayBuffer, VBOid[ 1 ] );
It is not required to bind a buffer to both targets, for example you could store only the vertices in the VBO and keep the indices in system memory. Also, the two objects are not tied together in any way, for example you could build different triangle lists for BufferTarget.ElementArrayBuffer to implement LOD on the same set of vertices, simply by binding the desired element array.
Theres two important things to keep in mind though:
1) While working with VBOs, GL.EnableClientState(EnableCap.VertexArray); must be enabled. if using Normals, GL.EnableClientState(EnableCap.NormalArray), just like classic Vertex Arrays.
2) All Vertex Array related commands will be used on the currently bound objects until you explicitly bind zero '0' to disable hardware VBO.
GL.BindBuffer( BufferTarget.ArrayBuffer, 0 ); GL.BindBuffer( BufferTarget.ElementArrayBuffer, 0 );
Passing Data
There are several ways to fill the object's data, we will focus on using GL.BufferData and directly writing to video memory. The third option would be GL.BufferSubData which is quite straightforward to use once you are familiar with GL.BufferData.
We make sure the correct object is bound (it is not required to do this, if the buffer is already bound. Just here to clarify on which object we currently work on)
GL.BindBuffer( BufferTarget.ElementArrayBuffer, VBOid[ 1 ] );
In the example application ushort has been used for Indices, because 16 Bits [0..65535] are more available Vertices than used by most real-time rendered meshes, however the mesh could index way more Vertices using a type like uint. Using ushort, OpenGL will store this data as 2 Bytes per index, saving memory compared to a 4 Bytes UInt32 per index.
The function GL.BufferData's first parameter is the target we want to use, the second is the amount of memory (in bytes) we need allocated to hold all our data. The third parameter is pointing at the data we wish to send to the graphics card, this can be IntPtr.Zero and you may send the data at a later stage with GL.MapBuffer (more about this later). The last parameter is an optimization hint for the driver, it will place your data in the best suited place for your purposes.
GL.BufferData( BufferTarget.ElementArrayBuffer, (IntPtr) ( Indices.Length * sizeof( ushort ) ), Indices, BufferUsageHint.StaticDraw );
That's all, OpenGL now has a copy of Indices available and we could dispose the array, assuming we have the Index Count of the array stored in a variable for the draw call later on.
Now that we've stored the indices in an IBO, the Vertices are next. Again, we make sure the binding is correct, give a pointer to the Vertex count, and finally the usage hint.
GL.BindBuffer( BufferTarget.ArrayBuffer, VBOid[ 0 ] ); GL.BufferData( BufferTarget.ArrayBuffer, (IntPtr) ( Vertices.Length * 8 * sizeof( float ) ), Vertices, BufferUsageHint.StaticDraw );
There's a table at the bottom of this page, explaining the options in the enum BufferUsageHint in more detail.
While the first described technique to pass data into the objects required a copy of the data in system memory, this alternative will give us a pointer to the video memory reserved by the object. This is useful for dynamic models that have no copy in client memory that could be used by GL.BufferData, since you wish to rebuild it every single frame (e.g. fully procedural objects, particle system).
First we make sure that we got the desired object bound and reserve memory, the pointer towards the Indices is actually IntPtr.Zero, because we only need an empty buffer.
GL.BindBuffer( BufferTarget.ElementArrayBuffer, VBOid[ 0 ] ); GL.BufferData( BufferTarget.ElementArrayBuffer, (IntPtr) ( Indices.Length * sizeof( ushort ) ), IntPtr.Zero, BufferUsageHint.StaticDraw );
Note that you should change BufferUsageHint.StaticDraw properly according to what you intend to do with the Data, there's a table at the bottom of this page. Now we're able to request a pointer to the video memory.
IntPtr VideoMemoryIntPtr = GL.MapBuffer(BufferTarget.ElementArrayBuffer, BufferAccess.WriteOnly);
Valid access flags for the pointer are BufferAccess.ReadOnly, BufferAccess.WriteOnly or BufferAccess.ReadWrite, which help the driver understand what you're going to do with the data. Note that the data's object is locked until we unmap it, so we want to keep the timespan over which we use the pointer as short as possible. We may now write some data into the buffer, once we're done we must release the lock.
unsafe { fixed ( ushort* SystemMemory = &Indices[0] ) { ushort* VideoMemory = (ushort*) VideoMemoryIntPtr.ToPointer(); for ( int i = 0; i < Indices.Length; i++ ) VideoMemory[ i ] = SystemMemory[ i ]; // simulate what GL.BufferData would do } } GL.UnmapBuffer( BufferTarget.ElementArrayBuffer );
The pointer is now invalid and may not be stored for future use, if we wish to modify the object again, we have to call GL.MapBuffer again.
Further reading
Visit this link in order to tell OpenGL about the composition of your Vertex data, and this link for drawing the data.
Optimization:
One hint from the nVidia whitepaper was regarding the situation, if we want to update all data in the buffer object by using GL.MapBuffer and not retrieve any of the old data. Although this is a bad idea, because mapping the buffer is a more expensive operation than just calling GL.BufferData, it might be necessary in cases where you have no copy of the data in system memory, but build it on the fly. The solution to making this somewhat efficient is first calling GL.BufferData with a IntPtr.Zero again, which tells the driver that the old data isn't valid anymore. Calling GL.MapBuffer will return a new pointer to a valid memory location of the requested size to write to, while the old data will be cleaned up once it's not used in any draw operations anymore.
Also note that either reading from a VBO or wrapping it into a Display List is very slow and should both be avoided.
Table 1:
BufferUsageHint.Static... Assumed to be a 1-to-n update-to-draw. Means the data is specified once (during initialization).
BufferUsageHint.Dynamic... Assumed to be a n-to-n update-to-draw. Means the data is drawn multiple times before it changes.
BufferUsageHint.Stream... Assumed to be a 1-to-1 update-to-draw. Means the data is very volatile and will change every frame.
...Draw Means the buffer will be used to sending data to GPU. video memory (Static|StreamDraw) or AGP (DynamicDraw)
...Read Means the data must be easy to access, will most likely be system or AGP memory.
...Copy Means we are about to do some ..Read and ..Draw operations.
Setting Strides and Offsets for Vertex Arrays and VBO
There are 2 ways to tell OpenGL in which layout the Vertices are stored:
GL.InterleavedArrays( InterleavedArrayFormat.T2fN3fV3f, 0, null );
This command has the advantage that it's very obvious to the OpenGL driver what layout of data we have supplied, and it may be possible for the driver to optimize the memory. Remember that GL.InterleavedArrays will change states, if you manually disable EnableCap.VertexArray, EnableCap.NormalArray, EnableCap.TextureCoordArray or changing GL.VertexPointer, GL.NormalPointer or GL.TexCoordPointer (after calling GL.InterleavedArrays and before calling GL.DrawElements) make sure to enable them again or you won't see anything.
GL.TexCoordPointer( 2, TexCoordPointerType.Float, 8 * sizeof( float ), (IntPtr) ( 0 ) ); GL.NormalPointer( NormalPointerType.Float, 8 * sizeof( float ), (IntPtr) ( 2 * sizeof( float ) ) ); GL.VertexPointer( 3, VertexPointerType.Float, 8 * sizeof( float ), (IntPtr) ( 5 * sizeof( float ) ) );
Byte 0-7 are used for the Texture Coordinates, Byte 8-19 for the Normal and Byte 20-31 for the Vertex Position.
Vertex Arrays were removed from OpenGL in version 3.1. Vertex Buffer Objects are the recommended alternative, since they are both faster and safer.
This is the correct way to use Vertex Arrays in .Net (pseudocode):
struct Vertex { public Vector3 Position; public Vector2 TexCoord; } Vertex[] vertices = new Vertex[100]; unsafe { fixed (float* pvertices = vertices) { GL.VertexPointer(3, VertexPointerType.Float, BlittableValueType.StrideOf(vertices), pvertices); GL.TexCoordPointer(2, VertexPointerType.Float, BlittableValueType.StrideOf(vertices), pvertices + sizeof(Vector3)); GL.DrawArrays(BeginMode.Triangles, 0, vertices.Length); GL.Finish(); // Force OpenGL to finish rendering while the arrays are still pinned. } }
Vertex Arrays use client storage, because they are stored in system memory (not video memory). Since .Net is a Garbage Collected environment, the arrays must remain pinned until the GL.DrawArrays() or GL.DrawElements() call is complete.
If the arrays are unpinned prematurely, they may be moved or collected by the Garbage Collector before the draw call finishes. This will lead to random access violation exceptions and corrupted rendering, issues which can be difficult to trace.
Due to the asynchronous nature of OpenGL, GL.Finish() must be used to ensure that rendering is complete before the arrays are unpinned. However, this call introduces a sync point between the CPU and GPU, which can significantly degrade performance.
Vertex Buffer Objects and Display Lists use server storage (video memory) which does not suffer from this issue. Given the improved performance and safety of server storage, it is recommended to avoid Vertex Arrays completely.
Please visit this discussion in the forum for more information.
Vertex Array Objects (abbreviation: VAO) are storing vertex attribute setup and VBO related state. This allows to reduce the number of OpenGL calls when drawing from VBOs, because all attribute declaration and pointer setup only needs to be done once (ideally at initialization time) and is afterwards stored in the VAO for later re-use.
But before a VAO can be bound, a handle must be generated:
void GL.GenVertexArrays( uint[] ); void GL.DeleteVertexArrays( uint[] ); bool GL.IsVertexArray( uint );
Binding a VAO is as simple as
void GL.BindVertexArray( uint );
The currently bound VAO records state set by the following commands:
GL.EnableVertexAttribArray() GL.DisableVertexAttribArray() GL.VertexAttribPointer() GL.VertexAttribIPointer()
Indirectly it also saves the state set by GL.BindBuffer() at the point of time when GL.VertexAttribPointer() was called. A more technical description can be found at the OpenGL Wiki.
An example usage:
Initialization:
uint VboHandle; uint VaoHandle; GL.GenVertexArrays(1, out VaoHandle); GL.GenBuffers(1, out VboHandle); GL.BindVertexArray(VaoHandle); //Make sure to call BindVertexArray() before BindBuffer() GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle); GL.BufferData(...); GL.EnableClientState(...); GL.VertexPointer(...); GL.EnableVertexAttribArray(...); GL.BindVertexArray(0);
Drawing:
GL.BindVertexArray(VaoHandle); ///Drawing code here GL.BindVertexArray(0);
In order to tell OpenGL to draw primitives for us, there's basically two ways to go:
1. Immediate Mode, as in specifying every single Vertex manually.
2. Vertex Buffers (or Vertex Arrays), drawing a whole Mesh with a single Command.
In order for all GL.Draw*-Functions to output geometric Primitives, EnableCap.VertexArray must be enabled first.
// GL.DrawArrays behaviour GL.Begin( BeginMode.Points ); for ( uint i = 0; i < Vertices.Length; i++ ) { GL.TexCoord2( Vertices[ i ].TexCoord ); GL.Normal3( Vertices[ i ].Normal ); GL.Vertex3( Vertices[ i ].Position ); } GL.End( ); // GL.DrawElements behaviour GL.Begin( BeginMode.Points ); for ( uint i = 0; i < Indices.Length; i++ ) { GL.TexCoord2( Vertices[ Indices[ i ] ].TexCoord ); GL.Normal3( Vertices[ Indices[ i ] ].Normal ); GL.Vertex3( Vertices[ Indices[ i ] ].Position ); } GL.End( );
GL.DrawArrays( BeginMode.Points, 0, Vertices.Length );
byte, ushort, uint) to Index the Vertex Array. This is particularly useful for 3D Models where the Triangles describing the surface share Edges and Vertices.
GL.DrawElements( BeginMode.TriangleStrip, Indices.Length, DrawElementsType.UnsignedInt, Indices );
// behaviour equal to GL.DrawElements GL.DrawRangeElements( BeginMode.TriangleStrip, 0, Indices.Length-1, Indices.Length, DrawElementsType.UnsignedInt, Indices );
for (int gl_InstanceID=0; gl_InstanceID < primcount; gl_InstanceID++ ) GL.DrawArrays( BeginMode, First, Length );
gl_InstanceID is a uniform variable available to the Vertex Shader, which (in conjunction with GL.UniformMatrix) can be used to assign each Instance drawn it's own unique Orientation Matrix.
for (int gl_InstanceID=0; gl_InstanceID < primcount; gl_InstanceID++ ) GL.DrawElements( BeginMode, Length, DrawElementsType, Object );
gl_InstanceID is a uniform variable available to the Vertex Shader, which (in conjunction with GL.UniformMatrix) can be used to assign each Instance drawn it's own unique orientation Matrix.
Extension References
http://www.opengl.org/registry/specs/EXT/draw_range_elements.txt
http://www.opengl.org/registry/specs/EXT/draw_instanced.txt
http://www.opengl.org/registry/specs/EXT/multi_draw_arrays.txt
This page is just giving a starting point for optimizations, the links below provide more in-depth information.
Links:
http://www.mesa3d.org/brianp/sig97/perfopt.htm
http://ati.amd.com/developer/SDK/AMD_SDK_Samples_May2007/Documentations/...
http://developer.nvidia.com/object/gpu_programming_guide.html
http://www.opengl.org/pipeline/article/vol003_8/
http://developer.apple.com/graphicsimaging/opengl/
Last edit of Links: March 2008