With todays hardware it is quite difficult to do blurring by hand using the color buffer (at least in a way that is supported by all the gfx cards), so we need to do a little trick to achieve the same effect.
As a bonus while learning the radial blur effect, you will also learn how to render to a texture the easy way!
I decided to use a spring as the shape in this tutorial because it's a cool shape, and I'm tired of cubes :)
It's important to note that this tutorial is more a guideline on how to create the effect. I don't go into great detail explaining the code. You should know most of it off by heart :)
Below are the variable definitions and includes used:
#include <math.h> // We'll Need Some Math
float angle; // Used To Rotate The Helix
float vertexes[3][3]; // An Array Of 3 Floats To Store The Vertex Data
float normal[3]; // An Array To Store The Normal Data
GLuint BlurTexture; // An Unsigned Int To Store The Texture Number
The function EmptyTexture() creates an empty texture and returns the number of that texture. We just allocate some free space (exactly 128 * 128 * 4 unsiged integers).
128 * 128 is the size of the texture (128 pixels wide and tall), the 4 means that for every pixel we want 4 byte to store the RED, GREEN, BLUE and ALPHA components.
GLuint EmptyTexture() // Create An Empty Texture
{
GLuint txtnumber; // Texture ID
unsigned int* data; // Stored Data
// Create Storage Space For Texture Data (128x128x4)
data = (unsigned int*)new GLuint[((128 * 128)* 4 * sizeof(unsigned int))];
After allocating space we zero it using the ZeroMemory function, passing the pointer (data) and the size of memory to be "zeroed".
A semi important thing to note is that we set the magnification and minification methods to GL_LINEAR. That's because we will be stretching our texture and GL_NEAREST looks quite bad if stretched.
ZeroMemory(data,((128 * 128)* 4 * sizeof(unsigned int))); // Clear Storage Memory
glGenTextures(1, &txtnumber); // Create 1 Texture
glBindTexture(GL_TEXTURE_2D, txtnumber); // Bind The Texture
glTexImage2D(GL_TEXTURE_2D, 0, 4, 128, 128, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); // Build Texture Using Information In data
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
delete [] data; // Release data
return txtnumber; // Return The Texture ID
}
This function simply normalize the length of the normal vectors. Vectors are expressed as arrays of 3 elements of type float, where the first element rapresent X, the second Y and the third Z. A normalized vector (Nv) is expressed by Vn = (Vox / |Vo| , Voy / |Vo|, Voz / |Vo|), where Vo is the original vector, |vo| is the modulus (or length) of that vector, and x,y,z are the component of that vector. Doing it "digitally" will be: Calculating the length of the original vector by doing: sqrt(x^2 + y^2 + z^2) ,where x,y,z are the 3 components of the vector. And then dividing each normal vector component by the length of the vector.
void ReduceToUnit(float vector[3]) // Reduces A Normal Vector (3 Coordinates)
{ // To A Unit Normal Vector With A Length Of One.
float length; // Holds Unit Length
// Calculates The Length Of The Vector
length = (float)sqrt((vector[0]*vector[0]) + (vector[1]*vector[1]) + (vector[2]*vector[2]));
if (length == 0.0f) // Prevents Divide By 0 Error By Providing
length = 1.0f; // An Acceptable Value For Vectors To Close To 0.
vector[0] /= length; // Dividing Each Element By
vector[1] /= length; // The Length Results In A
vector[2] /= length; // Unit Normal Vector.
}
The following routine calculates the normal given 3 vertices (always in the 3 float array). We have two parameters : v[3][3] and out[3] of course the first parameter is a matrix of floats with m=3 and n=3 where every line is a vertex of the triangle. out is the place where we'll put the resulting normal vector.
A bit of (easy) math. We are going to use the famous cross product, by definition the cross product is an operation between two vectors that returns another vector orthogonal to the two original vectors. The normal is the vector orthogonal to a surface, with the versus opposite to that surface (and usually a normalized length). Imagine now if the two vectors above are the sides of a triangle, then the orthogonal vector (calculated with the cross product) of two sides of a triangle is exactly the normal of that triangle.
Harder to explain than to do.
We will start finding the vector going from vertex 0 to vertex 1, and the vector from vertex 1 to vertex 2, this is basically done by (brutally) subtracting each component of each vertex from the next. Now we got the vectors for our triangle sides. By doing the cross product (vXw) we get the normal vector for that triangle.
Let's see the code.
v[0][] is the first vertex, v[1][] is the second vertex, v[2][] is the third vertex. Every vertex has: v[][0] the x coordinate of that vertex, v[][1] the y coord of that vertex, v[][2] the z coord of that vertex.
By simply subtracting every coord of one vertex from the next we get the VECTOR from this vertex to the next. v1[0] = v[0][0] – v[1][0], this calculates the X component of the VECTOR going from VERTEX 0 to vertex 1. v1[1] = v[0][1] – v[1][1], this will calculate the Y component v1[2] = v[0][2] – v[1][2], this will calculate the Z component and so on…
Now we have the two VECTORS, so let's calculate the cross product of them to get the normal of the triangle.
The formula for the cross product is:
out[x] = v1[y] * v2[z] – v1[z] * v2[y]
out[y] = v1[z] * v2[x] – v1[x] * v2[z]
out[z] = v1[x] * v2[y] – v1[y] * v2[x]
We finally have the normal of the triangle in out[].
void calcNormal(float v[3][3], float out[3]) // Calculates Normal For A Quad Using 3 Points
{
float v1[3],v2[3]; // Vector 1 (x,y,z) & Vector 2 (x,y,z)
static const int x = 0; // Define X Coord
static const int y = 1; // Define Y Coord
static const int z = 2; // Define Z Coord
// Finds The Vector Between 2 Points By Subtracting
// The x,y,z Coordinates From One Point To Another.
// Calculate The Vector From Point 1 To Point 0
v1[x] = v[0][x] – v[1][x]; // Vector 1.x=Vertex[0].x-Vertex[1].x
v1[y] = v[0][y] – v[1][y]; // Vector 1.y=Vertex[0].y-Vertex[1].y
v1[z] = v[0][z] – v[1][z]; // Vector 1.z=Vertex[0].y-Vertex[1].z
// Calculate The Vector From Point 2 To Point 1
v2[x] = v[1][x] – v[2][x]; // Vector 2.x=Vertex[0].x-Vertex[1].x
v2[y] = v[1][y] – v[2][y]; // Vector 2.y=Vertex[0].y-Vertex[1].y
v2[z] = v[1][z] – v[2][z]; // Vector 2.z=Vertex[0].z-Vertex[1].z
// Compute The Cross Product To Give Us A Surface Normal
out[x] = v1[y]*v2[z] – v1[z]*v2[y]; // Cross Product For Y – Z
out[y] = v1[z]*v2[x] – v1[x]*v2[z]; // Cross Product For X – Z
out[z] = v1[x]*v2[y] – v1[y]*v2[x]; // Cross Product For X – Y
ReduceToUnit(out); // Normalize The Vectors