Heightmap producing odd results

EDIT: Updated code and questions on post #6.

Hi,

I want to try to make a simple game just for fun and began with basic rendering, etc; and have now started working on Heightmaps. I’ve written up some code to render a Heightmap using opengl, java, Triangle strips, vbos, and ibos.

I tried my best to gather an understanding and the following is what I produced. When used, it does semi draw a Heightmap however as you can tell the results are a little rough. I end up with 250fps and the terrain is only 200 x 200 on top of a few of the Triangle strips rendering in odd positions.

I’m not sure but it would seem that a few vertices are creating “bridges” to the opposite side of the map. (Also I haven’t added in normals yet, so the Heightmap is just black for now)

Here is two screen shots with the results:

http://imgur.com/5xpaiu4

Imgur

Anyone have any ideas on improving the performance and fixing the “bridging” issue? It would seem as if it could be an issue with the last triangle strip. The backside of the heightmap, at the last vertex is where it starts the “bridges”. I have no idea what went wrong. Any help with this is very much appreciated!

Edit: Just noticed, the last triangle is stretching to the very first triangle and the first triangle isn’t rendering. Very odd.

Here’s the code:


package com.base.engine;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL15.*;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;

import javax.imageio.ImageIO;

import org.lwjgl.BufferUtils;

public class TerrainTest2 {

    private static int width;
    private static int height;
    private static int verticies;
    private static int indicies;
    private static FloatBuffer vBuffer;
    private static IntBuffer iBuffer;
    private static float[][] data;
    private static int vbo;
    private static int ibo;


    public TerrainTest2 (String fileName) {
        glPushMatrix();
        glScalef(0.6f, 0.6f, 0.6f);
        glTranslatef(-70.0f, -62.0f, -100.0f);
        initHeightmap();
        loadHeightImage(fileName);
        loadHeightVerticies();
        drawHeightmap();
        glPopMatrix();
    }

    //Set buffer handles
    public static void initHeightmap() {
        vbo = glGenBuffers();
        ibo = glGenBuffers();
    }

    //Load heightmap and get heights from values 0-255
    public static void loadHeightImage(String fileName) {

        try {
            BufferedImage heightmapImage = ImageIO.read(new File("./res/heightmap/" + fileName));
            data = new float[heightmapImage.getWidth()][heightmapImage.getHeight()];
            Color colour;

            for (int x = 0; x < data.length; x++) {
                for (int z = 0; z < data[x].length; z++) {
                    colour = new Color(heightmapImage.getRGB(x, z));
                    data[z][x] = colour.getRed();
                }
            }
            height = heightmapImage.getHeight();
            width = heightmapImage.getWidth();
            verticies = (3*(2*height)*width);
            indicies = ((height*2)*width);
            vBuffer  = BufferUtils.createFloatBuffer(verticies);
            iBuffer  = BufferUtils.createIntBuffer(indicies);
        } catch (IOException e){
            e.printStackTrace();
        }
    }

    //Add verticies and indicies into there buffers
    public static void loadHeightVerticies() {

        for (int z = 0; z < data.length - 1; z++) {
            for (int x = 0; x < data[z].length; x++) {
                vBuffer.put(x).put(data[z][x]).put(z);
                vBuffer.put(x).put(data[z + 1][x]).put(z + 1);
            }
        }
        vBuffer.flip();
        for (int n = 0; n < indicies; n++) {
            iBuffer.put((int) n);
        }
        iBuffer.flip();
    }


    //Draw using vbo and ibo using triangle strips
    public static void drawHeightmap() {
        glEnableClientState(GL_VERTEX_ARRAY);
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glBufferData(GL_ARRAY_BUFFER, vBuffer, GL_STATIC_DRAW);
        glVertexPointer(3, GL_FLOAT, 0, 0L);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, iBuffer, GL_STATIC_DRAW);
        glDrawElements(GL_TRIANGLE_STRIP, indicies, GL_UNSIGNED_INT, 0);

        glDisableClientState(GL_VERTEX_ARRAY);

        // cleanup VBO handles
        vBuffer.put(0, vbo);
        iBuffer.put(0, ibo);

        glDeleteBuffers(vbo);
        glDeleteBuffers(ibo);

    }

}

Without running your code it looks to me like the ‘bridges’ as you describe them are due to the fact that you are not using degenerate triangles to link each row of the terrain. The triangles are rendered as a continuous strip, therefore you need to render two degenerate triangles (basically zero-area triangles that are not rendered) to end each row and link it to the next.

Also you seem to be creating an IBO with a sequential list of indices and repeating the triangle vertices in your VBO. A better approach is to store each vertex once and specify the indices of each triangle in the VBO (including the degenerates), otherwise there’s no point in using the IBO to be honest. In fact I suspect you could just omit it from your rendering code and it would produce the same results.

Here’s a decent tutorial explaining the concept: http://www.learnopengles.com/android-lesson-eight-an-introduction-to-index-buffer-objects-ibos/

Couple of other things I noticed after looking through the code that struck me:

  • Why is the heightmap sized to 2 * height? is this to account for the way you store four triangle vertices per point on the height-map? it’s certainly unusual, or is this some cunning terrain rendering technique that I haven’t come across before?

  • When you’re building the VBO the outer for loop goes to data.length - 1 missing out one row of the heightmap? Maybe this explains why you’re not seeing the first triangle?

  • It’s very hard to diagnose what’s going on when you’re essentially just seeing a black blob ;), I suggest you use wire-frame rendering so you can see what’s being rendered more easily. Also you might want to temporarily bodge your VBO or rendering code to only draw the first few triangles or the first row so you have less clutter.

  • What are vBuffer.put(0, vbo); and iBuffer.put(0, ibo); lines doing? And why are you deleting the buffers in the rendering loop? is that a typo?

Hope some of this helps.

  • stride

Hi,

Thanks for the reply and sorry for my late response. I scraped the code and started almost completely fresh and I think I’m starting to get some where. Only running into 2 problems and I think I’ll have it done. Could I bother you to take one more glance at this new one and tell me what ya think? The two issues are:

Everythings rendering in one strip. (See pictures) I did try to add degenerate triangles by putting two more indices (last of previous row and first of next row) in my ibo but I still ended with the same results. The other issue is that for some reason it appears to be using the index of 0 and 1 repeatedly and connecting it to every other index in the strip. I’ve recoded my method for generating the indices over and over again trying to fix it on my own but after days of trying still ended up with nothing. One method I tried for the indices is still in the code and commented out.If anyone has any idea why these results are happening I’d be very very grateful for a bit of help. Here are the pictures and code:


package com.base.engine;



import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;

import javax.imageio.ImageIO;

import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.opengl.GL20.*;
import static org.lwjgl.opengl.GL30.*;

import org.lwjgl.BufferUtils;

//TODO: Add degenerate triangles
//TODO: Fix indices method

//TODO: Recreate triangle method
public class terraintest5 {

	//Float Array containing z height coordinates
	private static float[][] data;
	private static int height;
	private static int width;
	private static int vertices;
	private static int indices;
	private int vaoId = 0;
	private int vboId = 0;
	private int vboiId = 0;
	private int[] triangleListIndices;
	FloatBuffer verticesBuffer;
	IntBuffer indicesBuffer;


	public terraintest5 (String fileName) {
		glPushMatrix();
		glScalef(0.6f, 0.6f, 0.6f);
		glTranslatef(-70.0f, -62.0f, -100.0f);
		setupTerrain(fileName);
		render();
		glPopMatrix();
	}

