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…
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!
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:
// 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;
// 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;
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;
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!
Photoshop CS5 has a content-aware fill filter which will try to seamlessly remove objects from an image – and that’s great. But GIMP has the same functionality, right now – for free. And it’s a doddle to use…
I posted about a cleverly designed glass which spells out what you’re drinking through linking dots on the glass with the liquid colour the other day, but to get that image, I needed to do a little bit of manipulation first. For this example we’re going to be using GIMP with the Resynthesizer plugin (package name: gimp-resynthesizer):
1.) Get an Image to Work With
I wanted to use a picture of the glasses, but the bar across the top was too close to them for it to be a nice shot with enough white-space around it, so the first thing I did was just stab the Print-Screen key to get a screengrab:
Remember that to fill in the missing details, you need as much as possible of what should be there – that is, a very high background to selection ratio! If you have a picture of someone’s face taking up a large section of the image and you try to remove the face – where can the plug-in get data from to know what to replace it with? It can’t! So it’ll make a guess, and it’ll fail badly. On the other hand, if you have a large swathe of grass with a football on it, and you’re removing the football, the plug-in has all the surrounding image to consider when doing the replacement!
In this case, I kept as much of the background as possible in the image while I was replacing the section I wanted removed so the plug-in could use that data for replacing content.
2.) Select the Section to Remove
Because the bar is rectangular in shape, the rectangular selection tool was the easiest option to select it – if you’ve got a more ragged section then use the lassoo selection tool, or a quick-mask or whatever to get your selection; just make sure it’s pretty tight to what you want to remove…
3.) Run the Resynthesizer Plugin
Once you’ve got your selection (i.e. what you want to remove selected), just pick Filters | Map | Resynthesizer from the GIMP menu and use the checkboxes as ticked below:
4.) Admire Your Handiwork
The Resynthesizer plugin is a little bit curious, in that running it, then undoing it, then running it again will produce different results. The first two times I ran it on the exact same selection on the exact same image ended up with some artifacts of text being dragged in, but the third time did the entire thing cleanly.
If you end up with stray artifacts from other parts of the image, you can either re-run the resynthesizing process, or just select the artifacts and re-run resynthesizer on them to remove them (remembering to keep the selections tight to what you want removed).
That’s pretty awesome… Kudos to Paul Harrison for the plug-in – that’s some killer code – what a guy! =D
You’ve got to admit, that’s pretty awesome… (best viewed full screen so you can really see what going on, btw).
Update 2: Here’s a guide to using the resynthesizer plug-in, which does goes through the steps to modify the exact same images as in the video, but in GIMP! Sweet!
Update 3: I decided to write my own guide… =D
The GIMP .xcf original is available here, with a couple of layers worth of other things I tried (James Squire Golden Ale, a photo of a tabasco sauce bottle [as opposed to a painting]). The version I settled on for the bottle was pulled from this (link dead so removed) painting (more here), to try to blend in a little more than a photo would with the cartoon Vader.
Props to the French bloke who did the painting, and whoever created the original Vader cartoon. All copyrights are owned by their respective overlords etc.