JTalton's picture

VBO Example

I wanted to play with vertex arrary buffers so I took a look at the VBO example code.
It only setup a single vertex array and I wanted to know how to specify normals or texture coordinates

I updated it to use a vertex array, normal array, texture array, and color array.

I then started wondering about dynamically updating the VBO so I added some methods to update the VBO from both the source array with the BufferData call and with getting and modifying the data from the VBO using MapBuffer.

I have uploaded the source if anyone is interested or if it would be good to have in the OpenTK examples.

As for performance, what do people suggest for updating an existing VBO? Copying over new data with BufferData or updating the data with MapBuffer? With MapBuffer you get an IntPtr back so I ended up wrapping it in unsafe so that I could work with the data.

AttachmentSize
T08_VBO.cs18.33 KB
Cube.cs2.64 KB

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
nythrix's picture

Nice example there! However, I have a question related to VBOs. How would I go about specifying multiple normals (hard edges) or texcoord sets per vertex while using DrawElements? So far I've come up with the idea of specifying the given vertex multiple times like so:

vertex buffer: V1 V2 V3 V1 V4
normal buffer: N1 N2 N3 N4 N5

so V1 is bound with two different normals during its processing with DrawElements. Any other thoughts?

JTalton's picture

You are correct in that you have to specify the vertex position multiple times, each with their own corresponding normal.

Example:
Cube without hard edges would only need 8 vertices.
Cube with hard edges would need 4 * 6 = 24 vertices so that they could have corresponding normals for each vertex.

Of course this only applies to hard edges.

The same also applies to texture coordinates where the face vertices happen to not be sharing the same texture coordinate.

nythrix's picture

I didn't search for this technique it just came to me. So, I was thinking someone smarter might know more.
It isn't perfect but I'm left with no other choice, I guess.

Thanks for the help.

Alina's picture

Hello !
I started to use opentk and vbo and I tried this example too. But I really don't know how to add for loading normals, colors and textures. Could anyone help me ? Thanks !

the Fiddler's picture

The code in the opening post of this thread shows how to specify normals, textures, etc. A simple class with the necessary functionality distilled:

// Change this struct to add e.g. color data or anything else you need.
struct Vertex
{
    public Vector3 Position, Normal;
    public Vector2 TexCoord;
 
    public static readonly int Stride = Marshal.SizeOf(default(Vertex));
}
 
// As simple as it gets. Usage:
// VertexBuffer vbo  = new VertexBuffer();
// vbo.SetData(new Vertex[] { ... });
// vbo.Render();
// You can make it as fancy as you want (for example, I have an implementation using generics).
sealed class VertexBuffer
{
    int id;
 
    int Id
    {
        get 
        {
            // Create an id on first use.
            if (id == 0)
            {
                GraphicsContext.Assert();
 
                GL.GenBuffers(1, out id);
                if (id == 0)
                    throw new Exception("Could not create VBO.");
            }
 
            return id;
        }
    }
 
    public VertexBuffer()
    { }
 
    public void SetData(Vertex[] data)
    {
        if (data == null)
            throw new ArgumentNullException("data");
 
        GL.BindBuffer(BufferTarget.ArrayBuffer, Id);
        GL.BufferData(BufferTarget.ArrayBuffer, new IntPtr(data.Length * Vertex.Stride), data, BufferUsageHint.StaticDraw);
    }
 
    public void Render()
    {
        GL.EnableClientState(EnableCap.VertexArray);
        GL.EnableClientState(EnableCap.NormalArray);
        GL.EnableClientState(EnableCap.TexCoordArray);
 
        GL.BindBuffer(BufferTarget.ArrayBuffer, Id);
        GL.VertexPointer(3, VertexPointerType.Float, Vertex.Stride, new IntPtr(0));
        GL.NormalPointer(3, NormalPointerType.Float, Vertex.Stride, new IntPtr(Vector3.SizeInBytes));
        GL.TexCoordPointer(2, VertexPointerType.Float, Vertex.Stride, new IntPtr(2 * Vector3.SizeInBytes));
        GL.DrawArrays(BeginMode.Triangles, 0, data.Length);
    }
}

For better performance, you could also create an element array and draw with DrawElements and that's it, more or less.

Edit: Updated the code.

Edit 2: Incorporated fixes from post below.

Alina's picture

Thank you for the code ! It works very well, with minor modifications
( public static readonly Stride = Marshal.SizeOf(default(Vertex)); ---> public static readonly int Stride = Marshal.SizeOf(default(Vertex));
GL.NormalPointer(3, VertexPointerType.Float, Vertex.Stride, new IntPtr(Vector3.SizeInBytes)); ---> GL.NormalPointer(3, NormalPointerType.Float, Vertex.Stride, new IntPtr(Vector3.SizeInBytes)); )
But I have one more problem with GL.DrawArrays(BeginMode.TriangleStrip, 0, data.Length);
It draws the same thing as BeginMode.Triangles and I need triangleStrip mode.
Cheers !

the Fiddler's picture

Thanks, I wrote the code from memory which explains the mistakes. I've updated it with your fixes, in case someone reads it in the future.

I'm not sure I understand what the problem with TriangleStrip is. Can you provide an example (or even better some screenshots)?

tcsavage's picture

Hey Fiddler! Thanks for condensing the VBO code. I looks useful but I'm having a hard time to get it working. After fixing some compiler errors (I assume the API changed between then and 1.0.0 RC1) I can get my application to run but nothing is drawn to the screen from the VBOs. There are no errors from OpenGL or anywhere else. Here is my 'fixed' version:

namespace app
{
	sealed public class VertexBuffer
	{
		private int length;
 
		private int _id;
 
		int id {
			get {
				// Create an id on first use.
				if (_id == 0) {
					GraphicsContext.Assert ();
 
					GL.GenBuffers (1, out _id);
					if (_id == 0)
						throw new Exception ("Could not create VBO.");
				}
				return _id;
			}
		}
 
		public VertexBuffer ()
		{
		}
 
		public void SetData (Vertex[] data)
		{
			if (data == null) {
				throw new ArgumentNullException ("data");
			}
 
			System.Console.WriteLine(data.Length);
 
			this.length = data.Length;
			GL.BindBuffer (BufferTarget.ArrayBuffer, id);
			GL.BufferData (BufferTarget.ArrayBuffer, new IntPtr (data.Length * Vertex.stride), data, BufferUsageHint.StaticDraw);
		}
 
		public void Render ()
		{
			GL.EnableClientState (ArrayCap.VertexArray);
			GL.EnableClientState (ArrayCap.NormalArray);
			GL.EnableClientState (ArrayCap.TextureCoordArray);
 
			GL.BindBuffer (BufferTarget.ArrayBuffer, id);
			GL.VertexPointer (3, VertexPointerType.Float, Vertex.stride, new IntPtr (0));
			GL.NormalPointer (NormalPointerType.Float, Vertex.stride, new IntPtr (Vector3.SizeInBytes));
			GL.TexCoordPointer (2, TexCoordPointerType.Float, Vertex.stride, new IntPtr (2 * Vector3.SizeInBytes));
			GL.DrawArrays (BeginMode.Triangles, 0, this.length);
		}
	}
}

One slight difference is that my Vertex struct has the Serializable attribute. Otherwise, it's the same.

Can you verify this as correct or suggest anything I might be doing wrong?