scimesh 0.3.4
Headless CPU-only 3D software renderer for scientific mesh visualization
Loading...
Searching...
No Matches
rasterizer.cpp
Go to the documentation of this file.
3#include <algorithm>
4#include <cmath>
5
6#ifdef _OPENMP
7#include <omp.h>
8#endif
9
10namespace scimesh {
11
13 : width(w), height(h), z_buffer(w * h, 1.0f), normal_buffer(w * h, Vec3(0.0f)) {}
14
16 std::fill(z_buffer.begin(), z_buffer.end(), clear_depth);
17 std::fill(normal_buffer.begin(), normal_buffer.end(), Vec3(0.0f));
18}
19
20// ---- depth buffer <-> world units ------------------------------------------
21//
22// The rasterizer stores the normalized device depth of a fragment (the z of the
23// clip-space position after the perspective divide, i.e. values in [-1, 1] with
24// -1 at the near and +1 at the far plane) - see ndc_to_screen(). World-space
25// fog and SSAO both need the distance from the camera in world units, so the
26// inverse of the projection is applied here: linear for an orthographic
27// projection, hyperbolic for a perspective one.
28static float view_distance_from_ndc(float z_ndc, float z_near, float z_far,
29 bool orthographic) {
30 if (!(z_far > z_near) || z_near <= 0.0f) {
31 // Degenerate/behind-the-camera projection: no meaningful mapping.
32 return 0.0f;
33 }
34 if (orthographic) {
35 // glm::ortho maps view z in [-n, -f] linearly onto [-1, 1]:
36 // z_ndc = -2/(f-n) * z_view - (f+n)/(f-n)
37 // => distance from camera = -z_view = (z_ndc*(f-n) + f+n) / 2
38 return (z_ndc * (z_far - z_near) + (z_far + z_near)) * 0.5f;
39 }
40 // glm::perspective maps 1/w linearly onto z_ndc, so inverting it gives the
41 // exact view-space distance of the fragment:
42 // z_ndc = (f+n)/(f-n) - 2*n*f/((f-n) * d)
43 // => d = 2*n*f / (f + n - z_ndc*(f-n))
44 const float denom = (z_far + z_near) - z_ndc * (z_far - z_near);
45 if (std::abs(denom) < 1e-12f) return z_far;
46 return (2.0f * z_near * z_far) / denom;
47}
48
50 return view_distance_from_ndc(z_ndc, z_near, z_far, orthographic);
51}
52
75static void canonical_edge_endpoints(Vec3 &a, Vec3 &b, bool &in_canonical_order) {
76 if (b.x < a.x || (b.x == a.x && b.y < a.y)) {
77 std::swap(a, b);
79 }
80}
81
82void Rasterizer::shade_and_write(int x, int y, float depth,
83 const Color &color, const Vec3 &normal,
84 const Vec3 &light_direction, Image &output,
85 bool lit) {
87 return;
88
89 int idx = y * width + x;
90 // Translucent fragments never write the depth buffer (see below), so
91 // `z_buffer` always holds the nearest *opaque* surface. Testing against it
92 // in the blended pass as well hides translucent geometry that lies behind
93 // opaque geometry, while translucent geometry in front of it still blends;
94 // the correct order among translucent fragments comes from the
95 // back-to-front sort in Renderer::render_pipeline().
96 if (depth < z_buffer[idx]) {
97 Color shaded;
98 if (!lit) {
99 // Flat color, e.g. for line layers: skip the lighting calculation.
100 shaded = color;
101 } else if (lights.empty()) {
102 shaded = shade_pixel(color, normal, light_direction,
104 } else {
105 shaded = shade_pixel_multi(color, normal, lights, ambient,
107 }
108
109 if (contrast != 1.0f) {
110 auto apply_contrast = [&](float v) {
111 return std::clamp((v - 0.5f) * contrast + 0.5f, 0.0f, 1.0f);
112 };
113 shaded.r = apply_contrast(shaded.r);
114 shaded.g = apply_contrast(shaded.g);
115 shaded.b = apply_contrast(shaded.b);
116 }
117
118 uint8_t r = static_cast<uint8_t>(std::clamp(shaded.r, 0.0f, 1.0f) * 255.0f);
119 uint8_t g = static_cast<uint8_t>(std::clamp(shaded.g, 0.0f, 1.0f) * 255.0f);
120 uint8_t b = static_cast<uint8_t>(std::clamp(shaded.b, 0.0f, 1.0f) * 255.0f);
121 uint8_t a = static_cast<uint8_t>(std::clamp(shaded.a, 0.0f, 1.0f) * 255.0f);
122
123 if (fog_enabled) {
124 // World-space fog needs the fragment distance in world units;
125 // NDC fog uses the raw depth-buffer value (legacy behaviour).
127 ? fog_depth_from_ndc(depth)
128 : depth;
129 float span = fog_end - fog_start;
130 float fog_fac;
131 if (std::abs(span) < 1e-12f) {
132 // Empty/degenerate range: hard step instead of dividing by 0.
133 fog_fac = (fog_depth >= fog_start) ? 1.0f : 0.0f;
134 } else {
136 }
137 fog_fac = std::max(0.0f, std::min(1.0f, fog_fac));
138 r = static_cast<uint8_t>(r * (1.0f - fog_fac) + fog_color.r * 255.0f * fog_fac);
139 g = static_cast<uint8_t>(g * (1.0f - fog_fac) + fog_color.g * 255.0f * fog_fac);
140 b = static_cast<uint8_t>(b * (1.0f - fog_fac) + fog_color.b * 255.0f * fog_fac);
141 a = static_cast<uint8_t>(a * (1.0f - fog_fac) + fog_color.a * 255.0f * fog_fac);
142 }
143
144 if (blend_mode) {
145 uint8_t dr, dg, db, da;
146 output.get_pixel(x, y, dr, dg, db, da);
147 float src_a = a / 255.0f;
148 float inv_a = 1.0f - src_a;
149 r = static_cast<uint8_t>(r * src_a + dr * inv_a);
150 g = static_cast<uint8_t>(g * src_a + dg * inv_a);
151 b = static_cast<uint8_t>(b * src_a + db * inv_a);
152 a = static_cast<uint8_t>(a + da * inv_a);
153 } else {
154 z_buffer[idx] = depth;
155 normal_buffer[idx] = normal;
156 }
157
158 output.set_pixel(x, y, r, g, b, a);
159 }
160}
161
163 const Vec3 &screen_v0, const Color &color0, const Vec3 &normal0, const Vec2 &uv0,
164 const Vec3 &screen_v1, const Color &color1, const Vec3 &normal1, const Vec2 &uv1,
165 const Vec3 &screen_v2, const Color &color2, const Vec3 &normal2, const Vec2 &uv2,
166 bool backface_culling,
167 bool smooth_shading,
168 const Vec3 &light_direction,
169 bool wireframe,
170 const Color &wireframe_color,
171 Image &output) {
172
173 float area = (screen_v1.x - screen_v0.x) * (screen_v2.y - screen_v0.y) -
174 (screen_v2.x - screen_v0.x) * (screen_v1.y - screen_v0.y);
175
176 if (backface_culling && area > 0.0f)
177 return;
178
179 if (std::abs(area) < 1e-12f)
180 return;
181
182 float abs_area = std::abs(area);
183
184 // Two-sided lighting. The sign of the screen-space area is the winding
185 // order, i.e. it tells which side of the triangle faces the viewer (a
186 // positive area is the back-facing case that `backface_culling` drops
187 // above). A fragment whose back side is visible AND whose stored normal
188 // points away from the viewer is shaded with that normal flipped towards the
189 // camera, instead of falling back to the ambient term only (a dark, unlit
190 // patch). This matters for double-sided geometry - like generate_plane(),
191 // whose front and back faces are exactly coplanar, so *which* of the two
192 // wins the depth test is a floating point tie - and for open surfaces seen
193 // from their back side. Only the orientation of the *normal* is corrected:
194 // a mesh whose normals disagree with its winding is left alone, so
195 // RenderOptions::invert_normals keeps its effect.
196 const bool back_facing = area > 0.0f;
197
198 float min_x = std::min({screen_v0.x, screen_v1.x, screen_v2.x});
199 float max_x = std::max({screen_v0.x, screen_v1.x, screen_v2.x});
200 float min_y = std::min({screen_v0.y, screen_v1.y, screen_v2.y});
201 float max_y = std::max({screen_v0.y, screen_v1.y, screen_v2.y});
202
203 int x_start = std::max(0, static_cast<int>(std::floor(min_x)));
204 int x_end = std::min(width - 1, static_cast<int>(std::ceil(max_x)));
205 int y_start = std::max(0, static_cast<int>(std::floor(min_y)));
206 int y_end = std::min(height - 1, static_cast<int>(std::ceil(max_y)));
207
208 float inv_area = 1.0f / area;
209
210 // The three edge functions, evaluated in a canonical endpoint order (see
211 // canonical_edge_endpoints()). `own<i>` states whether the triangle walks
212 // along the edge opposite vertex <i> in the canonical direction: it undoes
213 // the canonicalization in the barycentric weight (`k<i>`), and it decides
214 // the fill rule for a pixel that lies exactly on that edge.
215 Vec3 e0_a = screen_v1, e0_b = screen_v2; bool own0 = true;
216 Vec3 e1_a = screen_v2, e1_b = screen_v0; bool own1 = true;
217 Vec3 e2_a = screen_v0, e2_b = screen_v1; bool own2 = true;
218 canonical_edge_endpoints(e0_a, e0_b, own0);
219 canonical_edge_endpoints(e1_a, e1_b, own1);
220 canonical_edge_endpoints(e2_a, e2_b, own2);
221 const float k0 = own0 ? inv_area : -inv_area;
222 const float k1 = own1 ? inv_area : -inv_area;
223 const float k2 = own2 ? inv_area : -inv_area;
224
225 float wire_thresh = 0.0f;
226 if (wireframe) {
227 wire_thresh = 1.5f / std::sqrt(abs_area > 1e-9f ? abs_area : 1.0f);
228 if (wire_thresh > 0.5f) wire_thresh = 0.5f;
229 }
230
231#ifdef _OPENMP
232#pragma omp parallel for if((y_end - y_start) > 16) schedule(static)
233#endif
234 for (int y = y_start; y <= y_end; ++y) {
235 for (int x = x_start; x <= x_end; ++x) {
236 float px = static_cast<float>(x) + 0.5f;
237 float py = static_cast<float>(y) + 0.5f;
238
239 // Barycentric weights, one per vertex: w0 belongs to screen_v0 and
240 // is the normalized edge function of the opposite edge (v1 -> v2),
241 // and so on. Each weight is derived from its own edge function (and
242 // not as 1 - w0 - w1), because only an edge function gives two
243 // triangles that share the edge the same value - and hence exactly
244 // opposite signs, see canonical_edge_endpoints(). The weights
245 // therefore sum to 1 only up to a few ULP, which is irrelevant for
246 // the interpolation.
247 float w0 = k0 * ((e0_a.x - px) * (e0_b.y - py) -
248 (e0_b.x - px) * (e0_a.y - py));
249 float w1 = k1 * ((e1_a.x - px) * (e1_b.y - py) -
250 (e1_b.x - px) * (e1_a.y - py));
251 float w2 = k2 * ((e2_a.x - px) * (e2_b.y - py) -
252 (e2_b.x - px) * (e2_a.y - py));
253
254 if (w0 < 0.0f || w1 < 0.0f || w2 < 0.0f)
255 continue;
256
257 // Fill rule: a pixel lying exactly on an edge is rasterized by the
258 // triangle that walks along that edge in the canonical direction, so
259 // that exactly one of the triangles sharing the edge covers it.
260 if (w0 == 0.0f && !own0)
261 continue;
262 if (w1 == 0.0f && !own1)
263 continue;
264 if (w2 == 0.0f && !own2)
265 continue;
266
267 if (wireframe) {
268 if (w0 >= wire_thresh && w1 >= wire_thresh && w2 >= wire_thresh)
269 continue;
270 float depth = w0 * screen_v0.z + w1 * screen_v1.z + w2 * screen_v2.z;
271 int pidx = y * width + x;
272 output.set_pixel(x, y,
273 static_cast<uint8_t>(std::clamp(wireframe_color.r, 0.0f, 1.0f) * 255.0f),
274 static_cast<uint8_t>(std::clamp(wireframe_color.g, 0.0f, 1.0f) * 255.0f),
275 static_cast<uint8_t>(std::clamp(wireframe_color.b, 0.0f, 1.0f) * 255.0f),
276 static_cast<uint8_t>(std::clamp(wireframe_color.a, 0.0f, 1.0f) * 255.0f));
277 if (!blend_mode) {
278 z_buffer[pidx] = depth;
280 if (back_facing && wf_normal.z < 0.0f) wf_normal = -wf_normal;
282 }
283 continue;
284 }
285
286 float depth = w0 * screen_v0.z + w1 * screen_v1.z + w2 * screen_v2.z;
287
290 if (smooth_shading) {
292 w0 * color0.r + w1 * color1.r + w2 * color2.r,
293 w0 * color0.g + w1 * color1.g + w2 * color2.g,
294 w0 * color0.b + w1 * color1.b + w2 * color2.b,
295 w0 * color0.a + w1 * color1.a + w2 * color2.a);
297 } else {
300 }
301
302 // Shading normal of a visible back side (see the two-sided lighting
303 // note above the pixel loop).
304 if (back_facing && interp_normal.z < 0.0f) {
306 }
307
308 if (active_texture) {
310 ? w0 * uv0 + w1 * uv1 + w2 * uv2
311 : uv0;
312 // Mesh UVs are image-space coordinates (v = 0 is the first row
313 // of the texture image, i.e. its top edge), exactly like the
314 // arguments of sample_bilinear(): there is no flip here. UVs
315 // from OBJ/PLY/rgl files use the opposite, bottom-left origin,
316 // so they have to be converted once with flip_uvs() (see
317 // Mesh::uvs and examples/cpp/spot_cow).
320 base_color.b * tex.b, base_color.a * tex.a);
321 }
322
323 shade_and_write(x, y, depth, base_color, interp_normal, light_direction, output);
324 }
325 }
326}
327
328void Rasterizer::rasterize_point(float screen_x, float screen_y, float depth,
329 float radius, const Color &color,
330 const Vec3 &normal, const Vec3 &light_direction,
331 Image &output) {
332 int cx = static_cast<int>(std::floor(screen_x));
333 int cy = static_cast<int>(std::floor(screen_y));
334 int r = static_cast<int>(std::ceil(radius));
335 float r_sq = radius * radius;
336
337 for (int dy = -r; dy <= r; ++dy) {
338 int py = cy + dy;
339 if (py < 0 || py >= height) continue;
340 for (int dx = -r; dx <= r; ++dx) {
341 int px = cx + dx;
342 if (px < 0 || px >= width) continue;
343 if (static_cast<float>(dx*dx + dy*dy) > r_sq) continue;
344 shade_and_write(px, py, depth, color, normal, light_direction, output);
345 }
346 }
347}
348
350 const Vec3 &screen_v1, const Color &color1,
351 float width, bool lit, const Vec3 &normal,
352 const Vec3 &light_direction, Image &output) {
353 const float dx = screen_v1.x - screen_v0.x;
354 const float dy = screen_v1.y - screen_v0.y;
355
356 // NOTE: the parameter `width` (line width in pixels) shadows the
357 // Rasterizer::width member (image width), so the members have to be
358 // qualified explicitly in the bounds checks below.
359 const int img_width = this->width;
360 const int img_height = this->height;
361
362 // Walk along the dominant screen axis, one sample per device pixel, so that
363 // the stamps of consecutive samples overlap without gaps.
364 const float span = std::max(std::abs(dx), std::abs(dy));
365 const int steps = std::max(1, static_cast<int>(std::ceil(span)));
366
367 // The stamp is a disc, like rasterize_point(). `half_extent` is how many
368 // whole pixels the stamp reaches from the sample point, `r_sq` is the
369 // squared stamp radius used for the coverage test.
370 const float stamp_radius = std::max(0.5f, width * 0.5f);
371 const int half_extent = static_cast<int>(std::ceil(stamp_radius - 0.5f));
372 const float r_sq = stamp_radius * stamp_radius;
373
374 for (int i = 0; i <= steps; ++i) {
375 const float t = static_cast<float>(i) / static_cast<float>(steps);
376 const float sx = screen_v0.x + t * dx;
377 const float sy = screen_v0.y + t * dy;
378 const float depth = screen_v0.z + t * (screen_v1.z - screen_v0.z);
379 const Color c(color0.r + t * (color1.r - color0.r),
380 color0.g + t * (color1.g - color0.g),
381 color0.b + t * (color1.b - color0.b),
382 color0.a + t * (color1.a - color0.a));
383 const int cx = static_cast<int>(std::lround(sx));
384 const int cy = static_cast<int>(std::lround(sy));
385
386 for (int py = cy - half_extent; py <= cy + half_extent; ++py) {
388 continue;
389 }
390 for (int px = cx - half_extent; px <= cx + half_extent; ++px) {
392 continue;
393 }
394 const float ox = static_cast<float>(px - cx);
395 const float oy = static_cast<float>(py - cy);
396 if (ox * ox + oy * oy > r_sq) {
397 continue;
398 }
399 shade_and_write(px, py, depth, c, normal, light_direction,
400 output, lit);
401 }
402 }
403 }
404}
405
406
407// Screen-space ambient occlusion. The depth buffer holds NDC z in [-1, 1]
408// (see view_distance_from_ndc() above); pass the camera's near and far clipping
409// planes, and set `orthographic` for a parallel projection.
410void Rasterizer::apply_ssao(Image &output, float z_near, float z_far) {
411 if (!ssao_enabled || width < 2 || height < 2) return;
412
413 // --- TUNABLE PARAMETERS (in linear world units, e.g. meters) ---
414 // How far a sample must jut out to cast a shadow (fixes ground plane acne)
415 const float depth_bias = 0.05f;
416
417 // Max distance before we assume it's a different object (fixes skybox/cow halo)
418 const float max_occlusion_distance = 1.5f;
419
420 // Fixed normalized sample directions (8 samples on a spiral)
421 const int ns = 8;
422 const float dirs[ns][2] = {
423 { 0.309f, 0.951f}, {-0.809f, 0.588f}, { 1.000f, -0.000f}, { 0.809f, -0.588f},
424 {-0.309f, -0.951f}, { 0.588f, 0.809f}, {-0.588f, 0.809f}, {-1.000f, -0.000f},
425 };
426
427 // Fast inline lambda for depth linearization. The depth buffer holds NDC z
428 // ([-1, 1]); view_distance_from_ndc() inverts the projection (perspective or
429 // orthographic) to world units.
430 auto linearize = [&](float raw_z) {
431 return view_distance_from_ndc(raw_z, z_near, z_far, orthographic);
432 };
433
434 for (int y = 0; y < height; ++y) {
435 for (int x = 0; x < width; ++x) {
436 int idx = y * width + x;
438
439 // Skip background pixels
440 if (center_depth_raw >= 1.0f) continue;
441
442 // Convert center depth to actual world units
444
446 if (center_normal.x == 0.0f && center_normal.y == 0.0f && center_normal.z == 0.0f) continue;
447
448 center_normal = glm::normalize(center_normal);
449
450 // `ssao_radius` is documented as a screen-space radius in pixels.
451 int screen_radius = static_cast<int>(std::lround(ssao_radius));
452 screen_radius = std::max(1, std::min(screen_radius, 100)); // Clamp to sane bounds
453
454 float occlusion = 0.0f;
455 for (int s = 0; s < ns; ++s) {
456 int sx = x + static_cast<int>(dirs[s][0] * screen_radius);
457 int sy = y + static_cast<int>(dirs[s][1] * screen_radius);
458
459 // Bounds check
461 continue;
462 }
463
464 float sample_depth_raw = z_buffer[sy * width + sx];
466
467 // Delta: Positive means the sample is CLOSER to the camera than the center
469
470 // 1. Bias Check: Is it actually jutting out, or is it just a tilted flat plane?
471 if (depth_delta > depth_bias) {
472
473 // 2. Range Check: Smooth falloff to prevent halos across large gaps
475
476 // Only add occlusion if it's within the max distance
477 if (range_falloff > 0.0f) {
478 // 3. Hemisphere check: only count samples that lie on the
479 // side of the surface the normal points to. The offset
480 // is built in a common scale: x/y as screen pixels, z from
481 // the world-unit depth difference (a closer sample has a
482 // positive delta, i.e. it sits on the camera side). The
483 // normal comes from the normal buffer and is in view space.
485 dirs[s][0] * screen_radius,
486 -dirs[s][1] * screen_radius,
487 depth_delta * (width + height) * 0.1f);
488 offset_dir = glm::normalize(offset_dir);
489 float hem = glm::dot(center_normal, offset_dir);
490 if (hem > 0.0f) {
492 }
493 }
494 }
495 }
496
497 // Calculate final AO and apply intensity
498 float ao = 1.0f - (ssao_intensity * (occlusion / static_cast<float>(ns)));
499 ao = std::max(0.0f, std::min(1.0f, ao));
500
501 // Apply to color buffer
502 if (ao < 1.0f) {
503 uint8_t r, g, b, a;
504 output.get_pixel(x, y, r, g, b, a);
505 r = static_cast<uint8_t>(r * ao);
506 g = static_cast<uint8_t>(g * ao);
507 b = static_cast<uint8_t>(b * ao);
508 output.set_pixel(x, y, r, g, b, a);
509 }
510 }
511 }
512}
513} // namespace scimesh
Low-level math utilities for the rendering pipeline.
glm::vec2 Vec2
2-component floating-point vector (xy).
Definition types.h:32
glm::vec3 Vec3
3-component floating-point vector (xyz).
Definition types.h:46
Color shade_pixel(const Color &base_color, const Vec3 &normal, const Vec3 &light_direction, const Color &specular_color=Color(0, 0, 0, 0), float shininess=0.0f)
Compute the shaded color of a pixel with a single directional light.
Definition math_utils.h:191
Color shade_pixel_multi(const Color &base_color, const Vec3 &normal, const std::vector< Light > &lights, float ambient, const Color &specular_color=Color(0, 0, 0, 0), float shininess=0.0f)
Compute the shaded color with multiple light sources.
Definition math_utils.h:231
@ WORLD
World units: distance from the camera, measured along the camera's viewing direction.
The Rasterizer — the per-pixel rendering engine.
Vec3 normal2
Definition renderer.cpp:24
Vec3 screen_v2
Definition renderer.cpp:22
Vec3 normal1
Definition renderer.cpp:24
Vec3 normal0
Definition renderer.cpp:24
Vec3 screen_v1
Definition renderer.cpp:22
Vec2 uv1
Definition renderer.cpp:25
Color color0
Definition renderer.cpp:23
bool lit
Definition renderer.cpp:37
Vec2 uv0
Definition renderer.cpp:25
Color color1
Definition renderer.cpp:23
Vec3 screen_v0
Definition renderer.cpp:22
Vec2 uv2
Definition renderer.cpp:25
float width
Definition renderer.cpp:36
Color color2
Definition renderer.cpp:23
An RGBA color with floating-point components.
Definition types.h:88
float g
Green channel, [0, 1].
Definition types.h:90
float r
Red channel, [0, 1].
Definition types.h:89
float b
Blue channel, [0, 1].
Definition types.h:91
float a
Alpha (opacity) channel, [0, 1]. 1.0 = fully opaque.
Definition types.h:92
A 2D RGBA image buffer.
Definition image.h:92
Color sample_bilinear(float u, float v) const
Sample the image at texture coordinates (u, v) using bilinear interpolation.
Definition image.cpp:351
void rasterize_line(const Vec3 &screen_v0, const Color &color0, const Vec3 &screen_v1, const Color &color1, float width, bool lit, const Vec3 &normal, const Vec3 &light_direction, Image &output)
Rasterize a line segment with a screen-space width.
float ssao_radius
SSAO sample radius in pixels (default: 16).
Definition rasterizer.h:172
void clear(float clear_depth=1.0f)
Clear the depth and normal buffers.
void rasterize_point(float screen_x, float screen_y, float depth, float radius, const Color &color, const Vec3 &normal, const Vec3 &light_direction, Image &output)
Rasterize a single point (filled circle) into the output image.
float fog_end
Distance where fog is fully opaque (default: 1).
Definition rasterizer.h:138
Color fog_color
The fog color (what distant objects blend into).
Definition rasterizer.h:161
bool fog_enabled
Enable depth fog (default: false).
Definition rasterizer.h:128
FogSpace fog_space
The space (and unit) of fog_start / fog_end (default: FogSpace::WORLD).
Definition rasterizer.h:144
float z_far
Far plane distance of the current projection (default: 10000.0).
Definition rasterizer.h:154
float fog_depth_from_ndc(float z_ndc) const
Convert a depth-buffer value to a distance in world units.
float fog_start
Distance where fog begins (default: 0).
Definition rasterizer.h:133
bool orthographic
Whether the current projection is orthographic (default: false).
Definition rasterizer.h:158
Rasterizer(int w, int h)
Construct a rasterizer for the given output size.
void apply_ssao(Image &output, float z_near, float z_far)
Apply screen-space ambient occlusion to the output image.
int height
Output image height in pixels.
Definition rasterizer.h:53
Image * active_texture
Optional texture image for textured meshes.
Definition rasterizer.h:331
float ssao_intensity
SSAO darkening intensity (0.0–1.0, default: 0.8).
Definition rasterizer.h:175
float ambient
Ambient light level (0.0–1.0, default: 0.3).
Definition rasterizer.h:112
int width
Output image width in pixels.
Definition rasterizer.h:50
Color specular_color
Specular highlight color.
Definition rasterizer.h:91
std::vector< Light > lights
Light sources for Blinn-Phong shading.
Definition rasterizer.h:107
float shininess
Shininess exponent (Phong model).
Definition rasterizer.h:96
void rasterize_triangle(const Vec3 &screen_v0, const Color &color0, const Vec3 &normal0, const Vec2 &uv0, const Vec3 &screen_v1, const Color &color1, const Vec3 &normal1, const Vec2 &uv1, const Vec3 &screen_v2, const Color &color2, const Vec3 &normal2, const Vec2 &uv2, bool backface_culling, bool smooth_shading, const Vec3 &light_direction, bool wireframe, const Color &wireframe_color, Image &output)
Rasterize a single triangle into the output image.
bool ssao_enabled
Enable SSAO (default: false).
Definition rasterizer.h:169
float z_near
Near plane distance of the current projection (default: 0.1).
Definition rasterizer.h:149
std::vector< float > z_buffer
Z-buffer (depth buffer), one float per pixel.
Definition rasterizer.h:64
bool blend_mode
Enable alpha blending for transparent triangles (default: false).
Definition rasterizer.h:81
float contrast
Contrast adjustment (1.0 = no change).
Definition rasterizer.h:120
std::vector< Vec3 > normal_buffer
Normal buffer, one Vec3 per pixel.
Definition rasterizer.h:69
int y
Top edge of the bitmap, in image pixels.
Definition text.cpp:224
int x
Left edge of the bitmap, in image pixels.
Definition text.cpp:223