2

I am currently working on an OpenGL (JOGL) project in java. The goal is to create a terrain with textures and shading.

I'm creating a random simplex noise, using these values as a heightmap. The heights are mapped to a 1D texture to simulate coloring based on height. A material (ambient/diffuse/specular/shininess) is then used to simulate shading.

However; after adding shading to the terrain, 'stripes' appear on each 'column' (Y direction) of the terrain.

The following material is then applied:

TERRAIN(
        new float[]{0.5f, 0.5f, 0.5f, 1.0f},
        new float[]{0.7f, 0.7f, 0.7f, 1.0f},
        new float[]{0.2f, 0.2f, 0.2f, 1.0f},
        new float[]{100f})

The material enum constructor:

Material(float[] ambient, float[] diffuse, float[] specular, float[] shininess) {
    this.ambient = ambient;
    this.diffuse = diffuse;
    this.specular = specular;
    this.shininess = shininess;
}

I apply the material using the following method:

public void use(GL2 gl) {
    // set the material properties
    gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GLLightingFunc.GL_AMBIENT, ambient, 0);
    gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GLLightingFunc.GL_DIFFUSE, diffuse, 0);
    gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GLLightingFunc.GL_SPECULAR, specular, 0);
    gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GLLightingFunc.GL_SHININESS, shininess, 0);
}

After creating a 2D 'noisearray' consistent of 0-1 values, an 2D vectorarray is created, consiting of X*Y vectors, where each vector represents a point in the plane/terrain.

Here is the method that draws triangles in between those points, where you can see I draw the plane per column (Y direction):

public void draw(GL2 gl, GLU glu, GLUT glut, Drawer drawer) {
    Material.TERRAIN.use(gl);
    texture.bind(gl);
    if (showGrid)
        gl.glPolygonMode( gl.GL_FRONT_AND_BACK, gl.GL_LINE );
    ArrayList<Vector[]> normals = new ArrayList<>();
    for(int i=1;i<vectors.length;i++) {
        gl.glBegin(gl.GL_TRIANGLE_STRIP);
        for (int j = 0; j < vectors[i].length; j++) {
            Vector normalTopRight, normalBottomLeft;

            //Calculate normals top right
            Vector v1, v2, triangleCenterTR;
            if (j < vectors[i].length - 1)
            {
                v1 = vectors[i-1][j].subtract(vectors[i][j]);
                v2 = vectors[i][j+1].subtract(vectors[i][j]);
                normalTopRight = v2.cross(v1).normalized();
                // Get center (a+b+c)*(1/3)
                triangleCenterTR = (vectors[i][j].add(vectors[i - 1][j]).add(vectors[i][j + 1])).scale(1.0 / 3);
            } else {
                v1 = vectors[i-1][j].subtract(vectors[i][j]);
                v2 = vectors[i][j-1].subtract(vectors[i][j]);
                normalTopRight = v1.cross(v2).normalized();
                // Get center (a+b+c)*(1/3)
                triangleCenterTR = (vectors[i][j].add(vectors[i-1][j]).add(vectors[i][j-1])).scale(1.0/3);
            }
            normals.add(new Vector[] {triangleCenterTR, triangleCenterTR.add(normalTopRight)});

            if (j != 0)
            {
                v1 = vectors[i][j].subtract(vectors[i-1][j]);
                v2 = vectors[i-1][j-1].subtract(vectors[i-1][j]);
                normalBottomLeft = v2.cross(v1).normalized();
                // Get center (a+b+c)*(1/3)
                Vector triangleCenterBL = (vectors[i - 1][j].add(vectors[i][j]).add(vectors[i - 1][j - 1])).scale(1.0 / 3);
                normals.add(new Vector[]{triangleCenterBL, triangleCenterBL.add(normalBottomLeft)});
            } else {
                normalBottomLeft = null; // If j==0, there is no bottom left triangle above
            }

            /**
             * We have everything to start drawing
             */
            // Set some color
            if (j == 0) {
                // Initialization vector
                gl.glTexCoord1d(mapTextureToHeight(vectors[i][j].z));
                drawer.glVertexV(vectors[i][j]);
            } else {
                drawer.glNormalV(normalBottomLeft);
            }

            // Shift left
            gl.glTexCoord1d(mapTextureToHeight(vectors[i - 1][j].z));
            drawer.glVertexV(vectors[i - 1][j]);

            // Right down diagonally
            if (j < vectors[i].length - 1) { // Skip if we are reached the end
                gl.glTexCoord1d(mapTextureToHeight(vectors[i][j + 1].z));
                drawer.glNormalV(normalTopRight);
                drawer.glVertexV(vectors[i][j + 1]);
            }
        }
        gl.glEnd();
    }
    if (showGrid)
        gl.glPolygonMode( gl.GL_FRONT_AND_BACK, gl.GL_FILL );
    if (drawNormals) {
        for (Vector[] arrow : normals) {
            if (yellowNormals)
                Material.YELLOW.use(gl);
            else
                gl.glTexCoord1d(mapTextureToHeight(arrow[0].z));
            drawer.drawArrow(arrow[0], arrow[1], 0.05);
        }
    }

    texture.unbind(gl);
}

