rasterizer

C++ software renderer

feat: texture sampling

Arjun Choudhary contact@arjunchoudhary.com

commit: 622e4ca parent: 4f739ef

11 files changed, 107 insertions(+), 21 deletions(-)
MOD.gitattributes+7-0
MODassets/Default.png+0-0
MODassets/DuckCM.png+0-0
MODassets/Missing_t.png+0-0
MODbuild_win32.bat+6-3
MODsrc/draw.cpp+14-8
MODsrc/draw.h+2-1
MODsrc/mesh.cpp+1-1
MODsrc/renderer.cpp+20-8
ADDsrc/texture.cpp+41-0
ADDsrc/texture.h+16-0
MOD · .gitattributes +7 -0
--- a/.gitattributes
+++ b/.gitattributes
@@ -2,3 +2,10 @@
 *.bat text eol=crlf
 *.ps1 text eol=crlf
 *.sh text=auto eol=lf
+
+*.png  binary
+*.jpg  binary
+*.jpeg binary
+*.gif  binary
+*.webp binary
+*.ico  binary
MOD · assets/Default.png +0 -0
--- a/assets/Default.png
+++ b/assets/Default.png
MOD · assets/DuckCM.png +0 -0
--- a/assets/DuckCM.png
+++ b/assets/DuckCM.png
MOD · assets/Missing_t.png +0 -0
--- a/assets/Missing_t.png
+++ b/assets/Missing_t.png
MOD · build_win32.bat +6 -3
--- a/build_win32.bat
+++ b/build_win32.bat
@@ -2,8 +2,9 @@ echo off
 setlocal enabledelayedexpansion
 
 set ROOT=%~dp0
-::set SRC_FILES=src\renderer.cpp
 set SRC_FILES=%ROOT%src\*.cpp
+set STB_FILES=%ROOT%external\stb
+set SDL_FILES=%ROOT%external\SDL\include
 
 
 :: =====================
