Skip to content

E.g.1: draw vertices

Draw a triangle using vertices and indices (requires at least GL30 support OR arb/ext support)

java
int prevVao = GL11.glGetInteger(GL30.GL_VERTEX_ARRAY_BINDING);
int prevVbo = GL11.glGetInteger(GL15.GL_ARRAY_BUFFER_BINDING);
int prevEbo = GL11.glGetInteger(GL15.GL_ELEMENT_ARRAY_BUFFER_BINDING);

int vao;
int vbo;
int ebo;

float[] vertices = new float[]
{
      // positions         // texcoords   // normals
      -0.5f, -0.5f, 0.0f,  0.0f, 0.0f,    0.0f, 0.0f, 1.0f,  // bottom-left
      0.5f, -0.5f, 0.0f,   1.0f, 0.0f,    0.0f, 0.0f, 1.0f,  // bottom-right
      0.0f, 0.5f, 0.0f,    0.5f, 1.0f,    0.0f, 0.0f, 1.0f   // top
};

int[] indices = new int[]
{
      // triangle: bottom-left, bottom-right, top
      0, 1, 2
};

vao = GL30.glGenVertexArrays();
GL30.glBindVertexArray(vao);

FloatBuffer vertexBuffer = ByteBuffer.allocateDirect(vertices.length * Float.BYTES)
        .order(ByteOrder.nativeOrder()).asFloatBuffer();
vertexBuffer.put(vertices).flip();

IntBuffer indexBuffer = ByteBuffer.allocateDirect(indices.length * Integer.BYTES)
        .order(ByteOrder.nativeOrder()).asIntBuffer();
indexBuffer.put(indices).flip();

vbo = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertexBuffer, GL15.GL_STATIC_DRAW);

ebo = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indexBuffer, GL15.GL_STATIC_DRAW);

// first 3 floats for position
GL20.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 8 * Float.BYTES, 0);
GL20.glEnableVertexAttribArray(0);

// next 2 floats for texCoord
GL20.glVertexAttribPointer(1, 2, GL11.GL_FLOAT, false, 8 * Float.BYTES, 3 * Float.BYTES);
GL20.glEnableVertexAttribArray(1);

// last 3 floats for normal
GL20.glVertexAttribPointer(2, 3, GL11.GL_FLOAT, false, 8 * Float.BYTES, 5 * Float.BYTES);
GL20.glEnableVertexAttribArray(2);

// trying to be safe so we unbind
GL30.glBindVertexArray(prevVao);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, prevVbo);
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);

// now we've set up a reusable vao

// ========== render part ==========
useShader(); // we don't care what shaders are for now

GL30.glBindVertexArray(vao);
GL11.glDrawElements(GL11.GL_TRIANGLES, indices.length, GL11.GL_UNSIGNED_INT, 0);
GL30.glBindVertexArray(0); // 0 stands for an "empty VAO"/"null VAO"

unuseShader(); // we don't care what shaders are for now

Snipaste_2025-02-02_14-42-38

In this case, we use 8 floats per vertex: postion (layout (location = 0)), texcoord (layout (location = 1)), and normal (layout (location = 2)). (this is also known as the vertex format)

While this format might seem redundant, it's actually quite useful for handling complex models. Of course, you're free to define your own format but be sure to make your shaders follow the same format.

Your GL code must be consistent with you shader layout regarding the vertex format.

EG2

Draw two triangles using instancing

java
int prevVao = GL11.glGetInteger(GL30.GL_VERTEX_ARRAY_BINDING);
int prevVbo = GL11.glGetInteger(GL15.GL_ARRAY_BUFFER_BINDING);
int prevEbo = GL11.glGetInteger(GL15.GL_ELEMENT_ARRAY_BUFFER_BINDING);

int vao;
int vbo;
int ebo;

int instanceVbo;

float[] vertices = new float[]
{
        // positions         // texcoords   // normals
        -0.5f, -0.5f, 0.0f,  0.0f, 0.0f,    0.0f, 0.0f, 1.0f,  // bottom-left
        0.5f, -0.5f, 0.0f,   1.0f, 0.0f,    0.0f, 0.0f, 1.0f,  // bottom-right
        0.0f, 0.5f, 0.0f,    0.5f, 1.0f,    0.0f, 0.0f, 1.0f   // top
};

