views:

410

answers:

4

I have created a regular dodecahedron with OpenGL. I wanted to make the faces transparent (as in the image on Wikipedia) but this doesn't always work. After some digging in the OpenGL documentation, is appears that I "need to sort the transparent faces from back to front". Hm. How do I do that?

I mean I call glRotatef() to rotate the coordinate system but the reference coordinates of the faces stay the same; the rotation effect is applied "outside" of my renering code.

If I apply the transformation to the coordinates, then everything else will stop moving.

How can I sort the faces in this case?

[EDIT] I know why this happens. I have no idea what the solution could look like. Can someone please direct me to the correct OpenGL calls or a piece of sample code? I know when the coordinate transform is finished and I have the coordinates of the vertices of the faces. I know how to calculate the center coordinates of the faces. I understand that I need to sort them by Z value. How to I transform a Vector3f by the current view matrix (or whatever this thing is called that rotates my coordinate system)?

Code to rotate the view:

    glRotatef(xrot, 1.0f, 0.0f, 0.0f);
    glRotatef(yrot, 0.0f, 1.0f, 0.0f);
A: 

Have you tried just drawing each face, in relation to regular world coordinates from back to front? Often it seems like the wording in some of the OpenGL docs is weird. I think if you get the drawing in the right order with out worrying about rotation, it might automatically work when you add rotation. OpenGL might take care of the reordering of faces when rotating the matrix.

Alternatively you can grab the current matrix as you draw ( glGetMatrix() ) and reorder your drawing algorithm depending on which faces are going to be the rotated back/front.

Brian Gianforcaro
ordering by world coordinates: Doesn't work.
Aaron Digulla
There is no call glGetMatrix() in lwjgl; is that the GL_MODELVIEW_MATRIX or the GL_PROJECTION_MATRIX?
Aaron Digulla
A: 

That quote says it all - you need to sort the faces.

When drawing such a simple object you can just render the back faces first and the front faces second using the z-buffer (by rendering twice with different z-buffer comparison functions).

