Log inGet started

postprocess

Updated October 16, 2026

Fullscreen post-process effects — register / remove / toggle / named-property updates / list the chain / read one effect's declared property schema and the value each property holds.

require("@builtin::modules.api.engine.postprocess") -- postprocess (also available as global 'postprocess')

The postprocess namespace: 27 functions.

globals/postprocess/add

postprocess.add(name: string, shader: string | AssetRef, opts: PostprocessOpts?) -> (boolean, string?)

Register a fullscreen post-process effect. This call is what puts a pass into the frame — a post-process .shader asset defines an effect, and renders only once registered here. The chain applies the registration on the caller's own stack and the returned boolean is its answer, so a setProperty or setTexture naming the effect in the same call finds it. shader is a .shader asset reference whose shader.wgsl provides fn fragment(in: PostInput) -> vec4<f32> and whose properties.yaml declares the effect's properties; the engine generates the group(0) framework + schema-driven group(1) from that schema. Editing that shader afterwards recompiles this effect in place, keeping its enabled state, priority, layer and tuned property values. WGSL text is accepted in place of a shader reference, and then opts.properties is the whole schema — this form is deprecated and will be removed, author the effect as a .shader asset instead. Effects run in priority order (lower first, default 100). A registered effect runs over the live viewport's frame AND over every offscreen one — a capture from a world-space station, one orbiting an entity, one of a named camera, a render-to-texture camera. In each of those the effect's engine.view_proj / engine.prev_view_proj / engine.inv_view_proj are the camera THAT render was drawn from and engine.resolution is that target's own size, so a pass reconstructing world space from zero_scene_depth(uv) reconstructs against the station and lens the capture asked for. An offscreen capture is therefore an oracle for an authored grade: it photographs a chosen station without taking the on-screen camera from whoever else is driving the scene, and a capture's postProcessing = false is the one control that takes the chain off the frame it returns. An offscreen render keeps no view history of its own, so engine.prev_view_proj there holds that same matrix rather than the frame before it, and a pass taking camera motion from the two reads none.

Parameters

  • name string — Unique effect name.
  • shader string | AssetRef — A resolved shader asset reference, or author WGSL (fn fragment(in: PostInput) only).
  • opts PostprocessOpts (optional) — { priority = 100, enabled = true, layer = "scene"|"all" (omitted: follows space), space = "light"|"display", properties = {{name, type, default?, min?, max?, textureDefault?}}, passes = {{name, entry, scale?, inputs?}} } — with a shader asset, properties layers over the asset's own schema. layer picks which composited image the effect grades: "scene" runs it before the UI is drawn, over the scene's own light in a float target, so it grades the rendered picture and leaves every widget on screen as authored, and "all" runs it after the UI has landed, over the picture the display transform fitted to the screen, so the interface is graded along with the picture. A call that names no layer takes the one its space belongs to: a "light" effect grades "scene" and a "display" effect grades "all", so a bloom reads the scene's highlights where they still stand above 1.0 and a lookup table reads code values. Naming "all" is what an effect that must grade the interface says — and on a world several authors share, it grades everyone else's screens too. space names what fragment() reads and returns: "light" (the default) for a threshold/blur/grade on the scene, "display" for a lookup table, dither ladder or authored overlay working on code values. With a shader asset, defaults to what that asset's getSpace() declares. passes names the passes the effect draws, each an entry in the shader, a fraction of the view it draws at, and what it reads. With a shader asset, defaults to what that asset's getPasses() declares; a single-pass effect (the common case) declares none. textureDefault is what a type = "texture" property samples while nothing is bound to it: "white" (1,1,1,1 — the default), "black" (0,0,0,1), "normal" (0.5,0.5,1,1) or "transparent" (0,0,0,0). An effect that lays its texture over the scene wants "transparent", so the frame is untouched until setTexture binds a texture that exists.

Returns (boolean, string?) — True when the chain took the effect; false and the compiler's message when it refused it. A shader that does not compile draws nothing at any property value, so it is not registered and postprocess.list() never names it. __display__ and __blit__ are the renderer's own effects, and a registration under either name is refused with a message saying so. A call made from inside queue() or batch(), where the engine has not run the registration by the time the call returns, answers true for the queued request.

