0
votes

I have recently created 2D height map grid which generates 3D terrain mesh for my world With the ability to add hills/bumps with mouse click events during runtime. My Problem is that every time i add to the height of the vertices i update the whole terrain's normal and position vbos(very not efficient). what is the way to change specific part of vbo? I have heared that glBufferSubData is the way, but How can i change only the Y value? (the vbo is x,y,z,x,y,z...) and get the changed verticies in order for glBufferSubData?

Terrain class:

public class Terrain {
    public static final int SIZE =  500;
    //VAO, vertexCount, VBOS
    private RawModel model;
    //textures for the terrain
    private terrainTexturePack texturePack;
    Loader loader;
    private static int VERTEX_COUNT =128;


    float[] Vertices;
    float[] Normals;
    float[] TextureCoords;
    int[] Indices;
    private float[][] heights;

    public Terrain(Loader loader, terrainTexturePack texturePack) {
        this.texturePack = texturePack;
        this.loader = loader;
        this.model = generateTerrain(loader);
    }


    public RawModel getModel() {
        return model;
    }

    public terrainTexturePack getTexturePack() {
        return texturePack;
    }

    //player collision detection witn the terrain
    public Vector3f getXYZOfTerrain(float worldX, float worldZ) {
        float gridSquareSize = SIZE / ((float) heights.length - 1);
        int gridX = (int) Math.floor(worldX / gridSquareSize);
        int gridZ = (int) Math.floor(worldZ / gridSquareSize);
        if(gridX >= heights.length - 1 || gridZ >= heights.length - 1 || gridX < 0 || gridZ < 0) {
            return null;
        }

        float xCoord = (worldX % gridSquareSize)/gridSquareSize;
        float zCoord = (worldZ % gridSquareSize)/gridSquareSize;
        float yCoord;

        if (xCoord <= (1-zCoord)) {
            yCoord = Maths.barryCentric(new Vector3f(0, heights[gridX][gridZ], 0), new Vector3f(1,
                            heights[gridX + 1][gridZ], 0), new Vector3f(0,
                            heights[gridX][gridZ + 1], 1), new Vector2f(xCoord, zCoord));
        } else {
            yCoord = Maths.barryCentric(new Vector3f(1, heights[gridX + 1][gridZ], 0), new Vector3f(1,
                            heights[gridX + 1][gridZ + 1], 1), new Vector3f(0,
                            heights[gridX][gridZ + 1], 1), new Vector2f(xCoord, zCoord));
        }
        return new Vector3f(gridX, yCoord, gridZ);
    }

    //GENERATE THE TERRAIN
    private RawModel generateTerrain(Loader loader) {
        int pointer = 0;
        int count = VERTEX_COUNT * VERTEX_COUNT;
        heights = new float[VERTEX_COUNT][VERTEX_COUNT];
        float[] vertices = new float[count * 3];
        float[] normals = new float[count * 3];
        float[] textureCoords = new float[count * 2];
        int[] indices = new int[6 * (VERTEX_COUNT - 1) * (VERTEX_COUNT * 1)];
        int vertexPointer = 0;

        for (int i = 0; i < VERTEX_COUNT; i++) {
            for (int j = 0; j < VERTEX_COUNT; j++) {
                vertices[vertexPointer * 3] = (float) j / ((float) VERTEX_COUNT - 1) * SIZE;
               float height = 0f;
               vertices[vertexPointer * 3 + 1] = height;
                heights[j][i] = height;
                vertices[vertexPointer * 3 + 2] = (float) i / ((float) VERTEX_COUNT - 1) * SIZE;
                Vector3f normal =new Vector3f(0, 1, 0);// calculateNormal(j, i, noise);
                normals[vertexPointer * 3] = normal.x;
                normals[vertexPointer * 3 + 1] = normal.y;
                normals[vertexPointer * 3 + 2] = normal.z;
                textureCoords[vertexPointer * 2] = (float) j / ((float) VERTEX_COUNT - 1);
                textureCoords[vertexPointer * 2 + 1] = (float) i / ((float) VERTEX_COUNT - 1);
                vertexPointer++;
                if(i < VERTEX_COUNT - 1 && j < VERTEX_COUNT - 1){
                    int topLeft = (i * VERTEX_COUNT) + j;
                    int topRight = topLeft + 1;
                    int bottomLeft = ((i + 1) * VERTEX_COUNT) + j;
                    int bottomRight = bottomLeft + 1;
                    indices[pointer++] = topLeft;
                    indices[pointer++] = bottomLeft;
                    indices[pointer++] = topRight;
                    indices[pointer++] = topRight;
                    indices[pointer++] = bottomLeft;
                    indices[pointer++] = bottomRight;
                }
            }
        }
        Vertices = vertices;
        TextureCoords = textureCoords;
        Normals = normals;
        Indices = indices;

        return loader.loadToVAO(vertices, textureCoords, normals, indices);
    }

