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;

}