Compare commits

...

4 commits

7 changed files with 308 additions and 184 deletions

View file

@ -2,5 +2,6 @@ project('opengl-learning', 'cpp', 'c', default_options: ['cpp_std=c++20'])
glfw = dependency('glfw3')
libepoxy = dependency('epoxy')
glm = dependency('glm')
subdir('src')

View file

@ -1,31 +1,20 @@
#include <epoxy/gl.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <iostream>
#include <format>
#include <memory>
#include "shader.hh"
#include "utilities.hh"
#include "image.hh"
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void process_input(GLFWwindow* window);
void key_pressed(GLFWwindow *window, int key, int scancode, int action, int mods);
int check_shader_compilation_status(unsigned int shaderId);
bool wireframe;
bool rectangleToggled;
struct GlRenderObjects {
unsigned int vertexBufferObject;
unsigned int vertexArrayObject;
unsigned int elementBufferObject;
GlRenderObjects(unsigned int vbo, unsigned int vao, unsigned int ebo) {
vertexBufferObject = vbo;
vertexArrayObject = vao;
elementBufferObject = ebo;
}
~GlRenderObjects() {
glDeleteBuffers(1, &vertexBufferObject);
glDeleteVertexArrays(1, &vertexArrayObject);
@ -33,7 +22,291 @@ struct GlRenderObjects {
}
};
GlRenderObjects* triangle() {
enum state {
CUBE,
RECTANGLE,
TRIANGLE
};
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void process_input(GLFWwindow* window);
void key_pressed(GLFWwindow *window, int key, int scancode, int action, int mods);
int check_shader_compilation_status(unsigned int shaderId);
void createTexture(uint* textureInt, const char* path, GLenum imageType, GLenum minFilter, GLenum magFilter);
GlRenderObjects cube();
GlRenderObjects triangle();
GlRenderObjects rectangle();
bool wireframe;
bool rectangleToggled;
state currentState = CUBE;
int main() {
glfwInit();
// These brackets are a RAII (Resource Acquisition Is Initialization) pattern,
// which means these brackets are its own "scope" which means stack allocated memory
// and heap allocated memory wrapped in a smart pointer like unique_ptr will be cleaned
// up after you go out of scope.
{
// Set OpenGL version to 3.3.
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
// Set OpenGL to use the Core profile.
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// Create a OpenGL window with the 800x600 resolution and with the title "OpenGL Sussy Window".
GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGL Sussy Window", nullptr, nullptr);
// Fail if the window is failed to create.
if (window == nullptr) {
std::cout << "Failed to create GLFW window." << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
// Tell OpenGL the size of the rendering window. We're telling it right now that its 800x600.
glViewport(0, 0, 800, 600);
// Register a callback for window size changes.
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
std::string currentPath = utilities::getCurrentPath();
// Create a shader class.
Shader shader(std::format("{}/vertex.glsl", currentPath).c_str(), std::format("{}/fragment.glsl", currentPath).c_str());
GlRenderObjects cubeRenderObjs = cube();
GlRenderObjects triangleRenderObjs = triangle();
GlRenderObjects rectangleRenderObjs = rectangle();
unsigned int texture1, texture2;
createTexture(&texture1, "container.jpg", GL_RGB, GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR);
createTexture(&texture2, "awesomeface.png", GL_RGBA, GL_LINEAR, GL_LINEAR);
// activate the shader
shader.activate();
// tell the shader what texture unit the textures are on
shader.setInt("texture1", 0);
shader.setInt("texture2", 1);
// glm::mat4 trans = glm::mat4(1.0f);
// trans = glm::rotate(trans, glm::radians(90.0f), glm::vec3(0.0, 0.0, 1.0));
// trans = glm::scale(trans, glm::vec3(0.5, 0.5, 0.5));
// Create a render loop, which keeps the program open until glfw tells the loop that the window should close.
while (!glfwWindowShouldClose(window)) {
// Process input.
process_input(window);
// Set a callback to trigger whenever a key is pressed.
glfwSetKeyCallback(window, key_pressed);
// Set the GL state to use this color when clearing the screen.
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
// Clear the screen and use the color from the state.
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// assign texture1 to the GL_TEXTURE0 texture unit.
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
// assign texture1 to the GL_TEXTURE1 texture unit.
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);
shader.activate();
// the model matrix. this rotates the plane on the x axis by -55 degrees.
glm::mat4 model(1.0f);
model = glm::rotate(model, (float)glfwGetTime() * glm::radians(50.0f), glm::vec3(0.5f, 1.0f, 0.0f));
// the view matrix. this moves the "camera" back by 3 units.
glm::mat4 view(1.0f);
view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));
// the projection matrix. the fov is 45 degrees. the aspect is 800/600 (4:3). the near plane is 0.1 units. the far plane is 100 units.
glm::mat4 projection;
projection = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 0.1f, 100.0f);
// create the identity matrix
glm::mat4 trans(1);
// translate it by this vector3
trans = glm::translate(trans, glm::vec3(0.5f, -0.5f, 0.0f));
// rotate by the z axis with the angle being controlled by time
trans = glm::rotate(trans, (float)glfwGetTime(), glm::vec3(0.0f, 0.0f, 1.0f));
shader.setMat4("model", model);
shader.setMat4("view", view);
shader.setMat4("projection", projection);
switch (currentState) {
case CUBE:
glEnable(GL_DEPTH_TEST);
glBindVertexArray(cubeRenderObjs.vertexArrayObject);
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);
glDisable(GL_DEPTH_TEST);
break;
case RECTANGLE:
glBindVertexArray(rectangleRenderObjs.vertexArrayObject);
// Draw 6 elements (indices) that are unsigned ints.
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
break;
case TRIANGLE:
// Bind the state to use the vertexArrayObject object so OpenGL knows what to do with the verticies.
glBindVertexArray(triangleRenderObjs.vertexArrayObject);
// Draw the verticies with the first index being 0 and the size of the vertex being 3 floats.
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
break;
}
// This swaps completely drawn frames from the "second buffer" to the front one which is displayed on the screen.
glfwSwapBuffers(window);
// Check if any events are triggered.
glfwPollEvents();
}
}
glfwTerminate();
return 0;
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
// Tell OpenGL the size of the rendering window.
glViewport(0, 0, width, height);
}
void process_input(GLFWwindow* window) {
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
glfwSetWindowShouldClose(window, true);
}
}
void key_pressed(GLFWwindow *window, int key, int scancode, int action, int mods) {
// If the W key is pressed, toggle wireframe mode.
if (key == GLFW_KEY_W && action == GLFW_PRESS) {
if (!wireframe) {
// Render polygons in wireframe mode.
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
wireframe = true;
} else {
// Render polygons in fill mode.
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
wireframe = false;
}
}
if (key == GLFW_KEY_R && action == GLFW_PRESS) {
currentState = RECTANGLE;
} else if (key == GLFW_KEY_T && action == GLFW_PRESS) {
currentState = TRIANGLE;
} else if (key == GLFW_KEY_C && action == GLFW_PRESS) {
currentState = CUBE;
}
}
int check_shader_compilation_status(unsigned int shaderId) {
int success;
glGetShaderiv(shaderId, GL_COMPILE_STATUS, &success);
return success;
}
void createTexture(uint* textureInt, const char* path, GLenum imageType, GLenum minFilter, GLenum magFilter) {
// Create a texture ID and assign it to the textureInt pointer.
glGenTextures(1, textureInt);
// Bind that texture to the GL_TEXTURE_2D state.
glBindTexture(GL_TEXTURE_2D, *textureInt);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
Image texture(utilities::getCurrentPath(path));
if (texture.data) {
// Read the texture data and assign it to the GL texture.
glTexImage2D(GL_TEXTURE_2D, 0, imageType, texture.width, texture.height, 0, imageType, GL_UNSIGNED_BYTE, texture.data);
// Generate a mipmap of the texture data.
glGenerateMipmap(GL_TEXTURE_2D);
} else {
std::cout << "Failed to load texture." << std::endl;
}
}
GlRenderObjects cube() {
float vertices[] = {
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f
};
unsigned int vertexArrayObject, vertexBufferObject;
glGenVertexArrays(1, &vertexArrayObject);
glGenBuffers(1, &vertexBufferObject);
glBindVertexArray(vertexArrayObject);
glBindBuffer(GL_ARRAY_BUFFER, vertexBufferObject);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
return {vertexBufferObject, vertexArrayObject, 0};
}
GlRenderObjects triangle() {
// An array of verticies containing data for a triangle.
float vertices[] = {
// positions // colors // texture coords
@ -75,10 +348,10 @@ GlRenderObjects* triangle() {
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
return new GlRenderObjects(vertexBufferObject, vertexArrayObject, 0);
return {vertexBufferObject, vertexArrayObject, 0};
}
GlRenderObjects* rectangle() {
GlRenderObjects rectangle() {
float vertices[] = {
// positions // colors // texture coords
0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
@ -129,168 +402,5 @@ GlRenderObjects* rectangle() {
glBindVertexArray(0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
return new GlRenderObjects(vertexBufferObject, vertexArrayObject, elementBufferObject);
}
int main() {
glfwInit();
{
// Set OpenGL version to 3.3.
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
// Set OpenGL to use the Core profile.
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// Create a OpenGL window with the 800x600 resolution and with the title "OpenGL Sussy Window".
GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGL Sussy Window", nullptr, nullptr);
// Fail if the window is failed to create.
if (window == nullptr) {
std::cout << "Failed to create GLFW window." << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
// Tell OpenGL the size of the rendering window. We're telling it right now that its 800x600.
glViewport(0, 0, 800, 600);
// Register a callback for window size changes.
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
std::string currentPath = utilities::getCurrentPath();
// Create a shader class.
Shader shader(std::format("{}/vertex.glsl", currentPath).c_str(), std::format("{}/fragment.glsl", currentPath).c_str());
std::unique_ptr<GlRenderObjects> triangleRenderObjs(triangle());
std::unique_ptr<GlRenderObjects> rectangleRenderObjs(rectangle());
unsigned int texture1, texture2;
{
glGenTextures(1, &texture1);
glBindTexture(GL_TEXTURE_2D, texture1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
Image texture(utilities::getCurrentPath("container.jpg"));
if (texture.data) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texture.width, texture.height, 0, GL_RGB, GL_UNSIGNED_BYTE, texture.data);
glGenerateMipmap(GL_TEXTURE_2D);
} else {
std::cout << "Failed to load texture." << std::endl;
}
}
{
glGenTextures(1, &texture2);
glBindTexture(GL_TEXTURE_2D, texture2);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
Image texture(utilities::getCurrentPath("awesomeface.png"));
if (texture.data) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture.width, texture.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, texture.data);
glGenerateMipmap(GL_TEXTURE_2D);
} else {
std::cout << "Failed to load texture." << std::endl;
}
}
// activate the shader
shader.activate();
// tell the shader what texture unit the textures are on
shader.setInt("texture1", 0);
shader.setInt("texture2", 1);
// Create a render loop, which keeps the program open until glfw tells the loop that the window should close.
while (!glfwWindowShouldClose(window)) {
// Process input.
process_input(window);
// Set a callback to trigger whenever a key is pressed.
glfwSetKeyCallback(window, key_pressed);
// Set the GL state to use this color when clearing the screen.
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
// Clear the screen and use the color from the state.
glClear(GL_COLOR_BUFFER_BIT);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);
shader.activate();
if (!rectangleToggled) {
// Bind the state to use the vertexArrayObject object so OpenGL knows what to do with the verticies.
glBindVertexArray(triangleRenderObjs->vertexArrayObject);
// Draw the verticies with the first index being 0 and the size of the vertex being 3 floats.
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
} else {
glBindVertexArray(rectangleRenderObjs->vertexArrayObject);
// Draw 6 elements (indices) that are unsigned ints.
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
}
// This swaps completely drawn frames from the "second buffer" to the front one which is displayed on the screen.
glfwSwapBuffers(window);
// Check if any events are triggered.
glfwPollEvents();
}
}
glfwTerminate();
return 0;
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
// Tell OpenGL the size of the rendering window.
glViewport(0, 0, width, height);
}
void process_input(GLFWwindow* window) {
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
glfwSetWindowShouldClose(window, true);
}
}
int check_shader_compilation_status(unsigned int shaderId) {
int success;
glGetShaderiv(shaderId, GL_COMPILE_STATUS, &success);
return success;
}
void key_pressed(GLFWwindow *window, int key, int scancode, int action, int mods) {
// If the W key is pressed, toggle wireframe mode.
if (key == GLFW_KEY_W && action == GLFW_PRESS) {
if (!wireframe) {
// Render polygons in wireframe mode.
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
wireframe = true;
} else {
// Render polygons in fill mode.
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
wireframe = false;
}
}
// Flip a boolean indicating the rectangle should be rendered.
if (key == GLFW_KEY_R && action == GLFW_PRESS) {
rectangleToggled = !rectangleToggled;
}
return {vertexBufferObject, vertexArrayObject, elementBufferObject};
}

View file

@ -12,5 +12,5 @@ vertex = custom_target('vertex', output: 'vertex.glsl', input: 'vertex.glsl', co
container = custom_target('container', output: 'container.jpg', input: 'container.jpg', command: [copy, '@INPUT@', '@OUTPUT@'])
awesomeface = custom_target('awesomeface', output: 'awesomeface.png', input: 'awesomeface.png', command: [copy, '@INPUT@', '@OUTPUT@'])
executable('main', [sources, fragment, vertex, container, awesomeface], dependencies: [glfw, libepoxy])
executable('main', [sources, fragment, vertex, container, awesomeface], dependencies: [glfw, libepoxy, glm])
executable('fixedfunction', ['fixedfunction.cc'], dependencies: [glfw, libepoxy])

View file

@ -103,3 +103,7 @@ void Shader::setInt(const char* name, int value) {
void Shader::setFloat(const char* name, float value) const {
glUniform1f(glGetUniformLocation(id, name), value);
}
void Shader::setMat4(const char* name, glm::mat4 matrix) {
glUniformMatrix4fv(glGetUniformLocation(id, name), 1, GL_FALSE, glm::value_ptr(matrix));
}

View file

@ -2,6 +2,9 @@
#define SHADER_HH
#include <string>
#include <epoxy/gl.h>
#include <glm/glm.hpp>
#include <glm/gtc/type_ptr.hpp>
class Shader {
public:
// The Shader ID.
@ -17,6 +20,7 @@ class Shader {
void setBool(const char* name, bool value) const;
void setInt(const char* name, int value) ;
void setFloat(const char* name, float value) const;
void setMat4(const char* name, glm::mat4 matrix);
private:
uint compileShader(std::string code, GLenum type, std::string shaderTypeName);
uint compileVertexShader(std::string code);

View file

@ -3,7 +3,7 @@
#include <string>
#include <sstream>
#include <unistd.h>
#include <linux/limits.h>
#include <limits.h>
#include <format>
std::string utilities::getCurrentPath() {

View file

@ -7,8 +7,13 @@ layout (location = 2) in vec2 aTexCoord;
out vec3 ourColor;
out vec2 TexCoord;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main() {
gl_Position = vec4(aPos, 1.0);
// matrix multiplication goes from right to left.
gl_Position = projection * view * model * vec4(aPos, 1.0);
ourColor = aColor;
TexCoord = aTexCoord;
}