Changelog: v2.9

v2.9.4 (unreleased)

Rebuild applications after updating (InstancedVisuals, OpenGLMesh, RayraiGlobalAsset, RayraiWindow and assimp::AsyncModelLoadResult ABI changes).

rayrai

  • Faster foliage depth prepass, with unchanged images (forest frames verified bit-identical with still and turning cameras). The depth prepass first draws the foliage instances that the camera’s last colour pass kept, summarizes that depth in tiles again, and then tests the other instances against it on the GPU, so foliage hidden behind nearer foliage no longer reaches the prepass: the forest prepass draws about 41M grass triangles per frame instead of 51M, as many as the colour pass. Instanced visuals that are not tested for occlusion, such as rocks, now draw first in the depth prepass, so their depth culls foliage in both phases. Each collected foliage batch keeps one bit per instance for each of up to four cameras. RTX 5070, 1280x800: forest 16.2 → 15.5 ms with a still camera and 22.5 → 21.9 ms with a turning one; the city is unchanged. FoliageRenderDiagnostics::sceneGpuOcclusionFilteredDrawCalls now also counts the prepass’s second-phase draws.

  • Faster foliage colour pass, with unchanged images (forest frames verified bit-identical with still and turning cameras). Where the depth prepass draws opaque foliage with its depth-only fragment stage, that stage now records which instances have a fragment passing the depth test, counting fragments that tie the stored depth. The colour pass, which tests the same finished depth, skips the instances the prepass drew without one, and the next prepass draws the instances the colour pass kept first: the forest’s colour pass draws about 6,700 grass instances instead of 12,400, 6,100 of them visible. Cameras without alpha-to-coverage (single-sample cameras, such as most RGB sensors) now also draw the prepass of opaque foliage with the depth-only fragment stage, whatever its alpha. Recording needs OpenGL 4.3 and GL_ARB_shader_draw_parameters; without them the colour pass draws as before. RTX 5070, 1280x800: forest 15.1 → 13.1 ms with a still camera and 21.5 → 19.6 ms with a turning one; the city and rayrai_city are unchanged.

  • Faster scenes with many instanced props, with unchanged images (city frames identical up to the existing one-level run-to-run noise; forest frames bit-identical). Transparent instanced visuals now skip the instances outside the camera’s view, as opaque ones already did; every instance used to be drawn. After a camera’s opaque depth prepass, instanced visuals that the foliage collector does not take (fewer than 4096 instances, with enough geometry in view) test each instance’s box against that depth on the GPU (OpenGL 4.3), and the colour and transparent passes draw only the instances that can show, in their order, from indirect commands; one filter run serves every batch of the camera. RTX 5070, 1280x800: city 6.75 → 5.46 ms with a still camera and 8.80 → 7.51 ms with a turning one; rayrai_city 7.19 → 5.89 ms per frame; the High and Ultra viewer scenes of rayrai_benchmark about 10% faster; the forest is unchanged. RAYRAI_DEBUG_DISABLE_INSTANCE_OCCLUSION turns the depth test off for comparisons.

  • InstancedVisuals::sortInstancesBackToFront() leaves instances already in back-to-front order untouched. It used to reorder them and mark their data changed on every call, so a sorted transparent batch rebuilt its instance caches and uploads, and kept the static shadow layer from being reused, every frame. RenderQualitySettings::sortTransparentInstances, on in the presets, turns sorting on for every batch when the settings are applied, so applications that set quality settings after creating their scene were affected: the city with its High settings applied after loading went from 10.71 to 5.44 ms (with the culling above). Sorted transparent batches now also skip instances outside the view and take the depth test above.

  • Faster contact shadows. Each pixel blurs nine contact-shadow rays (its centre, 1.5 pixels along each axis and 3.5 pixels along each diagonal), and neighbouring pixels share most of them. The contact shadow pass now marches each shared ray once, four rays per pixel instead of nine, into a single-channel float texture at twice the image’s resolution (about 33 MB at 1920×1080, 8 MB before), and the post program filters every pixel from it. Every program now places a ray by its integer half-pixel coordinates, so the pass and the post program still give the same image bit for bit. A ray that starts exactly on a texel boundary may now read the neighbouring texel, which changes isolated pixels at contact-shadow edges: 77 of 1,024,000 pixels in the city, by at most 19 levels (turning the camera by 0.001° changes 37% of them). RTX 5070: city 5.46 → 4.62 ms with a still camera and 7.52 → 6.68 ms with a turning one (1280×800), rayrai_city 5.93 → 5.06 ms per frame, and at 1920×1080 rayrai_frame_overhead_benchmark --contact-shadows 5.90 → 3.24 ms (High) and 6.38 → 3.72 ms (Ultra). Contact shadows are off in every preset; scenes without them are unchanged. The new rayrai_frame_overhead_benchmark --contact-shadows option turns them on.

  • Faster GPU occlusion filtering, with unchanged images (forest frames verified bit-identical). The pass that compacts the visible instances reuses each instance’s verdict from the counting pass instead of testing its depth tiles again. RTX 5070, 1280×800: forest 13.06 → 12.86 ms with a still camera and 19.62 → 19.43 ms with a turning one; rayrai_foliage_paths_benchmark alpha prepass with occlusion 5.40 → 5.35 ms.

  • Faster shadow cascades while the camera moves, with unchanged images (forest frames verified bit-identical; city frames identical up to the existing one-level run-to-run noise; a new test compares every frame with one drawn with every caster). Cascaded maps that a render draws again for its camera are now drawn after the camera’s opaque depth prepass instead of before its scene pass, and skip the instanced casters whose shadows reach no surface the camera can see. On the GPU (OpenGL 4.3), each instance’s box, grown by the receivers’ shadow filter footprint and swept along the light down to the lowest point the camera can see, is tested against the prepass depth and the cascade’s range of view depths; each cascade draws the remaining instances from indirect commands. Ordinary instanced visuals and collected foliage are filtered, for the viewer and for external cameras such as RGB sensors; the cached static layer still holds every caster. RTX 5070, 1280×800: city with a turning camera 6.69 → 5.89 ms, forest 19.41 → 19.31 ms, still cameras unchanged; rayrai_frame_overhead_benchmark at 1080p with its moving camera: High with 64 instanced groups 1.095 → 1.073 ms and with 64 foliage groups 1.109 → 1.092 ms. The new ShadowCacheDiagnostics::receiverDepthFilteredMaps counts the cascades drawn this way; RAYRAI_DEBUG_DISABLE_SHADOW_RECEIVER_DEPTH_FILTER turns it off. The foliage depth-pass tuner leaves those cascades out of its timings.

  • Fixed instanced visuals, such as the rocks of the forest example, drawing the wrong instances for one frame when a moving camera stopped (they flickered dark). The camera’s cascaded shadow maps, drawn after its depth prepass while it moves, build the cached static layer on the first still frame; those shadow draws reused the buffer that held the instances the camera’s depth-tile test had kept for its colour pass. The kept instances now have a buffer of their own.

  • Fixed cascaded shadow maps drawn after a moving camera’s depth prepass occasionally missing a few casters whose shadows were visible (a few pixels lighter for a frame; seen on an RTX 5070 with driver 595). The receiver filter now uploads each map’s instance records, draw table and indirect commands to new buffers instead of respecifying the same ones.

  • ShadowCacheDiagnostics::receiverDepthFilteredMaps also counts the cascades of a render that composes the cached static layer with the moving casters after the depth prepass.

  • Much faster scene loading, with unchanged meshes, LOD chains, textures and images (forest frames verified bit-identical; city frames identical up to the existing one-level run-to-run noise; regenerated LOD and prepared-geometry sidecars hold byte-identical vertices and indices). RTX 5070, 20 hardware threads, from launch until every mesh has loaded: rayrai_forest 2.74 → 0.38 s and rayrai_city 9.70 → 0.51 s; the first launch after updating (caches rebuilt) 15.2 → 7.0 s and 17.3 → 7.0 s; a launch with no caches at all 17.6 → 9.3 s and 21.6 → 11.1 s; rayrai_rscene_load_benchmark city 3.23 → 0.34 s, forest 3.88 → 0.33 s, desert 1.19 → 0.94 s. Frame times after loading are unchanged.

    • Each update() and external-camera render spends up to 8 ms of render-thread time on pending loads, and an asset keeps advancing until it waits for a worker; it used to advance one stage of one asset per frame (the city needed 475 frames). Larger models go first.

    • Model imports and texture decodes share up to eight worker threads; each import used to start a thread of its own (the city started over a hundred at once, and its imports took 0.2 s each instead of milliseconds). Up to 256 MiB of decoded images wait for upload (64 MiB before); a model’s images are queued together, larger models first, and the render thread finishes an import only once they are decoded instead of decoding them itself. Images of three channels are decoded with an opaque alpha into four: stb_image decodes them faster and OpenGL uploads them into the same RGB textures much faster.

    • Imported geometry and instanced LOD chains of different assets are prepared at the same time, on up to half the hardware threads (one of each at a time before). The render thread only builds an import’s materials: workers convert its vertices and free the import. A large LOD chain builds up to three of its levels at once (about 1 GB of memory per level for a 1.7M-vertex tree), and tangents of meshes with at least 65,536 vertices or triangles are generated on up to eight threads, with the same results.

    • Models of at least 65,536 vertices keep their prepared geometry (foliage vertex order, tangents, UV statistics) beside the model as rayrai_cache_<file>.rgeo (the forest’s three largest models: 180 MB), keyed by the imported arrays, so a later load converts no vertex. Both these and the LOD sidecars (.lods, in a new format with a 64-bit hash) store each mesh’s bounds, content digest and index range, and are read straight into vertex arrays; existing sidecars are rebuilt on the first launch. Large sidecars are read while their model still imports, and a matching LOD sidecar needs no copy of the source meshes and no new hash of them.

    • Uploads go through persistently mapped staging buffers that the worker threads fill, so the render thread issues GPU copies instead of copying textures and vertex buffers into the driver itself (OpenGL 4.4 or GL_ARB_buffer_storage; up to two 128 MiB blocks per context, made while loads use them and freed once loading is done). Prepared meshes and compact foliage vertex buffers use immutable buffer storage, and loaded textures define their mip levels before the image is uploaded: both upload about three times faster on NVIDIA. Editing such a mesh moves it to new buffers, so every edit still reaches the GPU. With OpenGL 4.3, a compute shader copies a mesh’s compact foliage vertex stream out of its vertex buffer instead of the CPU building and uploading it.

    • Compute programs are kept in the shader binary cache too, so a cached launch compiles no shader. Shader::setCompileThreadCount() (called by RayraiWindow’s constructor) now applies the driver compile thread count at once and skips it when the context already holds it: setting it after programs were loaded stalled the NVIDIA driver for about 0.1 s each time.

    • With asynchronous loading, the HDR background of a .rscene scene is decoded and resampled on a worker thread and appears once a later render installs it (pendingAsyncMeshLoadCount() counts it until then; a background set meanwhile replaces it); the decoded image is no longer copied. Visuals made while their mesh file loads use the bounds the import recorded instead of scanning the vertices again.

    • Models whose prepared geometry is stored also keep how they were imported, as rayrai_cache_<file>.rimp beside the model: their materials, embedded textures and what the import measured of their meshes, with the content digest of every file the import read and whether each file it looked for existed. While those files are unchanged, a later load restores the description and the prepared geometry and runs no import at all (the forest’s largest tree took 0.2 s of a worker to import); any change, such as a buffer or material library edited in place or a file that appears, imports the model again, as does a prepared-geometry sidecar found damaged after the restore.

    • The bundled stb_image decodes baseline JPEG images about 1.3 times faster: its Huffman decoder keeps the bit buffer in registers, takes whole words from memory and looks codes up in 10-bit tables. Every input decodes, or fails, exactly as with stb_image 2.28 (verified on 849 JPEG files, from memory and from files, and on damaged and truncated ones).

    • Sidecars are read a chunk at a time, each chunk checked while it is in the cache and appended once to its array, which Linux backs with huge pages on request (faulting in small pages took longer than reading the file): reading the forest tree’s 240 MB LOD sidecar takes about half as long. Large LOD sidecars are read from the moment an instanced visual is created instead of its first draw.

    • Meshes and LOD levels that fit the staging buffers wait for room, which completed copies free every frame, instead of being copied by the render thread; a model whose textures another load staged waits for their copies instead of spinning on the render thread.

    • Generated sky and image-based-lighting cubemaps and heightmap visual vertices are computed by the calling thread with idle decode workers helping (they are never started for it), with identical results. Each model’s mesh preprocess metadata is read, or computed and stored, by the worker that prepares its geometry instead of the render thread.

    • The render thread resolves fewer file paths: a texture load resolves its file once (an absolute path loaded before not at all), stored prefetch keys are reused, and an import resolves its model directory once (the city’s render thread: 3,219 → 550 resolutions, each several system calls).

    • The forest example starts loading its plant and rock models before it sets up the sky and the ground.

  • New: RayraiGlobalAsset::pollAsyncLoadsUntil(deadline), InstancedVisuals::pollAsyncMeshPreparation(deadline), an OpenGLMesh::createCpuGeometry() overload that takes the vertex and index arrays over, OpenGLMesh::preparedAttributes(), restorePreparedGeometry(), precomputeVertexSummaries(), vertexSummaries(), adoptVertexSummaries(), contentDigest(), uploadsCompactIndices() and an uploadPreparedGeometry() overload taking OpenGLMesh::StagedGeometry (internal, for the loading caches and staging buffers), assimp::importedTexturesDecoded() and assimp::AsyncModelLoadResult::geometry (internal).

  • Build: instanced occlusion and shadow receiver filtering scopes are owned by the draw shaders instead of thread-local pointers. Render planning and diagnostics were moved out of RayraiWindow.cpp to keep the source file within its frozen size limit.

