normal maps, ambient occlusion baking tutorial (lots of pics)

okay so, this will teach you to bake ambient occlusion, and turn it into a color map, and a normal map. (this tutorial assumes you have slight knowledge of modeling, the UV editor, and sculpt mode)
also, ignore my spelling mistakes please :slight_smile:
Step one, model the low poly, and unwrap it with the UV editor, i wont go into detail about modeling or unwrapping.
http://i53.photobucket.com/albums/g75/Chaosdragondz/step1.jpg
Step two, duplicate that mesh, go into sculpt mode and add your details, when I finished, this is what I had:
http://i53.photobucket.com/albums/g75/Chaosdragondz/step2.jpg
here’s the low poly next to the high holy:
http://i53.photobucket.com/albums/g75/Chaosdragondz/step3.jpg
Now add a new image to bake the ambient occlusion onto


then start baking…

then when it finishes, save the texture it makes

here’s the texture on the low and high poly meshes:

Now, take that ambient occlusion and convert it to a normal map, you can use the GIMP, or Photoshop, or CrazyBump like I used, CrazyBump is a program just for generating normal maps from photographs or other pictures.

this is what CrazyBump generated next to the ambient occlusion

Next step, is to paint a color map, I used the baked ambient occlusion as a base, then pained over it with my tablet and photoshop, this is what the color map looks like applied to the low poly head:

Now, add a material to the head, and in the texture slots, make sure the color map is first, then below it the normal map, remember to set UV for both of em, and put Nor and Col for the appropriate ones.


Now that the textures are done, add an empty, and give it an always sensor, attached to this script:

import GameLogic as g
objlist = g.getCurrentScene().getObjectList()
g.setLogicTicRate(60.0)

-------------------------------------

ShaderObjects = [ objlist[‘OBCube’] ]
MaterialIndexList = [0,1]
GlobalAmbient = [0.39,0.35,0.32,1]
AmbF = 0.5

-------------------------------------

VertexShader = “”"
uniform mat4 mvi;
vec4 Tangent;

varying vec4 vcol;

varying vec3 lvec,vvec;

vec3 TBN(in vec3 by)
{
vec3 tangent = (Tangent.xyz);
vec3 binormal = cross(gl_Normal, Tangent.xyz)*Tangent.w;
vec3 normal = gl_Normal;

vec3 tvec;
tvec.x = dot(tangent,  by);
tvec.y = dot(binormal, by);
tvec.z = dot(normal,   by);
return tvec;

}

void main() {
gl_Position = ftransform();

// texcoord now (vec4())
Tangent = gl_MultiTexCoord1;

vec4 light0    = (mvi*gl_LightSource[0].position)-gl_Vertex;

vec4 view = mvi[3]-gl_Vertex;

lvec = TBN(light0.xyz);
vvec = TBN(view.xyz);

gl_TexCoord[0] = gl_MultiTexCoord0;

vcol =gl_Color;

}
“”"

FragmentShader = “”"
varying vec4 vcol;
varying vec3 lvec,vvec,hvec;

uniform sampler2D color;
uniform sampler2D bump;

void main() {
vec4 diffuse_color = vcol*gl_LightSource[0].diffuse;
vec4 specular_color = gl_FrontMaterial.specular;
vec4 colormap = texture2D(color, gl_TexCoord[0].st);

vec3 lv     = normalize(lvec);
vec3 vv     = normalize(vvec);
vec3 nv     = normalize(2.0*texture2D(bump, gl_TexCoord[0].st).xyz-1.0);

float diff     = max(dot(lv, nv), 0.0);
float spec    = pow(max(dot(reflect(-vv, nv), lv),0.0), gl_FrontMaterial.shininess);

vec4 diff_pass = colormap*(diff*diffuse_color);
vec4 spec_pass = spec*specular_color;    
gl_FragColor = vec4(0.1,0.1,0.1,1.0)+diff_pass+spec_pass;

}
“”"

