9  3D Visualisation

Published

January 1, 2026

Numbers are useful; actually seeing the mesh is better. This chapter covers two routes: rgl (Adler et al. 2024) for interactive exploration in RStudio (spin it with the mouse, zoom in, inspect details), and rayshader (Morgan-Wall 2024) for rendering publication-quality static images.

Both work on mesh_vcg from Reading OBJ and STL Meshes.

9.1 Interactive 3D with rgl

shade3d() opens an OpenGL window where you can rotate and zoom with the mouse. Takes one line:

library(rgl)
open3d()
shade3d(mesh_vcg, col = "#C0C0C0", alpha = 0.9)
bg3d("white")
axes3d()
title3d("Aerial DWG — Translated Mesh")

9.1.1 Embedding in HTML Output

rgl windows are interactive in RStudio but don’t embed in HTML documents on their own. rglwidget() wraps the scene in a self-contained WebGL widget that works in any browser:

# Add this once at the top of your document (setup chunk)
knitr::knit_hooks$set(webgl = rgl::hook_webgl)

open3d()
shade3d(mesh_vcg, col = "#4A90D9", alpha = 0.85)
rglwidget()
Note

The WebGL widget is fully self-contained in the rendered HTML. Readers can rotate and zoom without a running R server. It does require a modern browser (Chrome, Firefox, Safari, Edge all work fine).

9.2 Colouring by Height

Map vertex Z-values to a colour ramp to reveal the model’s vertical structure instantly. Blues for low, reds for high:

z_vals <- mesh_vcg$vb[3, ]
z_norm <- (z_vals - min(z_vals)) / diff(range(z_vals))   # 0–1

cols      <- colorRampPalette(c("#2166ac", "#92c5de", "#f7f7f7",
                                 "#f4a582", "#d6604d"))(100)
vert_cols <- cols[ceiling(z_norm * 99) + 1]

open3d()
shade3d(mesh_vcg, col = vert_cols)
rglwidget()

9.3 Rendered Images with rayshader

rayshader renders the active rgl scene with ray-traced lighting and ambient occlusion, great for reports and presentations. The output is a PNG file.

library(rayshader)

vertices  <- t(mesh_vcg$vb[1:3, ])
triangles <- t(mesh_vcg$it)

open3d()
triangles3d(vertices[triangles[, 1], ],
            vertices[triangles[, 2], ],
            vertices[triangles[, 3], ],
            col = "steelblue")

render_snapshot(filename    = "aerial_render.png",
                width       = 1200,
                height      = 900,
                title_text  = "Aerial DWG — Rayshader Render",
                title_color = "white",
                title_size  = 24,
                clear       = TRUE)

Include the render in your Quarto document with a standard image link:

![Aerial DWG rendered with rayshader](aerial_render.png)

9.4 Depth of Field

Add a depth-of-field effect to emphasise the model’s 3D structure in the output image, works especially well for tall structures:

render_depth(focus       = 0.7,
             focallength = 200,
             filename    = "aerial_dof.png")

9.5 Combining Multiple Meshes

Overlay two meshes in one scene to compare them — useful when you have an original and a repaired or simplified version. Rvcg::vcgMerge() fuses tmesh3d objects into a single mesh; for separate colours, render them individually before merging:

library(Rvcg)

mesh_a <- vcgImport("aerial_v1.obj")   # original
mesh_b <- vcgImport("aerial_v2.obj")   # repaired

open3d()
shade3d(mesh_a, col = "#4A90D9", alpha = 0.8)   # blue — original
shade3d(mesh_b, col = "#E74C3C", alpha = 0.6)   # red  — changes
title3d("v1 (blue) vs v2 (red)")
rglwidget()

To merge them into one object (e.g., for a metrics pass on the combined geometry):

mesh_combined <- vcgMerge(mesh_a, mesh_b)
cat("Combined vertices:", ncol(mesh_combined$vb), "\n")

9.6 Exporting the Scene

Save the interactive rgl scene as a standalone HTML file — no R session needed to view it:

open3d()
shade3d(mesh_vcg, col = "#4A90D9")

# Self-contained HTML with embedded WebGL
writeWebGL(dir = "webgl_export", filename = "aerial_viewer.html",
           width = 900, height = 600)

For a quick PNG without launching rayshader:

snapshot3d("aerial_snapshot.png", width = 1200, height = 900)

9.7 Quick Comparison: rgl vs. rayshader

rgl rayshader
Output Interactive HTML widget Static PNG
Use for Exploration, dashboards Reports, presentations
Render time Instant Seconds–minutes
WebGL support Yes (via rglwidget()) No