Log inGet started

Rendering

Most of how a scene looks comes from its content — meshes, materials, and lights. This guide is about the scene-wide controls on top of that: lighting, post-process effects, and the renderer's global…

Lighting

The lighting toolbox is the scene's light environment — sun, ambient, sky, and point/spot lights, each with modify-or-spawn semantics. When you're setting a scene up interactively from execute(), reach for the tools; a component or runtime script that adjusts lighting each frame uses the modules.api.engine.lighting capability directly (same opts, no tool call):

-- authoring the scene's lighting from execute():
tools.use("lighting", "setSun", { direction = { -0.5, -1, -0.3 }, intensity = 3, color = { 1, 0.95, 0.8 } })
tools.use("lighting", "setAmbient", { intensity = 0.2 })
tools.use("lighting", "setSky", "sunset")       -- preset word, material word, or "none"
tools.use("lighting", "addLight", "torch", { x = 2, y = 3, z = 0 }, { intensity = 5 })
tools.use("lighting", "setLight", "torch", { intensity = 8 })
tools.use("lighting", "removeLight", "torch")
tools.use("lighting", "setup", opts)            -- sun + ambient + sky + clearColor at once
tools.use("lighting", "get")                    -- full lighting + sky state, feeds back into setup

Lighting is real: with every source removed the scene goes dark. Lights also exist as a component (entity.component.add("Light", { intensity = 5 })) for ones that move with an entity — the lighting surface is the scene-environment one, the Light component is the per-entity one.

The sky is an explicit component on an entity — ProceduralSky for the procedural day/night look, or Skybox for a material sky or kind = "none" to turn it off. A scene with no sky entity renders the engine's fallback sky and carries a missing-sky warning; tools.use("lighting", "setSky", ...) creates (or reconfigures) the scene's sky entity and clears the warning.

Global illumination — baked lightmaps & light probes

The lights above are direct light. Indirect light — bounce, soft ambient, contact shadows — comes from the @builtin::systems.globalIllumination system, which bakes global illumination offline so static scenes get realistic indirect lighting at no per-frame cost. Two flavours:

  • Lightmaps — baked indirect light stored per-entity, for static geometry. Add the Lightmap component, then bake:

    tools.use("lightmap", "attach", entity)   -- add the Lightmap component (sets bake params)
    tools.use("lightmap", "bakeAll")          -- bake every entity in the scene that carries a Lightmap
    tools.use("lightmap", "bake", entity)     -- re-bake just one; clear frees its resources
    
  • Light probes — baked indirect light sampled through a volume, for dynamic entities moving through a baked space. Author a VolumeProbe, bake it, and bind movers (which carry a LightProbeReceiver):

    tools.use("volumeProbe", "create", opts)  -- spawn a probe-volume entity
    tools.use("volumeProbe", "bakeAll")       -- bake every VolumeProbe in the scene
    tools.use("volumeProbe", "bind", entity)  -- a dynamic entity samples indirect light from the volume
    

The full system (bake settings, denoising, UV requirements, the math) is its own package — guides / asset.inspect("@builtin::systems.globalIllumination") and lsp.methods("lightmap") / lsp.methods("volumeProbe") cover it in depth. ReflectionProbe (per-entity specular reflections) is a sibling component.

Post-process

Full-screen effects run after the scene renders — bloom, tonemap, colour grading, your own. Each is a shader (the shaders guide) registered as a named pass:

postprocess.add("bloom", bloomShaderSource, opts)
postprocess.setEnabled("bloom", true)
postprocess.setParam("bloom", slot, values)
postprocess.list()            -- also setTexture / setSampler / remove

Render features — custom GPU passes (outlines, x-ray, wireframe, decals)

