1
votes

Im new in Open GL and Im a bit confused about Gouraud shading. Im mostly dealing with coplanar triangles. What I understood is that in Gouraud shading I have to find the normal for each of the three vertices and then before pushing the vertex I have to push the its normal.


    glNormal3d(nx1.x, nx1.y, nx1.z);
    glVertex3d(x1.x, x1.y, x1.z);

The normals are obtained by averaging the the face normals of the adjacent triangles. Now, lets assume that I want to draw a cone, with an n-gon as base. For the base, I use a triangle_fan and all triangles have the same normal right? So, how I push the normals? I compute only one normal and I use it for all triangles? Or I have anyway to compute the normal for each vertex? I wanted to use Gouraud shading also for the top part of the cone (these triangles are also defined as triangle_fan), but seems to not work, why? I changed single triangle to be able to use Gouraud.

Now, I want to create a cylinder, with both n-gons as top and bottom faces. For the lateral face I use triangle_strips and for the base I use triangle fan. So, the lateral face like:


    *--------*
    |\       |
    | \      |
    |  \     |
    |   \    |
    |    \   |
    |     \  |
    |      \ |
    |       \|
    *--------*   

Now, I have the same question as above. For Gouraud shading in the bases, is it enough to use a single normal? For the Gourdaud shading of the lateral triangles, two triangles have the same normal, should I use the same for both?

2

2 Answers

2
votes

Drawing a cone is a bit complex. The top vertex has to have different normals for each of its adjacent triangles. That's because the vertex doesn't have a well-defined "surface" (infinitely small and flat) which it belongs to.

Think of a cone like a cylinder, but with a top radius of 0.

So to draw a cone, you need to duplicate the top vertex n times, with a different normal each, and you can't use a triangle fan for that (as there is a different top vertex for each triangle).

Basically, you need to duplicate a vertex if it isn't part of a contiguous surface, and the top of a cone isn't contiguous. The surface of a sphere is contiguous, on the other hand, so here you can simply average the face's normals (like you explained). The corners of a cube aren't, and need to be drawn with three different normals each. (Just to give you some other examples.)

2
votes

@leemes covered the specific cylinder case in a previous answer. But I wanted to address a more general item from your question:

The normals are obtained by averaging the the face normals of the adjacent triangles.

This is often not what you should really be doing. If your model is based on analytic surfaces, you want to calculate the normal of the surface to obtain your normal vectors. Typical examples of analytic surfaces include:

  • Geometric shapes, like sphere, cone, torus, etc.
  • Spline surfaces.

You will use a triangulation of these surfaces for OpenGL rendering. This triangulation is an approximation of the surface. If you calculate normals as averages of the normals of adjacent triangles, those normals are also approximations of the surface normals. Unless your surface has very little curvature, or you use a very fine tessellation, these approximated normals are nowhere near as good as the actual surface normals.

This is not just theory. Surfaces with normals calculated as averages mostly look kind of rough. You can try and improve the approximation by taking the size of the triangles, or the adjacent angle, into account for the averaging. But it's always an approximation of the real thing.

Fortunately, we can calculate the actual surface normals for analytic surfaces. And often times, it's fairly easy.

  • For example, a sphere is trivial. The normal vector is the same as the normalized vector from the center to the vertex. You will often not even need to have separate normal vectors. For a sphere with radius 1.0 centered at the origin, vertices and normals have exactly the same coordinates. So you can calculate the vertices of a unit sphere, use them directly as the normals, and get the final vertices with translation/scaling that matches the desired position and radius.

  • For your cone example, you can also derive the normals intuitively by a couple of paper sketches. But to illustrate the more generic way of calculating them, let's go through the calculation.

Many surfaces are expressed by parametric equations based on two parameters. This is probably how you calculate your cone vertices already.

Take a cone with its base on the xy-plane, with the radius of the base being r, and height h, with the tip on the positive z-axis. You then calculate vertices as:

(a = 0..2*pi, t = 0..h)

          ( r * cos(a) * (h - t) / h )
f(a, t) = ( r * sin(a) * (h - t) / h )
          ( t                        )

For such parametric surfaces, the normal can be calculated as the cross product of the two gradients. Calculating the two gradient vectors first:

        ( -r * sin(a) * (h - t) / h )
df/da = (  r * cos(a) * (h - t) / h )
        ( 0                         )

        ( -r * cos(a) / h )
df/dt = ( -r * sin(a) / h )
        ( 1               )

Now we can calculate the cross product of these two vectors:

( r * cos(a) * (h - t) / h  )
( r * sin(a) * (h - t) / h  )
( r * r * (h - t) / (h * h) )

Since we have to normalize this vector anyway, we can get rid of some common factors, which simplifies it to:

( cos(a) )
( sin(a) )
( r / h  )

Which makes intuitive sense. It points outwards from the center in the same direction as the vertices, and it points increasingly more in the z-direction the larger the radius and the smaller the height is.

With another slight manipulation (we still don't care about the magnitude), this can be written as:

( h * cos(a) )
( h * sin(a) )
( r          )

Now this needs to be normalized. The length becomes:

sqrt(h * h + r * r)

Which conveniently is the same for all vertices. So we can calculate a scaling factor once as:

s = 1 / sqrt(h * h + r * r)

And the normal vector for each point is calculated as:

( s * h * cos(a) )
( s * h * sin(a) )
( s * r          )

This was obviously much more convoluted than it needed to be for the cylinder, but it should help illustrate the general process that can be applied to more complex surfaces.

The approach with the averaged normals should only be used when all you have is a mesh, and there's no way to get more information to calculate good normals.