Simple GLSL Bump-Mapping / Normal-Mapping

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…

[nggallery id=5]

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!

Main C++ Source

#include <iostream>
#include <ctime>
#include <math.h>
 
#include <GL/glew.h              // Include glew, which calls GL.h for us
#include <GL/glfw.h>             // Include OpenGL Framework library
 
// OpenGL SuperBible utils
#include <GLTools.h>      
#include <GLMatrixStack.h>
#include <GLFrame.h>
#include <GLFrustum.h>
#include <GLGeometryTransform.h>
#include <GLBatch.h>
 
// Include the DevIL libraties for image loading
#define ILUT_USE_OPENGL
#include <IL/il.h>
#include <IL/ilu.h>
#include <IL/ilut.h>
 
#include "../../GLTools/r3dTools.h" // Include custom tools to do things like load images
 
using namespace std;
 
GLint   windowWidth  = 800;     // Define our window width
GLint   windowHeight = 600;     // Define our window height
GLfloat fieldOfView  = 45.0f;   // Frustrum field of view
GLfloat zNear        = 1.0f;    // Frustrum near distance
GLfloat zFar         = 100.0f;  // Frustrum far distance
GLint   frameCount   = 0;       // Keep track of how many frames we've drawn
 
GLFrame             viewFrame;
GLFrustum           viewFrustum;
GLTriangleBatch     torusBatch;
GLMatrixStack       modelViewMatrix;
GLMatrixStack       projectionMatrix;
GLGeometryTransform transformPipeline;
 
// Handle for our shader program
GLuint	            r3dShaderProgram;
 
// Locations of our UNIFORM variables.
//Type needs to be INT not UINT because if the location of the uniform variable cannot be found glGetUniformLocation returns -1!
GLint               locMVMatrix;
GLint               locMVPMatrix;
GLint               locNormalMatrix;
GLint               locLightPos;
GLint               locAmbientColour;
GLint               locDiffuseColour;
GLint               locSpecularColour;
GLint               locColourMap; // The texture map
GLint               locNormalMap; // The normal  map
 
// Handles for our textures
GLuint              textureID;
GLuint              normalMapID;
 
GLfloat vEyeLightPos[]    = { -20.0f, 20.0f, 100.0f };
GLfloat vAmbientColour[]  = { 0.1f, 0.1f, 0.1f, 1.0f };
GLfloat vDiffuseColour[]  = { 1.0f, 1.0f, 1.0f, 1.0f };
GLfloat vSpecularColour[] = { 1.0f, 1.0f, 1.0f, 1.0f };
 