But usually, you just want to transform the object, then sort the faces. You transform just your representation of the object in memory, then determine the drawing order by sorting, then draw in that order with the original coordinates, using transformations as needed (need to be consistent with the sorting you've done). In a real application, you would probably do the transformation implicitly, eg. by storing the scene as a BSP- or Quad- or R- or whatever-tree and simply traversing the tree from various directions.

Note that the sorting part can be tricky, because the function "is-obsucred-by" which is the function you want to compare the faces by (because you need to draw the obscured faces first) is not an ordering, eg. there can be cycles (face A obscures B && face B obscures A). In this case, you would probably split one of the faces to break the loop.

EDIT:

You get the z-coordinate of a vertex by taking the coordinates you pass to glVertex3f(), make it 4D (homogenous coordinates) by appending 1, transform it with the modelview matrix, then transform it with the projection matrix, then do the perspective division. The details are in the OpenGL specs in Chapter 2, section Coordinate transformations.

However, there isn't any API for you to actually do the transformation. The only thing OpenGL lets you do is to draw the primitives, and tell the renderer how to draw them (eg. how to transform them). It doesn't let you easily transform coordinates or anything else (although there IIUC are ways to tell OpenGL to write transformed coordinates to a buffer, this is not that easy). If you want some library to help you manipulate actual objects, coordinates etc., consider using some sort of scenegraph library (OpenInventor or something)

jpalecek
How do I get the Z coordinate of a face?
Aaron Digulla
+3  A: 

When the OpenGL documentation says "sort the transparent faces" it means "change the order in which you draw them". You don't transform the geometry of the faces themselves, instead you make sure that you draw the faces in the right order: farthest from the camera first, nearest to the camera last, so that the colour is blended correctly in the frame buffer.

One way to do this is to compute for each transparent face a representative distance from the camera (for example, the distance of its centre from the centre of the camera), and then sort the list of transparent faces on this representative distance.

You need to do this because OpenGL uses the Z-buffering technique.

(I should add that the technique of "sorting by the distance of the centre of the face" is a bit naive, and leads to the wrong result in cases where faces are large or close to the camera. But it's simple and will get you started; there'll be plenty of time later to worry about more sophisticated approaches to Z-sorting.)


Update: Aaron, you clarified the post to indicate that you understand the above, but don't know how to calculate a suitable Z value for each face. Is that right? I would usually do this by measuring the distance from the camera to the face in question. So I guess this means you don't know where the camera is?

If that's a correct statement of the problem you're having, see OpenGL FAQ 8.010:

As far as OpenGL is concerned, there is no camera. More specifically, the camera is always located at the eye space coordinate (0., 0., 0.).


Update: Maybe the problem is that you don't know how to transform a point by the modelview matrix? If that's the problem, see OpenGL FAQ 9.130:

Transform the point into eye-coordinate space by multiplying it by the ModelView matrix. Then simply calculate its distance from the origin.

Use glGetFloatv(GL_MODELVIEW_MATRIX, dst) to get the modelview matrix as a list of 16 floats. I think you'll have to do the multiplication yourself: as far as I know OpenGL doesn't provide an API for this.

Gareth Rees
That shouldn't matter in my case since I have no overlapping faces.
Aaron Digulla
To multiply matrices in opengl try glMultMatrixf(float*);
ing0
A: 

For reference, here is the code (using lwjgl 2.0.1). I define my model by using an array of float arrays for the coordinates:

        float one = 1f * scale;

        // Cube of size 2*scale
        float[][] coords = new float[][] {
            {  one,  one,  one }, // 0
            { -one,  one,  one },
            {  one, -one,  one },
            { -one, -one,  one },
            {  one,  one, -one },
            { -one,  one, -one },
            {  one, -one, -one },
            { -one, -one, -one }, // 7
        };

Faces are defined in an array of int arrays. The items in the inner array are indices of vertices:

        int[][] faces = new int[][] {
            { 0, 2, 3, 1, },
            { 0, 4, 6, 2, },
            { 0, 1, 5, 4, },
            { 4, 5, 7, 6, },
            { 5, 1, 3, 7, },
            { 4, 5, 1, 0, },
        };

These lines load the Model/View matrix:

        Matrix4f matrix = new Matrix4f ();
        FloatBuffer params = FloatBuffer.allocate (16);
        GL11.glGetFloat (GL11.GL_MODELVIEW_MATRIX, params );
        matrix.load (params);

I store some information of each face in a Face class:

public static class Face
{
    public int id;
    public Vector3f center;

    @Override
    public String toString ()
    {
        return String.format ("%d %.2f", id, center.z);
    }
}

This comparator is then used to sort the faces by Z depth:

public static final Comparator<Face> FACE_DEPTH_COMPARATOR = new Comparator<Face> ()
{
    @Override
    public int compare (Face o1, Face o2)
    {
        float d = o1.center.z - o2.center.z;
        return d < 0f ? -1 : (d == 0 ? 0 : 1);
    }

};

getCenter() returns the center of a face:

    public static Vector3f getCenter (float[][] coords, int[] face)
    {
        Vector3f center = new Vector3f ();
        for (int vertice = 0; vertice < face.length; vertice ++)
        {
            float[] c = coords[face[vertice]];
            center.x += c[0];
            center.y += c[1];
            center.z += c[2];
        }
        float N = face.length;
        center.x /= N;
        center.y /= N;
        center.z /= N;
        return center;
    }

Now I need to set up the face array:

        Face[] faceArray = new Face[faces.length]; 
        Vector4f v = new Vector4f ();
        for (int f = 0; f < faces.length; f ++)
        {
            Face face = faceArray[f] = new Face ();
            face.id = f;
            face.center = getCenter (coords, faces[f]);
            v.x = face.center.x;
            v.y = face.center.y;
            v.z = face.center.z;
            v.w = 0f;
            Matrix4f.transform (matrix, v, v);
            face.center.x = v.x;
            face.center.y = v.y;
            face.center.z = v.z;
        }

After this loop, I have the transformed center vectors in faceArray and I can sort them by Z value:

        Arrays.sort (faceArray, FACE_DEPTH_COMPARATOR);
        //System.out.println (Arrays.toString (faceArray));

Rendering happens in another nested loop:

        float[] faceColor = new float[] { .3f, .7f, .9f, .3f };
        for (Face f: faceArray)
        {
            int[] face = faces[f.id];
            glColor4fv(faceColor);

            GL11.glBegin(GL11.GL_TRIANGLE_FAN);
            for (int vertice = 0; vertice < face.length; vertice ++)
            {
                glVertex3fv (coords[face[vertice]]);
            }
            GL11.glEnd();
        }
Aaron Digulla