v2.9.3 (2026-10-13)

Rebuild applications after updating (Camera, OpenGLMesh and RayraiWindow ABI changes).

rayrai

  • Faster shadow passes with unchanged images (verified bit-identical). Shadow casters whose materials cannot discard (no alpha mask, and no alpha blend while transparent shadows dither) draw with a depth program without a fragment stage, so the GPU tests depth before shading. When every render draws its shadow maps again (updateShadowsEveryFrame with moving casters and foliage wind, as in the High and Ultra presets), the main map and the directional additional-light tiles rasterize only the texels the camera’s surfaces can sample and skip casters whose shadows cannot reach them. The additional-light shadow atlas is cleared whole only when its layout changes. ANYmal walking on a heightmap, RTX 5070: an RGB-D sensor frame (640x480 RGB with post-processing and depth, both read back) 0.71 → 0.65 ms (High) and 0.85 → 0.71 ms (Ultra); the getting-started sensor flow (RGB, the depth camera’s color view and depth) 1.05 → 0.98 ms and 1.33 → 1.07 ms; 1920x1080 viewer frame with a following camera 1.00 → 0.93 ms and 1.41 → 1.22 ms.

  • Lower frame latency, with unchanged images (verified bit-identical). Mesh draws with a program that reads no material textures, such as the depth programs for shadow casters that cannot discard and for depth cameras, no longer validate and bind those textures. The renderer flushes after the main shadow map when additional-light shadow maps follow, so the GPU starts on it sooner. The screen-space AO prepass runs with the leanest post program, which computes the same AO with fewer registers than one compiled with more effects, and AO evaluates its per-pixel derivatives once instead of once per band. ANYmal walking on a heightmap, RTX 5070: RGB-D sensor frame (640x480 RGB with post-processing and depth, both read back) 0.65 → 0.59 ms (High) and 0.72 → 0.65 ms (Ultra); the getting-started sensor flow 0.97 → 0.88 ms and 1.06 → 0.99 ms; 1920x1080 viewer with a following camera 0.93 → 0.88 ms and 1.24 → 1.17 ms, and 1.22 → 1.08 ms and 1.63 → 1.40 ms when an effect outside the presets (for example a vignette) selects the full post program.

  • Faster large outdoor scenes, with unchanged images (forest frames verified bit-identical; city frames identical up to the existing one-level run-to-run noise). A visual that casts shadows joins the static shadow layer once it has stayed unchanged for 30 shadow updates, and so does wind-animated foliage while the wind settings hold. Cascaded shadow maps copy back only the texels the moving casters drew the frame before instead of whole layers. Contact shadows run in a pass of their own with a leaner program. For opaque foliage under the depth prepass and alpha-to-coverage, the renderer times writing its depth in the colour pass against drawing it in the prepass for each camera and keeps the faster mode; in the prepass, foliage whose alpha is always one draws with a depth-only fragment stage. RTX 5070, 1280x800: forest example 23.6 → 17.4 ms with a still camera and 25.9 → 24.8 ms with a turning one; city example 9.8 → 6.9 ms still and 9.6 → 9.0 ms moving, and rayrai_city reports 9.8 → 7.3 ms per frame. rayrai_frame_overhead_benchmark at 1080p: Ultra with mixed foliage 1.71 → 1.61 ms, the other cases within noise.

  • Faster foliage occlusion culling, with unchanged images (forest frames verified bit-identical with still and turning cameras). Foliage the renderer collects into shared draws now tests its instances against the camera’s depth tiles on the GPU (OpenGL 4.3 compute shaders) and keeps the visible ones in their original order; the draws take their instance counts from indirect commands. The renderer no longer waits for the depth tiles before the foliage prepass and colour pass, and multisampled cameras use 4×4-pixel tiles like single-sample ones. Draws the collector cannot take keep the CPU test, and OpenGL 4.1 (macOS) has no foliage occlusion culling, as before. RTX 5070, 1280x800: forest 16.7 → 15.8 ms with a still camera and 24.0 → 22.0 ms with a turning one (the forest example now reaches the 60 Hz display rate: 17.6 → 16.7 ms per frame); the city is unchanged. rayrai_foliage_paths_benchmark: alpha prepass with occlusion 6.30 → 5.40 ms. The new FoliageRenderDiagnostics::sceneGpuOcclusionFilteredDrawCalls counts the draws filtered on the GPU.

  • Fix: with more than one directional shadow cascade (directionalShadowCascadeCount), a camera synced to a RaiSim sensor could show a straight-edged, unshadowed wedge in a bottom corner of its image, near the camera, at some headings. syncRaisimCameraPose left Camera::right as the camera was constructed, and the cascades were fitted to the frustum it spanned. syncRaisimCameraPose now sets right as well, and the cascades are fitted to the view matrix’s own axes (front and up), so a camera whose right was set by hand and left stale is covered too. Cameras whose right already matched render as before; the feature showcase’s cascaded shadow scene (11_shadow_cutout_atlas), whose camera helper left right stale, changes at shadow edges (0.9% of pixels, PSNR 62 dB).

  • Behaviour change: with GPU occlusion filtering, InstancedVisuals::foliageImpostorDiagnostics().renderedInstanceCount and the window’s sceneFoliageRenderedInstanceVisits count the instances sent to the GPU test rather than those it keeps, and conservativeOcclusionCullingDiagnostics() reports no whole-group depth tests for such batches, whose instances are each tested on the GPU.

  • OpenGLMesh::drawInstancedIndirectWithVao takes an optional byte offset of the command in the indirect buffer.

  • RaisimObject::shadowDepthMayDiscard() and Visuals::shadowDepthMayDiscard() report whether a shadow depth draw of the object can discard fragments.

  • Camera::postProcessDoF() takes optional programs for the AO prepass (aoPrepassShader) and the contact shadow pass (contactShadowShader).

  • Visuals::shadowMaterialSignature() (internal) hashes the material inputs of a visual’s shadow depth draws, so cached shadow layers notice materials edited in place.

  • Documentation: the threading contract now states that rendering reads the world. Render calls read the bodies’ poses while they issue draw calls (updateObjectLists takes no snapshot), so an application that steps the world on another thread must keep it out for the whole render call, for example by holding the world’s mutex; reading the image back can follow after releasing it.

