506 lines
17 KiB
C++
506 lines
17 KiB
C++
// GLAD
|
|
#include "glad/glad.h"
|
|
|
|
// GLM
|
|
#define GLM_ENABLE_EXPERIMENTAL
|
|
#include "glm/ext/matrix_transform.hpp"
|
|
#include "glm/ext/vector_float3.hpp"
|
|
#include "glm/gtc/type_ptr.hpp"
|
|
#include "glm/trigonometric.hpp"
|
|
#include "glm/gtx/rotate_vector.hpp"
|
|
#include "glm/gtx/string_cast.hpp"
|
|
|
|
// SDL
|
|
#include <SDL2/SDL.h>
|
|
#include <SDL2/SDL_stdinc.h>
|
|
#include <SDL2/SDL_error.h>
|
|
#include <SDL2/SDL_video.h>
|
|
#include <SDL2/SDL_events.h>
|
|
#include <SDL2/SDL_mouse.h>
|
|
#include <SDL2/SDL_keycode.h>
|
|
|
|
// STDLIB
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cmath>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#define WINDOW_WIDTH 1280
|
|
#define WINDOW_HEIGHT 720
|
|
#define WINDOW_HALF_WIDTH 640
|
|
#define WINDOW_HALF_HEIGHT 360
|
|
|
|
enum exit_codes : int {
|
|
EXIT_CODE_SUCCESS,
|
|
EXIT_CODE_SDL_INIT_FAILED,
|
|
EXIT_CODE_WINDOW_CREATION_FAILED,
|
|
EXIT_CODE_OPENGL_CONTEXT_FAILED,
|
|
EXIT_CODE_GLAD_LOADER_FAILED,
|
|
};
|
|
|
|
struct Camera {
|
|
// Point in world coordinates where the camera is located
|
|
glm::vec3 position;
|
|
|
|
// view_direction is an actual direction vector. When used with glm::lookAt, it gets added to the
|
|
// position to calculate the target point the camera is looking at
|
|
glm::vec3 view_direction;
|
|
|
|
// The up vector in world coordinates
|
|
glm::vec3 up;
|
|
|
|
// pitch, yaw
|
|
glm::vec2 rotation;
|
|
|
|
// View matrix to be sent as uniform to the vertex shader
|
|
glm::mat4 view_mat;
|
|
GLint u_view_idx;
|
|
|
|
// Projection matrix to be sent as uniform to the vertex shader
|
|
glm::mat4 projection_mat;
|
|
GLint u_projection_idx;
|
|
};
|
|
|
|
struct Transform {
|
|
glm::mat4 translation;
|
|
glm::mat4 rotation;
|
|
glm::mat4 scale;
|
|
};
|
|
|
|
struct Mesh {
|
|
// VAO is an object that stores the state needed to supply the GPU with the vertex data.
|
|
// Think of it as a specification or a C struct that defines the types of data stored for the
|
|
// vertex array including the attributes for each vertex as well the indices buffer if it exists.
|
|
GLuint vao;
|
|
|
|
// The buffer that contains all the vertex data
|
|
// e.g. assume 3 vertices with position, colour and uv, the buffer would look like this
|
|
// -----------------------------------------------------------------------------------
|
|
// | position1 | colour1 | uv1 | position2 | colour2 | uv2 | position3 | colour3 | uv3 |
|
|
// -----------------------------------------------------------------------------------
|
|
GLuint vbo;
|
|
|
|
// Holds the indices of the vertices that draw each triangle. Each triangle constitutes what
|
|
// OpenGL refers to as element, hence the name Element Buffer Object
|
|
GLuint ebo;
|
|
|
|
GLuint shader_program;
|
|
};
|
|
|
|
struct Model {
|
|
Transform transform;
|
|
Mesh mesh;
|
|
|
|
// Model matrix to be sent as uniform to the vertex shader
|
|
glm::mat4 model_mat;
|
|
GLint u_model_idx;
|
|
};
|
|
|
|
struct App {
|
|
SDL_Window *window;
|
|
SDL_GLContext context;
|
|
SDL_Event event;
|
|
Model model;
|
|
Camera camera;
|
|
float speed;
|
|
glm::vec2 prev_mouse;
|
|
bool running;
|
|
};
|
|
|
|
int init (App &app);
|
|
void create_vertex_spec (Model &model);
|
|
void create_graphics_pipeline (Model &model, Camera &camera);
|
|
void run_main_loop (App &app);
|
|
void cleanup (App &app);
|
|
GLuint create_shader_program (const std::string &vertex_shader_source, const std::string &fragment_shader_source);
|
|
GLuint compile_shader (GLuint type, const std::string &source);
|
|
std::string load_shader (const std::string &filepath);
|
|
void handle_object_movement (App &app);
|
|
void handle_camera_movement (App &app);
|
|
glm::vec3 rotation_to_view_direction(const Camera &camera);
|
|
|
|
int main() {
|
|
App app = {};
|
|
int result = init(app);
|
|
if (result != EXIT_CODE_SUCCESS) {
|
|
return result;
|
|
}
|
|
|
|
// Setup the geometry
|
|
create_vertex_spec(app.model);
|
|
|
|
// Setup graphics pipeline. At the very minimum creating vertex and fragment shaders
|
|
create_graphics_pipeline(app.model, app.camera);
|
|
|
|
run_main_loop(app);
|
|
|
|
cleanup(app);
|
|
|
|
return EXIT_CODE_SUCCESS;
|
|
}
|
|
|
|
int init(App &app) {
|
|
if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {
|
|
fprintf(stderr, "Failed to initialise SDL: %s", SDL_GetError());
|
|
return EXIT_CODE_SDL_INIT_FAILED;
|
|
}
|
|
|
|
uint32_t flags = SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL | SDL_WINDOW_ALLOW_HIGHDPI;
|
|
app.window = SDL_CreateWindow("Window", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
|
WINDOW_WIDTH, WINDOW_HEIGHT, flags);
|
|
if (!app.window) {
|
|
fprintf(stderr, "Failed to create window: %s", SDL_GetError());
|
|
return EXIT_CODE_WINDOW_CREATION_FAILED;
|
|
}
|
|
|
|
SDL_WarpMouseInWindow(app.window, WINDOW_HALF_WIDTH, WINDOW_HALF_HEIGHT);
|
|
SDL_SetRelativeMouseMode(SDL_TRUE);
|
|
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 6);
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
|
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
|
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
|
|
|
app.context = SDL_GL_CreateContext(app.window);
|
|
if (!app.context) {
|
|
fprintf(stderr, "Failed to create OpenGL context: %s", SDL_GetError());
|
|
return EXIT_CODE_OPENGL_CONTEXT_FAILED;
|
|
}
|
|
|
|
// Init GLAD
|
|
if (!gladLoadGLLoader(SDL_GL_GetProcAddress)) {
|
|
fprintf(stderr, "Failed to initialise glad");
|
|
return EXIT_CODE_GLAD_LOADER_FAILED;
|
|
}
|
|
|
|
printf("%s\n%s\n%s\n%s\n",
|
|
glGetString(GL_VENDOR),
|
|
glGetString(GL_RENDERER),
|
|
glGetString(GL_VERSION),
|
|
glGetString(GL_SHADING_LANGUAGE_VERSION)
|
|
);
|
|
|
|
app.speed = 0.04f;
|
|
app.model.transform.translation = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, 1.5f));
|
|
app.model.transform.rotation = glm::mat4(1.0f);
|
|
app.model.transform.scale = glm::mat4(1.0f);
|
|
app.camera.rotation = glm::vec2(0.0f, -90.0f);
|
|
app.camera.position = glm::vec3(0.0f, 0.0f, 3.0f);
|
|
app.camera.view_direction = rotation_to_view_direction(app.camera);
|
|
app.camera.up = glm::vec3(0.0f, 1.0f, 0.0f);
|
|
app.model.model_mat = app.model.transform.translation *
|
|
app.model.transform.rotation * app.model.transform.scale;
|
|
app.camera.view_mat = glm::lookAt(app.camera.position, app.camera.position + app.camera.view_direction, app.camera.up);
|
|
app.camera.projection_mat = glm::perspective(glm::radians(60.0f),
|
|
(float)WINDOW_WIDTH / (float)WINDOW_HEIGHT,
|
|
0.1f,
|
|
20.0f);
|
|
app.prev_mouse = glm::vec2(WINDOW_HALF_WIDTH, WINDOW_HALF_HEIGHT);
|
|
|
|
return EXIT_CODE_SUCCESS;
|
|
}
|
|
|
|
void create_vertex_spec(Model &model) {
|
|
const std::vector<GLfloat> vertices = {
|
|
// vert0
|
|
-0.5f, -0.5f, 0.0f, // position
|
|
1.0f, 0.0f, 0.0f, // colour
|
|
// vert1
|
|
0.5f, -0.5f, 0.0f, // position
|
|
0.0f, 1.0f, 0.0f, // colour
|
|
// vert2
|
|
-0.5f, 0.5f, 0.0f, // position
|
|
0.0f, 0.0f, 1.0f, // colour
|
|
// vert3
|
|
0.5f, 0.5f, 0.0f, // position
|
|
1.0f, 1.0f, 0.0f, // colour
|
|
};
|
|
|
|
const std::vector<GLuint> indices = {
|
|
// first triangle
|
|
0, 1, 2,
|
|
// second triangle
|
|
2, 1, 3,
|
|
};
|
|
|
|
// Create and activate the VAO
|
|
glGenVertexArrays(1, &model.mesh.vao);
|
|
glBindVertexArray(model.mesh.vao);
|
|
|
|
// Create and set up the VBO
|
|
glGenBuffers(1, &model.mesh.vbo);
|
|
glBindBuffer(GL_ARRAY_BUFFER, model.mesh.vbo);
|
|
glBufferData(GL_ARRAY_BUFFER,
|
|
vertices.size() * sizeof(GLfloat),
|
|
(void *)vertices.data(),
|
|
GL_STATIC_DRAW
|
|
);
|
|
|
|
// Create and set up the EBO
|
|
glGenBuffers(1, &model.mesh.ebo);
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, model.mesh.ebo);
|
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
|
|
indices.size() * sizeof(GLuint),
|
|
(void *)indices.data(),
|
|
GL_STATIC_DRAW
|
|
);
|
|
|
|
GLsizei vertex_stride = 6 * sizeof(GLfloat);
|
|
GLvoid *position_start_offset = (void *)0;
|
|
GLvoid *colour_start_offset = (void *)(3 * sizeof(GLfloat));
|
|
|
|
// Defines the vertex attributes and their indices. This defines the attribute for the currently
|
|
// bound VAO
|
|
glEnableVertexAttribArray(0);
|
|
glEnableVertexAttribArray(1);
|
|
|
|
// Defines the number of components for the attribute, its type, the stride between the component
|
|
// for each vertex and the offset of the first instance of the component in the buffer
|
|
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, vertex_stride, position_start_offset);
|
|
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, vertex_stride, colour_start_offset);
|
|
|
|
// Cleanup.
|
|
// The order matters. Unbind VAO first then disable the attributes. If you do it the
|
|
// other way around, the VAO will store that the attributes are disabled and nothing will be
|
|
// drawn during the rendering stage
|
|
glBindVertexArray(0);
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
glDisableVertexAttribArray(0);
|
|
glDisableVertexAttribArray(1);
|
|
}
|
|
|
|
void create_graphics_pipeline(Model &model, Camera &camera) {
|
|
const std::string vs_source = load_shader("shaders/vert.glsl");
|
|
const std::string fs_source = load_shader("shaders/frag.glsl");
|
|
|
|
model.mesh.shader_program = create_shader_program(vs_source, fs_source);
|
|
|
|
const char *u_model = "u_model";
|
|
model.u_model_idx = glGetUniformLocation(model.mesh.shader_program, u_model);
|
|
if (model.u_model_idx < 0) {
|
|
printf("Failed to find uniform %s\n", u_model);
|
|
}
|
|
|
|
// TODO (Abdelrahman): Finding the camera matrices uniforms shouldn't be handled here
|
|
const char *u_view = "u_view";
|
|
camera.u_view_idx = glGetUniformLocation(model.mesh.shader_program, u_view);
|
|
if (camera.u_view_idx < 0) {
|
|
printf("Failed to find uniform %s\n", u_view);
|
|
}
|
|
|
|
const char *u_projection = "u_projection";
|
|
camera.u_projection_idx = glGetUniformLocation(model.mesh.shader_program, u_projection);
|
|
if (camera.u_projection_idx < 0) {
|
|
printf("Failed to find uniform %s\n", u_projection);
|
|
}
|
|
}
|
|
|
|
void run_main_loop(App &app) {
|
|
app.running = true;
|
|
|
|
while (app.running) {
|
|
while (SDL_PollEvent(&app.event)) {
|
|
switch (app.event.type) {
|
|
case SDL_QUIT:
|
|
app.running = false;
|
|
break;
|
|
case SDL_KEYDOWN:
|
|
if (app.event.key.keysym.sym == SDLK_ESCAPE) {
|
|
app.running = false;
|
|
} else {
|
|
handle_camera_movement(app);
|
|
}
|
|
|
|
break;
|
|
case SDL_MOUSEMOTION:
|
|
handle_camera_movement(app);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Pre draw setup
|
|
glDisable(GL_DEPTH_TEST);
|
|
glDisable(GL_CULL_FACE);
|
|
|
|
glViewport (0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
|
|
glClearColor(0.36f, 0.34f, 0.42f, 1.0f);
|
|
glClear (GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
|
|
|
|
glUseProgram(app.model.mesh.shader_program);
|
|
if (app.model.u_model_idx >= 0) {
|
|
glUniformMatrix4fv(app.model.u_model_idx, 1, GL_FALSE, glm::value_ptr(app.model.model_mat));
|
|
}
|
|
if (app.camera.u_view_idx >= 0) {
|
|
glUniformMatrix4fv(app.camera.u_view_idx, 1, GL_FALSE, glm::value_ptr(app.camera.view_mat));
|
|
}
|
|
if (app.camera.u_projection_idx >= 0) {
|
|
glUniformMatrix4fv(app.camera.u_projection_idx, 1, GL_FALSE, glm::value_ptr(app.camera.projection_mat));
|
|
}
|
|
// End pre draw setup
|
|
|
|
// Draw call
|
|
glBindVertexArray(app.model.mesh.vao);
|
|
glDrawElements (GL_TRIANGLES, 6, GL_UNSIGNED_INT, (void *)0);
|
|
// End draw call
|
|
|
|
// Not necessary if we only have one shader program
|
|
glUseProgram(0);
|
|
|
|
SDL_GL_SwapWindow(app.window);
|
|
}
|
|
}
|
|
|
|
void cleanup(App &app) {
|
|
SDL_GL_DeleteContext(app.context);
|
|
SDL_DestroyWindow(app.window);
|
|
SDL_Quit();
|
|
}
|
|
|
|
GLuint create_shader_program(const std::string &vertex_shader_source, const std::string &fragment_shader_source) {
|
|
GLuint shader_program = glCreateProgram();
|
|
GLuint vertex_shader = compile_shader(GL_VERTEX_SHADER, vertex_shader_source);
|
|
GLuint fragment_shader = compile_shader(GL_FRAGMENT_SHADER, fragment_shader_source);
|
|
|
|
glAttachShader(shader_program, vertex_shader);
|
|
glAttachShader(shader_program, fragment_shader);
|
|
glLinkProgram (shader_program);
|
|
|
|
GLint link_status;
|
|
glGetProgramiv(shader_program, GL_LINK_STATUS, &link_status);
|
|
if (link_status != GL_TRUE) {
|
|
GLsizei log_length = 0;
|
|
GLchar message[1024];
|
|
glGetProgramInfoLog(shader_program, 1024, &log_length, message);
|
|
fprintf(stderr, "Failed to link program: %s\n", message);
|
|
}
|
|
|
|
// Validate the shader_program
|
|
glValidateProgram(shader_program);
|
|
GLint validation_status;
|
|
glGetProgramiv(shader_program, GL_VALIDATE_STATUS, &validation_status);
|
|
if (validation_status != GL_TRUE) {
|
|
GLsizei log_length = 0;
|
|
GLchar message[1024];
|
|
glGetProgramInfoLog(shader_program, 1024, &log_length, message);
|
|
fprintf(stderr, "Failed to validate program: %s\n", message);
|
|
}
|
|
|
|
return shader_program;
|
|
}
|
|
|
|
GLuint compile_shader(GLuint type, const std::string &source) {
|
|
GLuint shader = glCreateShader(type);
|
|
const char *shader_source = source.c_str();
|
|
|
|
glShaderSource (shader, 1, &shader_source, NULL);
|
|
glCompileShader(shader);
|
|
|
|
GLint compile_status;
|
|
glGetShaderiv(shader, GL_COMPILE_STATUS, &compile_status);
|
|
if (compile_status != GL_TRUE) {
|
|
GLsizei log_length = 0;
|
|
GLchar message[1024];
|
|
glGetShaderInfoLog(shader, 1024, &log_length, message);
|
|
fprintf(stderr, "Failed to compile shader: %s\n", message);
|
|
}
|
|
|
|
return shader;
|
|
}
|
|
|
|
std::string load_shader(const std::string &filepath) {
|
|
FILE *fp = fopen(filepath.c_str(), "r");
|
|
if (!fp) {
|
|
return "";
|
|
}
|
|
|
|
std::string output = {};
|
|
|
|
char buf[1024] = {0};
|
|
while (fgets(buf, sizeof(buf), fp)) {
|
|
output += buf;
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
// Example of moving an object
|
|
void handle_object_movement(App &app) {
|
|
switch (app.event.key.keysym.sym) {
|
|
case SDLK_w:
|
|
app.model.transform.translation = glm::translate(app.model.transform.translation,
|
|
glm::vec3(0.0f, 0.0f, app.speed));
|
|
break;
|
|
case SDLK_s:
|
|
app.model.transform.translation = glm::translate(app.model.transform.translation,
|
|
glm::vec3(0.0f, 0.0f, -app.speed));
|
|
break;
|
|
case SDLK_d:
|
|
app.model.transform.translation = glm::translate(app.model.transform.translation,
|
|
glm::vec3(app.speed, 0.0f, 0.0f));
|
|
break;
|
|
case SDLK_a:
|
|
app.model.transform.translation = glm::translate(app.model.transform.translation,
|
|
glm::vec3(-app.speed, 0.0f, 0.0f));
|
|
break;
|
|
case SDLK_RIGHT:
|
|
app.model.transform.rotation = glm::rotate(app.model.transform.rotation,
|
|
glm::radians(10.0f), glm::vec3(0.0f, 1.0f, 0.0f));
|
|
break;
|
|
case SDLK_LEFT:
|
|
app.model.transform.rotation = glm::rotate(app.model.transform.rotation,
|
|
glm::radians(-10.0f), glm::vec3(0.0f, 1.0f, 0.0f));
|
|
break;
|
|
case SDLK_r:
|
|
app.model.transform.scale = glm::scale(app.model.transform.scale, glm::vec3(1.2f));
|
|
break;
|
|
case SDLK_e:
|
|
app.model.transform.scale = glm::scale(app.model.transform.scale, glm::vec3(0.8f));
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
app.model.model_mat = app.model.transform.translation * app.model.transform.rotation * app.model.transform.scale;
|
|
}
|
|
|
|
void handle_camera_movement(App &app) {
|
|
if (app.event.type == SDL_MOUSEMOTION) {
|
|
glm::vec2 offset = glm::vec2(-app.event.motion.yrel, app.event.motion.xrel) * 0.1f;
|
|
app.prev_mouse += offset;
|
|
app.camera.rotation += offset;
|
|
app.camera.rotation.x = glm::clamp(app.camera.rotation.x, -89.0f, 89.0f);
|
|
app.camera.view_direction = rotation_to_view_direction(app.camera);
|
|
} else {
|
|
switch (app.event.key.keysym.sym) {
|
|
case SDLK_w:
|
|
app.camera.position += app.camera.view_direction * app.speed;
|
|
break;
|
|
case SDLK_s:
|
|
app.camera.position -= app.camera.view_direction * app.speed;
|
|
break;
|
|
case SDLK_d:
|
|
app.camera.position += glm::normalize(glm::cross(app.camera.view_direction, app.camera.up)) * app.speed;
|
|
break;
|
|
case SDLK_a:
|
|
app.camera.position -= glm::normalize(glm::cross(app.camera.view_direction, app.camera.up)) * app.speed;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
app.camera.view_mat = glm::lookAt(app.camera.position, app.camera.position + app.camera.view_direction, app.camera.up);
|
|
}
|
|
|
|
glm::vec3 rotation_to_view_direction(const Camera &camera) {
|
|
glm::vec3 direction;
|
|
direction.x = cos(glm::radians(camera.rotation.y)) * cos(glm::radians(camera.rotation.x));
|
|
direction.y = sin(glm::radians(camera.rotation.x));
|
|
direction.z = sin(glm::radians(camera.rotation.y)) * cos(glm::radians(camera.rotation.x));
|
|
|
|
return glm::normalize(direction);
|
|
}
|