r3dux.org

A number-pimping side project from the valleys in *NEW* upside-down flavour.

  • Home
  • ABOUT
  • OLD SITE
  • SEARCH
  • FEEDBACK

GLSL Image Processing

r3dux | June 3, 2011

I had a play around doing some image processing earlier using GLSL shaders from chapter 17 of the OpenGL SuperBible (4th Edition, I’m not seeing them in the 5th) and the results aren’t too shabby. I’m rendering the skybox plus torus’ with ambient/diffuse/specular lighting and cubemap texture lookup to a FBO, then rendering a full-screen quad textured with the result of the previous pass, and it’s this full-screen quad that I’m processing to get these effects.

YouTube Preview Image

Pretty easy & kinda fun – next up depth of field…

Image Processing Vertex Shader

#version 330
 
// Read-only uniform values shared across all vertexes
uniform mat4 mvpMatrix;
 
// Incoming per-vertex values
in vec4 vVertex;
in vec2 vTexCoord0;
 
// Outgoing interpolated values
smooth out vec2 vTex;
 
void main(void) 
{
	// Pass through the texture coordinates
	vTex = vTexCoord0;
 
	// Transform the geometry 
	gl_Position = mvpMatrix * vVertex;
}

Image Processing Fragment Shader

#version 330
 
uniform sampler2D quadTexture;
uniform int       filterNumber;
uniform vec2      tcOffset[25]; // Texture coordinate offsets
 
in vec2 vTex;
 
out vec4 vFragColour;
 
