Tutorial 04.1: Object Selection
Complete Source Code:
Main.cpp
...
int Main::MainLoop()
{
double tnow = 0, tframe = 0, tlastframe = 0;
unsigned int tlastrenamed = 0, fpscounter = 0;
m_renderer = std::unique_ptr<Renderer>(new Renderer_OpenGL(800, 600));
Scene scene;
while (!Framework->IsCloseRequested())
{
// limit the FPS
tnow = Framework->Time();
tframe = tnow - tlastframe;
if (tframe < 1.0 / m_fps_goal)
continue;
tlastframe = tnow;
// rename the window title each second, show current FPS
fpscounter++;
if ((unsigned int)tnow != tlastrenamed)
{
tlastrenamed = (unsigned int)tnow;
Framework->SetWindowTitle("OpenGL FPS: " + std::to_string(fpscounter));
fpscounter = 0;
}
// simulate scene
static int tracked_ID = 0;
scene.AnimateNextFrame(tframe, tracked_ID);
// render scene
if (m_renderer.get())
m_renderer->Render(scene, tracked_ID);
Framework->SwapBuffers();
Framework->Update();
}
return EXIT_SUCCESS;
}
...
Math.h
#pragma once
#include <glm/glm.hpp>
#include <glm/gtx/transform.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/quaternion.hpp>
#include <glm/gtx/quaternion.hpp>
#include <glm/gtx/rotate_vector.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <iostream>
using namespace glm;
#define PI 3.14159265358979323846264338327950288419716939937510f
#define min2(arg1, arg2) (arg1 < arg2 ? arg1 : arg2) // minimum of 2 values
#define max2(arg1, arg2) (arg1 > arg2 ? arg1 : arg2) // maximum of 2 values
#define abs2(arg) (arg > 0 ? arg : -arg)) // positive value
unsigned int GetUniqueID();
Math.cpp
#include "Math.h"
unsigned int GetUniqueID()
{
static unsigned int ID = 0;
return ++ID;
}
Model.h
#pragma once
#include "Math.h"
struct Vertex
{
vec3 Position;
vec4 Color;
};
struct ModelInstance
{
mat4 ModelMatrix;
int ObjectID;
};
Renderer.h
#pragma once
#include "Scene.h"
class Renderer
{
public:
virtual ~Renderer() {}
virtual void Render(const Scene& scene, int& tracked_ID) = 0;
};
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, int& tracked_ID);
protected:
unsigned int m_width{ 0 }, m_height{ 0 };
unsigned int m_framebuffer{ 0 };
unsigned int m_framebuffercolortexture{ 0 };
unsigned int m_framebufferintegertexture{ 0 };
unsigned int m_framebufferdepthtexture{ 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 "Main.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 framebuffer:
// ----------------------------------------------------------------------------------------------------------
// make sure that the framebuffer isnt too small
m_width = max2(100, width);
m_height = max2(100, height);
glGenTextures(1, &m_framebuffercolortexture);
glBindTexture(GL_TEXTURE_2D, m_framebuffercolortexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, m_width, m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
glGenTextures(1, &m_framebufferintegertexture);
glBindTexture(GL_TEXTURE_2D, m_framebufferintegertexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// special texture to store only integer values (32 bit)
glTexImage2D(GL_TEXTURE_2D, 0, GL_R32I, m_width, m_height, 0, GL_RED_INTEGER, GL_INT, 0);
glBindTexture(GL_TEXTURE_2D, 0);
glGenTextures(1, &m_framebufferdepthtexture);
glBindTexture(GL_TEXTURE_2D, m_framebufferdepthtexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, m_width, m_height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, 0);
glBindTexture(GL_TEXTURE_2D, 0);
// create framebuffer
glGenFramebuffers(1, &m_framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_framebuffercolortexture, 0);
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, m_framebufferintegertexture, 0);
glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, m_framebufferdepthtexture, 0);
std::vector<GLenum> drawbuffers = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 };
glDrawBuffers(drawbuffers.size(), drawbuffers.data());
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// ERROR CHECK:
GLenum status = glCheckNamedFramebufferStatus(m_framebuffer, GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE)
{
if (status == GL_FRAMEBUFFER_UNDEFINED)
std::cout << "ERROR: undefined framebuffer" << std::endl;
if (status == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT)
std::cout << "ERROR: a necessary attachment is uninitialized" << std::endl;
if (status == GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT)
std::cout << "ERROR: no attachments" << std::endl;
if (status == GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER)
std::cout << "ERROR: incomplete draw buffer" << std::endl;
if (status == GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER)
std::cout << "ERROR: incomplete read buffer" << std::endl;
if (status == GL_FRAMEBUFFER_UNSUPPORTED)
std::cout << "ERROR: combination of attachments is not supported" << std::endl;
if (status == GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE)
std::cout << "ERROR: number if samples for all attachments does not match" << std::endl;
}
// ----------------------------------------------------------------------------------------------------------
// 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));
glVertexAttribIPointer(6, 1, GL_INT, sizeof(ModelInstance), (void*)(sizeof(float) * 16));
glBindBuffer(GL_ARRAY_BUFFER, 0);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glEnableVertexAttribArray(3);
glEnableVertexAttribArray(4);
glEnableVertexAttribArray(5);
glEnableVertexAttribArray(6);
// sent these attributes only once per instance to the program:
glVertexAttribDivisor(2, 1);
glVertexAttribDivisor(3, 1);
glVertexAttribDivisor(4, 1);
glVertexAttribDivisor(5, 1);
glVertexAttribDivisor(6, 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);
glDeleteFramebuffers(1, &m_framebuffer);
glDeleteTextures(1, &m_framebuffercolortexture);
glDeleteTextures(1, &m_framebufferintegertexture);
glDeleteTextures(1, &m_framebufferdepthtexture);
glDeleteVertexArrays(1, &m_vertexarray);
glDeleteBuffers(1, &m_vertexbuffer);
glDeleteBuffers(1, &m_instancebuffer);
}
void Renderer_OpenGL::Render(const Scene & scene, int& tracked_ID)
{
glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
// explicitly clear each buffer:
float clearvalue0[] = { 0.0f, 0.5f, 0.0f, 0.0f }; // background color
int clearvalue1[] = { -1 }; // background integer values
glClearBufferfv(GL_COLOR, 0, clearvalue0);
glClearBufferiv(GL_COLOR, 1, clearvalue1);
glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_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(), (int)object.ID() });
// 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);
// read ID at cursor position:
ivec2 cursorposition = Framework->CursorPosition();
glReadBuffer(GL_COLOR_ATTACHMENT1);
glReadPixels(cursorposition.x, cursorposition.y, 1, 1, GL_RED_INTEGER, GL_INT, &tracked_ID);
// copy to default framebuffer:
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, m_framebuffer);
glReadBuffer(GL_COLOR_ATTACHMENT0);
glBlitFramebuffer(
0, 0, m_width, m_height,
0, 0, m_width, m_height,
GL_COLOR_BUFFER_BIT,
GL_NEAREST);
// check for errors
CheckForGLError();
}
Scene.h
#pragma once
#include "Math.h"
#include "Orientation.h"
#include <vector>
struct Object : public Orientation
{
Object(const vec3& position)
: Orientation(position)
{
m_ID = GetUniqueID();
}
unsigned int ID() const { return m_ID; }
// ...
private:
unsigned int m_ID;
};
struct Scene
{
struct : public Orientation {
float FoV{ radians(45.0f) };
float ZNear{ 0.1f }, ZFar{ 100.0f };
} Camera;
std::vector<Object> Objects;
Scene();
void AnimateNextFrame(float timestep, int tracked_ID);
};
Scene.cpp
#include "Scene.h"
#include "Main.h"
Scene::Scene()
{
// put the camera back a little ...
Camera.SetPosition(vec3(0, 1, 5));
// create some object ...
Objects.push_back(Object(vec3(-2, 0, -2)));
Objects.push_back(Object(vec3(+2, 0, -2)));
Objects.push_back(Object(vec3(+2, 0, +2)));
Objects.push_back(Object(vec3(-2, 0, +2)));
// ...
}
void Scene::AnimateNextFrame(float timestep, int tracked_ID)
{
// camera control
// hold right mouse button to rotate the camera
// scroll up/down to move the camera
// ----------------------------------------------------------------------------------------------------------
if (Framework->IsClicked(MOUSE_BUTTON_RIGHT))
{
Camera.Yaw(Framework->EventCursorMoved().x * -0.01f);
Camera.Pitch(Framework->EventCursorMoved().y * +0.01f);
}
if (Framework->ScrolledUp())
Camera.Move(Camera.Forward() * +0.5f);
if (Framework->ScrolledDown())
Camera.Move(Camera.Forward() * -0.5f);
// ----------------------------------------------------------------------------------------------------------
// animate all objects
// ----------------------------------------------------------------------------------------------------------
for (auto& object : Objects)
object.Yaw(radians(360.0f) * timestep);
// ----------------------------------------------------------------------------------------------------------
// just print the ID at cursor position:
std::cout << "ID at cursor position: \t" << tracked_ID << std::endl;
// ...
}
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 ...
layout (location = 6) in int in_objectID;
out VS_FS_INTERFACE {
vec4 color;
flat int objectID;
} 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;
vs_out.objectID = in_objectID;
}
Fragment Shader: "shader.fs"
#version 450 core
in VS_FS_INTERFACE {
vec4 color;
flat int objectID;
} vs_out;
layout (location = 0) out vec4 Fragment0;
layout (location = 1) out int Fragment1;
void main(void)
{
Fragment0 = vs_out.color;
Fragment1 = vs_out.objectID;
}