Skip to content

Instantly share code, notes, and snippets.

@kbenzie
Created January 18, 2013 10:56
Show Gist options
  • Save kbenzie/4563858 to your computer and use it in GitHub Desktop.
Save kbenzie/4563858 to your computer and use it in GitHub Desktop.
OpenGL instancing a model with multiple sub meshes using a single model matrix transform buffer.
#version 330
struct material_data
{
vec4 emissive;
vec4 ambient;
vec4 diffuse;
vec4 specular;
float shininess;
};
struct light_data
{
vec4 ambient;
vec4 diffuse;
vec4 specular;
vec4 direction;
};
uniform material_data material;
uniform light_data light;
uniform vec3 camera_position;
in vec3 position;
in vec3 normal;
in vec3 instanced_colour;
out vec4 colour;
vec4 phong(in vec3 eye)
{
vec4 ambient = material.ambient * light.ambient * vec4(instanced_colour, 1);
vec4 diffuse = max(dot(light.direction.xyz, normal), 0) *
(material.diffuse * light.diffuse) * vec4(instanced_colour, 1);
vec4 specular = pow(max(dot(reflect(-light.direction.xyz, normal), eye), 0), material.shininess) *
(material.specular * light.specular);
return vec4(ambient.xyz + diffuse.xyz + specular.xyz, 1);
}
void main()
{
vec3 eye = normalize(camera_position - position);
colour = vec4(material.emissive.xyz + phong(eye).xyz, 1);
}
#version 330
layout (location = 0) in vec3 vertex_position;
layout (location = 1) in vec3 vertex_normal;
layout (location = 2) in vec3 colour;
layout (location = 3) in vec4 model_matrix_0;
layout (location = 4) in vec4 model_matrix_1;
layout (location = 5) in vec4 model_matrix_2;
layout (location = 6) in vec4 model_matrix_3;
out vec3 position;
out vec3 normal;
out vec3 instanced_colour;
uniform mat4 view_matrix;
uniform mat4 projection_matrix;
void main()
{
mat4 model_matrix;
model_matrix[0] = model_matrix_0;
model_matrix[1] = model_matrix_1;
model_matrix[2] = model_matrix_2;
model_matrix[3] = model_matrix_3;
normal = normalize(mat3(inverse(transpose(model_matrix))) * vertex_normal);
mat4 model_view_matrix = view_matrix * model_matrix;
instanced_colour = colour;
position = vec3(model_view_matrix * vec4(vertex_position, 1));
gl_Position = (projection_matrix * model_view_matrix) * vec4(vertex_position, 1);
}
#include "model.h"
#include "log.h"
#include <IL\il.h>
#include <IL\ilut.h>
#include <assimp\scene.h>
#include <assimp\Importer.hpp>
#include <assimp\postprocess.h>
namespace elixir
{
mesh::mesh()
: m_vao(0)
, m_ibo(0)
, m_size(0)
{}
mesh::mesh(const std::vector<glm::vec3> &positions,
const std::vector<glm::vec3> &normals,
const std::vector<GLuint> &indices)
{
m_vbos.resize(2);
if (indices.size()) m_size = indices.size();
else m_size = positions.size();
glGenBuffers(m_vbos.size(), &m_vbos[0]);
if (indices.size()) glGenBuffers(1, &m_ibo);
else m_ibo = 0;
glBindBuffer(GL_ARRAY_BUFFER, m_vbos[0]); // vertex_position
glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec3) * positions.size(), &positions[0], GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, m_vbos[1]); // vertex_normal
glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec3) * normals.size(), &normals[0], GL_STATIC_DRAW);
if (m_ibo) {
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ibo); // index_buffer_object
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * indices.size(), &indices[0], GL_STATIC_DRAW);
}
glGenVertexArrays(1, &m_vao);
glBindVertexArray(m_vao);
glEnableVertexAttribArray(0); // vertex_position
glEnableVertexAttribArray(1); // vertex_normal
glBindBuffer(GL_ARRAY_BUFFER, m_vbos[0]); // vertex_position
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
glBindBuffer(GL_ARRAY_BUFFER, m_vbos[1]); // vertex_normal
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
model::model()
{}
model::model(const std::vector<mesh> &meshes)
: m_vbos(2, 0)
, m_meshes(meshes)
{
glGenBuffers(2, &m_vbos[0]);
for (auto i=0u; i < m_meshes.size(); ++i) {
glBindVertexArray(m_meshes[i].m_vao);
glEnableVertexAttribArray(2); // colour
glEnableVertexAttribArray(3); // model_matrix_0
glEnableVertexAttribArray(4); // model_matrix_1
glEnableVertexAttribArray(5); // model_matrix_2
glEnableVertexAttribArray(6); // model_matrix_3
}
glBindBuffer(GL_ARRAY_BUFFER, m_vbos[0]); // colour
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(glm::vec3), (GLvoid*)0);
glVertexAttribDivisor(2, 1);
glBindBuffer(GL_ARRAY_BUFFER, m_vbos[1]);
glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (GLvoid*)0);
glVertexAttribDivisor(3, 1); // model_matrix_0
glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (GLvoid*)sizeof(glm::vec4));
glVertexAttribDivisor(4, 1); // model_matrix_1
glVertexAttribPointer(5, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (GLvoid*)(sizeof(glm::vec4)*2));
glVertexAttribDivisor(5, 1); // model_matrix_2
glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (GLvoid*)(sizeof(glm::vec4)*3));
glVertexAttribDivisor(6, 1); // model_matrix_3
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
model::~model()
{
for (auto i=0u; i < m_meshes.size(); ++i) {
// Delete buffers
if (m_meshes[i].m_vbos.size())
glDeleteBuffers(m_meshes[i].m_vbos.size(), &m_meshes[i].m_vbos[0]);
if (glIsBuffer(m_meshes[i].m_vao)) glDeleteBuffers(1, &m_meshes[i].m_vao);
if (glIsBuffer(m_meshes[i].m_ibo)) glDeleteBuffers(1, &m_meshes[i].m_ibo);
m_meshes[i].m_vbos.clear();
m_meshes[i].m_vao = 0;
m_meshes[i].m_ibo = 0;
m_meshes[i].m_size = 0;
}
if (m_vbos.size()) glDeleteBuffers(2, &m_vbos[0]);
}
void model::add(const glm::mat4 &model_matrix, const glm::vec3 &colour)
{
m_model_matrices.push_back(model_matrix);
m_colours.push_back(colour);
set_buffers();
}
void model::add(const unsigned size, const glm::vec3 &colour)
{
m_model_matrices.resize(size, glm::mat4(1));
set_buffers();
}
void model::set(const std::vector<glm::mat4> &model_matrices, const std::vector<glm::vec3> &colours)
{
m_model_matrices = model_matrices;
m_colours = colours;
set_buffers();
}
const GLuint & model::model_matrix_buffer() const
{
return m_vbos[1];
}
void model::render(glsl_program &program)
{
for (auto i=0u; i<m_meshes.size(); ++i) {
program.set_uniform("material.ambient", m_meshes[i].m_material.ambient);
program.set_uniform("material.diffuse", m_meshes[i].m_material.diffuse);
program.set_uniform("material.specular", m_meshes[i].m_material.specular);
program.set_uniform("material.shininess", m_meshes[i].m_material.shininess);
glBindVertexArray(m_meshes[i].m_vao);
if (m_meshes[i].m_ibo)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_meshes[i].m_ibo);
for (auto j=0u; j<m_model_matrices.size(); ++j) {
program.set_uniform("model_matrix", m_model_matrices[j]);
if (m_meshes[i].m_ibo) {
glDrawElements(GL_TRIANGLES,
m_meshes[i].m_size,
GL_UNSIGNED_INT,
0);
} else {
glDrawArrays(GL_TRIANGLES,
0,
m_meshes[i].m_size);
}
}
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
}
void model::render_instanced(glsl_program &program)
{
for (auto i=0u; i<m_meshes.size(); ++i) {
program.set_uniform("material.ambient", m_meshes[i].m_material.ambient);
program.set_uniform("material.diffuse", m_meshes[i].m_material.diffuse);
program.set_uniform("material.specular", m_meshes[i].m_material.specular);
program.set_uniform("material.shininess", m_meshes[i].m_material.shininess);
glBindVertexArray(m_meshes[i].m_vao);
if (m_meshes[i].m_ibo) {
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_meshes[i].m_ibo);
glDrawElementsInstanced(GL_TRIANGLES,
m_meshes[i].m_size,
GL_UNSIGNED_INT,
0,
m_model_matrices.size());
} else {
glDrawArraysInstanced(GL_TRIANGLES,
0,
m_meshes[i].m_size,
m_model_matrices.size());
}
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
}
void model::set_buffers()
{
if (glIsBuffer(m_vbos[0])) {
glBindBuffer(GL_ARRAY_BUFFER, m_vbos[0]);
glBufferData(GL_ARRAY_BUFFER,
sizeof(glm::vec3) * m_colours.size(),
&m_colours[0],
GL_DYNAMIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
if (glIsBuffer(m_vbos[1])) {
glBindBuffer(GL_ARRAY_BUFFER, m_vbos[1]);
glBufferData(GL_ARRAY_BUFFER,
sizeof(glm::mat4) * m_model_matrices.size(),
&m_model_matrices[0],
GL_DYNAMIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
}
}
namespace
{
elixir::material extract_material(const aiScene &scene,
const unsigned material_index)
{
using namespace std;
using namespace glm;
const aiMaterial &mat = *scene.mMaterials[material_index];
aiColor3D colour; float value;
elixir::material material;
if (mat.Get(AI_MATKEY_COLOR_AMBIENT, colour) == AI_SUCCESS)
material.ambient = vec4(colour.r, colour.g, colour.b, 1);
if (mat.Get(AI_MATKEY_COLOR_DIFFUSE, colour) == AI_SUCCESS)
material.diffuse = vec4(colour.r, colour.g, colour.b, 1);
if (mat.Get(AI_MATKEY_COLOR_SPECULAR, colour) == AI_SUCCESS)
material.specular = vec4(colour.r, colour.g, colour.b, 1);
if (mat.Get(AI_MATKEY_SHININESS, value) == AI_SUCCESS)
material.shininess = value;
return material;
}
std::unique_ptr<elixir::model> extract_meshes(const aiScene &scene,
const std::string &filename)
{
using namespace std;
using namespace glm;
vector<elixir::mesh> meshes(scene.mNumMeshes);
for (auto mesh_index = 0u; mesh_index < scene.mNumMeshes; ++mesh_index) {
auto &mesh = *scene.mMeshes[mesh_index];
vector<vec3> positions(mesh.mNumVertices);
vector<vec3> normals(mesh.mNumVertices);
vector<GLuint> indices;
for (auto i=0u; i < mesh.mNumVertices; ++i) {
positions[i] = vec3(mesh.mVertices[i].x, mesh.mVertices[i].y, mesh.mVertices[i].z);
normals[i] = vec3(mesh.mNormals[i].x, mesh.mNormals[i].y, mesh.mNormals[i].z);
}
for (auto i=0u; i < mesh.mNumFaces; ++i) {
indices.push_back(mesh.mFaces[i].mIndices[0]);
indices.push_back(mesh.mFaces[i].mIndices[1]);
indices.push_back(mesh.mFaces[i].mIndices[2]);
}
meshes[mesh_index] = elixir::mesh(positions,
normals,
indices,
elixir::material());//extract_material(scene, mesh.mMaterialIndex));
}
return unique_ptr<elixir::model>(new elixir::model(meshes));
}
}
namespace elixir
{
std::unique_ptr<model> load_model(const std::string &filename)
{
Assimp::Importer importer;
auto scene = importer.ReadFile(filename.c_str()
, aiProcess_ValidateDataStructure // perform a full validation of the loader's output
| aiProcess_Triangulate // triangulate polygons with more than 3 edges
| aiProcess_ImproveCacheLocality // improve the cache locality of the output vertices
| aiProcess_RemoveRedundantMaterials // remove redundant materials
| aiProcess_FindDegenerates // remove degenerated polygons from the import
| aiProcess_FindInvalidData // detect invalid model data, such as invalid normal vectors
| aiProcess_OptimizeMeshes // join small meshes, if possible;
| aiProcess_SplitLargeMeshes // split large, unrenderable meshes into submeshes
);
if (scene) return extract_meshes(*scene, filename);
else {
Log << "model_facotry::load() failed read file: " << filename << "\n";
return nullptr;
}
}
}
#pragma once
#include "glsl_program.h"
#include <vector>
#include <tuple>
#include <memory>
#include <GL\glew.h>
#include <glm\glm.hpp>
namespace elixir
{
struct mesh
{
mesh();
mesh(const std::vector<glm::vec3> &positions,
const std::vector<glm::vec3> &normals,
const std::vector<GLuint> &indices);
std::vector<GLuint> m_vbos;
GLuint m_ibo, m_vao;
GLsizei m_size;
};
class model
{
public:
model();
model(const std::vector<mesh> &meshes);
~model();
void add(const glm::mat4 &model_matrix, const glm::vec3 &colour);
void add(const unsigned size, const glm::vec3 &colour);
void set(const std::vector<glm::mat4> &model_matrices, const std::vector<glm::vec3> &colours);
void set_model_matrices(const unsigned size);
const GLuint & model_matrix_buffer() const;
const GLuint & colour_buffer() const;
void render(glsl_program &program);
void render_instanced(glsl_program &program);
private:
void set_buffers();
std::vector<GLuint> m_vbos;
std::vector<mesh> m_meshes;
std::vector<glm::mat4> m_model_matrices;
std::vector<glm::vec3> m_colours;
};
std::unique_ptr<model> load_model(const std::string &filename);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment