0. Basic Setup

Complete Source Code:

Main.cpp

#include <GL/glew.h>

#include <GLFW/glfw3.h>

#include <glm/gtx/rotate_vector.hpp>

#include <iostream>

#include "Scene.h"

#include "Graphics.h"

/* variables */

#define SCREEN_WIDTH 800

#define SCREEN_HEIGHT 600

GLFWwindow* window = nullptr;

Scene scene;

Graphics graphics;

/* callbacks */

void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods);

void cursor_position_callback(GLFWwindow* window, double xpos, double ypos);

void mouse_button_callback(GLFWwindow* window, int button, int action, int mods);

void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);

void window_size_callback(GLFWwindow* window, int width, int height);

int main(void)

{

/* Initialize the library */

if (!glfwInit())

return -1;

/* Create a windowed mode window and its OpenGL 4.5 context */

glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);

glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5);

glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

window = glfwCreateWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Scene, Light, Material", NULL, NULL);

if (!window)

{

glfwTerminate();

return -2;

}

/* Make the window's context current */

glfwMakeContextCurrent(window);

/* Set callback functions */

glfwSetKeyCallback(window, key_callback);

glfwSetCursorPosCallback(window, cursor_position_callback);

glfwSetMouseButtonCallback(window, mouse_button_callback);

glfwSetScrollCallback(window, scroll_callback);

glfwSetWindowSizeCallback(window, window_size_callback);

/* Initialize GLEW */

if (glewInit() != GLEW_OK)

{

glfwTerminate();

return -3;

}

/* Initialize graphics */

if (!graphics.Initialize(SCREEN_WIDTH, SCREEN_HEIGHT))

{

graphics.CleanUp();

glfwTerminate();

return -4;

}

/* Loop until the user closes the window */

while (!glfwWindowShouldClose(window))

{

/* Render here */

graphics.Render(scene);

/* Swap front and back buffers */

glfwSwapBuffers(window);

/* Poll for and process events */

glfwPollEvents();

}

graphics.CleanUp();

glfwTerminate();

return 0;

}

void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)

{

if (action == GLFW_PRESS || action == GLFW_REPEAT)

{

/* camera position */

if (key == GLFW_KEY_W)

scene.Camera.Position += scene.Camera.Forward * 0.5f;

if (key == GLFW_KEY_S)

scene.Camera.Position -= scene.Camera.Forward * 0.5f;

if (key == GLFW_KEY_A)

scene.Camera.Position -= glm::cross(scene.Camera.Forward, scene.Camera.Up) * 0.5f;

if (key == GLFW_KEY_D)

scene.Camera.Position += glm::cross(scene.Camera.Forward, scene.Camera.Up) * 0.5f;

}

}

void cursor_position_callback(GLFWwindow* window, double xpos, double ypos)

{

static double xpos_previous = xpos;

static double ypos_previous = ypos;

double xpos_delta = xpos - xpos_previous;

double ypos_delta = ypos - ypos_previous;

/* camera yaw */

float angle_yaw = xpos_delta * -0.01f;

scene.Camera.Forward = glm::rotate(scene.Camera.Forward, angle_yaw, scene.Camera.Up);

xpos_previous = xpos;

ypos_previous = ypos;

}

void mouse_button_callback(GLFWwindow* window, int button, int action, int mods)

{

if (action == GLFW_PRESS)

{

if (button == GLFW_MOUSE_BUTTON_LEFT)

{ /* unused */ }

if (button == GLFW_MOUSE_BUTTON_MIDDLE)

{ /* unused */ }

if (button == GLFW_MOUSE_BUTTON_RIGHT)

{ /* unused */ }

}

}

void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)

{

/* camera position */

if (yoffset > 0)

scene.Camera.Position += scene.Camera.Forward * 0.5f;

if (yoffset < 0)

scene.Camera.Position -= scene.Camera.Forward * 0.5f;

}

void window_size_callback(GLFWwindow* window, int width, int height)

{

/* window size */

graphics.SetWindowSize(width, height);

}

Graphics.h

#pragma once

#include <GL/glew.h>

#include <glm/glm.hpp>

#include <glm/gtc/quaternion.hpp>

#include "Scene.h"

struct Vertex

{

glm::vec3 Position;

glm::vec3 Normal;

};

class Graphics