void main(void)
{
	// Standard
	if (filterNumber == 0)
	{
		vFragColour = texture2D(quadTexture, vTex);
	}
 
	// Greyscale
	if (filterNumber == 1)
	{
		// Convert to greyscale using NTSC weightings
		float grey = dot(texture2D(quadTexture, vTex).rgb, vec3(0.299, 0.587, 0.114));
 
		vFragColour = vec4(grey, grey, grey, 1.0);
	}
 
	// Sepia tone
	if (filterNumber == 2)
	{
		// Convert to greyscale using NTSC weightings
		float grey = dot(texture2D(quadTexture, vTex).rgb, vec3(0.299, 0.587, 0.114));
 
		// Play with these rgb weightings to get different tones.
		// (As long as all rgb weightings add up to 1.0 you won't lighten or darken the image)
		vFragColour = vec4(grey * vec3(1.2, 1.0, 0.8), 1.0);
	}
 
	// Negative
	if (filterNumber == 3)
	{
		vec4 texMapColour = texture2D(quadTexture, vTex);
 
		vFragColour = vec4(1.0 - texMapColour.rgb, 1.0);
	}
 
	// Blur (gaussian)
	if (filterNumber == 4)
	{
		vec4 sample[25];
 
		for (int i = 0; i < 25; i++)
		{
			// Sample a grid around and including our texel
			sample[i] = texture(quadTexture, vTex + tcOffset[i]);
		}
 
		// Gaussian weighting:
		// 1  4  7  4 1
		// 4 16 26 16 4
		// 7 26 41 26 7 / 273 (i.e. divide by total of weightings)
		// 4 16 26 16 4
		// 1  4  7  4 1
 
    		vFragColour = (
        	           (1.0  * (sample[0] + sample[4]  + sample[20] + sample[24])) +
	                   (4.0  * (sample[1] + sample[3]  + sample[5]  + sample[9] + sample[15] + sample[19] + sample[21] + sample[23])) +
	                   (7.0  * (sample[2] + sample[10] + sample[14] + sample[22])) +
	                   (16.0 * (sample[6] + sample[8]  + sample[16] + sample[18])) +
	                   (26.0 * (sample[7] + sample[11] + sample[13] + sample[17])) +
	                   (41.0 * sample[12])
	                   ) / 273.0;
 
	}
 
	// Blur (median filter)
	if (filterNumber == 5)
	{
		vFragColour = vec4(0.0);
 
		for (int i = 0; i < 25; i++)
		{
			// Sample a grid around and including our texel
			vFragColour += texture(quadTexture, vTex + tcOffset[i]);
		}
 
		vFragColour /= 25;
	}
 
	// Sharpen
	if (filterNumber == 6)
	{
		vec4 sample[25];
 
		for (int i = 0; i < 25; i++)
		{
			// Sample a grid around and including our texel
			sample[i] = texture(quadTexture, vTex + tcOffset[i]);
		}
 
		// Sharpen weighting:
		// -1 -1 -1 -1 -1
		// -1 -1 -1 -1 -1
		// -1 -1 25 -1 -1
		// -1 -1 -1 -1 -1
		// -1 -1 -1 -1 -1
 
    		vFragColour = 25.0 * sample[12];
 
		for (int i = 0; i < 25; i++)
		{
			if (i != 12)
				vFragColour -= sample[i];
		}
	}
 
	// Dilate
	if (filterNumber == 7)
	{
		vec4 sample[25];
		vec4 maxValue = vec4(0.0);
 
		for (int i = 0; i < 25; i++)
		{
			// Sample a grid around and including our texel
			sample[i] = texture(quadTexture, vTex + tcOffset[i]);
 
			// Keep the maximum value		
			maxValue = max(sample[i], maxValue);
		}
 
		vFragColour = maxValue;
	}
 
	// Erode
	if (filterNumber == 8)
	{
		vec4 sample[25];
		vec4 minValue = vec4(1.0);
 
		for (int i = 0; i < 25; i++)
		{
			// Sample a grid around and including our texel
			sample[i] = texture(quadTexture, vTex + tcOffset[i]);
 
			// Keep the minimum value		
			minValue = min(sample[i], minValue);
		}
 
		vFragColour = minValue;
	}
 
	// Laplacian Edge Detection (very, very similar to sharpen filter - check it out!)
	if (filterNumber == 9)
	{
		vec4 sample[25];
 
		for (int i = 0; i < 25; i++)
		{
			// Sample a grid around and including our texel
			sample[i] = texture(quadTexture, vTex + tcOffset[i]);
		}
 
		// Laplacian weighting:
		// -1 -1 -1 -1 -1
		// -1 -1 -1 -1 -1
		// -1 -1 24 -1 -1
		// -1 -1 -1 -1 -1
		// -1 -1 -1 -1 -1
 
    		vFragColour = 24.0 * sample[12];
 
		for (int i = 0; i < 25; i++)
		{
			if (i != 12)
				vFragColour -= sample[i];
		}
	}
}

The only other bit of code worth mentioning is the generation of the texture coordinate offsets, which is accomplished with the following C++ code (yes, it’s kinda ugly – no, I didn’t write it – yes, it works):

// Set up texture sampling offset storage
const GLint tcOffsetColumns = 5;
const GLint tcOffsetRows    = 5;
GLfloat	texCoordOffsets[tcOffsetColumns * tcOffsetRows * 2];
 
// Calculate texture coordinate offsets for kernel convolution effects
void genTexCoordOffsets(GLuint width, GLuint height, GLfloat step = 1.0f) // Note: Change this step value to increase the number of pixels we sample across...
{
	// Note: You can multiply the step to displace the samples further. Do this with diff values horiz and vert and you have directional blur of a sort...
	float xInc = step / (GLfloat)(windowWidth);
	float yInc = step / (GLfloat)(windowHeight);
 
	for (int i = 0; i < tcOffsetColumns; i++)
	{
		for (int j = 0; j < tcOffsetRows; j++)
		{
			texCoordOffsets[(((i*5)+j)*2)+0] = (-2.0f * xInc) + ((GLfloat)i * xInc);
			texCoordOffsets[(((i*5)+j)*2)+1] = (-2.0f * yInc) + ((GLfloat)j * yInc);
		}
	}
}