postprocess.add("vignette", asset.resolve("@builtin::shaders.post.vignette", "shader"))
postprocess.add("vignette", VIGNETTE_WGSL, { properties = {{ name = "intensity", type = "float", default = 0.5 }} })

globals/postprocess/describe

postprocess.describe(name: string) -> PostprocessDescription?

One effect by name, read in full: the chain state postprocess.status() lists for it, and on top of that properties — the schema the effect declared, each entry { name, type, default?, min?, max?, textureDefault? } in the shape add takes — and values, what each of those properties currently holds. A property's value is the one the last setProperty wrote, or the schema's own default where nothing has written one, and it comes back as a number for a scalar and as the array for a wider value, which is what setProperty takes, so a property read here is written straight back.

This is the read-back for a property write. setProperty answers whether the uniform took the value; this answers what the effect holds now, which is the reading a pass that writes its properties every frame needs and the one that tells a mistyped property name from an effect that is not grading. The schema and the values are the engine's own record of the effect — the schema it was registered with and every write the chain accepted into its uniform, the same record /runtime/fx/<name>/meta.json is serialized from. A write the chain refused is not in it, and neither is one made against a property the schema does not declare.

Before an effect is registered its schema lives on the .shader asset it will render: asset.resolve("@builtin::shaders.post.bloom", "shader"):getProperties() names what that shader declares.

Parameters

  • name string — Effect name.

Returns PostprocessDescription? — The effect's state, schema and live values, or nil when nothing is registered under the name. An effect the renderer registers itself declares no properties of its own, and its properties and values are empty.

local e = postprocess.describe("vignette")
print(if e then e.values.intensity else "not registered")
for _, p in ipairs(postprocess.describe("bloom").properties) do
print(p.name, p.type, p.min, p.max)
end

globals/postprocess/list

postprocess.list() -> { string }

List all registered post-process effect names in renderer priority order (lower priority runs first).

Returns { string } — Array of effect names.

for _, n in ipairs(postprocess.list()) do print(n) end

globals/postprocess/remove

postprocess.remove(name: string) -> boolean

Queue removal of a post-process effect. Takes effect on the next frame. Removing a name that isn't registered is a silent no-op.

Parameters

  • name string — Effect name to remove.

Returns boolean — True when the mutation was queued. False, named in a WARN, for the two effects the renderer registers itself: __display__, the chain's display transform, which stays in every frame and is tuned through its curve and exposure properties, and __blit__, the copy that carries every render into its target, every capture and the viewport included.

postprocess.remove("vignette")

globals/postprocess/setEnabled

postprocess.setEnabled(name: string, enabled: boolean) -> boolean

Queue an enable/disable toggle on a registered post-process effect. Targeting an unknown name is a silent no-op.

Parameters

  • name string — Effect name.
  • enabled boolean — True to enable, false to disable.

