Skip to content

Aura3D 4.5.0

Pre-release
Pre-release

Choose a tag to compare

@gchahal1982 gchahal1982 released this 29 Oct 02:30
· 3551 commits to main since this release

Superseded. This version is deprecated. Upgrade to Aura3D 5.5.1; see the 4.x to 5.0 migration guide.

A release with two visual changes: the default render path and the exposure calibration. Read both before upgrading. All timings below are software WebGL2 on a remote runner; no real-GPU run has been made.

  • Visual change: createAuraApp takes quality and picks a cheaper render path for plain scenes (engine, rendering). quality: "auto" | "production" | "safe-basic" | "cinematic" (default auto). A scene with no post-processing, output options, transmission or custom shader draws in one pass to the canvas; every other scene, and quality: "production", uses the HDR chain exactly as before (the production call sequence is unchanged and covered by render-path-selection.test.ts). app.diagnostics().renderPath and its reason say which one ran. setOutput on a direct-path app now warns instead of silently doing nothing. Measured on the cube scene: steady frame 42.9 ms to about 6 ms, first frame 954 ms to about 450 ms, 2 draw calls to 1.

  • preserveDrawingBuffer can be requested and is on for capture pages (engine). In 4.3.7 the renderer profiles listed preserveDrawingBuffer: true but the value never reached the WebGL context, which was always created without it. 4.5.0 adds a preserveDrawingBuffer option to createAuraApp and turns it on for ?capture=1 and ?qa=1 pages; every other page keeps the 4.3.7 behaviour. app.screenshot() now re-renders before reading, so screenshots and the stock check-deploy --browser black-frame check work on any page. If you read the canvas yourself outside a render call, pass preserveDrawingBuffer: true or use app.capture() / app.screenshot().

  • Bloom fuses draws without adding fill on the auto and cinematic paths; renderer.bloomFusion selects the mode (engine, rendering). bloomFusion: "separable" (default) folds the bright extract into the level-0 downsample, sums the mips in one pass and folds the last upsample into the composite, and blurs mips 3 and 4 in 2-D only within a tap budget: the PBR viewer drops from 33 to 25 draw calls with frames within 0.001/255 mean and 1/255 max of production in three remote runs, and frame time at parity with production within runner noise (strictly lower is not established). bloomFusion: "fused-2d" (opt-in, also ?aura3d-bloom-fusion=) blurs mips 1 to 4 in 2-D as well (23 draws) and costs 45% to 52% more frame time on software WebGL2, so it is off until a real-GPU run exists. An invalid value throws. app.diagnostics().renderPath.bloomFusion reports the mode that ran and says off when no fused pass can run (production, safe-basic, or no bloom). quality: "production" keeps the separable chain unchanged.

  • Per-frame cost (engine, rendering). The GL error check runs on the first frames, after resource changes and every 60th frame (every frame in strict and ?diag=1), the default lighting probe builds faster with bit-identical output, and per-frame report objects are built on read. See the next entries for the second round of cuts.

  • Per-frame JS memoisation and allocation cut on the direct path (engine, rendering). Program keys, the registry's active list, the derived core-output-off flags, the page-URL flag and shadow lookups, and the WebGL2 state-cache bookkeeping no longer rebuild or re-serialise what has not changed. On the cube scene this cut allocation from 385 KB to 235 KB per frame and CPU in frame() from 0.17 to 0.12 ms in the deterministic budget test (frame-submission-budget). The remote A/B on software WebGL2 did not reach the 0.5 ms target for the cube frame (1.5 to 1.1 ms gate figure, 1.0 to 0.8 ms settled); evidence/vs-threejs/wp5-frame-submission.md has the profile and the floor.

  • Shadow cascades no visible item can sample are skipped, and the program warm stops compiling variants nothing draws (rendering). On auto and cinematic only (production still draws every cascade), the game-loop scene goes from 38 to 20 draws and from 6 to 4 programs, and the skinned character from 8 to 4 programs, with production pixels byte-identical on both frames. Frame time did not improve on software WebGL2 (both scenes are fill-bound there). See evidence/vs-threejs/wp7/README.md.

  • Dense instanced shadow casters draw depth in one call per node per cascade (rendering). Nodes with 1,024 or more instances that use the engine's instanced shadow proxy go from batches of 64 to one instanced depth draw per cascade: the 10,000-instance forest goes from 569 to 11 draws, pixel-identical on the remote runner. A leak in the vertex-array eviction index (one empty set per disposed instance buffer) is fixed. Opt out with __A3D_DENSE_DEPTH_DRAWS__ = "off".

  • Shadow PCF tap tiers are opt-in (rendering). ?shadowtaps=32|16|8 caps the kernel length of the forward shadow filter; ?qualitytaps=on maps the quality tier (low 8, medium 16) and ?densetaps=on sends scenes with 8,192 or more shadow-casting instances to 8 taps. Nothing changes unless one is set. Measured on the 18,000-instance forest the 8-tap tier changes the frame by 0 to 2% on software WebGL2 (the kernel is already 9 taps) and differs by up to 9/255 at penumbrae, so no tier is applied by default.

  • Time to first frame of the cube (rendering). The analytic floor probe is built and turned into its equirect without per-texel work, and the baked default probe decodes after the first frame has been submitted (a 3 s timeout, and no wait where no renderer exists). Settled frames are bit-identical (SHA-256 equal on all 15 loads). On software WebGL2 through the unbundled dev server, createAuraApp to first frame went from 407-491 ms to 209-252 ms and navigation to first frame from 755-873 ms to 596-649 ms. The 500 ms target holds for the first and not for the second: about 390 ms of navigation is the dev server loading the module graph. The deferral of the baked probe showed no measurable difference in that harness. See evidence/vs-threejs/wp8-first-frame.md.

  • Exposure gaps against three.js are attributed, not calibrated (docs, tests). Remote ablations of the cube, PBR viewer and forest scenes trace each difference to the benchmark sources or documented defaults; no engine change was made. See evidence/exposure-parity.

  • Options that were accepted and ignored now say so (engine). primitives.*, instances.* and interactions.* raise an OPTION_IGNORED diagnostic, naming the valid keys, for an option they do not read (in app.diagnostics(); strict mode throws). primitive() now forwards renderOrder, frustumCulled, layers, userData, rotationOrder and quaternion, which it used to drop. interactions.orbit accepts a node name or a world point plus position and autoRotate.

  • Instancing at scale (engine, rendering). setInstanceTransforms(typedArray) is the per-frame path (about 0.3 ms of CPU for 18,000 instances), unchanged instance buffers skip the upload, and large instanced draws send only the instances inside the camera frustum. Dense shadow-casting instanced nodes (1,024 or more instances of a builtin primitive) use a coarser shadow stand-in, so their shadows are more faceted; opt out with shadowproxy=off. The 10,000-instance forest now completes on software GL in about 4 minutes (it did not finish before); it is still 3.3x to 3.9x the frame time of the same scene in three.js there.

  • Animation layers, root motion, retargeting and two-bone IK work from the root with receipts (engine, animation). Fixes found on the way: playLayer no longer activates its clip as a base action (masks leaked onto the whole body), mask: { humanoid, exclude } is honoured, addClipsFrom accepts a model node handle or id, and the retarget worker falls back to an in-process bake when it cannot start. See docs/api/animation-root.md.

  • Root physics collision for box, sphere and capsule bodies has a receipt (engine). A bare capsule or cylinder physics spec now gets a collider that spans the visible size; physics: { bindNodes: true } makes stepping app.physics move the visible nodes.

  • Scene kits and effects have pixel receipts (engine). Nine scene kits and the ambient-occlusion, colour-grade, vignette and depth-of-field effects have software-WebGL2 receipts. effects.contactShadows produced identical frames in 4.3.7; it is now a real screen-space contact-shadow mask (next entry). Three kits reported problems against their own defaults (particle readiness gate, the solar-system sun check, neon-tunnel ring count) and are fixed; the built-in humanoid GLB is no longer rejected as an unsafe URL. See docs/agents/effects-and-scene-kits.md.

  • effects.contactShadows() builds a screen-space contact-shadow mask that forward lighting samples (rendering). It replaces the interim lowering to the ambient-occlusion pass. The mask is built from scene depth before the forward pass, marched along the sun direction, and multiplied into the visibility of the shadow-casting directional light only (an absent mask is exactly 1.0). It needs a directional light with shadow: true and a shadow caster: with a spot or point light only, or none, the effect renders nothing, diagnostics().shadows.contactShadows.unavailable says why and one warning is logged. Orthographic cameras are supported. The glTF extension variants (clearcoat, transmission and similar) do not sample the mask, and a blocker deeper than twice the march length is ignored by design. Receipt evidence/receipts/effects-contact-shadows/4.5.0.json (software WebGL2, 600x400): the near-base floor darkens where the mask acts, the far floor and the spot and no-caster pairs are unchanged, and no ambient-occlusion pass is requested. Where the shadow map already shadows the receiver the mask adds little; the large effect is in the scene where the subject is outside the shadow map.

  • @aura3d/react: AuraCanvas renders its children and keeps its app across parent re-renders (react). Before, useAuraFrame, useAuraApp and <Model suspendOnLoad> inside it never ran and the app was recreated on every parent render. The package stays experimental; mount, cleanup and StrictMode have not run in a browser.

  • aura3d migrate three: fidelity is tested and reported (cli). Every exact row names a fixture that runs against real three.js; 48 rows are exact (21 before), 150 approximate, 47 none and 0 pending (3 before). 14 math rows (Vector2/3/4, Matrix3/4, Quaternion, Euler, Color, Box3, Sphere, Plane, Ray, Frustum, Triangle) became exact by pointing at the new mutable subpath below; at option sites the codemod still emits the hex string or tuple.

  • New @aura3d/math/three subpath: mutable, three.js-shaped math (math). Vector2/3/4, Matrix3/4, Quaternion, Euler, Color, Box3, Sphere, Plane, Ray, Frustum and Triangle mutate in place, return this, take target out-parameters and return null on a miss, with fromImmutable / toImmutable to cross to the immutable @aura3d/math classes, which are unchanged. 453 method-level goldens taken from three@0.185.1 back tests/unit/math/three-compat-parity.test.ts. See evidence/vs-threejs/wp2-math-compat.md. Fixed on the way: Clock.start() resets elapsed time, step tracks return the key's own value at its keyframe time, ShapeGeometry drops the repeated closing vertex, and SphericalHarmonics3.getBasisAt(normal, target) fills its target.

  • Capability audit and gates (docs, tools). The audit's Proof column names the receipt or test behind each shipped row, and node tools/receipts/check.mjs fails a missing proof and lists receipts to regenerate when the minor version moves. tools/docs-codeblocks typechecks the effect, scene-kit and genre-kit examples and now resolves @aura3d/engine/physics. The benchmark gate is tools/bench-vs-threejs/gate.mjs.

  • Look presets with no HDRI keep their brightness (engine). night-city, polar-night, neon-arcade, space and underwater used to be lit by the implicit neutral-room fill; they now carry an explicit environments.neutral fill (AuraLookV0Expansion.fill, preset: "neutral", intensity 0.85 for the three night looks, 1.0 for space, 0.65 for underwater, calibrated on steady-state captures), so their mean luma stays within about 5% of 4.3.7 in the standard look scene and inside the games. The first cut used the baked night/studio probes, whose diffuse irradiance is about 30x (night) and 0.8x (studio) of the neutral room: they read right on the first frame, while the analytic neutral floor was still bound, and dropped to near black once the baked probe loaded (Aura Diamond 3D Skyline 71 to 2.6, Hoops Shanghai 53.5 to 5.7). Like the implicit fill it replaces, the fill is dropped when the scene authors a lights.ambient. The other ten presets bring an HDRI and are unchanged.

  • Visual change: a scene that authors its own lights and no environment is now lit by those lights only (engine). A directional light of intensity I on a Lambert surface of albedo A gives A * I / PI, ambient gives A * I / PI, and a hemisphere light gives A * mean(ground, sky) * I / PI (three.js r185's value on a horizontal normal; up- and down-facing surfaces differ by half the sky/ground gap). Before this, the scene category's implicit studio/daylight environment was stacked on top of the authored lights, which made the same nominal lights 2x to 13x brighter in linear light (measured on software WebGL2: one grey plane, directional intensity 1 gave linear 0.434 against three.js 0.069; the skinned-character bench scene read mean luma 192.5 against three.js 144.0). Scenes with an environments.* node, scenes with no lights at all, and scenes with a light probe are unchanged. To get the old fill back, add environments.studio() (or any environments.*); to brighten or darken the whole frame, set output: { exposure } on createAuraApp. Hemisphere lights now contribute in this mode (they were dropped before).

  • Ambient occlusion and depth of field read real depth (rendering). createRenderTarget gave RGBA32F colour targets linear filters, which makes them texture-incomplete until OES_texture_float_linear is enabled, so the v2 post chain's view-space depth read as zero: ambient occlusion darkened the frame by a constant factor with no crease structure and depth of field read the same zeros. renderTargetSampleFilter enables the extension and falls back to NEAREST for RGBA32F without it; RGBA8 and RGBA16F are unchanged, and scenes that never create an RGBA32F target are unchanged. This is an intended change to quality: "production" output, for scenes with ambient occlusion, god rays, depth of field, TAA, motion blur or auto-exposure.

  • WEIGHTS_0 rescaling of rounding noise no longer logs a console warning (assets). Skin weights whose sum is within 5% of 1 (export rounding on unmodified assets; the three.js Soldier has 52 vertices up to 2% off) are renormalized silently with one console.debug line per primitive; real defects (degenerate or badly scaled weights) still warn.

Upgrade notes for the 25 live showcase games: docs/upgrade-4.5.0.