{

public:

Graphics();

virtual ~Graphics();

bool Initialize(unsigned int width, unsigned int height);

void Render(const Scene& scene);

void CleanUp();

void SetWindowSize(unsigned int width, unsigned int height);

protected:

static void CheckForGLError();

glm::uvec2 m_windowsize{ 0, 0 };

GLuint m_program = 0;

GLuint m_vertexshader = 0;

GLuint m_fragmentshader = 0;

GLuint m_vertexarray = 0;

GLuint m_vertexbuffer = 0;

struct {

GLint Model{ -1 };

GLint View{ -1 };

GLint Projection{ -1 };

} m_uniformlocation;

};

Graphics.cpp

#include "Graphics.h"

#include "Shader.h"

#include <glm/gtc/type_ptr.hpp>

#include <glm/gtx/transform.hpp>

#include <glm/gtc/matrix_transform.hpp>

#include <glm/gtx/rotate_vector.hpp>

#include <glm/gtx/quaternion.hpp>

#include <iostream>

#include <vector>

using namespace std;

using namespace glm;

Graphics::Graphics()

{}

Graphics::~Graphics()

{}

bool Graphics::Initialize(unsigned int width, unsigned int height)

{

// cleanup previously allocated objects

CleanUp();

// background color

glClearColor(0.3f, 0.5f, 0.8f, 0.0f);

glEnable(GL_DEPTH_TEST);

SetWindowSize(width, height);

// create all objects

m_program = glCreateProgram();

m_vertexshader = glCreateShader(GL_VERTEX_SHADER);

m_fragmentshader = glCreateShader(GL_FRAGMENT_SHADER);

glGenVertexArrays(1, &m_vertexarray);

glGenBuffers(1, &m_vertexbuffer);

// compile shaders and link program

if (!CompileShader(m_vertexshader, LoadTextFile("model.vertexshader.txt")))

{ cin.get(); return false; }

if (!CompileShader(m_fragmentshader, LoadTextFile("model.fragmentshader.txt")))

{ cin.get(); return false; }

if (!LinkProgram(m_program, { m_vertexshader, m_fragmentshader }))

{ cin.get(); return false; }

// query uniform locations

m_uniformlocation.Model = glGetUniformLocation(m_program, "Model");

m_uniformlocation.View = glGetUniformLocation(m_program, "View");

m_uniformlocation.Projection = glGetUniformLocation(m_program, "Projection");

// setup vertex array

glBindVertexArray(m_vertexarray);

glBindBuffer(GL_ARRAY_BUFFER, m_vertexbuffer);

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)(0));

glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)(sizeof(float) * 3));

glBindBuffer(GL_ARRAY_BUFFER, 0);

glEnableVertexAttribArray(0);

glEnableVertexAttribArray(1);

glBindVertexArray(0);

// setup vertex buffer (a simple triangularized cube)

vector<Vertex> vertices = {

/* +x */

{ { +1, -1, -1 }, { +1, 0, 0 } },

{ { +1, +1, -1 }, { +1, 0, 0 } },

{ { +1, +1, +1 }, { +1, 0, 0 } },

{ { +1, -1, -1 }, { +1, 0, 0 } },

{ { +1, +1, +1 }, { +1, 0, 0 } },

{ { +1, -1, +1 }, { +1, 0, 0 } },

/* -x */

{ { -1, +1, -1 }, { -1, 0, 0 } },

{ { -1, -1, -1 }, { -1, 0, 0 } },

{ { -1, -1, +1 }, { -1, 0, 0 } },

{ { -1, +1, -1 }, { -1, 0, 0 } },

{ { -1, -1, +1 }, { -1, 0, 0 } },

{ { -1, +1, +1 }, { -1, 0, 0 } },

/* +y */

{ { +1, +1, -1 }, { 0, +1, 0 } },

{ { -1, +1, -1 }, { 0, +1, 0 } },

{ { -1, +1, +1 }, { 0, +1, 0 } },

{ { +1, +1, -1 }, { 0, +1, 0 } },

{ { -1, +1, +1 }, { 0, +1, 0 } },

{ { +1, +1, +1 }, { 0, +1, 0 } },

/* -y */

{ { -1, -1, -1 }, { 0, -1, 0 } },

{ { +1, -1, -1 }, { 0, -1, 0 } },

{ { +1, -1, +1 }, { 0, -1, 0 } },

{ { -1, -1, -1 }, { 0, -1, 0 } },

{ { +1, -1, +1 }, { 0, -1, 0 } },

{ { -1, -1, +1 }, { 0, -1, 0 } },

/* +z */

{ { -1, -1, +1 }, { 0, 0, +1 } },

{ { +1, -1, +1 }, { 0, 0, +1 } },

{ { +1, +1, +1 }, { 0, 0, +1 } },

{ { -1, -1, +1 }, { 0, 0, +1 } },

{ { +1, +1, +1 }, { 0, 0, +1 } },

{ { -1, +1, +1 }, { 0, 0, +1 } },

/* -z */

{ { +1, -1, -1 }, { 0, 0, -1 } },

{ { -1, -1, -1 }, { 0, 0, -1 } },

{ { -1, +1, -1 }, { 0, 0, -1 } },

{ { +1, -1, -1 }, { 0, 0, -1 } },

{ { -1, +1, -1 }, { 0, 0, -1 } },

{ { +1, +1, -1 }, { 0, 0, -1 } },

};