	public void setupTerrain(String fileName) {
		try {
			BufferedImage heightmapImage = ImageIO.read(new File("./res/heightmap/" + fileName));
			data = new float[heightmapImage.getWidth()][heightmapImage.getHeight()];
			Color colour;
			height = heightmapImage.getHeight();
			width = heightmapImage.getWidth();
			vertices = ((height  * width)  * 6);//maybe no 3?

			for (int x = 0; x < data.length; x++)
			{
				for (int z = 0; z < data[x].length; z++)
				{
					colour = new Color(heightmapImage.getRGB(x, z));
					data[z][x] = colour.getRed();

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

		verticesBuffer = BufferUtils.createFloatBuffer(vertices);

		System.out.println("Length of data: " + data.length);

		for (int z = 0; z < this.data.length - 1; z++) {
			for (int x = 0; x < this.data[z].length; x++) {
				verticesBuffer.put(x).put(this.data[z][x]).put(z);
				System.out.println(verticesBuffer);

				verticesBuffer.put(x).put(this.data[z + 1][x]).put(z + 1);
				System.out.println(verticesBuffer);
			}
			//add degenerate triangles here
		}
		System.out.println("vbo: " + verticesBuffer);
		verticesBuffer.flip();

		indices =  (width - 1) * (height - 1) * 6;
		triangleListIndices = new int[indices];
//		for (int x = 0; x < width - 1; x++)//map height
//		{
//		    for (int y = 0; y < height - 1; y++)//map width
//		    {
//				triangleListIndices[(x + y * (width - 1)) * 6] = (2 * x);
//		        triangleListIndices[(x + y * (width - 1)) * 6 + 1] = (2 * x + 1);
//		        triangleListIndices[(x + y * (width - 1)) * 6 + 2] = (2 * x + 2);
//
//		        triangleListIndices[(x + y * (width - 1)) * 6 + 3] = (2 * x + 2);
//		        triangleListIndices[(x + y * (width - 1)) * 6 + 4] = (2 * x + 1);
//		        triangleListIndices[(x + y * (width - 1)) * 6 + 5] = (2 * x + 3);
//		    }
//		    //add degenerate index here
//		}
		int i = 0;
		for(int y = 0; y < height - 1; ++y)
	    {
	        for(int x = 0; x < width - 1; ++x)
	        {
	            int start = y * width + x;
	            triangleListIndices[i++] = (short)start;
	            triangleListIndices[i++] = (short)(start + 1);
	            triangleListIndices[i++] = (short)(start + width);
	            triangleListIndices[i++] = (short)(start + 1);
	            triangleListIndices[i++] = (short)(start + 1 + width);
	            triangleListIndices[i++] = (short)(start + width);
	        }
	    }

//		indices = triangleListIndices.length;
//		System.out.println("TriangleListIndices" + triangleListIndices);
		indicesBuffer = BufferUtils.createIntBuffer(indices);

		for (int n = 0; n < triangleListIndices.length; n++) {
			System.out.println(triangleListIndices[n]);
		}

		indicesBuffer.put(triangleListIndices);
		System.out.println("IndexBuffer" + indicesBuffer);
		indicesBuffer.flip();
		// Create a new Vertex Array Object in memory and select it (bind)
		// A VAO can have up to 16 attributes (VBO's) assigned to it by default
		vaoId = glGenVertexArrays();
		glBindVertexArray(vaoId);

		// Create a new Vertex Buffer Object in memory and select it (bind)
		// A VBO is a collection of Vectors which in this case resemble the location of each vertex.
		vboId = glGenBuffers();
		glBindBuffer(GL_ARRAY_BUFFER, vboId);
		glBufferData(GL_ARRAY_BUFFER, verticesBuffer, GL_STATIC_DRAW);
		// Put the VBO in the attributes list at index 0
		glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);
		// Deselect (bind to 0) the VBO
		glBindBuffer(GL_ARRAY_BUFFER, 0);

		// Deselect (bind to 0) the VAO
		glBindVertexArray(0);

		// Create a new VBO for the indices and select it (bind)
		vboiId = glGenBuffers();
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboiId);
		glBufferData(GL_ELEMENT_ARRAY_BUFFER, indicesBuffer, GL_STATIC_DRAW);
		// Deselect (bind to 0) the VBO
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	}

	public void render(){
		glClear(GL_COLOR_BUFFER_BIT);

		// Bind to the VAO that has all the information about the vertices
		glBindVertexArray(vaoId);
		glEnableVertexAttribArray(0);

		// Bind to the index VBO that has all the information about the order of the vertices
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboiId);

		// Draw the vertices
		glDrawElements(GL_LINES, indices, GL_UNSIGNED_BYTE, 0);
//		for (int z = 0; z < data.length - 1; z++) {
//            // Render a triangle strip for each 'strip'.
//            glBegin(GL_LINES);
//            for (int x = 0; x < data[z].length; x++) {
//                // Take a vertex from the current strip
//                glVertex3f(x, data[z][x], z);
//                // Take a vertex from the next strip
//                glVertex3f(x, data[z + 1][x], z + 1);
//            }
//            glEnd();
//        }
		// Put everything back to default (deselect)
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
		glDisableVertexAttribArray(0);
		glBindVertexArray(0);
		}
}


