/* * MIT License * Author: Mark Allyn * * basic_triangle/main.cpp * * Learning exercise: draw a single triangle using a vertex shader and * fragment shader. The vertex shader places three corners in clip space; * the fragment shader colours each pixel by interpolating the per-vertex * colours the GPU receives. * * Key concepts demonstrated * 1. Compiling and linking a GLSL shader program at runtime * 2. Uploading vertex data to the GPU via a VAO + VBO * 3. How the rasteriser interpolates per-vertex attributes (colour here) * across a primitive before the fragment shader runs * 4. The minimal render loop: clear → draw → swap */ #include #include #include #include // --------------------------------------------------------------------------- // GLSL source — kept inline so the whole exercise lives in one file // --------------------------------------------------------------------------- /* * Vertex shader * * Runs once per vertex (three times for our triangle). * layout(location = 0) → slot 0 in the VAO: the 2-D position * layout(location = 1) → slot 1 in the VAO: the RGB colour * * gl_Position must be set; it tells the rasteriser where on screen the * vertex lands. We set w = 1.0 so the position is not divided (no * perspective). * * vertex_color is an "out" variable — the rasteriser will smoothly * interpolate it across the triangle and hand the result to the fragment * shader as an "in" variable with the same name. */ static const char* vertex_shader_src = R"glsl( #version 330 core layout(location = 0) in vec2 position; layout(location = 1) in vec3 color; out vec3 vertex_color; // passed to the fragment shader void main() { gl_Position = vec4(position, 0.0, 1.0); vertex_color = color; } )glsl"; /* * Fragment shader * * Runs once per screen pixel that the rasteriser decides is inside the * triangle. It receives the interpolated vertex_color and outputs a * final RGBA colour for that pixel. */ static const char* fragment_shader_src = R"glsl( #version 330 core in vec3 vertex_color; // interpolated from the three vertices out vec4 frag_color; // final pixel colour (RGBA) void main() { frag_color = vec4(vertex_color, 1.0); // alpha = 1 (fully opaque) } )glsl"; // --------------------------------------------------------------------------- // Helper: compile one shader stage and report any errors // --------------------------------------------------------------------------- static GLuint compile_shader(GLenum type, const char* src) { GLuint shader = glCreateShader(type); glShaderSource(shader, 1, &src, nullptr); glCompileShader(shader); GLint ok = 0; glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); if (!ok) { char log[512]; glGetShaderInfoLog(shader, sizeof(log), nullptr, log); std::fprintf(stderr, "Shader compile error:\n%s\n", log); } return shader; } // --------------------------------------------------------------------------- // Helper: link vertex + fragment shaders into a program // --------------------------------------------------------------------------- static GLuint build_program(const char* vert_src, const char* frag_src) { GLuint vert = compile_shader(GL_VERTEX_SHADER, vert_src); GLuint frag = compile_shader(GL_FRAGMENT_SHADER, frag_src); GLuint program = glCreateProgram(); glAttachShader(program, vert); glAttachShader(program, frag); glLinkProgram(program); GLint ok = 0; glGetProgramiv(program, GL_LINK_STATUS, &ok); if (!ok) { char log[512]; glGetProgramInfoLog(program, sizeof(log), nullptr, log); std::fprintf(stderr, "Program link error:\n%s\n", log); } // Once linked the individual stage objects are no longer needed glDeleteShader(vert); glDeleteShader(frag); return program; } // --------------------------------------------------------------------------- // main // --------------------------------------------------------------------------- int main() { // ----------------------------------------------------------------------- // Window setup // ----------------------------------------------------------------------- if (!glfwInit()) { std::fprintf(stderr, "glfwInit failed\n"); return EXIT_FAILURE; } glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); GLFWwindow* window = glfwCreateWindow(800, 600, "Basic Triangle", nullptr, nullptr); if (!window) { std::fprintf(stderr, "glfwCreateWindow failed\n"); glfwTerminate(); return EXIT_FAILURE; } glfwMakeContextCurrent(window); if (!gladLoadGLLoader(reinterpret_cast(glfwGetProcAddress))) { std::fprintf(stderr, "GLAD init failed\n"); return EXIT_FAILURE; } // ----------------------------------------------------------------------- // Vertex data // // Clip space runs from -1 to +1 on both axes. Each row below is: // x y R G B // The GPU interpolates R,G,B across the triangle surface. // ----------------------------------------------------------------------- float vertices[] = { // position color (RGB) 0.0f, 0.5f, 1.0f, 0.0f, 0.0f, // top — red -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, // left — green 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, // right — blue }; // ----------------------------------------------------------------------- // GPU objects // // VAO — remembers which VBO(s) and attribute layouts belong together // VBO — raw buffer holding the vertex data above // ----------------------------------------------------------------------- GLuint vao = 0; GLuint vbo = 0; glGenVertexArrays(1, &vao); glGenBuffers(1, &vbo); glBindVertexArray(vao); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); /* Tell OpenGL how to read attribute 0 (position): - 2 floats - stride = 5 floats (2 position + 3 colour) - offset = 0 */ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 5 * static_cast(sizeof(float)), reinterpret_cast(0)); glEnableVertexAttribArray(0); /* Attribute 1 (colour): - 3 floats - same stride - offset = 2 floats in */ glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 5 * static_cast(sizeof(float)), reinterpret_cast(2 * sizeof(float))); glEnableVertexAttribArray(1); glBindVertexArray(0); // unbind so later state changes don't affect it // ----------------------------------------------------------------------- // Shader program // ----------------------------------------------------------------------- GLuint shader_program = build_program(vertex_shader_src, fragment_shader_src); // ----------------------------------------------------------------------- // Render loop // ----------------------------------------------------------------------- while (!glfwWindowShouldClose(window)) { if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) glfwSetWindowShouldClose(window, GLFW_TRUE); glClearColor(0.1f, 0.1f, 0.1f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glUseProgram(shader_program); glBindVertexArray(vao); glDrawArrays(GL_TRIANGLES, 0, 3); // 3 vertices = 1 triangle glfwSwapBuffers(window); glfwPollEvents(); } // ----------------------------------------------------------------------- // Cleanup // ----------------------------------------------------------------------- glDeleteVertexArrays(1, &vao); glDeleteBuffers(1, &vbo); glDeleteProgram(shader_program); glfwDestroyWindow(window); glfwTerminate(); return EXIT_SUCCESS; }