Tutorial 03: Instanced Rendering

Instanced Rendering allows us to render one model multiple times more efficient.

Instead of calling "glDrawArrays(...)" once for each object, and upload certain uniform data (like the model matrix), we call:

glDrawArraysInstanced(... , instancecount);

To access the instanced data, we need to modify the vertex array:

glBindBuffer(GL_ARRAY_BUFFER, m_instancebuffer);

glVertexAttribPointer(2, 4, GL_FLOAT, false, sizeof(ModelInstance), (void*)(sizeof(float) * 0));

glVertexAttribPointer(3, 4, GL_FLOAT, false, sizeof(ModelInstance), (void*)(sizeof(float) * 4));

glVertexAttribPointer(4, 4, GL_FLOAT, false, sizeof(ModelInstance), (void*)(sizeof(float) * 8));

glVertexAttribPointer(5, 4, GL_FLOAT, false, sizeof(ModelInstance), (void*)(sizeof(float) * 12));

glBindBuffer(GL_ARRAY_BUFFER, 0);

This adds 4 new pipes to the VAO (vertex array object). Each pipe sends a 4 x float to the program (vec4). Together these 4 pipes build a Matrix 4x4 "in_model":

layout (location = 2) in mat4 in_model;

//layout (location = 3) in use ...

//layout (location = 4) in use ...

//layout (location = 5) in use ...

So now we can send model matrices as attributes, which we will have to put into a separate buffer, the "instance buffer":

struct ModelInstance

{

mat4 ModelMatrix;

};

...

// setup instance buffer

// ----------------------------------------------------------------------------------------------------------

glBindBuffer(GL_UNIFORM_BUFFER, m_instancebuffer);

glBufferData(GL_UNIFORM_BUFFER, sizeof(ModelInstance) * MAX_MODEL_INSTANCES, NULL, GL_STREAM_DRAW);

glBindBuffer(GL_UNIFORM_BUFFER, 0);

// ----------------------------------------------------------------------------------------------------------

We want to send ONLY 1 instanced set of data to the program (vertex shader), so we need to prepare the VAO for that:

// sent these attributes only once per instance to the program:

glVertexAttribDivisor(2, 1);

glVertexAttribDivisor(3, 1);

glVertexAttribDivisor(4, 1);

glVertexAttribDivisor(5, 1);

Now everything is set up correctly, the remaining task is to build the instanced data each frame, upload the data to the GPU and call "glDrawArraysInstanced(...)":

// create instance buffer data:

std::vector<ModelInstance> instances;

for (auto& object : scene.Objects)

instances.push_back({ object.ModelMatrix() });

// determine instance count:

unsigned int instancecount = instances.size();

if (instancecount >= MAX_MODEL_INSTANCES)

instancecount = MAX_MODEL_INSTANCES;

// upload instance buffer data:

glBindBuffer(GL_UNIFORM_BUFFER, m_instancebuffer);

glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(ModelInstance) * instancecount, instances.data());

glBindBuffer(GL_UNIFORM_BUFFER, 0);

// render objects in scene

glDrawArraysInstanced(GL_TRIANGLES, 0, m_vertexcount, instancecount);

Complete Source Code:

Model.h

#pragma once

#include "Math.h"

struct Vertex

{

vec3 Position;

vec4 Color;

};

struct ModelInstance

{

mat4 ModelMatrix;

};

Renderer_OpenGL.h

#pragma once

#include "Renderer.h"

#include <GL/glew.h>

class Renderer_OpenGL : public Renderer

{

public:

Renderer_OpenGL(unsigned int width, unsigned int height);

virtual ~Renderer_OpenGL();

virtual void Render(const Scene& scene);

protected:

unsigned int m_width{ 0 }, m_height{ 0 };

unsigned int m_program{ 0 };

unsigned int m_vertexarray{ 0 };

unsigned int m_vertexbuffer{ 0 };

unsigned int m_instancebuffer{ 0 };

unsigned int m_vertexcount{ 0 };

};

Renderer_OpenGL.cpp

#include "Renderer_OpenGL.h"

#include "Shader.h"

#include "Model.h"

#include "OBJ.h"

#include <iostream>

#define MAX_MODEL_INSTANCES 1000