glBindBuffer(GL_ARRAY_BUFFER, m_vertexbuffer);

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

glBindBuffer(GL_ARRAY_BUFFER, 0);

/* successfully initialized */

return true;

}

void Graphics::Render(const Scene& scene)

{

/* set scene camera matrices */

float aspectratio = (float)m_windowsize.x / m_windowsize.y;

mat4 View = lookAt(scene.Camera.Position, scene.Camera.Position + scene.Camera.Forward, scene.Camera.Up);

mat4 Projection = perspective(scene.Camera.FieldOfView, aspectratio, scene.Camera.ZNear, scene.Camera.ZFar);

glProgramUniformMatrix4fv(m_program, m_uniformlocation.View, 1, false, value_ptr(View));

glProgramUniformMatrix4fv(m_program, m_uniformlocation.Projection, 1, false, value_ptr(Projection));

/* clear framebuffer */

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glUseProgram(m_program);

glBindVertexArray(m_vertexarray);

/* draw objects */

for (auto& object : scene.Objects)

{

mat4 Model = translate(object.Position) * toMat4(object.Rotation) * scale(object.Size);

glProgramUniformMatrix4fv(m_program, m_uniformlocation.Model, 1, false, value_ptr(Model));

glDrawArrays(GL_TRIANGLES, 0, 36);

}

glBindVertexArray(0);

glUseProgram(0);

/* check for any error */

CheckForGLError();

}

void Graphics::CleanUp()

{

// destroy all objects

glDeleteProgram(m_program);

glDeleteShader(m_vertexshader);

glDeleteShader(m_fragmentshader);

glDeleteVertexArrays(1, &m_vertexarray);

glDeleteBuffers(1, &m_vertexbuffer);

}

void Graphics::SetWindowSize(unsigned int width, unsigned int height)

{

if (width < 100)

width = 100;

if (height < 100)

height = 100;

m_windowsize = glm::uvec2(width, height);

glViewport(0, 0, m_windowsize.x, m_windowsize.y);

}

void Graphics::CheckForGLError()

{

for (GLenum error; (error = glGetError()) != GL_NO_ERROR;)

{

cout << "OpenGL Error: \t";

if (error == GL_INVALID_ENUM)

cout << "GL_INVALID_ENUM";

if (error == GL_INVALID_VALUE)

cout << "GL_INVALID_VALUE";

if (error == GL_INVALID_OPERATION)

cout << "GL_INVALID_OPERATION";

if (error == GL_STACK_OVERFLOW)

cout << "GL_STACK_OVERFLOW";

if (error == GL_STACK_UNDERFLOW)

cout << "GL_STACK_UNDERFLOW";

if (error == GL_OUT_OF_MEMORY)

cout << "GL_OUT_OF_MEMORY";

if (error == GL_INVALID_FRAMEBUFFER_OPERATION)

cout << "GL_INVALID_FRAMEBUFFER_OPERATION";

if (error == GL_CONTEXT_LOST)

cout << "GL_CONTEXT_LOST";

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

cin.get();

}

}

Scene.h

#pragma once

#include <glm/glm.hpp>

#include <glm/gtc/quaternion.hpp>

#include <list>

struct Object

{

glm::vec3 Position{ 0, 0, 0 };

glm::quat Rotation{ 1, 0, 0, 0 };

glm::vec3 Size{ 1, 1, 1 };

};

struct Scene

{

Scene();

struct {

glm::vec3 Position{ 2, 2, 5 };

glm::vec3 Forward{ 0, 0, -1 };

glm::vec3 Up{ 0, 1, 0 };

float FieldOfView{ glm::radians(45.0f) };

float ZNear{ 0.1f };

float ZFar{ 100.0f };

} Camera;

std::list<Object> Objects;

};

Scene.cpp

#include "Scene.h"

Scene::Scene()

{

/* create 1 object (which will be represented as cube) */

Objects.push_back(Object());

}

Shader.h

#pragma once

#include <string>

#include <list>

// shader functions

std::string LoadTextFile(const std::string& filepath);

std::string ShaderTypeName(unsigned int shader);

bool CompileShader(unsigned int shader, const std::string& sourcecode);

std::string ShaderInfoLog(unsigned int shader);

bool LinkProgram(unsigned int program, const std::list<unsigned int>& shaderlist);

std::string ProgramInfoLog(unsigned int program);

Shader.cpp

#include "Shader.h"

#include <iostream>

#include <fstream>

#include <GL/glew.h>

std::string LoadTextFile(const std::string & filepath)

{

std::string result(""), line;

std::fstream f(filepath, std::ios::in);

while (f.good())

{

std::getline(f, line);

result += line + '\n';

}

return result;

}

std::string ShaderTypeName(unsigned int shader)

{

if (glIsShader(shader))

{

int type;

glGetShaderiv(shader, GL_SHADER_TYPE, &type);

if (type == GL_VERTEX_SHADER)

return "Vertex Shader";

if (type == GL_TESS_CONTROL_SHADER)

return "Tessellation Control Shader";

if (type == GL_TESS_EVALUATION_SHADER)

return "Tessellation Evaluation Shader";

if (type == GL_GEOMETRY_SHADER)

return "Geometry Shader";

if (type == GL_FRAGMENT_SHADER)

return "Fragment Shader";

if (type == GL_COMPUTE_SHADER)

return "Compute Shader";

}

return "invalid shader";

}

bool CompileShader(unsigned int shader, const std::string& sourcecode)

{

if (!glIsShader(shader))

{

std::cout << "ERROR: shader compilation failed, no valid shader specified" << std::endl;

return false;

}

if (sourcecode.empty())

{

std::cout << "ERROR: shader compilation failed, no source code specified (" << ShaderTypeName(shader) << ")" << std::endl;

return false;

}

// array of source code components

const char* sourcearray[] = { sourcecode.c_str() };

// set source code

glShaderSource(shader, 1, sourcearray, NULL);

// compile shaders

glCompileShader(shader);

// check compile status

int status;

glGetShaderiv(shader, GL_COMPILE_STATUS, &status);

// successfully compiled shader

if (status == GL_TRUE)

return true;

// show compile errors

std::cout << "ERROR: shader compilation failed (" << ShaderTypeName(shader) << ")" << std::endl << ShaderInfoLog(shader) << std::endl;

return false;

}

std::string ShaderInfoLog(unsigned int shader)

{

if (glIsShader(shader))

{

int logsize;

char infolog[1024] = { 0 };

glGetShaderInfoLog(shader, 1024, &logsize, infolog);

return std::string(infolog);

}

return "invalid shader";

}

bool LinkProgram(unsigned int program, const std::list<unsigned int>& shaderlist)

{

if (!glIsProgram(program))

{

std::cout << "ERROR: shader linking failed, no valid program specified" << std::endl;

return false;

}

// attach all shaders to the program

for (auto& shader : shaderlist)

{

if (glIsShader(shader))

glAttachShader(program, shader);

}

// link program

glLinkProgram(program);

// detach all shaders again

for (auto& shader : shaderlist)

{

if (glIsShader(shader))

glDetachShader(program, shader);

}

int status;

glGetProgramiv(program, GL_LINK_STATUS, &status);

// successfully linked program

if (status == GL_TRUE)

return true;

// show link errors

std::cout << "ERROR: shader linking failed" << std::endl << ProgramInfoLog(program) << std::endl;

return false;

}

std::string ProgramInfoLog(unsigned int program)

{

if (glIsProgram(program))

{

int logsize;

char infolog[1024] = { 0 };

glGetProgramInfoLog(program, 1024, &logsize, infolog);

return std::string(infolog);

}

return "invalid program";

}

Shader Source Code:

Vertex Shader: "model.vertexshader.txt"

#version 450 core

in layout (location = 0) vec3 in_position;

in layout (location = 1) vec3 in_normal;

uniform mat4 Model = mat4(1);

uniform mat4 View = mat4(1);

uniform mat4 Projection = mat4(1);

void main ()

{

mat4 MVP = Projection * View * Model;

gl_Position = MVP * vec4(in_position, 1);

}

Fragment Shader: "model.fragmentshader.txt"

#version 450 core

out layout (location = 0) vec4 out_color;

void main ()

{

out_color = vec4(0, 1, 0, 1);

}