You can then draw your full-screen quad something like this:

// *** Draw the original geometry to a FBO here ***
 
// Now draw our full-screen quad
projectionMatrix.PushMatrix();
 
	projectionMatrix.LoadIdentity();
	projectionMatrix.LoadMatrix(orthoMatrix);
 
	modelViewMatrix.PushMatrix();
 
		modelViewMatrix.LoadIdentity();
 
		glDisable(GL_DEPTH_TEST);
 
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, fboTexture); // Select the texture in our framebuffer
 
		glUseProgram(r3dTextureProgram);
		glUniformMatrix4fv(locTexMVPMatrix, 1, GL_FALSE, transformPipeline.GetModelViewProjectionMatrix());
		glUniform1i(locTexTextureMap,       0);
		glUniform1i(locTexFilterNumber,     filterNumber);
		glUniform2fv(locTexTCOffsets,       25, texCoordOffsets); // Pass in 25 vec2s in our texture coordinate offset array
 
		screenQuad.Draw();
 
		glEnable(GL_DEPTH_TEST);
 
	modelViewMatrix.PopMatrix();
 
projectionMatrix.PopMatrix();

Job’s a good ‘un.

Comments
4 Comments »
Categories
Coding
Tags
Blur, C++, Dilate, Erode, Gaussian, GLSL, Grayscale, Greyscale, Image Processing, Laplacian, Median, Negative, OpenGL, Sepia
Comments rss Comments rss
Trackback Trackback

OpenGL – A Modified Phong-Blinn Light Model For Shadowed Areas

r3dux | May 28, 2011

I was looking through the book Graphics Programming Methods (2003) the other day when I came across a paper titled “A Modified Phong-Blinn Light Model for Shadowed Areas” and thought it was pretty good, so I implemented it. It’s a really short paper, but what’s it’s basically saying is that by not allowing ambient light to be modified by geometry normals, any characteristics of the geometry are lost because we calculate the diffuse intensity as the dot product of the light location and the surface normal and limit the result between 0 and 1 (i.e. we only take diffuse lighting into consideration for surfaces facing our light source).

What the paper proposes instead is that we allow the dot product to run as -1.0 to +1.0, and when the value is within the range 0.0 to 1.0 (i.e. facing the light) we shade as usual, but when it’s -1.0 to 0.0 (facing away from the light) we calculate the lighting as the ambient light plus the negative diffuse intensity multiplied by the ambient light multiplied by q, where q is a value between 0.0 and 1.0. When q is 0.0, it’s like we’re using a traditional Phong-Blinn model, when it’s 1.0 we’re using the fully modified version, and any value in between is a sliding-scale combination of them.

To put that another way, if our surface is facing away from the light source, we use: Lighting = Ambient + (Diffuse * Ambient * q)

In the following images, the light source is roughly in line with the geometry on the Y and Z axis’, and is a way over on the positive X axis (i.e. to the right). Check out the difference…

Standard lighting with q = 0.0

Standard lighting with q = 0.0 - notice that the inside right area of the torus is getting no diffuse lighting, and all detail is lost to the ambient lighting.

Improved lighting with q = 0.5

Improved lighting with q = 0.5 - there's now some detail in the shadowed area on the inside-right of the torus

Improved lighting with q = 1.0

Improved lighting with q = 1.0 - there's now significantly more detail in the shadowed area on the inside-right of the torus

To create this effect, I used the following fragment shader:

Modified Phong-Blinn Light Model Fragment Shader

#version 330
 
// Uniforms
uniform vec4 ambientColour;
uniform vec4 diffuseColour;
uniform vec4 specularColour;
uniform sampler2D colourMap;
uniform sampler2D normalMap;
uniform float qUniform; // A value to manipulate our lighting by when surface is facing away from light source
 
// Input from our vertex shader
smooth in vec3 vVaryingNormal;
smooth in vec3 vVaryingLightDir;
smooth in vec2 vTexCoords;
 