void GLFWCALL resizeWindow(GLsizei theNewWidth, GLsizei theNewHeight)
{
	// Set our globals so we can query the window size at any time
	windowWidth  = theNewWidth;
	windowHeight = theNewHeight;
 
	// Set the viewport to the full size of the new window
	glViewport(0, 0, windowWidth, windowHeight); // 0, 0 is the BOTTOM-LEFT!!
 
	// Create the projection matrix, and load it on to the projection matrix stack
	viewFrustum.SetPerspective(fieldOfView, float(windowWidth)/float(windowHeight), zNear, zFar);
 
	projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix()); // Get the projection matrix from our frustum (defined in the line above) and load it into our proper projection matrix stack
 
	// Set the transformation pipeline to use the two matrix stacks
	transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
 
	checkGLError("Resize");
 
	cout << "Resized window." << endl;
}
 
 
// Function to set some initial OpenGL state-machine properties
void initGL()
{
	//  ----- Initialise GLEW -----
 
	GLenum err = glewInit();
	if (GLEW_OK != err)
	{
		cout << "GLEW initialisation error: " << glewGetErrorString(err) << endl;
		exit(-1);
	}
	cout << "GLEW intialised successfully. Using GLEW version: " << glewGetString(GLEW_VERSION) << endl;
 
	// Set the window title
	glfwSetWindowTitle("Bumpmap Test | r3dux.org");
 
	// Lock to vsync
	glfwSwapInterval(1);
 
	// Initialise all DevIL functionality
	ilutRenderer(ILUT_OPENGL); // Tell DevIL that we're using OpenGL for our rendering
	ilInit();
	iluInit();
	ilutInit();
	ilutRenderer(ILUT_OPENGL);	// Yes, this is here twice - but I'm never sure to call it before or after il/ilu/ilut initialisation...
 
	// Background
	glClearColor(0.0f, 0.0f, 0.0f, 1.0f );
 
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_CULL_FACE);
 
	viewFrame.MoveForward(4.0f);
 
	// Make the torus (0.8 diameter, 0.25 thickness diameter, 52 slices, 26 vertexes per slice)
	// Put the final number to 3 for a pyramid-torus thing =P
	gltMakeTorus(torusBatch, 0.8f, 0.25f, 52, 26);
 
	// Load the texture for the torus
	glActiveTexture(GL_TEXTURE0);
	textureID = loadImage("CoolTexture.tga", GL_LINEAR, GL_LINEAR, GL_CLAMP_TO_EDGE);
 
	glActiveTexture(GL_TEXTURE1);
	normalMapID = loadImage("CoolTextureNormalMap.tga", GL_LINEAR, GL_LINEAR, GL_CLAMP_TO_EDGE);
 
	// Load shaders and bind attributes
	r3dShaderProgram = gltLoadShaderPairWithAttributes("r3dBumpMapShader.vp",  "r3dBumpMapShader.fp", 3, // We're passing in 3 attributes per vertex
												        GLT_ATTRIBUTE_VERTEX,   "vVertex",
												        GLT_ATTRIBUTE_NORMAL,   "vNormal",
												        GLT_ATTRIBUTE_TEXTURE0, "vTexture0");
 
	locMVMatrix = glGetUniformLocation(r3dShaderProgram, "mvMatrix");
	checkValidUniformLocation(locMVMatrix, "mvMatrix");
 
	locMVPMatrix = glGetUniformLocation(r3dShaderProgram, "mvpMatrix");
	checkValidUniformLocation(locMVPMatrix, "mvpMatrix");
 
	locNormalMatrix = glGetUniformLocation(r3dShaderProgram, "normalMatrix");
	checkValidUniformLocation(locNormalMatrix, "normalMatrix");
 
	locLightPos = glGetUniformLocation(r3dShaderProgram, "vLightPosition");
	checkValidUniformLocation(locLightPos, "vLightPosition");
 
	locAmbientColour = glGetUniformLocation(r3dShaderProgram, "ambientColour");
	checkValidUniformLocation(locAmbientColour, "ambientColour");
 
	locDiffuseColour = glGetUniformLocation(r3dShaderProgram, "diffuseColour");
	checkValidUniformLocation(locDiffuseColour, "diffuseColour");
 
	locSpecularColour = glGetUniformLocation(r3dShaderProgram, "specularColour");
	checkValidUniformLocation(locSpecularColour, "specularColour");
 
	locColourMap = glGetUniformLocation(r3dShaderProgram, "colourMap"); // The texture map
	checkValidUniformLocation(locColourMap, "colourMap");
 
	locNormalMap = glGetUniformLocation(r3dShaderProgram, "normalMap"); // The normal map
	checkValidUniformLocation(locNormalMap, "normalMap");
 
	checkGLError("End of init");
}
 