@@ -66,8 +67,10 @@ if "%BUILD%"=="Release" (
 :: === Compilation ===
 :: ===================
 pushd build
-cl %CFLAGS% ^
-  /I %ROOT%external\SDL\include %SRC_FILES% ^
+cl /D_CRT_SECURE_NO_WARNINGS ^
+  %CFLAGS% ^
+  /I %STB_FILES% ^
+  /I %SDL_FILES% %SRC_FILES% ^
   /link %LFLAGS% ^
   shell32.lib ^
   %ROOT%external\SDL\lib\x64\SDL2main.lib ^
MOD · src/draw.cpp +14 -8
--- a/src/draw.cpp
+++ b/src/draw.cpp
@@ -1,6 +1,8 @@
 #include "draw.h"
 
 #include "globals.h"
+#include "mesh.h"
+#include "texture.h"
 #include "util_math.h"
 
 static inline u32 vec3_to_u32RGBA(vec3f &c) {
@@ -108,6 +110,8 @@ void rasterize_triangle(ScreenTriangle *tri, Window *window) {
     vec2f uv_over_w1 = tri->uv_over_w[1];
     vec2f uv_over_w2 = tri->uv_over_w[2];
 
+    Material mat = materials[tri->mat_idx];
+
     // bounding box (expensive fmin, fmax here I think)
     float minX_f = fmin(v0.x, fmin(v1.x, v2.x));
     float minY_f = fmin(v0.y, fmin(v1.y, v2.y));
@@ -180,16 +184,18 @@ void rasterize_triangle(ScreenTriangle *tri, Window *window) {
                 float gamma = E2 * invArea;
 
                 // lerp the attribute accross the triangle's barycentric coords
-                float lerp_inv_w   = alpha * inv_w0 + beta * inv_w1 + gamma * inv_w2;
-                float lerp_depth   = alpha * ndc_depth0 + beta * ndc_depth1 + gamma * ndc_depth2;
-                vec2f lerp_uv      = uv_over_w0 * alpha + uv_over_w1 * beta + uv_over_w2 * gamma;
-                vec3f lerped_color = alpha * c0 + beta * c1 + gamma * c2;  // TODO: not needed, sample a texture by lerp_uv here
+                float lerp_inv_w = alpha * inv_w0 + beta * inv_w1 + gamma * inv_w2;
+                float lerp_depth = alpha * ndc_depth0 + beta * ndc_depth1 + gamma * ndc_depth2;
+                vec2f lerp_uv    = uv_over_w0 * alpha + uv_over_w1 * beta + uv_over_w2 * gamma;
+
+                // vec3f lerped_color = alpha * c0 + beta * c1 + gamma * c2;
+                // vec3f color     = lerped_color / lerp_inv_w;
+                // u32   u32_color = vec3_to_u32RGBA(color);
 
-                vec3f color     = lerped_color / lerp_inv_w;
-                u32   u32_color = vec3_to_u32RGBA(color);
-                vec2f uv        = lerp_uv / lerp_inv_w;
+                vec2f uv       = lerp_uv / lerp_inv_w;
+                u32   tex_rgba = sample_texture(mat.texture_img_idx, uv);
 
-                process_fragment(buffer_idx, lerp_depth, u32_color, window);
+                process_fragment(buffer_idx, lerp_depth, tex_rgba, window);
             }
 
             // increment edge functions
MOD · src/draw.h +2 -1
--- a/src/draw.h
+++ b/src/draw.h
@@ -1,8 +1,8 @@
 #pragma once
 
 #include "globals.h"
-#include "util_math.h"
 #include "renderer.h"
+#include "util_math.h"
 
 struct ScreenTriangle {
     vec3f points[3];
@@ -10,6 +10,7 @@ struct ScreenTriangle {
     vec3f color_over_w[3];
     float inv_w[3];
     float ndc_depth[3];
+    u32   mat_idx;
 };
 
 void clear_color_buffer(u32 color, Window *window);
MOD · src/mesh.cpp +1 -1
--- a/src/mesh.cpp
+++ b/src/mesh.cpp
@@ -4,8 +4,8 @@
 #include <stdlib.h>
 #include <string.h>
 
-#include "util_string.h"
 #include "texture.h"
+#include "util_string.h"
 
 int current_material_idx = 0;
 int current_texture_idx  = 0;
MOD · src/renderer.cpp +20 -8
--- a/src/renderer.cpp
+++ b/src/renderer.cpp
@@ -3,16 +3,13 @@
 #include <math.h>
 #include <stdio.h>
 
-#include <vector>
-
 #include "SDL.h"
 #include "camera.h"
 #include "clip.h"
 #include "draw.h"
-#include "util_math.h"
 #include "mesh.h"
-
-using std::vector;
+#include "texture.h"
+#include "util_math.h"
 
 static_global Window window = {
     .sdl_window   = nullptr,
@@ -39,11 +36,26 @@ static_global float aspect       = float(window.width) / float(window.height);
 static_global float fov_y        = 59.0f * M_PI / 180.0f;
 static_global float grid_spacing = (fmin(window.width, window.height) / 2) / 10;
 
-// NOTE: should be like a module type thingy along with the entire pipeline?
-static_global vector<Mesh> scene;                   // vertex stage input
+static_global vector<Mesh> scene;  // vertex stage input
+
+// should be a part of a single big scene struct
+vector<Material> materials;
+vector<Texture>  textures;
+
 static_global vector<ScreenTriangle> raster_queue;  // raster stage input
 
 void initialize_scene() {
+    Texture  missing_texture  = load_texture("../assets/Missing_t.png");
+    Material missing_material = {
+        .name            = "missing_material",
+        .ambient         = vec3f(1.0f, 1.0f, 1.0f),
+        .diffuse         = vec3f(1.0f, 1.0f, 1.0f),
+        .specular        = vec3f(1.0f, 1.0f, 1.0f),
+        .texture_img_idx = 0,
+    };
+    textures.push_back(missing_texture);
+    materials.push_back(missing_material);
+
     Mesh plane = parse_obj("../assets/Plane.obj");
     scene.emplace_back(plane);
     scene.back().transform.position = {0.0, 0.0, 0.0};
@@ -397,6 +409,7 @@ void run_pipeline(void) {
                     tri.uv_over_w[i]    = cvs[i].uv * inv_w;
                     tri.color_over_w[i] = cvs[i].color * inv_w;
                     tri.ndc_depth[i]    = ndc.z;
+                    tri.mat_idx         = f0.mat_idx;
                 }
                 raster_queue.push_back(tri);
             }
@@ -419,7 +432,6 @@ void rasterize(void) {
 
     for (int i = 0; i < raster_queue.size(); i++) {
         ScreenTriangle &triangle = raster_queue[i];
-        // TODO: toggle primitives
 
         // triangle primitives
         rasterize_triangle(&triangle, &window);
ADD · src/texture.cpp +41 -0
--- a/src/texture.cpp
+++ b/src/texture.cpp
@@ -0,0 +1,41 @@
+#include "texture.h"
+
+#include "globals.h"
+
+#define STB_IMAGE_IMPLEMENTATION
+#include "stb_image.h"
+
+Texture load_texture(const char *filename) {
+    int            width, height, channels;
+    unsigned char *data = stbi_load(filename, &width, &height, &channels, 4);
+
+    if (!data) {
+        fprintf(stderr, "Failed to load image: %s\n", filename);
+        return textures[0];  // can see this being annoying to debug, change
+    }
+
+    Texture tex;
+    tex.width  = width;
+    tex.height = height;
+    tex.pixels = (u32 *)malloc(sizeof(u32) * width * height);
+    for (int y = 0; y < height; ++y) {
+        for (int x = 0; x < width; ++x) {
+            int pixel_idx         = y * width + x;
+            int flipped_y         = height - 1 - y;
+            u8  r                 = data[flipped_y * width * 4 + x * 4 + 0];
+            u8  g                 = data[flipped_y * width * 4 + x * 4 + 1];
+            u8  b                 = data[flipped_y * width * 4 + x * 4 + 2];
+            u8  a                 = data[flipped_y * width * 4 + x * 4 + 3];
+            tex.pixels[pixel_idx] = (r << 24) | (g << 16) | (b << 8) | a;
+        }
+    }
+    stbi_image_free(data);
+    return tex;
+}
+
+u32 sample_texture(int texture_index, vec2f uv) {
+    const Texture &tex = textures[texture_index];
+    int            x   = (int)(uv.x * tex.width);
+    int            y   = (int)(uv.y * tex.height);
+    return tex.pixels[y * tex.width + x];
+}
ADD · src/texture.h +16 -0
--- a/src/texture.h
+++ b/src/texture.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include "globals.h"
+#include "util_math.h"
+#include "vector"
+
+struct Texture {
+    int  width;
+    int  height;
+    u32 *pixels;  // sizeof(u32)[width * height]
+};
+
+extern std::vector<Texture> textures;
+
+Texture load_texture(const char *filename);
+u32     sample_texture(int texture_index, vec2f uv);