v2.9.2 (2026-10-12)

Rebuild applications after updating (Camera, RayraiWindow and RayraiWindow::SynchronousMeshLoadingScope ABI changes).

rayrai

  • Faster sensor readback with unchanged images (verified byte-identical on NVIDIA). Camera::getRawImage and readSceneColorRgbaAsync now convert the scene colour to 8 bits on the GPU; before, the driver converted every pixel on the CPU. getRawImage also flips depth rows on the GPU, and SensorStorageMode::SENSOR_SETTER reads straight into the sensor’s image instead of copying it. Render and readback at 640×480: RGB 1.20 → 0.14 ms, depth 0.27 → 0.17 ms, readSceneColorRgbaAsync 1.030 → 0.161 ms; an ANYmal RGB-D frame 2.04 → 0.87 ms with the Fast preset and 2.42 → 1.32 ms with Ultra (Core Ultra 7 265K, RTX 5070). A camera allocates a staging target the size of its image on its first readback (listed in renderTargetDiagnostics()).

  • Faster Ultra preset. With legacy temporal AA (TemporalAaMethod::Legacy, which Ultra uses), screen-space AO is evaluated once per pixel and cached in the alpha channel of the TAA history, as it already was without TAA, instead of once per denoiser tap. Ultra frame 2.90 → 1.52 ms at 1080p and 1.56 → 0.95 ms at 720p (rayrai_frame_overhead_benchmark; Core Ultra 7 265K, RTX 5070). Ultra images change slightly at silhouettes (0.25% of pixels at 1080p, PSNR 71 dB); Fast, Balanced and High images are byte-identical.

  • Faster shadows for robots on terrain, with unchanged images (shadow maps and frames verified bit-identical). The main light’s single shadow map keeps a cached layer of the static casters: each frame copies back only the region the moving casters covered and draws those casters again. It does so while the static casters include more than batched primitives and the moving casters have known bounds and cover at most a quarter of the map. Additional-light shadow tiles copy back only that region instead of the whole tile, static layers are built only once two consecutive frames share their light, region and view (a moving camera no longer pays for a static pass and a copy every frame), and shadow passes draw the largest static caster, usually the terrain, first. ANYmal on a heightmap, RTX 5070: 640x480 RGB camera render 0.33 → 0.23 ms (High) and 0.50 → 0.25 ms (Ultra) while the robot stands, 0.36 → 0.29 ms and 0.54 → 0.42 ms while it walks; 1920x1080 viewer frame 1.02 → 0.92 ms (High) and 1.45 → 1.18 ms (Ultra) with a still camera, 1.07 → 1.00 ms and 1.53 → 1.40 ms with a following camera. Scenes on a ground plane are unchanged.

  • Light::shadowPrePassInto(framebuffer, clearDepth) prepares a light’s 2D shadow pass in another framebuffer.

  • rayrai_frame_overhead_benchmark --terrain replaces the ground plane with a static heightmap.

  • Behaviour change: Camera::getRawImage for an RGBCamera is a fatal error on a camera without a scene colour target, such as one constructed from a DepthCamera and never used for a colour render, and readSceneColorRgbaAsync returns false there. Both used to read the default framebuffer.