    //Calculate normal  
    private Vector3f calculateNormal(int x, int z) {
        float heightL = Vertices[(((    (z)   *VERTEX_COUNT)+  (x-1)    )*3)+1];
        float heightR = Vertices[(((    (z)   *VERTEX_COUNT)+  (x+1)    )*3)+1];
        float heightD = Vertices[(((    (z-1)   *VERTEX_COUNT)+  (x)    )*3)+1];
        float heightU = Vertices[(((    (z+1)   *VERTEX_COUNT)+  (x)    )*3)+1];
        Vector3f normal = new Vector3f(heightL - heightR, 2f, heightD - heightU);
        normal.normalise();
        return normal;
    }
    //create mountain where the mouse clicked
    //Vertices[(((y*VERTEX_COUNT)+x)*3)+1] = one Vertex in 2d grid
    public void createHill(int x0, int y0){
       float h = 0.06f;
       int xs=VERTEX_COUNT; 
       int ys=VERTEX_COUNT;
       float maxHeight =Vertices[(((y0*xs)+x0)*3)+1]+h;
       float r = (9*maxHeight)/30;

       //Loop the vertices
       for(int y=(int) (y0-r);y<=y0+r;y++)
        for(int x=(int) (x0-r);x<=x0+r;x++){
            double circule = Math.sqrt((x-x0)*(x-x0)+(y0-y)*(y0-y));
            if (circule <= r)            
                if ((x>=1)&&(x<xs-1))    
                    if ((y>=1)&&(y<ys-1)){
                        Vertices[(((y*xs)+x)*3)+1]  = Maths.hillsHeight(x0, x, y0, y,(maxHeight), r);
                        Vector3f normal = calculateNormal(x,y);
                        Normals[((((y*xs)+x))) * 3] = normal.x;
                        Normals[((((y*xs)+x))) * 3 + 1] = normal.y;
                        Normals[((((y*xs)+x))) * 3 + 2] = normal.z;

                    }
        }

        //change the whole VBO's not effective
        //Note: i know that i dont need to update textures and indices 
        this.model=loader.loadToVAO(Vertices, TextureCoords, Normals, Indices);

      }   

}

Raw model class(vbo and vao holder):

//Store the VAOS and VBOS
public class RawModel {

    private int vaoID;
    private int vertexCount;
    private int positionVbo;
    private int normalVbo;
    private int textureVbo;

        public RawModel(int vaoID, int vertexCount, int positionVbo, int normalVbo, int textureVbo) {        

        this.vaoID = vaoID;
        this.vertexCount = vertexCount;
        this.positionVbo = positionVbo;
        this.normalVbo = normalVbo;
        this.textureVbo = textureVbo;
    }

    public RawModel(int vaoID, int vertexCount) {
        this.vaoID = vaoID;
        this.vertexCount = vertexCount;
    }

    public int getVaoID() {
        return vaoID;
    }

    public int getVertexCount() {
        return vertexCount;
    }

    public int getPositionVbo() {
        return positionVbo;
    }

    public int getTextureVbo() {
        return textureVbo;
    }


    public int getNormalVbo() {
        return normalVbo;
      }

}

loader class:

public class Loader {
    //For clean up
    private List<Integer> vaos = new ArrayList<Integer>();
    private List<Integer> vbos = new ArrayList<Integer>();
    private List<Integer> textures = new ArrayList<Integer>();

    //Load mesh into VAO
    public RawModel loadToVAO(float[] positions,float[] textureCoords,float[] normals,int[] indices){
        int vaoID = createVAO();
        bindIndicesBuffer(indices);
        int positionvbo = storeDataInAttributeList(0,3,positions);
        int textureVbo = storeDataInAttributeList(1,2,textureCoords);
        int normalsnvbo = storeDataInAttributeList(2,3,normals);
        unbindVAO();
        return new RawModel(vaoID,indices.length, positionvbo, textureVbo, normalsnvbo);
    }