Just a side note, are you following theBennyBox’s tutorials on youtube?

Yeah, I was but then I started branching off on my own.

If I’m following this correctly you’re adding duplicate vertices into your VBO:

for (int z = 0; z < this.data.length - 1; z++) {
         for (int x = 0; x < this.data[z].length; x++) {
            verticesBuffer.put(x).put(this.data[z][x]).put(z);
            System.out.println(verticesBuffer);

            verticesBuffer.put(x).put(this.data[z + 1][x]).put(z + 1);     <----- THIS
            System.out.println(verticesBuffer);
         }
         //add degenerate triangles here
      }

What’s the purpose of the second part of this code that I’ve highlighted? This will generate duplicate vertices which is probably why you’re seeing all the lines connecting to each vertex. Normally I’d expect the VBO to be the same size as your height-map, i.e. width x height, there’s no point in storing the data more than once unless there’s some rendering performance benefit.

I’ve never heard of theBennyBox’s tutorial - will take a look.

Ok I’m back and I’ve rewritten the code yet again. Yet resulting in another error. So what I’m essentially trying to do is create a list of vertices by iterating down the height of each column and then adding to y to switch to the next column. For indices, since I’m using triangle strips, I go down each column for the first index and then add the height of the map to the first index to get the index parallel to the latter, at the end of each column I repeat the previous index and the first index of the next column to add my degenerate triangles. I then flip the buffers and hand them to opengl. Afterwards using draw elements to render. However this is my result:

Imgur

with this heightmap:

Imgur

I have a feeling I’ve either messed something very tiny up like a double for loop calculation or I just misinterpreted how this works… If anyone gets the chance to review the code and help me out, I’d greatly appreciate it. I’m really starting to hate heightmaps and just want to be done…Lol. Thanks

package com.base.engine;

import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.opengl.GL20.*;
import static org.lwjgl.opengl.GL30.*;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;

import javax.imageio.ImageIO;

import org.lwjgl.BufferUtils;


public class TerrainTest8 {

	private static float[][] data;
	private static int height;
	private static int width;
	private static int vertices;
	private static int indices;
	private int vaoId = 0;
	private int vboId = 0;
	private int vboiId = 0;
	FloatBuffer verticesBuffer;
	IntBuffer indicesBuffer;

	public TerrainTest8 (String fileName) {
		glPushMatrix();
		glScalef(0.6f, 0.6f, 0.6f);
		glTranslatef(-70.0f, -62.0f, -100.0f);
		setupTerrain(fileName);
		render();
		glPopMatrix();
	}

