Tangents (GLSL)

Could somebody please point me in the right direction to getting a tangent attribute into my GLSL shader please.

I've read this post where Renanse sets the tangent attribute, but he says he calculates them by hand?



http://www.jmonkeyengine.com/jmeforum/index.php?topic=2057.0



Therefore, is there a built in method for calculating tangents for verticies or how do I go about it?

In one of my GeometryInstancing demos (http://www.jmonkeyengine.com/jmeforum/index.php?topic=5945.0) I calculate and use tangents in a glsl shader to be able to normalmap objects.



Source: http://snylt.nonsen.se/jme/GeometryInstancing/source/src.rar

Just out of interest, did you use/follow the Bump Mapping tutorial from ozone3d.net for your shader?

If not is there any chance I could see your vertex shader. I have 3 different examples of normal map vertex shaders from "The Orange Book", O Zone 3D.net, and Typhoon Labs. And i'm interested in their slightly different approaches.

Would you be able to provide a quick code example of how to populate the tangent FloatBuffer with data according to a Nodes geometry?



FloatBuffer tangents = BufferUtils.createVector3Buffer(node.getVertexCount());

....
// populate tangent
....

shader.setAttributePointer("tangents", 3, true, 0, tangents);
shader.apply();



Thanks

Missed to add the shaders, sorry about that, they can be found in this jar: http://snylt.nonsen.se/jme/GeometryInstancing/source/build/data.jar



In the source code i linked to, from worm.java, the tangents are populated as follows:


private class TangentMesh {
   public TriMesh mesh;
   public FloatBuffer tangents;
   public TangentMesh(TriMesh mesh) {
      this.mesh = mesh;
      tangents = BufferUtils.createFloatBuffer(mesh.getVertexCount() * 3);
      
      IntBuffer indexBuffer = mesh.getIndexBuffer(0);
      FloatBuffer vertexBuffer = mesh.getVertexBuffer(0);
      FloatBuffer textureBuffer = mesh.getTextureBuffer(0, 0);
      indexBuffer.rewind();
      Vector3f tangent = new Vector3f();
      Vector3f binormal = new Vector3f();
      Vector3f normal = new Vector3f();
      Vector3f verts[] = new Vector3f[3];
      Vector2f texcoords[] = new Vector2f[3];
      for(int i=0; i<3; i++) {
         verts[i] = new Vector3f();
         texcoords[i] = new Vector2f();
      }
      for (int t=0; t<indexBuffer.capacity() / 3; t++) {
         int index[] = new int [3];
         for (int v=0; v<3; v++ ) {
            index[v] = indexBuffer.get();
            verts[v].x = vertexBuffer.get(index[v] * 3);
            verts[v].y = vertexBuffer.get(index[v] * 3 + 1);
            verts[v].z = vertexBuffer.get(index[v] * 3 + 2);
            
            texcoords[v].x = textureBuffer.get(index[v] * 2);
            texcoords[v].y = textureBuffer.get(index[v] * 2 + 1);
         }
         TriangleTangentSpace(tangent, binormal, normal, verts, texcoords);
         
         for (int v=0; v<3; v++ ) {
            tangents.position(index[v] * 3);
            tangents.put(tangent.x);
            tangents.put(tangent.y);
            tangents.put(tangent.z);
         }
      }
   }
   
   private void TriangleTangentSpace(Vector3f tangent, Vector3f binormal, Vector3f normal, Vector3f v[], Vector2f t[])
   {         
      Vector3f edge1 = v[1].subtract(v[0]);
      Vector3f edge2 = v[2].subtract(v[0]);
      Vector2f edge1uv = t[1].subtract(t[0]);
      Vector2f edge2uv = t[2].subtract(t[0]);
      
      float cp = edge1uv.y * edge2uv.x - edge1uv.x * edge2uv.y;

      if ( cp != 0.0f ) {
         float mul = 1.0f / cp;   
         tangent .set((edge1.mult(-edge2uv.y).add(edge2.mult(edge1uv.y))).mult(mul));
         binormal.set((edge1.mult(-edge2uv.x).add(edge2.mult(edge1uv.x))).mult(mul));            
         tangent.normalizeLocal();
         binormal.normalizeLocal();            
      }
      edge1.cross(edge2, normal);
      normal.normalizeLocal();
   }
}

Thank you Synlt, very good stuff.

I overlooked the bottom of the Worm.java file - thanks for pointing it out.

I've tried your code (TangentMesh) to calculate tangents for a much more complex model than a box. Firstly there was no effect in game (through the shader) and secondly it constantly crashed my JVM after a few seconds.



I've seen within the GeomBatch class a getTangents method, already built into jME, however I need all tangents from batches for a particular piece of Geometry.

Thanks for pointing out that there exists a tangentBufffer in the GeomBatch, i must have missed it, or it might have been added lately.



The code I used is not tested on more than boxes, spheres, and such objects. It wont work for objects containing more than one batch, didn't add this because I didn't need it :slight_smile: To get the tangents from all the batches, you need to loop through all of them, which should be extremely simple … Saving the tangents in the batchs tangentBuf is ofc much easier…



However I can't guarantee that it is not also something else that isn't working :slight_smile:

Yes I agree its easy, but I was being lazy and hoping someone would provie the code

hi,



you can calculate the tangent directly in vertex-shader.



ne need to set it via attribute by hand.


Yes but you actually need to pass those tangents into the attribute as OpenGL doesn't provide them automatically.

here is the code snipped from my parallax-vertex-shader to compute tangent and binormal directly without attributes


    vec3 tangent;
   vec3 binormal;
   
   vec3 c1 = cross(gl_Normal, vec3(0.0, 0.0, 1.0));
   vec3 c2 = cross(gl_Normal, vec3(0.0, 1.0, 0.0));
   
   if(length(c1)>length(c2))
   {
      tangent = c1;   
   }
   else
   {
      tangent = c2;   
   }
   
   tangent = normalize(tangent);
   
   binormal = cross(gl_Normal, tangent);
   binormal = normalize(binormal);



here is the code snipped from my parallax-vertex-shader to compute tangent and binormal directly without attributes

That method will only work with very specific geometry with no explicitly set texture coordinates. Unfortunately it is not possible to escape the fact that, since tangents/binormals depend on texture coordinates of other vertexes than the one you are operating on, one way or another you need to pass data into the vertex shader. The bottom part of the shader (calculating the binormal from the tangent and normal) should work on arbitrary geometry though.
That method will only work with very specific geometry with no explicitly set texture coordinates


all my models have explicit texture coordinates set?

no problems found

If you use arbitrary models with that shader then that should not give you correct results. Your tangents and binormals need to be lined up with the directions of U and V in your texture coordinates. You have card coded those directions, so they will be correct for planar mapped meshes (terrain, quads, etc), but they will be incorrect for more complex meshes.

marqx is right…

MrCoder, what do you think of a function in Geometry which combines all tangent float buffers from its GeomBatches into a single buffer?

There are so many things happening with the move towards version 2.0 so these things will be handled differently anyway

hi,



i try the tangentmesh class. it looks good. For the terrainmeshes in my game is no optical different to the shader-version.



Thx for the tangentmesh code.I change it it a little bit and fill the buffers for tanget and binormal in the Geometry.class


public class TangentMesh extends TriMesh {
    /**
     * serialVersionUID
     */
    private static final long serialVersionUID = 1L;

    public TangentMesh(TriMesh mesh) {

        super(mesh.getName(), mesh.getVertexBuffer(0), mesh.getNormalBuffer(0),
                mesh.getColorBuffer(0), mesh.getTextureBuffer(0, 0), mesh
                        .getIndexBuffer(0));

        FloatBuffer tangents = BufferUtils.createFloatBuffer(mesh
                .getVertexCount() * 3);
        FloatBuffer binormals = BufferUtils.createFloatBuffer(mesh
                .getVertexCount() * 3);

        IntBuffer indexBuffer = mesh.getIndexBuffer(0);
        FloatBuffer vertexBuffer = mesh.getVertexBuffer(0);
        FloatBuffer textureBuffer = mesh.getTextureBuffer(0, 0);
        indexBuffer.rewind();

        Vector3f tangent = new Vector3f();
        Vector3f binormal = new Vector3f();
        Vector3f normal = new Vector3f();
        Vector3f verts[] = new Vector3f[3];
        Vector2f texcoords[] = new Vector2f[3];

        for (int i = 0; i < 3; i++) {
            verts[i] = new Vector3f();
            texcoords[i] = new Vector2f();
        }

        for (int t = 0; t < indexBuffer.capacity() / 3; t++) {

            int index[] = new int[3];

            for (int v = 0; v < 3; v++) {
                index[v] = indexBuffer.get();
                verts[v].x = vertexBuffer.get(index[v] * 3);
                verts[v].y = vertexBuffer.get(index[v] * 3 + 1);
                verts[v].z = vertexBuffer.get(index[v] * 3 + 2);

                texcoords[v].x = textureBuffer.get(index[v] * 2);
                texcoords[v].y = textureBuffer.get(index[v] * 2 + 1);
            }

            computeTriangleTangentSpace(tangent, binormal, normal, verts,
                    texcoords);

            for (int v = 0; v < 3; v++) {
                tangents.position(index[v] * 3);
                tangents.put(tangent.x);
                tangents.put(tangent.y);
                tangents.put(tangent.z);

                binormals.position(index[v] * 3);
                binormals.put(binormal.x);
                binormals.put(binormal.y);
                binormals.put(binormal.z);
            }
        }

        setTangentBuffer(0, tangents);
        setBinormalBuffer(0, binormals);
    }

    private void computeTriangleTangentSpace(Vector3f tangent,
            Vector3f binormal, Vector3f normal, Vector3f v[], Vector2f t[]) {
        Vector3f edge1 = v[1].subtract(v[0]);
        Vector3f edge2 = v[2].subtract(v[0]);
        Vector2f edge1uv = t[1].subtract(t[0]);
        Vector2f edge2uv = t[2].subtract(t[0]);

        float cp = edge1uv.y * edge2uv.x - edge1uv.x * edge2uv.y;

        if (cp != 0.0f) {
            float mul = 1.0f / cp;
            tangent.set((edge1.mult(-edge2uv.y).add(edge2.mult(edge1uv.y)))
                    .mult(mul));
            binormal.set((edge1.mult(-edge2uv.x).add(edge2.mult(edge1uv.x)))
                    .mult(mul));
            tangent.normalizeLocal();
            binormal.normalizeLocal();
        }
        edge1.cross(edge2, normal);
        normal.normalizeLocal();
    }
}

This should come in handy Black, until everything changes.

Cheers.