❒
package · drop-in viewer
What it does
No description provided yet.
Interface
What this asset declares: the schema it conforms to, what it exposes, and the rendered structured payload.
This asset doesn't declare a schema or structured payload.
Sub-parts
Everything contained inside this part. Assets are composite children (clickable cards). Files are leaf payloads. Expand any row to view its source.
18items
# OcclusionProxy
A coarse stand-in for an entity's bulk, so it darkens the ground under and
around itself.
```lua
local id = entity.spawn("boulder").id
entity(id).component.add("OcclusionProxy", {
radius = 2.0,
height = 3.0, -- 0 gives a sphere
})
```
Put it on a character and the character grounds itself; put it on a boulder and
the boulder shades the dirt beside it — without a shadow view, and at any
distance. A subject well past the sun's cascade range still gets this, which is
the difference between standing on a surface and floating over it.
The proxy follows its entity every frame, so a moving subject keeps its
grounding with no popping.
Every field is live: edit one in the inspector and the proxy follows. A
`radius` of 0 removes it without losing the rest of the settings.
The capsule's axis runs along world up. A capsule that tilted with its entity
would need the entity's rotation resolved every frame for a shape whose whole
job is to be approximate.
For code placing proxies directly, reach for
`@builtin::systems.proxyOcclusion.proxyOcclusion` — same shapes, no component.
The full description is in the package readme:
`guides { path = "systems/proxyOcclusion" }`.
▲ 0↑ born
# capsuleOcclusion
The render feature behind `OcclusionProxy` and
`@builtin::systems.proxyOcclusion.proxyOcclusion`. One compute pass runs after
lighting, at order 50.
For each pixel it reconstructs the world position and normal from
`@scene.depth` and `@scene.normal`, then integrates the solid angle each proxy
capsule subtends over the hemisphere above that point.
## The sphere term
The occlusion of a sphere is computed exactly rather than with the usual
`cos(theta) * r^2 / d^2` approximation. That approximation collapses as the
shaded point approaches or enters the sphere — which is precisely where a
grounding contact lives, so the cheap form fails exactly where the feature is
supposed to work.
A capsule is a swept sphere, so its occlusion is the sphere term evaluated at
whichever point of the sweep is nearest the surface.
Proxies occlude independently, so what survives them all is the **product** of
what survives each. Summing would let three weak proxies black out a surface
none of them covers.
## Not double-darkening
There is no shadow buffer to read, so how lit a pixel already is is estimated
from its lit colour against its own albedo (`@scene.color` over
`@scene.material`'s base colour). A surface sitting in shadow-map shadow reads near zero there,
and scaling the occlusion by it is what stops a proxy darkening what is already
dark.
## Camera
The camera comes from `@frame.camera` and the target is screen-sized, so
occlusion is correct in offscreen captures and render-to-texture cameras, not
only the live viewport.
Parameters and shapes arrive through the `proxy_occlusion_params` and
`proxy_occlusion_shapes` buffers, packed by
`@builtin::systems.proxyOcclusion.proxyOcclusion`.
▲ 0↑ born
# proxyOcclusion
Analytic occlusion from coarse proxy shapes — grounding for subjects a shadow
map does not reach.
```lua
local proxyOcclusion = require("@builtin::systems.proxyOcclusion.proxyOcclusion")
proxyOcclusion.set("boulder", { a = { 0, 2, 0 }, radius = 2 }) -- sphere
proxyOcclusion.set("limb", { a = { 0, 1, 0 }, b = { 0, 3, 0 }, radius = 0.4 })
proxyOcclusion.remove("boulder")
proxyOcclusion.configure({ intensity = 0.8, minDistance = 40 })
proxyOcclusion.count() --> how many proxies are registered
proxyOcclusion.active() --> is the pass running
proxyOcclusion.clear() --> drop every proxy, release the pass
```
Proxies are capsules, keyed by a caller-chosen string. Re-submitting the same
key **moves** that proxy rather than adding another, which is what lets a
component push its shape every frame as its entity travels. A sphere is a
capsule whose ends coincide, so one shape covers a boulder and a limb alike.
Cost scales with the number of proxies rather than with scene geometry, so the
range a proxy works at is bounded by nothing — which is the point. A shadow map
only covers what its cascades reach, and a subject past that range receives no
grounding at all.
Occlusion is scaled by how lit a pixel already is, so a surface already sitting
in shadow-map shadow cannot be darkened a second time.
For a proxy that should simply follow an entity, author the `OcclusionProxy`
component instead. This module is for code placing proxies directly: a crowd
system, a destruction event, anything without an entity to hang a component on.
The full description is in the package readme:
`guides { path = "systems/proxyOcclusion" }`.
▲ 0↑ born
# proxy_occlusion
Darkens what a character's own limbs occlude, approximating each part as a capsule instead of tracing the mesh.
## Bindings
| name | kind |
|------|------|
| `scene_color` | `texture2d` |
| `scene_depth` | `texture_depth` |
| `scene_normal` | `texture2d` |
| `scene_material` | `texture2d` (`sample: uint`) |
| `frame_cam` | `buffer, read vec4<f32>` |
| `proxy_occlusion_params` | `buffer, read vec4<f32>` |
| `proxy_occlusion_shapes` | `buffer, read vec4<f32>` |
| `occlusion_out` | `storage2d (rgba16f)` |
▲ 0↑ born
# gbuffer_material
The G-buffer's surface description, and the one encoding of it.
`@scene.shaded` carries what a surface sent — the light the geometry pass shaded
it to, scene-referred and unbounded above 1. `@scene.material` carries what the
surface is: the base colour it reflects, how metallic it is, and how rough. A
screen-space pass that multiplies incoming light by a reflectance — a bounce
multiplier, a Fresnel tint, an ambient floor — reads the second.
The channel is `Rgba8Uint`. The G-buffer's five colour attachments spend the
whole 32-byte `maxColorAttachmentBytesPerSample` a conformant device guarantees,
and this attachment's four bytes come out of alignment padding the set
otherwise burns, so 32 bits is the entire budget:
| Lane | Content |
|---|---|
| `.r` | base colour, RGB565 low byte |
| `.g` | base colour, RGB565 high byte |
| `.b` | metallic |
| `.a` | perceptual roughness |
Base colour lands at 5/6/5 — steps of about 3% per channel. Metallic and
roughness keep a full byte each.
The geometry pass packs through `zero_gbuffer_pack_material`; every reader
unpacks through this module, so the layout is stated once.
Pure math, no bindings.
```wgsl
#include "@builtin::shaderModules.gbuffer_material"
// bindings.yaml: - { name: scene_material, kind: texture2d, sample: uint }
let m = zero_gbuffer_material(scene_material, coord);
let bounce = incoming * m.base_color;
```
One value at a time:
```wgsl
let base = zero_gbuffer_base_color(scene_material, coord);
let metallic = zero_gbuffer_metallic(scene_material, coord);
let roughness = zero_gbuffer_roughness(scene_material, coord);
```
A `.computeShader` reading the channel declares its slot `kind: texture2d` with
`sample: uint`, because an `Rgba8Uint` texture binds as `texture_2d<u32>`. The
engine refuses the pass when the declared sample type and the bound texture's
format disagree, and names both.
## Signatures
```wgsl
struct ZeroGbufferMaterial {
base_color: vec3<f32>,
metallic: f32,
perceptual_roughness: f32,
}
fn zero_gbuffer_unpack_material(texel: vec4<u32>) -> ZeroGbufferMaterial
fn zero_gbuffer_material(t: texture_2d<u32>, coord: vec2<i32>) -> ZeroGbufferMaterial
fn zero_gbuffer_base_color(t: texture_2d<u32>, coord: vec2<i32>) -> vec3<f32>
fn zero_gbuffer_metallic(t: texture_2d<u32>, coord: vec2<i32>) -> f32
fn zero_gbuffer_roughness(t: texture_2d<u32>, coord: vec2<i32>) -> f32
```
`texel` is one `textureLoad` result off `@scene.material` — the four raw
`u32` lanes before this module gives them meaning. `t` is the bound
`@scene.material` slot itself (`texture_2d<u32>`, declared `sample: uint`);
`coord` is the pixel to read, an integer texel coordinate (`vec2<i32>`) — the
same `textureLoad`-shaped argument every accessor and `zero_gbuffer_material`
take. The three single-value accessors are `zero_gbuffer_material` narrowed to
one field, for a caller that wants only one of the three.
The channel is a G-buffer channel, so the deferred G-buffer fragment entry is
what writes it: an opaque draw on the deferred path. A pixel no geometry wrote
reads a black surface at metallic 0 and mid roughness.
▲ 0↑ born
backing path · systems/proxyOcclusion.package
Problems
Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.
0problems
No problems reported. This asset, its contents, and its direct deps are clean as of the latest commit.
⌬ZeroMind agent review · awaiting first pass
Findings
Reviewer findings (handle · model · tag · quoted note) appear here once the per-pass review log lands. Today only the rolled-up
agent_score is exposed.usability—
did it work as advertised
quality—
authoring polish + cohesion
performance—
frame & memory budget held
agent review score
—
/ 100
awaiting first pass
usability × 0.40
+ quality × 0.35
+ performance × 0.25
± compat factor
+ quality × 0.35
+ performance × 0.25
± compat factor
Usability ratings
Did the part work as advertised when consumers tried to drop it in. Separate from upvotes: those are taste; this is "did it function".
—%no reports yet
Sign in to report whether this part worked for you.
Discussion
Scoped to this part · feeds back into the world's score.
0comments
Sign in to post.sign in
No comments yet. Be the first.