// Output fragments
out vec4 vFragColour;
 
void main(void)
{ 
	const float maxVariance = 2.0; // Mess around with this value to increase/decrease Normal pertubation
	const float minVariance = maxVariance / 2.0;
 
	// Create a normal which is our standard normal + the normal map perturbation (which is going to be either positive or negative)
	vec3 normalAdjusted = vVaryingNormal + normalize(texture2D(normalMap, vTexCoords.st).rgb * maxVariance - minVariance);
 
	// Get our diffuse intensity as a dot product which is NOT limited 0..1
	float diffuseIntensity = dot(normalize(normalAdjusted), normalize(vVaryingLightDir)); 
 
	vec3 colour = vec3(0.0);
 
	// If the surface is facing the light then shade as normal
	if (diffuseIntensity > 0.0)
	{
		colour = (diffuseIntensity * diffuseColour.rgb) * texture2D(colourMap, vTexCoords.st).rgb + ambientColour.rgb;
	}
	else // Otherwise use the modified light model
	{
		colour = (diffuseIntensity * diffuseColour.rgb * ambientColour.rgb * qUniform) + ambientColour.rgb;
	}
 
	// Set the almost final output color as a vec4 - only specular to go
	vFragColour = vec4(colour, 1.0);
 
	// Specular light
	vec3 vReflection        = normalize(reflect(-normalize(normalAdjusted), normalize(vVaryingLightDir)));
	float specularIntensity = max(0.0, dot(normalize(normalAdjusted), vReflection));
 
	// Add in specular component
	if (diffuseIntensity > 0.0)
	{
		float fSpec = pow(specularIntensity, 64.0);
		vFragColour.rgb += vec3(fSpec * specularColour.rgb);
	}
}

Credits for this method go to Anders Hast, Tony Barrera, and Ewer Bengtsson – good work, fellas! =D

Comments
No Comments »
Categories
Coding
Tags
Blinn, Fragment, GLSL, Lighting, Modified, OpenGL, Phong, Shader, Shadow, Shadowed
Comments rss Comments rss
Trackback Trackback

Simple OpenGL FBO Textures

r3dux | May 26, 2011

I was playing around with FBOs and rendering to textures the other week and came up with this. A spinning yellow torus is rendered to a texture, then a second spinning torus is textured using the texture we just created of the first spinning torus… Yeah, I need to stop using donuts as my test objects.

YouTube Preview Image

Full source code & shaders after the jump…

Read the rest of this entry »

Comments
No Comments »
Categories
Coding
Tags
C++, FBO, Fragment, GLSL, OpenGL, Shader, Texture, Vertex
Comments rss Comments rss
Trackback Trackback

Simple GLSL Bump-Mapping / Normal-Mapping

r3dux | May 13, 2011

I had an hour to kill earlier on so thought I’d take a shot at some bump-mapping/normal-mapping (whatever you want to call it) – and it’s actually not too hard. Well, it’s probably kinda hard to do properly, but it’s pretty easy to get something that works without going into TBN (Tangent/Binormal/Normal) space, like this…

YouTube Preview Image
[Show as slideshow]
[View with PicLens]
r3dbumpmap6
r3dbumpmap5
r3dbumpmap4
r3dbumpmap2
r3dbumpmap3
r3dbumpmap1
r3dbumpmap7
r3dbumpmap8

For this simple way of doing things there are two important steps:

  • Generate normal-map versions of your texture(s)
  • Perturb your per-pixel geometry normals by a value derived from sampling your normal map.

You can use lots of different tools to generate normal maps, I wanted something quick and free, so I used the GIMP, specifically the GIMP Normal-Map Plugin. Just load the texture image, then select Filters | Map | Normalmap… from the menu and and save that bad boy for later use. I upped the scale of the normal-map generation to 10.000 from it’s original 1.000 just to magnify the effect, you can do the same, or you can magnify it in the fragment shader, but it’s probably going to run quicker if you encode the normal-map with the magnitude of normal perturbation you’re going to use in your final work.