Returns boolean — True when the mutation was queued. False, named in a WARN, for a __display__ disable (the chain's display transform stays in every frame, and curve = 0 is its linear curve) and for a __blit__ enable (the copy that carries every render into its target joins no chain).

postprocess.setEnabled("bloom", false)

globals/postprocess/setProperty

postprocess.setProperty(name: string, prop: string, value: (number | { number })) -> boolean

Set a named material property on a registered post-process effect. The property must be declared in the effect's properties schema; read in WGSL as material.<prop>. value is a number or a number array (vec/color).

Parameters

  • name string — Effect name.
  • prop string — Declared property name.
  • value (number | { number }) — Number or array of numbers.

Returns boolean — True when the effect's uniform took the value; false when it did not — an effect that is not registered, or one that declares no property by that name, is named in a WARN in the engine log. A call made from inside queue() or batch(), where the engine has not run the write by the time the call returns, answers true for the queued request.

postprocess.setProperty("vignette", "intensity", 0.6)

globals/postprocess/setSampler

postprocess.setSampler(name: string, opts: { [string]: any }) -> boolean

Configure the per-effect user sampler shared by the effect's declared texture properties. opts.filter = "linear" (default) or "nearest". opts.wrap (alias .address) = "clamp" (default), "repeat", or "mirror" — applied to all axes.

Parameters

  • name string — Effect name.
  • opts { [string]: any } — { filter = "linear"|"nearest", wrap = "clamp"|"repeat"|"mirror" }.

Returns boolean — True — the mutation was queued.

postprocess.setSampler("blur", { filter = "linear", wrap = "clamp" })

globals/postprocess/setTexture

postprocess.setTexture(name: string, prop: string, path: string) -> boolean

Bind a texture to one of an effect's declared texture properties. Declare it in properties ({ name = "noise", type = "texture" }) and sample in WGSL as textureSample(noise, noise_sampler, in.uv). path is any TextureCache-resolvable spec (@builtin::textures.foo, color:1,0,0, default:white, a render-target name, ...). A path whose texture has not reached the GPU yet — one this same script created — is held and bound as soon as it does; postprocess.status() reports it under pendingTextures until then.

Parameters

  • name string — Effect name.
  • prop string — Declared texture-property name.
  • path string — Texture path / spec.

Returns boolean — True when the slot took the binding, including one held until its texture reaches the GPU; false when it did not — an effect that is not registered, or one that declares no texture property by that name, is named in a WARN in the engine log. A call made from inside queue() or batch(), where the engine has not run the binding by the time the call returns, answers true for the queued request.

postprocess.setTexture("__vsky_composite", "cloud", "cloud_target")

globals/postprocess/status

postprocess.status() -> { PostprocessStatus }

Every registered effect in chain order with the state that decides whether it reaches the frame — enabled flag, priority, layer, the shader's compile error when it has one, the .shader asset it renders when it was registered from one, the texture each declared slot is bound to (textures) and the bindings still waiting for their texture (pendingTextures). This is what the renderer draws with, so a survey of the chain answers "is this one affecting the picture right now?" without capturing a frame and reading pixels.

An effect this script has just registered is listed with pending = true until the renderer publishes it, since a registration is queued for the next frame.

Returns { PostprocessStatus } — Array of per-effect state, in the order the chain runs them.

for _, e in ipairs(postprocess.status()) do
print(e.name, e.enabled, e.layer, e.error)
end

modules/postprocess/add

add(name: string, shader: string | AssetRef, opts: PostprocessOpts?): (boolean, string?)

Register a fullscreen post-process effect. This call is what puts a pass into the frame — a post-process .shader asset defines an effect, and renders only once registered here. The chain applies the registration on the caller's own stack and the returned boolean is its answer, so a setProperty or setTexture naming the effect in the same call finds it. shader is a .shader asset reference whose shader.wgsl provides fn fragment(in: PostInput) -> vec4<f32> and whose properties.yaml declares the effect's properties; the engine generates the group(0) framework + schema-driven group(1) from that schema. Editing that shader afterwards recompiles this effect in place, keeping its enabled state, priority, layer and tuned property values. WGSL text is accepted in place of a shader reference, and then opts.properties is the whole schema — this form is deprecated and will be removed, author the effect as a .shader asset instead. Effects run in priority order (lower first, default 100). A registered effect runs over the live viewport's frame AND over every offscreen one — a capture from a world-space station, one orbiting an entity, one of a named camera, a render-to-texture camera. In each of those the effect's engine.view_proj / engine.prev_view_proj / engine.inv_view_proj are the camera THAT render was drawn from and engine.resolution is that target's own size, so a pass reconstructing world space from zero_scene_depth(uv) reconstructs against the station and lens the capture asked for. An offscreen capture is therefore an oracle for an authored grade: it photographs a chosen station without taking the on-screen camera from whoever else is driving the scene, and a capture's postProcessing = false is the one control that takes the chain off the frame it returns. An offscreen render keeps no view history of its own, so engine.prev_view_proj there holds that same matrix rather than the frame before it, and a pass taking camera motion from the two reads none.

Parameters

  • name string — Unique effect name.
  • shader string | AssetRef — A resolved shader asset reference, or author WGSL (fn fragment(in: PostInput) only).
  • opts PostprocessOpts? (optional) — { priority = 100, enabled = true, layer = "scene"|"all" (omitted: follows space), space = "light"|"display", properties = {{name, type, default?, min?, max?, textureDefault?}}, passes = {{name, entry, scale?, inputs?}} } — with a shader asset, properties layers over the asset's own schema. layer picks which composited image the effect grades: "scene" runs it before the UI is drawn, over the scene's own light in a float target, so it grades the rendered picture and leaves every widget on screen as authored, and "all" runs it after the UI has landed, over the picture the display transform fitted to the screen, so the interface is graded along with the picture. A call that names no layer takes the one its space belongs to: a "light" effect grades "scene" and a "display" effect grades "all", so a bloom reads the scene's highlights where they still stand above 1.0 and a lookup table reads code values. Naming "all" is what an effect that must grade the interface says — and on a world several authors share, it grades everyone else's screens too. space names what fragment() reads and returns: "light" (the default) for a threshold/blur/grade on the scene, "display" for a lookup table, dither ladder or authored overlay working on code values. With a shader asset, defaults to what that asset's getSpace() declares. passes names the passes the effect draws, each an entry in the shader, a fraction of the view it draws at, and what it reads. With a shader asset, defaults to what that asset's getPasses() declares; a single-pass effect (the common case) declares none. textureDefault is what a type = "texture" property samples while nothing is bound to it: "white" (1,1,1,1 — the default), "black" (0,0,0,1), "normal" (0.5,0.5,1,1) or "transparent" (0,0,0,0). An effect that lays its texture over the scene wants "transparent", so the frame is untouched until setTexture binds a texture that exists.
postprocess.add("vignette", asset.resolve("@builtin::shaders.post.vignette", "shader"))
postprocess.add("vignette", VIGNETTE_WGSL, { properties = {{ name = "intensity", type = "float", default = 0.5 }} })

modules/postprocess/describe

describe(name: string): PostprocessDescription?

One effect by name, read in full: the chain state postprocess.status() lists for it, and on top of that properties — the schema the effect declared, each entry { name, type, default?, min?, max?, textureDefault? } in the shape add takes — and values, what each of those properties currently holds. A property's value is the one the last setProperty wrote, or the schema's own default where nothing has written one, and it comes back as a number for a scalar and as the array for a wider value, which is what setProperty takes, so a property read here is written straight back.

This is the read-back for a property write. setProperty answers whether the uniform took the value; this answers what the effect holds now, which is the reading a pass that writes its properties every frame needs and the one that tells a mistyped property name from an effect that is not grading. The schema and the values are the engine's own record of the effect — the schema it was registered with and every write the chain accepted into its uniform, the same record /runtime/fx/<name>/meta.json is serialized from. A write the chain refused is not in it, and neither is one made against a property the schema does not declare.

Before an effect is registered its schema lives on the .shader asset it will render: asset.resolve("@builtin::shaders.post.bloom", "shader"):getProperties() names what that shader declares.

Parameters

  • name string — Effect name.
local e = postprocess.describe("vignette")
print(if e then e.values.intensity else "not registered")
for _, p in ipairs(postprocess.describe("bloom").properties) do
print(p.name, p.type, p.min, p.max)
end

modules/postprocess/list

list(): { string }

List all registered post-process effect names in renderer priority order (lower priority runs first).

for _, n in ipairs(postprocess.list()) do print(n) end

modules/postprocess/remove

remove(name: string): boolean

Queue removal of a post-process effect. Takes effect on the next frame. Removing a name that isn't registered is a silent no-op.

Parameters

  • name string — Effect name to remove.
postprocess.remove("vignette")

modules/postprocess/setEnabled

setEnabled(name: string, enabled: boolean): boolean

Queue an enable/disable toggle on a registered post-process effect. Targeting an unknown name is a silent no-op.

Parameters

  • name string — Effect name.
  • enabled boolean — True to enable, false to disable.
postprocess.setEnabled("bloom", false)

modules/postprocess/setProperty

setProperty(name: string, prop: string, value: (number | { number })): boolean

Set a named material property on a registered post-process effect. The property must be declared in the effect's properties schema; read in WGSL as material.<prop>. value is a number or a number array (vec/color).

Parameters

  • name string — Effect name.
  • prop string — Declared property name.
  • value (number | { number }) — Number or array of numbers.
postprocess.setProperty("vignette", "intensity", 0.6)

modules/postprocess/setSampler

setSampler(name: string, opts: { [string]: any }): boolean

Configure the per-effect user sampler shared by the effect's declared texture properties. opts.filter = "linear" (default) or "nearest". opts.wrap (alias .address) = "clamp" (default), "repeat", or "mirror" — applied to all axes.

Parameters

  • name string — Effect name.
  • opts { [string]: any } — { filter = "linear"|"nearest", wrap = "clamp"|"repeat"|"mirror" }.
postprocess.setSampler("blur", { filter = "linear", wrap = "clamp" })

modules/postprocess/setTexture

setTexture(name: string, prop: string, path: string): boolean

Bind a texture to one of an effect's declared texture properties. Declare it in properties ({ name = "noise", type = "texture" }) and sample in WGSL as textureSample(noise, noise_sampler, in.uv). path is any TextureCache-resolvable spec (@builtin::textures.foo, color:1,0,0, default:white, a render-target name, ...). A path whose texture has not reached the GPU yet — one this same script created — is held and bound as soon as it does; postprocess.status() reports it under pendingTextures until then.

Parameters

  • name string — Effect name.
  • prop string — Declared texture-property name.
  • path string — Texture path / spec.
postprocess.setTexture("__vsky_composite", "cloud", "cloud_target")

modules/postprocess/status

status(): { PostprocessStatus }

Every registered effect in chain order with the state that decides whether it reaches the frame — enabled flag, priority, layer, the shader's compile error when it has one, the .shader asset it renders when it was registered from one, the texture each declared slot is bound to (textures) and the bindings still waiting for their texture (pendingTextures). This is what the renderer draws with, so a survey of the chain answers "is this one affecting the picture right now?" without capturing a frame and reading pixels.

An effect this script has just registered is listed with pending = true until the renderer publishes it, since a registration is queued for the next frame.

for _, e in ipairs(postprocess.status()) do
print(e.name, e.enabled, e.layer, e.error)
end

typed/builtin//modules/api/engine/postprocess/postprocess/add

postprocess.add(name: string, shader: string | AssetRef, opts: PostprocessOpts?) -> (boolean, string?)

Register a fullscreen post-process effect. This call is what puts a pass into the frame — a post-process .shader asset defines an effect, and renders only once registered here. The chain applies the registration on the caller's own stack and the returned boolean is its answer, so a setProperty or setTexture naming the effect in the same call finds it. shader is a .shader asset reference whose shader.wgsl provides fn fragment(in: PostInput) -> vec4<f32> and whose properties.yaml declares the effect's properties; the engine generates the group(0) framework + schema-driven group(1) from that schema. Editing that shader afterwards recompiles this effect in place, keeping its enabled state, priority, layer and tuned property values. WGSL text is accepted in place of a shader reference, and then opts.properties is the whole schema — this form is deprecated and will be removed, author the effect as a .shader asset instead. Effects run in priority order (lower first, default 100). A registered effect runs over the live viewport's frame AND over every offscreen one — a capture from a world-space station, one orbiting an entity, one of a named camera, a render-to-texture camera. In each of those the effect's engine.view_proj / engine.prev_view_proj / engine.inv_view_proj are the camera THAT render was drawn from and engine.resolution is that target's own size, so a pass reconstructing world space from zero_scene_depth(uv) reconstructs against the station and lens the capture asked for. An offscreen capture is therefore an oracle for an authored grade: it photographs a chosen station without taking the on-screen camera from whoever else is driving the scene, and a capture's postProcessing = false is the one control that takes the chain off the frame it returns. An offscreen render keeps no view history of its own, so engine.prev_view_proj there holds that same matrix rather than the frame before it, and a pass taking camera motion from the two reads none.

Parameters

  • name string — Unique effect name.
  • shader string | AssetRef — A resolved shader asset reference, or author WGSL (fn fragment(in: PostInput) only).
  • opts PostprocessOpts (optional) — { priority = 100, enabled = true, layer = "scene"|"all" (omitted: follows space), space = "light"|"display", properties = {{name, type, default?, min?, max?, textureDefault?}}, passes = {{name, entry, scale?, inputs?}} } — with a shader asset, properties layers over the asset's own schema. layer picks which composited image the effect grades: "scene" runs it before the UI is drawn, over the scene's own light in a float target, so it grades the rendered picture and leaves every widget on screen as authored, and "all" runs it after the UI has landed, over the picture the display transform fitted to the screen, so the interface is graded along with the picture. A call that names no layer takes the one its space belongs to: a "light" effect grades "scene" and a "display" effect grades "all", so a bloom reads the scene's highlights where they still stand above 1.0 and a lookup table reads code values. Naming "all" is what an effect that must grade the interface says — and on a world several authors share, it grades everyone else's screens too. space names what fragment() reads and returns: "light" (the default) for a threshold/blur/grade on the scene, "display" for a lookup table, dither ladder or authored overlay working on code values. With a shader asset, defaults to what that asset's getSpace() declares. passes names the passes the effect draws, each an entry in the shader, a fraction of the view it draws at, and what it reads. With a shader asset, defaults to what that asset's getPasses() declares; a single-pass effect (the common case) declares none. textureDefault is what a type = "texture" property samples while nothing is bound to it: "white" (1,1,1,1 — the default), "black" (0,0,0,1), "normal" (0.5,0.5,1,1) or "transparent" (0,0,0,0). An effect that lays its texture over the scene wants "transparent", so the frame is untouched until setTexture binds a texture that exists.

Returns (boolean, string?) — True when the chain took the effect; false and the compiler's message when it refused it. A shader that does not compile draws nothing at any property value, so it is not registered and postprocess.list() never names it. __display__ and __blit__ are the renderer's own effects, and a registration under either name is refused with a message saying so. A call made from inside queue() or batch(), where the engine has not run the registration by the time the call returns, answers true for the queued request.

postprocess.add("vignette", asset.resolve("@builtin::shaders.post.vignette", "shader"))
postprocess.add("vignette", VIGNETTE_WGSL, { properties = {{ name = "intensity", type = "float", default = 0.5 }} })

typed/builtin//modules/api/engine/postprocess/postprocess/describe

postprocess.describe(name: string) -> PostprocessDescription?

One effect by name, read in full: the chain state postprocess.status() lists for it, and on top of that properties — the schema the effect declared, each entry { name, type, default?, min?, max?, textureDefault? } in the shape add takes — and values, what each of those properties currently holds. A property's value is the one the last setProperty wrote, or the schema's own default where nothing has written one, and it comes back as a number for a scalar and as the array for a wider value, which is what setProperty takes, so a property read here is written straight back.

This is the read-back for a property write. setProperty answers whether the uniform took the value; this answers what the effect holds now, which is the reading a pass that writes its properties every frame needs and the one that tells a mistyped property name from an effect that is not grading. The schema and the values are the engine's own record of the effect — the schema it was registered with and every write the chain accepted into its uniform, the same record /runtime/fx/<name>/meta.json is serialized from. A write the chain refused is not in it, and neither is one made against a property the schema does not declare.

Before an effect is registered its schema lives on the .shader asset it will render: asset.resolve("@builtin::shaders.post.bloom", "shader"):getProperties() names what that shader declares.

Parameters

  • name string — Effect name.

Returns PostprocessDescription? — The effect's state, schema and live values, or nil when nothing is registered under the name. An effect the renderer registers itself declares no properties of its own, and its properties and values are empty.

local e = postprocess.describe("vignette")
print(if e then e.values.intensity else "not registered")
for _, p in ipairs(postprocess.describe("bloom").properties) do
print(p.name, p.type, p.min, p.max)
end

typed/builtin//modules/api/engine/postprocess/postprocess/list

postprocess.list() -> { string }

List all registered post-process effect names in renderer priority order (lower priority runs first).

Returns { string } — Array of effect names.

for _, n in ipairs(postprocess.list()) do print(n) end

typed/builtin//modules/api/engine/postprocess/postprocess/remove

postprocess.remove(name: string) -> boolean

Queue removal of a post-process effect. Takes effect on the next frame. Removing a name that isn't registered is a silent no-op.

Parameters

  • name string — Effect name to remove.

Returns boolean — True when the mutation was queued. False, named in a WARN, for the two effects the renderer registers itself: __display__, the chain's display transform, which stays in every frame and is tuned through its curve and exposure properties, and __blit__, the copy that carries every render into its target, every capture and the viewport included.

postprocess.remove("vignette")

typed/builtin//modules/api/engine/postprocess/postprocess/setEnabled

postprocess.setEnabled(name: string, enabled: boolean) -> boolean

Queue an enable/disable toggle on a registered post-process effect. Targeting an unknown name is a silent no-op.

Parameters

  • name string — Effect name.
  • enabled boolean — True to enable, false to disable.

Returns boolean — True when the mutation was queued. False, named in a WARN, for a __display__ disable (the chain's display transform stays in every frame, and curve = 0 is its linear curve) and for a __blit__ enable (the copy that carries every render into its target joins no chain).

postprocess.setEnabled("bloom", false)

typed/builtin//modules/api/engine/postprocess/postprocess/setProperty

postprocess.setProperty(name: string, prop: string, value: (number | { number })) -> boolean

Set a named material property on a registered post-process effect. The property must be declared in the effect's properties schema; read in WGSL as material.<prop>. value is a number or a number array (vec/color).

Parameters

  • name string — Effect name.
  • prop string — Declared property name.
  • value (number | { number }) — Number or array of numbers.

Returns boolean — True when the effect's uniform took the value; false when it did not — an effect that is not registered, or one that declares no property by that name, is named in a WARN in the engine log. A call made from inside queue() or batch(), where the engine has not run the write by the time the call returns, answers true for the queued request.

postprocess.setProperty("vignette", "intensity", 0.6)

typed/builtin//modules/api/engine/postprocess/postprocess/setSampler

postprocess.setSampler(name: string, opts: { [string]: any }) -> boolean

Configure the per-effect user sampler shared by the effect's declared texture properties. opts.filter = "linear" (default) or "nearest". opts.wrap (alias .address) = "clamp" (default), "repeat", or "mirror" — applied to all axes.

Parameters

  • name string — Effect name.
  • opts { [string]: any } — { filter = "linear"|"nearest", wrap = "clamp"|"repeat"|"mirror" }.

Returns boolean — True — the mutation was queued.

postprocess.setSampler("blur", { filter = "linear", wrap = "clamp" })

typed/builtin//modules/api/engine/postprocess/postprocess/setTexture

postprocess.setTexture(name: string, prop: string, path: string) -> boolean

Bind a texture to one of an effect's declared texture properties. Declare it in properties ({ name = "noise", type = "texture" }) and sample in WGSL as textureSample(noise, noise_sampler, in.uv). path is any TextureCache-resolvable spec (@builtin::textures.foo, color:1,0,0, default:white, a render-target name, ...). A path whose texture has not reached the GPU yet — one this same script created — is held and bound as soon as it does; postprocess.status() reports it under pendingTextures until then.

Parameters

  • name string — Effect name.
  • prop string — Declared texture-property name.
  • path string — Texture path / spec.

Returns boolean — True when the slot took the binding, including one held until its texture reaches the GPU; false when it did not — an effect that is not registered, or one that declares no texture property by that name, is named in a WARN in the engine log. A call made from inside queue() or batch(), where the engine has not run the binding by the time the call returns, answers true for the queued request.

postprocess.setTexture("__vsky_composite", "cloud", "cloud_target")

typed/builtin//modules/api/engine/postprocess/postprocess/status

postprocess.status() -> { PostprocessStatus }

Every registered effect in chain order with the state that decides whether it reaches the frame — enabled flag, priority, layer, the shader's compile error when it has one, the .shader asset it renders when it was registered from one, the texture each declared slot is bound to (textures) and the bindings still waiting for their texture (pendingTextures). This is what the renderer draws with, so a survey of the chain answers "is this one affecting the picture right now?" without capturing a frame and reading pixels.

An effect this script has just registered is listed with pending = true until the renderer publishes it, since a registration is queued for the next frame.

Returns { PostprocessStatus } — Array of per-effect state, in the order the chain runs them.

for _, e in ipairs(postprocess.status()) do
print(e.name, e.enabled, e.layer, e.error)
end
  • api
  • reference