Sensors

  • RGBCamera::getImageBuffer() has a non-const overload, like DepthCamera::getDepthArray(), for writing MANUAL images in place; keep its size at width * height * 4 bytes.

TCP viewer and server

  • The TCP viewer renders a server’s RGB and depth sensors faster, with unchanged images and depth previews (verified byte-identical on NVIDIA with glibc). It uses the faster readback, keeps one RaiSim camera per sensor stream instead of constructing one per request (a DepthCamera computes a ray per pixel), and shades depth previews from a table instead of a logarithm per pixel. One RGB and one depth request at 640×480: 4.1 → 0.73 ms, or 5.9 → 1.1 ms when every depth pixel is valid (Core Ultra 7 265K, RTX 5070).

  • Fix: closing a pane, or the viewer, while the meshes of the server’s scene were still loading wrote into the closed pane’s destroyed mesh assets, a heap use-after-free once nothing else kept them allocated. A RayraiWindow::SynchronousMeshLoadingScope may now outlive its window; it then ends without touching the window’s mesh assets.

Fixes and build

  • Windows: building libraisim from source no longer fails with LNK1189. Its DLL exports leave out std:: and Eigen:: template instantiations, which pushed it past the 65,535 symbols a DLL can export.

v2.9.1 (2026-10-12)

