Time to give up… ?
No, the solution is quite easy, OpenGL gives us the ability to "blur" textures. Ok… Not really blurring, but if we scale a texture using linear filtering, the result (with a bit of imagination) looks like gaussian blur.
So what would happen if we put a lot of stretched textures right on top of the 3D scene and scaled them?
The answer is simple… A radial blur effect!
There are two problems: How do we create the texture realtime and how do we place the texture exactly in front of the 3D object?
The solutions are easier than you may think!
Problem ONE: Rendering To A Texture
The problem is easy to solve on pixel formats that have a back buffer. Rendering to a texture without a back buffer can be a real pain on the eyes!
Rendering to texture is achieved with just one function! We need to draw our object and then copy the result (BEFORE SWAPPING THE BACK BUFFER WITH THE FRONT BUFFER) to a texture using the glCopytexSubImage function.
Problem TWO: Fitting The Texture Exactly In Front Of The 3D Object
We know that, if we change the viewport without setting the right perspective, we get a stretched rendering of our object. For example if we set a viewport really wide we get a vertically stretched rendering.
The solution is first to set a viewport that is square like our texture (128×128). After rendering our object to the texture, we render the texture to the screen using the current screen resolution. This way OpenGL reduces the object to fit into the texture, and when we stretch the texture to the full size of the screen, OpenGL resizes the texture to fit perfectly over top of our 3d object. Hopefully I haven't lost anyone. Another quick example… If you took a 640×480 screenshot, and then resized the screenshot to a 256×256 bitmap, you could load that bitmap as a texture and stretch it to fit on a 640x480 screen. The quality would not be as good, but the texture should line up pretty close to the original 640×480 image.
On to the fun stuff! This function is really easy and is one of my preferred "design tricks". It sets a viewport with a size that matches our BlurTexture dimensions (128×128). It then calls the routine that renders the spring. The spring will be stretched to fit the 128*128 texture because of the viewport (128×128 viewport).
After the spring is rendered to fit the 128×128 viewport, we bind to the BlurTexture and copy the colour buffer from the viewport to the BlurTexture using glCopyTexSubImage2D.
The parameters are as follows:
GL_TEXTURE_2D indicates that we are using a 2Dimensional texture, 0 is the mip map level we want to copy the buffer to, the default level is 0, GL_LUMINANCE indicates the format of the data to be copied. I used GL_LUMINANCE because the final result looks better, this way the luminance part of the buffer will be copied to the texture. Other parameters could be GL_ALPHA, GL_RGB, GL_INTENSITY and more.
The next 2 parameters tell OpenGL where to start copying from (0,0). The width and height (128,128) is how many pixels to copy from left to right and how many to copy up and down. The last parameter is only used if we want a border which we dont.
Now that we have a copy of the colour buffer (with the stretched spring) in our BlurTexture we can clear the buffer and set the viewport back to the proper dimensions (640×480 — fullscreen).
IMPORTANT:
This trick can be used only with double buffered pixel formats. The reason why is because all these operations are hidden from the viewer (done on the back buffer).
void RenderToTexture() // Renders To A Texture
{
glViewport(0,0,128,128); // Set Our Viewport (Match Texture Size)
ProcessHelix(); // Render The Helix
glBindTexture(GL_TEXTURE_2D,BlurTexture); // Bind To The Blur Texture
// Copy Our ViewPort To The Blur Texture (From 0,0 To 128,128… No Border)
glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, 0, 0, 128, 128, 0);
glClearColor(0.0f, 0.0f, 0.5f, 0.5); // Set The Clear Color To Medium Blue
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear The Screen And Depth Buffer
glViewport(0, 0, 640 ,480); // Set Viewport (0,0 to 640x480)
}
The DrawBlur function simply draws some blended quads in front of our 3D scene, using the BlurTexture we got before. This way, playing a bit with alpha and scaling the texture, we get something that really looks like radial blur.
I first disable GEN_S and GEN_T (I'm addicted to sphere mapping, so my routines usually enable these instructions :P ).
We enable 2D texturing, disable depth testing, set the proper blend function, enable blending and then bind the BlurTexture.
The next thing we do is switch to an ortho view, that way it's easier to draw a quad that perfectly fits the screen size. This is how line up the texture over top of the 3D object (by stretching the texture to match the screen ratio). This is where problem two is resolved!
void DrawBlur(int times, float inc) // Draw The Blurred Image
{
float spost = 0.0f; // Starting Texture Coordinate Offset
float alphainc = 0.9f / times; // Fade Speed For Alpha Blending
float alpha = 0.2f; // Starting Alpha Value
// Disable AutoTexture Coordinates
glDisable(GL_TEXTURE_GEN_S);
glDisable(GL_TEXTURE_GEN_T);
glEnable(GL_TEXTURE_2D); // Enable 2D Texture Mapping
glDisable(GL_DEPTH_TEST); // Disable Depth Testing
glBlendFunc(GL_SRC_ALPHA,GL_ONE); // Set Blending Mode
glEnable(GL_BLEND); // Enable Blending
glBindTexture(GL_TEXTURE_2D,BlurTexture); // Bind To The Blur Texture
ViewOrtho(); // Switch To An Ortho View
alphainc = alpha / times; // alphainc=0.2f / Times To Render Blur
We draw the texture many times to create the radial effect, scaling the texture coordinates and raising the blend factor every time we do another pass. We draw 25 quads stretching the texture by 0.015f each time.
glBegin(GL_QUADS); // Begin Drawing Quads
for (int num = 0; num < times;num++) // Number Of Times To Render Blur
{
glColor4f(1.0f, 1.0f, 1.0f, alpha); // Set The Alpha Value (Starts At 0.2)
glTexCoord2f(0+spost,1-spost); // Texture Coordinate ( 0, 1 )
glVertex2f(0,0); // First Vertex ( 0, 0 )
glTexCoord2f(0+spost,0+spost); // Texture Coordinate ( 0, 0 )
glVertex2f(0,480); // Second Vertex ( 0, 480 )
glTexCoord2f(1-spost,0+spost); // Texture Coordinate ( 1, 0 )
glVertex2f(640,480); // Third Vertex ( 640, 480 )
glTexCoord2f(1-spost,1-spost); // Texture Coordinate ( 1, 1 )
glVertex2f(640,0); // Fourth Vertex ( 640, 0 )
spost += inc; // Gradually Increase spost (Zooming Closer To Texture Center)
alpha = alpha – alphainc; // Gradually Decrease alpha (Gradually Fading Image Out)
}
glEnd(); // Done Drawing Quads
ViewPerspective(); // Switch To A Perspective View
glEnable(GL_DEPTH_TEST); // Enable Depth Testing
glDisable(GL_TEXTURE_2D); // Disable 2D Texture Mapping
glDisable(GL_BLEND); // Disable Blending
glBindTexture(GL_TEXTURE_2D,0); // Unbind The Blur Texture