	public void setupTerrain(String fileName) {
		try {
			BufferedImage heightmapImage = ImageIO.read(new File("./res/heightmap/" + fileName));
			data = new float[heightmapImage.getWidth()][heightmapImage.getHeight()];
			Color colour;
			height = heightmapImage.getHeight();
			width = heightmapImage.getWidth();
			vertices = (height  * width);

			for (int z = 0; z < data.length; z++) {
                // Iterate over the pixels in the image on the y-axis
                for (int x = 0; x < data[z].length; x++) {
                    // Retrieve the colour at the current x-location and y-location in the image
                    colour = new Color(heightmapImage.getRGB(z, x));
                    // Store the value of the red channel as the height of a heightmap-vertex in 'data'. The choice for
                    // the red channel is arbitrary, since the heightmap-image itself only has white, gray, and black.
                    data[z][x] = colour.getRed();
                }
            }
		} catch (IOException e){
			e.printStackTrace();
		}

		verticesBuffer =  BufferUtils.createFloatBuffer(vertices * 3);

		for (int z = 0; z < width; z++) {
			for (int x = 0; x < height; x++) {
				verticesBuffer.put(x).put(this.data[z][x]).put(z);
			}
		}
		verticesBuffer.flip();
		int degenIndices = (width * 2) - 4;
		indices = (height * width) + (height * (width - 2)) + degenIndices;
		indicesBuffer =  BufferUtils.createIntBuffer(indices);

		for(int y = 0; y < width - 1; y++)
	    {
	        for(int x = 0; x < height; x++)
	        {
	        	int start = y * width + x;
	        	indicesBuffer.put(start);
	        	indicesBuffer.put(start + height);
	        }
	        if(y < width - 2) {
	        indicesBuffer.put(((y + 2) * height) - 1);
	        indicesBuffer.put((y + 1) *(height));
	        }

	    }
		indicesBuffer.flip();
		vaoId = glGenVertexArrays();
		glBindVertexArray(vaoId);

		// Create a new Vertex Buffer Object in memory and select it (bind)
		// A VBO is a collection of Vectors which in this case resemble the location of each vertex.
		vboId = glGenBuffers();
		glBindBuffer(GL_ARRAY_BUFFER, vboId);
		glBufferData(GL_ARRAY_BUFFER, verticesBuffer, GL_STATIC_DRAW);
		// Put the VBO in the attributes list at index 0
		glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);
		// Deselect (bind to 0) the VBO
		glBindBuffer(GL_ARRAY_BUFFER, 0);

		// Deselect (bind to 0) the VAO
		glBindVertexArray(0);

		// Create a new VBO for the indices and select it (bind)
		vboiId = glGenBuffers();
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboiId);
		glBufferData(GL_ELEMENT_ARRAY_BUFFER, indicesBuffer, GL_STATIC_DRAW);
		// Deselect (bind to 0) the VBO
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	}

	public void render(){
		glClear(GL_COLOR_BUFFER_BIT);

		// Bind to the VAO that has all the information about the vertices
		glBindVertexArray(vaoId);
		glEnableVertexAttribArray(0);

		// Bind to the index VBO that has all the information about the order of the vertices
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboiId);

		// Draw the vertices
		glDrawElements(GL_LINES, indices, GL_UNSIGNED_BYTE, 0);

		// Put everything back to default (deselect)
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
		glDisableVertexAttribArray(0);
		glBindVertexArray(0);
		}
	}


Here, I wrote a project that can load an image and generate it into a heightmap, take a look:

Thanks but I’m trying to use vbos and ibos instead of display list for my strips. :’(

Well, it should be the same general concept should it not? They both have the same vertices, you just add them into the buffer differently! Sorry I couldn’t help more!

I hope you didn’t take that offensive, I promise I wasn’t trying to be rude. It is a little different because the order of the indices and vertices plus how things are rendered with the indices and degenerates and what not. Thank you though.

Btw I did figure out the error after reviewing the android heightmap tutorial for the hundredth time. Turns out I never was binding to the vertex buffer object during the render calls. Only the index buffer object. I took out the vertex array object and replaced it with a bind to the vertex buffer object and it draws like a charm.

Thanks everyone for the help and links!

Oh, I didn’t take offense to that, sorry if you thought I did!

Good job fixing it and good luck :slight_smile: