如何更改vbo的特定部分?

时间:2016-07-09 16:29:27

标签: java opengl lwjgl vbo vao

我最近创建了2D高度贴图网格,为我的世界生成3D地形网格能够在运行时使用鼠标单击事件添加山丘/凹凸。我的问题是,每次我添加顶点的高度,我更新整个地形的正常和位置vbos(非常无效)。是什么方式 改变vbo的特定部分? 我听说 glBufferSubData 就是这样,但我怎样才能改变Y值呢? (vbo是x,y,z,x,y,z ......) 并获取更改的verticies以便 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持有者):

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

}

解决了感谢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 个答案:

答案 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()。这为您提供了指向缓冲区内容的CPU指针,允许您仅修改实际要更改的数据。但是,您必须小心不要在CPU和GPU之间引入不合需要的同步。 glMapBuffer()调用可能会阻塞,直到GPU使用缓冲区的先前内容完成所有渲染调用。一种常见的技术是在一组缓冲区中使用多个数据副本,并循环使用它们,以最小化同步。但是,如果数据量很大,那么显然会导致内存使用量急剧增加。

在您的使用案例中,我怀疑您还必须更新法线,因为它们取决于身高值。