Skip to contents

Lighting modifies the fill and/or color of polygon faces based on their orientation relative to a light source, giving surfaces and volumes a sense of depth and shape. ggcube’s lighting system only renders form shadows, not cast shadows. Lighting can be applied to any polygon-based 3D layer — surfaces, hulls, bars, voxels, and polygon text — but is useful mainly for layers where polygon orientation varies.

This vignette covers how to control what parts of your plot lighting is applied to, how lighting model parameters change the visual qualities of the lighting, how to change the direction of the light source, how to control how the underside of a polygon is lit, and how to add lighting-aware guides to your plot.

We’ll use a common base plot throughout. Here it is with default lighting:

p <- ggplot(sphere_points, aes(x, y, z)) +
  geom_hull_3d(fill = "#9e2602", color = "#5e1600") +
  coord_3d(scales = "fixed")
p

Lighting targets

Lighting can be set at two levels:

  • Coord-level: coord_3d(light = light(...)) applies to all layers in the plot.
  • Layer-level: geom_*_3d(light = light(...)) overrides the coord-level setting for that layer.

Use light = "none" to disable lighting entirely, or light = NULL in a layer to inherit the coord-level setting (the default layer behavior). Here we add a second layer to our plot, and override the inherited light settings to disable lighting for this layer:

p + geom_hull_3d(aes(x = x + 2.5), fill = "#9e2602", color = "#5e1600",
                 light = "none")

The fill and color parameters control which aesthetics get modified by lighting. By default both are TRUE:

(p + ggtitle("fill + color (default)")) +
  (p + coord_3d(light = light(fill = TRUE, color = FALSE)) +
      ggtitle("fill only"))

Lighting works with both solid and aesthetically-mapped colors:

ggplot(sphere_points, aes(x, y, z)) +
  geom_hull_3d(aes(fill = x, color = x)) +
  scale_fill_viridis_c() +
  scale_color_viridis_c() +
  guides(fill = guide_colorbar_3d()) +
  coord_3d(light = light("direct"))

Light qualities

Lighting methods

The method parameter controls the lighting model:

  • "diffuse" (default): Soft, atmospheric lighting. Only surfaces pointing directly away from the light source are fully dark.
  • "direct": Hard lighting. All surfaces angled beyond 90 degrees from the light are fully dark.
  • "rgb": Maps surface orientation to RGB color space, replacing the base colors entirely.
(p + coord_3d(light = light(method = "diffuse")) + ggtitle('"diffuse" (default)')) +
  (p + coord_3d(light = light(method = "direct")) + ggtitle('"direct"')) +
  (p + coord_3d(light = light(method = "rgb")) + ggtitle('"rgb"')) +
  plot_layout(ncol = 3)

Color modes

The mode parameter controls how lighting modifies colors. This applies to the "diffuse" and "direct" methods (not "rgb"). There are two color modes:

  • "hsv" (default): Modifies the value component of HSV color. Highlights preserve hue; shadows fade to black.
  • "hsl": Modifies the lightness component of HSL color. Highlights fade to white; shadows fade to black.

The two modes give identical results for grayscale base colors.

(p + coord_3d(light = light(mode = "hsv")) +
    ggtitle('"hsv" (default)')) +
  (p + coord_3d(light = light(mode = "hsl")) +
      ggtitle('"hsl"'))

Contrast strength

The contrast parameter scales the intensity of lighting effects. Values less than 1 give subtler effects; values greater than 1 give more dramatic ones:

(p + coord_3d(light = light(mode = "hsl", contrast = 0.5)) +
    ggtitle("contrast = 0.5")) +
  (p + coord_3d(light = light(mode = "hsl", contrast = 1)) +
      ggtitle("contrast = 1 (default)")) +
  (p + coord_3d(light = light(mode = "hsl", contrast = 1.5)) +
      ggtitle("contrast = 1.5")) +
  plot_layout(ncol = 3)

