AURA

JSGG

AuraJS
DOCSEXAMPLESGITHUB
08 Draw3D, Camera, Lighting, Shadows, and PostFX
3D drawing, camera setup, lighting, shadows, and post-processing surfaces.
docs/external/game-dev-api/08-draw3d-camera-lighting-shadows-and-postfx.md

Draw3D, Camera, Lighting, Shadows, and PostFX

These are the main real-time 3D submission and render-control surfaces.

`aura.draw3d`

Primary 3D drawing/control methods:

  • drawMesh(meshHandle, materialHandle, transformOrNode)
  • billboard(textureHandleOrSource, options)
  • drawSkybox(textureHandleOrSpec)
  • setEnvironmentMap(pathOrCubeCameraOrNull)
  • clear3d(color)
  • setFog(fogSpec)
  • clearFog()
  • setOcclusionCulling(enabledOrOptions)
  • addDecal(options)
  • createCubeCamera(options?)
  • updateCubeCamera(cubeCameraHandle)
  • destroyCubeCamera(cubeCameraHandle)
  • createRenderTarget(width, height, options?)
  • setRenderTarget(renderTargetHandleOrNull, captureCamera?)
  • destroyRenderTarget(renderTargetHandle)
  • createLine(options)
  • createLineSegments(options)
  • updateLine(lineHandle, patch)
  • destroyLine(lineHandle)
  • clearStencil(value?)

PostFX lives here today, not in a separate namespace:

  • setPostFXPass(name, options)
  • setPostFXEnabled(name, enabled)
  • removePostFXPass(name)
  • getPostFXState()
  • registerPostFXShader(name, wgslSource)

Stage 180 landed stock named postFX presets on top of this same seam rather than creating a separate post-processing namespace.

Fog and Atmosphere V1

The retained fog seam still starts at setFog(...), and Atmosphere V1 layers onto that same entry point instead of introducing a second namespace.

Example:

aura.draw3d.setFog({
  mode: 'exp2',
  color: { r: 0.08, g: 0.12, b: 0.18 },
  density: 0.045,
  atmosphere: {
    baseY: 1.5,
    falloff: 14.0,
    rayStrength: 0.35,
    rayDecay: 0.92,
    rayExposure: 0.26,
  },
});

Truthful boundaries:

  • the base fog contract remains global retained fog on the main forward 3D scene pass
  • Atmosphere V1 adds one bounded height-aware fog overlay plus one renderer- owned screen-space shaft lane
  • the shaft direction comes from the active directional light; with no directional light, the height-fog overlay can still run but shafts fall away
  • the atmosphere overlay is perspective-camera-only
  • this is not true volumetric fog, not local fog volumes, and not a per-object fog-exclusion system
  • the overlay does not promise cube-camera capture, render-target capture, or other non-swapchain paths

clearFog() clears both the retained fog state and the coupled Atmosphere V1 overlay.

Cube-camera truth

Stage 191 closes one bounded live cube-camera capture lane on top of the existing command surface.

Public seam:

  • createCubeCamera({ position?, near?, far?, resolution?, facesPerFrame? })
  • updateCubeCamera(cubeCamera)
  • destroyCubeCamera(cubeCamera)
  • setEnvironmentMap(cubeCamera) to use the captured cubemap as the shared live IBL source

Runtime and cost truth:

  • capture is explicit and manual, not an automatic every-frame reflection pass
  • new cube cameras start dirty, and updateCubeCamera(...) or position changes mark all six faces dirty again
  • facesPerFrame clamps to 1..6 and defaults to 6
  • the renderer refreshes faces round-robin, so facesPerFrame: 2 settles over three frames instead of forcing all six faces in one frame
  • this is one shared scene-level live IBL source, not per-material localized cubemap reflections

Current explicit boundaries:

  • aura.material.setCubeMapTexture(...) is still a later per-material consumer lane; the current shipped live path is setEnvironmentMap(cubeCamera)
  • cube-camera capture covers the skybox/gradient plus queued forward mesh draws
  • the capture path does not currently include postFX, skinned meshes, terrain, billboards, decals, retained 3D lines, shadow passes, HiZ occlusion, or stencil-on-capture behavior
  • while faces are being refreshed, the capture pass uses fallback IBL rather than recursively sampling the in-flight cube camera

Example:

const cubeCam = aura.draw3d.createCubeCamera({
  position: { x: 0, y: 2.5, z: 0 },
  resolution: 256,
  facesPerFrame: 2,
});

aura.draw3d.setEnvironmentMap(cubeCam);

// Refresh the shared live IBL source after moving the probe.
aura.draw3d.updateCubeCamera(cubeCam);

Custom postFX contract

Custom WGSL postFX now has one explicit truthful input contract:

  • @binding(0) input_texture: texture_2d<f32> current scene color immediately before this custom pass
  • @binding(1) input_sampler: sampler shared filtering sampler for scene-color reads
  • @binding(2) u_postfx resolution, texel size, stock params, time, 8 custom param slots, camera near/far, projection mode, and step/input-contract flags
  • @binding(3) depth_sampler: sampler_comparison comparison sampler for depth-aware reads
  • @binding(4) depth_texture: texture_depth_2d scene depth from the current swapchain 3D pass
  • @binding(5) original_scene_texture: texture_2d<f32> stable scene-color copy from before the postfx chain started