// Function to draw our OpenGL scene
void drawScene()
{
   	// Clear the window and the depth buffer
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
	modelViewMatrix.PushMatrix(viewFrame);
 
        modelViewMatrix.Translate(0.0, 0.0, -1.2f);
 
		// Rotate around the y-axis
		modelViewMatrix.Rotate(frameCount * 0.005, 0.0f, 1.0f, 0.0f);
 
		// Rotate around the x and z axis'
		// In the below, the framerate multiplier is the rate of change, and 180 is how many degrees to rotate through + or - (because sin/cos go -1 to +1)
		modelViewMatrix.Rotate(sin(frameCount * 0.004) * 180.0f, 1.0f, 0.0f, 0.0f);
		modelViewMatrix.Rotate(cos(frameCount * 0.004) * 180.0f, 0.0f, 1.0f, 0.0f);
 
		/* // Uncomment to move the light around so it bounces from left to right etc.
		static bool lightMoveLeft = true;
		static float w = 400.0f;
		if (vEyeLightPos[0] > w) { lightMoveLeft = true; vEyeLightPos[0] = w; }
		if (vEyeLightPos[0] < -w) { lightMoveLeft = false; vEyeLightPos[0] = -w; }
		if (lightMoveLeft == true)
		{
			vEyeLightPos[0]--;
		}
		else
		{
			vEyeLightPos[0]++;
		} */
 
		// Bind our texture to the appropriate texture image unit
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, textureID);
 
		glActiveTexture(GL_TEXTURE1);
		glBindTexture(GL_TEXTURE_2D, normalMapID);
 
		glUseProgram(r3dShaderProgram); // Specify our shader program
 
		// Specify all the uniform variables for our shader program
		glUniform4fv(locAmbientColour,      1, vAmbientColour);
		glUniform4fv(locDiffuseColour,      1, vDiffuseColour);
		glUniform4fv(locSpecularColour,     1, vSpecularColour);
 
		glUniform1i(locColourMap, 0); // Specify that our texture (called "colourMap" in the shader, is on texture unit 0)
		glUniform1i(locNormalMap, 1); // Specify that our texture (called "normalMap" in the shader, is on texture unit 1)
 
		glUniform3fv(locLightPos,           1, vEyeLightPos);
		glUniformMatrix4fv(locMVMatrix,     1, GL_FALSE, transformPipeline.GetModelViewMatrix());
		glUniformMatrix4fv(locMVPMatrix,    1, GL_FALSE, transformPipeline.GetModelViewProjectionMatrix());
		glUniformMatrix3fv(locNormalMatrix, 1, GL_FALSE, transformPipeline.GetNormalMatrix());
 
		torusBatch.Draw();
 
    modelViewMatrix.PopMatrix();
 
    // ----- Stop Drawing Stuff! ------
 
    glfwSwapBuffers(); // Swap the buffers to display the scene (so we don't have to watch it being drawn!)
 
} // End of drawScene function
 
 
// Our main function
int main()
{
	srand((unsigned)time(NULL)); // Seed the random number generator
 
	// Define our buffer settings
	int redBits    = 8, greenBits = 8,    blueBits    = 8;
	int alphaBits  = 8, depthBits = 24,   stencilBits = 8;
 
	// Flag to keep our main loop running
	bool running = true;
 
	// Initialise glfw
	glfwInit();
 
	// Ask for 8x AntiAliasing (this doesn't mean we'll get it - it'll work only if the GLX_ARB_multisample extension is available)
	glfwOpenWindowHint(GLFW_FSAA_SAMPLES, 8);
 
	// Create a window
	if(!glfwOpenWindow(windowWidth, windowHeight, redBits, greenBits, blueBits, alphaBits, depthBits, stencilBits, GLFW_WINDOW))
	{
		cout << "Failed to open window!" << endl;
		glfwTerminate();
		return 0;
	}
 
	// Call our initGL function to set up our OpenGL options
	initGL();
 
	// Specify the callback function for when the window is resized.
	glfwSetWindowSizeCallback(resizeWindow);
 
	while (running == true)
	{
		// Draw our scene
		drawScene();
 
		// Increase our frame counter
		frameCount++;
 
		checkGLError("Main loop");
 
		// Exit if ESC was pressed or the window was closed
		running = !glfwGetKey(GLFW_KEY_ESC) && glfwGetWindowParam( GLFW_OPENED);
	}
 
	glfwTerminate();
 
	return 0;
}

I’ve used a couple of functions from my r3dTools package in the above code, so I’ll add those in here and you can use ’em if you want:

void checkValidUniformLocation(GLint locationValue, std::string uniformName)
{
	if (locationValue == -1)
	{
		std::cout << "Could not locate " << uniformName << " uniform in shader!" << std::endl;
		exit(-1);
	}
	else
	{
		std::cout << "Found " << uniformName << " uniform at location: " << locationValue << std::endl;
	}
}
 
