using System; using OpenTK; using OpenTK.Graphics; using OpenTK.Graphics.OpenGL; using OpenTK.Input; namespace Examples.Tutorial { [Example("Dynamic Vertex Buffer Objects", ExampleCategory.OpenGL, "VBO", Documentation = "DynamicVBO")] class T09_VBO_Dynamic: GameWindow { /// Creates a 800x600 window with the specified title. public T09_VBO_Dynamic( ) : base( 800, 600 ) { this.VSync = VSyncMode.Off; } #region Particles const int MaxParticleCount = 1000; int VisibleParticleCount; VertexC4ubV3f[] VBO = new VertexC4ubV3f[MaxParticleCount]; ParticleAttribut[] ParticleAttributes = new ParticleAttribut[MaxParticleCount]; // this struct is used for drawing struct VertexC4ubV3f { public byte R, G, B, A; public Vector3 Position; public static int SizeInBytes = 16; } // this struct is used for updates struct ParticleAttribut { public Vector3 Direction; public uint Age; // more stuff could be here: Rotation, Radius, whatever } uint VBOHandle; #endregion Particles /// Load resources here. /// Not used. protected override void OnLoad( EventArgs e ) { GL.ClearColor( .1f, 0f, .1f, 0f ); GL.Enable( EnableCap.DepthTest ); // Setup parameters for Points GL.PointSize( 5f ); GL.Enable( EnableCap.PointSmooth ); GL.Hint( HintTarget.PointSmoothHint, HintMode.Nicest ); // Setup VBO state GL.EnableClientState( EnableCap.ColorArray ); GL.EnableClientState( EnableCap.VertexArray ); GL.GenBuffers( 1, out VBOHandle ); // Since there's only 1 VBO in the app, might aswell setup here. GL.BindBuffer( BufferTarget.ArrayBuffer, VBOHandle ); GL.ColorPointer( 4, ColorPointerType.UnsignedByte, VertexC4ubV3f.SizeInBytes, (IntPtr) 0 ); GL.VertexPointer( 3, VertexPointerType.Float, VertexC4ubV3f.SizeInBytes, (IntPtr) (4*sizeof(byte)) ); Random rnd = new Random( ); Vector3 temp = Vector3.Zero; // generate some random stuff for the particle system for ( uint i = 0 ; i < MaxParticleCount ; i++ ) { VBO[i].R = (byte) rnd.Next( 0, 256 ); VBO[i].G = (byte) rnd.Next( 0, 256 ); VBO[i].B = (byte) rnd.Next( 0, 256 ); VBO[i].A = (byte) rnd.Next( 0, 256 ); // isn't actually used VBO[i].Position = Vector3.Zero; // all particles are born at the origin // generate direction vector in the range [-0.1f...+0.1f] // that's slow enough so you can see particles 'disappear' when they are respawned temp.X = (float) ( ( rnd.NextDouble( ) - 0.5 ) * 0.2 ); temp.Y = (float) ( ( rnd.NextDouble( ) - 0.5 ) * 0.2 ); temp.Z = (float) ( ( rnd.NextDouble( ) - 0.5 ) * 0.2 ); ParticleAttributes[i].Direction = temp; // copy ParticleAttributes[i].Age = 0; } VisibleParticleCount = 0; } protected override void OnUnload(EventArgs e) { GL.DeleteBuffers( 1, ref VBOHandle ); } /// /// Called when your window is resized. Set your viewport here. It is also /// a good place to set up your projection matrix (which probably changes /// along when the aspect ratio of your window). /// /// Contains information on the new Width and Size of the GameWindow. protected override void OnResize(EventArgs e) { GL.Viewport(0, 0, Width, Height); GL.MatrixMode(MatrixMode.Projection); Matrix4 p = Matrix4.Perspective(45.0f, Width / (float)Height, 0.1f, 50.0f); GL.LoadMatrix(ref p); GL.MatrixMode(MatrixMode.Modelview); Matrix4 mv = Matrix4.LookAt(Vector3.UnitZ, Vector3.Zero, Vector3.UnitY); GL.LoadMatrix(ref mv); } /// /// Called when it is time to setup the next frame. Add you game logic here. /// /// Contains timing information for framerate independent logic. protected override void OnUpdateFrame( FrameEventArgs e ) { if ( Keyboard[Key.Escape] ) { Exit( ); } // will update particles here. When using a Physics SDK, it's update rate is much higher than // the framerate and it would be a waste of cycles copying to the VBO more often than drawing it. if ( VisibleParticleCount < MaxParticleCount ) VisibleParticleCount++; Vector3 temp; for ( int i = MaxParticleCount - VisibleParticleCount ; i < MaxParticleCount ; i++ ) { if ( ParticleAttributes[i].Age >= MaxParticleCount ) { // reset particle ParticleAttributes[i].Age = 0; VBO[i].Position = Vector3.Zero; } else { ParticleAttributes[i].Age++; Vector3.Mult( ref ParticleAttributes[i].Direction, (float) e.Time, out temp ); Vector3.Add( ref VBO[i].Position, ref temp, out VBO[i].Position ); } } } /// /// Called when it is time to render the next frame. Add your rendering code here. /// /// Contains timing information. protected override void OnRenderFrame( FrameEventArgs e ) { this.Title = VisibleParticleCount + " Points. FPS: " + string.Format( "{0:F}", 1.0 / e.Time ); GL.Clear( ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit ); GL.PushMatrix( ); GL.Translate( 0f, 0f, -5f ); // Tell OpenGL to discard old VBO when done drawing it and reserve memory _now_ for a new buffer. // without this, GL would wait until draw operations on old VBO are complete before writing to it GL.BufferData( BufferTarget.ArrayBuffer, (IntPtr) ( VertexC4ubV3f.SizeInBytes * MaxParticleCount ), IntPtr.Zero, BufferUsageHint.StreamDraw ); // Fill newly allocated buffer GL.BufferData( BufferTarget.ArrayBuffer, (IntPtr) ( VertexC4ubV3f.SizeInBytes * MaxParticleCount ), VBO, BufferUsageHint.StreamDraw ); // Only draw particles that are alive GL.DrawArrays( BeginMode.Points, MaxParticleCount - VisibleParticleCount, VisibleParticleCount ); GL.PopMatrix( ); SwapBuffers( ); } /// /// The main entry point for the application. /// [STAThread] static void Main() { // The 'using' idiom guarantees proper resource cleanup. // We request 30 UpdateFrame events per second, and unlimited // RenderFrame events (as fast as the computer can handle). using (T09_VBO_Dynamic example = new T09_VBO_Dynamic()) { // 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(60.0, 0.0); } } } }