    //Load texture
    public int loadTexture(String fileName) {
        Texture texture = null;
        try {
            texture = TextureLoader.getTexture("PNG",
                    new FileInputStream("res/textures/" + fileName + ".png"));
              GL30.glGenerateMipmap(GL11.GL_TEXTURE_2D);
                GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER,
                        GL11.GL_LINEAR_MIPMAP_LINEAR);
                GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS, -2);
              if(GLContext.getCapabilities().GL_EXT_texture_filter_anisotropic){
                 float amount = Math.min(4f, 
                         GL11.glGetFloat(EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT));
                 GL11.glTexParameterf(GL11.GL_TEXTURE_2D, 
                         EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, amount);    
              }
        } catch (Exception e) {
            e.printStackTrace();
            System.err.println("Tried to load texture " + fileName + ".png , didn't work");
            System.exit(-1);
        }
        textures.add(texture.getTextureID());
        return texture.getTextureID();
    }

    //Clean up
    public void cleanUp(){
        for(int vao:vaos){
            GL30.glDeleteVertexArrays(vao);
        }
        for(int vbo:vbos){
            GL15.glDeleteBuffers(vbo);
        }
        for(int texture:textures){
            GL11.glDeleteTextures(texture);
        }
    }

    //Creates vao
    private int createVAO(){
        int vaoID = GL30.glGenVertexArrays();
        vaos.add(vaoID);
        GL30.glBindVertexArray(vaoID);
        return vaoID;
    }
    //Store data in vbo
    private int storeDataInAttributeList(int attributeNumber, int coordinateSize,float[] data){
        int vboID = GL15.glGenBuffers();
        vbos.add(vboID);
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboID);
        FloatBuffer buffer = storeDataInFloatBuffer(data);
        GL15.glBufferData(GL15.GL_ARRAY_BUFFER, buffer, GL15.GL_STATIC_DRAW);
        GL20.glVertexAttribPointer(attributeNumber,coordinateSize,GL11.GL_FLOAT,false,0,0);
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);


        return vboID;
    }

    private void unbindVAO(){
        GL30.glBindVertexArray(0);
    }


    //Bind indices buffer
    private void bindIndicesBuffer(int[] indices){
        int vboID = GL15.glGenBuffers();
        vbos.add(vboID);
        GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, vboID);
        IntBuffer buffer = storeDataInIntBuffer(indices);
        GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, buffer, GL15.GL_STATIC_DRAW);
    }

    //Store in int buffer
    private IntBuffer storeDataInIntBuffer(int[] data){
        IntBuffer buffer = BufferUtils.createIntBuffer(data.length);
        buffer.put(data);
        buffer.flip();
        return buffer;
    }

    //Store in float buffer
    private FloatBuffer storeDataInFloatBuffer(float[] data){
        FloatBuffer buffer = BufferUtils.createFloatBuffer(data.length);
        buffer.put(data);
        buffer.flip();
        return buffer;
    }

    //Load skyBox textures
    public int loadCubeMap(String[] textureFiles){
        int texID = GL11.glGenTextures();
        GL13.glActiveTexture(GL13.GL_TEXTURE0);
        GL11.glBindTexture(GL13.GL_TEXTURE_CUBE_MAP, texID);
        for(int i = 0; i < textureFiles.length; i++){
            TextureData data = decodeTextureFile("res/textures/"+ textureFiles[i] + ".png");
            GL11.glTexImage2D(GL13.GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, 0, GL11.GL_RGBA, data.getWidth(), data.getHeight(), 0, 
                    GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, data.getBuffer());
        }

        GL11.glTexParameteri(GL13.GL_TEXTURE_CUBE_MAP, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
        GL11.glTexParameteri(GL13.GL_TEXTURE_CUBE_MAP, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);



        textures.add(texID);
        GL11.glTexParameteri(GL13.GL_TEXTURE_CUBE_MAP, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
        GL11.glTexParameteri(GL13.GL_TEXTURE_CUBE_MAP, GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
        return texID;
    }
    private TextureData decodeTextureFile(String fileName) {
        int width = 0;
        int height = 0;
        ByteBuffer buffer = null;
        try {
            FileInputStream in = new FileInputStream(fileName);
            PNGDecoder decoder = new PNGDecoder(in);
            width = decoder.getWidth();
            height = decoder.getHeight();
            buffer = ByteBuffer.allocateDirect(4 * width * height);
            decoder.decode(buffer, width * 4, Format.RGBA);
            buffer.flip();
            in.close();
        } catch (Exception e) {
            e.printStackTrace();
            System.err.println("Tried to load texture " + fileName + ", didn't work");
            System.exit(-1);
        }
        return new TextureData(buffer, width, height);
    }

      //Load textures for GUI
      public RawModel loadToVAO(float[] positions, int dimensions) {
            int vaoID = createVAO();
            this.storeDataInAttributeList(0, dimensions, positions);
            unbindVAO();
            return new RawModel(vaoID, positions.length / dimensions);
      }

}

Solved Thank to Reto Koradi

public void changeVbo(int position, float[] data, int VboId){
    GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, VboId);

    FloatBuffer ArrayData = storeDataInFloatBuffer(data);
    GL15.glBufferSubData(GL15.GL_ARRAY_BUFFER,position * 4, ArrayData);

    GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);

}
1

1 Answers

1
votes

The easiest and likely most efficient way is to store the heights (y-values) in a separate VBO, and specify them as a separate vertex attribute.

Then, in your vertex shader code, you can simply reassemble the position from the separate attributes. You might have something like this in your shader code now:

in vec3 pos;

This changes to:

in vec3 posXZ;
in float posY;
...
vec3 pos = vec3(posXZ.x, posY, posXZ.y);

Using a separate VBO for data that changes frequently also allows you to specify the allocation flags accordingly. You can use GL_DYNAMIC_DRAW for the data that changes frequently, GL_STATIC_DRAW for the rest.

Another option would be to use glMapBuffer(). This gives you a CPU pointer to the buffer content, allowing you to modify only the data that you actually want to change. However, you'll have to be careful that you don't introduce undesirable synchronization between CPU and GPU. The glMapBuffer() call might block until the GPU finished all rendering calls using the previous content of the buffer. One common technique is to use multiple copies of the data in a set of buffers, and cycle through them, to minimize synchronization. But if the amount of data is large, that will obviously cause memory usage to increase dramatically.

In your use case, I suspect that you'll have to update the normals as well, since they depend on the height values.