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}