scimesh 0.3.4
Headless CPU-only 3D software renderer for scientific mesh visualization
Loading...
Searching...
No Matches
renderer.cpp
Go to the documentation of this file.
1#include <scimesh/renderer.h>
3#include <scimesh/normals.h>
4#include <scimesh/clipping.h>
6#include <scimesh/text.h>
7#include <glm/glm.hpp>
8#include <glm/gtc/matrix_transform.hpp>
9#include <algorithm>
10#include <stdexcept>
11#include <string>
12
13#ifdef _OPENMP
14#include <omp.h>
15#endif
16
17namespace scimesh {
18
19namespace {
20
21struct DeferredTri {
26 bool smooth;
29 float view_z;
30};
31
33struct DeferredLine {
35 Color color0, color1;
36 float width;
37 bool lit;
39 float view_z;
40};
41
46inline float signed_distance_near_plane(const Vec3 &p_view, float near_plane) {
47 return -near_plane - p_view.z;
48}
49
54inline float signed_distance_clip_plane(const Vec3 &p_view, const ClipPlane &plane) {
55 return glm::dot(plane.normal, p_view) + plane.offset;
56}
57
70bool clip_segment_view(const Vec3 &v0, const Vec3 &v1, float near_plane,
71 const std::vector<ClipPlane> &clip_planes,
72 float &t0, float &t1) {
73 t0 = 0.0f;
74 t1 = 1.0f;
75
76 // Clip against a single plane given the signed distances of the endpoints.
77 auto clip_against = [&](float d0, float d1) -> bool {
78 const bool inside0 = d0 >= 0.0f;
79 const bool inside1 = d1 >= 0.0f;
80 if (!inside0 && !inside1) {
81 return false;
82 }
83 if (inside0 && inside1) {
84 return true;
85 }
86 const float denom = d0 - d1;
87 if (std::abs(denom) < 1e-12f) {
88 return true;
89 }
90 const float t = d0 / denom;
91 if (inside0) {
92 t1 = std::min(t1, t);
93 } else {
94 t0 = std::max(t0, t);
95 }
96 return t0 <= t1;
97 };
98
99 if (!clip_against(signed_distance_near_plane(v0, near_plane),
100 signed_distance_near_plane(v1, near_plane))) {
101 return false;
102 }
103 for (const ClipPlane &plane : clip_planes) {
106 return false;
107 }
108 }
109 return true;
110}
111
112} // anonymous namespace
113
115 if (options.width <= 0 || options.height <= 0) {
116 throw std::invalid_argument("RenderOptions width and height must be > 0");
117 }
118 int aa = std::max(1, options.aa_samples);
119 Image internal(options.width * aa, options.height * aa);
120 std::vector<SceneNodeRef> nodes;
121 nodes.push_back({&mesh, Mat4(1.0f), ""});
122 render_pipeline(nodes, {}, {}, camera, options, internal);
123 return internal.downsample_box(aa);
124}
125
127 if (options.width <= 0 || options.height <= 0) {
128 throw std::invalid_argument("RenderOptions width and height must be > 0");
129 }
130 int aa = std::max(1, options.aa_samples);
131 Image internal(options.width * aa, options.height * aa);
132 std::vector<SceneNodeRef> nodes = scene.nodes();
133 render_pipeline(nodes, scene.line_nodes(), scene.text_nodes(), camera, options, internal);
134 return internal.downsample_box(aa);
135}
136
137Image Renderer::render_triangles_raw(const std::vector<Vec3> &positions,
138 const std::vector<Color> &colors,
139 const Camera &camera,
140 const RenderOptions &options) {
141 Mesh mesh;
142 mesh.vertices = positions;
143 mesh.colors = colors;
144 int nv = static_cast<int>(positions.size());
145 for (int i = 0; i < nv / 3; i++) {
146 mesh.triangles.push_back({
147 static_cast<uint32_t>(i * 3),
148 static_cast<uint32_t>(i * 3 + 1),
149 static_cast<uint32_t>(i * 3 + 2)});
150 }
151 for (const auto &c : colors) {
152 if (c.a < 1.0f - 1e-6f) {
153 mesh.has_transparency = true;
154 break;
155 }
156 }
157 return render_mesh(mesh, camera, options);
158}
159
160Image Renderer::render_points_raw(const std::vector<Vec3> &positions,
161 const std::vector<Color> &colors,
162 float radius,
163 const Camera &camera,
164 const RenderOptions &options) {
165 if (options.width <= 0 || options.height <= 0) {
166 throw std::invalid_argument("RenderOptions width and height must be > 0");
167 }
168 int np = static_cast<int>(positions.size());
169 if (np == 0 || colors.empty()) {
170 Image img(options.width, options.height);
171 img.clear_float(options.background_color.r, options.background_color.g,
172 options.background_color.b, options.background_color.a);
173 return img;
174 }
175
176 int aa = std::max(1, options.aa_samples);
177 Image output(options.width * aa, options.height * aa);
178 output.clear_float(options.background_color.r, options.background_color.g,
179 options.background_color.b, options.background_color.a);
180
181 Rasterizer rasterizer(output.width, output.height);
182 rasterizer.clear(1.0f);
183 rasterizer.specular_color = options.specular_color;
184 rasterizer.shininess = options.shininess;
185 rasterizer.lights = options.lights;
186 rasterizer.ambient = options.ambient;
187 rasterizer.fog_enabled = options.fog_enabled;
188 rasterizer.fog_start = options.fog_start;
189 rasterizer.fog_end = options.fog_end;
190 rasterizer.fog_color = options.fog_color;
191 rasterizer.fog_space = options.fog_space;
192 rasterizer.z_near = options.near_plane;
193 rasterizer.z_far = options.far_plane;
194 rasterizer.orthographic = (options.projection == ProjectionType::ORTHOGRAPHIC);
195 rasterizer.ssao_enabled = options.ssao_enabled;
196 rasterizer.ssao_radius = options.ssao_radius;
197 rasterizer.ssao_intensity = options.ssao_intensity;
198
199#ifdef _OPENMP
200 if (options.threads > 0) omp_set_num_threads(options.threads);
201#endif
202
203 Mat4 view = camera.get_view_matrix();
204 float aspect = static_cast<float>(output.width) / static_cast<float>(output.height);
206 proj_cam.projection = options.projection;
207 Mat4 projection = proj_cam.get_projection_matrix(aspect, options.near_plane, options.far_plane);
208 Mat4 view_projection = projection * view;
209
210 Vec3 light_direction = Vec3(0.0f, 0.0f, 1.0f);
211 if (!options.lights.empty()) {
212 light_direction = options.lights[0].position;
214 }
215
216 float aa_radius = radius * static_cast<float>(aa);
217
218 for (int i = 0; i < np; i++) {
221 float sx, sy, sz;
222 ndc_to_screen(ndc, output.width, output.height, sx, sy, sz);
223
224 Vec3 normal(0.0f, 0.0f, 1.0f);
225 rasterizer.rasterize_point(sx, sy, sz, aa_radius, colors[i],
226 normal, light_direction, output);
227 }
228
229 if (rasterizer.ssao_enabled) {
230 rasterizer.apply_ssao(output, options.near_plane, options.far_plane);
231 }
232
233 return output.downsample_box(aa);
234}
235
236Image Renderer::render_lines_raw(const std::vector<Vec3> &from,
237 const std::vector<Vec3> &to,
238 const std::vector<Color> &colors,
239 float width,
240 const Camera &camera,
241 const RenderOptions &options) {
242 if (options.width <= 0 || options.height <= 0) {
243 throw std::invalid_argument("RenderOptions width and height must be > 0");
244 }
245
246 // A raw line render is simply a scene with a single line layer and no
247 // meshes, so there is only one code path for line rendering.
248 LineLayer layer;
249 layer.from = from;
250 layer.to = to;
251 layer.colors = colors;
252 layer.width = width;
253
254 Scene scene;
255 scene.add_lines(layer);
256
257 int aa = std::max(1, options.aa_samples);
258 Image internal(options.width * aa, options.height * aa);
259 std::vector<SceneNodeRef> no_meshes;
260 render_pipeline(no_meshes, scene.line_nodes(), {}, camera, options, internal);
261 return internal.downsample_box(aa);
262}
263
264void Renderer::render_pipeline(const std::vector<SceneNodeRef> &nodes,
265 const std::vector<LineNodeRef> &line_nodes,
266 const std::vector<TextNodeRef> &text_nodes,
267 const Camera &camera,
268 const RenderOptions &options,
269 Image &output) {
270 // Validate all non-empty meshes before rendering
271 for (size_t i = 0; i < nodes.size(); ++i) {
272 const auto *mp = nodes[i].mesh;
273 if (mp->empty()) continue; // empty is harmless
274 if (!mp->is_valid()) {
275 throw std::invalid_argument(
276 "Mesh " + std::to_string(i) + " failed validation: "
277 "check indices, vertex data, and array sizes");
278 }
279 }
280 output.clear_float(options.background_color.r, options.background_color.g,
281 options.background_color.b, options.background_color.a);
282
283 Rasterizer rasterizer(output.width, output.height);
284 rasterizer.clear(1.0f);
285 rasterizer.specular_color = options.specular_color;
286 rasterizer.shininess = options.shininess;
287 rasterizer.lights = options.lights;
288 rasterizer.ambient = options.ambient;
289 rasterizer.contrast = options.contrast;
290 rasterizer.fog_enabled = options.fog_enabled;
291 rasterizer.fog_start = options.fog_start;
292 rasterizer.fog_end = options.fog_end;
293 rasterizer.fog_color = options.fog_color;
294 rasterizer.fog_space = options.fog_space;
295 rasterizer.z_near = options.near_plane;
296 rasterizer.z_far = options.far_plane;
297 rasterizer.orthographic = (options.projection == ProjectionType::ORTHOGRAPHIC);
298 rasterizer.ssao_enabled = options.ssao_enabled;
299 rasterizer.ssao_radius = options.ssao_radius;
300 rasterizer.ssao_intensity = options.ssao_intensity;
301
302#ifdef _OPENMP
303 if (options.threads > 0) omp_set_num_threads(options.threads);
304#endif
305
306 Mat4 view = camera.get_view_matrix();
307
308 for (auto &light : rasterizer.lights) {
309 light.position = glm::normalize(transform_direction(view, light.position));
310 }
311 float aspect = static_cast<float>(output.width) / static_cast<float>(output.height);
312 Camera proj_cam = camera;
313 proj_cam.projection = options.projection;
314 Mat4 projection = proj_cam.get_projection_matrix(aspect, options.near_plane, options.far_plane);
315 Mat4 view_projection = projection * view;
316
317 // User clip planes default to world space; clipping itself happens in
318 // view space, so convert them here (see ClipPlane::space).
319 std::vector<ClipPlane> view_clip_planes;
320 view_clip_planes.reserve(options.clip_planes.size());
321 for (const auto &cp : options.clip_planes) {
322 view_clip_planes.push_back(
324 }
325
326 Vec3 light_direction = Vec3(0.0f, 0.0f, 1.0f);
327
328 std::vector<DeferredTri> deferred;
329 std::vector<DeferredLine> deferred_lines;
330
331 for (const auto &node : nodes) {
332 const Mesh &mesh = *node.mesh;
333 if (mesh.empty()) continue;
334
335 // Translucent meshes are deferred to the blended pass. This is derived
336 // from the mesh's colors (and default_color), so callers no longer have
337 // to keep Mesh::has_transparency in sync by hand.
338 const bool mesh_transparent = mesh.is_transparent();
339
340 // Placement transform for this mesh (model matrix in world space).
341 const Mat4 &model = node.transform;
342 const Mat4 view_model = view * model;
344
345 if (mesh.has_uvs() && mesh.has_texture()) {
346 rasterizer.active_texture = const_cast<Image *>(&mesh.texture);
347 } else {
348 rasterizer.active_texture = nullptr;
349 }
350
351 std::vector<Vec3> computed_normals;
352 const std::vector<Vec3> *normals_ptr;
353 if (mesh.has_normals()) {
354 normals_ptr = &mesh.normals;
355 } else {
358 }
359
360 std::vector<Vec3> view_normals(normals_ptr->size());
361 for (size_t i = 0; i < normals_ptr->size(); ++i) {
362 Vec3 n = (*normals_ptr)[i];
363 if (options.invert_normals) n = -n;
364 view_normals[i] = glm::normalize(
366 }
367
368 for (int ti = 0; ti < static_cast<int>(mesh.triangles.size()); ++ti) {
369 const auto &tri = mesh.triangles[ti];
370 Vec3 v0 = mesh.vertices[tri.v0];
371 Vec3 v1 = mesh.vertices[tri.v1];
372 Vec3 v2 = mesh.vertices[tri.v2];
373
374 Color c0, c1, c2;
375 if (mesh.has_face_colors()) {
376 c0 = c1 = c2 = mesh.face_colors[ti];
377 } else if (mesh.has_colors()) {
378 c0 = mesh.colors[tri.v0];
379 c1 = mesh.colors[tri.v1];
380 c2 = mesh.colors[tri.v2];
381 } else {
382 c0 = c1 = c2 = options.default_color;
383 }
384
385 Vec3 n0 = view_normals[tri.v0];
386 Vec3 n1 = view_normals[tri.v1];
387 Vec3 n2 = view_normals[tri.v2];
388
389 Vec2 uv0 = mesh.has_uvs() ? mesh.uvs[tri.v0] : Vec2(0, 0);
390 Vec2 uv1 = mesh.has_uvs() ? mesh.uvs[tri.v1] : Vec2(0, 0);
391 Vec2 uv2 = mesh.has_uvs() ? mesh.uvs[tri.v2] : Vec2(0, 0);
392
394 (c0.a < 1.0f - 1e-6f || c1.a < 1.0f - 1e-6f || c2.a < 1.0f - 1e-6f);
395
396 ClipVertex cv0, cv1, cv2;
398 cv0.color = c0; cv0.normal = n0; cv0.uv = uv0;
400 cv1.color = c1; cv1.normal = n1; cv1.uv = uv1;
402 cv2.color = c2; cv2.normal = n2; cv2.uv = uv2;
403
404 bool has_user_clips = !view_clip_planes.empty();
405
406 std::vector<ClipVertex> clipped_vertices;
407 std::vector<Triangle> clipped_triangles;
408
409 if (has_user_clips) {
413
414 std::vector<ClipVertex> view_clipped;
415 std::vector<Triangle> view_clip_tris;
417 vv0, vv1, vv2, n0, n1, n2, c0, c1, c2,
418 uv0, uv1, uv2,
420
421 for (int ci = 1; ci < static_cast<int>(view_clip_planes.size()) && vc_count > 0; ++ci) {
422 std::vector<ClipVertex> next_vertices;
423 std::vector<Triangle> next_triangles;
424 for (const auto &vt : view_clip_tris) {
425 const ClipVertex &a = view_clipped[vt.v0];
426 const ClipVertex &b = view_clipped[vt.v1];
427 const ClipVertex &c = view_clipped[vt.v2];
428 Vec3 pa(a.position.x, a.position.y, a.position.z);
429 Vec3 pb(b.position.x, b.position.y, b.position.z);
430 Vec3 pc(c.position.x, c.position.y, c.position.z);
431 Vec3 na(a.normal), nb(b.normal), nc(c.normal);
432 Color ca(a.color), cb(b.color), cc(c.color);
433 Vec2 ua(a.uv), ub(b.uv), uc(c.uv);
435 pa, pb, pc, na, nb, nc, ca, cb, cc,
436 ua, ub, uc,
438 }
441 vc_count = static_cast<int>(view_clip_tris.size());
442 if (vc_count == 0) break;
443 }
444
445 if (vc_count == 0) continue;
446
447 clipped_vertices.clear();
448 clipped_triangles.clear();
449 for (const auto &vt : view_clip_tris) {
450 const ClipVertex &a = view_clipped[vt.v0];
451 const ClipVertex &b = view_clipped[vt.v1];
452 const ClipVertex &c = view_clipped[vt.v2];
453
454 ClipVertex cva, cvb, cvc;
455 cva.position = projection * Vec4(a.position.x, a.position.y, a.position.z, 1.0f);
456 cva.color = a.color; cva.normal = a.normal;
457 cvb.position = projection * Vec4(b.position.x, b.position.y, b.position.z, 1.0f);
458 cvb.color = b.color; cvb.normal = b.normal;
459 cvc.position = projection * Vec4(c.position.x, c.position.y, c.position.z, 1.0f);
460 cvc.color = c.color; cvc.normal = c.normal;
461
464 (void)nc;
465 }
466 if (clipped_triangles.empty()) continue;
467 } else {
468 clipped_vertices.clear();
469 clipped_triangles.clear();
472 if (num_clipped == 0) continue;
473 }
474
475 bool smooth = (options.shading == ShadingMode::SMOOTH);
476
477 for (const auto &ct : clipped_triangles) {
478 const ClipVertex &cv_a = clipped_vertices[ct.v0];
479 const ClipVertex &cv_b = clipped_vertices[ct.v1];
480 const ClipVertex &cv_c = clipped_vertices[ct.v2];
481
482 Vec3 ndc0 = perspective_divide(cv_a.position);
483 Vec3 ndc1 = perspective_divide(cv_b.position);
484 Vec3 ndc2 = perspective_divide(cv_c.position);
485
486 float sx0, sy0, sz0, sx1, sy1, sz1, sx2, sy2, sz2;
487 ndc_to_screen(ndc0, output.width, output.height, sx0, sy0, sz0);
488 ndc_to_screen(ndc1, output.width, output.height, sx1, sy1, sz1);
489 ndc_to_screen(ndc2, output.width, output.height, sx2, sy2, sz2);
490
494
496 if (!smooth) {
498 transform_point(view_model, mesh.vertices[tri.v0]),
499 transform_point(view_model, mesh.vertices[tri.v1]),
500 transform_point(view_model, mesh.vertices[tri.v2]));
502 }
503
504 const Vec3 &normal_a = smooth ? cv_a.normal : flat_normal_a;
505 const Vec3 &normal_b = smooth ? cv_b.normal : flat_normal_b;
506 const Vec3 &normal_c = smooth ? cv_c.normal : flat_normal_c;
507
508 if (tri_transparent) {
511 transform_point(view_model, v2)) * (1.0f / 3.0f);
512 deferred.push_back({
514 cv_a.color, cv_b.color, cv_c.color,
516 cv_a.uv, cv_b.uv, cv_c.uv,
518 });
519 } else {
520 rasterizer.rasterize_triangle(
521 screen_v0, cv_a.color, normal_a, cv_a.uv,
522 screen_v1, cv_b.color, normal_b, cv_b.uv,
523 screen_v2, cv_c.color, normal_c, cv_c.uv,
524 options.backface_culling, smooth,
526 options.wireframe, options.wireframe_color,
527 output);
528 }
529 }
530 }
531 }
532
533 // ---- Line layers -------------------------------------------------------
534 // Line layers are drawn after the meshes, against the same depth buffer:
535 // opaque lines are occluded by meshes in front of them and occlude meshes
536 // behind them, translucent lines are deferred to the blended pass below.
537 // The output image is supersampled by `aa_samples`, so the screen-space
538 // line width has to be scaled by the same factor (like the point radius).
539 const float line_width_scale =
540 static_cast<float>(output.width) /
541 static_cast<float>(std::max(1, options.width));
542 for (const auto &line_node : line_nodes) {
543 const LineLayer &layer = *line_node.layer;
544 if (layer.empty()) {
545 continue;
546 }
547
548 const Mat4 view_model = view * line_node.transform;
549 const float width = std::max(0.5f, layer.width) * line_width_scale;
550 const size_t num_segments = layer.size();
551
552 for (size_t i = 0; i < num_segments; ++i) {
553 const Vec3 v0 = transform_point(view_model, layer.from[i]);
554 const Vec3 v1 = transform_point(view_model, layer.to[i]);
555
556 // Clip against the near plane and the user clip planes. A segment
557 // with an endpoint behind the camera would otherwise project to
558 // garbage (w <= 0).
559 float t0 = 0.0f, t1 = 1.0f;
560 if (!clip_segment_view(v0, v1, options.near_plane, view_clip_planes,
561 t0, t1)) {
562 continue;
563 }
564
565 const Vec3 dir = v1 - v0;
566 const Vec3 p0 = v0 + t0 * dir;
567 const Vec3 p1 = v0 + t1 * dir;
568
569 float sx0, sy0, sz0, sx1, sy1, sz1;
571 output.width, output.height, sx0, sy0, sz0);
573 output.width, output.height, sx1, sy1, sz1);
574
575 // One color per segment: clipping does not change it.
576 const Color color = layer.color_or(i, options.default_color);
577 const Vec3 screen_v0(sx0, sy0, sz0);
578 const Vec3 screen_v1(sx1, sy1, sz1);
579 const Vec3 line_normal(0.0f, 0.0f, 1.0f);
580
581 if (color.a < 1.0f - 1e-6f) {
582 deferred_lines.push_back({screen_v0, screen_v1, color, color, width,
583 layer.lit, (p0.z + p1.z) * 0.5f});
584 } else {
585 rasterizer.rasterize_line(screen_v0, color, screen_v1, color,
586 width, layer.lit, line_normal,
588 }
589 }
590 }
591
592 if (rasterizer.ssao_enabled) {
593 rasterizer.apply_ssao(output, options.near_plane, options.far_plane);
594 }
595
596 if (!deferred.empty() || !deferred_lines.empty()) {
597 // Painter's algorithm: farthest primitive first, nearest last, so that
598 // nearer translucent surfaces are blended *over* the ones behind them.
599 // Ascending view-space z is farthest-to-nearest (the camera looks down
600 // -Z); sorting the other way round blends front-to-back and makes the
601 // nearest surface disappear behind the ones behind it. Triangles and
602 // lines are sorted together, so their mutual order is correct too.
603 std::sort(deferred.begin(), deferred.end(),
604 [](const DeferredTri &a, const DeferredTri &b) {
605 return a.view_z < b.view_z;
606 });
607 std::sort(deferred_lines.begin(), deferred_lines.end(),
608 [](const DeferredLine &a, const DeferredLine &b) {
609 return a.view_z < b.view_z;
610 });
611
612 rasterizer.set_blend_mode(true);
613
614 size_t tri_idx = 0;
615 size_t line_idx = 0;
616 while (tri_idx < deferred.size() || line_idx < deferred_lines.size()) {
617 const bool take_line =
618 (tri_idx >= deferred.size()) ||
619 (line_idx < deferred_lines.size() &&
620 deferred_lines[line_idx].view_z < deferred[tri_idx].view_z);
621 if (take_line) {
622 const DeferredLine &dl = deferred_lines[line_idx++];
623 rasterizer.rasterize_line(
624 dl.screen_v0, dl.color0, dl.screen_v1, dl.color1,
625 dl.width, dl.lit, Vec3(0.0f, 0.0f, 1.0f), light_direction,
626 output);
627 } else {
628 const DeferredTri &dt = deferred[tri_idx++];
629 rasterizer.rasterize_triangle(
630 dt.screen_v0, dt.color0, dt.normal0, dt.uv0,
631 dt.screen_v1, dt.color1, dt.normal1, dt.uv1,
632 dt.screen_v2, dt.color2, dt.normal2, dt.uv2,
633 options.backface_culling, dt.smooth,
635 options.wireframe, options.wireframe_color,
636 output);
637 }
638 }
639
640 rasterizer.set_blend_mode(false);
641 }
642
643 // ---- Text layers -------------------------------------------------------
644 // Text layers are drawn last, so labels end up on top of the geometry and
645 // of the lines. They are drawn before the image is downsampled, so glyphs
646 // get the same anti-aliasing as everything else; the depth buffer is handed
647 // to the text renderer so that labels whose anchor is hidden behind a
648 // surface can be skipped (see TextLayer::depth_test). Screen-space offsets
649 // (font size, pixel offsets, halo width) are scaled by the same factor as
650 // the supersampled image.
651 if (!text_nodes.empty()) {
652 const float pixel_scale =
653 static_cast<float>(output.width) /
654 static_cast<float>(std::max(1, options.width));
656 pixel_scale, rasterizer.z_buffer,
657 options.default_color);
658 }
659}
660
661} // namespace scimesh
Image render_mesh(const Mesh &mesh, const Camera &camera, const RenderOptions &options)
Render a single mesh to an image.
Definition renderer.cpp:114
Image render_lines_raw(const std::vector< Vec3 > &from, const std::vector< Vec3 > &to, const std::vector< Color > &colors, float width, const Camera &camera, const RenderOptions &options)
Render line segments with a screen-space width to an image.
Definition renderer.cpp:236
Image render_scene(const Scene &scene, const Camera &camera, const RenderOptions &options)
Render a scene (collection of meshes) to an image.
Definition renderer.cpp:126
Image render_points_raw(const std::vector< Vec3 > &positions, const std::vector< Color > &colors, float radius, const Camera &camera, const RenderOptions &options)
Render a point cloud (spheres at each position) to an image.
Definition renderer.cpp:160
Image render_triangles_raw(const std::vector< Vec3 > &positions, const std::vector< Color > &colors, const Camera &camera, const RenderOptions &options)
Render raw triangles (no Mesh wrapper) to an image.
Definition renderer.cpp:137
Triangle clipping against planes (view frustum and clip planes).
Low-level math utilities for the rendering pipeline.
void render_text_layers(const std::vector< TextNodeRef > &layers, Image &output, const Mat4 &view_projection, float pixel_scale, const std::vector< float > &z_buffer, const Color &default_color)
Draw all text layers into an already rendered image.
Definition text.cpp:448
glm::vec2 Vec2
2-component floating-point vector (xy).
Definition types.h:32
void ndc_to_screen(const Vec3 &ndc, int width, int height, float &screen_x, float &screen_y, float &depth)
Convert from normalized device coordinates (NDC) to screen (pixel) coordinates.
Definition math_utils.h:125
glm::mat4 Mat4
4×4 floating-point matrix.
Definition types.h:65
void compute_vertex_normals(const Mesh &mesh, std::vector< Vec3 > &normals)
Compute per-vertex normals by averaging adjacent face normals.
Definition normals.cpp:5
glm::vec3 Vec3
3-component floating-point vector (xyz).
Definition types.h:46
Vec3 transform_direction(const Mat4 &m, const Vec3 &d)
Transform a direction vector by a 4×4 matrix (with implicit w=0).
Definition math_utils.h:90
Vec3 compute_face_normal(const Vec3 &v0, const Vec3 &v1, const Vec3 &v2)
Compute the unit-length normal vector of a triangle face.
Definition math_utils.h:37
int clip_triangle_view_plane(const Vec3 &v0, const Vec3 &v1, const Vec3 &v2, const Vec3 &n0, const Vec3 &n1, const Vec3 &n2, const Color &c0, const Color &c1, const Color &c2, const Vec2 &uv0, const Vec2 &uv1, const Vec2 &uv2, const ClipPlane &plane, std::vector< ClipVertex > &output_vertices, std::vector< Triangle > &output_triangles)
Clip a triangle against an arbitrary plane in view space.
Definition clipping.cpp:162
ClipPlane clip_plane_to_view_space(const ClipPlane &plane, const Vec3 &eye, const Mat4 &view)
Convert a ClipPlane to view (eye) space, ready for clipping.
Definition clipping.h:85
Vec3 transform_point(const Mat4 &m, const Vec3 &p)
Transform a point by a 4×4 matrix (with implicit w=1).
Definition math_utils.h:60
int clip_triangle_near_plane(const ClipVertex &v0, const ClipVertex &v1, const ClipVertex &v2, std::vector< ClipVertex > &output_vertices, std::vector< Triangle > &output_triangles)
Clip a triangle against the near clipping plane in clip space.
Definition clipping.cpp:67
@ SMOOTH
Smooth (Gouraud) shading: normals are interpolated across each triangle, producing a smooth,...
@ ORTHOGRAPHIC
Orthographic projection: parallel lines stay parallel.
Vec3 perspective_divide(const Vec4 &clip)
Perform perspective division: divide xyz by w.
Definition math_utils.h:105
Vec4 transform_point_homogeneous(const Mat4 &m, const Vec3 &p)
Transform a point by a 4×4 matrix, returning the full Vec4 result.
Definition math_utils.h:75
glm::vec4 Vec4
4-component floating-point vector (xyzw).
Definition types.h:52
Compute per-vertex surface normals for lighting.
The Rasterizer — the per-pixel rendering engine.
Vec3 normal2
Definition renderer.cpp:24
Vec3 screen_v2
Definition renderer.cpp:22
bool smooth
Definition renderer.cpp:26
float view_z
Centroid depth in view space: the camera looks down -Z, so a smaller (more negative) value is farther...
Definition renderer.cpp:29
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
The Renderer — the main entry point for drawing meshes to images.
A virtual camera that defines the viewpoint for rendering.
Definition camera.h:77
ProjectionType projection
Which projection type to use.
Definition camera.h:99
A 2D RGBA image buffer.
Definition image.h:92
A batch of independent line segments drawn with a fixed screen-space width.
Definition lines.h:49
std::vector< Vec3 > from
Segment start points, in world space.
Definition lines.h:51
float width
Line width in screen pixels (default: 1.0).
Definition lines.h:66
std::vector< Color > colors
One color per segment; may be empty (then the renderer uses RenderOptions::default_color) or shorter ...
Definition lines.h:59
std::vector< Vec3 > to
Segment end points, in world space (one per entry in from).
Definition lines.h:54
A 3D triangle mesh using an indexed face set representation.
Definition mesh.h:76
bool has_transparency
Whether the mesh contains any transparent fragments.
Definition mesh.h:187
std::vector< Color > colors
Per-vertex RGBA colors.
Definition mesh.h:107
std::vector< Vec3 > vertices
3D vertex positions.
Definition mesh.h:86
std::vector< Triangle > triangles
Triangle index triplets.
Definition mesh.h:94
Low-level triangle rasterizer with depth buffering and lighting.
Definition rasterizer.h:45
All settings that control rendering output.
A collection of Mesh objects to be rendered together.
Definition scene.h:69
void add_lines(const LineLayer &layer, const Mat4 &transform=Mat4(1.0f), const std::string &name="")
Add a line layer to the scene with an optional placement transform and name.
Definition scene.h:204
int x
Left edge of the bitmap, in image pixels.
Definition text.cpp:223
Text labels — the TextLayer.