#region --- License ---
/* Copyright (c) 2006, 2007 Stefanos Apostolopoulos
* See license.txt for license info
*/
#endregion
#region --- Using directives ---
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Threading;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Platform;
using OpenTK.Math;
using Examples.Shapes;
using System.Drawing;
using System.Drawing.Imaging;
#endregion
namespace Examples.Tutorial
{
///
/// Generates Array Buffers Objects for Vertices, Normals, TexCoords, Colors, and primitive Indices.
/// The uses DrawElements to draw the primitives from the Array Buffers.
/// Also shows an example of dynamic updating of VBOs.
///
[Example("Vertex Buffer Objects", ExampleCategory.Tutorial, 8, false)]
public class T08_VBO : GameWindow
{
#region --- Private Fields ---
Shape shape = new Cube();
Vbo vbo = new Vbo();
int textureID;
TextureFont font = new TextureFont(new System.Drawing.Font(System.Drawing.FontFamily.GenericSansSerif, 14.0f));
ITextPrinter textPrinter = new TextPrinter();
// Flag to indicate it the Array Buffer should dynamically update
bool dynamicUpdate = true;
#endregion
#region --- Constructor ---
public T08_VBO() : base(800, 600)
{
Keyboard.KeyDown += new OpenTK.Input.KeyDownEvent(Keyboard_KeyDown);
}
void Keyboard_KeyDown(OpenTK.Input.KeyboardDevice sender, OpenTK.Input.Key key)
{
switch (key)
{
// Lighting
case OpenTK.Input.Key.L:
if (GL.IsEnabled(EnableCap.Lighting)) GL.Disable(EnableCap.Lighting);
else GL.Enable(EnableCap.Lighting);
break;
// Texture
case OpenTK.Input.Key.T:
if (GL.IsEnabled(EnableCap.Texture2D)) GL.Disable(EnableCap.Texture2D);
else GL.Enable(EnableCap.Texture2D);
break;
// Dynamic Update
case OpenTK.Input.Key.D:
dynamicUpdate = !dynamicUpdate;
break;
// Exit
case OpenTK.Input.Key.Escape:
Exit();
break;
}
}
#endregion
#region OnLoad override
public override void OnLoad(EventArgs e)
{
base.OnLoad(e);
if (!GL.SupportsExtension("VERSION_1_5"))
{
System.Windows.Forms.MessageBox.Show("You need at least OpenGL 1.5 to run this example. Aborting.", "VBOs not supported",
System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Exclamation);
Exit();
}
GL.ClearColor(0.1f, 0.1f, 0.5f, 0.0f);
GL.Enable(EnableCap.DepthTest);
// Vertex Buffers
vbo = LoadVBO(shape);
// Lighting
GL.Enable(EnableCap.Light0);
GL.Enable(EnableCap.Lighting);
// Texture
GL.Hint(HintTarget.PerspectiveCorrectionHint, HintMode.Nicest);
GL.GenTextures(1, out textureID);
GL.BindTexture(TextureTarget.Texture2D, textureID);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
Bitmap bitmap = new Bitmap("Data/logo.jpg");
BitmapData data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height),
ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
{
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, data.Width, data.Height, 0,
OpenTK.Graphics.PixelFormat.Bgra, PixelType.UnsignedByte, data.Scan0);
}
bitmap.UnlockBits(data);
GL.Enable(EnableCap.Texture2D);
}
#endregion
#region OnResize override
protected override void OnResize(ResizeEventArgs e)
{
GL.Viewport(0, 0, Width, Height);
double ratio = e.Width / (double)e.Height;
GL.MatrixMode(MatrixMode.Projection);
GL.LoadIdentity();
Glu.Perspective(45.0, ratio, 1.0, 64.0);
}
#endregion
public override void OnUpdateFrame(UpdateFrameEventArgs e)
{
// Dynamic updating of the Vertex Array using different methods
if (dynamicUpdate)
{
if( GL.IsEnabled(EnableCap.Lighting) )
{
// Method 1 (BufferData) : Modify local copy and resend data
GL.BindBuffer(BufferTarget.ArrayBuffer, vbo.VertexBufferID);
for (int i = 0; i < shape.Vertices.Length; i++)
{
if (shape.Vertices[i].Y < 0) shape.Vertices[i].Y = -1.0f + (float)Math.Sin(counter*2) / 2.0f;
else shape.Vertices[i].Y = 1.0f - (float)Math.Sin(counter * 2) / 2.0f;
}
GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(shape.Vertices.Length * Vector3.SizeInBytes), shape.Vertices, BufferUsageHint.DynamicDraw);
}
else if (GL.IsEnabled(EnableCap.Texture2D))
{
// Method 2 (BufferSubData) : Modify PART of local copy and resend data
GL.BindBuffer(BufferTarget.ArrayBuffer, vbo.VertexBufferID);
for (int i = 0; i < shape.Vertices.Length; i++)
{
if (shape.Vertices[i].Y < 0) shape.Vertices[i].Y = -1.0f + (float)Math.Sin(counter * 2) / 2.0f;
else shape.Vertices[i].Y = 1.0f - (float)Math.Sin(counter * 2) / 2.0f;
}
// Even though we modified all the local data - we are only sending the first half of the changes
GL.BufferSubData(BufferTarget.ArrayBuffer, (IntPtr)(0), (IntPtr)(shape.Vertices.Length * Vector3.SizeInBytes /2), shape.Vertices);
}
else
{
// Method 3 (MapBuffer) : Bring data local and modify it
unsafe
{
GL.BindBuffer(BufferTarget.ArrayBuffer, vbo.VertexBufferID);
IntPtr intPtr = GL.MapBuffer(BufferTarget.ArrayBuffer, BufferAccess.ReadWrite);
if (intPtr == IntPtr.Zero) return;
Vector3* vertexArray = (Vector3*)intPtr;
for (int i = 0; i < shape.Vertices.Length; i++)
{
if (vertexArray[i].Y < 0) vertexArray[i].Y = -1.0f + (float)Math.Sin(counter * 2) / 2.0f;
else vertexArray[i].Y = 1.0f - (float)Math.Sin(counter * 2) / 2.0f;
}
GL.UnmapBuffer(BufferTarget.ArrayBuffer);
}
}
}
}
#region OnRenderFrame
double counter = 0;
public override void OnRenderFrame(RenderFrameEventArgs e)
{
base.OnRenderFrame(e);
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
GL.PushAttrib(AttribMask.LightingBit);
GL.Disable(EnableCap.Lighting);
GL.Color3(Color.White);
textPrinter.Begin();
textPrinter.Draw((1.0 / e.Time).ToString("F2"), font);
GL.Translate(150, 0, 0);
textPrinter.Draw("(L)ighting (T)exture (D)ynamicUpdate", font);
textPrinter.End();
GL.PopAttrib();
GL.MatrixMode(MatrixMode.Modelview);
GL.LoadIdentity();
Glu.LookAt(0.0, 3.5, 3.5, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
counter += e.Time;
GL.Rotate(counter * 100, 0, 1, 0);
Draw(vbo);
SwapBuffers();
}
#endregion
///
/// Generate a VertexBuffer for each of Color, Normal, TextureCoordinate, Vertex, and Indices
///
Vbo LoadVBO(Shape shape)
{
Vbo vbo = new Vbo();
if (shape.Vertices == null) return vbo;
if (shape.Indices == null) return vbo;
int bufferSize;
// Color Array Buffer
if (shape.Colors != null)
{
// Generate Array Buffer Id
GL.GenBuffers(1, out vbo.ColorBufferID);
// Bind current context to Array Buffer ID
GL.BindBuffer(BufferTarget.ArrayBuffer, vbo.ColorBufferID);
// Send data to buffer
GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(shape.Colors.Length * sizeof(int)), shape.Colors, BufferUsageHint.StaticDraw);
// Validate that the buffer is the correct size
GL.GetBufferParameter(BufferTarget.ArrayBuffer, BufferParameterName.BufferSize, out bufferSize);
if (shape.Colors.Length * sizeof(int) != bufferSize)
throw new ApplicationException("Vertex array not uploaded correctly");
// Clear the buffer Binding
GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
}
// Normal Array Buffer
if (shape.Normals != null)
{
// Generate Array Buffer Id
GL.GenBuffers(1, out vbo.NormalBufferID);
// Bind current context to Array Buffer ID
GL.BindBuffer(BufferTarget.ArrayBuffer, vbo.NormalBufferID);
// Send data to buffer
GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(shape.Normals.Length * Vector3.SizeInBytes), shape.Normals, BufferUsageHint.StaticDraw);
// Validate that the buffer is the correct size
GL.GetBufferParameter(BufferTarget.ArrayBuffer, BufferParameterName.BufferSize, out bufferSize);
if (shape.Normals.Length * Vector3.SizeInBytes != bufferSize)
throw new ApplicationException("Normal array not uploaded correctly");
// Clear the buffer Binding
GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
}
// TexCoord Array Buffer
if (shape.Texcoords != null)
{
// Generate Array Buffer Id
GL.GenBuffers(1, out vbo.TexCoordBufferID);
// Bind current context to Array Buffer ID
GL.BindBuffer(BufferTarget.ArrayBuffer, vbo.TexCoordBufferID);
// Send data to buffer
GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(shape.Texcoords.Length * 8), shape.Texcoords, BufferUsageHint.StaticDraw);
// Validate that the buffer is the correct size
GL.GetBufferParameter(BufferTarget.ArrayBuffer, BufferParameterName.BufferSize, out bufferSize);
if (shape.Texcoords.Length * 8 != bufferSize)
throw new ApplicationException("TexCoord array not uploaded correctly");
// Clear the buffer Binding
GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
}
// Vertex Array Buffer
{
// Generate Array Buffer Id
GL.GenBuffers(1, out vbo.VertexBufferID);
// Bind current context to Array Buffer ID
GL.BindBuffer(BufferTarget.ArrayBuffer, vbo.VertexBufferID);
// Send data to buffer
GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(shape.Vertices.Length * Vector3.SizeInBytes), shape.Vertices, BufferUsageHint.DynamicDraw);
// Validate that the buffer is the correct size
GL.GetBufferParameter(BufferTarget.ArrayBuffer, BufferParameterName.BufferSize, out bufferSize);
if (shape.Vertices.Length * Vector3.SizeInBytes != bufferSize)
throw new ApplicationException("Vertex array not uploaded correctly");
// Clear the buffer Binding
GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
}
// Element Array Buffer
{
// Generate Array Buffer Id
GL.GenBuffers(1, out vbo.ElementBufferID);
// Bind current context to Array Buffer ID
GL.BindBuffer(BufferTarget.ElementArrayBuffer, vbo.ElementBufferID);
// Send data to buffer
GL.BufferData(BufferTarget.ElementArrayBuffer, (IntPtr)(shape.Indices.Length * sizeof(int)), shape.Indices, BufferUsageHint.StaticDraw);
// Validate that the buffer is the correct size
GL.GetBufferParameter(BufferTarget.ElementArrayBuffer, BufferParameterName.BufferSize, out bufferSize);
if (shape.Indices.Length * sizeof(int) != bufferSize)
throw new ApplicationException("Element array not uploaded correctly");
// Clear the buffer Binding
GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0);
}
// Store the number of elements for the DrawElements call
vbo.NumElements = shape.Indices.Length;
return vbo;
}
void Draw(Vbo vbo)
{
// Push current Array Buffer state so we can restore it later
GL.PushClientAttrib(ClientAttribMask.ClientVertexArrayBit);
if (vbo.VertexBufferID == 0) return;
if (vbo.ElementBufferID == 0) return;
if (GL.IsEnabled(EnableCap.Lighting))
{
// Normal Array Buffer
if (vbo.NormalBufferID != 0)
{
// Bind to the Array Buffer ID
GL.BindBuffer(BufferTarget.ArrayBuffer, vbo.NormalBufferID);
// Set the Pointer to the current bound array describing how the data ia stored
GL.NormalPointer(NormalPointerType.Float, Vector3.SizeInBytes, IntPtr.Zero);
// Enable the client state so it will use this array buffer pointer
GL.EnableClientState(EnableCap.NormalArray);
}
}
else
{
// Color Array Buffer (Colors not used when lighting is enabled)
if (vbo.ColorBufferID != 0)
{
// Bind to the Array Buffer ID
GL.BindBuffer(BufferTarget.ArrayBuffer, vbo.ColorBufferID);
// Set the Pointer to the current bound array describing how the data ia stored
GL.ColorPointer(4, ColorPointerType.UnsignedByte, sizeof(int), IntPtr.Zero);
// Enable the client state so it will use this array buffer pointer
GL.EnableClientState(EnableCap.ColorArray);
}
}
// Texture Array Buffer
if (GL.IsEnabled(EnableCap.Texture2D))
{
if (vbo.TexCoordBufferID != 0)
{
// Bind to the Array Buffer ID
GL.BindBuffer(BufferTarget.ArrayBuffer, vbo.TexCoordBufferID);
// Set the Pointer to the current bound array describing how the data ia stored
GL.TexCoordPointer(2, TexCoordPointerType.Float, 8, IntPtr.Zero);
// Enable the client state so it will use this array buffer pointer
GL.EnableClientState(EnableCap.TextureCoordArray);
}
}
// Vertex Array Buffer
{
// Bind to the Array Buffer ID
GL.BindBuffer(BufferTarget.ArrayBuffer, vbo.VertexBufferID);
// Set the Pointer to the current bound array describing how the data ia stored
GL.VertexPointer(3, VertexPointerType.Float, Vector3.SizeInBytes, IntPtr.Zero);
// Enable the client state so it will use this array buffer pointer
GL.EnableClientState(EnableCap.VertexArray);
}
// Element Array Buffer
{
// Bind to the Array Buffer ID
GL.BindBuffer(BufferTarget.ElementArrayBuffer, vbo.ElementBufferID);
// Draw the elements in the element array buffer
// Draws up items in the Color, Vertex, TexCoordinate, and Normal Buffers using indices in the ElementArrayBuffer
GL.DrawElements(BeginMode.Triangles, vbo.NumElements, DrawElementsType.UnsignedInt, IntPtr.Zero);
// Could also call GL.DrawArrays which would ignore the ElementArrayBuffer and just use primitives
// Of course we would have to reorder our data to be in the correct primitive order
}
// Restore the state
GL.PopClientAttrib();
}
#region public static void Main()
///
/// Entry point of this example.
///
[STAThread]
public static void Main()
{
using (T08_VBO example = new T08_VBO())
{
// Get the title and category of this example using reflection.
ExampleAttribute info = ((ExampleAttribute)example.GetType().GetCustomAttributes(false)[0]);
example.Title = String.Format("OpenTK | {0} {1}: {2}", info.Category, info.Difficulty, info.Title);
example.Run();
}
}
#endregion
struct Vbo
{
public int VertexBufferID;
public int ColorBufferID;
public int TexCoordBufferID;
public int NormalBufferID;
public int ElementBufferID;
public int NumElements;
}
}
}