Repository navigation
2026-10-11 #431
markaren
announced in
Announcements
2026-10-11
#431
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.
The short version: the Vulkan ocean keeps what boats leave on it (a wake field of foam, bubbles and the waves a hull makes, for up to eight vessels on one sea), Python gets a set of vehicle rigs (three sea drones, an AUV, five aircraft and a car, each built from one spec file with its sources cited), text follows the font's real curves and is UTF-8, a Vulkan process exits in milliseconds instead of 20 seconds, the Vulkan renderer runs on a GPU without ray tracing, and the web build is lit by its environment and plays sound. Three new OpenGL demos load no assets at all:
showpiece,MarbleRunandSandTable.Figures below come from the commits that measured them, on the hardware those commits name. They were not re-benchmarked for this release.
API
type()returnsconst std::string&onObject3D,Material,BufferGeometryand everything under them. It returned astd::stringby value, which cost a heap allocation per call for the longer names, several times per draw. Callers compile unchanged. A class of your own that overridestype()must take the new return type.Font::glyphsis keyed by Unicode code point, not bychar.VulkanRenderer:setDeferredDenoise/deferredDenoise(usesetDenoise/denoise),setVolumetricFog(a no-op since the fog unification; Python'svolumetric_foggoes with it), and theVulkanRendererCore.hppheader and alias.FireEffect::Params::legacyEmbersandemberRateare gone, withvulkan_fire --legacy-embers. The embers are aParticleField.PhysxVehicle:Settings::stickyDampingLongitudinalandstickyDampingLateralare gone. The vehicle now chooses betweenstickyDampingandstickyDampingHoldeach step (see the grade hold below).Texture::premultiplyAlphais removed. Neither renderer read it. The key in an old scene file is ignored on load.Material::clippingis gone from the base class. It shadowedMaterialWithClipping::clipping, which is the one that remains.PhysxSoftBody::registerGlTexturereturnsbool, likeregisterVulkanMemory.THREEPP_DLSS_JITTER_SIGNis removed. The per-axisTHREEPP_DLSS_JITTER_SIGN_X/_Ystay.Behaviour
Canvas::Parameters::fastExitis on by default. A process on its way out leaves its Vulkan device to the OS instead of destroying it, which on NVIDIA drivers could wait about 20 s invkDestroyDevice. An app that tears its renderer down and builds a new one while the process goes on setsfastExit(false). Python'sCanvas(fast_exit=...)keeps itsFalsedefault, since interpreter exit is covered automatically.VulkanRendereris created on a device without ray tracing. It used to throw there. It now draws a degraded frame (see the no-ray-tracing mode under New). Code that caught the exception to fall back toGLRendererasksrayTracingSupported()(Pythonray_tracing) instead. Where there are several devices, one with ray tracing is still preferred.readRGBPixels(),writeFramebuffer()and Python'ssave_frameused to copy a swapchain image up to three frames old, so a script that changed the scene and saved a still got the old scene. The readback now ends the open frame first. An ortho HUD pass that arrives after the readback in the same iteration is skipped with a one-time hint: put it before the readback. Baselines captured through this path change once.VulkanRendererdraws anOrthographicCameraas a 3D view (orthographic_scene_rendering, on by default). A top-down render used to come out as unlit fills in scene order with a black background. The C++ default is unchanged, but a lit mesh sent down the 2D path now prints a one-time hint that names the call to make.renderOrder, asGLRendererdoes: opaque before transparent, then the enclosing group's order, then the object's own. It used to draw in traversal order with every sprite last.RenderTarget::Options::generateMipmapsis honoured on GL (defaultfalse, as in three.js). It was never read, and every target regenerated its chain after each render.SVGLoader.load/parsereturn strokes as well as fills, in document order, named after the element's id and layered byrender_order.fill="none"no longer yields a white mesh.DisplacedMeshof more than 512 cells a side is ray traced through a coarser proxy (rtProxyCells, default 512; 0 restores the full grid). The raster keeps the full grid.PhysxVehicleholds on a grade. It used to creep downhill at about 0.7 km/h. Scripted drives on the flat are bit-identical per step.Scenes that look different
?per byte.COLOR_0show their vertex colours, multiplied into the base colour as the format specifies.ReflectororWatermay be lit differently: see the light-version fix below.sampleWakeHeightsums every vessel's wake.setUnderwaterSpriteForeshortening(false)restores the old look).Examples that moved
vulkan_ui.pyis nowui_demo.py --vulkan, andwarp_nebula_vk.pyiswarp_nebula.py --vulkan.warp_squishy_ball.pyis removed. The rover demo and slope sweep are inrover/, anddrone_rig.pyis inrigs/. Demos write to anout/folder beside the script unless told otherwise.vulkan_aov_audit,vulkan_lidar_audit,vulkan_furnace_testandrg_abare inexamples/vulkan/audits/. Target names are unchanged.New
Ocean and marine (Vulkan)
DisplacedMesh::WakeField; Pythonocean.wake_field,wake_patch(i),add_wake_source(...),clear_wake_sources()). Square patches of simulated surface, anchored in the world and not tiled. Each texel carries foam, the bubbles under the surface, turbulence, a lane where the short waves are damped, and the water's own velocity. A source is a capsule with a radius, a velocity and an amount of each quantity. Off by default: nothing is allocated and the frame is the same to the pixel.WakeField::rippleResolution,WakePatch::rippleSize). A patch can hold a small linear sea that is forced by the pressure its sources bear down with (WakeSource::push). Nothing draws a V: a source moving at speed U makes the Kelvin wake of that speed, bent where it turned, and several superpose.marine::VesselWash(C++) andtp.VesselWash(Python): what a vessel leaves on the wake field. Her propulsors' races by momentum theory, the lane her hulls drag, and the waves her weight makes, spread down to her draft.vesselHull(i),vesselWake(i); Pythonocean.vessel(i)). Vessel 0 is the existinghullExclusionandwake, so current scenes are unchanged.rtProxyCells: on a 1024 x 1024 ocean the per-frame BLAS refit went from 5.0 to 0.4 ms (RTX 4070).setUnderwaterMurk(Vector3, Color),murkExtinctionFromColor; Pythonset_underwater_murk(density, color, chromatic=...)) andsetUnderwaterAmbientFalloff(k), both opt-in. A hull 3 m down no longer reads as lying at the surface.WakeField::albedoscales what the film and bubbles turn back, for night scenes.Vulkan
VK_KHR_ray_query, or on any device underTHREEPP_VULKAN_NO_RT=1, it is a raster renderer in which every ray misses. No acceleration structures are built. GI is sky and environment light, and reflections and refraction show the environment. RT AO, probe GI, traced fog shadowing and auto-LOD are off. The sun casts screen-space shadows, marched through the depth buffer, so only what the camera sees casts one; lamps cast none. A lidar scan throws, and so do the sensors built on it.rayTracingSupported()(Pythonray_tracing) says which mode is running. With ray tracing nothing changes: the ray-traced shaders' SPIR-V and an A/B render were compared and are identical.setLightingView/lighting_view): show one term in place of the lit frame, such as the unshadowed direct light, the shadowed direct light, the diffuse bounce or the reflections.probeGrid()/probe_grid()returns the fitted probe grid, with the SH store as numpy.FrameTimings::passes, Pythonpass_timings,rg_ab --time, and a tree in the ImGui timings node. Python'sframe_timingsnow names every pass (ocean, particle and splat stages,probe_gi_ms,tlas_refit_ms).THREEPP_VK_BLAS_COMPACT=0turns it off.render(): scene build while chains land went from 27.7 to 4.7 ms on the same site. With the clock pinned (setSimTime), the frame a chain lands on is now a function of the scene, so runs replay under load.setCutoutRayCap(texels, fromDistance),setCutoutRayDistance; Pythoncutout_ray_cap,cutout_ray_cap_from,cutout_ray_distance). Off by default. In a scene of card trees, 2 texels from 40 m took 15 to 18 % off the frame from the air.THREEPP_VULKAN_SUPPRESS_PRESENT=0restores presents). The pipeline cache blob is capped at 64 MB and not rewritten when unchanged; one had grown to 475 MB.THREEPP_VK_EXIT_TIMING=1prints where a teardown spends its time.sharpen_strength,motion_blur, andPointLight.radius/SpotLight.radiusfor ray-traced soft shadows.THREEPP_VK_SHADE_SPEC_MASK,THREEPP_VK_SHADE_SPEC_PROBEandTHREEPP_VK_ASYNC_PILOT(an async-compute pilot; a loss on an RTX 4070, kept for measurement).OpenGL, core and loaders
FXAAPass, ported from three.js r186. It runs after anOutputPassand smooths the edges MSAA leaves stepped next to bright emitters.Font::advance,ascenderanddescender, also from Python. The TTF loader takes Latin-1, Extended-A, Greek and the common punctuation, arrow and maths blocks.TextSpritecan size text by the font's line (TextBox::Line): every string gets one glyph size and one baseline, so HUD text can be laid out without measuring each string. The default staysInk.OBJLoaderloads polylines (lrecords) asLineSegments.Material::linewidthanddefaultAttributeValuesreach GL (see known issues for wide lines).AsyncGroup::hasFailed(), andmath::spatialHashfor the cell hash five sites wrote out by hand.Physics
PhysxVehicle::associate(node): sensors at or under a node ride the vehicle's chassis.Settings::engineBrakeTorqueandengineBrakeIdleSpeed(Pythonengine_brake_torque,engine_brake_idle_speed). Off by default.physx_softbody). Phong materials get GPU tet skinning.RigidBody.add_force_at_posandadd_torque.Python
ScalarField.from_numpy: any field computed in numpy (a fused TSDF, an analytic SDF) can go throughmarching_cubes.Texture.to_numpy()returns a uint8 texture's pixels.MeshStandardMaterialterrain bands (terrain_weight_map,set_terrain_band, and their knobs) for a terrain loaded from a file.color_write,stencil_*,StencilFunc,StencilOp), so one draw can clip another. GL only.threepp.lesson(experimental, opt-in): the explainer-video toolkit, with aStage, aHud, measured-speech timing, a film writer and a shared command line. Extrasthreepp[lesson]andthreepp[narration]. The films made with it live in threepp-lessons.hub = model.get_object_by_name("hub"); del modelused to dangle.Vehicle rigs (
python/examples/rigs)Each vehicle has a spec JSON that holds every number with its source, a generator that builds the
.glbthrough headless Blender, and a flight or motion model that imports no renderer. The.glbfiles are generated, not shipped. Numbers that are not from the cited source are marked as assumed.usv_rig.py: strip-theory buoyancy on a 6-DOF body (StripHull), three drives (waterjet, azimuth pods, fixed pods), the hull's wake and wash, and an opt-in 3-DOF manoeuvring model with a current. Boats: the Mariner (6 m, waterjet), the Otter X (4.6 m catamaran) and the Otter (2 m catamaran), all after Maritime Robotics' sea drones. Run them withusv/usv_ocean.py --boat mariner|otterx|otter.x8_rig.py: the Skywalker X8 fixed wing on the 6-DOF model of Løw-Hansen et al. (CEAS Aeronautical Journal, 2025).remus_rig.py: the REMUS 100 AUV on Prestero's 6-DOF model (MIT / WHOI, 2001).m350_rig.py: the DJI Matrice 350 RTK as a multirotor in wind, with Dryden turbulence, a mission layer and a gimbal. There is no published model behind it.babyshark_rig.py: the Foxtech Babyshark 260 VTOL quadplane on Graesdal's wing model (NTNU, 2021), withuav/babyshark_fly.pyto fly it from the keyboard.aerosonde_rig.pyandbixler3_rig.py: Beard and McLain's Aerosonde, and the Bix3 that Simmons identified from flight tests (Virginia Tech, 2018).evoque_rig.py: the Range Rover Evoque onPhysxVehicle, with head lamps that light, a seat a camera rides in, and keys that steer by sideways acceleration.Examples
showpiece: a car in a chrome vortex with soft shadows, glass with iridescence and dispersion, and a composer chain of MSAA, GTAO, bloom, outline and FXAA.MarbleRun: a rolling-ball sculpture. 96 marbles, instanced glass with transmission, marbles that carry lights, a marble camera, a mirror-polished floor, depth of field, and chimes played from where each hoop is.SandTable: a box of sand and water to dig, flood and drain. Shallow water on a 240 x 160 grid, sand that slumps and erodes, a tap, a drain and rain. About 3 ms of physics a frame on one core.SpheroControlis rebuilt as a Sphero RVR at its real size that follows a ball by its own camera.turbine/: five robots inspect an offshore wind turbine from blade tip to seabed (the IEA 15 MW reference turbine, a drone, two sea drones, an ROV and a snake robot).uav/m350_acoustic.py: nine microphones locate the M350 by the sound of its rotors (GCC-PHAT and SRP-PHAT; 0.44 m median error on a held-out flight, with the rotor timbre assumed).warp_soft_tentacle.py: soft tentacles that wrap a ball each and lift it.warp_round_trip.py: cloth, sparks and the frame shared between Warp and the renderer.warp_sailboat.pyleaves her mark through the wake field. The white patch astern at sailing speeds is gone.gtaoexample gets a panel for every knob and shows its data outputs without the tone curve.Web build
scene.environmentwas drawn without it.HEAPF32, and the engine mixes 2048 frames at a time in the browser.MarbleRun,SandTable,SpheroControlandshowpieceare built for the web, andtransmissionnow has its HDR.Build
CanvasandEmbeddedFonts.threepp_datais pinned to9689df3, which addsempty_warehouse_01_2k.hdr.VulkanValidation_teston a real GPU, no longer in CI.Fixes
OpenGL
ReflectororWatermirror pass) passed for up to date in the main pass, which was then lit from the mirror camera's side. With a second render target in the frame it showed as a floor flickering at half the frame rate. EveryLightsnow shares one version counter, as in three.js.ShaderMaterialthat asked for clipping never got its planes.Material::linewidthnever reached GL, anddefaultAttributeValueswas never applied.InterleavedBufferAttributehad no elements, so a clonedSpritehad no corners.copyTextureToImageleft the framebuffer cache naming the wrong target.Vulkan
setEmissiveCastsLightshared a flag bit with "the index buffer is uint16". Turning a mesh back on made every ray that hit it read out of bounds, and large meshes crumpled in reflections.Core and loaders
SVGLoaderrect corners, and radii clamped to half the side as the SVG spec requires.CubeTextureLoadertook its channel count from whichever face decoded last, so a mixed jpg and png set declared one count and held another.OBJLoaderread past its buffer when the uv count did not match the vertex count.AsyncGroupswallowed a throwing loader and stayed unloaded forever, silently.TerrainScatter's cell hash overflowed a signed int (undefined behaviour) for cells beyond 29.Python
drone_rig's gimbal stood 12 degrees off its target on average (Euler order).CatmullRomCurve3.points,LidarModel.elevation_angles, ...) say that they are copies: assign the whole list.PhysxWorldin one process raises with advice on how to release the first.Known open issues
ReflectororWaterhad the light-version fault too, so their lighting may move. OnlyMarbleRunwas compared before and after.THREEPP_VULKAN_NO_RT=1, on an RTX 4070 and on lavapipe, not on a device that lacks ray query. The sun's screen-space shadows miss casters that are off screen or hidden, and lamps cast no shadows.linewidthis declined there without an error, as in WebGL.transmissionpage is 24 MB because of its 4k HDR.This discussion was created from the release 2026-10-11.
All reactions