A pack's `sky` function gets vanilla's colour along each view ray and draws where the vanilla sky dome draws, under the sun, moon, stars and clouds. The pipeline's shader is the pack's checked stage (`CheckedStage::source`, new) followed by a copy of terrain.wgsl's sky with every name prefixed `horizon_sky_`, so none can meet a pack's; terrain.wgsl's `fs_sky` now calls `sky_along_ray`, the same code as before. A running pack is one `NativePack`: the frame every stage reads (written once a frame), its sky and post stages and its pass timer. In a frame F3 times, the sky draws in a pass of its own so the overlay can show its time; otherwise it stays in the world's pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6.0 KiB
Shader API
Horizon has no shader packs today: the earlier implementation was removed on 2026-10-24. Two things will replace it, designed separately: a Horizon shader API, described here, and a clean-room loader for the existing pack format (its own spec; it is not part of this API). Both sit on the same renderer hooks, agreed between the two.
Overview: what a shader author will be able to do
Write WGSL against documented stages of Horizon's renderer, with typed inputs, options the player can change, a reload as soon as a file is saved and an overlay that shows what each pass costs and draws. The renderer keeps everything that makes it fast (culling, batching, LOD, the mesher); a pack changes how surfaces and light look, not how the world is fed to the GPU.
The design
- A pack is a folder or a zip with
horizon-shaders.toml(id, name, version, theapiline, the stages it uses, its options) and WGSL files. No preprocessor and no other shading language: WGSL is what wgpu validates and compiles for Vulkan, Metal and DirectX 12. - Stages, not pipelines. A stage is a function with a fixed signature that the renderer calls inside its own pass,
for example a terrain material (
fn material(input: TerrainInput) -> Surface), the lighting of the visible surfaces, the sky, an optional shadow pass, and a chain of full-screen post passes ending in the frame. A pack that leaves a stage out gets vanilla's look for it. - Typed inputs. Each stage receives structs (the frame: time, camera, sun and moon, weather, fog; the surface: position, normal, block and its light) whose WGSL is generated from the Rust types the renderer uploads. The generated reference lists every field with its unit and range, and a test fails when it drifts.
- Options are WGSL
overrideconstants. The manifest declares each option (on/off, a number in a range, a choice) with its default; the game shows them on the pack's settings screen and sets them when it builds the pipeline. No text substitution, and every option combination is validated the same way. - Hot reload. The game watches the pack's folder. A saved file is validated with naga and the stage rebuilt; on an error the last good version keeps drawing and the error, with its file and line, is shown in the overlay.
- Debug overlay. GPU time per stage and pass (timestamp queries), the targets each pass writes (show any of them full-screen), the pack's options and its compile errors.
- Budgets. A pack declares the targets and passes it needs; the renderer refuses more than its limits, and the benchmark (benchmark-report.md) records the pack and its cost, so "fast" stays measured.
- Off by default. Without a pack the renderer is vanilla's look, and no pack code runs.
The pack format (schema 1)
A pack is a folder, or the same files zipped as a .hshader, with horizon-shaders.toml at its root
(crate horizon-shader-api; the sample with every key is
crates/horizon-shader-api/tests/samples/schema-1/):
schema = 1 # the manifest's format; a reader refuses schemas newer than it knows
id = "soft_light" # [a-z0-9_]{1,64}: the settings key and the registry id
name = "Soft Light"
version = "1.0.0"
api = "0.1" # the shader API line the WGSL is written for
description = "..." # optional, like authors and license
shared = ["common.wgsl"] # optional: WGSL put before every stage's file, in order
[sky] # each stage is optional, but a pack has at least one
file = "sky.wgsl"
[material]
file = "material.wgsl"
[[post]] # full-screen passes, in the order they run (8 at most)
file = "post/grade.wgsl"
name = "Colour grade" # optional: the GPU time overlay's label
[[options]] # 64 at most; `id` is the WGSL `override` it sets
id = "saturation"
name = "Saturation"
type = "float" # bool, float (min, max, step), int (min, max), choice (values)
default = 1.1
min = 0.0
max = 2.0
step = 0.05
[budget]
gpu_ms = 1.5 # optional: the GPU time the pack aims to add (0 to 16 ms)
An option's override has the type of its kind: bool, f32, i32, or u32 for a choice (the index of the
value). The pack holds only the manifest, the .wgsl files it names (each used, UTF-8, 256 KiB at most),
README.md, LICENSE (.md, .txt) and icon.png, under plain paths, 8 MiB in all. A .hshader is built
the same way every time (files in path order, fixed dates), so a pack's SHA-256 depends only on its files.
Guide, reference and examples
- shaders-reference.md: every stage's signature, input and output structs, resources and
helpers, the frame's fields with their units, option types and limits, generated from
crates/horizon-shader-api. - The guide: making a pack with
horizon-shader new,checkandpack; the templates and the example pack (crates/horizon-shader-api/packs/golden_hour).
Stability
Not started. The first stages will be experimental (opt-in), then preview once a reference, an example and a test pack exist (stability.md).
Not there yet
The material stage in the game (the sky and post stages run, with hot reload), option values other than the defaults, then the overlay, lighting and shadow stages.
Changelog
- 2026-10-26: the sky stage in the game: the pack's
skygets vanilla's colour along each ray. - 2026-10-26: the post stage in the game (
HORIZON_SHADER_PACK, hot reload with the last good pack kept). - 2026-10-25:
horizon-shader(new,check,pack), three templates, the example pack and the guide. - 2026-10-25: the check (naga validation, signatures, options, budgets, SPIR-V/Metal/HLSL translation).
- 2026-10-25: the stages of API 0.1 (sky, material, post), their inputs and the generated reference.
- 2026-10-25: the pack format, schema 1:
horizon-shaders.tomland the.hshaderpackage. - 2026-10-24: the earlier shader pack code was removed; this API is designed from scratch.