void CheckForGLError()

{

GLenum error;

while ((error = glGetError()) != GL_NO_ERROR)

{

std::cout << "ERROR: (Renderer_OpenGL) \t";

if (error == GL_INVALID_ENUM)

std::cout << "GL_INVALID_ENUM";

if (error == GL_INVALID_VALUE)

std::cout << "GL_INVALID_VALUE";

if (error == GL_INVALID_OPERATION)

std::cout << "GL_INVALID_OPERATION";

if (error == GL_INVALID_FRAMEBUFFER_OPERATION)

std::cout << "GL_INVALID_FRAMEBUFFER_OPERATION";

if (error == GL_OUT_OF_MEMORY)

std::cout << "GL_OUT_OF_MEMORY";

if (error == GL_STACK_UNDERFLOW)

std::cout << "GL_STACK_UNDERFLOW";

if (error == GL_STACK_OVERFLOW)

std::cout << "GL_STACK_OVERFLOW";

std::cout << (char)7 << std::endl; /*play sound*/

std::cin.get();

}

}

Renderer_OpenGL::Renderer_OpenGL(unsigned int width, unsigned int height)

{

// settings

// ----------------------------------------------------------------------------------------------------------

glEnable(GL_DEPTH_TEST); // 3D

glEnable(GL_CULL_FACE); // dont render backward faces

// ----------------------------------------------------------------------------------------------------------

// setup program

// ----------------------------------------------------------------------------------------------------------

m_program = glCreateProgram();

unsigned int vertexshader = glCreateShader(GL_VERTEX_SHADER);

unsigned int fragmentshader = glCreateShader(GL_FRAGMENT_SHADER);

CompileShader(vertexshader, LoadTextFile("shader.vs"));

CompileShader(fragmentshader, LoadTextFile("shader.fs"));

LinkProgram(m_program, { vertexshader , fragmentshader });

glDeleteShader(vertexshader);

glDeleteShader(fragmentshader);

// ----------------------------------------------------------------------------------------------------------

// setup vertexarray

// ----------------------------------------------------------------------------------------------------------

glGenVertexArrays(1, &m_vertexarray);

glGenBuffers(1, &m_vertexbuffer);

glGenBuffers(1, &m_instancebuffer);

glBindVertexArray(m_vertexarray);

glBindBuffer(GL_ARRAY_BUFFER, m_vertexbuffer);

glVertexAttribPointer(0, 3, GL_FLOAT, false, sizeof(Vertex), (void*)(sizeof(float) * 0));

glVertexAttribPointer(1, 4, GL_FLOAT, false, sizeof(Vertex), (void*)(sizeof(float) * 3));

glBindBuffer(GL_ARRAY_BUFFER, 0);

glBindBuffer(GL_ARRAY_BUFFER, m_instancebuffer);

glVertexAttribPointer(2, 4, GL_FLOAT, false, sizeof(ModelInstance), (void*)(sizeof(float) * 0));

glVertexAttribPointer(3, 4, GL_FLOAT, false, sizeof(ModelInstance), (void*)(sizeof(float) * 4));

glVertexAttribPointer(4, 4, GL_FLOAT, false, sizeof(ModelInstance), (void*)(sizeof(float) * 8));

glVertexAttribPointer(5, 4, GL_FLOAT, false, sizeof(ModelInstance), (void*)(sizeof(float) * 12));

glBindBuffer(GL_ARRAY_BUFFER, 0);

glEnableVertexAttribArray(0);

glEnableVertexAttribArray(1);

glEnableVertexAttribArray(2);

glEnableVertexAttribArray(3);

glEnableVertexAttribArray(4);

glEnableVertexAttribArray(5);

// sent these attributes only once per instance to the program:

glVertexAttribDivisor(2, 1);

glVertexAttribDivisor(3, 1);

glVertexAttribDivisor(4, 1);

glVertexAttribDivisor(5, 1);

glBindVertexArray(0);

// ----------------------------------------------------------------------------------------------------------

// setup vertex buffer

// ----------------------------------------------------------------------------------------------------------

OBJ model("monkey.obj", "");

if (!model.GetErrorString().empty())

std::cout << model.GetErrorString() << std::endl;

std::vector<Vertex> vertices;

for (auto& object : model.Objects)

{

for (auto& groupmember : object.second.Groups)

{

for (auto& facemap : groupmember.second.Faces)

{

for (auto& facetype : facemap.second)

{

// only process triangles:

if (facetype.first == 3)

{

for (auto& face : facetype.second)

{

vec3 position, normal;

position = model.Positions[face.Points[0].Index_v - 1];

vertices.push_back({ position,{ 1, 0, 0, 1 } });

position = model.Positions[face.Points[1].Index_v - 1];

vertices.push_back({ position,{ 0, 1, 0, 1 } });

position = model.Positions[face.Points[2].Index_v - 1];

vertices.push_back({ position,{ 0, 0, 1, 1 } });

}

}

}

}

}

}

if (vertices.size() == 0)

{

vertices.push_back({ { 0, 0, 0 },{ 1, 0, 0, 1 } });

vertices.push_back({ { 1, 0, 0 },{ 0, 1, 0, 1 } });

vertices.push_back({ { 0, 1, 0 },{ 0, 0, 1, 1 } });

}

m_vertexcount = vertices.size();

glBindBuffer(GL_ARRAY_BUFFER, m_vertexbuffer);

glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * vertices.size(), vertices.data(), GL_STATIC_DRAW);