Rebuild applications after updating (RayraiWindow and InstancedVisuals ABI changes).

rayrai

  • Depth cameras: RayraiWindow::renderDepthPlaneDistance(), which renders the linear depth of depth sensors, now draws visual objects and instanced visuals for its camera the way the color pass does. It used to draw every instance of every batch at full detail with no culling, so the depth image of a large scene took longer than its color image. Instanced batches are now culled per instance (on the GPU for large batches) and pick their mesh LOD for the depth camera. New InstancedVisuals::drawLinearDepth().

  • Behaviour change: in depth images, visual objects and non-foliage instanced visuals now have the same geometry as in the color image: the automatic mesh LOD for the depth camera (setAutomaticMeshLodEnabled(false) keeps full detail), setProjectedLodPolicy thinning and Visuals::setVisibilityRange, so a visual outside its range is no longer in the depth image. Foliage keeps its source meshes and every instance in depth, with no simplified LODs, impostors or thinning, because those would drop leaves whose depth a sensor still measures.

  • Faster rendering with unchanged images. Instanced batches too small for GPU culling skip, in every pass, the instances whose bounds miss the view frustum or, in a shadow cascade, cannot shadow a visible receiver. Directional shadow cascades refreshed every frame draw only the shadow-map texels their receivers sample. External cameras, such as simulated RGB sensors, now also cull cascade casters to their receivers; before, only the viewer’s own camera did. A camera that moves every frame renders each cascade in one pass instead of rebuilding the static-caster layer and copying the whole map each frame; the layer is built once a frame repeats its static signature. A camera without MSAA, such as a sensor, now skips foliage instances hidden behind its depth prepass, which only MSAA views did before (4×4-pixel tiles by default; RAYRAI_FOLIAGE_OCCLUSION_TILE_SIZE now also takes 8 and 4).

