Как изменить конкретную часть VBO?

Недавно я создал сетку 2D карты высот, которая генерирует трехмерную сетку рельефа для моего мира с возможностью добавлять холмы / выпуклости с помощью событий щелчка мыши во время выполнения. Моя проблема заключается в том, что каждый раз, когда я добавляю к высоте вершин, я обновляю vbos нормалей и положения всей местности (очень неэффективно). Как изменить конкретную часть VBO? Я слышал, что glBufferSubData это путь, но как я могу изменить только значение Y? (vbo это x,y,z,x,y,z...) и получить измененные вершины в порядке для glBufferSubData?

Класс местности:

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);

      }   

}

Класс необработанных моделей (vbo и 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;
      }

}

класс погрузчика:

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);
      }

}

Решено Спасибо Рето Коради

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 ответ

Самый простой и, вероятно, наиболее эффективный способ - сохранить высоты (значения y) в отдельном VBO и указать их как отдельный атрибут вершины.

Затем в своем коде вершинного шейдера вы можете просто собрать позицию из отдельных атрибутов. Теперь у вас может быть что-то подобное в вашем шейдерном коде:

in vec3 pos;

Это меняется на:

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

Использование отдельного VBO для данных, которые часто изменяются, также позволяет соответствующим образом указывать флаги распределения. Ты можешь использовать GL_DYNAMIC_DRAW для данных, которые часто меняются, GL_STATIC_DRAW что касается прочего.

Другой вариант будет использовать glMapBuffer(), Это дает вам указатель процессора на содержимое буфера, позволяя вам изменять только те данные, которые вы действительно хотите изменить. Однако вы должны быть осторожны, чтобы не вводить нежелательную синхронизацию между процессором и графическим процессором. glMapBuffer() Вызов может блокироваться до тех пор, пока графический процессор не завершит все вызовы рендеринга с использованием предыдущего содержимого буфера. Одним из распространенных методов является использование нескольких копий данных в наборе буферов и их циклическое переключение для минимизации синхронизации. Но если объем данных велик, это, очевидно, приведет к значительному увеличению использования памяти.

Я подозреваю, что в вашем случае вам придется обновить и нормали, так как они зависят от значений высоты.

Другие вопросы по тегам