def MainLoop ():
# for each object
for obj in ShaderObjects:

    mesh_index = 0
    mesh = obj.getMesh(mesh_index)

    while mesh != None:

        for mat in mesh.materials:
            
            # regular TexFace materials do NOT have this function
            if not hasattr(mat, "getMaterialIndex"):
                return
            
            mat_index = mat.getMaterialIndex()

            # find an index                
            found = 0
            for i in range(len(MaterialIndexList)):
                if mat_index == MaterialIndexList[i]:
                    found=1
                    break
            if not found: continue

            shader = mat.getShader()
            if shader != None:
                if not shader.isValid():
                    shader.setSource(VertexShader, FragmentShader,1)

            
                # shader.setAttrib(g.SHD_TANGENT)
                shader.setUniformDef('mvi', g.MODELVIEWMATRIX_INVERSE)
                shader.setSampler('color', 0)
                shader.setSampler('bump', 1)
                
        mesh_index += 1
        mesh = obj.getMesh(mesh_index)

-------------------------------------

MainLoop()

-------------------------------------

change “OBCube” to the name of the object that should be normal mapped, also make sure you have “use Blender Materials” enabled.

this is what I came up with, normal mapped is on the left, and not normal mapped is on the right:


Sure its not that great cuz I did it quickly, maybe you can do something better :smiley:
So thats pretty much it… have fun normal mapping

keep on Blendering too people!

Looks nice!!

Oh, and wrap the script in

 tags, it makes it easier to distinguish.

I continue the happy blendering theme!

<b>~~Stu_Flowers</b>

I dont think my graphics card supports the bump mapping blender uses.

It works in other games, but not in blender.

Great tutorial btw.

Excellent tutorial. You can especially see the benefit of the bump map in your final image on the nostrils and the mouth.

My lousy card doesn’t support bump maps (though it is lightning fast for a 4MB gfx card…), but it’s still worth knowing how to do it. spike1907 was saying a while back that you can code GLSL support detection and run your shader scripts based on what the player’s graphics card supports. One of the official graphics demos has a GLSL support detector in it, so I would say that would be a good place to start if anyone is interested in finding out how to do this. Then you would just have to program an initialization script that would start the shaders depending on the machine.

So sculpt mode work with triangles. Didn’t know that, I thought sharpconstruct had issues w/ triangles in the base mesh. Also so you first created your low poly unwrapped it then duplicated. When you take the duplicate and alter it does it retain the uv map layout of the first, assuming the alterations are only done in sculpt?

@ Jessegp

yes, the duplicate retains the unwrapped UV coordinates, it stays exactly the same after sculpting it, except its subdivided alot ofcourse

I just started working on a concept for a new character and I wanted to try my hand at normal maps so you’ve saved me about an hour. Thanks … my time is worth a lot of money LOL (I should say now I won’t cut into my homework time with blendering). I was wonder if you’ve consider the situation where you have to break up your low poly model into say the head and body… or more pieces. My machine will only push roughly 400k polys So I’ve been wondering about the best method for dividing up the model without creating issues. Such as the connecting edgeloop between the pieces. I wonder if there is a good method to fix the connecting loop into place so the sculpt tools wont muck it up. The bigger the diffence between the connecting loops the more of a seam issue you’ll create.

Not bad, but your script doesn’t work… I’ve had it working some months ago but i havent used it recently and couldn’t find it anywhere in my computer.

Oh, i must by stupid.:o But i dont have baking option.

@ Jessegp
by breaking up the low poly model, do you mean separating the UVs, or the mesh itself into different parts? I dont usually separate any parts of the meshes, I just keep it as one mesh cuz that makes for less of a hassle. i know theres someking of partial redraw option or something that hides part of the mesh when your sculpting, try looking into that

@ Zuker 12:
hm… I just copied the working NormalMap.py script and pasted it here so it should work… I dont know why it’s not working hm…

@ EST:
Are you sure you’re using Version 2.43?

Like i sed i am stupid…

Thanks.

The error im getting:

IndendationError: expected an intended block
PYTHON SCRIPT ERROR:
   File "normalmapscript.txt", line 81
     for obj in ShaderObjects:

IndendationError: expected an intended block
Compiled with python version 2.4.

It maybe because of my python version, because normalmaps worked before installing it. But im not really sure.

How much juice in the GE do you think that normal map stuff uses up?