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);
}