2026-09-06 #426
markaren
announced in
Announcements
2026-09-06
#426
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
New this release: imaging sonar, ray-traced AO with bent normals, zero-copy frame export to CUDA, splat point clouds and splat scene serialization, PhysX tendons, and the
GeoSceneterrain stack.Breaking changes
Object3D::lookAt()now applies the camera convention (local-Ztoward the target) to sensors. New virtualObject3D::usesCameraLookAtConvention()(defaultfalse) is overridden byTracedRasterVisionSensor(soDepthSensorandLidarSensor),PathTracedLidarSensor,CameraSensorandSonarSensor. Code that compensated with the mirror pointlookAt(2*position - target)now aims backwards and must pass the target itself. Adding a virtual toObject3Dchanges its vtable layout.Imudefaults are the ICM-42688-P datasheet figures:gyroNoise0.005 → 4.8869e-5 rad/s/√Hz,accelNoise0.06 → 6.8647e-4 m/s²/√Hz, both random-walk terms → 0. Seeds are unchanged, so seeded replays keep their streams, but existing scenes get different noise magnitudes.LidarModel::OS1_64()andOS0_128()step azimuth at 360/1024° (0.3515625) instead of 0.35°: 1024 columns per revolution instead of 1029.BVH::intersects()andBVH::intersect(..., accurate=true)run an exact triangle-triangle test instead of the triangle-AABB overlap approximation. Near-miss pairs that previously reported a hit no longer do.BVH::build(positions, indices)throws on malformed input, andgetGeometry()returnsnullptrafter the array build.ArticulationLink's constructor gained a leadingconst PhysxWorld*parameter.PhysxGpuBatch::blockFloatsWritethrowsstd::runtime_errorfor unhandled write types (including the four tendon types) instead of returningmaxDofs.ParticleField::submitgainedfloat dtSec = 0.f, andVulkanRenderer::enableVertexInteropgainedbool stableCorrespondence = true. Both source-compatible; both ABI-breaking.RoadNetwork::buildBridgeMeshes()andbuildGroundChunkMeshes()keep their signatures but ignoretexWidth/texHeight. Resolution, tile length, UVs, vertex/index counts and materials now come fromMarkingRules; materials are shared per (marking class, surface, width, wear, variant), so mutating one returned mesh's material changes every piece of that class; ground chunks gain a child patch-decal mesh.buildMeshes()keeps the legacy path.buildGeoBuildingMeshes()emits up to nine named child meshes (geo_buildings_wood,_masonry,_industrial,_roof_slate, …) instead of the threegeo_buildings_walls,_walls_plain,_roof. Lookups by name now fail at runtime rather than at compile time.terrain::detail::geoBldRoofPalette()was removed fromGeoBuildings.hpp. Buildings with no OSM colour tag render in different colours.makeNorwegianSplat()emits eight layers instead of six (new shallow and seabed layers; wetlandheightLomoved from sea−5 m to sea−0.5 m). Terrain colour below the waterline changes for existing geo scenes.makeGeoProvider()tints terrain albedo from land cover by default:GeoTerrainOptions::landCoverdefaults totrueandpack.flowis always computed, so wet-rock and scree tinting apply to every existing pack.loadSogWithLodthrows when a multi-level SOG asset's tree leaves do not tile a chunk exactly.setDeferredAO(true)now selects the half-res RT AO + bent-normal pass; TAA history invalidation changes frame-0 output; the foam accumulator is capped at 1024 (THREEPP_OCEAN_FOAM_RES=2048restores the old density); the GLfogColoruniform is encoded at upload.Added
Sensors
SonarSensor(new headerthreepp/helpers/SonarSensor.hpp) — Vulkan-only forward-looking imaging sonar, traced through the renderer's acceleration structure as one dispatch. Synchronousscan()plus the pipelinedscanBegin/scanFired/scanReady/scanCollect.SonarModel(new headerthreepp/helpers/SonarModel.hpp) — renderer-free model with presetsWide130(),OculusM750d(),BlueViewM900(),Gemini720is(), plusSonarImageandSonarReflectivity.ImuModelwith the presetImuModel::ICM42688P();Imu's defaults are that preset.VulkanRenderer::EventCameraSource {Shaded, Final}withsetEventCameraSource().Shaded(default) is the deterministic G-buffer Lambert proxy;Finalfeeds the detector the presented frame box-averaged to sensor resolution.EventCameraSensorand the GPU detector.readEventStreamIntoreturns events sorted by(t_us, y, x, polarity).Vulkan renderer
VulkanRenderer::resetTemporalHistory()— restarts every temporal accumulator on every live view: TAA history and its deviation streak, the deferred shade's GI / momentsSq / reflection / reflAux / shadowVis histories, froxel fog, RTAO, cloud march, the probe-GI store, ReSTIR reservoirs, occlusion-cull visibility bits, the sample index and Halton jitter phase, plus one forced dynamic-BLAS rebuild. Costs one frame of convergence.setDeferredAO(true)(rtao.comp): four cosine occlusion rays per half-res pixel, temporally reprojected, depth-aware bilateral upsample, writing AO, bent normal, near-field AO and sky visibility. RTX 4070 at 1920×1080: fjordgpuTotal29.9 → 22.3 ms, Sponza interior 14.25 → 12.29 ms/frame. NewFrameTimings::rtaoMs.enableFrameInterop(viewHandle, channels),disableFrameInterop,frameInteropActive,syncFrameInterop, theFrameChannelenum{Color, Depth, Normal, Motion, Ids, Albedo, SplatDepth}andFrameInteropExport. Single-buffered, and disabled with a stderr line if the source images are reallocated. Measured 640×480 Color+Depth+Ids: 4.41 ms host readback vs 0.36 ms zero-copy.setFogWaterSurfaceYrenders as submerged: murk column on env miss, Snell's window, TIR mirror, murk sun shafts with a caustic proxy. Gated onmurkDensity > 0and a set surface Y.MaterialWithAttenuation::scatterColorandscatterDistance(mean free path in metres, 0 = off), consumed by the deferred water body as a single-scattering integral.MeshStandardMaterial::envMapIntensityandHemisphereLightare read on the deferred path.FrameTimings::probeGiMs.Ocean and particles
Ocean::Options::fetchandDisplacedMesh::Params::fetch— JONSWAP fetch scaling on top of Phillips (0 = fully developed sea), live-tunable likewindSpeed.Ocean::Look::Fjord.DisplacedMesh::HullExclusiongainscenterY,pitchandroll: inside the hull footprint the surface is pulled onto the vessel's own waterline plane. All three default to 0, bit-identical to before.ParticleField::Config::attributesis functional — onevec4per particle (rgb linear HDR radiance, a = medium weight), withsetAttributes,hostAttributesandattributeSerial. Exported alongside positions byenableParticleFieldInterop.ParticleField::Config::hostStableSlotslets aHostRingfield declare stable slot identity, which makesBillboardRepr::stretchSecondsvelocity streaks work for host-driven fields.BillboardReprgainsalphaOver,lit/litPhaseG/litAmbient,volumeExtinction/volumeShadow/volumeAmbient/volumeSunGain,sunGeometryShadow(one ray query per particle against the scene TLAS) andcoreWeight. All default to the previous behaviour.THREEPP_OCEAN_FOAM_RESandTHREEPP_OCEAN_FOAM_INTERVAL.Gaussian splats
SplatCloud::setPointMix/setPointSize/pointSigmaPixels()draw every splat as an opaque disc; intermediate mixes lerp the projected covariance and opacity toward it. Both backends.SplatLoader::loadPointCloudPly/parsePointCloudPly/isPointCloudPlywithPointCloudOptionsandPointCloudInfo, reading binary little/big-endian and ascii PLY.SplatLoader::writePlywrites the INRIA layoutloadPlyreads.threepp/splats/PointSurface.hpp—splats::buildPointSurfacebuilds a triangleSurfaceMeshon the CPU from point means alone (voxel hash, sparse union-of-balls field, marching cubes, island filter). Works on every backend, unlike the Vulkan-onlybakeSurface.splats::medianNeighbourSpacing.SogLoader::describe()returns the SSOG tree's leaves inInfo::nodes(NodeInfo,NodeRange);splats::selectLodPerNodegives every leaf its own level,selectLodWholeCloudkeeps the previous policy, andselectLoddispatches between them.SplatCloudis serialized byObjectExporter/ObjectLoaderas athreeppSplatblock (source path, import ops to replay, point-mode look, splat count — never the splat data). A.tpzcarries the source file undersplats/; a pathless cloud gets asplats/<uuid>.plysidecar.ObjectLoader::setSplatCloudResolverhands live clouds back by uuid instead of re-reading from disk.PhysX and robotics
threepp/extras/physx/ArticulationTendon.hpp—SpatialTendon/SpatialAttachmentandFixedTendon/TendonJointover PhysX's own tendon primitives.threepp/extras/physx/TendonCable.hpp—TendonCable, a routed pull-only cable with via points and wrap surfaces, driven by tension (Mode::Tension) or spool length (Mode::Length), applying the frictionless-pulley free-body force at every via point.PhysxWorld::addStaticHeightField(raw pointer andstd::vector<float>overloads).PhysxVehicleBase::setRoadOverride(wheel, height, mu, vRoad),clearRoadOverride,roadOverrideActive, andsuspensionForce(wheel).Articulation::links(),nameLink(),findLink(),linkNames().UrdfArticulationregisters each URDF link name against the articulation link that governs it, so a fixed-collapsed child (a tool frame, say) resolves to the link it was welded into.ArticulationLink::addTorque,addForceAtPos,worldPoint,axis.Terrain and geodata
terrain/GeoScene.hpp,terrain/CellStreamer.hpp,terrain/CliffShell.hpp,terrain/StraightSkeleton.hpp,terrain/UrbanProps.hpp,vegetation/CanopyForest.hpp.terrain::GeoScene::create(opts)assembles the whole geodata stack — pack load, road network, road carve, tiled terrain, band sets, cliff shell, canopy forest, synthetic bathymetry — behind one call, withupdate(camPos),heightAt(x, z)andStats.GeoScene::makeBathymetryis a public static usable on anyHeightGrid.GeoTerrainPackgains optional pack data withhas*()guards:GeoBuilding::roof(GeoRoof),landuse(GeoLandUse),canopy(a canopy height model) andflow.GeoBuildingsOptions::measuredRoofs(defaulttrue) builds hip / gable / flat roofs from a Felkel straight skeleton, with eaves, fascias, parapets and cornices, reporting throughGeoBuildingsStats.FacadeMapsgains masonry (upper / ground / plain), industrial, slate-roof and tile-roof sets.DetailTexturegainsBandKind::Cliff;makeTerrainBandSet()takes an optionalrockKind(defaultBandKind::Rock, so existing calls are unchanged).RoadGenerator::bakeSurfaceMapsproduces albedo, tangent-space normal and roughness/metalness with coverage-antialiased markings, wheel-track wear, wandering cracks and repair patches, viaSurfaceKind,LinePatternandRoadSurfaceStyle.RoadNetworkgains N302 marking classification (MarkingClass,MarkingRules,classOf()) and shared surface bakes.TileTerrain::treeSignature().TreeParams::blobOcclusion,blobLatSegs,blobLonSegs.scripts/geodata/fetch_norway_terrain.pygains--canopy(lidar canopy height model),--roofs(measured eave/ridge/axis per building),--landuse,--texture(Kartverket raster WMS, needs pillow) and--cache.--outnow defaults to the repo'sgeodata/root instead of a hard-coded absolute path.Core and loaders
BVH::distance(b1, b2, m1, m2, maxDistance),BVH::build(positions, indices),triangleCount(),boundingBox().URDFLoaderreports the<visual>and<collision>elements the articulation description drops, one line per affected link, todiagnostics()andcerr.Object3D::usesCameraLookAtConvention().Python
BVH/RayHit; curves and 2-D shapes (Curve3,CatmullRomCurve3,Path,Shape,TubeGeometry,ExtrudeGeometry,ShapeGeometry);Flock(theextras/faunabird flock, with its ~40-fieldParams, perch pipeline and startle);ParticleFieldwithdata_texture/float_texture;PhysxVehicle; soft bodies (add_soft_body,SoftBody,PhysxSoftBodyMaterial);add_static_heightfield; the tendon types;IkSolver/IkOptions/IkResult/IkTaskwithRobot.set_end_effector;SonarSensor/SonarModel/SonarImage;PathTracedLidarSensor;renderer.scan_lidar; the Vulkan event camera; secondary views (add_view,set_view_camera,set_view_display_rect,read_view_rgb_pixels);GeoScene;SogLoader,select_lod,bake_surface,make_sensor_mesh; and the KHR specular / scatter material knobs.VulkanRenderergainssim_time(None= wall clock),dlss/dlss_available,restir_di,starfield,splat_depth_aov,set_sensor_only_surfaces,set_fog_water_surface_y,reset_temporal_history(),enable_frame_interop/sync_frame_interop,enable_particle_field_interop, andrtao_msinframe_timings.threepp.torch_frames.FrameTensorsexposes renderer images as live torch tensors;threepp.cuda_interopgainsVkInteropTensor,VkInteropTensorView,VkInteropArrayandVkInteropMemory.threepp.__version__;threepp.vulkan_validation_active()andvulkan_validation_error_count().threepp.rl:GpuSim.apply_link_force,VecTask.on_pre_substep,VecTask.settling, CUDA-graph replay of the per-step torch region (VecTask(graph=True),verify_graph()), and the newrl.raycastandrl.perceptionmodules.threepp.terrain_deform.threepp.imguigainsplot_lines,dummy,item_rectand a draw list (draw_line,draw_polyline,draw_rect,draw_circle,draw_text).BufferGeometry.set_indexandapply_matrix4.Changed
RoadGenerator::buildSurface(vOffsetMeters = 0, mirrorAlternateTiles = false)— both defaults reproduce the previous geometry byte for byte.Oceanmaterial's Beer-Lambert plus analytic deep body, so hulls and particle sprites in the same column no longer disagree.MeshPhysicalMaterial::specularIntensityscales a water reflection that lands on geometry, leaving the escape-to-sky at full Fresnel.TP_Froxel0.144 → 0.083 ms), in-place ocean IFFT permute, one ORM tap inpost_composite, dead work sunk below the water early-out, and an SSE2 world-matrix kernel plus batched BLAS refits on the CPU scene walk.Fixed
Vulkan renderer
GeometryDescmirror still named, so every RT hit on an armed mesh read freed memory and eventually hitVK_ERROR_DEVICE_LOST; the per-frame refit builtPREFER_FAST_BUILDintoPREFER_FAST_TRACE-sized storage and overran the acceleration structure; and a same-count edit to a CPU-owned side attribute under an armed registration destroyed exports a foreign API still held. Records are now sized for both build modes and transplanted rather than evicted.canvas.size()against the swapchain extent, so whenever the platform granted a different extent the swapchain plus itsvkDeviceWaitIdlewas recreated every frame. Secondary views neverremoveView()d leaked their G-buffer images, colour target, readback buffer and post chain pastvkDestroyDevice.setGbufferMsaa(2)or(4)the deferred shade reconstructed world position along the pixel-centre ray whilegbuf_resolvewrites the dominant sample's depth, putting refraction origins outside glass so the interior trace escaped to the environment.traceRadianceshaded anytransmission > 0hit as opaque, so nested glTF glass rendered as a solid block; textured BLEND materials withopacity < 1rasterized fully opaque.LineandLineSegmentsoverlay pipelines inherited the wireframe pipeline's blend-off attachment state, so transparency, opacity and additive blending never reached the hardware.BufferGeometry::drawRangecould be picked for a coarser auto-LOD level whose index set has no correspondence with the range, drawing the whole simplified mesh. Such spans now pin to LOD0.readGBufferAOV(SplatDepth)gated onsetSplatDepthAovrather than on allocation, so a scene whose splats were correctly occluding overlays reported the AOV as unallocated.Ocean, particles and splats
DisplacedMesh::sampleHeight()read a CPU mirror while in-flight frames were still copying into it, so the sampled field was 1–2 frames old and torn depending on GPU pacing (58 of 60 frames of a pinned sim-time sequence differed between paced and unpaced runs).ParticleFieldglow leg drew into a target with no depth attachment, so embers behind opaque geometry rendered their halo through the wall.DensityReprmarch, including a medium lit throughshadowVis()alone — which reads a shadow map a participating medium never renders into, so a density volume was fully sunlit through its whole depth and read as white ash at any albedo.Sensors
max(256k, width * height * maxEventsPerPixel)per ring slot.PhysX, terrain and core
ArticulationLink::addImpulsecalledaddForcewithPxForceMode::eIMPULSE, which PhysX documents as invalid for articulation links; it now appliesJ / fixedTimestepaseFORCE.PhysxGpuBatch::writeHostmis-sized host-staged link writes (eLINK_FORCEandeLINK_TORQUEaremaxLinks*3, not DOF-sized).TileTerraindeadlocked on its own bake budget: bakes that had finished but were waiting on the LOD swap gate kept occupying an in-flight slot, so the streamer could sit permanently atmaxBakesInFlightwith no tile ever refining.--roofswrote a roof block where the lidar predates or misses the building.CurvePath<T>::getPoint()returned NaN unconditionally (a missingreturnafter the segment lookup), soCurvePath/Path/ShapegetPoint(),getPointAt()andgetSpacedPoints()were unusable.getPoints()was unaffected.Frustum::intersectsObject(),intersectsSprite()andintersectsBox()wrote into namespace-scope scratch singletons, so two threads culling against differentFrustuminstances clobbered each other's scratch. The scratch is now stack-local.GLTFLoadervalidates every accessor index a primitive references and skips just that primitive with a warning, instead of failing the assembly — OpenCASCADE'sRWGltf_CafWriteremits such primitives for faces it cannot triangulate.BufferGeometry.set_attributereset the draw range on every call, so setting a per-vertex colour afterset_indextruncated an indexed mesh and it drew a fraction of itself.make_sensor_meshraisesRuntimeErroron a build without the Vulkan backend instead of failing to link, and an empty event-visualisation array no longer breaks the GCC build.Examples, tooling and docs
examples/vulkan/vulkan_sonar— an imaging sonar on an ROV inside a net pen, with a polar echogram fan and a model combo, demonstrating the sensor-only proxy pattern.vulkan_aov_auditgains ~20 options (--scene fjord,--terrain,--events,--lidar,--scene-edit,--fsr,--settle,--reset-history, …);vulkan_lidar_auditgains--sonarand the same scene-edit axis.examples/objects/gaussian_splatsgains--points,--point-size,--point-mixand point-cloud PLY detection.examples/extras/terrain/norway_terraingains--viewpresets,--fly,--list-roads,--camand--fov, and builds an Ålesund capstone scene on the new terrain headers.warp_netpen,warp_prop_vortex,warp_mudsnow_drive/_mpm,warp_mudsnow,warp_explosion,warp_cloth_catch,warp_hydraulic_press,tendon_hand,tendon_probe,stewart_balancer,lidar_sculpt,warp_hull_sculpt, and thecalicoset (Spot walking a Gaussian-splat scan).python/README.mdnow indexes every script rather than the fifteen with a table row.sensor_audit.py,multiview_bench.py,netpen_e3.py,gazebo_audit/andisaac_audit/(the same determinism audit run against Gazebo Harmonic and Isaac Sim 4.5),spot_push_probe.py,spot_gait_probe.py,spot_scan_ab.py.cartpole_swingup.ptandscratch_flat_best.pt.doc/vulkan_splats.mddocuments point mode and the splat depth AOV's allocation rule.Build
RelWithDebInforewrites/Ob1to/Ob2for the library target (directory-scoped tosrc/), so it no longer inlines only functions markedinline.CMakePresets.json,vcpkg.json,pyproject.toml,CITATION.cffand everything under.github/are unchanged. The geodata--texturefetch adds an optional pillow dependency, recorded ingeodata/README.md.Full Changelog: 2026-08-21...2026-09-06
This discussion was created from the release 2026-09-06.
All reactions