// This code was written for the OpenTK library and has been released // to the Public Domain. // It is provided "as is" without express or implied warranty of any kind. using System; using OpenTK; using OpenTK.Graphics; using OpenTK.Graphics.OpenGL; using OpenTK.Input; namespace Examples.Tutorial { [Example("VBO Dynamic", ExampleCategory.OpenGL, "1.x", 4, Documentation = "VBODynamic")] 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 static int MaxParticleCount = 2000; 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) { base.OnLoad(e); Version version = new Version(GL.GetString(StringName.Version).Substring(0, 3)); Version target = new Version(1, 5); if (version < target) { throw new NotSupportedException(String.Format( "OpenGL {0} is required (you only have {1}).", target, version)); } 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(ArrayCap.ColorArray); GL.EnableClientState(ArrayCap.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.25f...+0.25f] // that's slow enough so you can see particles 'disappear' when they are respawned temp.X = (float)((rnd.NextDouble() - 0.5) * 0.5f); temp.Y = (float)((rnd.NextDouble() - 0.5) * 0.5f); temp.Z = (float)((rnd.NextDouble() - 0.5) * 0.5f); 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.CreatePerspectiveFieldOfView(MathHelper.PiOver4, 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 += (uint)Math.Max(ParticleAttributes[i].Direction.LengthFast * 10, 1); Vector3.Multiply(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()) { Utilities.SetWindowTitle(example); example.Run(60.0, 0.0); } } } }