Page 1 of 2 12 LastLast
Results 1 to 10 of 18

Thread: Normal mapping

  1. #1

    Normal mapping

    Has anyone done a simple demo about normal mapping with OpenGL? All it would need to do is draw a cube, using texture and normalmap texture, as a bonus even a specular map texture.

    It would be nice if it's possible without getting into pixel-shaders, but i know there are some demos using GL_ARB_multitexture. I have looked up some C sources but just can't get it working. Must be missing some detail. Best i have come up with is like in attached screenshot (you can see that it's actually just drawing the normal map, instead of using it for lighting, which in itself goes wrong).

    Also, i am aware there are 2 different kinds of normal maps. One where it multiplies the normalmap with models own face normals, and one where normalmap is the only thing used for lighting calculation. I'm actually more interested in the latter, because it would mean less memory use if i don't need vector arrays for normals.

    Well, i can give some converted code which has propably ton of bugs (it's fit for nxPascal engine):
    Code:
        model: TGLModel;
        texture, normalMap, cubemap: cardinal;
        dl: TDisplayList;
        tangentSpaceLight: array of TVector;
        sTangent, tTangent, normal: array of TVector;
    Code:
    procedure GenerateNormalisationCubeMap;
    var size: integer; offset, halfSize: single; data: array of byte;
    
      procedure Generate(arb: byte);
      var i, j, dp: integer; tempVector: TVector;
      begin
        dp:=0;
        for j:=0 to size-1 do
          for i:=0 to size-1 do begin
            case arb of
              0: begin // +X
                   tempVector.x:=halfSize;
                   tempVector.y:=-(j+offset-halfSize);
                   tempVector.z:=-(i+offset-halfSize);
                 end;
              1: begin // -X
                   tempVector.x:=-halfSize;
                   tempVector.y:=-(j+offset-halfSize);
                   tempVector.z:=(i+offset-halfSize);
                 end;
              2: begin // +Y
                   tempVector.x:=(i+offset-halfSize);
                   tempVector.y:=halfSize;
                   tempVector.z:=(j+offset-halfSize);
                 end;
              3: begin // -Y
                   tempVector.x:=(i+offset-halfSize);
                   tempVector.y:=-halfSize;
                   tempVector.z:=-(j+offset-halfSize);
                 end;
              4: begin // +Z
                   tempVector.x:=(i+offset-halfSize);
                   tempVector.y:=-(j+offset-halfSize);
                   tempVector.z:=halfSize;
                 end;
              5: begin // -Z
                   tempVector.x:=-(i+offset-halfSize);
                   tempVector.y:=-(j+offset-halfSize);
                   tempVector.z:=-halfSize;
                 end;
            end;
            tempVector:=Norm2(tempVector);
            tempVector.x:=0.5*tempVector.x+0.5;
            tempVector.y:=0.5*tempVector.y+0.5;
            tempVector.z:=0.5*tempVector.z+0.5;
            data[dp]:=round(tempVector.x*255);
            data[dp+1]:=round(tempVector.y*255);
            data[dp+2]:=round(tempVector.z*255);
            inc(dp,3);
          end;
        arb:=GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB+arb;
        glTexImage2D(arb, 0, GL_RGBA8, size, size, 0, GL_RGB,
          GL_UNSIGNED_BYTE, @data[0]);
      end;
    
    var i: integer;
    begin
      size:=32; offset:=0.5; halfSize:=size/2;
      setlength(data, size*size*3);
      for i:=0 to 5 do Generate(i);
    end;
    Code:
    procedure TForm1.FormCreate(Sender: TObject);
    var bmp: TBitmap; i: integer;
    begin
      clientwidth:=800; clientheight:=600;
      nx.CreateGlWindow(self);
      nx.Perspective(false); nx.DefaultLights;
    
      if not nx.GLInfo('GL_ARB_multitexture') then begin
        showmessage('GL_ARB_multitexture not supported!'); exit;
      end;
    
      tex.Options:=tex.Options+[toKeepData];
    
      model:=TGLModel.Create;
      model.LoadFromW3D('data\donut.w3d');
      model.LoadTextures('data');
      texture:=model.mat[0].texIndex;
      model.MakeDisplayList(dl);
      setlength(tangentSpaceLight, model.vCount);
      setlength(sTangent, model.fCount);
      setlength(tTangent, model.fCount);
      setlength(normal, model.fCount);
      for i:=0 to model.fCount-1 do CalculateTangentSpace(i);
    
      bmp:=TBitmap.Create;
      MakeBumpTexture(0, bmp);
     
      normalMap:=tex.AddTexture('bump','');
      tex.LoadBMPData(normalMap, bmp);
      tex.Restore(normalMap);
    
      normalMap:=tex.texture[normalMap].index;
      model.mat[0].texIndex:=normalMap;
      bmp.Free;
    
      cubemap:=tex.AddTexture('_cubemap_','');
      cubemap:=tex.texture[cubemap].index;
      glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, cubemap);
      GenerateNormalisationCubeMap;
      glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
      glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
      glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
      glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
      glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
      ...
    end;
    Code:
    procedure TForm1.Timer1Timer(Sender: TObject);
    var i, j: integer; objectLightPosition, lightVector: TVector;
    begin
      ...
      objectLightPosition:=vector(0, 5, -10);
    
      for i:=0 to model.fCount-1 do
        for j:=0 to 2 do begin
          lightVector:=VectorSub2(objectLightPosition, model.va[model.fa[i, j]]);
          tangentSpaceLight[model.fa[i, j]].x:=Dot(sTangent[i], lightVector);
          tangentSpaceLight[model.fa[i, j]].y:=Dot(tTangent[i], lightVector);
          tangentSpaceLight[model.fa[i, j]].z:=Dot(normal[i], lightVector);
        end;
      
      //Bind normal map to texture unit 0
      glBindTexture(GL_TEXTURE_2D, normalMap);
      glEnable(GL_TEXTURE_2D);
    
      //Bind normalisation cube map to texture unit 1
      glActiveTextureARB(GL_TEXTURE1_ARB);
      glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, CubeMap);
      glEnable(GL_TEXTURE_CUBE_MAP_ARB);
      glActiveTextureARB(GL_TEXTURE0_ARB);
    
      glVertexPointer(3, GL_FLOAT, 0, @model.va[0]);
      glEnableClientState(GL_VERTEX_ARRAY);
    
      glTexCoordPointer(2, GL_FLOAT, 0, @model.ta[0]);
      glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    
      glClientActiveTextureARB(GL_TEXTURE1_ARB);
      glTexCoordPointer(3, GL_FLOAT, 0, @tangentSpaceLight[0]);
      glEnableClientState(GL_TEXTURE_COORD_ARRAY);
      glClientActiveTextureARB(GL_TEXTURE0_ARB);
    
      glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB);
      glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE);
      glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_REPLACE);
    
      glActiveTextureARB(GL_TEXTURE1_ARB);
    
      glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB);
      glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE);
      glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_DOT3_RGB_ARB);
      glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB);
    
      glActiveTextureARB(GL_TEXTURE0_ARB);
    
      // Render Bumps
      glDrawElements(GL_TRIANGLES, model.fCount*3, GL_UNSIGNED_SHORT, @model.fa[0]);
    
      glDisable(GL_TEXTURE_2D);
    
      glActiveTextureARB(GL_TEXTURE1_ARB);
      glDisable(GL_TEXTURE_CUBE_MAP_ARB);
      glActiveTextureARB(GL_TEXTURE0_ARB);
    
      //disable vertex arrays
      glDisableClientState(GL_VERTEX_ARRAY);
    
      glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    
      glClientActiveTextureARB(GL_TEXTURE1_ARB);
      glDisableClientState(GL_TEXTURE_COORD_ARRAY);
      glClientActiveTextureARB(GL_TEXTURE0_ARB);
    
      //Return to standard modulate texenv
      glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    
      //Enable multiplicative blending
      glBlendFunc(GL_DST_COLOR, GL_ZERO);
      glEnable(GL_BLEND);
    
      glBindTexture(GL_TEXTURE_2D, Texture);
      glEnable(GL_TEXTURE_2D);
    
      glVertexPointer(3, GL_FLOAT, 0, @model.va[0]);
      glEnableClientState(GL_VERTEX_ARRAY);
    
      glNormalPointer(GL_FLOAT, 0, @model.va[0]);
      glEnableClientState(GL_NORMAL_ARRAY);
    
      glTexCoordPointer(2, GL_FLOAT, 0, @model.ta[0]);
      glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    
      // Render Object
      glDrawElements(GL_TRIANGLES, model.fCount*3, GL_UNSIGNED_SHORT, @model.fa[0]);
    
      //Disable texture
      glDisable(GL_TEXTURE_2D);
    
      //disable vertex arrays
      glDisableClientState(GL_VERTEX_ARRAY);
      glDisableClientState(GL_NORMAL_ARRAY);
      glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    
      //Disable blending if it is enabled
      glDisable(GL_BLEND);
    
      nx.Flip;
    But of course, if this would be simpler or more efficient to do with pixel-shaders, then i will of course try that instead. I just don't know about this topic much.
    Attached Images Attached Images
    Last edited by User137; 14-07-2012 at 04:23 PM.

  2. #2
    Started research on GLSL, which seems to be mainstream for pixel/fragment-shading languages. What i gathered so far:

    http://oss.sgi.com/projects/ogl-samp...ent_shader.txt
    http://en.wikipedia.org/wiki/OpenGL_...ragment_shader
    http://nehe.gamedev.net/article/glsl...duction/25007/

    GL functions that are related:
    glCreateProgram
    glShaderSource
    glUseProgram
    glBindFragDataLocation
    glValidateProgram
    glLinkProgram
    glAttachShader
    glGetFragDataLocation
    glDetachShader
    glDeleteProgram

    I'm also assuming that i still need to use multitexturing, but not cube-mapping.

    edit: Finally it's working. GL code is much easier with GLSL than the above early attempt. But the shader code is beyond understanding for some parts, luckily there is ready code in the net. Shader in taken screenshot only supports 1 light source with its ambient, diffuse and position.
    Attached Images Attached Images
    Last edited by User137; 17-07-2012 at 09:09 PM.

  3. #3
    If you want to use normal mapping to simulate bump surfaces, then the typical approach would be:

    1) Creating normal map. You seem to do this in code, but you can also use free tools like this one. The normal map can be loaded using typical format like GL_RGBA.
    2) In addition to vertex normals, you will also need to generate tangent and binormal vectors. This can be tricky, search for it on Internet.
    3) Pass vertex normals, tangents and binormals to vertex shader, where you will also receive information about your light sources. You will need to convert your light source parameters such as direction into tangent space (this is why you need normals, tangents and binormals, to form the needed 3D matrix). Send these parameters to fragment shader.
    4) In fragment shader, you receive interpolated light source parameters in tangent space along with normal map texture. Load/decode normal value from normal map texture, which should be by default in tangent space; using this information and interpolated light parameters all in tangent space, do the illumination calculation. You should receive perfect normal mapping surface.

    I once provided bump-mapping example in Asphyre 4, which used DX9 and HLSL shaders. You could probably translate shader code into GLSL easily.

    P.S. You don't need multitexturing, cube texturing or other weird stuff for this.

  4. #4
    1) Yeah that was done in code, but for this demo i did after-edit with Gimp to smooth it, and save to PNG. And i did notice there's alot going on with the normal map. It seems like lighting is little different on each face of the cube, they take the light coming from different angle... Guess it's about what you explain in 2).
    edit: But here i am assuming that everything would be automatically fixed if i only had 1 texture that covers whole object. Not so that it's repeating itself, but each face being separate part of the texture.

    And i did not need even multitexturing luckily Those examples were just too good. I just updated nxPascal SVN and here is source code, if someone wants to try it.
    ( There btw: http://code.google.com/p/nxpascal/ )

    Then this just needs to be wrapped in a class for even easier use. Most of future additions to this should be just in the separate custom shader file and with its Uniform parameters.

    1 more thing, i had to use vertex shader aswell to get something showing. Is this really necessary? Maybe it's preparing the data somehow to format that's usable for fragment shader. Maybe even a performance boost of some sort?

    edit2: I have now tested that it still works on a surface of a sphere. This shader takes the vertex normals into account, so it's not true normal map, but there are uses for both styles.
    Attached Files Attached Files
    Last edited by User137; 18-07-2012 at 12:00 AM.

  5. #5
    I was trying to display some Skyrim objects that have both color and normalmap. The technique it use is not bump-mapping i believe. You can see example here.
    whereas typical bump-map looks like this.

    It is hard to find shaders that can render it I have tried modifying the code myself a bit too, but the math behind them is still complicated. Basically neither shader should not include word "gl_Normal", in my opinion. The normal in pixel position is exactly that of normalMap texture in those coords. Every shader that i have found is using gl_Normal.

    This also explains a bit:
    http://mvarts.wordpress.com/2011/03/...t-compression/
    Normal mapping is an application of bump mapping, and was introduced by Peercy et al. [2]. While bump mapping perturbs the existing surface normals of an object, normal mapping replaces the normals entirely. A normal map is a texture that stores normals. These normals are usually stored as unit-length vectors with three components: X, Y and Z. Normal mapping has significant performance benefits over bump mapping, in that far fewer operations are required to calculate the surface lighting.
    Last edited by User137; 02-10-2012 at 12:46 PM.

  6. #6
    Quote Originally Posted by User137 View Post
    It is hard to find shaders that can render it I have tried modifying the code myself a bit too, but the math behind them is still complicated. Basically neither shader should not include word "gl_Normal", in my opinion. The normal in pixel position is exactly that of normalMap texture in those coords. Every shader that i have found is using gl_Normal.
    In order to use normal mapping, you either need to work in tangent space altogether or transform the texture normal from tangent space to world space in pixel shader. Because of this, at one point or another you will have to pass vertex normal, tangent and binormal vectors either to vertex shader only or both, while the texture normal is used in pixel shader as a final step.

  7. #7
    That is still too complicated instructions. Why would i need to pass vertex normal? I do not see why vertex-shader would use it. All it needs to do is pass light-vector to fragment shader, propably multiplied by modelview matrix, or inverse of it. But i don't know how to do any of that, especially the part in fragment shader.

    Well, i have something like this in frag (overall doesn't work yet, but i feel like this might be close to right):
    Code:
    uniform sampler2D colorMap;
    uniform sampler2D normalMap;
    varying vec3 lightDir;
    void main()
    {
        vec3 l = lightDir;
        vec3 n = normalize(texture2D(normalMap, gl_TexCoord[0].st).xyz * 2.0 - 1.0);
        float power = dot(n, l);
        if (power<0.0) power = 0.0;
        vec4 ambient = vec4(0.2, 0.2, 0.2, 1.0);
        vec4 diffuse = vec4(0.6, 0.6, 0.6, 1.0);
        vec4 specular = vec4(1, 1, 1, 1) * power;
        gl_FragColor = (ambient+diffuse) * texture2D(colorMap, gl_TexCoord[0].st)+specular;
    }
    (Yes i took off all materials and light settings for now... And trying to use just 1 directional light.)

    Update: I got much further after tried to render normalMap in place of colorMap. To my surprise it wasn't showing right. I changed code around from this:
    Code:
    tex.TextureUnit:=1; tex.SetByName('normalmap');
    tex.TextureUnit:=0; tex.SetByName('texture');
    to this, and it started working better. It is using the normals for specular, but i do need to multiply by modelview or something in vertex shader still:
    Code:
    tex.TextureUnit:=0; tex.SetByName('texture');
    tex.TextureUnit:=1; tex.SetByName('normalmap');
    I mean, i don't understand why that order would matter. All it does is:
    glActiveTexture(GL_TEXTURE0 + n);
    edit2: Oh, i think nxPascal thinks that texture index didn't change and didn't bind... Bug!
    Last edited by User137; 02-10-2012 at 01:55 PM.

  8. #8
    Quote Originally Posted by User137 View Post
    That is still too complicated instructions. Why would i need to pass vertex normal? I do not see why vertex-shader would use it.
    Your normal map texture has normal values that are specified in tangent space, while light direction and other lighting parameters are typically specified in world space. You need vertex normal, vertex tangent and vertex binormal for doing conversions between tangent space and world space.

  9. #9
    Yeah, i know what you mean by world space. I passed modelview rotation to vertex shader with uniform now:
    Code:
    uniform mat3 mv_rotation;
    varying vec3 lightDir;
    void main()
    {
    	gl_Position = ftransform();
    	gl_TexCoord[0] = gl_MultiTexCoord0;
    	vec3 vertexPos = vec3(gl_ModelViewMatrix * gl_Vertex);
    	lightDir = (gl_LightSource[0].position.xyz - vertexPos); // / lightRadius;
    	lightDir = mv_rotation * lightDir;
    	lightDir = normalize(lightDir);
    }
    Applied materials to fragment-shader, used shininess power etc, end result is already quite impressive. But it's not working with all rotations.

    If model is looking towards me, and i rotate it 360 degrees around Y-axis, everything looks perfect.

    If model is looking towards me, and i rotate it 360 degrees around X-axis, it feels like normals are inverted.

    If model is backwards to me, and i rotate it 360 degrees around X-axis, everything looks perfect.

    edit: Actually, i'm starting to think this is behaving like point light now, not directional light. Have to check the math.
    Last edited by User137; 02-10-2012 at 03:14 PM.

  10. #10
    You can see example here.
    if your normal maps look like the one in this example then you are simply dealing with another approach to bump mapping. instead of storing the normal map values in tangent space, they are here converted to the model space (the space where the 3d model is unmodified by any transformations). in a way this approach is actually more simple than the one with the tangent space normal maps. what's good about is that you don't even need to deal with tangent space at all, and you don't even need the vertex normals anymore because all the normals are stored in the texture, so all you need to do in pixel shader is fetch the normal map, get the model space normal vector (NormalMapPixel.xyz * 2 - 1), transform it by the world space transformation, normalize and you've got your world space normal vector. the major disadvantage of this method and the reason the tangent space is normally used is that these normal map textures are not reusable for different meshes, so it's ok for the character models and such, but it's not going to work with the various environment models.

Page 1 of 2 12 LastLast

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •