> ## Documentation Index
> Fetch the complete documentation index at: https://docs.eucaengine.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Materials & post-processing

> Set an entity material, tune the post-process stack and fog, and snap quality presets — and learn what's on by default, opt-in, inert, or settable only on a render host.

Euca uses a metallic-roughness PBR material model plus one post-process stack shared by the whole renderer.
Over HTTP you can override a material per entity, tune part of the post stack, snap to a quality preset, and
adjust volumetric fog. The values are **stored as CPU state on the headless host** and **applied by a GPU
render host** — so headless these calls succeed and echo state back, but nothing is drawn. This page runs
every endpoint against a real [local server](/quickstart), shows the honest headless responses, and maps out
the defaults: what's on, what's opt-in, what's inert, and what only a host can show. Output is captured from a
headless host.

## Set a material, tune the stack, snap a preset

**1. Override a material.** `POST /material/set` takes an `entity_id` plus any of `albedo` `[r,g,b,a]`,
`metallic`, `roughness`, `emissive` `[r,g,b]`, and `alpha_mode` (`"opaque"`, `"blend"`, or `"mask:0.5"`). It
creates a `Material` component if the entity has none:

```bash theme={null}
curl -s -X POST http://localhost:3917/material/set -H 'Content-Type: application/json' \
  -d '{"entity_id":1,"albedo":[0.8,0.1,0.1,1.0],"metallic":0.9,"roughness":0.3,"emissive":[1.0,0.4,0.0]}'
# {"ok":true,"message":"Material updated on entity 1"}

curl -s -X POST http://localhost:3917/material/set -H 'Content-Type: application/json' \
  -d '{"entity_id":999,"metallic":0.5}'
# {"ok":false,"message":"Entity 999 not found"}
```

The material is stored on the entity; a render host reads it when drawing. Headless there is no draw, but the
component is real — the only honest failure is a missing entity.

**2. Read the post stack.** `GET /postprocess/settings` returns the live stack. The headless host carries a
`PostProcessSettings` resource, so you read it back straight away (note the f32 round-trip noise):

```bash theme={null}
curl -s http://localhost:3917/postprocess/settings
# {"bloom_enabled":true,"bloom_threshold":0.800000011920929,"contrast":1.0,"exposure":0.0,"fxaa_enabled":true,
#  "ok":true,"saturation":1.0,"ssao_enabled":true,"ssao_intensity":1.0,"ssao_radius":0.5,"temperature":0.0}
```

The on-by-default trio is live — `ssao_enabled`, `fxaa_enabled`, `bloom_enabled` all true, color grading
neutral. (Fog is the exception: its resource is created lazily on first write — see step 5.)

**3. Tune a subset.** `POST /postprocess/settings` creates the resource if absent, changes only the fields
you send, and echoes the full stack back (note the f32 round-trip, e.g. `0.8 → 0.800000011920929`):

```bash theme={null}
curl -s -X POST http://localhost:3917/postprocess/settings -H 'Content-Type: application/json' \
  -d '{"bloom_enabled":true,"bloom_threshold":0.8,"exposure":1.2,"saturation":1.1}'
# {"bloom_enabled":true,"bloom_threshold":0.800000011920929,"contrast":1.0,"exposure":1.2000000476837158,
#  "fxaa_enabled":true,"ok":true,"saturation":1.100000023841858,"ssao_enabled":true,"ssao_intensity":1.0,
#  "ssao_radius":0.5,"temperature":0.0}
```

The echo shows the on-by-default trio (`ssao_enabled`, `fxaa_enabled` true) untouched. **The fields this
route exposes are the whole story for HTTP**: SSAO (enabled/radius/intensity), FXAA, bloom
(enabled/threshold), and color grading (exposure/contrast/saturation/temperature). The higher-tier effects —
TAA, motion blur, depth-of-field, SSR, IBL, PCSS — are **not settable over this route**; they only move via
presets or the host's defaults.

**4. Snap a quality preset.** `POST /postprocess/preset` replaces the whole stack with `low`, `medium`,
`high`, or `ultra`:

```bash theme={null}
curl -s -X POST http://localhost:3917/postprocess/preset -H 'Content-Type: application/json' -d '{"quality":"ultra"}'
# {"bloom_enabled":true,"bloom_threshold":0.800000011920929,"contrast":1.0499999523162842,"exposure":1.0,
#  "fxaa_enabled":true,"ok":true,"saturation":1.0499999523162842,"ssao_enabled":true,
#  "ssao_intensity":1.2000000476837158,"ssao_radius":0.5,"temperature":0.0}

curl -s -X POST http://localhost:3917/postprocess/preset -H 'Content-Type: application/json' -d '{"quality":"low"}'
# {...,"exposure":0.0,...,"ssao_enabled":false,...}     low turns SSAO off and exposure to 0.0

curl -s -X POST http://localhost:3917/postprocess/preset -H 'Content-Type: application/json' -d '{"quality":"cinematic"}'
# {"error":"Unknown quality preset 'cinematic'. Use low, medium, high, or ultra.","ok":false}
```

The request field is `quality` (not `preset`). Names are case-insensitive; an unknown name returns `ok:false`
with the valid list. `low` is the only tier that disables SSAO.

**5. Tune fog.** Unlike the post stack, the fog resource is created lazily — `GET /fog/settings` returns
"resource not found" until the first write, and `POST /fog/settings` creates the resource and echoes the
full settings:

```bash theme={null}
curl -s http://localhost:3917/fog/settings
# {"error":"VolumetricFogSettings resource not found","ok":false}

curl -s -X POST http://localhost:3917/fog/settings -H 'Content-Type: application/json' \
  -d '{"enabled":true,"density":0.05,"color":[0.4,0.5,0.6]}'
# {"absorption":0.10000000149011612,"color":[0.4000000059604645,0.5,0.6000000238418579],
#  "density":0.05000000074505806,"enabled":true,"height_falloff":0.10000000149011612,
#  "light_contribution":1.0,"max_distance":100.0,"ok":true,"scattering":0.5}
```

Fog is **off by default** (`enabled:false` in the defaults) and opt-in via this call — unlike SSAO/FXAA/bloom,
which are on.

## Behavior and gotchas

The things you only find out by running it:

* **`GET /postprocess/settings` returns the live stack immediately** — the headless host boots a
  `PostProcessSettings` resource (SSAO/FXAA/bloom **on**, color grading neutral). **`GET /fog/settings` is the
  lazy one:** it returns `"resource not found"` until the first POST creates it (fog defaults **off**). Neither
  is "disabled."
* **The HTTP post-process route only exposes a subset.** SSAO, FXAA, bloom, and color grading are settable;
  TAA, motion blur, DoF, SSR, IBL, and PCSS are **not** reachable over `/postprocess/settings` — only through
  presets or host defaults. TAA/MB/DoF/SSR/IBL are off by default; PCSS is on by default at every tier.
* **`pcss_enabled` is effectively inert.** It's set true by the defaults and all presets, but no render path
  reads the flag — soft shadows are driven by per-light `light_size`. Toggling it changes nothing.
* **Default indirect light is an SH sky, not IBL.** Even with IBL off, diffuse indirect comes from an analytic
  **spherical-harmonic sky light** (`LightProbe::from_sky`): a procedural sky dome projected onto L2 SH
  irradiance and evaluated per surface normal. This is the production ambient source; an environment-map IBL is
  the opt-in upgrade.
* **IBL is bound to a dummy environment by default.** `ibl_enabled` is false by default and the renderer binds
  black placeholder cube/BRDF views until you supply a real environment map; turning IBL on without one gets
  you the dummy.
* **The `SetColor` rule action is a stub.** In [rules-as-data](/concepts/rules-as-data), `SetColor` logs
  "not yet implemented (needs render access)" and no-ops — it does not change a material. Drive color via
  `/material/set` instead.
* **Preset field is `quality`, not `preset`; setters echo the full stack** (with f32 round-trip noise). Only
  `low` disables SSAO; it also zeroes exposure.

## Endpoints

| Method | Path                    | Description                                                              |
| ------ | ----------------------- | ------------------------------------------------------------------------ |
| `POST` | `/material/set`         | Set `albedo`/`metallic`/`roughness`/`emissive`/`alpha_mode` on an entity |
| `GET`  | `/postprocess/settings` | Current post stack (resource booted by default on the headless host)     |
| `POST` | `/postprocess/settings` | Tune SSAO/FXAA/bloom/color-grading (other effects not exposed here)      |
| `POST` | `/postprocess/preset`   | Replace the stack with `low`/`medium`/`high`/`ultra` (field: `quality`)  |
| `GET`  | `/fog/settings`         | Current fog settings; `"resource not found"` until first write           |
| `POST` | `/fog/settings`         | Enable/tune volumetric fog (off by default)                              |

Camera framing and screenshot capture share the same render-host split — see
[Camera & rendering](/systems/camera-and-rendering). Generated appearance never touches the authoritative
world state ([why that matters](/concepts/determinism)).

## Status

* **Shipped** — metallic-roughness PBR materials, alpha modes, the post stack (SSAO/FXAA/bloom on by default),
  SH sky-light ambient (the default diffuse-indirect source), color grading, `low`/`medium`/`high`/`ultra`
  presets, and volumetric fog (off by default).
* **Opt-in / higher-tier** — TAA, motion blur, depth-of-field, SSR, IBL — implemented, off by default, and
  not settable over the HTTP post-process route (presets or host defaults only).
* **Inert** — `pcss_enabled` is carried in settings but unread; soft shadows come from per-light `light_size`.
* **Caveat** — IBL binds a dummy (black) environment until you supply a real one.
* **Stub** — the `SetColor` rule action logs and no-ops (needs render access); it does not recolor a material.
* **Host-only** — values are stored headless but **only a GPU render host applies them visually**; the
  headless host records the state and draws nothing.

<Card title="Material & post-process endpoints" icon="palette" href="/api-reference/introduction" horizontal>
  Schemas for every material, post-process, and fog endpoint.
</Card>
