Files
updated-radar/shader_experiments/basic_triangle/main.cpp

248 lines
8.0 KiB
C++

/*
* 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 <glad/glad.h>
#include <GLFW/glfw3.h>
#include <cstdio>
#include <cstdlib>
// ---------------------------------------------------------------------------
// 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<GLADloadproc>(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<int>(sizeof(float)),
reinterpret_cast<void*>(0));
glEnableVertexAttribArray(0);
/* Attribute 1 (colour):
- 3 floats
- same stride
- offset = 2 floats in */
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE,
5 * static_cast<int>(sizeof(float)),
reinterpret_cast<void*>(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;
}