TCP viewer and server

  • The meshes of a server’s .rscene scene load in the background. The pane keeps drawing and shows a “Loading the meshes of <scene>” progress bar (share done and elapsed seconds) at the top while ready meshes are finished for up to 8 ms per frame. Before, the pane drew nothing until every mesh was loaded (seconds for a large scene). A streamed body’s mesh still loads in the frame the body appears. The scene’s download prompt and progress move to the right of the left panel when they would overlap it, and their bars use the theme’s accent colour instead of red.

  • Behaviour change: the viewer keeps downloaded and copied scenes in the scene directory ~/.raisim/scenes instead of ~/.rayrai/scene_cache, which is no longer read, so a scene cached there asks for Download again. raisin::tcp_viewer::defaultSceneDirectory() gives the location: under HOME, or USERPROFILE on Windows, else raisim/scenes in the temporary directory. Scene files placed in the scene directory are found like those in a resource directory, so such a scene loads without the download prompt (SceneSync::sceneDirectory()).

  • When you accept Download from a server reached by a non-loopback address (for example this computer by its network address) and its files exist at the server’s paths on this computer, the scene is now copied into the scene directory instead of loaded in place. Later connections then load it without asking.

v2.9.0 (2026-10-11)

Rebuild applications after updating (World, ArticulatedSystem, CollisionBodyHandle, Single3DContactProblem and RayraiWindow ABI changes). The World ABI guard now also covers ArticulatedSystem, CollisionBodyHandle and Single3DContactProblem, so an application built against older headers stops with a fatal error when it creates a World.

Deformable terrain

  • World::addDeformableTerrain(xSamples, ySamples, xSize, ySize, centerX, centerY, heights, soil) adds a DeformableTerrain: a height-map soil bed that rigid bodies and articulated systems sink into and drive on. Its surface terramechanics model (DeformableTerrain::Material: bekkerKc, bekkerKphi, sinkageExponent, elasticStiffness, damping, cohesion, frictionCoefficient, shearDisplacement; SI units) combines Bekker pressure–sinkage loading, elastic unloading that leaves permanent ruts, loading-only viscosity, and Mohr–Coulomb/Janosi–Hanamoto traction that builds up with slip. Contacts are area-quadrature samples solved implicitly with the bodies’ dynamics, so forces do not depend on the number of contact rows and the mesh spacing sets the spatial accuracy; rendering and ray queries use the deformed mesh. See Deformable terrain.

  • getSoilState()/setSoilState() (per-vertex sinkage, plasticSinkage, shear), resetSoil(), getUndeformedHeights(), getSoilStepStats() and pressure(). World checkpoints save the soil history; exportToXml writes the undeformed heights and the state. World XML: a <soil bekker_kc="..." .../> child makes a <heightmap> with explicit height samples deformable. BisectionContactSolver::Config::terrainWarmStart (on by default) seeds soil contacts with the last converged traction.

  • The bed is static and deforms only along its local z axis. It can be placed with setPosition/setOrientation between steps, and setBodyType accepts only STATIC. HeightMap::update, updateVisualHeight, updateVisualHeightPatch and init throw std::logic_error on it. A world cannot hold a deformable terrain together with a DeformableObject or GranularSystem; adding the second is a fatal error. Bodies resting on the soil do not fall asleep, and material pairs do not tune the soil response. It is a surface model: no soil inertia, berms or excavation.

  • New rayrai_deformable_terrain example: the off-road buggy drives over a soil bed and leaves permanent wheel ruts (--headless, --steps=N, --export=DIR).

