R/render_scene.R

Defines functions render_scene

Documented in render_scene

#' Render Scene
#'
#' Takes the scene description and renders an image, either to the device or to a filename. The
#' user can also interactively fly around the 3D scene if they have X11 support on their system
#' or are on Windows.
#'
#' @param scene Tibble of object locations and properties.
#' @param width Default `400`. Width of the render, in pixels.
#' @param height Default `400`. Height of the render, in pixels.
#' @param fov Default `20`. Field of view, in degrees. If this is `0`, the camera will use an orthographic projection. The size of the plane
#' used to create the orthographic projection is given in argument `ortho_dimensions`. From `0` to `180`, this uses a perspective
#' projections. If this value is `360`, a 360 degree environment image will be rendered.
#' @param samples Default `100`. The maximum number of samples for each pixel. If this is a length-2
#' vector and the `sample_method` is `stratified`, this will control the number of strata in each dimension.
#' The total number of samples in this case will be the product of the two numbers.
#' @param preview Default `interactive()`. Whether to display a real-time progressive preview of the render. Press ESC to cancel the render.
#' If `deferred_render = TRUE`, the preview stays interactive until the final render is explicitly started.
#' @param interactive Default `interactive()`. Whether the scene preview should be interactive. Camera movement orbits around the
#' lookat point (unless the mode is switched to free flying), with the following control mapping:
#' W = Forward, S = Backward, A = Left, D = Right, Q = Up, Z = Down,
#' Shift-W/Shift-S = Pitch Camera Forward/Back, Shift-A/Shift-D = Roll Camera Left/Right,
#' E = 2x Step Distance (max 128), C = 0.5x Step Distance, Up Key = Zoom In (decrease FOV), Down Key = Zoom Out (increase FOV),
#' Left Key = Decrease Aperture, Right Key = Increase Aperture, 1 = Decrease Focal Distance, 2 = Increase Focal Distance,
#' 3/4 = Rotate Environment Light,
#' Right bracket/left bracket = Increase/Decrease Preview Exposure, Shift + right bracket/left bracket = Increase/Decrease shutter speed,
#' Shift-Enter = Save Preview Snapshot, R = Reset Camera,
#' Return = Toggle between deferred and final render if `deferred_render = TRUE`, TAB: Toggle Orbit Mode,
#' Left Mouse Click: Set Look At and Focal Distance, Right Mouse Click: Set Look At.
#' If the interactive preview window is wide enough, a status bar at the bottom shows the current camera, exposure, environment rotation,
#' and keyframe state.
#' K: Save Keyframe (at the conclusion of the render, this will create the `ray_keyframes`
#' data.frame in the global environment, which can be passed to `generate_camera_motion()` to tween between those saved positions.
#' L: Reset Camera to Last Keyframe (if set), Shift-L: Toggle keyframe path open/closed,
#' < and >: Jump to previous/next keyframe, /: Delete current keyframe,
#' M: Preview/cancel keyframe motion,
#' F: Toggle Fast Travel Mode,
#' B: Toggle Camera Motion Blur,
#' H: Toggle Atmospheric Haze, Y: Toggle Altitude Queries (with `sky_light()`).
#' Haze requires altitude queries: enabling haze also enables altitude queries,
#' and disabling altitude queries also disables haze. Each change restarts sampling.
#'
#' Initial step size is 1/20th of the distance from `lookat` to `lookfrom`.
#'
#' With \code{integrator_type = "nee"}, clicks select the first point where accumulated volume opacity
#' reaches 15\%, or the first ordinary surface if reached sooner. Picking integrates extinction
#' deterministically with a fixed shutter sample and a centered lens sample. Thin or empty regions
#' allow selection of surfaces behind them; invisible container faces are skipped. Clicking the
#' background without reaching this opacity turns perspective and panoramic cameras toward that
#' direction, preserving focal distance and orbit radius. Orthographic background clicks leave the
#' view unchanged. Selected surface and volume points remain the orbit center when rotating.
#' Right clicking preserves the focal distance.
#' Some options aren't available for all cameras. When using a realistic camera,
#' the aperture and field of view cannot be changed from their initial settings.
#' @param deferred_render Default `FALSE`. If `TRUE` and interactive preview is enabled, rayrender will keep
#' updating the progressive preview until Return is pressed. Pressing Return toggles the full render in the
#' same window; pressing Return again returns to deferred mode.
#' @param denoise Default `TRUE`. Whether to de-noise the final image and preview images. Note, this requires
#' the free Intel Open Image Denoise (OIDN) library be installed on your system. Pre-compiled binaries can be installed from
#' ppenimagedenoise.org, as well as . Linking during rayrender installation is done by defining the environment variable
#' OIDN_PATH (set it in the .Renviron file by calling `usethis::edit_r_environ()`) to the top-level directory for OIDN (the directory containing the "lib", "bin", and "include"
#' directories) and re-installing this package from source.
#' @param camera_description_file Default `NA`. Filename of a camera description file for rendering with
#' a realistic camera. Several camera files are built-in: `"50mm"`,`"wide"`,`"fisheye"`, and `"telephoto"`.
#' @param camera_scale Default `1`. Amount to scale the camera up or down in size. Use this rather than scaling a
#' scene.
#' @param iso Default `100`. Camera exposure.
#' @param auto_exposure Default `FALSE`. If `TRUE`, automatically adjust the exposure with
#' `rayimage::render_exposure(auto = TRUE)`. If `preview = TRUE`, the preview window exposure
#' is calibrated from the 90\% luminance quantile of the first rendered frame.
#' @param film_size Default `22`, in `mm` (scene units in `m`. Size of the film if using a realistic camera, otherwise
#' ignored.
#' @param min_variance Default `0`. Minimum acceptable variance for a block of pixels for the
#' adaptive sampler. Smaller numbers give higher quality images, at the expense of longer rendering times.
#' If this is set to zero, the adaptive sampler will be turned off and the renderer
#' will use the maximum number of samples everywhere.
#' @param min_adaptive_size Default `8`. Width of the minimum block size in the adaptive sampler.
#' @param sample_method Default `sobol`. The type of sampling method used to generate
#' random numbers. The other options are `random` (worst quality but fastest),
#' `stratified` (only implemented for completion), `sobol_blue` (best option for sample counts below 256),
#' and `sobol` (slowest but best quality, better than `sobol_blue` for sample counts greater than 256). If
#' `samples > 256` and `sobol_blue` is selected, the method will automatically switch to `sample_method = "sobol"`.
#' @param max_depth Default `NA`, automatically sets to 50. Maximum number of bounces a ray can make in a scene. Alternatively,
#' if a debugging option is chosen, this sets the bounce to query the debugging parameter (only for some options).
#' @param roulette_active_depth Default `100`. Number of ray bounces until a ray can stop bouncing via
#' Russian roulette.
#' @param ambient_light Default `FALSE`, unless there are no emitting objects in the scene.
#' If `TRUE`, the background will be a gradient varying from `backgroundhigh` directly up (+y) to
#' `backgroundlow` directly down (-y).
#' @param lookfrom Default `c(0,1,10)`. Location of the camera.
#' @param lookat Default `c(0,0,0)`. Location where the camera is pointed.
#' @param camera_up Default `c(0,1,0)`. Vector indicating the "up" position of the camera.
#' @param aperture Default `0.1`. Aperture of the camera. Smaller numbers will increase depth of field, causing
#' less blurring in areas not in focus.
#' @param clamp_value Default `Inf`. If a bright light or a reflective material is in the scene, occasionally
#' there will be bright spots that will not go away even with a large number of samples. These
#' can be removed (at the cost of slightly darkening the image) by setting this to a small number greater than 1.
#' @param filename Default `NULL`. If present, the renderer will write to the filename instead
#' of the current device. Can write to JPEG/JPG, PNG, and high dynamic range EXR images. In the
#' interactive preview, press Shift+Enter to save the current preview. A source filename with an
#' extension produces numbered snapshots with the number inserted before the extension; otherwise
#' snapshots are saved as `rayrender_snapshot1.png`, `rayrender_snapshot2.png`, and so on in the
#' current directory.
#' @param backgroundhigh Default `#80b4ff`. The "high" color in the background gradient. Can be either
#' a hexadecimal code, or a numeric rgb vector listing three intensities between `0` and `1`.
#' @param backgroundlow Default `#ffffff`. The "low" color in the background gradient. Can be either
#' a hexadecimal code, or a numeric rgb vector listing three intensities between `0` and `1`.
#' @param shutteropen Default `0`. Time at which the shutter is open. Only affects moving objects.
#' @param shutterclose Default `1`. Time at which the shutter is open. Only affects moving objects.
#' @param camera_motion_blur Default `FALSE`. Whether to blur camera movement over the shutter interval. Press `B` in interactive preview to toggle.
#' @param shutter_speed Default `NULL`. Optional render-time override for the
#' selected camera's frame-relative shutter speed. A value of `1` samples the
#' full frame-to-frame motion interval, `2` samples one-half, and `4` samples
#' one-quarter. Higher values produce less motion blur. `Inf` disables temporal
#' motion blur. This does not affect exposure or brightness.
#' @param focal_distance Default `NULL`, automatically set to the `lookfrom-lookat` distance unless
#' otherwise specified.
#' @param ortho_dimensions Default `c(1,1)`. Width and height of the orthographic camera. Will only be used if `fov = 0`.
#' @param tonemap Default `raw`, no tonemapping. Choose the tone mapping function,
#' `reinhard` scales values by their individual color channels `color/(1+color)` and then performs the
#' gamma adjustment. `uncharted` uses the mapping developed for Uncharted 2 by John Hable. `hbd` uses an
#' optimized formula by Jim Hejl and Richard Burgess-Dawson.
#' @param bloom Default `TRUE`. Set to `FALSE` to get the raw, pathtraced image. Otherwise,
#' this performs a convolution of the HDR image of the scene with a sharp, long-tailed
#' exponential kernel, which does not visibly affect dimly pixels, but does result in emitters light
#' slightly bleeding into adjacent pixels. This provides an antialiasing effect for lights, even when
#' tonemapping the image. Pass in a matrix to specify the convolution kernel manually, or a positive number
#' to control the intensity of the bloom (higher number = more bloom).
#' @param environment_light Default `NULL`. An image to be used for the background for rays that escape
#' the scene. Supports EXR, HDR, PNG, and JPEG images. For reusable scene lights and
#' multiple environments, use \code{\link{infinite_light}()} and \code{\link{add_infinite_light}()}. This
#' argument adds a light to any infinite lights already attached to the scene.
#' @param rotate_env Default `0`. The number of degrees to rotate all infinite lights around the scene,
#' in addition to their individual rotations.
#' @param intensity_env Default `1`. The amount to increase the intensity of the environment lighting. Useful
#' if using a LDR (JPEG or PNG) image as an environment map. Applies only to the
#' `environment_light` argument; scene lights have their own intensity.
#' @param transparent_background Default `FALSE`. If `TRUE`, any initial camera rays that escape the scene
#' will be marked as transparent in the final image. If for a pixel some rays escape and others hit a surface,
#' those pixels will be partially transparent.
#' @param integrator_type Default `"rtiow"` (the algorithm specified in the book "Raytracing in One Weekend", a basic
#' form of path guiding). Other options include `"nee"` (Next Event Estimation, with direct light sampling)
#' and `"basic"` (basic pathtracing, for high sample reference renders and debugging only).
#' With `nee`, surfaces and participating media use RGB null-scattering transport;
#' \code{nee} is selected automatically for scenes containing \code{\link{sky_light}()} or attached
#' media (including clouds and media inside instances), overriding \code{rtiow} or \code{basic}.
#' See \code{\link{set_medium}()}.
#' @param debug_channel Default `none`. If `depth`, function will return a depth map of rays into the scene
#' instead of an image. If `normals`, function will return an image of scene normals, mapped from 0 to 1.
#' If `uv`, function will return an image of the uv coords. If `variance`, function will return an image
#' showing the number of samples needed to take for each block to converge. If `dpdu` or `dpdv`, function will return
#' an image showing the differential `u` and `u` coordinates. If `color`, function will return the raw albedo
#' values (with white for `metal` and `dielectric` materials).
#' @param plot_scene Default `TRUE`. Whether to plot the rendered scene.
#' @param parallel Default `TRUE`. If `FALSE`, it will use all available cores to render the image
#'  (or the number specified in `options("cores")` or `options("Ncpus")` if that option is not `NULL`).
#' @param bvh_type Default `"sah"`, "surface area heuristic". Method of building the bounding volume
#' hierarchy structure used when rendering. Other option is "equal", which splits tree into groups
#' of equal size.
#' @param progress Default `interactive()` if interactive session, `FALSE` otherwise.
#' @param verbose Default `FALSE`. Prints information and timing information about scene
#' construction and raytracing progress.
#' @param print_debug_info Default `FALSE`. This will print out additional information on the compilation environment with
#' each render.
#' @param new_page Default `TRUE`. Whether to call `grid::grid.newpage()` when plotting the image (if
#' no filename specified). Set to `FALSE` for faster plotting (does not affect render time).
#' @param screen_text Default `NULL`. Optional screen-space text overlay created with `screen_text()`,
#' or a list of `screen_text()` outputs to draw in order.
#' @param screen_line Default `NULL`. Optional screen-space line overlay created with `screen_line()`,
#' or a list of `screen_line()` outputs to draw in order.
#' Labels are anchored to 3D world-space points, projected through the current camera, and drawn after
#' rendering so text size and justification are independent of scene scale and view distance.
#' @param camera Default `NULL`. Scene-attached camera name, `"all"`, or a `ray_camera` object created with `camera()`.
#' @param start_frame Default `1`. First camera frame to render when using an animated camera.
#' @param end_frame Default `NA`. Last camera frame to render when using an animated camera. If `NA`, renders through the final frame.
#' @param mode Default `"auto"`. Rendering mode. `"auto"` renders a still image for static cameras and an animation for animated cameras.
#' @export
#' @importFrom  grDevices col2rgb
#' @return A pathtraced image to the current device, or an image saved to a file. Invisibly returns the
#' array (containing either debug data or the RGB).
#'
#'@examplesIf interactive() || identical(Sys.getenv("IN_PKGDOWN"), "true")
#' # Generate a large checkered sphere as the ground
#'   scene = generate_ground(depth = -0.5,
#'                           material = diffuse(color = "white", checkercolor = "darkgreen"))
#'   render_scene(scene, parallel = TRUE, samples = 16, sample_method = "sobol")
#'   # Add a sphere to the center
#'   scene = scene |>
#'     add_object(sphere(x = 0, y = 0, z = 0, radius = 0.5, material = diffuse(color = c(1, 0, 1))))
#'   render_scene(scene, fov = 20, parallel = TRUE, samples = 16)
#'   # Add a marbled cube
#'   scene = scene |>
#'     add_object(cube(x = 1.1, y = 0, z = 0, material = diffuse(noise = 3)))
#'   render_scene(scene, fov = 20, parallel = TRUE, samples = 16)
#'   # Add a metallic gold sphere, using stratified sampling for a higher quality render
#'   # We also add a light, which turns off the default ambient lighting
#'   scene = scene |>
#'     add_object(sphere(x = -1.1, y = 0, z = 0, radius = 0.5,
#'                       material = metal(color = "gold", fuzz = 0.1))) |>
#'     add_object(sphere(y=10,z=-13,radius=2,material=light(intensity=40)))
#'   render_scene(scene, fov = 20, parallel = TRUE, samples = 16)
#'   # Lower the number of samples to render more quickly (here, we also use only one core).
#'   render_scene(scene, samples = 4, parallel = FALSE)
#'   # Add a floating R plot using the iris dataset as a png onto a floating 2D rectangle
#'   tempfileplot = tempfile()
#'   png(filename = tempfileplot, height = 400, width = 800)
#'   plot(iris$Petal.Length, iris$Sepal.Width, col = iris$Species, pch = 18, cex = 4)
#'   dev.off()
#'   image_array = aperm(png::readPNG(tempfileplot), c(2, 1, 3))
#'   scene = scene |>
#'     add_object(xy_rect(x = 0, y = 1.1, z = 0, xwidth = 2, angle = c(0, 0, 0),
#'                        flipped = TRUE,
#'                        material = diffuse(image_texture = image_array)))
#'   render_scene(scene, fov = 20, parallel = TRUE, samples = 16)
#'   # Move the camera
#'   render_scene(scene, lookfrom = c(7, 1.5, 10), lookat = c(0, 0.5, 0), fov = 15, parallel = TRUE)
#'   # Change the background gradient to a firey sky
#'   render_scene(scene, lookfrom = c(7, 1.5, 10), lookat = c(0, 0.5, 0), fov = 15,
#'                backgroundhigh = "orange", backgroundlow = "red", parallel = TRUE,
#'                ambient = TRUE,
#'                samples = 16)
#'   # Increase the aperture to blur objects that are further from the focal plane.
#'   render_scene(scene, lookfrom = c(7, 1.5, 10), lookat = c(0, 0.5, 0), fov = 15,
#'                aperture = 1, parallel = TRUE, samples = 16)
#'   # We can also capture a 360 environment image by setting `fov = 360` (can be used for VR).
#'   # The left edge of the image is directly where the camera is pointing--we point the image
#'   # backwards so the full cornell box is in the "center" of the environment map.
#'   generate_cornell() |>
#'     add_object(ellipsoid(x = 555 / 2, y = 100, z = 555 / 2, a = 50, b = 100, c = 50,
#'                           material = metal(color = "lightblue"))) |>
#'     add_object(cube(x = 100, y = 130 / 2, z = 200, xwidth = 130, ywidth = 130, zwidth = 130,
#'                      material = diffuse(checkercolor = "purple",
#'                                         checkerperiod = 30), angle = c(0, 10, 0))) |>
#'     add_object(pig(x = 100, y = 190, z = 200, scale = 40, angle = c(0, 30, 0))) |>
#'     add_object(sphere(x = 420, y = 555 / 8, z = 100, radius = 555 / 8,
#'                        material = dielectric(color = "orange"))) |>
#'     add_object(xz_rect(x = 555 / 2, z = 555 / 2, y = 1, xwidth = 555, zwidth = 555,
#'                        material = glossy(checkercolor = "white",
#'                                          checkerperiod = 10, color = "dodgerblue"))) |>
#'     render_scene(lookfrom = c(278, 278, -10), lookat = c(278, 278, -300), clamp_value = 100,
#'                  fov = 360,  samples = 16, width = 800, height = 800)
#'   # Spin the camera around the scene, decreasing the number of samples to render faster. To make
#'   # an animation, specify the a filename in `render_scene` for each frame and use the `av` package
#'   # or ffmpeg to combine them all into a movie.
#'   t = 1:9
#'   xpos = 10 * sin(t * 18 * pi / 180 + pi / 2)
#'   zpos = 10 * cos(t * 18 * pi / 180 + pi / 2)
#'   image_output = list()
#'   for (i in 1:9) {
#'     image_output[[i]] = render_scene(scene, samples = 16, plot_scene = FALSE,
#'                  lookfrom = c(xpos[i], 1.5, zpos[i]), lookat = c(0, 0.5, 0), parallel = TRUE)
#'   }
#'   rayimage::plot_image_grid(image_output, dim = c(3,3) )
render_scene = function(
  scene,
  width = 400,
  height = 400,
  fov = 20,
  samples = 100,
  camera_description_file = NA,
  preview = interactive(),
  interactive = TRUE,
  deferred_render = FALSE,
  denoise = TRUE,
  camera_scale = 1,
  iso = 100,
  auto_exposure = FALSE,
  film_size = 22,
  min_variance = 0,
  min_adaptive_size = 8,
  sample_method = "sobol_blue",
  max_depth = NA,
  roulette_active_depth = 100,
  ambient_light = NULL,
  lookfrom = c(0, 1, -10),
  lookat = c(0, 0, 0),
  camera_up = c(0, 1, 0),
  aperture = 0.1,
  clamp_value = Inf,
  filename = NA,
  backgroundhigh = "#80b4ff",
  backgroundlow = "#ffffff",
  shutteropen = 0.0,
  shutterclose = 1.0,
  camera_motion_blur = FALSE,
  shutter_speed = NULL,
  focal_distance = NULL,
  ortho_dimensions = c(1, 1),
  tonemap = "raw",
  bloom = TRUE,
  parallel = TRUE,
  bvh_type = "sah",
  environment_light = NULL,
  rotate_env = 0,
  intensity_env = 1,
  transparent_background = FALSE,
  debug_channel = "none",
  plot_scene = TRUE,
  progress = interactive(),
  verbose = FALSE,
  print_debug_info = FALSE,
  new_page = TRUE,
  integrator_type = "rtiow",
  screen_text = NULL,
  screen_line = NULL,
  camera = NULL,
  start_frame = 1,
  end_frame = NA,
  mode = c("auto", "image", "animation", "preview")
) {
  mode = match.arg(mode)
  if (!is.logical(camera_motion_blur) || length(camera_motion_blur) != 1) {
    stop("camera_motion_blur must be a single TRUE/FALSE value.")
  }
  shutter_speed_supplied = !is.null(shutter_speed)
  if (shutter_speed_supplied) {
    validate_shutter_speed(shutter_speed)
  }
  camera_shutter_speed = if (shutter_speed_supplied) shutter_speed else 2
  camera_arg = if (missing(camera)) NULL else camera
  filename_supplied = !missing(filename)
  legacy_camera_supplied = c(
    lookfrom = !missing(lookfrom),
    lookat = !missing(lookat),
    camera_up = !missing(camera_up),
    fov = !missing(fov),
    aperture = !missing(aperture),
    focal_distance = !missing(focal_distance),
    ortho_dimensions = !missing(ortho_dimensions),
    camera_description_file = !missing(camera_description_file),
    camera_scale = !missing(camera_scale),
    iso = !missing(iso),
    film_size = !missing(film_size),
    shutteropen = !missing(shutteropen),
    shutterclose = !missing(shutterclose),
    camera_motion_blur = !missing(camera_motion_blur),
    filename = filename_supplied
  )
  legacy_camera_geometry_supplied = any(legacy_camera_supplied[c(
    "lookfrom",
    "lookat",
    "camera_up",
    "fov",
    "aperture",
    "focal_distance",
    "ortho_dimensions"
  )])
  metadata_supplied = legacy_camera_supplied[c(
    "camera_description_file",
    "camera_scale",
    "iso",
    "film_size",
    "shutteropen",
    "shutterclose",
    "camera_motion_blur",
    "shutter_speed",
    "filename"
  )]
  metadata_overrides = list(
    camera_description_file = camera_description_file,
    camera_scale = camera_scale,
    iso = iso,
    film_size = film_size,
    shutteropen = shutteropen,
    shutterclose = shutterclose,
    camera_motion_blur = camera_motion_blur,
    shutter_speed = camera_shutter_speed,
    filename = filename
  )
  metadata_supplied[["shutter_speed"]] = shutter_speed_supplied
  scene_camera_available = length(ray_scene_cameras(scene)) > 0 ||
    !is.null(camera_arg)
  legacy_filename = if (
    !legacy_camera_geometry_supplied &&
      scene_camera_available
  ) {
    NA_character_
  } else {
    filename
  }
  legacy_camera = render_scene_legacy_camera(
    scene = scene,
    supplied = legacy_camera_supplied,
    lookfrom = lookfrom,
    lookat = lookat,
    camera_up = camera_up,
    fov = fov,
    aperture = aperture,
    focal_distance = focal_distance,
    ortho_dimensions = ortho_dimensions,
    filename = legacy_filename,
    camera_description_file = camera_description_file,
    camera_scale = camera_scale,
    iso = iso,
    film_size = film_size,
    shutteropen = shutteropen,
    shutterclose = shutterclose,
    camera_motion_blur = camera_motion_blur,
    shutter_speed = camera_shutter_speed,
    message_cornell = is.null(camera_arg) &&
      length(ray_scene_cameras(scene)) == 0
  )
  cameras = resolve_scene_camera(
    scene = scene,
    camera = camera_arg,
    legacy_camera = legacy_camera,
    legacy_camera_supplied = legacy_camera_geometry_supplied,
    allow_all = TRUE,
    default_camera = legacy_camera
  )
  if (!legacy_camera_geometry_supplied) {
    cameras = lapply(
      cameras,
      apply_camera_overrides,
      overrides = metadata_overrides,
      supplied = metadata_supplied
    )
  }

  if (length(cameras) > 1) {
    validate_camera_output_filenames(
      cameras,
      mode = mode,
      start_frame = start_frame,
      end_frame = end_frame
    )
    batch_blockers = character()
    if (!camera_batch_metadata_compatible(cameras)) {
      batch_blockers = c(batch_blockers, "camera metadata differs")
    }
    if (!is.null(screen_text) || !is.null(screen_line)) {
      batch_blockers = c(batch_blockers, "screen overlays are enabled")
    }
    if (isTRUE(auto_exposure)) {
      batch_blockers = c(batch_blockers, "auto_exposure is enabled")
    }
    if (isTRUE(deferred_render)) {
      batch_blockers = c(batch_blockers, "deferred_render is enabled")
    }
    if (isTRUE(print_debug_info)) {
      batch_blockers = c(batch_blockers, "print_debug_info is enabled")
    }
    if (
      isTRUE(preview) &&
        isTRUE(interactive) &&
        !identical(mode, "preview")
    ) {
      batch_blockers = c(batch_blockers, "interactive preview is enabled")
    }
    if (length(batch_blockers) == 0) {
      return(render_camera_batch(
        scene = scene,
        cameras = cameras,
        mode = mode,
        start_frame = start_frame,
        end_frame = end_frame,
        width = width,
        height = height,
        preview = if (identical(mode, "preview")) TRUE else preview,
        denoise = denoise,
        samples = samples,
        min_variance = min_variance,
        min_adaptive_size = min_adaptive_size,
        sample_method = sample_method,
        keep_colors = FALSE,
        sample_dist = Inf,
        max_depth = if (is.na(max_depth)) 50 else max_depth,
        roulette_active_depth = roulette_active_depth,
        ambient_light = ambient_light,
        clamp_value = clamp_value,
        backgroundhigh = backgroundhigh,
        backgroundlow = backgroundlow,
        focal_distance = focal_distance,
        ortho_dimensions = ortho_dimensions,
        tonemap = tonemap,
        bloom = bloom,
        parallel = parallel,
        bvh_type = bvh_type,
        environment_light = environment_light,
        rotate_env = rotate_env,
        intensity_env = intensity_env,
        debug_channel = debug_channel,
        plot_scene = if (identical(mode, "preview")) FALSE else plot_scene,
        progress = progress,
        verbose = verbose,
        transparent_background = transparent_background,
        integrator_type = integrator_type,
        force_no_write = identical(mode, "preview")
      ))
    }
    warning(
      "Rendering cameras one at a time because ",
      paste(batch_blockers, collapse = ", "),
      "."
    )
    output = lapply(cameras, function(cam) {
      render_scene(
        scene = scene,
        width = width,
        height = height,
        samples = samples,
        preview = preview,
        interactive = interactive,
        deferred_render = deferred_render,
        denoise = denoise,
        auto_exposure = auto_exposure,
        min_variance = min_variance,
        min_adaptive_size = min_adaptive_size,
        sample_method = sample_method,
        max_depth = max_depth,
        roulette_active_depth = roulette_active_depth,
        ambient_light = ambient_light,
        clamp_value = clamp_value,
        backgroundhigh = backgroundhigh,
        backgroundlow = backgroundlow,
        tonemap = tonemap,
        bloom = bloom,
        parallel = parallel,
        bvh_type = bvh_type,
        environment_light = environment_light,
        rotate_env = rotate_env,
        intensity_env = intensity_env,
        transparent_background = transparent_background,
        debug_channel = debug_channel,
        plot_scene = plot_scene,
        progress = progress,
        verbose = verbose,
        print_debug_info = print_debug_info,
        new_page = new_page,
        integrator_type = integrator_type,
        screen_text = screen_text,
        screen_line = screen_line,
        camera = cam,
        start_frame = start_frame,
        end_frame = end_frame,
        mode = mode
      )
    })
    names(output) = vapply(cameras, function(cam) cam$name, character(1))
    return(invisible(output))
  }

  selected_camera = cameras[[1]]
  render_mode = camera_render_mode(selected_camera, mode)
  if (
    render_mode == "animation" ||
      (render_mode == "preview" && nrow(selected_camera$motion) > 1)
  ) {
    return(render_animation_camera(
      scene = scene,
      camera = selected_camera,
      start_frame = start_frame,
      end_frame = end_frame,
      width = width,
      height = height,
      preview = if (render_mode == "preview") TRUE else preview,
      denoise = denoise,
      samples = samples,
      min_variance = min_variance,
      min_adaptive_size = min_adaptive_size,
      sample_method = sample_method,
      keep_colors = FALSE,
      sample_dist = Inf,
      max_depth = if (is.na(max_depth)) 50 else max_depth,
      roulette_active_depth = roulette_active_depth,
      ambient_light = ambient_light,
      clamp_value = clamp_value,
      backgroundhigh = backgroundhigh,
      backgroundlow = backgroundlow,
      focal_distance = focal_distance,
      ortho_dimensions = ortho_dimensions,
      tonemap = tonemap,
      bloom = bloom,
      parallel = parallel,
      bvh_type = bvh_type,
      environment_light = environment_light,
      rotate_env = rotate_env,
      intensity_env = intensity_env,
      debug_channel = debug_channel,
      plot_scene = if (render_mode == "preview") FALSE else plot_scene,
      progress = progress,
      verbose = verbose,
      transparent_background = transparent_background,
      integrator_type = integrator_type,
      force_no_write = render_mode == "preview"
    ))
  }

  snapshot_filename = if (
    length(selected_camera$filename) == 1 &&
      !is.na(selected_camera$filename) &&
      nzchar(tools::file_ext(selected_camera$filename))
  ) {
    selected_camera$filename
  } else {
    NA_character_
  }

  frame = if (render_mode %in% c("image", "preview")) {
    camera_frame_range(nrow(selected_camera$motion), start_frame, start_frame)[
      1
    ]
  } else {
    1
  }
  camera_args = camera_frame_args(selected_camera, frame)
  lookfrom = camera_args$lookfrom
  lookat = camera_args$lookat
  camera_up = camera_args$camera_up
  fov = camera_args$fov
  aperture = camera_args$aperture
  focal_distance = camera_args$focal_distance
  ortho_dimensions = camera_args$ortho_dimensions
  camera_description_file = camera_args$camera_description_file
  camera_scale = camera_args$camera_scale
  iso = camera_args$iso
  film_size = camera_args$film_size
  shutteropen = camera_args$shutteropen
  shutterclose = camera_args$shutterclose
  camera_motion_blur = camera_args$camera_motion_blur
  shutter_speed = camera_args$shutter_speed
  filename = camera_image_filename(selected_camera, frame)
  if (render_mode == "preview") {
    filename = NA
    preview = TRUE
    plot_scene = FALSE
  }

  init_time()
  if (print_debug_info) {
    message(sprintf(
      "------Debug Info------
RAY_HAS_X11: %s 
HAS_SSE: %s 
HAS_SSE2: %s 
HAS_SSE3: %s 
HAS_SSE41:  %s
HAS_NEON: %s
HAS_OIDN: %s
----------------------",
      ifelse(has_gui_capability(), "TRUE", "FALSE"),
      ifelse(cppdef_HAS_SSE(), "TRUE", "FALSE"),
      ifelse(cppdef_HAS_SSE2(), "TRUE", "FALSE"),
      ifelse(cppdef_HAS_SSE3(), "TRUE", "FALSE"),
      ifelse(cppdef_HAS_SSE41(), "TRUE", "FALSE"),
      ifelse(cppdef_HAS_NEON(), "TRUE", "FALSE"),
      ifelse(cppdef_HAS_OIDN(), "TRUE", "FALSE")
    ))
  }
  if (samples > 256 && sample_method == "sobol_blue") {
    warning(
      '"sobol_blue" sample method only valid for `samples` than or equal to 256--switching to `sample_method = "sobol"`'
    )
    sample_method = "sobol"
  }

  if (width < 3 || height < 3) {
    stop("Must specify a minimum width/height of 3 or more pixels")
  }
  if (
    preview &&
      interactive &&
      has_gui_capability() &&
      !is.numeric(debug_channel) &&
      debug_channel == "none"
  ) {
    controls_lines = c(
      "------------------------ Interactive Mode Controls ------------------------",
      "Move:       W/A/S/D horizontal | Q/Z vertical | E/C step size | F fast travel",
      "Look:       Shift-W/S pitch | Shift-A/D roll | Tab toggle orbit",
      "Lens/Env:   Up/Down FOV | Left/Right aperture | 1/2 focal | 3/4 env rotate",
      "Keyframes:  K save | L last | Shift-L loop | </> prev/next | / delete | M preview/cancel",
      "Mouse:      Left click lookat + focal distance | Right click lookat",
      "Status:     Wide window shows camera/exposure/env/keyframes/loop",
      "Exposure:   ]/[ preview exposure | Shift-]/[ shutter speed",
      "Blur:       B camera motion blur",
      "Atmosphere: H haze | Y altitude queries (haze requires altitude queries)",
      "Snapshot:   Shift-Enter save current preview",
      "General:    P print camera | R reset camera | ESC close"
    )
    if (deferred_render) {
      controls_lines = c(
        controls_lines,
        "Render:     Return start final render"
      )
    }
    controls_message = paste(controls_lines, collapse = "\n")
    message(controls_message)
  }
  print_time(verbose, "Pre-processing scene")
  debug_string = debug_channel
  scene_list = prepare_scene_list(
    scene = scene,
    width = width,
    height = height,
    fov = fov,
    samples = samples,
    camera_description_file = camera_description_file,
    camera_scale = camera_scale,
    iso = iso,
    film_size = film_size,
    min_variance = min_variance,
    min_adaptive_size = min_adaptive_size,
    sample_method = sample_method,
    max_depth = max_depth,
    roulette_active_depth = roulette_active_depth,
    ambient_light = ambient_light,
    lookfrom = lookfrom,
    lookat = lookat,
    camera_up = camera_up,
    aperture = aperture,
    clamp_value = clamp_value,
    filename = filename,
    backgroundhigh = backgroundhigh,
    backgroundlow = backgroundlow,
    shutteropen = shutteropen,
    shutterclose = shutterclose,
    camera_motion_blur = camera_motion_blur,
    shutter_speed = shutter_speed,
    focal_distance = focal_distance,
    ortho_dimensions = ortho_dimensions,
    tonemap = tonemap,
    bloom = bloom,
    parallel = parallel,
    bvh_type = bvh_type,
    environment_light = environment_light,
    rotate_env = -rotate_env,
    intensity_env = intensity_env,
    debug_channel = debug_channel,
    plot_scene = plot_scene,
    progress = progress,
    verbose = verbose,
    sample_dist = Inf,
    deferred_render = deferred_render,
    integrator_type = integrator_type,
    denoise = denoise,
    print_debug_info = print_debug_info
  )
  print_time(verbose, "Pre-processed scene")

  camera_info = scene_list$camera_info
  scene_info = scene_list$scene_info
  render_info = scene_list$render_info
  render_info$transparent_background = transparent_background
  processed_scene = scene_info$scene
  screen_text_native_overlay = screen_text_needs_native_overlay(screen_text)
  screen_line_native_overlay = screen_line_needs_native_overlay(screen_line)
  render_info$screen_text_preview = if (
    isTRUE(preview) || screen_text_native_overlay
  ) {
    prepare_screen_text_preview(screen_text)
  } else {
    list(active = FALSE)
  }
  render_info$screen_text_occlusion = prepare_screen_text_occlusion(screen_text)
  render_info$screen_text_native_overlay = screen_text_native_overlay
  render_info$screen_line_preview = if (
    isTRUE(preview) || screen_line_native_overlay
  ) {
    prepare_screen_line_preview(screen_line)
  } else {
    list(active = FALSE)
  }
  render_info$screen_line_occlusion = prepare_screen_line_occlusion(screen_line)
  render_info$screen_line_native_overlay = screen_line_native_overlay

  camera_info$preview = preview
  camera_info$interactive = interactive
  camera_info$auto_exposure = auto_exposure
  camera_info$camera_motion_blur = isTRUE(camera_motion_blur)
  camera_info$shutter_speed = shutter_speed
  camera_info$snapshot_filename = snapshot_filename
  camera_info$keyframe_motion_args = normalize_keyframe_motion_args(
    selected_camera$keyframe_motion_args
  )
  debug_channel = scene_info$debug_channel # converted to numeric

  #Pathtrace Scene
  rgb_mat = render_scene_rcpp(
    scene = processed_scene,
    camera_info = camera_info,
    scene_info = scene_info,
    render_info = render_info
  )
  screen_camera_info = attr(rgb_mat, "screen_camera_info")
  if (!is.null(screen_camera_info)) {
    camera_info$screen_camera_origin = screen_camera_info$origin
    camera_info$screen_camera_u = screen_camera_info$u
    camera_info$screen_camera_v = screen_camera_info$v
    camera_info$screen_camera_w = screen_camera_info$w
    camera_info$screen_camera_fov = screen_camera_info$fov
    camera_info$screen_camera_ortho_dimensions =
      screen_camera_info$ortho_dimensions
  }
  if (!is.null(attr(rgb_mat, "keyframes"))) {
    message(
      "Saving camera keyframes: Call `get_saved_keyframes()` function to return them."
    )
    keyframes = do.call(
      rbind,
      lapply(attr(rgb_mat, "keyframes"), as.data.frame)
    )
    assign("keyframes", keyframes, envir = ray_environment)
  }
  preview_exposure = attr(rgb_mat, "preview_exposure")
  if (is.null(preview_exposure)) {
    preview_exposure = 1
  }
  return_array = post_process_scene(
    rgb_mat,
    camera_info$iso,
    use_iso = FALSE,
    tonemap,
    debug_string,
    filename,
    plot_scene,
    bloom,
    new_page,
    transparent_background = transparent_background,
    auto_exposure = auto_exposure,
    verbose = verbose,
    screen_text = screen_text,
    screen_line = screen_line,
    camera_info = camera_info,
    screen_text_visible = attr(rgb_mat, "screen_text_visible"),
    screen_text_overlay = attr(rgb_mat, "screen_text_overlay"),
    screen_line_visible = attr(rgb_mat, "screen_line_visible"),
    screen_line_overlay = attr(rgb_mat, "screen_line_overlay"),
    exposure_adjustment = preview_exposure
  )
  print_time(verbose, "Post-processed image")

  return(invisible(return_array))
}

Try the rayrender package in your browser

Any scripts or data that you put into this service are public.

rayrender documentation built on Sept. 16, 2026, 9:08 a.m.