Fundamental problem with smooth normals

Started by
21 comments, last by steven katic 16 years, 2 months ago
Hello from Germany :) I created 2 triangles with different vertex order (counter clockwise and clockwise). This orders are user data and may not be changed. For the flat shading the view is ok. But for the smooth shading I get partial or full black triangles for the clockwise orders :( The problem is that I have only 1 averaged normal for a vertex. Is there a openGl statement to correct this mistake? Are the normals to be set principle after the "right hand rule"? Thanks in advance Greetings hoon Here's is a pic: black triangle And here's is the code with jogl (executable):

import javax.swing.*;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.GLCanvas;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GL;
import javax.media.opengl.glu.GLU;
import java.nio.FloatBuffer;

public class NormalProblem
        extends JFrame
        implements GLEventListener
{
    private static final boolean SMOOTH = true;
    private int angle = 0;

    public static void main(final String[] args)
    {
        new NormalProblem();
    }

    public NormalProblem()
    {
        final GLCanvas canvas = new GLCanvas();
        canvas.addGLEventListener(this);
        add(canvas);

        setSize(500, 500);
        setVisible(true);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        while (isWoken())
            canvas.display();
    }

    public void display(final GLAutoDrawable drawable)
    {
        final GL gl = drawable.getGL();

        gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
        gl.glLoadIdentity();

        gl.glTranslatef(0, 0, -6);
        gl.glRotated(angle, 1, 1, 1);
        gl.glRotated(angle++, 0, 1, 0);

        /*           3
         *          /          *         / 1 \    triangle 1 [1, 2, 3] (counter clockwise vertex order)
         *       1/_____\ 2
         *        \     /
         *  y      \ 2 /    triangle 2 [1, 2, 4] (clockwise vertex order)
         *  ^       \ /
         *  |        4
         *  '---> x
         */

        if (SMOOTH)
            gl.glShadeModel(GL.GL_SMOOTH);

        else
            gl.glShadeModel(GL.GL_FLAT);

        drawTriangles(gl);
        drawNormals(gl);
    }

    private void drawTriangles(final GL gl)
    {
        gl.glBegin(GL.GL_TRIANGLES);

        if (SMOOTH)
        {
            // triangle 1 [1, 2, 3]
            gl.glNormal3f(0, 0, 1);
            gl.glVertex3f(-2, 0, 0);

            gl.glNormal3f(0, 0, 1);
            gl.glVertex3f(2, 0, 0);

            gl.glNormal3f(0, 0, 1);
            gl.glVertex3f(0, 2, 0);

            // triangle 2 [1, 2, 4]
            gl.glNormal3f(0, 0, 1); // (0, 0, -1) is correct but I have only "one" vertex normal
            gl.glVertex3f(-2, 0, 0);

            gl.glNormal3f(0, 0, 1); // (0, 0, -1) is correct but I have only "one" vertex normal
            gl.glVertex3f(2, 0, 0);

            gl.glNormal3f(0, 0, -1);
            gl.glVertex3f(0, -2, 0);
        }

        else
        {
            // triangle 1 [1, 2, 3]
            gl.glNormal3f(0, 0, 1);
            gl.glVertex3f(-2, 0, 0);
            gl.glVertex3f(2, 0, 0);
            gl.glVertex3f(0, 2, 0);

            // triangle 2 [1, 2, 4]
            gl.glNormal3f(0, 0, -1);
            gl.glVertex3f(-2, 0, 0);
            gl.glVertex3f(2, 0, 0);
            gl.glVertex3f(0, -2, 0);
        }

        gl.glEnd();
    }

    private void drawNormals(final GL gl)
    {
        gl.glDisable(GL.GL_LIGHTING);
        gl.glBegin(GL.GL_LINES);

        if (SMOOTH)
        {
            gl.glColor3f(1, 1, 0);
            // vertex normals of triangle 1 [1, 2, 3]
            // common normal on vertex 1 for triangle 1 and 2
            gl.glVertex3f(-2, 0, 0);
            gl.glVertex3f(-2, 0, 1);
            // common normal on vertex 2 for triangle 1 and 2
            gl.glVertex3f(2, 0, 0);
            gl.glVertex3f(2, 0, 1);
            gl.glColor3f(0, 1, 0);
            gl.glVertex3f(0, 2, 0);
            gl.glVertex3f(0, 2, 1);

            gl.glColor3f(1, 0, 0);
            // vertex normals of triangle 2 [1, 2, 4]
            /* normals for vertex 1 and 2 already exist
            gl.glVertex3f(-2, 0, 0);
            gl.glVertex3f(-2, 0, -1);
            gl.glVertex3f(2, 0, 0);
            gl.glVertex3f(2, 0, -1);
            */
            gl.glVertex3f(0, -2, 0);
            gl.glVertex3f(0, -2, -1);
        }

        else
        {
            gl.glColor3f(0, 1, 0);
            // normal of triangle 1 [1, 2, 3]
            gl.glVertex3f(0, 1, 0);
            gl.glVertex3f(0, 1, 1);

            gl.glColor3f(1, 0, 0);
            // normal of triangle 2 [1, 2, 4]
            gl.glVertex3f(0, -1, 0);
            gl.glVertex3f(0, -1, -1);
        }

        gl.glEnd();
        gl.glEnable(GL.GL_LIGHTING);
    }

    public void init(final GLAutoDrawable drawable)
    {
        final GL gl = drawable.getGL();

        gl.glEnable(GL.GL_DEPTH_TEST);

        gl.glLightModeli(GL.GL_LIGHT_MODEL_TWO_SIDE, GL.GL_TRUE);

        gl.glEnable(GL.GL_LIGHT0);
        gl.glEnable(GL.GL_LIGHTING);
        gl.glLightfv(GL.GL_LIGHT0, GL.GL_AMBIENT, FloatBuffer.wrap(new float[]{0, 0, 0, 1}));
        gl.glLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, FloatBuffer.wrap(new float[]{1, 1, 1, 1}));
        gl.glLightfv(GL.GL_LIGHT0, GL.GL_SPECULAR, FloatBuffer.wrap(new float[]{1, 1, 1, 1}));
        gl.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, FloatBuffer.wrap(new float[]{0, 0, 6}));

        gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL.GL_AMBIENT, FloatBuffer.wrap(new float[]{0.2f, 0.1f, 0, 1}));
        gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL.GL_DIFFUSE, FloatBuffer.wrap(new float[]{0.6f, 0.2f, 0.1f, 1}));
        gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL.GL_SPECULAR, FloatBuffer.wrap(new float[]{0.6f, 0.2f, 0.1f, 1}));
        gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL.GL_EMISSION, FloatBuffer.wrap(new float[]{0, 0, 0, 1}));
        gl.glMaterialf(GL.GL_FRONT_AND_BACK, GL.GL_SHININESS, 50);

        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glLoadIdentity();
        new GLU().gluPerspective(50, drawable.getWidth() / drawable.getHeight(), 1, 1000);
        gl.glMatrixMode(GL.GL_MODELVIEW);
        gl.glLoadIdentity();

        gl.glLineWidth(3);
    }

    private boolean isWoken()
    {
        try
        {
            Thread.sleep(10);
        }

        catch (InterruptedException e)
        {
            e.printStackTrace();
        }

        return true;
    }

    public void reshape(
            final GLAutoDrawable drawable,
            final int x,
            final int y,
            final int width,
            final int height)
    {
    }

    public void displayChanged(
            final GLAutoDrawable drawable,
            final boolean mode,
            final boolean device)
    {
    }
}