Light sources

Direction and anchoring

The direction parameter is a length-3 vector specifying where light comes from; for example, direction = c(0, -1, 0) lights the plot from the negative y direction. The anchor parameter controls whether this direction is relative to the data axes or the camera:

  • anchor = "scene" (default): Direction is relative to the data axes and rotates with the plot. direction = c(1, 0, 0) always lights surfaces facing the “xmax” face.
  • anchor = "camera": Direction is fixed relative to the viewing pane. direction = c(1, 0, 0) always lights surfaces facing the right side of the plot, regardless of rotation.
(p + coord_3d(light = light("direct", direction = c(0, -1, 0))) +
    ggtitle('anchor = "scene"')) +
  (p + coord_3d(light = light("direct", direction = c(0, -1, 0), anchor = "camera")) +
      ggtitle('anchor = "camera"'))

Positional lighting

In addition to directional lighting (parallel rays from a direction), ggcube also supports positional lighting from a point source. Use position instead of direction to specify a light source location in data coordinates. Setting distance_falloff = TRUE applies inverse-square intensity falloff from the light source position.

ggplot(mountain, aes(x, y, z)) +
  stat_surface_3d(fill = "red", color = "red") +
  coord_3d(
    light = light(position = c(.5, .7, 95), distance_falloff = TRUE,
                  mode = "hsl", contrast = .9),
    ratio = c(1.5, 2, 1))

Backface lighting

A backface is the side of a polygon facing the underside of a surface or the inside of a volume. By default, backfaces receive inverted lighting relative to frontfaces (backface_scale = -1), creating strong visual contrast between the two sides. Setting backface_scale = 1 lights backfaces the same as frontfaces. Modifying backface_offset to uniformly darkens or brightens all backfaces.

pb <- ggplot() +
  geom_function_3d(fun = function(x, y) x^2 + y^2,
                   xlim = c(-3, 3), ylim = c(-3, 3),
                   fill = "steelblue", color = "steelblue")

(pb + coord_3d(pitch = 0, roll = -70, yaw = 0,
               light = light(mode = "hsl")) +
    ggtitle("default:\nbackface_scale = -1\nbackface_offset = 0")) +
  (pb + coord_3d(pitch = 0, roll = -70, yaw = 0,
                 light = light(backface_scale = 1, mode = "hsl")) +
      ggtitle("\nbackface_scale = 1\nbackface_offset = 0")) +
  (pb + coord_3d(pitch = 0, roll = -70, yaw = 0,
              light = light(backface_scale = 1, mode = "hsl",
                            backface_offset = -.5)) +
      ggtitle("\nbackface_scale = 1\nbackface_offset = -0.5")) +
  plot_layout(nrow = 1)

3D guides

When lighting is active, standard color guides don’t reflect the range of shaded colors visible in the plot. ggcube provides guide_colorbar_3d() and guide_legend_3d() to create guides that show the full shading gradient:

ggplot(mountain, aes(x, y, z, fill = z)) +
  stat_surface_3d(light = light(mode = "hsl", direction = c(1, 0, 0))) +
  guides(fill = guide_colorbar_3d()) +
  scale_fill_gradientn(colors = c("tomato", "dodgerblue")) +
  coord_3d(ratio = c(2, 3, 1.5))

ggplot(mountain, aes(x, y, z, fill = x > .5, group = 1)) +
  stat_surface_3d(light = light(mode = "hsl", direction = c(1, 0, 0))) +
  guides(fill = guide_legend_3d()) +
  coord_3d(ratio = c(2, 3, 1.5))

Both guides accept shade_range to control the range of shading shown (a length-2 vector in the range -1 to 1), and reverse_shade to flip the gradient direction.

Note that when fill and color map to the same variable, ggplot2 creates a shared scale. In this case, apply the 3D guide via guides(fill = guide_colorbar_3d()), not via the color guide.