Renderers#

Since 3.0.0 every plot carries a renderer switch:

plot = k3d.plot(renderer='advanced')
# or at any moment:
plot.renderer = 'advanced'
simple (default)

The classic rasteriser: a fixed rig of four lights that follows the camera, and the cheapest of the three. Materials are physically based here too, so a surface answers to roughness and metalness without any environment map - what advanced adds is where the light comes from, not how the material responds to it.

advanced

Image-based lighting plus ambient occlusion. All light comes from an environment map, materials are physically based, and a GTAO pass grounds the geometry with contact shadows. Switching back and forth is a single assignment and never rebuilds the scene. Reckon on half again the cost of simple: measured over the visual suite it draws a frame in 1.56x the time, more where a scene is limited by the pixels it fills and less where it is limited by geometry.

cinematic (experimental)

Progressive path tracing: light bounces between surfaces, so shadows, reflections and colour bleeding come out of the simulation rather than from a screen-space approximation. The image refines sample by sample and restarts whenever the camera or the scene changes. Requires WebGL2 with renderable float textures - see Cinematic rendering below.

The golden rule: the renderer changes the light, never what you asked to draw. Unlit primitives - points with dot/flat shaders, line(simple), line(thick), labels, texts, wireframes - are a deliberate choice of no shading and look identical in simple and advanced. cinematic keeps the shape of everything you asked for but not always the implementation, and it has no concept of an unlit surface; the differences are listed in Cinematic rendering.

The same roughness/metalness sweep under both renderers - dielectrics in the front row, metals in the back, roughness growing to the right. First simple:

and the identical scene under advanced (switch the environment in the panel to see the light change):

Materials#

All lit objects use physically based materials with two knobs:

roughness

0.0 = polished mirror-like highlight, 1.0 = fully matte. Default 0.4.

metalness

0.0 = dielectric, 1.0 = metal (the surface reflects only its environment, tinted by its own colour). Default 0.0.

shininess was removed in 3.0.0. The equivalent conversion is roughness = sqrt(2 / (shininess + 2)); passing shininess raises a loud TraitError with that formula, and legacy .k3d snapshots are converted automatically on load.

Both parameters live in the 0-1 range and are validated at assignment.

Volumetric objects (volume, mip) carry the same two knobs for the specular highlight of their isodensity surface (default roughness=0.25) - lower roughness makes noisy gradients sparkle like wet tissue, which may even be desired. metalness tints and strengthens the highlight with the transfer-function colour; it never darkens the body, because a volume has no environment reflection to replace the lost diffuse light with.

Environments#

In advanced the environment map is the only light source. Every map is energy-normalised, so the environment carries the shape of the light while plot.lighting stays the exposure knob.

plot.environment = 'studio'            # procedural preset
plot.environment = 'burnt_warehouse'   # photographic catalog (Poly Haven, CC0)
plot.environment = my_hdr_array        # any (H, W, 3) float32 equirect
plot.environment_rotation = np.pi / 3  # spin it around the scene's up axis

Procedural presets (neutral - the default, studio, outdoor) travel as plain names and are generated deterministically on the CPU. The photographic catalog ships with the package:

import k3d.environments
k3d.environments.available()
# ['autoshop_01', 'brown_photostudio_02', 'burnt_warehouse',
#  'moonless_golf', 'venice_sunset']

A photographic map with a filmic curve changes the mood entirely - the same spheres again, under venice_sunset with AgX tone mapping:

Note

The photographic maps live in the Python package, so a kernel-less page cannot resolve their names. An exported HTML snapshot therefore offers only what it can regenerate: the procedural presets plus the map that was baked into it at export time. A page may widen that list by including the sideload script generated by k3d.environments.save_js(path) next to standalone.js - this documentation does exactly that, which is why the dropdown above carries the full catalog.

Since 3.0.0 a volume composes correctly with meshes that intersect it when depth peeling is enabled (plot.depth_peels >= 3 - fewer layers make the segmentation too coarse to be predictable). The ray march is split into segments bounded by the peel layers, so geometry inside the volume occludes and is occluded sample-accurately, in both renderers:

Volumetric data (volume, mip) and the points 3d impostors read the same environment: diffuse light from the map’s spherical harmonics plus one dominant directional light distilled from it, so a directional HDRI models volumes consistently with every mesh in the scene.

Ambient occlusion#

advanced always includes a GTAO pass with spatial denoising. The result is deterministic and screenshots are seam-free at any rendering_steps. Real surfaces occlude; volumes and MIPs contribute the shell where their accumulated opacity crosses one half, so dense structures cast and receive contact shadows too. Two knobs (shown in the panel only when the advanced renderer is active):

plot.ao_radius = 0.02    # occlusion radius, fraction of the scene diagonal
plot.ao_strength = 3.0   # shadow-deepening exponent; 0 disables

The default radius (0.07) suits typical scenes; dense point clouds and closed interiors (the inside of a skull) usually want a smaller one.

Tone mapping#

plot.tone_mapping = 'agx'   # or 'aces'; 'none' is the default

Filmic curves compress bright HDR highlights - useful with high-contrast photographic environments. All three renderers share the same curve, applied as the last step before the frame reaches the screen.

Cinematic: path tracing#

The third renderer traces the light instead of approximating it. Soft shadows, glossy reflections and colour bleeding come out of the simulation, the image refines sample by sample, and any change starts it over. The same material sweep as above, path traced (the counter in the corner tells you when it has settled):

It is experimental, it needs WebGL2 with renderable float textures, and it keeps the shape of everything you asked for without always keeping the implementation. Its parameters, the environments that light it, and the list of what changes object by object live on their own page: Cinematic rendering.