Tracks

  • URDF <track> (a child of <robot>) wraps a rigid belt around the convex hull of a link’s <wheel> elements and moves its surface with the <drive joint gain> joints. Contacts on the belt pull on the drive joints, so sprocket PD targets, torques or actuators drive the robot through friction, a blocked robot stalls its drives, and a track on a fixed base is a conveyor. The belt’s collision bodies (a cylinder per wrapped wheel, a box per straight run) are also its visual (color, visual="false"); contacts of one belt that coincide are solved once. ArticulatedSystem::getTracks() (raisim::Track, TrackDrive), getTrackIndex(), getTrackSpeed(); with setComputeInverseDynamics(true) a drive joint’s torque includes the belt’s pull. Belt sag and tension are not simulated. The templated_tracked_robot example now models its four belts with <track>. See Tracks.

Motors and joints

  • Fix: spherical joint angle limits now hold against large torques; the limit row used a contact basis that was never set for it.

  • ArticulatedSystem::getJointDamping() returns the passive joint damping coefficients, excluding actuator output damping and PD gains.

World loading

  • RaiSim XML world configs include a scene with <include value="[THIS_DIR]/scene.rscene"/>, so a robot and the scene it runs in live in separate files. The scene’s settings apply first and the XML file’s own settings override them; getRscene() returns the scene, so rayrai and the TCP viewer (--simulate robot.xml) show it. At most one scene per world. See A robot in a scene and the rayrai_city_from_xml example (rsc/xmlScripts/rscene/anymal_in_city.xml).

  • World XML <include> paths take @@ parameters (e.g. <include value="@@scene"/>).

  • A RayraiWindow shows its world’s scene (World::getRscene()) by itself, when it is constructed or given a new world with swapWorld; raisin::rsceneVisuals(viewer) returns the visuals it created, and raisin::setRsceneRenderSettings(viewer, render) shows the scene with C++ render-setting overrides. A scene rayrai cannot show is a fatal error in the constructor. RayraiWindow::setAsyncMeshLoadingEnabled(false) now also finishes the meshes still loading. Rebuild applications (RayraiWindow ABI change).

  • Behaviour change: raisin::applyRscene is removed; a RayraiWindow on the scene’s world applies it, and setRsceneRenderSettings replaces applyRscene(scene, viewer, render).

  • TCP viewer: a scene served over a loopback connection (--simulate, drag and drop, or a server on the same computer; judged by the connected address, not the host name) loads from the server’s files without the download prompt. Files that are not on this computer, as behind an SSH tunnel, still wait for Download.

  • Behaviour change: A world XML file without a <gravity> element keeps the gravity of the files it includes; it used to reset it to 0, 0, -9.81.

  • World XML <include> files and object-class urdf_path are also looked up relative to the folder of the XML file that names them; a file found neither as given nor there is a fatal error naming both paths.

rayrai

  • Faster rendering with unchanged images (verified bit-identical): directional shadow cascades skip visual, object and instanced-batch casters whose shadows cannot reach a receiver of the cascade (Visuals/InstancedVisuals/RaisimObject::shadowGeometryWithinBounds()); foliage instances that their distance fade hides entirely are not drawn, and shadow maps draw the ones it cannot touch without the fade’s fragment test; foliage instance culling tests spatial groups before instances; mesh draws resolve their uniform locations once per shader; an animated visual no longer rebuilds and re-sorts the instanced-visual candidates; per-frame mesh vertex updates generate tangents with less work. The off-road buggy example: 15.9 → 14.7 ms per frame, rayrai CPU 8.0 → 6.9 ms, shadow pass 4.4 → 3.6 ms GPU (RTX 2070 SUPER). Rebuild applications (RayraiWindow ABI change).

  • RayraiWindow::SynchronousMeshLoadingScope: while it lives, the meshes requested load synchronously, and other pending loads, such as a scene’s, continue in the background.

  • Fix: an articulated system’s mesh visual is drawn and culled with its current visShapeParam, so changing it at run time takes effect; rayrai kept the scale from when the visual was created.

  • Fix: captureSupersampledRgba keeps the camera’s gamma.

Fixes and build

  • Faster sleeping-island bookkeeping with unchanged simulation results (verified bit-identical): each step reads the objects’ body types again only after a body type changed, and visits only the dynamic objects and their contact bodies. The off-road buggy example’s world (2,153 objects, most of them static): 262 → 205 µs per step. Rebuild applications (World ABI change).

  • Documentation builds reuse the checked-in images. RAISIM_DOCS_BUILD_RAYRAI_IMAGES is now OFF by default; set it to ON to run the image generators.