
Rendering off screen dynamic textures using FBOs: Conway's Game of Life
Posted Friday, 22 July, 2011 - 22:47 by ZTKHello everyone,
In this tutorial, we will explore using FrameBuffer Objects to render dynamic textures. We will create two textures, named texture1 and texture2. texture1 will be seeded with random data, and we will apply the rules to Conway's Game of Life in the shader. The output will then be rendered offscreen to texture2. In the next frame, texture2 will be the input to the shader, and the output will be written to texture 1. With this ping-pong approach, we continue to process the game indefinitely.
Let's first take a look at how to generate an empty texture.
private void CreateNullTexture(out int texture) { // load texture GL.GenTextures(1, out texture); // Still required else TexImage2D will be applyed on the last bound texture GL.BindTexture(TextureTarget.Texture2D, texture); // generate null texture GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, size_w, size_h, 0, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, IntPtr.Zero); // set filtering to nearest so we get "atari 2600" look GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest); GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest); }
Note that we don't load any actual data from disk. We specify the dimensions of the texture with the private variables size_w and size_h, and then load empty data with IntPtr.Zero.
Another thing to notice is the use of Nearest filters. We don't want linear filters as that would give a blurred look to our textures. We want a nice blocky look, like the old Atari 2600 video game console, so we use Nearest filtering.
Now that we have our empty textures, we want to create two FrameBufferObjects, and bind a texture to each of them. This is done in the CreateFBOandAssignTexture function.
// create and bind an FBO GL.Ext.GenFramebuffers(1, out fbo); GL.Ext.BindFramebuffer(FramebufferTarget.DrawFramebuffer, fbo); // assign texture to FBO GL.Ext.FramebufferTexture2D(FramebufferTarget.DrawFramebuffer, FramebufferAttachment.ColorAttachment0Ext, TextureTarget.Texture2D, texture, 0); #region Test for Error // verbose error handling code here ... GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, 0); // disable rendering into the FBO
Note the last line, which makes sure that default rendering in on screen rather than in the FBO. It's not strictly necessary here, but I think it's a good habit to get into.
Next we'll generate random data, and render it in texture1.
private void RenderRandomStartTextureInFBO(int FBOHandle) { GL.Ext.BindFramebuffer(FramebufferTarget.DrawFramebuffer, FBOHandle); GL.PushAttrib(AttribMask.ViewportBit); { GL.Viewport(0, 0, size_w, size_h); // clear the screen in green, to make it very obvious what the clear affected. only the FBO, not the real framebuffer GL.ClearColor(0.0f, 1.0f, 0.0f, 1.0f); GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit); RenderRandomPoints(); } GL.PopAttrib(); GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, 0); // disable rendering into the FBO } private void RenderRandomPoints() { GL.PushAttrib(AttribMask.ColorBufferBit); // make sure no lingering textures are bound to draw vertices clearly. GL.BindTexture(TextureTarget.Texture2D, 0); GL.Begin(BeginMode.Points); { GL.Color3(1.0f, 0.0f, 0.0f); for (float x = 0; x <= size_w; x += 0.9f) { for (float y = 0; y <= size_h; y+= 0.9f) { if (rnd.Next(2) == 0) GL.Color3(1.0f, 0.0f, 0.0f); else GL.Color3(0.0f, 0.0f, 0.0f); GL.Vertex2(-1.0 + 2 * x / (float)size_w, -1.0 + 2 * y / (float)size_h); } } } GL.Color4(1.0f, 1.0f, 1.0f, 1.0f); GL.End(); GL.PopAttrib(); }
Note the GL.Clear() command with a green background. This would render the background to green on screen if we didn't have a FBO attached, which we do in the first line.
Another gotcha is to make sure texturing is off when we begin rendering random points. This is done with GL.BindTexture(TextureTarget.Texture2D, 0);.
The for loop has x and y as floats. The natural approach of treating them as ints and incrementing by one doesn't work correctly here, as some squares will be missed. Try changing them to ints, you'll see green bands where some squares were not hit. This comes from the float->int interpolation.
The fragment shader was the most fun part of this tutorial to write! It's written for clarity, not efficiency. A few characters of code changing is all it takes to modify the rules of life for some fun variants. The heart of the shader code is here
bool wasAlive = isRed(c); if (wasAlive){ // if we dont have 2 or 3 neighbors, die if (count != 2 && count !=3){ gl_FragColor.rgb = vec3(0.0, 0.0, 0.0); gl_FragColor.r = color.r * 0.49; } else{ gl_FragColor.rgb = vec3(1.0, 0.0, 0.0); } } else{ // spawn if 3 neighbors if (count == 3){ gl_FragColor.rgb = vec3(1.0, 0.0, 0.0); } else{ // fade away slowly gl_FragColor.rgb = vec3(0.0, 0.0, 0.0); gl_FragColor.r = color.r - 0.002; } }
Red cells are alive, black ones are dead. Recently dead cells get their red value cut in half, then progressively fade out over time. It's interesting to note that if the fadeout value is changed from 0.002 to 0.001, it doesn't fade at all! This is because of the float->int conversion back to the texture. The change is not large enough to make a change to the int representation of the color value when encoded back to the texture.
In the OnRenderFrame function, we make sure to set Orthographic projection. We keep this mode for rendering to the texture as well. First we render the contents of textures 1 and 2 in their own rectangles:
// draw a square that holds texture1 in upper left GL.UseProgram(0); GL.BindTexture(TextureTarget.Texture2D, texture1); GL.Begin(BeginMode.Quads); GL.TexCoord2(0.0f, 0.0f); GL.Vertex3(-1.0f, -1.0f + 2* third, 0.0f); GL.TexCoord2(0.0f, 1.0f); GL.Vertex3(-1.0f, -1.0f + 2* third + third, 0.0f); GL.TexCoord2(1.0f, 1.0f); GL.Vertex3(-1.0f + third, -1.0f + 2* third + third, 0.0f); GL.TexCoord2(1.0f, 0.0f); GL.Vertex3(-1.0f + third, -1.0f + 2*third, 0.0f); GL.End(); // now render texture 2 in a square in upper right GL.UseProgram(0); GL.Viewport(ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width, ClientRectangle.Height); GL.BindTexture(TextureTarget.Texture2D, texture2); GL.Begin(BeginMode.Quads); GL.TexCoord2(0.0f, 0.0f); GL.Vertex3(-1.0f + 2 * third, -1.0f + 2* third, 0.0f); GL.TexCoord2(0.0f, 1.0f); GL.Vertex3(-1.0f + 2 * third, -1.0f + 2* third + third, 0.0f); GL.TexCoord2(1.0f, 1.0f); GL.Vertex3(-1.0f + third + 2 * third, -1.0f + 2* third + third, 0.0f); GL.TexCoord2(1.0f, 0.0f); GL.Vertex3(-1.0f + third + 2 * third, -1.0f + 2* third, 0.0f); GL.End();
Then we render texture 1 off screen into texture 2, through the FBO. This is switched every other frame.
GL.UseProgram(shaderProgramHandle1); GL.Uniform1(GL.GetUniformLocation(shaderProgramHandle1, "MyTexture1"), 0); GL.Uniform1(GL.GetUniformLocation(shaderProgramHandle1, "pixel_w"), 1.0f / size_w); GL.Uniform1(GL.GetUniformLocation(shaderProgramHandle1, "pixel_h"), 1.0f / size_h); GL.Viewport(0, 0, size_w, size_h); // run texture1 through a shader, and set to texture2 GL.Ext.BindFramebuffer(FramebufferTarget.DrawFramebuffer, FBOHandle1); GL.BindTexture(TextureTarget.Texture2D, texture2); GL.Begin(BeginMode.Quads); GL.TexCoord2(0.0f, 0.0f); GL.Vertex3(-1.0f, -1.0f, 0.0f); GL.TexCoord2(0.0f, 1.0f); GL.Vertex3(-1.0f, 1.0f, 0.0f); GL.TexCoord2(1.0f, 1.0f); GL.Vertex3(1.0f, 1.0f, 0.0f); GL.TexCoord2(1.0f, 0.0f); GL.Vertex3(1.0f, -1.0f, 0.0f); GL.End(); GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, 0); // disable rendering into the FBO
Finally, let's draw a rectangle at the bottom of the screen that shows the full visual animation. This is what we would draw if we were presenting a simulation of Life to a user. Note that this texture is drawn as texture 1 half the time, and texture 2 half the time.
// to give final "life" effect, render in a square alternating between texture 1 and 2 in bottom center GL.UseProgram(0); GL.Viewport(ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width, ClientRectangle.Height); if (evenFrame) GL.BindTexture(TextureTarget.Texture2D, texture2); else GL.BindTexture(TextureTarget.Texture2D, texture1); GL.Begin(BeginMode.Quads); GL.TexCoord2(0.0f, 0.0f); GL.Vertex3(-1.0f + 1 * third, -1.0f, 0.0f); GL.TexCoord2(0.0f, 1.0f); GL.Vertex3(-1.0f + 1 * third, -1.0f + third, 0.0f); GL.TexCoord2(1.0f, 1.0f); GL.Vertex3(-1.0f + third + 1 * third, -1.0f + third, 0.0f); GL.TexCoord2(1.0f, 0.0f); GL.Vertex3(-1.0f + third + 1 * third, -1.0f, 0.0f); GL.End();
Finally, a bit of user control. Users can press "F" for the simulation to run at full screen, and "S" for it to slow down. The space bar reseeds the textures with random data in case the simulation stalls to a stable sate.
protected override void OnUpdateFrame(FrameEventArgs e) { base.OnUpdateFrame(e); if (Keyboard[Key.Escape]) Exit(); if (Keyboard[Key.F]) { slow = false; } if (Keyboard[Key.S]) { slow = true; } if (Keyboard[Key.Space]) { RenderRandomStartTextureInFBO(FBOHandle1); RenderRandomStartTextureInFBO(FBOHandle2); } }
That's most of our code. The full source is below
using System; using OpenTK; using OpenTK.Graphics; using OpenTK.Graphics.OpenGL; using OpenTK.Audio; using OpenTK.Audio.OpenAL; using OpenTK.Input; using System.Drawing; using System.Drawing.Imaging; using System.Diagnostics; namespace GameOfLife { class Game : GameWindow { int texture1, texture2, fragmentShaderHandle1, shaderProgramHandle1, FBOHandle1, FBOHandle2; bool evenFrame = false; bool tempo_counter = true; int size_w = 40; int size_h = 40; private double elapsedTime = 0; private bool slow = true; Random rnd = new Random(); string fragmentShaderSource1 = @" #version 400 uniform sampler2D MyTexture1; uniform float pixel_w; uniform float pixel_h; bool isRed(vec2 coor); void main(void) { // c is our current coordinate // color is our current texture vec2 c = gl_TexCoord[0].xy; vec4 color = texture2D( MyTexture1, c); // keypad coordinates for neighbors -- ie: 8 => North, 3 => SE, etc... vec2 neighbor_1 = c; vec2 neighbor_2 = c; vec2 neighbor_3 = c; vec2 neighbor_4 = c; vec2 neighbor_6 = c; vec2 neighbor_7 = c; vec2 neighbor_8 = c; vec2 neighbor_9 = c; // define neighbors neighbor_1.x -= pixel_w; neighbor_1.y -= pixel_h; neighbor_2.y -= pixel_h; neighbor_3.x += pixel_w; neighbor_3.y -= pixel_h; neighbor_4.x -= pixel_w; neighbor_6.x += pixel_h; neighbor_7.x -= pixel_w; neighbor_7.y += pixel_h; neighbor_8.y += pixel_h; neighbor_9.x += pixel_w; neighbor_9.y += pixel_h; // calculate number of alive neighbors int count = 0; if (isRed(neighbor_1)) { count++; } if (isRed(neighbor_2)) { count++; } if (isRed(neighbor_3)) { count++; } if (isRed(neighbor_4)) { count++; } if (isRed(neighbor_6)) { count++; } if (isRed(neighbor_7)) { count++; } if (isRed(neighbor_8)) { count++; } if (isRed(neighbor_9)) { count++; } bool wasAlive = isRed(c); if (wasAlive){ // if we dont have 2 or 3 neighbors, die if (count != 2 && count !=3){ gl_FragColor.rgb = vec3(0.0, 0.0, 0.0); gl_FragColor.r = color.r * 0.49; } else{ gl_FragColor.rgb = vec3(1.0, 0.0, 0.0); } } else{ // spawn if 3 neighbors if (count == 3){ gl_FragColor.rgb = vec3(1.0, 0.0, 0.0); } else{ // fade away slowly gl_FragColor.rgb = vec3(0.0, 0.0, 0.0); gl_FragColor.r = color.r - 0.002; } } } bool isRed(vec2 coor){ vec4 color = texture2D( MyTexture1, coor ); if (color.r > 0.5){ return true; } else{ return false; } } "; void CreateShaders() { // create shader fragmentShaderHandle1 = GL.CreateShader(ShaderType.FragmentShader); GL.ShaderSource(fragmentShaderHandle1, fragmentShaderSource1); GL.CompileShader(fragmentShaderHandle1); Debug.WriteLine(GL.GetShaderInfoLog(fragmentShaderHandle1)); // Create program shaderProgramHandle1 = GL.CreateProgram(); GL.AttachShader(shaderProgramHandle1, fragmentShaderHandle1); GL.LinkProgram(shaderProgramHandle1); Debug.WriteLine(GL.GetProgramInfoLog(shaderProgramHandle1)); } /// <summary>Creates a 800x600 window with the specified title.</summary> public Game() : base(800, 600, GraphicsMode.Default, "OpenTK Quick Start Sample") { VSync = VSyncMode.On; } /// <summary>Load resources here.</sudfmmary> /// <param name="e">Not used.</param> protected override void OnLoad(EventArgs e) { base.OnLoad(e); CreateShaders(); GL.Enable(EnableCap.DepthTest); GL.Enable(EnableCap.Texture2D); GL.Hint(HintTarget.PerspectiveCorrectionHint, HintMode.Nicest); int x = Width; CreateNullTexture(out texture1); CreateNullTexture(out texture2); CreateFBOandAssignTexture(out FBOHandle1, texture1); CreateFBOandAssignTexture(out FBOHandle2, texture2); // draw random points into the FBO that holds texture1 RenderRandomStartTextureInFBO(FBOHandle1); Console.WriteLine("Press F for fast clock, S for slow clock, Space to reseed textures"); } private void CreateFBOandAssignTexture(out int fbo, int texture) { // create and bind an FBO GL.Ext.GenFramebuffers(1, out fbo); GL.Ext.BindFramebuffer(FramebufferTarget.DrawFramebuffer, fbo); // assign texture to FBO GL.Ext.FramebufferTexture2D(FramebufferTarget.DrawFramebuffer, FramebufferAttachment.ColorAttachment0Ext, TextureTarget.Texture2D, texture, 0); #region Test for Error string version = string.Empty; try { GLControl control = new GLControl(); version = GL.GetString(StringName.Version); } catch (Exception ex) { } switch (GL.Ext.CheckFramebufferStatus(FramebufferTarget.FramebufferExt)) { case FramebufferErrorCode.FramebufferCompleteExt: { Console.WriteLine("FBO: The framebuffer " + fbo + " is complete and valid for rendering."); break; } case FramebufferErrorCode.FramebufferIncompleteAttachmentExt: { Console.WriteLine("FBO: One or more attachment points are not framebuffer attachment complete. This could mean there’s no texture attached or the format isn’t renderable. For color textures this means the base format must be RGB or RGBA and for depth textures it must be a DEPTH_COMPONENT format. Other causes of this error are that the width or height is zero or the z-offset is out of range in case of render to volume."); break; } case FramebufferErrorCode.FramebufferIncompleteMissingAttachmentExt: { Console.WriteLine("FBO: There are no attachments."); break; } /* case FramebufferErrorCode.GL_FRAMEBUFFER_INCOMPLETE_DUPLICATE_ATTACHMENT_EXT: { Console.WriteLine("FBO: An object has been attached to more than one attachment point."); break; }*/ case FramebufferErrorCode.FramebufferIncompleteDimensionsExt: { Console.WriteLine("FBO: Attachments are of different size. All attachments must have the same width and height."); break; } case FramebufferErrorCode.FramebufferIncompleteFormatsExt: { Console.WriteLine("FBO: The color attachments have different format. All color attachments must have the same format."); break; } case FramebufferErrorCode.FramebufferIncompleteDrawBufferExt: { Console.WriteLine("FBO: An attachment point referenced by GL.DrawBuffers() doesn’t have an attachment."); break; } case FramebufferErrorCode.FramebufferIncompleteReadBufferExt: { Console.WriteLine("FBO: The attachment point referenced by GL.ReadBuffers() doesn’t have an attachment."); break; } case FramebufferErrorCode.FramebufferUnsupportedExt: { Console.WriteLine("FBO: This particular FBO configuration is not supported by the implementation."); break; } default: { Console.WriteLine("FBO: Status unknown. (yes, this is really bad.)"); break; } } #endregion Test for Error GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, 0); // disable rendering into the FBO } private void RenderRandomStartTextureInFBO(int FBOHandle) { GL.Ext.BindFramebuffer(FramebufferTarget.DrawFramebuffer, FBOHandle); GL.PushAttrib(AttribMask.ViewportBit); { GL.Viewport(0, 0, size_w, size_h); // clear the screen in green, to make it very obvious what the clear affected. only the FBO, not the real framebuffer GL.ClearColor(0.0f, 1.0f, 0.0f, 1.0f); GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit); RenderRandomPoints(); } GL.PopAttrib(); GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, 0); // disable rendering into the FBO } private void RenderRandomPoints() { GL.PushAttrib(AttribMask.ColorBufferBit); // make sure no lingering textures are bound to draw vertices clearly. GL.BindTexture(TextureTarget.Texture2D, 0); GL.Begin(BeginMode.Points); { GL.Color3(1.0f, 0.0f, 0.0f); for (float x = 0; x <= size_w; x += 0.9f) { for (float y = 0; y <= size_h; y+= 0.9f) { if (rnd.Next(2) == 0) GL.Color3(1.0f, 0.0f, 0.0f); else GL.Color3(0.0f, 0.0f, 0.0f); GL.Vertex2(-1.0 + 2 * x / (float)size_w, -1.0 + 2 * y / (float)size_h); } } } GL.Color4(1.0f, 1.0f, 1.0f, 1.0f); GL.End(); GL.PopAttrib(); } private void CreateNullTexture(out int texture) { // load texture GL.GenTextures(1, out texture); // Still required else TexImage2D will be applyed on the last bound texture GL.BindTexture(TextureTarget.Texture2D, texture); // generate null texture GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, size_w, size_h, 0, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, IntPtr.Zero); // set filtering to nearest so we get "atari 2600" look GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest); GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest); } /// <summary> /// 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). /// </summary> /// <param name="e">Not used.</param> protected override void OnResize(EventArgs e) { base.OnResize(e); } /// <summary> /// Called when it is time to setup the next frame. Add you game logic here. /// </summary> /// <param name="e">Contains timing information for framerate independent logic.</param> protected override void OnUpdateFrame(FrameEventArgs e) { base.OnUpdateFrame(e); if (Keyboard[Key.Escape]) Exit(); if (Keyboard[Key.F]) { slow = false; } if (Keyboard[Key.S]) { slow = true; } if (Keyboard[Key.Space]) { RenderRandomStartTextureInFBO(FBOHandle1); RenderRandomStartTextureInFBO(FBOHandle2); } } /// <summary> /// Called when it is time to render the next frame. Add your rendering code here. /// </summary> /// <param name="e">Contains timing information.</param> protected override void OnRenderFrame(FrameEventArgs e) { base.OnRenderFrame(e); GL.ClearColor(0.4f, 0.3f, 0.9f, 0f); GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); GL.Viewport(ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width, ClientRectangle.Height); Matrix4 modelview = Matrix4.LookAt(0, 0, 1, 0, 0, 0, 0, 1, 0); GL.MatrixMode(MatrixMode.Modelview); GL.LoadMatrix(ref modelview); GL.MatrixMode(MatrixMode.Projection); GL.LoadIdentity(); GL.Ortho(-1, 1, -1, 1, -1, 1.1); float third = 2.0f / 3.0f; // draw a square that holds texture1 in upper left GL.UseProgram(0); GL.BindTexture(TextureTarget.Texture2D, texture1); GL.Begin(BeginMode.Quads); GL.TexCoord2(0.0f, 0.0f); GL.Vertex3(-1.0f, -1.0f + 2* third, 0.0f); GL.TexCoord2(0.0f, 1.0f); GL.Vertex3(-1.0f, -1.0f + 2* third + third, 0.0f); GL.TexCoord2(1.0f, 1.0f); GL.Vertex3(-1.0f + third, -1.0f + 2* third + third, 0.0f); GL.TexCoord2(1.0f, 0.0f); GL.Vertex3(-1.0f + third, -1.0f + 2*third, 0.0f); GL.End(); // now render texture 2 in a square in upper right GL.UseProgram(0); GL.Viewport(ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width, ClientRectangle.Height); GL.BindTexture(TextureTarget.Texture2D, texture2); GL.Begin(BeginMode.Quads); GL.TexCoord2(0.0f, 0.0f); GL.Vertex3(-1.0f + 2 * third, -1.0f + 2* third, 0.0f); GL.TexCoord2(0.0f, 1.0f); GL.Vertex3(-1.0f + 2 * third, -1.0f + 2* third + third, 0.0f); GL.TexCoord2(1.0f, 1.0f); GL.Vertex3(-1.0f + third + 2 * third, -1.0f + 2* third + third, 0.0f); GL.TexCoord2(1.0f, 0.0f); GL.Vertex3(-1.0f + third + 2 * third, -1.0f + 2* third, 0.0f); GL.End(); GL.UseProgram(shaderProgramHandle1); GL.Uniform1(GL.GetUniformLocation(shaderProgramHandle1, "MyTexture1"), 0); GL.Uniform1(GL.GetUniformLocation(shaderProgramHandle1, "pixel_w"), 1.0f / size_w); GL.Uniform1(GL.GetUniformLocation(shaderProgramHandle1, "pixel_h"), 1.0f / size_h); GL.Viewport(0, 0, size_w, size_h); elapsedTime += e.Time; if (tempo()) { if (!evenFrame) { evenFrame = true; // run texture2 through a shader, and set to texture1 GL.Ext.BindFramebuffer(FramebufferTarget.DrawFramebuffer, FBOHandle2); GL.BindTexture(TextureTarget.Texture2D, texture1); GL.Begin(BeginMode.Quads); GL.TexCoord2(0.0f, 0.0f); GL.Vertex3(-1.0f, -1.0f, 0.0f); GL.TexCoord2(0.0f, 1.0f); GL.Vertex3(-1.0f, 1.0f, 0.0f); GL.TexCoord2(1.0f, 1.0f); GL.Vertex3(1.0f, 1.0f, 0.0f); GL.TexCoord2(1.0f, 0.0f); GL.Vertex3(1.0f, -1.0f, 0.0f); GL.End(); GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, 0); // disable rendering into the FBO } } else { if (evenFrame) { evenFrame = false; // run texture1 through a shader, and set to texture2 GL.Ext.BindFramebuffer(FramebufferTarget.DrawFramebuffer, FBOHandle1); GL.BindTexture(TextureTarget.Texture2D, texture2); GL.Begin(BeginMode.Quads); GL.TexCoord2(0.0f, 0.0f); GL.Vertex3(-1.0f, -1.0f, 0.0f); GL.TexCoord2(0.0f, 1.0f); GL.Vertex3(-1.0f, 1.0f, 0.0f); GL.TexCoord2(1.0f, 1.0f); GL.Vertex3(1.0f, 1.0f, 0.0f); GL.TexCoord2(1.0f, 0.0f); GL.Vertex3(1.0f, -1.0f, 0.0f); GL.End(); GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, 0); // disable rendering into the FBO } } // to give final "life" effect, render in a square alternating between texture 1 and 2 in bottom center GL.UseProgram(0); GL.Viewport(ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width, ClientRectangle.Height); if (evenFrame) GL.BindTexture(TextureTarget.Texture2D, texture2); else GL.BindTexture(TextureTarget.Texture2D, texture1); GL.Begin(BeginMode.Quads); GL.TexCoord2(0.0f, 0.0f); GL.Vertex3(-1.0f + 1 * third, -1.0f, 0.0f); GL.TexCoord2(0.0f, 1.0f); GL.Vertex3(-1.0f + 1 * third, -1.0f + third, 0.0f); GL.TexCoord2(1.0f, 1.0f); GL.Vertex3(-1.0f + third + 1 * third, -1.0f + third, 0.0f); GL.TexCoord2(1.0f, 0.0f); GL.Vertex3(-1.0f + third + 1 * third, -1.0f, 0.0f); GL.End(); SwapBuffers(); } private bool tempo() { tempo_counter = !tempo_counter; if (slow) return ((int)(elapsedTime) % 2 == 0); else return tempo_counter; } /// <summary> /// The main entry point for the application. /// </summary> [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 (Game game = new Game()) { game.Run(30.0); } } } }
- ZTK's blog
- Login or register to post comments


Comments
Re: Rendering off screen dynamic textures using FBOs: ...
This is an interesting example, thanks for posting.
One thing I will point out, is I had difficulty in originally getting the sample to work correctly as posted above, as the shader was unable to run by default - the following errosr would be displayed in the Immediate window each time you started the program:
I assume the first line is because my graphics card is a couple of years old.
Changing the version to 120 caused the sample project to run correctly, without anything being printed in the Immediate window. Anything higher than this seemed to be broken as the the textures were displayed as a coarse dither and did not update.
Re: Rendering off screen dynamic textures using FBOs: ...
It didn't work for me either, until I changed the version to 120. Otherwise it's a great little example of how to use FBOs and how to use shaders. Thanks a lot. :)