7#include <glm/gtc/matrix_transform.hpp>
33 throw std::invalid_argument(
"RenderOptions width and height must be > 0");
37 std::vector<SceneNodeRef> nodes;
38 nodes.push_back({&mesh,
Mat4(1.0f),
""});
39 render_pipeline(nodes, camera, options, internal);
45 throw std::invalid_argument(
"RenderOptions width and height must be > 0");
49 std::vector<SceneNodeRef> nodes = scene.
nodes();
50 render_pipeline(nodes, camera, options, internal);
55 const std::vector<Color> &colors,
61 int nv =
static_cast<int>(positions.size());
62 for (
int i = 0; i < nv / 3; i++) {
64 static_cast<uint32_t
>(i * 3),
65 static_cast<uint32_t
>(i * 3 + 1),
66 static_cast<uint32_t
>(i * 3 + 2)});
68 for (
const auto &c : colors) {
69 if (c.a < 1.0f - 1e-6f) {
78 const std::vector<Color> &colors,
83 throw std::invalid_argument(
"RenderOptions width and height must be > 0");
85 int np =
static_cast<int>(positions.size());
86 if (np == 0 || colors.empty()) {
99 rasterizer.
clear(1.0f);
117 float aspect =
static_cast<float>(output.
width) /
static_cast<float>(output.
height);
121 Mat4 view_projection = projection * view;
123 Vec3 light_direction =
Vec3(0.0f, 0.0f, 1.0f);
124 if (!options.
lights.empty()) {
125 light_direction = options.
lights[0].position;
129 float aa_radius = radius *
static_cast<float>(aa);
131 for (
int i = 0; i < np; i++) {
137 Vec3 normal(0.0f, 0.0f, 1.0f);
139 normal, light_direction, output);
149void Renderer::render_pipeline(
const std::vector<SceneNodeRef> &nodes,
154 for (
size_t i = 0; i < nodes.size(); ++i) {
155 const auto *mp = nodes[i].mesh;
156 if (mp->empty())
continue;
157 if (!mp->is_valid()) {
158 throw std::invalid_argument(
159 "Mesh " + std::to_string(i) +
" failed validation: "
160 "check indices, vertex data, and array sizes");
166 Rasterizer rasterizer(output.
width, output.
height);
167 rasterizer.clear(1.0f);
169 rasterizer.shininess = options.
shininess;
170 rasterizer.lights = options.
lights;
171 rasterizer.ambient = options.
ambient;
172 rasterizer.contrast = options.
contrast;
174 rasterizer.fog_start = options.
fog_start;
175 rasterizer.fog_end = options.
fog_end;
176 rasterizer.fog_color = options.
fog_color;
187 for (
auto &light : rasterizer.lights) {
190 float aspect =
static_cast<float>(output.
width) /
static_cast<float>(output.
height);
191 Camera proj_cam = camera;
194 Mat4 view_projection = projection * view;
196 std::vector<ClipPlane> view_clip_planes = options.
clip_planes;
197 for (
auto &cp : view_clip_planes) {
201 Vec3 light_direction =
Vec3(0.0f, 0.0f, 1.0f);
203 bool scene_has_transparency =
false;
204 for (
const auto &nd : nodes) {
205 if (nd.mesh->has_transparency) { scene_has_transparency =
true;
break; }
208 std::vector<DeferredTri> deferred;
210 for (
const auto &node : nodes) {
211 const Mesh &mesh = *node.mesh;
212 if (mesh.empty())
continue;
215 const Mat4 &model = node.transform;
216 const Mat4 view_model = view * model;
217 const Mat4 view_proj_model = view_projection * model;
219 if (mesh.has_uvs() && mesh.has_texture()) {
220 rasterizer.active_texture =
const_cast<Image *
>(&mesh.texture);
222 rasterizer.active_texture =
nullptr;
225 std::vector<Vec3> computed_normals;
226 const std::vector<Vec3> *normals_ptr;
227 if (mesh.has_normals()) {
228 normals_ptr = &mesh.normals;
231 normals_ptr = &computed_normals;
234 std::vector<Vec3> view_normals(normals_ptr->size());
235 for (
size_t i = 0; i < normals_ptr->size(); ++i) {
236 Vec3 n = (*normals_ptr)[i];
238 view_normals[i] = glm::normalize(
242 for (
int ti = 0; ti < static_cast<int>(mesh.triangles.size()); ++ti) {
243 const auto &tri = mesh.triangles[ti];
244 Vec3 v0 = mesh.vertices[tri.v0];
245 Vec3 v1 = mesh.vertices[tri.v1];
246 Vec3 v2 = mesh.vertices[tri.v2];
249 if (mesh.has_face_colors()) {
250 c0 = c1 = c2 = mesh.face_colors[ti];
251 }
else if (mesh.has_colors()) {
252 c0 = mesh.colors[tri.v0];
253 c1 = mesh.colors[tri.v1];
254 c2 = mesh.colors[tri.v2];
259 Vec3 n0 = view_normals[tri.v0];
260 Vec3 n1 = view_normals[tri.v1];
261 Vec3 n2 = view_normals[tri.v2];
263 Vec2 uv0 = mesh.has_uvs() ? mesh.uvs[tri.v0] :
Vec2(0, 0);
264 Vec2 uv1 = mesh.has_uvs() ? mesh.uvs[tri.v1] :
Vec2(0, 0);
265 Vec2 uv2 = mesh.has_uvs() ? mesh.uvs[tri.v2] :
Vec2(0, 0);
267 bool tri_transparent = scene_has_transparency &&
268 (c0.a < 1.0f - 1e-6f || c1.a < 1.0f - 1e-6f || c2.a < 1.0f - 1e-6f);
270 ClipVertex cv0, cv1, cv2;
272 cv0.color = c0; cv0.normal = n0; cv0.uv =
uv0;
274 cv1.color = c1; cv1.normal = n1; cv1.uv =
uv1;
276 cv2.color = c2; cv2.normal = n2; cv2.uv =
uv2;
278 bool has_user_clips = !view_clip_planes.empty();
280 std::vector<ClipVertex> clipped_vertices;
281 std::vector<Triangle> clipped_triangles;
283 if (has_user_clips) {
288 std::vector<ClipVertex> view_clipped;
289 std::vector<Triangle> view_clip_tris;
291 vv0, vv1, vv2, n0, n1, n2, c0, c1, c2,
293 view_clip_planes[0], view_clipped, view_clip_tris);
295 for (
int ci = 1; ci < static_cast<int>(view_clip_planes.size()) && vc_count > 0; ++ci) {
296 std::vector<ClipVertex> next_vertices;
297 std::vector<Triangle> next_triangles;
298 for (
const auto &vt : view_clip_tris) {
299 const ClipVertex &a = view_clipped[vt.v0];
300 const ClipVertex &b = view_clipped[vt.v1];
301 const ClipVertex &c = view_clipped[vt.v2];
302 Vec3 pa(a.position.x, a.position.y, a.position.z);
303 Vec3 pb(b.position.x, b.position.y, b.position.z);
304 Vec3 pc(c.position.x, c.position.y, c.position.z);
305 Vec3 na(a.normal), nb(b.normal), nc(c.normal);
306 Color ca(a.color), cb(b.color), cc(c.color);
307 Vec2 ua(a.uv), ub(b.uv), uc(c.uv);
309 pa, pb, pc, na, nb, nc, ca, cb, cc,
311 view_clip_planes[ci], next_vertices, next_triangles);
313 view_clipped.swap(next_vertices);
314 view_clip_tris.swap(next_triangles);
315 vc_count =
static_cast<int>(view_clip_tris.size());
316 if (vc_count == 0)
break;
319 if (vc_count == 0)
continue;
321 clipped_vertices.clear();
322 clipped_triangles.clear();
323 for (
const auto &vt : view_clip_tris) {
324 const ClipVertex &a = view_clipped[vt.v0];
325 const ClipVertex &b = view_clipped[vt.v1];
326 const ClipVertex &c = view_clipped[vt.v2];
328 ClipVertex cva, cvb, cvc;
329 cva.position = projection *
Vec4(a.position.x, a.position.y, a.position.z, 1.0f);
330 cva.color = a.color; cva.normal = a.normal;
331 cvb.position = projection *
Vec4(b.position.x, b.position.y, b.position.z, 1.0f);
332 cvb.color = b.color; cvb.normal = b.normal;
333 cvc.position = projection *
Vec4(c.position.x, c.position.y, c.position.z, 1.0f);
334 cvc.color = c.color; cvc.normal = c.normal;
337 clipped_vertices, clipped_triangles);
340 if (clipped_triangles.empty())
continue;
342 clipped_vertices.clear();
343 clipped_triangles.clear();
345 clipped_vertices, clipped_triangles);
346 if (num_clipped == 0)
continue;
351 for (
const auto &ct : clipped_triangles) {
352 const ClipVertex &cv_a = clipped_vertices[ct.v0];
353 const ClipVertex &cv_b = clipped_vertices[ct.v1];
354 const ClipVertex &cv_c = clipped_vertices[ct.v2];
360 float sx0, sy0, sz0, sx1, sy1, sz1, sx2, sy2, sz2;
369 Vec3 flat_normal_a, flat_normal_b, flat_normal_c;
375 flat_normal_a = flat_normal_b = flat_normal_c = face_normal;
378 const Vec3 &normal_a =
smooth ? cv_a.normal : flat_normal_a;
379 const Vec3 &normal_b =
smooth ? cv_b.normal : flat_normal_b;
380 const Vec3 &normal_c =
smooth ? cv_c.normal : flat_normal_c;
382 if (tri_transparent) {
388 cv_a.color, cv_b.color, cv_c.color,
389 normal_a, normal_b, normal_c,
390 cv_a.uv, cv_b.uv, cv_c.uv,
394 rasterizer.rasterize_triangle(
395 screen_v0, cv_a.color, normal_a, cv_a.uv,
396 screen_v1, cv_b.color, normal_b, cv_b.uv,
397 screen_v2, cv_c.color, normal_c, cv_c.uv,
407 if (rasterizer.ssao_enabled) {
411 if (!deferred.empty()) {
412 std::sort(deferred.begin(), deferred.end(),
413 [](
const DeferredTri &a,
const DeferredTri &b) {
414 return a.view_z > b.view_z;
417 rasterizer.set_blend_mode(
true);
419 for (
const auto &dt : deferred) {
420 rasterizer.rasterize_triangle(
421 dt.screen_v0, dt.color0, dt.normal0, dt.uv0,
422 dt.screen_v1, dt.color1, dt.normal1, dt.uv1,
423 dt.screen_v2, dt.color2, dt.normal2, dt.uv2,
430 rasterizer.set_blend_mode(
false);
Image render_mesh(const Mesh &mesh, const Camera &camera, const RenderOptions &options)
Render a single mesh to an image.
Image render_scene(const Scene &scene, const Camera &camera, const RenderOptions &options)
Render a scene (collection of meshes) to an image.
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.
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.
Triangle clipping against planes (view frustum and clip planes).
Low-level math utilities for the rendering pipeline.
glm::vec2 Vec2
2-component floating-point vector (xy).
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.
glm::mat4 Mat4
4×4 floating-point matrix.
void compute_vertex_normals(const Mesh &mesh, std::vector< Vec3 > &normals)
Compute per-vertex normals by averaging adjacent face normals.
glm::vec3 Vec3
3-component floating-point vector (xyz).
Vec3 transform_direction(const Mat4 &m, const Vec3 &d)
Transform a direction vector by a 4×4 matrix (with implicit w=0).
Vec3 compute_face_normal(const Vec3 &v0, const Vec3 &v1, const Vec3 &v2)
Compute the unit-length normal vector of a triangle face.
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.
Vec3 transform_point(const Mat4 &m, const Vec3 &p)
Transform a point by a 4×4 matrix (with implicit w=1).
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.
@ SMOOTH
Smooth (Gouraud) shading: normals are interpolated across each triangle, producing a smooth,...
Vec3 perspective_divide(const Vec4 &clip)
Perform perspective division: divide xyz by w.
Vec4 transform_point_homogeneous(const Mat4 &m, const Vec3 &p)
Transform a point by a 4×4 matrix, returning the full Vec4 result.
glm::vec4 Vec4
4-component floating-point vector (xyzw).
Compute per-vertex surface normals for lighting.
The Rasterizer — the per-pixel rendering engine.
The Renderer — the main entry point for drawing meshes to images.
A virtual camera that defines the viewpoint for rendering.
Mat4 get_view_matrix() const
Compute the view matrix (world → camera space).
ProjectionType projection
Which projection type to use.
Mat4 get_projection_matrix(float aspect_ratio, float near_plane, float far_plane) const
Compute the projection matrix (camera → clip space).
float g
Green channel, [0, 1].
float r
Red channel, [0, 1].
float b
Blue channel, [0, 1].
float a
Alpha (opacity) channel, [0, 1]. 1.0 = fully opaque.
int height
Image height in pixels.
int width
Image width in pixels.
void clear_float(float r, float g, float b, float a)
Fill the entire image with an RGBA color (float values 0.0–1.0).
Image downsample_box(int factor) const
Downsample the image by a factor using box filtering.
A 3D triangle mesh using an indexed face set representation.
bool has_transparency
Whether the mesh contains any transparent fragments.
std::vector< Color > colors
Per-vertex RGBA colors.
std::vector< Vec3 > vertices
3D vertex positions.
std::vector< Triangle > triangles
Triangle index triplets.
Low-level triangle rasterizer with depth buffering and lighting.
float ssao_radius
SSAO sample radius in pixels (default: 16).
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.
Color fog_color
The fog color (what distant objects blend into).
bool fog_enabled
Enable depth fog (default: false).
float fog_start
Distance where fog begins.
void apply_ssao(Image &output, float z_near, float z_far)
Apply screen-space ambient occlusion to the output image.
float ssao_intensity
SSAO darkening intensity (0.0–1.0, default: 0.8).
float ambient
Ambient light level (0.0–1.0, default: 0.3).
Color specular_color
Specular highlight color.
std::vector< Light > lights
Light sources for Blinn-Phong shading.
float shininess
Shininess exponent (Phong model).
bool ssao_enabled
Enable SSAO (default: false).
All settings that control rendering output.
std::vector< Light > lights
List of light sources.
bool fog_enabled
Enable depth fog (default: false).
Color background_color
Background color (default: opaque white).
float ambient
Ambient light level (default: 0.3).
float fog_start
Distance at which fog begins (in world units).
float shininess
Shininess exponent (default: 0.0 = no specular).
float contrast
Contrast adjustment (default: 1.0 = no change).
int height
Image height in pixels (default: 600).
Color wireframe_color
Color of the wireframe lines (default: black).
int width
Image width in pixels (default: 800).
float ssao_intensity
SSAO darkening intensity (default: 0.8).
std::vector< ClipPlane > clip_planes
Optional clip planes for cross-section views.
Color default_color
Default color for meshes without explicit vertex/face colors.
float far_plane
Far clipping plane distance (default: 10000.0).
bool backface_culling
Enable backface culling (default: true).
Color specular_color
Specular highlight color (default: transparent = no specular).
bool ssao_enabled
Enable SSAO (default: false).
int threads
Number of render threads (default: 0 = auto-detect).
int aa_samples
Anti-aliasing sample count (default: 1 = no AA).
bool wireframe
Draw triangle edges as lines (default: false).
float ssao_radius
SSAO sample radius in pixels (default: 16).
ShadingMode shading
Shading mode (default: SMOOTH).
float near_plane
Near clipping plane distance (default: 0.1).
float fog_end
Distance at which fog is fully opaque (in world units).
ProjectionType projection
Projection type (default: PERSPECTIVE).
Color fog_color
The color that distant objects fade into.
bool invert_normals
Flip all surface normals (default: false).
A collection of Mesh objects to be rendered together.
std::vector< SceneNodeRef > nodes() const
Non-owning references to all meshes, in draw order.