.. _renderers: Renderers ========= Since 3.0.0 every plot carries a ``renderer`` switch: .. code-block:: python3 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 :ref:`cinematic` 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 :ref:`cinematic`. The same roughness/metalness sweep under both renderers - dielectrics in the front row, metals in the back, roughness growing to the right. First ``simple``: .. k3d_plot :: :filename: plots/renderers_simple_plot.py and the identical scene under ``advanced`` (switch the environment in the panel to see the light change): .. k3d_plot :: :filename: plots/renderers_advanced_plot.py 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. What they mean for a volume, which has no environment reflection to trade diffuse light for, is on :ref:`volumes`. 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. .. code-block:: python3 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: .. code-block:: python3 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: .. k3d_plot :: :filename: plots/renderers_environment_plot.py .. 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. 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 - see :ref:`volumes`. Two knobs (shown in the panel only when the advanced renderer is active): .. code-block:: python3 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 ------------ .. code-block:: python3 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): .. k3d_plot :: :filename: plots/renderers_cinematic_plot.py 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. A volume is where it differs most from the other two, and that comparison is on :ref:`volumes`. Its parameters, the environments that light it, and the list of what changes object by object live on their own page: :ref:`cinematic`. What you are rendering on ------------------------- All three renderers depend on the GPU the browser actually got, and that is not always the one you expect. A container that loses its GPU passthrough does not fail: it falls back to a software renderer, every image still comes out correct, and only the clock tells you - which is a trap when the thing you are measuring is time. K3D reports it at startup. In a browser the lines land in the console; from a headless session they are a dict: .. code-block:: python3 from k3d.headless import k3d_remote, get_headless_driver plot = k3d.plot() headless = k3d_remote(plot, get_headless_driver()) headless.get_gl_info() .. code-block:: text {'vendor': 'Google Inc. (NVIDIA)', 'renderer': 'ANGLE (NVIDIA, NVIDIA GeForce RTX 4070 Laptop GPU Direct3D11 vs_5_0 ps_5_0, D3D11)', 'unmaskedVendor': 'Google Inc. (NVIDIA)', 'unmaskedRenderer': 'ANGLE (NVIDIA, NVIDIA GeForce RTX 4070 Laptop GPU (0x00002860) Direct3D11 vs_5_0 ps_5_0, D3D11)', 'version': 'WebGL 2.0 (OpenGL ES 3.0 Chromium)', 'depthBits': 24, 'stencilBits': 8, 'maxTextureSize': 16384, 'maxTextureImageUnits': 16} The tell is the renderer string: software rendering names itself there, usually ``SwiftShader``. The limits are not a tell, and reading them that way misleads - the software renderer advertises 32 fragment texture units where the ANGLE/D3D11 path above offers 16, so a scene that fits in the container can still run out of units on the real card. ``cinematic`` is the renderer that notices, since it needs fifteen of them for a traced volume. A headless session says nothing about its own progress. It answers a /ping every few seconds for as long as it is open, and ``sync()`` runs once per frame of an animation - a line for each would bury whatever the cell was asked to show. Warnings and errors are never suppressed. To get the rest back: .. code-block:: python3 import logging logging.getLogger('k3d.headless').setLevel(logging.DEBUG)