// Function to show any current stack depths. These are fixed functionality stacks, not GLSL MartrixStacks...
void displayStackDepths()
{
	// Temp variables used to display current and maximum stack depths in case of an overflow or underflow
	GLint depth, max;
 
	glGetIntegerv(GL_MODELVIEW_STACK_DEPTH, &depth);
	glGetIntegerv(GL_MAX_MODELVIEW_STACK_DEPTH, &max);
	std::cout << "ModelView stack depth is: " << depth << " (Max is: " << max << ")" << std::endl;
 
	glGetIntegerv(GL_PROJECTION_STACK_DEPTH , &depth);
	glGetIntegerv(GL_MAX_PROJECTION_STACK_DEPTH, &max);
	std::cout << "Projection stack depth is: " << depth << " (Max is: " << max << ")" << std::endl;
 
	glGetIntegerv(GL_TEXTURE_STACK_DEPTH, &depth);
	glGetIntegerv(GL_MAX_TEXTURE_STACK_DEPTH, &max);
	std::cout << "Texture stack depth is: " << depth << " (Max is: " << max << ")" << std::endl;
 
	glGetIntegerv(GL_ATTRIB_STACK_DEPTH, &depth);
	glGetIntegerv(GL_MAX_ATTRIB_STACK_DEPTH, &max);
	std::cout << "Attribute stack depth is: " << depth << " (Max is: " << max << ")" << std::endl;
 
	glGetIntegerv(GL_CLIENT_ATTRIB_STACK_DEPTH, &depth);
	glGetIntegerv(GL_MAX_CLIENT_ATTRIB_STACK_DEPTH, &max);
	std::cout << "Client attribute stack depth is: " << depth << " (Max is: " << max << ")" << std::endl;
 
	glGetIntegerv(GL_COLOR_MATRIX_STACK_DEPTH, &depth);
	glGetIntegerv(GL_MAX_COLOR_MATRIX_STACK_DEPTH, &max);
	std::cout << "Colour matrix stack depth is: " << depth << " (Max is: " << max << ")" << std::endl;
 
	glGetIntegerv(GL_NAME_STACK_DEPTH, &depth);
	glGetIntegerv(GL_MAX_NAME_STACK_DEPTH, &max);
	std::cout << "Name stack depth is: " << depth << " (Max is: " << max << ")" << std::endl;
}
 
// Function to check if OpenGL is having issues - pass it a unique string of some kind to track down where in the code it's moaning
void checkGLError(const char * errorLocation)
{
	unsigned int gle = glGetError();
 
	if (gle != GL_NO_ERROR)
	{
		std::cout << "GL Error discovered from caller " << errorLocation << ": ";
 
		switch (gle)
		{
			case GL_INVALID_ENUM:
				std::cout << "Invalid enum." << std::endl;
				break;
 
			case GL_INVALID_VALUE:
				std::cout << "Invalid value." << std::endl;
				break;
 
			case GL_INVALID_OPERATION:
				std::cout << "Invalid Operation." << std::endl;
				break;
 
			case GL_STACK_OVERFLOW:
				std::cout << "Stack overflow." << std::endl;
				displayStackDepths();
				break;
 
			case GL_STACK_UNDERFLOW:
				std::cout << "Stack underflow." << std::endl;
				displayStackDepths();
				break;
 
			case GL_OUT_OF_MEMORY:
				std::cout << "Out of memory." <<  std::endl;
				break;
 
			default:
				std::cout << "Unknown error! Enum code is: " << gle << std::endl;
				break;
 
		} // End of switch
 
	} // End of if error detected
 
} // End of checkGLError function
 
