Created
May 5, 2019 05:55
-
-
Save ramntry/f173de25d061cf57ff944eb788f6475f to your computer and use it in GitHub Desktop.
Very pretty triangle (OpenGL)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma clang diagnostic push | |
#pragma clang diagnostic ignored "-Wdocumentation" | |
#define GLEW_STATIC | |
#include <GL/glew.h> | |
#include <GLFW/glfw3.h> | |
#pragma clang diagnostic pop | |
#include <iostream> | |
#include <vector> | |
#include <cmath> | |
static constexpr GLint WIDTH = 800, HEIGHT = 600; | |
struct Vector3f { | |
GLfloat x; | |
GLfloat y; | |
GLfloat z; | |
}; | |
struct Color3f { | |
GLfloat r; | |
GLfloat g; | |
GLfloat b; | |
}; | |
struct Vertex { | |
Vector3f position; | |
Color3f color; | |
}; | |
static constexpr char vertexShaderSource[] = R"( | |
#version 330 core | |
uniform mat4 transform; | |
uniform mat4 colorFlow; | |
layout (location = 0) in vec3 position; | |
layout (location = 1) in vec3 color; | |
out vec4 vertexColor; | |
void main() { | |
gl_Position = transform * vec4(position, 1.0); | |
vertexColor = colorFlow * vec4(color, 1.0); | |
} | |
)"; | |
static constexpr char fragmentShaderSource[] = R"( | |
#version 330 core | |
in vec4 vertexColor; | |
out vec4 color; | |
void main() { | |
color = vertexColor; | |
} | |
)"; | |
static const std::vector<Vertex> rgbTriangle = { | |
{.position={-0.5f, -0.625f, 0.0f}, .color={1.0f, 0.0f, 0.0f}}, | |
{.position={ 0.5f, -0.625f, 0.0f}, .color={0.0f, 1.0f, 0.0f}}, | |
{.position={ 0.0f, 0.375f, 0.0f}, .color={0.0f, 0.0f, 1.0f}}, | |
}; | |
class VertexArray { | |
GLuint VAO; | |
GLuint VBO; | |
const unsigned short numVertices; | |
VertexArray(const VertexArray &) = delete; | |
public: | |
VertexArray(const std::vector<Vertex> &vertices); | |
~VertexArray(); | |
void bind() const; | |
void unbind() const; | |
GLsizei size() const { return numVertices; } | |
static GLuint currentlyBoundVAO(); | |
}; | |
VertexArray::VertexArray(const std::vector<Vertex> &vertices) | |
: numVertices(static_cast<decltype(numVertices)>(vertices.size())) { | |
assert(numVertices == vertices.size() && "Unexpectedly large number of vertices"); | |
glGenVertexArrays(1, &VAO); | |
glBindVertexArray(VAO); | |
glGenBuffers(1, &VBO); | |
glBindBuffer(GL_ARRAY_BUFFER, VBO); | |
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(vertices[0]), &vertices[0], GL_STATIC_DRAW); | |
glVertexAttribPointer(0, sizeof(vertices[0].position) / sizeof(GLfloat), GL_FLOAT, GL_FALSE, sizeof(vertices[0]), 0); | |
glVertexAttribPointer(1, sizeof(vertices[0].color) / sizeof(GLfloat), GL_FLOAT, GL_FALSE, sizeof(vertices[0]), | |
reinterpret_cast<const GLvoid *>(sizeof(vertices[0].position))); | |
glBindBuffer(GL_ARRAY_BUFFER, 0); | |
glBindVertexArray(0); | |
} | |
void VertexArray::bind() const { | |
glBindVertexArray(VAO); | |
glEnableVertexAttribArray(0); | |
glEnableVertexAttribArray(1); | |
} | |
void VertexArray::unbind() const { | |
assert(currentlyBoundVAO() == VAO && "Unbinding an unexpected Vertex Array Object"); | |
glDisableVertexAttribArray(1); | |
glDisableVertexAttribArray(0); | |
glBindVertexArray(0); | |
} | |
VertexArray::~VertexArray() { | |
if (currentlyBoundVAO() == VAO) { | |
unbind(); | |
} | |
glDeleteVertexArrays(1, &VAO); | |
glDeleteBuffers(1, &VBO); | |
} | |
GLuint VertexArray::currentlyBoundVAO() { | |
GLint boundVAO; | |
glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &boundVAO); | |
assert(boundVAO >= 0 && "Unexpected value of a Vertex Array handle"); | |
return boundVAO; | |
} | |
struct GLFWInitTerminate { | |
GLFWInitTerminate(const GLFWInitTerminate &) = delete; | |
GLFWInitTerminate() : status(glfwInit()) { | |
if (status != GL_TRUE) { | |
std::cerr << "Failed to initialize GLFW" << std::endl; | |
} | |
} | |
~GLFWInitTerminate() { | |
if (status == GL_TRUE) { | |
glfwTerminate(); | |
} | |
} | |
const int status; | |
}; | |
bool compileShader(GLuint shaderHandle, const char *shaderSource, const char *shaderName) { | |
glShaderSource(shaderHandle, 1, &shaderSource, nullptr); | |
glCompileShader(shaderHandle); | |
GLint success; | |
glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &success); | |
if (success) { | |
return true; | |
} | |
static GLchar infoLog[1024]; | |
glGetShaderInfoLog(shaderHandle, sizeof(infoLog), nullptr, infoLog); | |
std::cerr << "Failed to compile the " << shaderName << " shader:\n" << infoLog << std::endl; | |
return false; | |
} | |
struct ShaderCreateDelete { | |
ShaderCreateDelete(const ShaderCreateDelete &) = delete; | |
ShaderCreateDelete(GLenum shaderType) : handle(glCreateShader(shaderType)) { | |
if (!handle) { | |
std::cerr << "Failed to create a shader object" << std::endl; | |
} | |
} | |
~ShaderCreateDelete() { | |
glDeleteShader(handle); | |
} | |
const GLuint handle; | |
}; | |
bool buildProgram(GLuint shaderProgram, const char *vertexShaderText, const char *fragmentShaderText) { | |
ShaderCreateDelete vertexShader(GL_VERTEX_SHADER); | |
if (!compileShader(vertexShader.handle, vertexShaderText, "vertex")) { | |
return false; | |
} | |
ShaderCreateDelete fragmentShader(GL_FRAGMENT_SHADER); | |
if (!compileShader(fragmentShader.handle, fragmentShaderText, "fragment")) { | |
return false; | |
} | |
glAttachShader(shaderProgram, vertexShader.handle); | |
glAttachShader(shaderProgram, fragmentShader.handle); | |
glLinkProgram(shaderProgram); | |
GLint success; | |
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); | |
if (success) { | |
return true; | |
} | |
static GLchar infoLog[1024]; | |
glGetProgramInfoLog(shaderProgram, sizeof(infoLog), nullptr, infoLog); | |
std::cerr << "Failed to link the shader program:\n" << infoLog << std::endl; | |
return false; | |
} | |
bool validateProgram(GLuint shaderProgram) { | |
glValidateProgram(shaderProgram); | |
GLint success; | |
glGetProgramiv(shaderProgram, GL_VALIDATE_STATUS, &success); | |
static GLchar infoLog[1024]; | |
glGetProgramInfoLog(shaderProgram, sizeof(infoLog), nullptr, infoLog); | |
std::string infoLogString(infoLog); | |
if (!success) { | |
std::cerr << "Failed shader program validation:\n" << infoLogString << std::endl; | |
return false; | |
} | |
if (!infoLogString.empty()) { | |
std::clog << "Shader program validation info:\n" << infoLogString << std::endl; | |
} | |
return true; | |
} | |
class ShaderProgram { | |
GLuint shaderProgram; | |
ShaderProgram(const ShaderProgram &) = delete; | |
public: | |
ShaderProgram(const char *vertexShaderText, const char *fragmentShaderText); | |
~ShaderProgram() { | |
glDeleteProgram(shaderProgram); | |
} | |
bool isValid() const { return shaderProgram; } | |
bool validate(); | |
void use() const { | |
assert(isValid() && "Can't use an invalid shader program object"); | |
glUseProgram(shaderProgram); | |
} | |
GLint getUniformLocation(const char *variableName) const; | |
}; | |
ShaderProgram::ShaderProgram(const char *vertexShaderText, | |
const char *fragmentShaderText) : shaderProgram(glCreateProgram()) { | |
if (!shaderProgram) { | |
std::cerr << "Failed to create a shader program object" << std::endl; | |
return; | |
} | |
if (!buildProgram(shaderProgram, vertexShaderText, fragmentShaderText)) { | |
glDeleteProgram(shaderProgram); | |
shaderProgram = 0; | |
} | |
} | |
bool ShaderProgram::validate() { | |
if (!validateProgram(shaderProgram)) { | |
glDeleteProgram(shaderProgram); | |
shaderProgram = 0; | |
} | |
return isValid(); | |
} | |
GLint ShaderProgram::getUniformLocation(const char *variableName) const { | |
assert(isValid() && "Can't get a uniform's location in an invalid shader program object"); | |
GLint Location = glGetUniformLocation(shaderProgram, variableName); | |
if (Location == -1) { | |
std::clog << "Uniform ``" << variableName << "'' is not used in the shader" << std::endl; | |
} | |
return Location; | |
} | |
struct Transform { | |
Transform &initAsScale(GLfloat x = 1.0f, GLfloat y = 1.0f, GLfloat z = 1.0f); | |
Transform &initAsRotateAroundZ(double angle); | |
Transform &initAsTranslate(GLfloat x, GLfloat y = 0.0f, GLfloat z = 0.0f); | |
Transform &multiplyFromLeftBy(const Transform &A); | |
Transform &addScale(GLfloat x, GLfloat y, GLfloat z) { | |
return multiplyFromLeftBy(Transform().initAsScale(x, y, z)); | |
} | |
Transform &addRotateAroundZ(double angle) { | |
return multiplyFromLeftBy(Transform().initAsRotateAroundZ(angle)); | |
} | |
Transform &addTranslate(GLfloat x, GLfloat y = 0.0f, GLfloat z = 0.0f) { | |
return multiplyFromLeftBy(Transform().initAsTranslate(x, y, z)); | |
} | |
GLfloat mat[4][4]; | |
}; | |
Transform &Transform::initAsScale(GLfloat x, GLfloat y, GLfloat z) { | |
std::memset(mat, 0, sizeof(mat)); | |
mat[0][0] = x; | |
mat[1][1] = y; | |
mat[2][2] = z; | |
mat[3][3] = 1.0f; | |
return *this; | |
} | |
Transform &Transform::initAsRotateAroundZ(double angle) { | |
initAsScale(); | |
mat[0][0] = std::cos(angle); mat[0][1] = std::sin(-angle); | |
mat[1][0] = std::sin(angle); mat[1][1] = std::cos(-angle); | |
return *this; | |
} | |
Transform &Transform::initAsTranslate(GLfloat x, GLfloat y, GLfloat z) { | |
initAsScale(); | |
mat[0][3] = x; | |
mat[1][3] = y; | |
mat[2][3] = z; | |
return *this; | |
} | |
Transform &Transform::multiplyFromLeftBy(const Transform &A) { | |
Transform B(*this); | |
for (int i = 0; i < 4; ++i) { | |
for (int j = 0; j < 4; ++j) { | |
mat[i][j] = 0.0f; | |
for (int k = 0; k < 4; ++k) { | |
mat[i][j] += A.mat[i][k] * B.mat[k][j]; | |
} | |
} | |
} | |
return *this; | |
} | |
int main(int argc, const char **argv) { | |
GLFWInitTerminate glfwScope; | |
if (glfwScope.status != GL_TRUE) { | |
return 1; | |
} | |
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); | |
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); | |
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); | |
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); | |
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); | |
GLFWwindow *window = glfwCreateWindow(WIDTH, HEIGHT, "Learn OpenGL", nullptr, nullptr); | |
if (!window) { | |
std::cerr << "Failed to create GLFW window" << std::endl; | |
return 2; | |
} | |
int screenWidth, screenHeight; | |
glfwGetFramebufferSize(window, &screenWidth, &screenHeight); | |
if (!screenWidth || !screenHeight) { | |
std::cerr << "Failed to retrieve the framebuffer size" << std::endl; | |
return 3; | |
} | |
glfwMakeContextCurrent(window); | |
glViewport(0, 0, screenWidth, screenHeight); | |
glewExperimental = GL_TRUE; | |
if (glewInit() != GLEW_OK) { | |
std::cerr << "Failed to initialize GLEW" << std::endl; | |
return 4; | |
} | |
ShaderProgram shaderProgram(vertexShaderSource, fragmentShaderSource); | |
if (!shaderProgram.isValid()) { | |
return 5; | |
} | |
VertexArray VAO(rgbTriangle); | |
VAO.bind(); | |
if (!shaderProgram.validate()) { | |
return 6; | |
} | |
shaderProgram.use(); | |
GLint transformUniformLocation = shaderProgram.getUniformLocation("transform"); | |
GLint colorFlowUniformLocation = shaderProgram.getUniformLocation("colorFlow"); | |
Transform transform; | |
Transform colorFlow; | |
glClearColor(0.0f, 0.0f, 0.0f, 1.0f); | |
while (!glfwWindowShouldClose(window)) { | |
glfwPollEvents(); | |
glClear(GL_COLOR_BUFFER_BIT); | |
const double t = glfwGetTime(); | |
if (transformUniformLocation != -1) { | |
const double st = std::sin(t); | |
transform.initAsTranslate(0.5 * st, 0.5 * st * st); | |
glUniformMatrix4fv(transformUniformLocation, 1, GL_TRUE, &transform.mat[0][0]); | |
} | |
if (colorFlowUniformLocation != -1) { | |
colorFlow.initAsScale(0.5f, 0.5f) | |
.addRotateAroundZ(12.0 * t) | |
.addTranslate(0.5f, 0.5f); | |
glUniformMatrix4fv(colorFlowUniformLocation, 1, GL_TRUE, &colorFlow.mat[0][0]); | |
} | |
glDrawArrays(GL_TRIANGLES, 0, VAO.size()); | |
glfwSwapBuffers(window); | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment