// software_rasterizer.
cpp
// A minimal OpenGL program that implements a custom CPU-based software rasterizer
// and uses OpenGL to display the resulting framebuffer as a texture.
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <iostream>
#include <vector>
#include <cmath>
#include <cstdint>
#include <algorithm> // for std::min, std::max (optional)
// Window dimensions
constexpr int WIDTH = 800;
constexpr int HEIGHT = 600;
// Vertex with position and color
struct Vertex {
glm::vec2 pos;
glm::u8vec3 color;
};
// Framebuffer as an array of RGB pixels
static std::vector<uint8_t> framebuffer(WIDTH* HEIGHT * 3);
// Clear framebuffer to black
static void clearFramebuffer() {
std::fill(framebuffer.begin(), framebuffer.end(), 0);
}
// Put a pixel with clamping
static void putPixel(int x, int y, const glm::u8vec3& color) {
if (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) return;
int idx = (y * WIDTH + x) * 3;
framebuffer[idx + 0] = color.r;
framebuffer[idx + 1] = color.g;
framebuffer[idx + 2] = color.b;
}
// Compute barycentric coordinates for point p with respect to triangle (a, b, c)
static bool barycentric(const glm::vec2& p,
const glm::vec2& a,
const glm::vec2& b,
const glm::vec2& c,
float& u, float& v, float& w) {
float denom = (b.y - c.y) * (a.x - c.x) + (c.x - b.x) * (a.y - c.y);
if (std::fabs(denom) < 1e-6f) return false;
u = ((b.y - c.y) * (p.x - c.x) + (c.x - b.x) * (p.y - c.y)) / denom;
v = ((c.y - a.y) * (p.x - c.x) + (a.x - c.x) * (p.y - c.y)) / denom;
w = 1.0f - u - v;
return u >= 0.f && v >= 0.f && w >= 0.f;
}
// Draw a filled triangle
static void drawTriangle(const Vertex& v0, const Vertex& v1, const Vertex& v2) {
// Compute bounding box in screen space
int minX = static_cast<int>(std::floor(std::min({ v0.pos.x, v1.pos.x,
v2.pos.x })));
int maxX = static_cast<int>(std::ceil(std::max({ v0.pos.x, v1.pos.x,
v2.pos.x })));
int minY = static_cast<int>(std::floor(std::min({ v0.pos.y, v1.pos.y,
v2.pos.y })));
int maxY = static_cast<int>(std::ceil(std::max({ v0.pos.y, v1.pos.y,
v2.pos.y })));
// Clip to screen using glm::clamp
minX = glm::clamp(minX, 0, WIDTH - 1);
maxX = glm::clamp(maxX, 0, WIDTH - 1);
minY = glm::clamp(minY, 0, HEIGHT - 1);
maxY = glm::clamp(maxY, 0, HEIGHT - 1);
// Rasterize
for (int y = minY; y <= maxY; ++y) {
for (int x = minX; x <= maxX; ++x) {
glm::vec2 p{ x + 0.5f, y + 0.5f };
float u, v, w;
if (barycentric(p, v0.pos, v1.pos, v2.pos, u, v, w)) {
glm::u8vec3 col(
static_cast<uint8_t>(u * v0.color.r + v * v1.color.r + w *
v2.color.r),
static_cast<uint8_t>(u * v0.color.g + v * v1.color.g + w *
v2.color.g),
static_cast<uint8_t>(u * v0.color.b + v * v1.color.b + w *
v2.color.b)
);
putPixel(x, y, col);
}
}
}
}
// Create a test triangle
static std::vector<Vertex> createTestTriangle() {
return {
{{100.0f, 100.0f}, {255, 0, 0}}, // Red
{{700.0f, 150.0f}, { 0, 255, 0}}, // Green
{{400.0f, 500.0f}, { 0, 0, 255}} // Blue
};
}
// Fullscreen quad for displaying texture
static float quadVerts[] = {
// positions // uvs
-1.0f, -1.0f, 0.0f, 0.0f,
1.0f, -1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 1.0f, 1.0f,
-1.0f, 1.0f, 0.0f, 1.0f
};
static unsigned int quadIdx[] = { 0,1,2, 2,3,0 };
// Shader sources
const char* vsSource = R"(
#version 330 core
layout(location = 0) in vec2 inPos;
layout(location = 1) in vec2 inUV;
out vec2 fragUV;
void main() {
fragUV = inUV;
gl_Position = vec4(inPos, 0.0, 1.0);
}
)";
const char* fsSource = R"(
#version 330 core
in vec2 fragUV;
out vec4 outColor;
uniform sampler2D tex;
void main() {
outColor = texture(tex, fragUV);
}
)";
// Compile shader utility
static GLuint compileShader(GLenum type, const char* src) {
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &src, nullptr);
glCompileShader(shader);
int success;
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
char log[512];
glGetShaderInfoLog(shader, 512, nullptr, log);
std::cerr << "Shader compile error: " << log << std::endl;
}
return shader;
}
int main() {
// Init GLFW
if (!glfwInit()) return -1;
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "Software Rasterizer",
nullptr, nullptr);
if (!window) { glfwTerminate(); return -1; }
glfwMakeContextCurrent(window);
// Init GLAD
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
std::cerr << "Failed to initialize GLAD\n";
return -1;
}
// Create texture for framebuffer
GLuint tex;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, WIDTH, HEIGHT, 0, GL_RGB,
GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// Setup quad VAO/VBO
GLuint vao, vbo, ebo;
glGenVertexArrays(1, &vao);
glGenBuffers(1, &vbo);
glGenBuffers(1, &ebo);
glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(quadVerts), quadVerts, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(quadIdx), quadIdx,
GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 *
sizeof(float)));
// Compile shaders and link program
GLuint vs = compileShader(GL_VERTEX_SHADER, vsSource);
GLuint fs = compileShader(GL_FRAGMENT_SHADER, fsSource);
GLuint prog = glCreateProgram();
glAttachShader(prog, vs);
glAttachShader(prog, fs);
glLinkProgram(prog);
glUseProgram(prog);
glUniform1i(glGetUniformLocation(prog, "tex"), 0);
// Main loop
while (!glfwWindowShouldClose(window)) {
// Rasterize scene
clearFramebuffer();
auto tri = createTestTriangle();
drawTriangle(tri[0], tri[1], tri[2]);
// Upload framebuffer to texture
glBindTexture(GL_TEXTURE_2D, tex);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, WIDTH, HEIGHT, GL_RGB,
GL_UNSIGNED_BYTE, framebuffer.data());
// Render quad
glClear(GL_COLOR_BUFFER_BIT);
glBindVertexArray(vao);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);
glfwSwapBuffers(window);
glfwPollEvents();
}
// Cleanup
glDeleteProgram(prog);
glDeleteShader(vs);
glDeleteShader(fs);
glDeleteBuffers(1, &vbo);
glDeleteBuffers(1, &ebo);
glDeleteVertexArrays(1, &vao);
glDeleteTextures(1, &tex);
glfwTerminate();
return 0;
}