/ Function load a image using DevIL, turn it into a texture, and return the texture ID as a GLuint for use
// Note: This function is passed default values of GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR and GL_CLAMP for the 2nd, 3rd and 4th values
GLuint loadImage(const char* theFileName, GLenum minFilter, GLenum magFilter, GLenum wrapFilter)
{
	std::cout << "in loadImage... If you sefault here you've forgotten to initialise DevIL!!!" << std::endl;
 
	ILuint imageID;                     // Create a image ID as a ULuint
 
	GLuint textureID;                   // Create a texture ID as a GLuint
 
	ILboolean success;                  // Create a flag to keep track of success/failure
 
	ILenum error;                       // Create a flag to keep track of the IL error state
 
	ilGenImages(1, &imageID);           // Generate the image ID
 
	ilBindImage(imageID);               // Bind the image
 
	success = ilLoadImage(theFileName); // Load the image file
 
	// If we managed to load the image, then we can start to do things with it...
	if (success)
	{
		// If the image is flipped (i.e. upside-down and mirrored, flip it the right way up!)
		ILinfo ImageInfo;
		iluGetImageInfo(&ImageInfo);
		if (ImageInfo.Origin == IL_ORIGIN_UPPER_LEFT)
		{
			iluFlipImage();
		}
 
		// Convert our image to RGB format with unsigned byte packing
		// NOTE: If your image contains alpha channel you can replace IL_RGB with IL_RGBA
		success = ilConvertImage(IL_RGB, IL_UNSIGNED_BYTE);
 
		// Quit out if we failed the conversion
		if (!success)
		{
			error = ilGetError();
			std::cout << "Image conversion failed on image: " << theFileName << std::endl;
			std::cout << "IL reports error: " << error << " - " << iluErrorString(error) << std::endl;
			exit(-1);
		}
 
		// Generate a new texture
		glGenTextures(1, &textureID);
 
		// Bind the texture to a name
		glBindTexture(GL_TEXTURE_2D, textureID);
 
		// Set texture interpolation method to the highest visual quality it can be:
		// GL_LINEAR_MIPMAP_LINEAR for minification - i.e. trilinear filtering
		// GL_LINEAR for magnification (choices are either GL_NEAREST or GL_LINEAR - we do not use any MIPMAP settings for magnification!!)
		if(magFilter == GL_LINEAR_MIPMAP_LINEAR || magFilter == GL_LINEAR_MIPMAP_NEAREST || magFilter == GL_NEAREST_MIPMAP_LINEAR || magFilter == GL_NEAREST_MIPMAP_NEAREST)
		{
			std::cout << "Bad Texture Magnification Filter: You can only use MIPMAPs for the minification filter, not the magnification filter." << std::endl;
			std::cout << "Setting texture magnification filter to GL_LINEAR." << std::endl;
			magFilter = GL_LINEAR;
		}
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
 
		// Set texture clamping method
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapFilter);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapFilter);
 
		// Specify the texture specification
		glTexImage2D(GL_TEXTURE_2D, 				// Type of texture
		             0,								// Pyramid level (for mip-mapping) - 0 is the top level
		             ilGetInteger(IL_IMAGE_BPP),	// Image colour depth
		             ilGetInteger(IL_IMAGE_WIDTH),	// Image width
		             ilGetInteger(IL_IMAGE_HEIGHT),	// Image height
		             0,								// Border width in pixels (can either be 1 or 0)
		             ilGetInteger(IL_IMAGE_FORMAT),	// Image format (i.e. RGB, RGBA, BGR etc.)
		             GL_UNSIGNED_BYTE,				// Image data type
		             ilGetData());					// The actual image data itself
 
		// If we're using mipmaps then generate them. Note: This requires OpenGL 3.0 or higher
		if(minFilter == GL_LINEAR_MIPMAP_LINEAR || minFilter == GL_LINEAR_MIPMAP_NEAREST || minFilter == GL_NEAREST_MIPMAP_LINEAR || minFilter == GL_NEAREST_MIPMAP_NEAREST)
		{
			glGenerateMipmap(GL_TEXTURE_2D);
		}
 
	}
	else // If we failed to open the image file in the first place...
	{
		error = ilGetError();
		std::cout << "Image loading failed on image: " << theFileName << std::endl;
		std::cout << "IL reports error: " << error << " - " << iluErrorString(error) << std::endl;
		exit(-1);
	}
 
	ilDeleteImages(1, &imageID); // Because we have already copied image data into texture data we can release memory used by image.
 
	std::cout << "Texture creation successful." << std::endl;
 
	// Check for any OpenGL errors (providing the location we called the function from)
	checkGLError("End of loadImage");
 
	return textureID; // Return the GLuint to the texture so you can use it!
}