Hint: If you’re on Ubuntu, just install the package gimp-plugin-registry – it comes with a stack of neat & useful plugins, including Normal Map. I tried to install it separately and it conflicted with the plugin-registry package, which would be because it’s already a part of it and I had all the tools I needed already!

Original and Normal-Map Textures

Original Texture on the left, Normal-Map version on the right

Notice how the normal-mapped version of the texture on the right is mainly blue? This is because the data being stored in it isn’t really “colour” anymore – instead of thinking of each value as a RGB triplet, think of it as an XYZ vector. If we look at the data that way, what we’re really seeing when we see “blue” is nothing on the X and Y axis’, and positive on the Z axis. Sneaky, huh?

Now that you’ve got your original texture and the normal-mapped version of it, texture your geometry as usual but add an additional Sampler2D uniform in your fragment shader for your normal-map texture. Bind to another texture unit before loading and glTexImage2D-ing it so you have the texture on one texture image unit and your normal map on another (so you can sample from each independently).

Once you’ve got that all sorted, use shaders along the lines of these:

Vertex Shader

#version 330
 
// Incoming per-vertex attribute values
in vec4 vVertex;
in vec3 vNormal;
in vec4 vTexture;
 
uniform mat4 mvpMatrix;
uniform mat4 mvMatrix;
uniform mat3 normalMatrix;
uniform vec3 vLightPosition;
 
// Outgoing normal and light direction to fragment shader
smooth out vec3 vVaryingNormal;
smooth out vec3 vVaryingLightDir;
smooth out vec2 vTexCoords;
 
void main(void) 
{
 
    // Get surface normal in eye coordinates and pass them through to the fragment shader
    vVaryingNormal = normalMatrix * vNormal;
 
    // Get vertex position in eye coordinates
    vec4 vPosition4 = mvMatrix * vVertex;
    vec3 vPosition3 = vPosition4.xyz / vPosition4.w;
 
    // Get vector to light source
    vVaryingLightDir = normalize(vLightPosition - vPosition3);
 
    // Pass the texture coordinates through the vertex shader so they get smoothly interpolated
    vTexCoords = vTexture.st;
 
    // Transform the geometry through the modelview-projection matrix
    gl_Position = mvpMatrix * vVertex;
}

Fragment Shader

#version 330
 
// Uniforms
uniform vec4 ambientColour;
uniform vec4 diffuseColour;
uniform vec4 specularColour;
uniform sampler2D colourMap; // This is the original texture
uniform sampler2D normalMap; // This is the normal-mapped version of our texture
 
// Input from our vertex shader
smooth in vec3 vVaryingNormal;
smooth in vec3 vVaryingLightDir;
smooth in vec2 vTexCoords;
 
// Output fragments
out vec4 vFragColour;
 
void main(void)
{ 
	const float maxVariance = 2.0; // Mess around with this value to increase/decrease normal perturbation
	const float minVariance = maxVariance / 2.0;
 
	// Create a normal which is our standard normal + the normal map perturbation (which is going to be either positive or negative)
	vec3 normalAdjusted = vVaryingNormal + normalize(texture2D(normalMap, vTexCoords.st).rgb * maxVariance - minVariance);
 
	// Calculate diffuse intensity
	float diffuseIntensity = max(0.0, dot(normalize(normalAdjusted), normalize(vVaryingLightDir)));
 
	// Add the diffuse contribution blended with the standard texture lookup and add in the ambient light on top
	vec3 colour = (diffuseIntensity * diffuseColour.rgb) * texture2D(colourMap, vTexCoords.st).rgb + ambientColour.rgb;
 
	// Set the almost final output color as a vec4 - only specular to go!
	vFragColour = vec4(colour, 1.0);
 
	// Calc and apply specular contribution
	vec3 vReflection        = normalize(reflect(-normalize(normalAdjusted), normalize(vVaryingLightDir)));
	float specularIntensity = max(0.0, dot(normalize(normalAdjusted), vReflection));
 
	// If the diffuse light intensity is over a given value, then add the specular component
	// Only calc the pow function when the diffuseIntensity is high (adding specular for high diffuse intensities only runs faster)
	// Put this as 0 for accuracy, and something high like 0.98 for speed
	if (diffuseIntensity > 0.98)
	{
		float fSpec = pow(specularIntensity, 64.0);
		vFragColour.rgb += vec3(fSpec * specularColour.rgb);
	}
}

