#region --- License --- /* Licensed under the MIT/X11 license. * Copyright (c) 2006-2008 the OpenTK Team. * This notice may not be removed from any source distribution. * See license.txt for licensing detailed licensing details. * * Written by Christoph Brandtner */ #endregion using System; using System.Windows.Forms; using System.Diagnostics; using System.Drawing; using System.Drawing.Imaging; using System.IO; using OpenTK; using OpenTK.Graphics; namespace Examples.Tutorial { /// /// Demonstrates how to render an animated Julia Set in real-time. Quality is sacrificed for speed. /// Info about the fractal: http://en.wikipedia.org/wiki/Julia_set /// One more major optimization could be applied (exploit the symmetry of the image with RTT), but /// that would make the program alot more complicated to follow. You can do this as an exercise. /// [Example("Julia Set Fractal", ExampleCategory.OpenGL, "GLSL", Documentation="JuliaSetFractal")] public class JuliaSetFractal : GameWindow { public JuliaSetFractal() : base(512, 512) { } #region Private Fields // GLSL Objects int VertexShaderObject, FragmentShaderObject, ProgramObject; int TextureObject; // Julia Variables for animation float AnimOffsetX = 0.213f; // using non-zero as starting point to make it more interesting float AnimOffsetY = 0.63f; const double AnimSpeedX = 0.65; // anim speed scaling is solely used to make the anim more interesting const double AnimSpeedY = 1.05; const double AnimCosinusPercent = 0.85f; // scales the cosinus down to 85% to avoid the (boring) borders float UniformScaleFactorX; // fractal horizontal scaling is only affected by window resize float UniformScaleFactorY; // fractal vertical scaling is only affected by window resize float UniformOffsetX = 1.8f; // fractal horizontal offset float UniformOffsetY = 1.8f; // fractal vertical offset // Text drawing (for fps) TextPrinter printer = new TextPrinter(); Font font = new Font(FontFamily.GenericSansSerif, 16.0f); #endregion private Fields #region OnLoad /// /// Setup OpenGL and load resources here. /// /// Not used. public override void OnLoad(EventArgs e) { // Check for necessary capabilities: if (!GL.SupportsExtension("VERSION_2_0")) { MessageBox.Show("You need at least OpenGL 2.0 to run this example. Aborting.", "GLSL not supported", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); this.Exit(); } this.VSync = VSyncMode.On; GL.Disable(EnableCap.Dither); GL.ClearColor(0.2f, 0f, 0.4f, 0f); // declare some variables for tracking which shader did compile, and which texture to use string[] ShaderFilenames = new string[2]; ShaderFilenames[0] = "Data/Shaders/JuliaSet_SM3_FS.glsl"; ShaderFilenames[1] = "Data/Shaders/JuliaSet_SM2_FS.glsl"; byte CurrentOption = 0; string LogInfo; #region Shaders // Load&Compile Vertex Shader using (StreamReader sr = new StreamReader("Data/Shaders/JuliaSet_VS.glsl")) { VertexShaderObject = GL.CreateShader(ShaderType.VertexShader); GL.ShaderSource(VertexShaderObject, sr.ReadToEnd()); GL.CompileShader(VertexShaderObject); } GL.GetShaderInfoLog(VertexShaderObject, out LogInfo); if (LogInfo.Length > 0 && !LogInfo.Contains("hardware")) Trace.WriteLine("Vertex Shader Log:\n" + LogInfo); else Trace.WriteLine("Vertex Shader compiled without complaint."); // Load&Compile Fragment Shader FragmentShaderObject = GL.CreateShader(ShaderType.FragmentShader); do { using (StreamReader sr = new StreamReader(ShaderFilenames[CurrentOption])) { GL.ShaderSource(FragmentShaderObject, sr.ReadToEnd()); GL.CompileShader(FragmentShaderObject); } GL.GetShaderInfoLog(FragmentShaderObject, out LogInfo); if (LogInfo.Length > 0 && !LogInfo.Contains("hardware")) Trace.WriteLine("Compiling " + ShaderFilenames[CurrentOption] + " failed!\nLog:\n" + LogInfo); else { Trace.WriteLine("Fragment Shader compiled without complaint."); break; } if (++CurrentOption > 1) { MessageBox.Show("Neither SM2 nor SM3 Fragment Shader compiled successfully. Aborting.", "Fatal Error", MessageBoxButtons.OK, MessageBoxIcon.Error); this.Exit(); } } while (true); // Link the Shaders to a usable Program ProgramObject = GL.CreateProgram(); GL.AttachShader(ProgramObject, VertexShaderObject); GL.AttachShader(ProgramObject, FragmentShaderObject); GL.LinkProgram(ProgramObject); // make current GL.UseProgram(ProgramObject); // Flag ShaderObjects for delete when app exits GL.DeleteShader(VertexShaderObject); GL.DeleteShader(FragmentShaderObject); #endregion Shaders #region Textures // Load&Bind the 1D texture for color lookups GL.ActiveTexture(TextureUnit.Texture0); // select TMU0 GL.GenTextures(1, out TextureObject); GL.TexParameter(TextureTarget.Texture1D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); GL.TexParameter(TextureTarget.Texture1D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); GL.TexParameter(TextureTarget.Texture1D, TextureParameterName.TextureWrapS, (int)(TextureWrapMode)All.ClampToEdge); using (Bitmap bitmap = new Bitmap("Data/JuliaColorTable.bmp")) { BitmapData data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb); GL.TexImage1D(TextureTarget.Texture1D, 0, PixelInternalFormat.Rgb8, data.Width, 0, OpenTK.Graphics.PixelFormat.Bgr, PixelType.UnsignedByte, data.Scan0); bitmap.UnlockBits(data); } #endregion Textures Keyboard.KeyUp += KeyUp; } int i = 0; void KeyUp(OpenTK.Input.KeyboardDevice sender, OpenTK.Input.Key e) { if (e == OpenTK.Input.Key.F12) { Bitmap bmp = new Bitmap(this.Width, this.Height); System.Drawing.Imaging.BitmapData data = bmp.LockBits(new Rectangle(0, 0, this.Width, this.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb); GL.ReadPixels(0, 0, this.Width, this.Height, OpenTK.Graphics.PixelFormat.Bgr, OpenTK.Graphics.PixelType.UnsignedByte, data.Scan0); bmp.UnlockBits(data); bmp.RotateFlip(RotateFlipType.RotateNoneFlipY); bmp.Save("Screenshot" + (i++).ToString() + ".png", ImageFormat.Png); } } #endregion #region OnUnload public override void OnUnload(EventArgs e) { if (TextureObject != 0) GL.DeleteTextures(1, ref TextureObject); if (ProgramObject != 0) GL.DeleteProgram(ProgramObject); // implies deleting the previously flagged ShaderObjects } #endregion #region OnResize /// /// Respond to resize events here. /// /// Contains information on the new GameWindow size. /// There is no need to call the base implementation. protected override void OnResize(EventArgs e) { // Magic numbers so the fractal almost fits inside the window. // If changing this, also change the -1.6f offset in the fragment shader accordingly. UniformScaleFactorX = Width / (UniformOffsetX * 2f); UniformScaleFactorY = Height / (UniformOffsetY * 2f); GL.Viewport(0, 0, Width, Height); GL.MatrixMode(MatrixMode.Projection); GL.LoadIdentity(); GL.Ortho(-1.0, 1.0, -1.0, 1.0, 0.0, 1.0); // 2D setup GL.MatrixMode(MatrixMode.Modelview); GL.LoadIdentity(); } #endregion #region OnUpdateFrame /// /// Add your game logic here. /// /// Contains timing information. /// There is no need to call the base implementation. protected override void OnUpdateFrame(FrameEventArgs e) { base.OnUpdateFrame(e); if (Keyboard[OpenTK.Input.Key.Escape]) { this.Exit(); } } #endregion #region OnRenderFrame /// /// Add your game rendering code here. /// /// Contains timing information. /// There is no need to call the base implementation. protected override void OnRenderFrame(FrameEventArgs e) { //this.Title = "FPS: " + 1 / e.Time; GL.Clear(ClearBufferMask.ColorBufferBit); // First, render the next frame of the Julia fractal. GL.UseProgram(ProgramObject); // advance the animation by elapsed time, scaling is solely used to make the anim more interesting AnimOffsetX += (float)(e.Time * AnimSpeedX); AnimOffsetY += (float)(e.Time * AnimSpeedY); // pass uniforms into the fragment shader // first the texture GL.Uniform1(GL.GetUniformLocation(ProgramObject, "COLORTABLE"), TextureObject); // the rest are floats GL.Uniform1(GL.GetUniformLocation(ProgramObject, "CETX"), (float)(Math.Cos(AnimOffsetX) * AnimCosinusPercent)); GL.Uniform1(GL.GetUniformLocation(ProgramObject, "CETY"), (float)(Math.Cos(AnimOffsetY) * AnimCosinusPercent)); GL.Uniform1(GL.GetUniformLocation(ProgramObject, "SCALINGX"), UniformScaleFactorX); GL.Uniform1(GL.GetUniformLocation(ProgramObject, "SCALINGY"), UniformScaleFactorY); GL.Uniform1(GL.GetUniformLocation(ProgramObject, "OFFSETX"), UniformOffsetX); GL.Uniform1(GL.GetUniformLocation(ProgramObject, "OFFSETY"), UniformOffsetY); // Fullscreen quad. Using immediate mode, since this app is fragment shader limited anyways. GL.Begin(BeginMode.Quads); { GL.Vertex2(-1.0f, -1.0f); GL.Vertex2(1.0f, -1.0f); GL.Vertex2(1.0f, 1.0f); GL.Vertex2(-1.0f, 1.0f); } GL.End(); // Then, render the fps: GL.UseProgram(0); printer.Begin(); printer.Print((1 / e.Time).ToString("F2"), font, Color.PaleGoldenrod, RectangleF.Empty, TextPrinterOptions.NoCache); printer.End(); SwapBuffers(); } #endregion #region public static void Main() /// /// Entry point of this example. /// [STAThread] public static void Main() { using (JuliaSetFractal example = new JuliaSetFractal()) { Utilities.SetWindowTitle(example); example.Run(30.0); } } #endregion } }