8 thoughts on “Simple GLSL Bump-Mapping / Normal-Mapping”

  1. Hi your bump mapping shader does not work on my old ati card. I however have found one somebody wrote and it does work with one exception i get flashing textures. This greatly reduces the effect and does not look professional. Can you please take a look and make some minor modifications to the shader programs please.

    [fragment shader]
    uniform sampler2D TexUnit;
    uniform sampler2D BaseTexUnit;
    uniform vec4 SurfaceColor;
    uniform float AmbientFactor;
    uniform float DiffuseFactor;
    uniform float SpecularFactor;
    uniform float Shininess;

    varying vec3 LightDir;
    varying vec3 ViewDir;

    void main()
    {
    vec3 normal = vec3(texture2D(TexUnit, gl_TexCoord[0].st));
    vec3 base = vec3(texture2D(BaseTexUnit, gl_TexCoord[0].st)); //shomari

    normal = vec3(2.0)*normal – vec3(1.0);
    normal = normalize(normal);
    vec3 light = normalize(LightDir);
    vec3 view = normalize(ViewDir);
    float diffuseCoeff = max(dot(light,normal),0.0);
    float specularCoeff = 0.0;
    if (diffuseCoeff > 0.0) {
    vec3 reflectedLight = reflect(-light,normal);
    specularCoeff = pow(max(dot(view, reflectedLight),0.0), Shininess) ;
    }

    vec3 color =
    vec3(DiffuseFactor*diffuseCoeff + AmbientFactor) * base +
    vec3(specularCoeff*SpecularFactor) ;

    color = min(color,vec3(1.0));
    gl_FragColor = vec4(color,1.0);
    }

    [vertex shader]
    uniform vec4 LightPos;

    attribute vec3 Tangent; // align with s-coord of texture map

    varying vec3 ViewDir;
    varying vec3 LightDir;

    const bool shaderTool = true; // using Apple’s Shader Builder Tool?

    void main() {
    vec3 eyeNormal = normalize(gl_NormalMatrix * gl_Normal);
    vec3 eyeTangent = gl_NormalMatrix * (shaderTool ? vec3(-1,0,0) : Tangent);
    vec3 eyeBinormal = normalize(cross(eyeNormal, eyeTangent));
    eyeTangent = cross(eyeBinormal, eyeNormal);
    vec4 eyeVertex = gl_ModelViewMatrix*gl_Vertex;
    vec3 eyeLightDir = normalize(vec3(LightPos – eyeVertex));
    LightDir.x = dot(eyeLightDir,eyeTangent);
    LightDir.y = dot(eyeLightDir,eyeBinormal);
    LightDir.z = dot(eyeLightDir,eyeNormal);
    vec3 eyeViewDir = normalize(-eyeVertex.xyz);
    ViewDir.x = dot(eyeViewDir,eyeTangent);
    ViewDir.y = dot(eyeViewDir,eyeBinormal);
    ViewDir.z = dot(eyeViewDir,eyeNormal);
    gl_TexCoord[0] = gl_MultiTexCoord0;
    gl_Position = ftransform();
    }

    1. My shaders and source code probably don’t work on your old ATI card because of the GLSL version – it’s likely that your old ATI card doesn’t support GLSL #330, but it will probably support #120 or #130 or #140 or whatever.

      Give me the link to where you got the shaders you provided and I’ll take a look and see what I can do.

  2. I have attempted to implement this using g-truc’s GLM, and I’ve noticed that this shader draws the geometry only in white. I have adjusted color, etc.

    My implementation had to vary slightly by sending the M and V components separately and multiplying them, otherwise its precisely the same.

    Question regarding VBO binding for GLSL’s in vecs in the vertex shader, is there any trouble with using glVertexAtrib related functions and treating this as an attrib? (I am assuming that’s ok) Another question is, do you have to use the older-style glAttribArray types of GL_TEX_COORD_ARRAY etc or can you use bind to them as above?

    Secondly, the model does not seem to respond to camera movements.
    Uniform(&Umvp,”mvpMatrix”);
    Uniform(&Um,”mMatrix”);
    Uniform(&Uv,”vMatrix”);
    glUniformMatrix4fv(Umvp, 1, GL_FALSE, &camera.MVP[0][0]);
    glUniformMatrix4fv(Um, 1, GL_FALSE, &camera.ModelMatrix[0][0]);
    glUniformMatrix4fv(Uv, 1, GL_FALSE, &camera.ViewMatrix[0][0]);

    The camera is constructed with GLM’s camera used in opengl-tutorial #13. This tutorial seems simpler than that tutorial in its approach on purpose, but I’m not sure how to make it go with the same math setup.

    1. Hi there,

      I’m afraid I don’t know the answer to your attribute questions – I’ve only ever used the helper code from the OpenGL SuperBible to bind attributes and such when I was playing with shaders

      However, I’ve wanted to know the deal is with GLM for a while, so I’ve basically taken the day to hack around and get it to play ball. As such, you can find a working version of the bump mapping source which uses GLM for all matrix and vector math here. It still uses the SuperBible shader loader and vertex generation for the torus’.

      It’s was hacky to being with, and then I bolted on the camera… and now it’s really hacky ;-) Use W/S/A/D and the mouse to move around, the light source is in eye coordinates so attached to the camera, you’ll have to convert the light eye-coords into world coords to fix it in place.

      Hope this helps, at least a bit.

      1. It says the link is not found, however here is a nice camera with GLM based on GLM tutorials. I’d like to see your demo though!

        class GLMCamera {
        public:
        glm::vec3 position;
        glm::vec3 direction;
        glm::vec3 right;
        glm::vec3 up;
        float horizontal,vertical,initialfov,fov,speed,mouseSpeed;
        glm::mat4 ViewMatrix;
        glm::mat4 ProjectionMatrix;
        glm::mat4 ModelMatrix;
        glm::mat4 ModelViewMatrix;
        glm::mat3 ModelView3x3Matrix;
        glm::mat3 NormalMatrix;
        glm::mat4 MVP;
        int cumulativeMouseWheel;
        float Near,Far,Aspect;
        GLMCamera() {
        position.x=0;
        position.y=0;
        position.z=5;
        up.x=0;
        up.y=1;
        up.z=0;
        horizontal=3.14f;
        vertical=0.0f;
        initialfov=45.0f;
        speed=3.0f;
        mouseSpeed=0.005f;
        cumulativeMouseWheel=0;
        fov=0.0f;
        Near=0.001f;
        Far=100.0f;
        Aspect=4.0f/3.0f; // set later to display.aspect or window aspect
        ModelMatrix = glm::mat4(1.0);
        }
        void Upload() {
        glMatrixMode(GL_PROJECTION);
        glLoadMatrixf((const GLfloat*)&ProjectionMatrix[0]);
        glMatrixMode(GL_MODELVIEW);
        glLoadMatrixf((const GLfloat*)&ModelViewMatrix[0]);
        }
        void MouseChangeOrientation() {
        float deltaT=FRAMETIME;
        // Compute new orientation
        horizontal = 3.14f + mouseSpeed * float(display.w2-input.mxi);
        vertical = mouseSpeed * float(display.h2-input.myi);

        // Direction : Spherical coordinates to Cartesian coordinates conversion
        direction.x= cos(vertical) * sin(horizontal);
        direction.y= sin(vertical);
        direction.z= cos(vertical) * cos(horizontal);
        right.x= sin(horizontal – HALF_PIf);
        right.y= 0;
        right.z= cos(horizontal – HALF_PIf);
        up = glm::cross( right, direction );

        // Move forward
        if ( input.KeyDown(DX_W) || input.KeyDown(DX_UP) ) position += direction * deltaT * speed;
        // Move backward
        if ( input.KeyDown(DX_S) || input.KeyDown(DX_DOWN) ) position -= direction * deltaT * speed;
        // Strafe right
        if ( input.KeyDown(DX_D) || input.KeyDown(DX_RIGHT) ) position += right * deltaT * speed;
        // Strafe left
        if ( input.KeyDown(DX_A) || input.KeyDown(DX_LEFT) ) position -= right * deltaT * speed;

        // “zoom”
        if ( input.wheelDown ) cumulativeMouseWheel++;
        else if ( input.wheelUp ) cumulativeMouseWheel–;

        fov = initialfov – 5.0f * (float) cumulativeMouseWheel;
        UpdateMatrices();
        }
        void UpdateMatrices() {
        // Projection matrix : 45° Field of View, 4:3 ratio, display range : 0.1 unit 100 units
        ProjectionMatrix = glm::perspective(fov, Aspect, Near,Far);
        // Camera matrix
        ViewMatrix = glm::lookAt(
        position, // Camera is here
        position+direction, // and looks here : at the same position, plus “direction”
        up // Head is up (set to 0,-1,0 to look upside-down)
        );
        ModelViewMatrix = ViewMatrix * ModelMatrix;
        ModelView3x3Matrix = glm::mat3(ModelViewMatrix);
        NormalMatrix = glm::transpose(glm::inverse(ModelView3x3Matrix));
        MVP = ProjectionMatrix * ViewMatrix * ModelMatrix;
        }
        void fromLap( LookAtPerspective *lap ) {
        up.x=(float)lap->up.x;
        up.y=(float)lap->up.y;
        up.z=(float)lap->up.z;
        position.x=(float)lap->eye.x;
        position.y=(float)lap->eye.y;
        position.z=(float)lap->eye.z;
        direction.x=(float)lap->center.x;
        direction.y=(float)lap->center.y;
        direction.z=(float)lap->center.z;
        fov=(float)lap->halfFOV;
        UpdateMatrices();
        }
        void Debug() {
        Blending(none);
        glColor3d(1.0,0.8,0.3);
        MultilineText(
        FORMAT(buf,1024,
        “GLMCamera:\nPOS: %f,%f,%f DIR: %f,%f,%f RIGHT: %f,%f,%f UP: %f,%f,%f\nHORIZ: %f VERT: %f initial-FOV: %f FOV: %f SPEED: %f mouseSPEED: %f\n”
        “V: %1.3f %1.3f %1.3f %1.3f P: %1.3f %1.3f %1.3f %1.3f M: %1.3f %1.3f %1.3f %1.3f MV: %1.3f %1.3f %1.3f %1.3f MVP: %1.3f %1.3f %1.3f %1.3f\n”
        ” %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f\n”
        ” %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f\n”
        ” %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f\n”,
        position.x, position.y, position.z,
        direction.x, direction.y, direction.z,
        right.x, right.y, right.z,
        up.x, up.y, up.z,
        horizontal,vertical,initialfov,fov,speed,mouseSpeed,
        ViewMatrix.value[0][0], ViewMatrix.value[0][1], ViewMatrix.value[0][2], ViewMatrix.value[0][3],
        ProjectionMatrix.value[0][0], ProjectionMatrix.value[0][1], ProjectionMatrix.value[0][2], ProjectionMatrix.value[0][3],
        ModelMatrix.value[0][0], ModelMatrix.value[0][1], ModelMatrix.value[0][2], ModelMatrix.value[0][3],
        ModelViewMatrix.value[0][0], ModelViewMatrix.value[0][1], ModelViewMatrix.value[0][2], ModelViewMatrix.value[0][3],
        MVP[0][0], MVP[0][1], MVP[0][2], MVP[0][3],
        ViewMatrix.value[1][0], ViewMatrix.value[1][1], ViewMatrix.value[1][2], ViewMatrix.value[1][3],
        ProjectionMatrix.value[1][0], ProjectionMatrix.value[1][1], ProjectionMatrix.value[1][2], ProjectionMatrix.value[1][3],
        ModelMatrix.value[1][0], ModelMatrix.value[1][1], ModelMatrix.value[1][2], ModelMatrix.value[1][3],
        ModelViewMatrix.value[1][0], ModelViewMatrix.value[1][1], ModelViewMatrix.value[1][2], ModelViewMatrix.value[1][3],
        MVP[1][0], MVP[1][1], MVP[1][2], MVP[1][3],
        ViewMatrix.value[2][0], ViewMatrix.value[2][1], ViewMatrix.value[2][2], ViewMatrix.value[2][3],
        ProjectionMatrix.value[2][0], ProjectionMatrix.value[2][1], ProjectionMatrix.value[2][2], ProjectionMatrix.value[2][3],
        ModelMatrix.value[2][0], ModelMatrix.value[2][1], ModelMatrix.value[2][2], ModelMatrix.value[2][3],
        ModelViewMatrix.value[2][0], ModelViewMatrix.value[2][1], ModelViewMatrix.value[2][2], ModelViewMatrix.value[2][3],
        MVP[2][0], MVP[2][1], MVP[2][2], MVP[2][3],
        ViewMatrix.value[3][0], ViewMatrix.value[3][1], ViewMatrix.value[3][2], ViewMatrix.value[3][3],
        ProjectionMatrix.value[3][0], ProjectionMatrix.value[3][1], ProjectionMatrix.value[3][2], ProjectionMatrix.value[3][3],
        ModelMatrix.value[3][0], ModelMatrix.value[3][1], ModelMatrix.value[3][2], ModelMatrix.value[3][3],
        ModelViewMatrix.value[3][0], ModelViewMatrix.value[3][1], ModelViewMatrix.value[3][2], ModelViewMatrix.value[3][3],
        MVP[3][0], MVP[3][1], MVP[3][2], MVP[3][3]
        ), 10, 100, 7, 8, 2, false
        );
        // glm::mat3 ModelView3x3Matrix;
        }
        };

        extern GLMCamera glmCamera;

        1. It was only through email that the link was not found, anyway. Thanks for getting back to me, I’ll try this.

          Question about the geometry:

          I get weird output and the light seems to penetrate the back faces, is there an easy way to avoid that when moving the camera around? The weird output is just that the sides are flipped or otherwise wrong. Do texcoords effect this, or normals, or both? I’ll try using this to see if it fixes the issues.

          1. With the “light penetrating the back faces” – I think that this is just due to the light being specified in eye coordinates, so when you move, the light also moves – it’s fixed in relation to the camera.

            As for the “sides are flipped or otherwise wrong” – I’m not entirely sure what you mean, sorry – maybe send me a screenshot at mail at r3dux dot org and I’ll take a look.

            Cheers!

            1. When implementing this in my engine, it actually turned out I had cross-mapped my normals to my texcoords when using glVertexAttribPtr, so I was seeing texcoords as only solid colors. I debugged this by setting glFragColor=final.r,UV.x,UVy .. I use that camera I provided, but now it can be simplified. However, it was not yours that I used, I used the #13 from OpenGL Tutorial . org and set it up for #version 120, though 330 would be fine too

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.