The most obvious reason for the stripes is the fact I draw the triangles per column, causing OpenGL to not be able to smoothen the shading on the polygons (GL_SMOOTH). Is there any way to fix this?

Image of stripes appearing on terrain

Using a higher resolution (more polygons)

[Edit1] Copied from your comment by Spektre

I just finished calculating the average normals, I indeed have a smooth terrain now, but the lighting looks kind of dull (no depth)

example

Here is the new code that draws the terrain:

public void draw() {
    if (showGrid)
        gl.glPolygonMode( gl.GL_FRONT_AND_BACK, gl.GL_LINE);
    texture.bind(gl);
    Material.TERRAIN.use(gl);
    for(int i=1;i<vectors.length;i++) {
        gl.glBegin(gl.GL_TRIANGLE_STRIP);
        for (int j = 0; j < vectors[i].length; j++) {
            // Initialization vector
            gl.glTexCoord1d(mapTextureToHeight(vectors[i][j].z));
            drawer.glNormalV(normals.get(vectors[i][j]));
            drawer.glVertexV(vectors[i][j]);

            // Shift left
            gl.glTexCoord1d(mapTextureToHeight(vectors[i - 1][j].z));
            drawer.glNormalV(normals.get(vectors[i - 1][j]));
            drawer.glVertexV(vectors[i - 1][j]);
        }
        gl.glEnd();
    }
    if (showGrid)
        gl.glPolygonMode( gl.GL_FRONT_AND_BACK, gl.GL_FILL );
    if (drawNormals)
        drawFaceNormals();
    texture.unbind(gl);
}

I cleaned it up, I am sure the normals are pointing the correct way using the drawnormals function and made sure OpenGL is seeing the top of the terrain as FRONT using (gl.GL_FRONT -> draws only above terrain, not below).

Here is the complete class: PasteBin

Cœur
  • 37,241
  • 25
  • 195
  • 267
  • 1
    you need recompute the normals per vertex so they match see [How to achieve smooth tangent space normals?](http://stackoverflow.com/a/21930058/2521214) – Spektre Jan 07 '16 at 10:56
  • @Spektre Thanks for your comment, I just finished calculating the average normals, I indeed have a smooth terrain now, but the lighting looks kind of dull (no depth) [image](http://i.imgur.com/EbtqLAK.png) Any suggestions? – Dennis van den Berg Jan 07 '16 at 11:49
  • 1
    My bet is your normals are pointing the other way try to negate them `normal = -normal` or are not normalized or are wrongly computed – Spektre Jan 07 '16 at 12:59
  • @Spektre I thought so too, I tried inverting them, but no effect. Besides; it would mean that lighting would be correct on the other side of the terrain, which is not the case.. :-( – Dennis van den Berg Jan 07 '16 at 13:20
  • You would not see it until change CULL_FACE between CW/CCW. anyway try to draw the normal vectors to see if they are correct or not like you have in your first image (from that you should estimate the correctness of direction and size of normals). – Spektre Jan 07 '16 at 13:26
  • after using `gl.glFrontFace(gl.GL_CW);` in my initialize and `gl.glEnable(gl.GL_CULL_FACE);` and `gl.glCullFace(gl.GL_BACK);` before drawing and `gl.glDisable(gl.GL_CULL_FACE);` after drawing, the underside of the terrain is see-through/removed. The normal vectors are drawn correct. The weird thing is, whether I invert my normal vector or not, the terrain upper side of the terrain looks the same (would this suggest my normals are wrong altogether)? – Dennis van den Berg Jan 07 '16 at 13:35
  • Looks like my face normals (which I draw) are correct, but my vertex normals (average on a vertex) are not. Wil try and fix it now. – Dennis van den Berg Jan 07 '16 at 13:46
  • Btw Dennis, you should avoid deprecated OpenGL unless needed – elect Jan 13 '16 at 07:56
  • @elect I couldn't agree more! I had to do it for a course on my university (Computer Graphics), have to say; I learned a lot and it was quite fun working with OpenGL. – Dennis van den Berg Jan 20 '16 at 22:22

1 Answers1

0

Thanks to @Spektre for helping me out.

After properly calculating the average normal of all surrounding faces on a vertex and using this normal for glNormal, the shading was correct.

Rabbid76
  • 202,892
  • 27
  • 131
  • 174