glBindBuffer(GL_ARRAY_BUFFER, 0);

// ----------------------------------------------------------------------------------------------------------

// setup instance buffer

// ----------------------------------------------------------------------------------------------------------

glBindBuffer(GL_UNIFORM_BUFFER, m_instancebuffer);

glBufferData(GL_UNIFORM_BUFFER, sizeof(ModelInstance) * MAX_MODEL_INSTANCES, NULL, GL_STREAM_DRAW);

glBindBuffer(GL_UNIFORM_BUFFER, 0);

// ----------------------------------------------------------------------------------------------------------

}

Renderer_OpenGL::~Renderer_OpenGL()

{

glDeleteProgram(m_program);

glDeleteVertexArrays(1, &m_vertexarray);

glDeleteBuffers(1, &m_vertexbuffer);

glDeleteBuffers(1, &m_instancebuffer);

}

void Renderer_OpenGL::Render(const Scene & scene)

{

glClearColor(0, 0.5, 0, 0);

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glUseProgram(m_program);

glBindVertexArray(m_vertexarray);

// set camera

glUniformMatrix4fv(glGetUniformLocation(m_program, "View"), 1, false, value_ptr(scene.Camera.View()));

glUniformMatrix4fv(glGetUniformLocation(m_program, "Projection"), 1, false, value_ptr(perspective(scene.Camera.FoV, 1.33f, scene.Camera.ZNear, scene.Camera.ZFar)));

// create instance buffer data:

std::vector<ModelInstance> instances;

for (auto& object : scene.Objects)

instances.push_back({ object.ModelMatrix() });

// determine instance count:

unsigned int instancecount = instances.size();

if (instancecount >= MAX_MODEL_INSTANCES)

instancecount = MAX_MODEL_INSTANCES;

// upload instance buffer data:

glBindBuffer(GL_UNIFORM_BUFFER, m_instancebuffer);

glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(ModelInstance) * instancecount, instances.data());

glBindBuffer(GL_UNIFORM_BUFFER, 0);

// render objects in scene

glDrawArraysInstanced(GL_TRIANGLES, 0, m_vertexcount, instancecount);

glBindVertexArray(0);

glUseProgram(0);

// check for errors

CheckForGLError();

}

Shader Source Code:

Vertex Shader: "shader.vs"

#version 450 core

layout (location = 0) in vec3 in_position;

layout (location = 1) in vec4 in_color;

layout (location = 2) in mat4 in_model;

//layout (location = 3) in use ...

//layout (location = 4) in use ...

//layout (location = 5) in use ...

out VS_FS_INTERFACE {

vec4 color;

} vs_out;

uniform mat4 View = mat4(1);

uniform mat4 Projection = mat4(1);

void main(void)

{

mat4 MVP = Projection * View * in_model;

gl_Position = MVP * vec4(in_position, 1);

vs_out.color = in_color;

}