int[] indices = new int[]
{
        // triangle: bottom-left, bottom-right, top
        0, 1, 2
};

float[] instanceOffsets = new float[]
{
        -0.1f, 0.0f, 0.0f,  // first instance: shift left
        0.1f, 0.0f, 0.0f    // second instance: shift right
};

vao = GL30.glGenVertexArrays();
GL30.glBindVertexArray(vao);

FloatBuffer vertexBuffer = ByteBuffer.allocateDirect(vertices.length * Float.BYTES)
        .order(ByteOrder.nativeOrder()).asFloatBuffer();
vertexBuffer.put(vertices).flip();

IntBuffer indexBuffer = ByteBuffer.allocateDirect(indices.length * Integer.BYTES)
        .order(ByteOrder.nativeOrder()).asIntBuffer();
indexBuffer.put(indices).flip();

vbo = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertexBuffer, GL15.GL_STATIC_DRAW);

ebo = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indexBuffer, GL15.GL_STATIC_DRAW);

// first 3 floats for position
GL20.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 8 * Float.BYTES, 0);
GL20.glEnableVertexAttribArray(0);

// next 2 floats for texCoord
GL20.glVertexAttribPointer(1, 2, GL11.GL_FLOAT, false, 8 * Float.BYTES, 3 * Float.BYTES);
GL20.glEnableVertexAttribArray(1);

// last 3 floats for normal
GL20.glVertexAttribPointer(2, 3, GL11.GL_FLOAT, false, 8 * Float.BYTES, 5 * Float.BYTES);
GL20.glEnableVertexAttribArray(2);

// pass instancing data
instanceVbo = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, instanceVbo);
FloatBuffer instanceBuffer = ByteBuffer.allocateDirect(instanceOffsets.length * Float.BYTES)
        .order(ByteOrder.nativeOrder()).asFloatBuffer();
instanceBuffer.put(instanceOffsets).flip();
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, instanceBuffer, GL15.GL_STATIC_DRAW);

// another 3 floats for instancing data
GL20.glVertexAttribPointer(3, 3, GL11.GL_FLOAT, false, 3 * Float.BYTES, 0);
GL20.glEnableVertexAttribArray(3);
GL33.glVertexAttribDivisor(3, 1);

// trying to be safe so we unbind
GL30.glBindVertexArray(prevVao);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, prevVbo);
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);

// now we've set up a reusable vao

// ========== render part ==========
useShader(); // we don't care what shaders are for now

GL30.glBindVertexArray(vao);

// instead of GL11.glDrawElements(GL11.GL_TRIANGLES, indices.length, GL11.GL_UNSIGNED_INT, 0);
GL31.glDrawElementsInstanced(GL11.GL_TRIANGLES, indices.length, GL11.GL_UNSIGNED_INT, 0, 2);

GL30.glBindVertexArray(0);

unuseShader(); // we don't care what shaders are for now

Snipaste_2025-02-21_18-52-07

Instance offset (layout (location = 3)) is being handled explicitly in our VS. By the way, we use instancing to reduce draw calls as glDrawElementsInstanced is the only draw call here.

Notice:

java
float[] instanceOffsets = new float[]
{
    -0.1f, 0.0f, 0.0f,  // first instance: shift left
    0.1f, 0.0f, 0.0f    // second instance: shift right
};

It seems that the left-hand-side tri will be drawn first and then the right-hand-side tri, but this is actually not guaranteed by GL. Unexpected ordering could happen.

And this is why simply batching UI draw calls is risky. Instead, we need to emulate the draw order with the depth test regarding a batched call.

Finally, hope you understand how composable VAO is, where we can attach multiple VBOs to it. Moreover, we are able to modify VBO contents freely and even have multiple objects in one VBO (just like how instanceOffsets works). That's how things get streamlined with the programmable pipeline.

Contributors

© 2024 CleanroomMC. All Rights Reserved.