[Edited by - hoonsworld on January 28, 2008 3:01:33 PM]
Advertisement
Yes, this is a fundamental problem in OpenGL. In order for the lighting to look good the polygon winding and normals have to be consistent. I haven't tested this, but could probably solve the problem like this:

Draw front facing polygons:
- Enable back face culling
- Draw the scene

Draw back facing polygons:
- Flip the normals
- Enable front face culling
- Draw the scene again

deathkrushPS3/Xbox360 Graphics Programmer, Mass Media.Completed Projects: Stuntman Ignition (PS3), Saints Row 2 (PS3), Darksiders(PS3, 360)
Quote:
Is there a openGl statement to correct this mistake?


there is also:

glFrontFace(GLenum mode);

(for mode: GL_CCW, GL_CW i.e. counterclockwise and clockwise respectively)
to match the simple, hardcoded and constrained nature of your sample code.

Editorial comment:It is a fundamental issue the programmer (or modeller?) should/may account for with regard to 3d geometry in both Opengl and DirectX.
Quote:Original post by deathkrush
Yes, this is a fundamental problem in OpenGL.


I won't say this is a problem in OpenGL. Polygon winding and normals need not to be consistant. In fact, if the normals are correct, your 3D model can still look good even its polygons have a lousy winding.

For hoon: When we use averaged normals, we are expecting a smooth surface. If you are not, why are you averaging your normals in the first place? The polygons share the same vertices doesn't mean they need to share normals. And, if your artist give you a 3D model that share/not-share normals, just let it be.