Truthful boundaries:

  • custom shaders can now compare the current pass input against the original pre-postfx scene color
  • custom shaders can read scene depth, and the uniform now includes camera near/far plus an orthographic flag so depth interpretation is no longer guesswork
  • there is still no normal buffer / G-buffer exposure
  • there is still no arbitrary intermediate-buffer readback or user-defined multi-input render graph

aura.draw3d.getPostFXState() now also reports:

  • resolvedSteps for the deterministic runtime execution order
  • customShaderBindings for the public binding map
  • customShaderContract for the supported input semantics and remaining limits

Stock outline postFX

Stage 191 adds one stock global outline lane on top of the existing postFX composer seam:

aura.draw3d.setPostFXPass('outline', {
  strength: 0.82,
  radius: 1.5,
  threshold: 0.35,
  customParams: {
    depthThreshold: 0.018,
    lumaThreshold: 0.08,
    outlineR: 0.08,
    outlineG: 0.95,
    outlineB: 1.0,
  },
});

Truthful boundaries:

  • this is a screen-space full-frame stylized edge pass, not geometry extrusion or per-material toon outlining
  • the pass reads scene color plus depth, and it may use the stable original_scene_texture copy to keep edge detection from drifting after earlier postFX
  • the public tuning knobs are strength, radius, threshold, depthThreshold, lumaThreshold, and outlineR/G/B
  • it does not expose normals or a G-buffer, so extremely subtle coplanar edges still depend on depth and luminance contrast rather than true normal-based edge detection

`particles3d` runtime truth on Draw3D

aura.particles3d.draw() currently renders through the same billboard/material submission seam described below rather than through a separate GPU-simulated particle pipeline.

Current truthful runtime behavior:

  • retained emitters now have explicit lifecycle controls through create/destroy/emit/burst/pause/resume/stop/getState
  • one-shot burst(options) emitters auto-clean after their particles empty
  • draw() submits billboard-backed particle draws into the main swapchain 3D scene path
  • aura.debug.inspectorStats() now includes scene3dRuntime.particles3d so particle counts, draw calls, and lifecycle state do not stay opaque

Current postfx truth:

  • the bounded proof lane is the main swapchain scene where particle billboards submit before the standard full-frame postfx composer executes
  • this is now covered by focused runtime proof for the shared draw3d plus particles3d seam
  • deeper compatibility claims for SSR, DOF, motion blur, SSAO, cube-camera capture, or render-target capture remain later follow-on work rather than a promised fully integrated particle render graph

Billboard seam

aura.draw3d.billboard(...) is the lightweight camera-facing quad path for actors, effect cards, and simple in-world screens.

Truthful texture-source inputs today:

  • numeric handle from an already-resolved material or video texture lane
  • { dataTextureHandle } bridge object for procedural billboards backed by aura.material.createDataTexture(...)

The bridge object exists because data textures and materials do not share one safe public handle namespace. Use the bridge form for procedural/no-asset billboards instead of guessing that a bare overlapping numeric handle will resolve the way you intended.

`particles3d` runtime truth

aura.particles3d.draw() currently uses aura.draw3d.billboard(...) on the shared scene3d runtime path.

Focused truth after Stage 187:

  • the public lifecycle seam now includes emit, pause, resume, stop, draw, and getState
  • focused proof covers particles lifecycle ownership plus normal postfx execution in the same runtime flow
  • this is not a dedicated particle-compositing path and does not yet promise bespoke compatibility guarantees for SSR, SSAO, DOF, or motion blur

Decal seam

aura.draw3d.addDecal(options) is currently a narrow detail-overlay seam. The shipped renderer path submits oriented decal quads / decal plane batches that you place explicitly in the world.

Use it for authored surface detail such as signage overlays, puddle-edge accents, grime cards, scorch marks, or graffiti planes.

Accepted authored albedo sources today:

  • texture: 'path/to/decal.png'
  • textureHandle: aura.material.createDataTexture(...)
  • color: { r, g, b, a? } for a flat-color fallback or tint

You can combine color with either texture source as a tint, but not with both texture and textureHandle at the same time.

Current geometry truth:

  • the active renderer path resolves one oriented overlay plane per authored decal command
  • size.x / size.z define the visible overlay footprint
  • size.y stays part of the authored payload for future projected-volume compatibility, but it does not currently widen the live overlay mesh into a true receiver-projected decal volume

It does not currently guarantee true projected decals onto arbitrary scene geometry. In particular, it is not a mesh-conforming deferred decal pass that automatically wraps across mixed receiver topology.

3D render-target capture seam

aura.draw3d.createRenderTarget(...) plus aura.draw3d.setRenderTarget(renderTargetHandleOrNull, captureCamera?) is the explicit offscreen full-scene 3D capture lane.