A render feature inserts your own GPU passes into the frame, every frame — the generic capability behind mesh outlines, x-ray, custom wireframe, decals, and any effect the built-ins don't cover. A feature is a .renderFeature asset whose render(ctx) hook calls ctx.enqueue{...} to queue passes; the engine runs them at a named pipeline phase. Three pass kinds: "fragment" (full-screen shader), "compute" (dispatch), and "geometry" (re-draw scene meshes with your pipeline).

A geometry pass is the one for mesh effects — it redraws scene render-objects with a material you construct, and can target specific entities:

ctx.enqueue {
    kind     = "geometry",
    material = myMatKey,          -- a material from renderer.material.create
    geometry = "@geometry.opaque", -- "@geometry.opaque" | "@geometry.transparent" | "@geometry.all"
    select   = { entityA, entityB }, -- OPTIONAL: only these entities (refs); omit = all
    phase    = "afterLighting",   -- lit scene + valid depth
}

select restricts the draw to the given entities — what turns a whole-scene pass into a chosen-meshes effect. The material decides how those meshes are drawn, and you control three things:

  • Render-state. renderer.material.create takes a render block — cull (back/front/none), depthWrite, blend — so the pass rasterizes differently from the surface pass. properties seeds the shader's declared uniforms (a flat { name = value } map, matching the names in properties.yaml; a colour is a {r,g,b,a} array). textures binds its declared texture slots. There is no colors/floats split — that is the mat.yaml authoring shape, not this runtime call:
    renderer.material.create({
        shader     = myShaderKey,               -- a .shader you authored (identity, not guid)
        render     = { cull = "none", depthWrite = false },
        properties = { my_color = { 1, 0.45, 0, 1 } }, -- seeds material.my_color
    }, "my_pass_mat")
    
  • The shader. A geometry .shader is a surface shader: fn vertex(v: VertexData) -> VertexData moves vertices (v.position/v.normal, object space); fn fragment(...) -> vec4<f32> is the colour.
  • Its properties. A shader reads ONLY the uniforms it declares in properties.yaml, via material.<yourProp> — and both vertex() and fragment() can read them (the generated material uniform is bound for both stages). Referencing one it didn't declare (e.g. material.base_color) fails to compile and renders magenta; the compile error in the engine log names the field and the fix (declare it, or use a constant). (See the shaders guide.)

What effect that produces — outline, x-ray, wireframe, highlight — is yours to design from those pieces; the engine names none of them.

The legacy visuals.setOutline / Model:setOutline / sc.outline (and the EntityVisuals.outline field) are deprecated — they predate render features and draw nothing. A mesh outline is a render-feature geometry pass.

The full feature contract (setup / render / teardown, ctx, every phase, the @scene.* / @frame.camera / @geometry.* names) is in the renderFeature assetType README. Reserved inputs resolve per render target, so a screen-space feature that reconstructs world space from depth via @frame.camera is correct in the live viewport AND in offscreen captures (capture(source = "position"/"entity")) from a different camera.

Scene-wide renderer settings

The global pipeline switches — shadows, deferred shading, order-independent transparency, culling, debug channel — live in the world's .world_settings file under [render], not in a runtime API. Edit them there (the engine/development guides cover world settings):

[render]
shadows = true
deferred = true
oit = true
culling_mode = "gpu"
debug_channel = 0      # > 0 visualises normals / depth / etc. while diagnosing

Per-camera overrides live on the Camera componentdebugChannel and the renderLayers spec (a space-separated list of layer names, e.g. "all !ui", where ui / sky / postProcess / EditorUI are built-in layers) decide what that camera draws (the render-textures guide uses this for secondary views).

Finding the rest

lsp.methods("lights") and lsp.methods("postprocess") list those surfaces in full; asset.inspect("@builtin::components.Camera") shows the camera's render fields. For rendering into a texture (mirrors, minimaps, in-UI 3D) see the render-textures guide; for the shaders these passes run, the shaders guide. The model to hold: content makes the look, lights/postprocess tune the scene at runtime, and .world_settings [render] sets the pipeline.

  • documentation
  • guide