Source code after the jump. Cheers!

Read the rest of this entry »

Comments
8 Comments »
Categories
Coding
Tags
Bump Mapping, C++, GIMP, GLSL, Normal Mapping, OpenGL
Comments rss Comments rss
Trackback Trackback

Anaglyphic 3D in GLSL

r3dux | May 9, 2011

I’ve been playing around with getting some red/cyan stereoscopic 3D working of late, and it’s all turned out rather well – take a look… (Red/Blue or Red/Cyan glasses are required for the effect to work):

Anaglyphic 3D in GLSL

Click for bigger version.

If you’ve got suitable glasses you should definitely see a 3D effect, although I don’t think me rescaling the image has done it any favours – so click the image to see the full sized version for the full effect.

The trick to this has been to render the scene twice to two separate FBO textures, then sample from the left and right textures to draw a fullscreen quad with a combined version as follows:

// Fragment shader to perform Analyphic 3D conversion of two textures from the left and right eyes
#version 330
 
uniform sampler2D leftEyeTexture;
uniform sampler2D rightEyeTexture;
 
in vec2 vTexCoord;
 
out vec4 vFragColour;
 
void main(void)
{
	vec4 leftFrag = texture(leftEyeTexture, vTexCoord);
	leftFrag = vec4(1.0, leftFrag.g, leftFrag.b, 1.0); // Left eye is full red and actual green and blue
 
	vec4 rightFrag = texture(rightEyeTexture, vTexCoord);
	rightFrag = vec4(rightFrag.r, 1.0, 1.0, 1.0); // Right eye is full green and blue and actual red
 
	// Multiply left and right components for final ourput colour
	vFragColour = vec4(leftFrag.rgb * rightFrag.rgb, 1.0); 
}

In the code itself, the torus’ spin around on the spot and look pretty good, although there’s no anti-aliasing as yet as I need to create some multisample buffers instead of straight/normal buffers for the FBO, but it’s not decided to play ball just yet – not to worry though, the hard part’s done and I’m sure multisampling will be sorted in a day or so. After that, I might give ColorCode3D (TM)(R)(C)(Blah) a go, as it seems to give a better colour representation whilst still allowing the same amount of depth as traditional anaglyphic techniques. Also, I’ve got to start using asymmetric frustums for the projection to minimise the likelihood of eye-strain, but I don’t see that as being too much of a problem.

Good times! =D

Comments
10 Comments »
Categories
Coding
Tags
3D, Anaglyphic 3D, Anaglyphs, Fragment, GLSL, OpenGL, Shader, Stereo
Comments rss Comments rss
Trackback Trackback

« Previous Entries

Translate

Categories

Archives

Tags

3D ActionScript ActionScript 3.0 Adobe AI Ballarat Bash C++ Class Convert CS4 Effect Error Film Flash FPS GLFW Glitch GLSL Hack How-To install Java Kinect Linux Live Mash-Up Microsoft Motion mount OpenGL Particle Problem PS3 Remix Retro script Slides Sound Ubuntu Video VirtualBox Wii Windows XBox

Gamercard

OpenR3dux

Misc.

Flattr this

RSS Feed

r3dux twitter feed



“Prepare not the path for the child; prepare the child for the path.”

rss Comments rss valid xhtml 1.1 design by jide powered by Wordpress get firefox