For more details about how and when to average the normals, please refer to
http://www.xmission.com/~nate/smooth.html
Hi

Thanks for your answers :)

Quote:Original post by ma_hty
The polygons share the same vertices doesn't mean they need to share normals.


Yes, I tried it with a second inverse normal on the shared vertices and it works correct :)
I thought it exist an OpenGL statement for this problem :(

OK, now I write a net algorithm to recognize the opposite wound triangles over the adjacent edges and their vertex order. A wound triangle obtains a boolean flag for usage the inverse normal.

I think it is a good idea :)
Or are there other ideas?

Here is our mini project:
gui3d.org

Best regards from Hamburg
hoon
The correct way is to obtain a 3D model with a consistant winding, instead of struggling about the winding problem. In general, I can easily construct a 3D model with a winding so ambiguous that impossible to be fixed. Therefore, no matter how much effort you spend on your routine, you cannot handle all cases.

By the way, any 3D model designing software or 3D model scanner will, try their best to, produce a 3D model with consistant winding. Why bother?
Arr......

In case your 3D model have its back-face visible, you can command OpenGL to flip the normal whenever the face is backward facing via

glLightModelf(GL_LIGHT_MODEL_TWO_SIDE, 1.0);
Hi ma_hty :)

In the example I set up the statement:

gl.glLightModeli(GL.GL_LIGHT_MODEL_TWO_SIDE, GL.GL_TRUE);

It works correct but it is not the solution for the problem :(


Yes, you are right! Any 3D modeler will try to produce consistant windings. But our program/engine works with simulation data too :) This simulation data based on finite elements. The user has the full control about the winding for each element (polygon) or element groups. Here are 2 links for the background and a little simulation video:
en.wikipedia.org/wiki/Finite_element_analysis
ncac.gwu.edu

This is the reason for our effort.
The inconsistent winding is a feature in our engine but I have to visualize it correctly.

OK, Thanks again :)

Regards from Germany
hoon
May I ask you what is the role of winding in your application? How is it get related to user's interaction? If you can give more information, may be I can help.
Hi :)

In our program we have to manage the windings. The windings are important for the
most finite element solvers. These solvers are very sophisticated programs in
view of mathematics and physics.

For example: An airbag has internal pressure!
But in which direction does the pressure have an effect?
The user has to set up this information over the windings or the finite
element normals. The following video shows an airbag simulation:
airbag deployment

This is not the only reason for the role of winding. There are contact problems, and many of much other ...
In our program the user can select one or several polygons or groups and can invert the winding direction.
The selecting is possible over picking or an intelligent fence mechanism with box or polygon selecting.

For flat shading it is not a problem but the smooth shading is difficult :(

I think, I write a net algorithm to recognize the opposite wound triangles or quads!?

Regards
hoon

This topic is closed to new replies.

Advertisement