rasterizer

c++ software renderer

draw.cpp

7.0 kB
  1#include "draw.hpp"
  2
  3#include "math.hpp"
  4
  5static inline u32 vec3_to_u32RGBA(vec3f &c) {
  6    u8 r = (u8)(c.x * 255.0f);
  7    u8 g = (u8)(c.y * 255.0f);
  8    u8 b = (u8)(c.z * 255.0f);
  9    u8 a = 255;
 10
 11    u32 final_color = (r << 24) | (g << 16) | (b << 8) | a;
 12    return final_color;
 13};
 14
 15void clear_color_buffer(u32 color, Window *window) {
 16    for (int i = 0; i < window->width * window->height; i++) {
 17        window->back_buffer[i] = color;
 18    };
 19};
 20
 21// (0,0) bottom left, screen space origin
 22// Y- in NDC is X+ in screen space
 23// Z+ in NDC is Y+ in screen space
 24static inline void draw_pixel(int x, int y, u32 color, Window *window) {
 25    if (x >= 0 && x < window->width && y >= 0 && y < window->height) {
 26        int flipped_y                                        = (window->height - 1) - y;
 27        window->back_buffer[(window->width * flipped_y) + x] = color;
 28    };
 29};
 30
 31void draw_rect(int x, int y, int width, int height, u32 color, Window *window) {
 32    for (int i = 0; i < height; i++) {
 33        for (int j = 0; j < width; j++) {
 34            int offset_x = x + j;
 35            int offset_y = y - i;
 36            draw_pixel(offset_x, offset_y, color, window);
 37        };
 38    };
 39};
 40
 41void draw_line_grid(u32 color, Window *window, u8 spacing = 10) {
 42    for (int y = 0; y < window->height; y++) {
 43        for (int x = 0; x < window->width; x++) {
 44            if (x % spacing == 0 || y % spacing == 0) {
 45                draw_pixel(x, y, color, window);
 46            };
 47        };
 48    };
 49};
 50
 51// Bresenham's Line Algorithm
 52// https://wikipedia.org/wiki/Bresenham's_line_algorithm
 53// TODO: Wu's whenever I add pure primitives toggling
 54void draw_line(int x0, int y0, int x1, int y1, u32 color, Window *window) {
 55    // differences
 56    int delta_x = (x1 > x0) ? (x1 - x0) : (x0 - x1);
 57    int delta_y = (y1 > y0) ? (y1 - y0) : (y0 - y1);
 58    // direction
 59    int step_x = (x0 < x1) ? 1 : -1;
 60    int step_y = (y0 < y1) ? 1 : -1;
 61    // error term, to adjust direction
 62    int error_calib = delta_x - delta_y;
 63
 64    while (1) {
 65        draw_pixel(x0, y0, color, window);
 66        if (x0 == x1 && y0 == y1) {
 67            break;
 68        }
 69        int error_double = 2 * error_calib;
 70        if (error_double > -delta_y) {
 71            error_calib -= delta_y;
 72            x0 += step_x;
 73        }
 74        if (error_double < delta_x) {
 75            error_calib += delta_x;
 76            y0 += step_y;
 77        };
 78    };
 79};
 80
 81void process_fragment(int x, int y, float pixel_depth, u32 pixel_color, Window *window) {
 82    int   buffer_idx             = (window->width * y) + x;
 83    float current_depth_at_pixel = window->depth_buffer[buffer_idx];
 84    if (pixel_depth < current_depth_at_pixel) {
 85        window->depth_buffer[buffer_idx] = pixel_depth;
 86        draw_pixel(x, y, pixel_color, window);
 87    }
 88}
 89
 90void rasterize_triangle(ScreenTriangle *tri, Window *window) {
 91    vec3f v0 = tri->points[0];
 92    vec3f v1 = tri->points[1];
 93    vec3f v2 = tri->points[2];
 94
 95    float inv_w0 = tri->inv_w[0];
 96    float inv_w1 = tri->inv_w[1];
 97    float inv_w2 = tri->inv_w[2];
 98
 99    vec3f c0 = tri->color_over_w[0];
100    vec3f c1 = tri->color_over_w[1];
101    vec3f c2 = tri->color_over_w[2];
102
103    float ndc_depth0 = tri->ndc_depth[0];
104    float ndc_depth1 = tri->ndc_depth[1];
105    float ndc_depth2 = tri->ndc_depth[2];
106
107    vec2f uv_over_w0 = tri->uv_over_w[0];
108    vec2f uv_over_w1 = tri->uv_over_w[1];
109    vec2f uv_over_w2 = tri->uv_over_w[2];
110
111    // bounding box (expensive fmin, fmax here I think)
112    float minX_f = fmin(v0.x, fmin(v1.x, v2.x));
113    float minY_f = fmin(v0.y, fmin(v1.y, v2.y));
114    float maxX_f = fmax(v0.x, fmax(v1.x, v2.x));
115    float maxY_f = fmax(v0.y, fmax(v1.y, v2.y));
116
117    minX_f = fmax(0.0f, minX_f);
118    minY_f = fmax(0.0f, minY_f);
119    maxX_f = fmin(static_cast<float>(window->width - 1), maxX_f);
120    maxY_f = fmin(static_cast<float>(window->height - 1), maxY_f);
121
122    int minX = static_cast<int>(floor(minX_f));
123    int minY = static_cast<int>(floor(minY_f));
124    int maxX = static_cast<int>(ceil(maxX_f));
125    int maxY = static_cast<int>(ceil(maxY_f));
126
127    // edge coefficients (dY, -dX) pineda-style rasterization
128    float dX01 = v0.x - v1.x;  // delta X (v0 to v1)
129    float dY01 = v0.y - v1.y;  // delta Y (v0 to v1)
130    float dX12 = v1.x - v2.x;  // delta X (v1 to v2)
131    float dY12 = v1.y - v2.y;  // delta Y (v1 to v2)
132    float dX20 = v2.x - v0.x;  // delta X (v2 to v0)
133    float dY20 = v2.y - v0.y;  // delta Y (v2 to v0)
134
135    float triangleArea = (v1.y - v2.y) * (v0.x - v2.x) + (v2.x - v1.x) * (v0.y - v2.y);
136    if (fabs(triangleArea) < 1e-6f) {  // degen triangle
137        return;
138    }
139    float invArea = 1.0f / triangleArea;
140
141    // center of the top-left pixel in the bounding box
142    vec2f pixel_start = {(float)minX + 0.5f, (float)minY + 0.5f};
143
144    // initial edge function (A0, B0, G0) for pixel_start
145    float E0 = (v1.y - v2.y) * (pixel_start.x - v2.x) + (v2.x - v1.x) * (pixel_start.y - v2.y);
146    float E1 = (v2.y - v0.y) * (pixel_start.x - v0.x) + (v0.x - v2.x) * (pixel_start.y - v0.y);
147    float E2 = (v0.y - v1.y) * (pixel_start.x - v1.x) + (v1.x - v0.x) * (pixel_start.y - v1.y);
148
149    // x delta step (move right one pixel)
150    float stepX0 = dY12;
151    float stepX1 = dY20;
152    float stepX2 = dY01;
153
154    // y delta step (move down one row)
155    float stepY0 = dX12;
156    float stepY1 = dX20;
157    float stepY2 = dX01;
158
159    float E0_row = E0;
160    float E1_row = E1;
161    float E2_row = E2;
162
163    for (int y = minY; y <= maxY; ++y) {
164        // reset
165        E0 = E0_row;
166        E1 = E1_row;
167        E2 = E2_row;
168
169        // inside tri
170        for (int x = minX; x <= maxX; ++x) {
171            if (E0 >= 0.0f && E1 >= 0.0f && E2 >= 0.0f) {
172                // barycentric coords
173                float alpha = E0 * invArea;
174                float beta  = E1 * invArea;
175                float gamma = E2 * invArea;
176
177                // lerp the attribute accross the triangle's barycentric coords
178                float lerp_inv_w   = alpha * inv_w0 + beta * inv_w1 + gamma * inv_w2;
179                float lerp_depth   = alpha * ndc_depth0 + beta * ndc_depth1 + gamma * ndc_depth2;
180                vec2f lerp_uv      = uv_over_w0 * alpha + uv_over_w1 * beta + uv_over_w2 * gamma;
181                vec3f lerped_color = alpha * c0 + beta * c1 + gamma * c2;  // TODO: not needed, sample a texture by lerp_uv here
182
183                vec3f color     = lerped_color / lerp_inv_w;
184                u32   u32_color = vec3_to_u32RGBA(color);
185                vec2f uv        = lerp_uv / lerp_inv_w;
186
187                process_fragment(x, y, lerp_depth, u32_color, window);
188            }
189
190            // increment edge functions
191            // advance right one pixel: x -> x + 1
192            E0 += stepX0;
193            E1 += stepX1;
194            E2 += stepX2;
195        }
196
197        // increment edge functions
198        // advance one row: y -> y + 1
199        // NOTE: Edge stepping for Y is subtraction since we start a triangle at top left (0, 0)
200        E0_row -= stepY0;
201        E1_row -= stepY1;
202        E2_row -= stepY2;
203    }
204}