Current truthful modes:

  • setRenderTarget(handle) redirects the current frame's full 3D scene pass into that render target using the current camera state
  • setRenderTarget(handle, { position, target, up?, fovDegrees?, near?, far? }) redirects that full 3D scene pass into the target from an alternate perspective camera
  • setRenderTarget(null) restores rendering to the swapchain

Current explicit boundaries:

  • this is an offscreen full-scene capture seam, not an automatic dual-render CCTV or picture-in-picture pass that also draws a second copy to the swapchain in the same call
  • the capture uses the render target's aspect ratio rather than the window aspect ratio
  • current postfx, HiZ occlusion, and stencil-on-render-target behavior remain outside this seam and stay swapchain-only

`aura.camera3d`

Projection, transform, and control helpers:

  • perspective(fov, near, far)
  • orthographic(sizeOrBounds, near, far)
  • setProjectionMode(mode)
  • setOrthoSize(size)
  • getProjectionMode()
  • lookAt(x, y, z)
  • setPosition(x, y, z)
  • setTarget(x, y, z)
  • setFOV(fov)
  • getViewMatrix()
  • getProjectionMatrix()
  • setControlProfile(profile, options)
  • updateControls(dt, inputState)
  • getControlState()

Use cases:

  • scripted cameras
  • orbit/editor-style controls
  • view/projection extraction for custom logic

`aura.light`

Light creation and updates:

  • ambient(color, intensity?)
  • hemisphere(skyColor, groundColor, intensity?, upDirection?)
  • directional(direction, color, intensity?)
  • point(position, color, intensity?, range?)
  • spot(position, direction, color, intensity?, range?, angle?)
  • update(lightHandle, patch)
  • remove(lightHandle)

Shadow control also lives here today:

  • configureDirectionalShadows(options)
  • configureShadow(lightHandle, options)
  • getShadowState()
  • getShadowStats()
  • setShadowCasting(nodeOrHandle, enabled)
  • setShadowQuality(levelOrOptions)
  • setShadowBudget(options)

Important note:

  • There is no separate aura.shadow namespace today.
  • If you are building tooling or agents, treat shadows as part of aura.light.

Reality Check: High-Impact 3D Features Already Ship

If you are planning "AAA next steps", do not treat the following as missing engine features in native AuraJS:

  • stock postFX presets now ship on top of the existing custom WGSL seam
  • a stock global outline postFX pass now ships on the same seam
  • custom WGSL postFX registration already exists
  • spot lights and hemisphere fill lighting already exist
  • IBL/environment maps already exist
  • one bounded shared live cube-camera IBL lane now exists
  • billboards already exist
  • occlusion culling already exists
  • 3D render targets already exist
  • one canonical neon vertical slice now exists to prove multiple shipped surfaces together

Stage 180 also closed the previously drifting seams:

  • stencil is truthful on the swapchain forward path
  • retained 3D lines are truthful as a retained runtime seam
  • decals are a narrow authored-overlay seam (oriented decal quads / planes), not arbitrary projected decals

The remaining frontier is broader renderer breadth, especially true projected decals and wider render-path coverage, not rediscovering the shipped quick wins above.

Stage 180 surfaced the shipped quick wins with copyable snippets and example coverage.

Quick-win starter snippet:

aura.draw3d.setEnvironmentMap('env/cyberpunk.hdr');
aura.draw3d.setOcclusionCulling({ enabled: true, conservative: true });
aura.light.spot(
  { x: 8, y: 6, z: 8 },
  { x: -0.45, y: -1.0, z: -0.25 },
  { r: 0.45, g: 0.78, b: 1.0 },
  1.35,
  28,
  0.72,
);

Material-side follow-up lives on the next page:

  • runtime emissive via aura.material.setEmissive(handle, color, strength?)
  • runtime clearcoat via aura.material.setClearcoat(handle, clearcoat, roughness)
  • denser grid planes via aura.mesh.createPlane(width, depth, widthSegments, depthSegments)
  • sheen via aura.material.create({ sheenColor, sheenRoughness })
  • texture transforms via aura.material.setTextureTransform(...)
  • data textures via aura.material.createDataTexture(...)
  • stencil APIs via aura.material.setStencil(...)

Current truthful boundary:

  • emissive and clearcoat now have a runtime setter seam
  • dense grid planes now stay on the existing createPlane(...) API with extra optional segment counts
  • subsurface remains a later advanced-material task, not part of the shipped runtime parity surface here

Current namespace layout caveats

  • There is no separate aura.postfx namespace today.
  • There is no separate aura.shadow namespace today.
  • aura.scene3d handles hierarchy, imported-scene metadata, clip helpers, and render binding; it does not replace aura.draw3d.drawMesh(...) ownership.

Follow-up page

For scene graph, meshes, materials, glTF import, and skinned meshes, continue to:

DOCUMENT REFERENCE
docs/external/game-dev-api/08-draw3d-camera-lighting-shadows-and-postfx.md
AURAJS
Cmd/Ctrl+K
aurajsgg