Verso DemoKit

A cross-platform real-time demo engine

Version 0.2.2 · updated 2026-08-25

How to read this manual. Start with §1 · General info — the concepts, a minimal example, and how to run & develop a demo. §2 · The demo file is the whole format: top-level fields, the demopart envelope, the shared params, a table of every part type, and the post-processing & beat-sync blocks. Then §3 the data/ layout and §4 seven runnable examples with full source. The deep reference comes last: §5 every part's parameters and §6 the value types — reach for those when you need an exact spec. Format tag is DemoKit04 only.

1. General info

DemoKit is a demo engine built on the verso-3d library. It lets you compose, parametrise and time several effects on a timeline — much like a video editor, except each block on the timeline is an effect that decides what gets rendered. In DemoKit these blocks are called DemoParts.

A whole demo is described by one JSON file (this document is its reference). The same file plays back fullscreen and can also be rendered to an image sequence for video capture. You build it from built-in verso.* DemoParts, or write your own in C++ by subclassing DemoPart.

Three ways in — pick the one that matches what you want to do:

1 · Start your own demo. new-demo.sh downloads the source release package, unpacks it into a new folder and names the default demo after you — it asks for the name as it runs, and whether to keep the bundled example demos (--examples/--no-examples answer that up front). No git anywhere: nothing is cloned and the new project has no .git of its own, so git init it yourself if you want one. The dependencies in lib/ are likewise git-less snapshots — they build, but cannot pull updates; if you develop the verso libs alongside the demo, pass --fetch-libs to git-clone them instead (--ssh for push-capable clones; both need git). From an existing checkout, scripts/new-demo.sh does the same job by copying locally — nothing is downloaded:
curl -fsSL https://dahlia.zone/demokit/new-demo.sh | bash    # standalone, downloads the package
scripts/new-demo.sh --name mydemo --dir ../mydemo --no-examples --fetch-libs   # from a checkout
On Windows without bash, curl or git? The PowerShell twin does the same job with nothing but built-in cmdlets, so there is nothing to install first:
irm https://dahlia.zone/demokit/new-demo.ps1 | iex
What you need to build it. The package already contains the dependencies in lib/, so there is nothing to fetch — but you do need a toolchain: a C++23 compiler, CMake 3.24+, Ninja, and Python 3 with Jinja2 (the OpenGL loader is generated at build time by a Python tool, on every platform). On Windows that means Visual Studio 2022 with Desktop development with C++ plus C++ CMake tools for Windows; on Debian/Ubuntu an apt one-liner; on macOS the Xcode command-line tools and a little Homebrew. README.md lists the exact packages per platform.
Then build and run it. The scripts/ helpers do the whole build — no --fetch needed, that is only for a bare git clone:
cd mydemo
scripts/run.sh                        # native Windows: scripts\run.cmd
scripts/run.sh -- -w -k               # windowed, skip the setup dialog
Everything after -- goes to the demo itself; Running lists the flags worth knowing (-w, -k, -d to pick the display, --debugmode for the editor).
Then start editing. data/demokit.json is your demo — the timeline, every parameter, the post-processing — and your GLSL lives in data/shaders/. Change something, re-run, watch it update. Need an effect that doesn't exist yet? Write it in C++ right here in your own project: subclass DemoPart under demo/<yourname>/, register it with REGISTER_DEMOPART_CLASS("mydemo.myeffect", MyEffect), and reference it by that "type" string from your JSON. It stays yours — nothing has to go back upstream.
2 · Compose a demo without touching C++. You never have to open a compiler: the timeline, every parameter and the post-processing graph live in the JSON, the built-in verso.* parts cover the usual effects, and verso.shadertoy runs GLSL you write yourself. Download a build, then edit the data/*.json and data/shaders/ that ship with it. Bare is the demo on its own; with examples adds every example-*.json and its assets, which you open with --input data/example-hello.json.
PlatformBareWith examples
Windows x64 demokit-starter-0.2.2-win64.zip demokit-starter-examples-0.2.2-win64.zip
Linux x64 demokit-starter-0.2.2-linux-x64.AppImage demokit-starter-examples-0.2.2-linux-x64.AppImage
Linux x64 portable folder demokit-starter-0.2.2-linux-x64-portable.tar.gz demokit-starter-examples-0.2.2-linux-x64-portable.tar.gz
macOS Universal demokit-starter-0.2.2-mac-universal.zip demokit-starter-examples-0.2.2-mac-universal.zip
On Windows and macOS the download unpacks to a folder with data/ next to the binary, and the Linux portable folder tarball has the same layout. The Linux AppImage carries data/ inside the single file — get an editable copy out with:
./demokit-starter-0.2.2-linux-x64.AppImage --appimage-extract
Editing needs no unpacking of the binary itself: pass an absolute --input path and its directory becomes the data root, so any packaged binary can run your working copy directly:
./demokit-starter-0.2.2-linux-x64.AppImage -i "$PWD/mydata/demokit.json" -w
Packing the finished demo. On Windows, macOS and the Linux portable folder the folder you edited is the release — make sure data/demokit.json is your demo (it's what the binary plays by default), then zip/tar the folder back up. The Linux AppImage is repacked with appimage-repack.sh, which ships at the root of the portable folder and inside the AppImage itself — --appimage-extract (above) drops it at squashfs-root/usr/bin/ — and is also in the repo's scripts/. It needs bash, and downloads appimagetool once on first use:
./appimage-repack.sh --extract-data mydata demokit-starter-0.2.2-linux-x64.AppImage  # start here
# ...edit mydata/, test with -i as above, then seal it:
./appimage-repack.sh demokit-starter-0.2.2-linux-x64.AppImage mydata -o MyDemo.AppImage
3 · Improve DemoKit itself. This one is for changes meant to go back to the project rather than live in your own demo — a bug fix, a new built-in verso.* part, a clearer paragraph in this manual. Clone gitlab.com/dahliazone/pc/demokit-starter — then the scripts/ helpers do the build & run for you: run.sh (macOS / Linux / Windows Git Bash) or run.cmd (native Windows). A clone starts with an empty lib/, so the first build needs --fetch; drop it afterwards. You will need a C++23 compiler, CMake 3.24+, Ninja and Python 3 with Jinja2 (the OpenGL loader is generated at build time by a Python tool) — README.md lists the exact packages per platform.
git clone https://gitlab.com/dahliazone/pc/demokit-starter
scripts/run.sh --fetch                              # fetch libs, build, run the default demo
scripts/run.sh -- --input data/example-hello.json   # ...or run a specific example (skip --fetch next time)
scripts\run.cmd --fetch
scripts\run.cmd -- --input data\example-hello.json
Change something in data/example-hello.json (the title text, a clear colour) and re-run to confirm the build is sound; see Helpful scripts for the capture & release helpers. Then the part that matters: branch off main, and when the change is ready open a merge request against gitlab.com/dahliazone/pc/demokit-starter so it ships to everyone in the next release.

Anatomy of a demo file

The top level is an object with a "format" tag and these sections:

Everything except format and demoparts has sensible defaults, so a demo can be tiny — this one clears a dark background, then fades a logo in over the first two seconds:

{
  "format": "DemoKit04",
  "general": { "name": "Hello DemoKit", "duration": 10, "loop": true },
  "demoparts": [
    { "type": "verso.clearscreen",
      "params": { "clear": { "color": [0.05, 0.06, 0.12] } } },

    { "type": "verso.imageviewer", "start": 0, "duration": 10, "priority": 1,
      "params": {
        "texture": { "source": "logo.png" },
        "alpha": { "interpolationType": "Linear",
                   "keyframes": [ { "time": 0, "value": 0 }, { "time": 2, "value": 1 } ] }
      } }
  ]
}

Every frame flows the same way — parts are composited by priority, then the whole image is post-processed:

demo .jsontimeline of parts
active partsby start / duration
compositelow → high priority
postfxshader graph
screenor --record

Key concepts

Two things that trip everyone up: a keyframed value needs at least two keyframes (a single-entry track fails to load), and every part needs a params object even when it takes none — use "params": {}.

Running & developing a demo

What's JSON vs C++. Almost everything is data. From JSON you compose built-in verso.* DemoParts, set their params, animate values with keyframes, bundle parts into verso.groups, share cameras/scenes, and chain post-processing in postfx. Shaders are plain GLSL files (e.g. a shadertoy .330.frag) and need no C++. You only reach for C++ to add a new kind of effect: subclass DemoPart (override create/render/destroy), register it with REGISTER_DEMOPART_CLASS("mydemo.myeffect", MyEffect), and reference it by that "type" string from JSON.

Editor hints. Next to the registration, a part may describe its parameters for the editor with REGISTER_DEMOPART_HINTS(MyEffect, myEffectHints), where myEffectHints() returns a DemoPartTypeHints (type, one-line description, a template part object used by add part) with a ParamHint per key path relative to params: ParamHint("alpha", ParamKind::Keyframes).keyframes(ParamKind::Float).range(0, 1), ParamHint("blend", ParamKind::Enum).enumOf({"Normal", "Additive"}), ParamHint("shader/frag", ParamKind::File).file("shaders"), ParamHint("lights/*/color", ParamKind::Color) (* matches one array index or key). Hints are optional: keys without one get a widget inferred from the JSON, and the raw JSON editor covers everything. The constructor stays the only parser — an edit is validated by constructing the part from the edited node, so the errors you throw there are what the editor shows.

Running. Launch with --input data/<demo>.json (default data/demokit.json). A start-up setup dialog picks display, audio device and resolution; -k/--skipdialog uses the defaults instead. Through the run scripts, everything after -- is passed to the demo: scripts/run.sh -- -w -k (scripts\run.cmd -- -w -k). Useful flags — -h lists them all:

In-app keys. Space play / pause  ·  Backspace rewind to the start of the playback range (or of the demo)  ·  / seek 5 s back / forward (Shift 1 s, Ctrl 20 s; they repeat while held)  ·  F11 show / hide the UI entirely  ·  F12 toggle the editor panels, i.e. flip between the full timeline and just the transport bar  ·  F5 reload the demo JSON in place  ·  Ctrl+E the code editor  ·  Ctrl+O (Cmd+O on macOS, editor visible) open another demo JSON  ·  Ctrl+S save  ·  Ctrl+Z / Ctrl+Shift+Z undo / redo  ·  Esc quit (asks first when anything is unsaved). The playback keys work whenever you are not typing in a text field, so clicking around the panels never takes Space away from the transport. F11 and F12 are the fast way to move between the three views below without restarting. (During --record these are all disabled so a stray key can't corrupt the capture.)

Hot reload. In Development play mode the app watches the demo JSON it was started with and, when the file changes on disk (a save is detected once it has settled for ~0.5 s), rebuilds the demo from it in place: the playback position, the play/pause state, the audio position and the editor layout are all kept, so you see the edit exactly where you were looking — no rewind. F5 (or File > Reload demo) forces a reload; File > Auto reload on save and the debug.hotReload key (default true) switch the watcher. A JSON that fails to parse, or a part whose asset is missing, leaves the running demo untouched and shows the error in the menu bar. Development builds redirect a relative --input into the project's source tree when the file exists there (the location is baked in at configure time), so reads, hot reload and everything the editor saves — the demo JSON and the shaders — work on the real repo files rather than the build-time copy of data/ that the next build overwrites. An absolute --input is honoured as given, and Release builds (or a build whose source tree is gone) read the bundled copy next to the binary. Hot reload is off during --record, and changes to general.music re-open the track while debug.font/midiEnabled need a restart.

Shader hot reload. The same watcher covers every shader file the running demo has loaded — verso.shadertoy and custom parts, postfx nodes and the engine's own built-ins under shaders/verso/. Saving a .vert/.frag recompiles just the programs that use it, in place, without rebuilding the demo: the new program is compiled and linked next to the running one and swapped in only when that succeeds, so a save with a syntax error keeps the last working version and shows the compiler's message (Could not compile FragmentShader: ERROR: 0:34: …) in the menu bar; the next save retries. The same debug.hotReload switch applies.

Live shader editing (the code editor). Ctrl+E — or the edit button next to a shader param, or double-clicking a part with a </> mark in the parts list or on the timeline — opens a code editor over the running picture (fully transparent by default, with outlined glyphs so the code reads on any background; the toolbar has an opacity drag and a split layout that puts the picture left, code right). It edits the very files the demo compiles: after a typing pause (debug.codeEditor.autoCompileMs, default 250 ms — the toolbar's auto toggle and delay change it live, 0 = manual) the shader is recompiled from the buffer and swapped in only when it compiles, so a broken edit keeps the last working program and puts the compiler's message on the offending line; Ctrl+Enter (or F5 in the editor) compiles immediately. Ctrl+S writes the buffer back to the file the programs compile from (the watcher knows it was you); Save As… into the demo's shader folder also rewrites the part's param as one undo step; Revert reloads the file from disk. The Shaders… list opens any live file-backed program — postfx nodes, engine built-ins, custom parts — with zero part code, and verso.truetype's injected GLSL sections compile with #line markers so errors land in the right file. loop part pins the playback range to the edited part while you work. A part opts its params in with ParamHint(…, ParamKind::File).file("shaders").editable(TextSyntax::Glsl); un-hinted string params ending in .frag/.vert/.geom/.glsl are picked up automatically.

Opening another demo. File > Open… (Ctrl+O) shows the system's file dialog and switches the running app to the chosen JSON; it is built the same way as a reload (a bad file keeps the current demo) but restarts the timeline from the beginning, and the watcher then follows the new file and its shaders. Demos outside the app's data/ work as with an absolute --input.

The editor. debug.debugMode — or -b on the command line — chooses how much shows over the running demo, and F11/F12 switch between the same three states while it runs:

So scripts/run.sh -- -w -k -b Editor comes straight up windowed, dialog skipped, with the editor open — and F12 gets you back to the bare transport bar when the panels are in the way.

Editing the demo in the editor

The editor edits the demo document — the very JSON tree the file was parsed into, kept for the whole run — and rebuilds only the part you changed, in place, at the current time. Nothing is lost on the way: keys the engine does not know ("comment", disabled "_keys"), groups, cameras, scenes and postfx all stay exactly as written, and Save writes the document back in the hand-written style (tabs, your key order, short objects on one line).

The editor knows a part type's parameters from hints the part registers (ranges, enum choices, which objects are keyframe tracks, a template for new parts — see writing a part); a part without hints is still fully editable, the widgets are just inferred from the JSON shape and key names.

Helpful scripts

The scripts/ folder wraps the whole toolchain, so you rarely call cmake directly. Each script has a .sh (macOS / Linux / Windows Git Bash) and a native .cmd (Windows) twin with the same flags — run any with --help, and see scripts/README.md for the full detail.

The usual build-and-run — --fetch the first time to pull the libraries, then plain run:

scripts/run.sh --fetch               # first time: fetch lib/ externals, then configure + build + run
scripts/run.sh                       # after that: incremental build + run
scripts/run.sh --rebuild --release   # clean, optimized build
scripts\run.cmd --fetch
scripts\run.cmd
scripts\run.cmd --rebuild --release

The libraries live under lib/ (gitignored, cloned by lib/fetch_externals.sh / .cmd); run --fetch just runs that for you before building. To drive the build by hand instead, fetch once and use CMake out-of-source: cd lib && ./fetch_externals.sh && cd .., then cd build && cmake .. && make -j9 (on Windows the scripts set up the Ninja + Visual Studio toolchain for you — prefer run.cmd).

Glossary

DemoPart
One timed effect on the timeline — a verso.* "type" (clear, text, a shader, a 3D object, particles…).
Timeline
The ordered demoparts list; each part has a start, duration and priority.
Priority
Draw order for parts active at the same moment — higher draws on top.
Keyframe
A {time, value} point; a value with a keyframes array animates over time (times relative to the part's start).
Camera / Scene
Named entries under top-level cameras / scenes (a camera plus lights) that 3D parts render into.
refScale
What a part's relative coordinates are measured against (e.g. "ViewportSize").
postfx node
One fullscreen fragment-shader pass in the post-processing graph applied to the finished frame.
.beats sidecar
A precomputed beat-analysis file (general.beats) that drives beat-reactive uniforms.

From here: §2 is the whole demo file — every top-level field, the demopart format, the shared params & keyframable values, the part types, and the postfx / beat-sync blocks; §4 has full runnable examples, and the reference sections §5 (per-part parameters) and §6 (value types) come last.

2. The demo file

{  
  "format": Format version number to future proofing.
Type: string required
Supported values: "DemoKit04"
  "name": Name of the demo.
Type: string required
Example: "Enlil, king of all the lands, father of all the gods"
  "version": Free-form text version name for the demo.
Type: string required
Example: "2018/05/02 some identification"
  "authors": Free-form list of authors.
Type: string required
Example: "codise / dahlia"
  "paths": { Object containing the paths to data files.
Type: object optional
    "models": Directory path to where the 3D objects will be loaded relative to DemoKit JSON file path.
Type: string optional
Default: "models"
    "shaders": Directory path to where the shader sources will be loaded relative to DemoKit JSON file path.
Type: string optional
Default: "shaders"
    "material": Directory path to where the material shader sources will be loaded relative to DemoKit JSON file path.
Type: string optional
Default: "shaders/material"
    "music": Directory path to where the music will be loaded relative to DemoKit JSON file path.
Type: string optional
Default: "music"
    "textures": Directory path to where the textures will be loaded relative to DemoKit JSON file path.
Type: string optional
Default: "textures"
    "fonts": Directory path to where the fonts will be loaded relative to DemoKit JSON file path.
Type: string optional
Default: "fonts"
    "particles": Directory path to where the particle textures will be loaded relative to DemoKit JSON file path.
Type: string optional
Default: "particles"
    "gui": Directory path to where the gui textures will be loaded relative to DemoKit JSON file path.
Type: string optional
Default: "gui"
    "guiIcons": Directory path to where the gui icons will be loaded relative to DemoKit JSON file path.
Type: string optional
Default: "gui/icons"
    "guiStyles": Directory path to where the gui styles will be loaded relative to DemoKit JSON file path.
Type: string optional
Default: "gui/styles"
  },  
  "music": Music file to play. Relative to "paths.music" directory.
Type: string optional
Example: "soundtrack.mp3"
Supported formats: MP3/MP2/MP1/OGG/WAV/AIFF
  "muted": If the music should be muted.
Type: boolean optional
Supported values: true | falsedefault
  "targetFps": FPS which the demo will try to match. Note that if VSync is on then that limit won't be surpassed.
Type: number optional
Default: 60
  "playmode": Mode which the demo player uses after starting. "Production" shows the demo normally. "Development" open up development mode.
Type: PlayMode optional
Supported values: "Production"default | "Development"
  "debugPlaybackRangeStart": Debug playback range start seconds. For development purposes to automatically skip the the defined debug playback range.
Type: seconds optional
Default: 0
  "debugPlaybackRangeEnd": Debug playback range end seconds. For development purposes to automatically skip the the defined debug playback range.
Type: seconds optional
Default: 0
  "loop": If demo playback should be looped. The editor edits it from the Demo tab's checkbox, and from the transport bar's loop toggle when no playback range is set.
Type: boolean optional
Supported values: true | falsedefault
  "duration": How many seconds the demo will last. Note that currently is the music is shorter then it will loop again from the beginning.
Type: seconds optional
Default: 180
  "demoparts": [ Array of demo parts.
Type: array of demopart objects required
  ]  
}  

Development settings (debug)

The optional top-level "debug" object holds everything that only matters while developing the demo — it is read on load and never required by a release. All keys are optional:

"debug": {  
  "playmode": "Development" enables the editor, hot reload and the file watcher; "Production" plays the demo clean. -p on the command line overrides.
Type: PlayMode optional
Supported values: "Production" | "Development"default
  "debugMode": How much UI comes up over the demo (see the editor); -b overrides.
Type: string optional
Supported values: "Fullscreen" | "TimeControls" | "Editor"default
  "font": The editor UI's font, relative to "paths.guiFonts".
Type: string optional
Default: "RobotoCondensed-Regular.ttf"
  "fontSizeSmaller":
"fontSizeBigger":
The editor UI's two font sizes in pixels (the timeline uses the smaller one).
Type: number optional
Defaults: 18 and 28
  "playbackRange": Play only this [start, end] slice (seconds) in "Development" — the quickest way to keep working on one spot. -r overrides, and the transport bar's range pins set a session-only range on top without touching this key. An active range loops by default (the transport's loop toggle turns that off — playback then pauses at the range end) and every seek is clamped inside it.
Type: array of two seconds optional
Example: [30, 60]
  "hotReload": Watch the demo JSON and every loaded shader file, rebuilding/recompiling in place on save (see hot reload). "Development" only.
Type: boolean optional
Supported values: truedefault | false
  "midiEnabled": Open the MIDI devices for the editor's controller bindings.
Type: boolean optional
Supported values: true | falsedefault
  "codeEditor": { Session defaults for the live code editor; everything here is also changeable from its toolbar while the app runs.
Type: object optional
    "autoCompileMs": Typing pause in milliseconds before an edited shader is recompiled live; 0 = manual only (Ctrl+Enter).
Type: number optional
Default: 250
    "opacity": Background opacity of the editor pane over the picture, 01. The text itself always stays readable (outlined glyphs).
Type: number optional
Default: 0
    "layout": "overlay" floats the code over the picture; "split" puts the picture left, code right.
Type: string optional
Supported values: "overlay"default | "split"
    "font": The editor's monospace font, relative to "paths.guiFonts" (falls back to the UI font when missing).
Type: string optional
Default: "Cousine-Regular.ttf"
    "fontSize": Preferred code size in pixels; the toolbar's A-/A+ step through a baked ladder (11–30 px) around it.
Type: number optional
Default: the fontSizeBigger value
  }  
}  

Demopart format

{  
  "name": Name of the demopart.
Type: string required
Example: "Good is thy riddle, Gestumblindi"
  "type": Type of demopart object. Defines which code the engine will execute for this demopart and whichs params will it parse.
Type: string required
Supported values: "verso.clearscreen" | "verso.imageviewer" | "verso.shadertoy" | "verso.truetype" | "verso.text" | "verso.simplescroller" | "verso.object3d" | "verso.sprites3d" | "verso.gpuparticles" | "verso.particles" | "verso.randombackground" | "verso.heightmap" | "verso.imguitest" | "verso.group" (timeline container — see below)
  "start": Time in seconds when the demopart will start playing.
Type: seconds required
Example: 2.52
  "duration": How many seconds the demopart will last. Note that playback will be capped to global duration even if there are demoparts longer than it.
Type: seconds required
Example: 12.45
  "priority": Render priority. When there are several demoparts active at the same time the demopart with bigger priority will be rendered after the demopart with the lower priority.
Type: integer number required
Example: 3
  "params": { Object containing custom parameters for the chosen demopart type. Even is demopart doesn't take any parameters you must at least define an empty object, e.g. "params": {}
Type: object required
  }  
}  

verso.group (timeline container)

verso.group is not a rendered effect but a container. It holds its own "demoparts" array and exists so a set of related parts can be moved and reprioritised together. A group takes only "start" and "priority" (both optional, default 0); it has no "params", and its "duration" is ignored.

At load time processDemoPartsRecursive() walks the tree and adds the group's start and priority to every descendant: each leaf part's final value is its own plus the sum of its enclosing groups' (start = own + accumulated, likewise priority). Groups may be nested, and the offsets accumulate down the tree. So to retime a whole scene you move the group's start once instead of editing every child; to lift a scene above another you raise the group's priority. Document order still breaks ties among parts with equal effective priority. A group does not render, so it has no visual params.

{
  "name": "scene-2",
  "type": "verso.group",
  "start": 30,
  "priority": 10,
  "demoparts": [
    { "name": "bg",    "type": "verso.clearscreen", "start": 0, "duration": 20, "params": {} },
    { "name": "title", "type": "verso.truetype",    "start": 2, "duration": 8, "priority": 1, "params": {} }
  ]
}

Here bg effectively starts at 30s (0 + 30) and title at 32s with effective priority 11 (1 + 10). Nudge the group's start and the whole scene shifts with it.

Shared params & keyframable values

Most render parts accept a common, all-optional set inside "params", so each per-type section below lists only that part's own params. The shared set:

Keyframable values. A keyframable param takes either a literal or an animated object {easingType, interpolationType, [looping], keyframes:[{time, value}]} — times are relative to the part's start, and a float track needs at least two keyframes. Everything else is a plain literal. The animatable params, per type:

Part types at a glance

Every built-in demopart "type" and what it renders. The full parameters for each are in §5 · Demopart parameters.

TypeWhat it does
verso.clearscreenClear the colour and/or depth buffer.
verso.imageviewerDraw a texture / image — move, scale, rotate, fade it.
verso.randombackgroundShow a random image picked from a folder.
verso.shadertoyFull-screen fragment-shader effect (Shadertoy-style).
verso.truetypeCrisp SDF TrueType text.
verso.textBitmap-font text.
verso.simplescrollerHorizontally scrolling bitmap-font line.
verso.object3dA 3D primitive or a glTF / OBJ model in a scene.
verso.sprites3dCamera-facing textured billboards in 3D.
verso.gpuparticlesGPU particle system (large counts).
verso.particlesCPU particle system (emitter / behaviour / sequencer).
verso.heightmapGrid mesh displaced by a heightmap.
verso.imguitestDear ImGui sandbox for UI testing.
verso.groupTimeline container — offsets its children's start & priority; not rendered.

Post-processing (postfx)

The optional top-level postfx section runs the finished demo frame through a small graph of fullscreen fragment-shader passes before it is presented (or recorded). It is PostProcessGraph in verso-demokit and is a no-op when a demo defines no postfx.

"postfx": {
  "nodes": {
    "trail": { "frag": "beattrail.330.frag", "in": ["demo", "feedback"],
               "uniforms": { "decay": 0.82, "zoom": 0.01 } },
    "vign":  { "frag": "vignette.330.frag",  "in": "trail" }
  },
  "output": "vign"
}

Each entry under nodes is one pass:

output names the node whose result is written back over the demo frame (and kept as the next frame's feedback).

A post-fx node shader

The engine supplies the vertex shader (a fullscreen quad), so a node fragment shader just reads the interpolated UV and samples its inputs. Note it uses ex_Uv (0..1) and samples inputs via iChannelN — unlike a verso.shadertoy part, which uses fragCoord. Declare only the uniforms you use:

#version 330 core
in  vec2 ex_Uv;                 // 0..1 across the screen
out vec4 fragColor;

uniform sampler2D iChannel0;    // input 0 (iChannel1.. for further inputs)
uniform vec2  iResolution;      // render resolution (pixels)
uniform float iTime;            // demo time (seconds)
uniform float alpha;            // the node's "alpha" param
uniform float decay;            // a custom value from "uniforms"
// beat-reactive built-ins (see section 7), all 0.0 when no sidecar is loaded:
uniform float uKick, uMid, uHigh, uBeatPulse, uBeatPhase, uBarPhase;

void main() {
    vec3 col = texture(iChannel0, ex_Uv).rgb;
    col *= 1.0 + 0.3 * uKick;   // punch brightness on the kick
    fragColor = vec4(col, 1.0);
}

Every active node also gets uKickRaw, and the integers uBar / uBeatInBar. See data/beatfx_test.json for a feedback-trail grade whose zoom punches on the beat, with its node shaders under data/shaders/osmium/.

Beat sync (general.beats)

A demo reacts to its music through a precomputed beat analysis — the .beats sidecar — not the live audio clock, so a fixed-timestep --record capture stays perfectly in sync. Point general.beats at the sidecar (resolved under paths.music); DemoKitDemo loads it into the process-wide BeatClock and evaluates it once per frame — before the parts render and before postfx — so everything sees the same beat that frame. With no sidecar loaded every value below is 0, so beat-reactive demos simply run inert (they don't break).

The current beat state (band energies are in [0,1]):

uKicklow-band energy, fast-attack / slow-release smoothed — the usual kick pulse.
uKickRawlow-band energy, unsmoothed.
uMidmid-band energy.
uHighhigh-band energy.
uBeatPulse1.0 at a beat onset, decaying to ~0 before the next beat.
uBeatPhase[0,1) progress through the current beat.
uBarPhase[0,1) progress through the current bar.
uBarint — bar index since the first downbeat.
uBeatInBarint — beat within the bar (0 = downbeat).

Two kinds of consumer read this state:

Generate a sidecar with VersoDemo --analyze <in.wav> <out.beats.json> [bpmHint], list it in the demo's .assets, and see the .beats file format in §3.

3. The data/ folder & assets

Everything the app loads at runtime lives under data/: demo definitions (JSON), shaders, textures, music, fonts and GUI assets. The build copies data/ next to the binary (into .app/Contents/Resources/data on macOS). This section covers that folder's layout and the asset conventions specific to this project.

Directory layout

data/
├── example-*.json                     the runnable example demos (see §4)
├── serpent_in_paradise.json           "Serpent in Paradise"
├── osmium.json                        "AI.EXP 002 Osmium" (beat-synced; general.beats sidecar)
├── shaders/    verso/ (engine) + one folder per production; <name>.330.vert/.frag
├── textures/   fallback.png + one folder per production (backgrounds/, shadertoy/, splash-*)
├── music/      per-demo soundtracks (mp3/wav)
├── gui/        fonts/ (RobotoCondensed), icons/, iconsttf/, styles/*.json (ImGui themes)
├── models/     OBJ/glTF assets
├── particles/  particle-system configs
└── fonts/      bitmap/demo fonts

A demo file's top-level format is "DemoKit04" (the loader rejects any other tag).

Release files: .assets and .txt

Alongside each data/<demo>.json, a release ships two sidecar files (consumed by scripts/release.cmd / release.sh):

Framework assets every release needs

A few files under data/ are not demo content — the engine loads them whatever demo is playing, so every .assets manifest must list them (the shipped manifests group them under a framework-assets comment). They resolve from the running demo's paths, and the release stages only what the manifest names — so a demo that runs fine from data/ in development can still ship broken if its manifest omits one.

Start from a working manifest. The surest way not to forget these is to copy an existing .assetsdata/example-hello.assets (minimal) or data/osmium.assets (a full production) — and swap in your demo's own shaders, music, textures and models.

Shaders

Music & textures

The .beats sidecar (beat analysis) (experimental)

Beat-reactive effects are driven by a precomputed analysis file, not live audio — so a fixed-timestep --record capture stays in sync. The analysis is indexed by track time and looked up per frame.

4. Full examples

Seven complete, runnable demos ship in data/. Each points its paths at per-type examples/ folders (fonts, shaders, textures, models, music), so the whole example set is self-contained and easy to delete. Run any of them windowed with --input data/<file> — click a filename to expand its full source.

data/example-hello.json — the smallest complete demo: a clear-screen background and one fading SDF-text title.

example-hello.json
{
	"format": "DemoKit04",
	"general": {
		"name": "Example — Hello DemoKit",
		"authors": "VersoDemo examples",
		"music": "test.mp3",
		"contentAspectRatio": "16:9",
		"borderColor": [0.02, 0.02, 0.04],
		"targetFps": 60,
		"duration": 8,
		"loop": true
	},
	"paths": {
		"models": "models",
		"shaders": "shaders/examples",
		"shadersVerso": "shaders/verso",
		"textures": "textures/examples",
		"music": "music/examples",
		"fonts": "fonts/examples",
		"gui": "gui",
		"guiFonts": "gui/fonts",
		"guiIcons": "gui/icons",
		"guiStyles": "gui/styles",
		"particles": "particles"
	},
	"setupDialog": {
		"windowTitle": "Hello DemoKit",
		"logo": "textures/examples/logo.png",
		"font": "RobotoCondensed-Regular.ttf",
		"backgroundColor": [0, 0, 0],
		"startButtonText": "PLAY",
		"quitButtonText": "QUIT"
	},
	"debug": {
		"playmode": "Development",
		"debugMode": "Fullscreen",
		"font": "RobotoCondensed-Regular.ttf",
		"playbackRange": [0, 8]
	},
	"demoparts": [
		{
			"name": "background",
			"type": "verso.clearscreen",
			"start": 0,
			"duration": 99999,
			"params": { "clear": { "color": [0.05, 0.06, 0.12] } }
		},
		{
			"name": "title",
			"type": "verso.truetype",
			"start": 0,
			"duration": 99999,
			"priority": 10,
			"params": {
				"font": { "source": "SpaceGrotesk-Bold.ttf", "pixelHeight": 96 },
				"text": "HELLO DEMOKIT",
				"style": "fillOutline",
				"weight": 0.68,
				"outlineWidth": 0.2,
				"fillColor": [0.98, 0.84, 0.32],
				"outlineColor": [0.10, 0.05, 0.22],
				"relativePosition": [0.5, 0.5],
				"relativeCharacterSize": [0.075, 0.075],
				"alpha": {
					"interpolationType": "Linear",
					"keyframes": [ { "time": 0, "value": 0 }, { "time": 1.5, "value": 1 } ]
				}
			}
		}
	]
}

data/example-scene.json — a short timeline showing groups, keyframes and an image: an intro group (a logo verso.imageviewer + an animated title/subtitle) followed by a staggered verso.group of credits that slide and fade in.

example-scene.json
{
	"format": "DemoKit04",
	"general": {
		"name": "Example — Timeline, keyframes & groups",
		"authors": "VersoDemo examples",
		"music": "test.mp3",
		"contentAspectRatio": "16:9",
		"borderColor": [0.02, 0.02, 0.04],
		"targetFps": 60,
		"duration": 19,
		"loop": false
	},
	"paths": {
		"models": "models",
		"shaders": "shaders/examples",
		"shadersVerso": "shaders/verso",
		"textures": "textures/examples",
		"music": "music/examples",
		"fonts": "fonts/examples",
		"gui": "gui",
		"guiFonts": "gui/fonts",
		"guiIcons": "gui/icons",
		"guiStyles": "gui/styles",
		"particles": "particles"
	},
	"setupDialog": {
		"windowTitle": "Timeline example",
		"logo": "textures/examples/logo.png",
		"font": "RobotoCondensed-Regular.ttf",
		"backgroundColor": [0, 0, 0],
		"startButtonText": "PLAY",
		"quitButtonText": "QUIT"
	},
	"debug": {
		"playmode": "Development",
		"debugMode": "Fullscreen",
		"font": "RobotoCondensed-Regular.ttf",
		"playbackRange": [0, 19]
	},
	"demoparts": [
		{
			"name": "background",
			"type": "verso.clearscreen",
			"start": 0,
			"duration": 99999,
			"params": { "clear": { "color": [0.05, 0.06, 0.12] } }
		},

		{
			"name": "intro",
			"type": "verso.group",
			"start": 0,
			"priority": 0,
			"demoparts": [
				{
					"name": "logo",
					"type": "verso.imageviewer",
					"start": 0.5,
					"duration": 8,
					"priority": 5,
					"params": {
						"texture": { "source": "logo.png" },
						"refScale": "ViewportSize",
						"relSize": [0.22, 0.22],
						"position": {
							"easingType": "EaseInOut", "interpolationType": "Linear",
							"keyframes": [ { "time": 0, "value": [0.5, 0.30] }, { "time": 1.4, "value": [0.5, 0.34] } ]
						},
						"alpha": {
							"easingType": "EaseInOut", "interpolationType": "Linear",
							"keyframes": [ { "time": 0, "value": 0 }, { "time": 1, "value": 1 }, { "time": 7, "value": 1 }, { "time": 8, "value": 0 } ]
						}
					}
				},
				{
					"name": "title",
					"type": "verso.truetype",
					"start": 1.2,
					"duration": 7,
					"priority": 10,
					"params": {
						"font": { "source": "SpaceGrotesk-Bold.ttf", "pixelHeight": 96 },
						"text": "DEMOKIT EXAMPLE",
						"style": "fillOutline",
						"weight": 0.68,
						"outlineWidth": 0.2,
						"fillColor": [0.98, 0.84, 0.32],
						"outlineColor": [0.10, 0.05, 0.22],
						"relativePosition": [0.5, 0.56],
						"relativeCharacterSize": [0.06, 0.06],
						"letterSpacing": {
							"easingType": "EaseInOut", "interpolationType": "Linear",
							"keyframes": [ { "time": 0, "value": -0.02 }, { "time": 7, "value": 0.12 } ]
						},
						"alpha": {
							"easingType": "EaseInOut", "interpolationType": "Linear",
							"keyframes": [ { "time": 0, "value": 0 }, { "time": 1, "value": 1 }, { "time": 6, "value": 1 }, { "time": 7, "value": 0 } ]
						}
					}
				},
				{
					"name": "subtitle",
					"type": "verso.truetype",
					"start": 2.0,
					"duration": 6,
					"priority": 10,
					"params": {
						"font": { "source": "RobotoCondensed-Regular.ttf", "pixelHeight": 64 },
						"text": "timeline · keyframes · groups",
						"style": "fill",
						"weight": 0.55,
						"fillColor": [0.55, 0.90, 1.0],
						"relativePosition": [0.5, 0.68],
						"relativeCharacterSize": [0.032, 0.032],
						"alpha": {
							"easingType": "EaseInOut", "interpolationType": "Linear",
							"keyframes": [ { "time": 0, "value": 0 }, { "time": 1, "value": 1 }, { "time": 5, "value": 1 }, { "time": 6, "value": 0 } ]
						}
					}
				}
			]
		},

		{
			"name": "credits",
			"type": "verso.group",
			"start": 10,
			"priority": 0,
			"demoparts": [
				{
					"name": "credit-1",
					"type": "verso.truetype",
					"start": 0.4,
					"duration": 8,
					"priority": 10,
					"params": {
						"font": { "source": "RobotoCondensed-Regular.ttf", "pixelHeight": 64 },
						"text": "code — you",
						"style": "fill",
						"weight": 0.55,
						"fillColor": [0.90, 0.92, 0.98],
						"relativePosition": {
							"easingType": "EaseOut", "interpolationType": "Linear",
							"keyframes": [ { "time": 0, "value": [0.5, 0.46] }, { "time": 1, "value": [0.5, 0.40] } ]
						},
						"relativeCharacterSize": [0.042, 0.042],
						"alpha": { "interpolationType": "Linear", "keyframes": [ { "time": 0, "value": 0 }, { "time": 1, "value": 1 }, { "time": 7, "value": 1 }, { "time": 8, "value": 0 } ] }
					}
				},
				{
					"name": "credit-2",
					"type": "verso.truetype",
					"start": 1.0,
					"duration": 7.4,
					"priority": 10,
					"params": {
						"font": { "source": "RobotoCondensed-Regular.ttf", "pixelHeight": 64 },
						"text": "music — test.mp3",
						"style": "fill",
						"weight": 0.55,
						"fillColor": [0.90, 0.92, 0.98],
						"relativePosition": {
							"easingType": "EaseOut", "interpolationType": "Linear",
							"keyframes": [ { "time": 0, "value": [0.5, 0.56] }, { "time": 1, "value": [0.5, 0.50] } ]
						},
						"relativeCharacterSize": [0.042, 0.042],
						"alpha": { "interpolationType": "Linear", "keyframes": [ { "time": 0, "value": 0 }, { "time": 1, "value": 1 }, { "time": 6.4, "value": 1 }, { "time": 7.4, "value": 0 } ] }
					}
				},
				{
					"name": "credit-3",
					"type": "verso.truetype",
					"start": 1.6,
					"duration": 6.8,
					"priority": 10,
					"params": {
						"font": { "source": "RobotoCondensed-Regular.ttf", "pixelHeight": 64 },
						"text": "engine — Verso",
						"style": "fill",
						"weight": 0.55,
						"fillColor": [0.90, 0.92, 0.98],
						"relativePosition": {
							"easingType": "EaseOut", "interpolationType": "Linear",
							"keyframes": [ { "time": 0, "value": [0.5, 0.66] }, { "time": 1, "value": [0.5, 0.60] } ]
						},
						"relativeCharacterSize": [0.042, 0.042],
						"alpha": { "interpolationType": "Linear", "keyframes": [ { "time": 0, "value": 0 }, { "time": 1, "value": 1 }, { "time": 5.8, "value": 1 }, { "time": 6.8, "value": 0 } ] }
					}
				}
			]
		}
	]
}

data/example-shadertoy.jsonthree full-screen verso.shadertoy effects (plasma, tunnel, kaleidoscope) that crossfade, with text on top. The crossfade is just keyframed alpha on each part plus "blend": "Transcluent" (alpha blending) so a fading layer mixes over the one below — the base plasma stays opaque underneath. A verso.shadertoy part references only a fragment shader; the engine supplies the full-screen vertex shader, and each shader here reads just iResolution, iTime and the part's alpha uniform — see the shadertoy uniform list in §3. (This same demo is the app's default data/demokit.json.)

example-shadertoy.json
{
	"format": "DemoKit04",
	"general": {
		"name": "Example — Shadertoy crossfade",
		"authors": "VersoDemo examples",
		"music": "test.mp3",
		"contentAspectRatio": "16:9",
		"borderColor": [0.02, 0.02, 0.04],
		"targetFps": 60,
		"duration": 15,
		"loop": true
	},
	"paths": {
		"models": "models",
		"shaders": "shaders/examples",
		"shadersVerso": "shaders/verso",
		"textures": "textures/examples",
		"music": "music/examples",
		"fonts": "fonts/examples",
		"gui": "gui",
		"guiFonts": "gui/fonts",
		"guiIcons": "gui/icons",
		"guiStyles": "gui/styles",
		"particles": "particles"
	},
	"setupDialog": {
		"windowTitle": "Shadertoy example",
		"logo": "textures/examples/logo.png",
		"font": "RobotoCondensed-Regular.ttf",
		"backgroundColor": [0, 0, 0],
		"startButtonText": "PLAY",
		"quitButtonText": "QUIT"
	},
	"debug": {
		"playmode": "Development",
		"debugMode": "Fullscreen",
		"font": "RobotoCondensed-Regular.ttf",
		"playbackRange": [0, 15]
	},
	"demoparts": [
		{
			"name": "background-clear",
			"type": "verso.clearscreen",
			"start": 0,
			"duration": 99999,
			"params": { "clear": { "color": [0.05, 0.06, 0.12] } }
		},
		{
			"name": "shader-plasma",
			"type": "verso.shadertoy",
			"start": 0,
			"duration": 99999,
			"priority": 1,
			"params": {
				"shader": { "frag": "example_bg.330.frag" },
				"channel": [],
				"alpha": {
					"interpolationType": "Linear",
					"keyframes": [ { "time": 0, "value": 1 }, { "time": 15, "value": 1 } ]
				}
			}
		},
		{
			"name": "shader-tunnel",
			"type": "verso.shadertoy",
			"start": 0,
			"duration": 99999,
			"priority": 2,
			"params": {
				"shader": { "frag": "example_tunnel.330.frag" },
				"channel": [],
				"blend": "Transcluent",
				"alpha": {
					"easingType": "EaseInOut", "interpolationType": "Linear",
					"keyframes": [
						{ "time": 0, "value": 0 }, { "time": 4.5, "value": 0 },
						{ "time": 6, "value": 1 }, { "time": 9, "value": 1 },
						{ "time": 10.5, "value": 0 }
					]
				}
			}
		},
		{
			"name": "shader-kaleido",
			"type": "verso.shadertoy",
			"start": 0,
			"duration": 99999,
			"priority": 3,
			"params": {
				"shader": { "frag": "example_kaleido.330.frag" },
				"channel": [],
				"blend": "Transcluent",
				"alpha": {
					"easingType": "EaseInOut", "interpolationType": "Linear",
					"keyframes": [
						{ "time": 0, "value": 0 }, { "time": 9, "value": 0 },
						{ "time": 10.5, "value": 1 }, { "time": 13.5, "value": 1 },
						{ "time": 15, "value": 0 }
					]
				}
			}
		},
		{
			"name": "title",
			"type": "verso.truetype",
			"start": 0.5,
			"duration": 99999,
			"priority": 10,
			"params": {
				"font": { "source": "SpaceGrotesk-Bold.ttf", "pixelHeight": 96 },
				"text": "SHADERTOY",
				"style": "fillOutline",
				"weight": 0.44,
				"outlineWidth": 0.22,
				"fillColor": [1.0, 1.0, 1.0],
				"outlineColor": [0.04, 0.05, 0.14],
				"relativePosition": [0.5, 0.48],
				"relativeCharacterSize": [0.085, 0.085],
				"alpha": {
					"interpolationType": "Linear",
					"keyframes": [ { "time": 0, "value": 0 }, { "time": 1.2, "value": 1 } ]
				}
			}
		},
		{
			"name": "subtitle",
			"type": "verso.truetype",
			"start": 1.2,
			"duration": 99999,
			"priority": 10,
			"params": {
				"font": { "source": "RobotoCondensed-Regular.ttf", "pixelHeight": 64 },
				"text": "three fragment shaders, crossfading",
				"style": "fill",
				"weight": 0.5,
				"fillColor": [0.85, 0.92, 1.0],
				"relativePosition": [0.5, 0.60],
				"relativeCharacterSize": [0.03, 0.03],
				"alpha": {
					"interpolationType": "Linear",
					"keyframes": [ { "time": 0, "value": 0 }, { "time": 1.2, "value": 1 } ]
				}
			}
		}
	]
}

data/example-3d.json — a 3D scene: a glTF box with a full PBR material (base-colour, normal & metallic-roughness maps) spinning via its animation clip, an orbiting primitive sphere, a moving point light and an orbiting camera — all wired through named cameras/scenes.

example-3d.json
{
	"format": "DemoKit04",
	"general": {
		"name": "Example — 3D scene (glTF + PBR)",
		"authors": "VersoDemo examples",
		"music": "test.mp3",
		"contentAspectRatio": "16:9",
		"borderColor": [0.02, 0.02, 0.04],
		"targetFps": 60,
		"duration": 12,
		"loop": true
	},
	"paths": {
		"models": "models/examples",
		"shaders": "shaders/examples",
		"shadersVerso": "shaders/verso",
		"textures": "textures/examples",
		"music": "music/examples",
		"fonts": "fonts/examples",
		"gui": "gui",
		"guiFonts": "gui/fonts",
		"guiIcons": "gui/icons",
		"guiStyles": "gui/styles",
		"particles": "particles"
	},
	"setupDialog": {
		"windowTitle": "3D scene",
		"logo": "textures/examples/logo.png",
		"font": "RobotoCondensed-Regular.ttf",
		"backgroundColor": [0, 0, 0],
		"startButtonText": "PLAY",
		"quitButtonText": "QUIT"
	},
	"debug": {
		"playmode": "Development",
		"debugMode": "Fullscreen",
		"font": "RobotoCondensed-Regular.ttf",
		"playbackRange": [0, 12]
	},

	"cameras": {
		"hero": {
			"type": "Target",
			"projectionType": "Perspective",
			"position": {
				"easingType": "EaseInOut", "interpolationType": "Linear", "looping": true,
				"keyframes": [
					{ "time": 0, "value": [3.6, 2.0, 4.4] },
					{ "time": 6, "value": [-3.6, 2.2, 4.4] },
					{ "time": 12, "value": [3.6, 2.0, 4.4] }
				]
			},
			"target": [0, 0.1, 0],
			"fovY": 42,
			"nearPlane": 0.1,
			"farPlane": 1000
		},
		"ortho": { "type": "Target", "projectionType": "Orthographic" }
	},

	"scenes": {
		"world": {
			"camera": "hero",
			"lights": [
				{ "type": "directional", "direction": [-0.4, -0.85, -0.5], "color": [1.0, 0.95, 0.88], "ambient": 0.28 },
				{
					"type": "point", "ambient": 0.10, "specular": 0.9,
					"position": {
						"interpolationType": "Linear", "looping": true,
						"keyframes": [
							{ "time": 0, "value": [4, 3, 3] },
							{ "time": 6, "value": [-4, 3, 3] },
							{ "time": 12, "value": [4, 3, 3] }
						]
					},
					"color": {
						"interpolationType": "Linear", "looping": true,
						"keyframes": [
							{ "time": 0, "value": [1.0, 0.6, 0.3, 1.0] },
							{ "time": 4, "value": [0.3, 0.8, 1.0, 1.0] },
							{ "time": 8, "value": [1.0, 0.4, 0.9, 1.0] },
							{ "time": 12, "value": [1.0, 0.6, 0.3, 1.0] }
						]
					}
				}
			]
		}
	},

	"demoparts": [
		{
			"name": "background",
			"type": "verso.clearscreen",
			"start": 0,
			"duration": 99999,
			"params": { "clear": { "color": [0.04, 0.05, 0.09] } }
		},
		{
			"name": "pbr-box (glTF model, Spin clip, all texture maps)",
			"type": "verso.object3d",
			"start": 0,
			"duration": 99999,
			"priority": 5,
			"params": {
				"scene": "world",
				"model": "pbrbox.gltf",
				"clip": "Spin",
				"tonemap": "aces",
				"position": [0.0, 0.0, 0.0],
				"scale": [1.5, 1.5, 1.5]
			}
		},
		{
			"name": "orbiting sphere (primitive, phong)",
			"type": "verso.object3d",
			"start": 0,
			"duration": 99999,
			"priority": 5,
			"params": {
				"scene": "world",
				"shape": "sphere",
				"size": [0.45, 0.45, 0.45],
				"color": [0.85, 0.9, 1.0],
				"specular": 0.9,
				"shininess": 64,
				"position": {
					"interpolationType": "Linear", "looping": true,
					"keyframes": [
						{ "time": 0, "value": [2.2, 0.0, 0.0] },
						{ "time": 3, "value": [0.0, 0.0, 2.2] },
						{ "time": 6, "value": [-2.2, 0.0, 0.0] },
						{ "time": 9, "value": [0.0, 0.0, -2.2] },
						{ "time": 12, "value": [2.2, 0.0, 0.0] }
					]
				}
			}
		},
		{
			"name": "title (ortho, on top)",
			"type": "verso.truetype",
			"start": 0,
			"duration": 99999,
			"priority": 10,
			"params": {
				"font": { "source": "SpaceGrotesk-Bold.ttf", "pixelHeight": 72 },
				"text": "3D SCENE · glTF + PBR",
				"style": "fillOutline",
				"weight": 0.66,
				"outlineWidth": 0.2,
				"fillColor": [0.98, 0.98, 1.0],
				"outlineColor": [0.04, 0.05, 0.12],
				"relativePosition": [0.5, 0.11],
				"relativeCharacterSize": [0.045, 0.045]
			}
		}
	]
}

data/example-3d-postfx.json — the same 3D scene run through a postfx graph (a feedback trail + a chromatic-aberration / vignette grade).

example-3d-postfx.json
{
	"format": "DemoKit04",
	"general": {
		"name": "Example — 3D scene + post-processing",
		"authors": "VersoDemo examples",
		"music": "test.mp3",
		"contentAspectRatio": "16:9",
		"borderColor": [0.02, 0.02, 0.04],
		"targetFps": 60,
		"duration": 12,
		"loop": true
	},
	"paths": {
		"models": "models/examples",
		"shaders": "shaders/examples",
		"shadersVerso": "shaders/verso",
		"textures": "textures/examples",
		"music": "music/examples",
		"fonts": "fonts/examples",
		"gui": "gui",
		"guiFonts": "gui/fonts",
		"guiIcons": "gui/icons",
		"guiStyles": "gui/styles",
		"particles": "particles"
	},
	"setupDialog": {
		"windowTitle": "3D scene + postfx",
		"logo": "textures/examples/logo.png",
		"font": "RobotoCondensed-Regular.ttf",
		"backgroundColor": [0, 0, 0],
		"startButtonText": "PLAY",
		"quitButtonText": "QUIT"
	},
	"debug": {
		"playmode": "Development",
		"debugMode": "Fullscreen",
		"font": "RobotoCondensed-Regular.ttf",
		"playbackRange": [0, 12]
	},

	"cameras": {
		"hero": {
			"type": "Target",
			"projectionType": "Perspective",
			"position": {
				"easingType": "EaseInOut", "interpolationType": "Linear", "looping": true,
				"keyframes": [
					{ "time": 0, "value": [3.6, 2.0, 4.4] },
					{ "time": 6, "value": [-3.6, 2.2, 4.4] },
					{ "time": 12, "value": [3.6, 2.0, 4.4] }
				]
			},
			"target": [0, 0.1, 0],
			"fovY": 42,
			"nearPlane": 0.1,
			"farPlane": 1000
		},
		"ortho": { "type": "Target", "projectionType": "Orthographic" }
	},

	"scenes": {
		"world": {
			"camera": "hero",
			"lights": [
				{ "type": "directional", "direction": [-0.4, -0.85, -0.5], "color": [1.0, 0.95, 0.88], "ambient": 0.28 },
				{
					"type": "point", "ambient": 0.10, "specular": 0.9,
					"position": {
						"interpolationType": "Linear", "looping": true,
						"keyframes": [
							{ "time": 0, "value": [4, 3, 3] },
							{ "time": 6, "value": [-4, 3, 3] },
							{ "time": 12, "value": [4, 3, 3] }
						]
					},
					"color": {
						"interpolationType": "Linear", "looping": true,
						"keyframes": [
							{ "time": 0, "value": [1.0, 0.6, 0.3, 1.0] },
							{ "time": 4, "value": [0.3, 0.8, 1.0, 1.0] },
							{ "time": 8, "value": [1.0, 0.4, 0.9, 1.0] },
							{ "time": 12, "value": [1.0, 0.6, 0.3, 1.0] }
						]
					}
				}
			]
		}
	},

	"demoparts": [
		{
			"name": "background",
			"type": "verso.clearscreen",
			"start": 0,
			"duration": 99999,
			"params": { "clear": { "color": [0.02, 0.02, 0.05] } }
		},
		{
			"name": "pbr-box (glTF model, Spin clip, all texture maps)",
			"type": "verso.object3d",
			"start": 0,
			"duration": 99999,
			"priority": 5,
			"params": {
				"scene": "world",
				"model": "pbrbox.gltf",
				"clip": "Spin",
				"tonemap": "aces",
				"position": [0.0, 0.0, 0.0],
				"scale": [1.5, 1.5, 1.5]
			}
		},
		{
			"name": "orbiting sphere (primitive, phong)",
			"type": "verso.object3d",
			"start": 0,
			"duration": 99999,
			"priority": 5,
			"params": {
				"scene": "world",
				"shape": "sphere",
				"size": [0.45, 0.45, 0.45],
				"color": [0.85, 0.9, 1.0],
				"specular": 0.9,
				"shininess": 64,
				"position": {
					"interpolationType": "Linear", "looping": true,
					"keyframes": [
						{ "time": 0, "value": [2.2, 0.0, 0.0] },
						{ "time": 3, "value": [0.0, 0.0, 2.2] },
						{ "time": 6, "value": [-2.2, 0.0, 0.0] },
						{ "time": 9, "value": [0.0, 0.0, -2.2] },
						{ "time": 12, "value": [2.2, 0.0, 0.0] }
					]
				}
			}
		},
		{
			"name": "title (ortho, on top)",
			"type": "verso.truetype",
			"start": 0,
			"duration": 99999,
			"priority": 10,
			"params": {
				"font": { "source": "SpaceGrotesk-Bold.ttf", "pixelHeight": 72 },
				"text": "3D SCENE + POSTFX",
				"style": "fillOutline",
				"weight": 0.66,
				"outlineWidth": 0.2,
				"fillColor": [0.98, 0.98, 1.0],
				"outlineColor": [0.04, 0.05, 0.12],
				"relativePosition": [0.5, 0.11],
				"relativeCharacterSize": [0.045, 0.045]
			}
		}
	],

	"postfx": {
		"nodes": {
			"trail": {
				"frag": "trail.330.frag",
				"in": ["demo", "feedback"],
				"uniforms": { "decay": 0.86, "zoom": 0.012 }
			},
			"grade": {
				"frag": "grade.330.frag",
				"in": "trail",
				"uniforms": { "aberration": 0.006 }
			}
		},
		"output": "grade"
	}
}

data/example-beatsync.json — a plasma background & title driven by a precomputed .beats sidecar: a postfx node punches on every beat. Its sidecar was made with --analyze from a 120 BPM click.

example-beatsync.json
{
	"format": "DemoKit04",
	"general": {
		"name": "Example — Beat sync",
		"authors": "VersoDemo examples",
		"music": "example-beat.wav",
		"beats": "example-beat.beats.json",
		"contentAspectRatio": "16:9",
		"borderColor": [0.02, 0.02, 0.04],
		"targetFps": 60,
		"duration": 10,
		"loop": true
	},
	"paths": {
		"models": "models/examples",
		"shaders": "shaders/examples",
		"shadersVerso": "shaders/verso",
		"textures": "textures/examples",
		"music": "music/examples",
		"fonts": "fonts/examples",
		"gui": "gui",
		"guiFonts": "gui/fonts",
		"guiIcons": "gui/icons",
		"guiStyles": "gui/styles",
		"particles": "particles"
	},
	"setupDialog": {
		"windowTitle": "Beat sync",
		"logo": "textures/examples/logo.png",
		"font": "RobotoCondensed-Regular.ttf",
		"backgroundColor": [0, 0, 0],
		"startButtonText": "PLAY",
		"quitButtonText": "QUIT"
	},
	"debug": {
		"playmode": "Development",
		"debugMode": "Fullscreen",
		"font": "RobotoCondensed-Regular.ttf",
		"playbackRange": [0, 10]
	},
	"demoparts": [
		{
			"name": "background-clear",
			"type": "verso.clearscreen",
			"start": 0,
			"duration": 99999,
			"params": { "clear": { "color": [0.03, 0.04, 0.08] } }
		},
		{
			"name": "plasma-bg",
			"type": "verso.shadertoy",
			"start": 0,
			"duration": 99999,
			"priority": 1,
			"params": {
				"shader": { "frag": "example_bg.330.frag" },
				"channel": [],
				"alpha": { "interpolationType": "Linear", "keyframes": [ { "time": 0, "value": 0 }, { "time": 1, "value": 1 } ] }
			}
		},
		{
			"name": "title",
			"type": "verso.truetype",
			"start": 0,
			"duration": 99999,
			"priority": 10,
			"params": {
				"font": { "source": "SpaceGrotesk-Bold.ttf", "pixelHeight": 96 },
				"text": "BEAT SYNC",
				"style": "fillOutline",
				"weight": 0.7,
				"outlineWidth": 0.22,
				"fillColor": [1.0, 1.0, 1.0],
				"outlineColor": [0.04, 0.05, 0.14],
				"relativePosition": [0.5, 0.46],
				"relativeCharacterSize": [0.085, 0.085]
			}
		},
		{
			"name": "subtitle",
			"type": "verso.truetype",
			"start": 0,
			"duration": 99999,
			"priority": 10,
			"params": {
				"font": { "source": "RobotoCondensed-Regular.ttf", "pixelHeight": 64 },
				"text": "postfx punches on every beat (general.beats)",
				"style": "fill",
				"weight": 0.55,
				"fillColor": [0.85, 0.92, 1.0],
				"relativePosition": [0.5, 0.58],
				"relativeCharacterSize": [0.028, 0.028]
			}
		}
	],

	"postfx": {
		"nodes": {
			"punch": { "frag": "beatpunch.330.frag", "in": "demo" }
		},
		"output": "punch"
	}
}

data/example-particles.json — a verso.gpuparticles fire fountain (20k particles) through a shared 3D camera/scene.

example-particles.json
{
	"format": "DemoKit04",
	"general": {
		"name": "Example — GPU particles",
		"authors": "VersoDemo examples",
		"music": "test.mp3",
		"contentAspectRatio": "16:9",
		"borderColor": [0.02, 0.02, 0.04],
		"targetFps": 60,
		"duration": 10,
		"loop": true
	},
	"paths": {
		"models": "models/examples",
		"shaders": "shaders/examples",
		"shadersVerso": "shaders/verso",
		"textures": "textures/examples",
		"music": "music/examples",
		"fonts": "fonts/examples",
		"gui": "gui",
		"guiFonts": "gui/fonts",
		"guiIcons": "gui/icons",
		"guiStyles": "gui/styles",
		"particles": "particles"
	},
	"setupDialog": {
		"windowTitle": "GPU particles",
		"logo": "textures/examples/logo.png",
		"font": "RobotoCondensed-Regular.ttf",
		"backgroundColor": [0, 0, 0],
		"startButtonText": "PLAY",
		"quitButtonText": "QUIT"
	},
	"debug": {
		"playmode": "Development",
		"debugMode": "Fullscreen",
		"font": "RobotoCondensed-Regular.ttf",
		"playbackRange": [0, 10]
	},

	"cameras": {
		"hero": {
			"type": "Target",
			"projectionType": "Perspective",
			"position": [0.0, 3.2, 8.5],
			"target": [0.0, 2.2, 0.0],
			"fovY": 45,
			"nearPlane": 0.1,
			"farPlane": 1000
		},
		"ortho": { "type": "Target", "projectionType": "Orthographic" }
	},

	"scenes": {
		"world": { "camera": "hero" }
	},

	"demoparts": [
		{
			"name": "background",
			"type": "verso.clearscreen",
			"start": 0,
			"duration": 99999,
			"params": { "clear": { "color": [0.02, 0.02, 0.05] } }
		},
		{
			"name": "fountain (GPU particles)",
			"type": "verso.gpuparticles",
			"start": 0,
			"duration": 99999,
			"priority": 5,
			"params": {
				"scene": "world",
				"maxParticles": 20000,
				"velocity": [0.0, 4.2, 0.0],
				"velocitySpread": [1.5, 0.8, 1.5],
				"gravity": [0.0, -3.0, 0.0],
				"emitterPositionSpread": [0.15, 0.1, 0.15],
				"lifeMin": 1.0,
				"lifeMax": 2.6,
				"sizeStart": 0.28,
				"sizeEnd": 0.02,
				"colorStart": [1.0, 0.9, 0.45, 1.0],
				"colorEnd": [1.0, 0.18, 0.0, 0.0],
				"emitterPosition": {
					"easingType": "EaseInOut", "interpolationType": "Linear", "looping": true,
					"keyframes": [
						{ "time": 0, "value": [-2.2, 0.0, 0.0] },
						{ "time": 5, "value": [2.2, 0.0, 0.0] },
						{ "time": 10, "value": [-2.2, 0.0, 0.0] }
					]
				}
			}
		},
		{
			"name": "title (ortho, on top)",
			"type": "verso.truetype",
			"start": 0,
			"duration": 99999,
			"priority": 10,
			"params": {
				"font": { "source": "SpaceGrotesk-Bold.ttf", "pixelHeight": 72 },
				"text": "GPU PARTICLES",
				"style": "fillOutline",
				"weight": 0.66,
				"outlineWidth": 0.2,
				"fillColor": [0.98, 0.98, 1.0],
				"outlineColor": [0.04, 0.05, 0.12],
				"relativePosition": [0.5, 0.12],
				"relativeCharacterSize": [0.05, 0.05]
			}
		}
	]
}
example_bg.330.frag — the verso.shadertoy fragment shader
#version 330 core
precision highp float;

// Shadertoy-style uniforms provided by verso.shadertoy (declare only what we use):
uniform vec3  iResolution;   // viewport resolution (pixels)
uniform float iTime;         // playback time (seconds)
uniform float alpha;         // the part's "alpha" param (0..1)

in vec2 fragCoord;
out vec4 fragColor;

void main() {
	// aspect-correct coordinates centred on the screen
	vec2 p = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
	float t = iTime;

	// a few moving sine waves summed into a classic "plasma"
	float v = 0.0;
	v += sin(p.x * 6.0 + t * 1.6);
	v += sin(p.y * 6.0 + t * 1.3);
	v += sin((p.x + p.y) * 5.0 + t * 1.9);
	v += sin(length(p) * 12.0 - t * 2.4);   // radial ripple from the centre
	v *= 0.25;                                // roughly back into [-1, 1]

	// cycle the colour with the plasma value and time
	vec3 col = 0.5 + 0.5 * cos(vec3(0.0, 2.0, 4.0) + v * 3.14159 + t * 0.4);
	col *= 0.55;                              // dim so white text stays readable

	fragColor = vec4(col, alpha);
}
example_tunnel.330.frag — a verso.shadertoy fragment shader
#version 330 core
precision highp float;

// Shadertoy-style uniforms provided by verso.shadertoy (declare only what we use):
uniform vec3  iResolution;   // viewport resolution (pixels)
uniform float iTime;         // playback time (seconds)
uniform float alpha;         // the part's "alpha" param (0..1)

in vec2 fragCoord;
out vec4 fragColor;

// A psychedelic tunnel: depth grows as 1/radius (scrolling toward the viewer),
// with concentric bands and angular spokes cycling colour over time.
void main() {
	vec2 p = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
	float t = iTime;
	float r = length(p);
	float a = atan(p.y, p.x);

	float depth = 0.35 / (r + 0.08) + t * 0.8;      // tunnel depth, scrolls in
	float twist = a * 3.0 + sin(depth * 0.5) * 1.5; // swirl the walls

	float band   = 0.5 + 0.5 * sin(depth * 6.2831); // rings down the tunnel
	float spokes = 0.5 + 0.5 * sin(twist * 4.0);    // radial spokes

	vec3 col = 0.5 + 0.5 * cos(vec3(0.0, 2.1, 4.2) + depth * 0.8 + a);
	col *= mix(0.35, 1.0, band * spokes);
	col *= smoothstep(0.0, 0.35, r);                // dark core at the centre
	col *= 0.6;                                      // dim so white text stays readable

	fragColor = vec4(col, alpha);
}
example_kaleido.330.frag — a verso.shadertoy fragment shader
#version 330 core
precision highp float;

// Shadertoy-style uniforms provided by verso.shadertoy (declare only what we use):
uniform vec3  iResolution;   // viewport resolution (pixels)
uniform float iTime;         // playback time (seconds)
uniform float alpha;         // the part's "alpha" param (0..1)

in vec2 fragCoord;
out vec4 fragColor;

// A kaleidoscope: fold the plane into N mirrored wedges, then draw a slowly
// drifting grid of cells whose colour cycles with radius and time.
void main() {
	vec2 p = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
	float t = iTime;

	float r = length(p);
	float a = atan(p.y, p.x) + t * 0.15;            // slow rotation
	float seg = 6.0;                                 // number of mirrored wedges
	a = mod(a, 6.2831853 / seg);
	a = abs(a - 3.1415927 / seg);                    // mirror within the wedge

	vec2 q = vec2(cos(a), sin(a)) * r * 3.5;
	q += vec2(t * 0.4, -t * 0.25);                   // drift the pattern

	vec2 g = fract(q) - 0.5;                          // grid cell local coords
	float d = min(abs(g.x), abs(g.y));               // distance to nearest cell edge
	float edge = smoothstep(0.02, 0.08, d);

	vec3 col = 0.5 + 0.5 * cos(vec3(0.0, 2.0, 4.0) + r * 5.0 - t * 0.8 + dot(floor(q), vec2(0.7)));
	col *= mix(0.2, 0.9, edge);
	col *= 0.6;                                       // dim so white text stays readable

	fragColor = vec4(col, alpha);
}

5. Demopart parameters

The full parameter reference for each built-in part type. Every part also accepts the shared params (clear/depth/blend/alpha/…); only each part's own params are listed here. New here? Skim the table below first, then jump to the part you need. Value types (Color, Vector3f, …) are in §6 · Types.

TypeWhat it does
verso.clearscreenClear the colour and/or depth buffer.
verso.imageviewerDraw a texture / image — move, scale, rotate, fade it.
verso.randombackgroundShow a random image picked from a folder.
verso.shadertoyFull-screen fragment-shader effect (Shadertoy-style).
verso.truetypeCrisp SDF TrueType text.
verso.textBitmap-font text.
verso.simplescrollerHorizontally scrolling bitmap-font line.
verso.object3dA 3D primitive or a glTF / OBJ model in a scene.
verso.sprites3dCamera-facing textured billboards in 3D.
verso.gpuparticlesGPU particle system (large counts).
verso.particlesCPU particle system (emitter / behaviour / sequencer).
verso.heightmapGrid mesh displaced by a heightmap.
verso.imguitestDear ImGui sandbox for UI testing.
verso.groupTimeline container — offsets its children's start & priority; not rendered.

5.1. verso.clearscreen params

ClearScreen clears the screen color and/or depth buffer.

{ ..., "type": "verso.clearscreen", ..., "params": {
  "clear": { Set how to clear the screen.
Type: ClearParamoptional
Default: Clear screen with "SolidColor" and color [ 0.2235, 0.2235, 0.2235 ].
  },  
}  

Example: Clear screen with default color


    { "name": "Clear", "type": "verso.clearscreen",
      "start": 0, "duration": 3000, "priority": 0, "params": {
    }},

Example: Clear screen with custom color


    { "name": "Clear", "type": "verso.clearscreen",
      "start": 0, "duration": 3000, "priority": 0, "params": {
         "clear": {
             "color": [1, 0, 1]
         }
    }},

Example: Clear screen with custom color but don't clear the Z buffer


    { "name": "Clear", "type": "verso.clearscreen",
      "start": 0, "duration": 3000, "priority": 0, "params": {
         "clear": {
             "color": [1, 0, 1],
             "clearFlag": "ColorBuffer"
         }
    }},

5.2. verso.heightmap params

Renders a grid mesh displaced by a heightmap. Own params:

Plus the shared params.

5.3. verso.imageviewer params

ImageViewer show a given image on the screen and optionally clears the screen.

{ ..., "type": "verso.imageviewer", ..., "params": {
  "source": Image file name to show. Relative to "paths.textures" directory.
Type: string required
Example: "goonies.png"
Supported file extensions: .jpg | .jpeg | .png | .tga | .bmp | .psd | .gif | .hdr | .pic | .pnm
  "clear": { Set how to clear the screen.
Type: ClearParamoptional
Default: Do not clear the screen.
  },  
  "align": { Align the image on the screen. "Undefined" means that image won't be aligned on that axis.
Type: Alignoptional
Default: { "horizontal": "Undefined", "vertical": "Undefined" }
  },  
  "relative": Translate and/or scale the image relative to screen width and height.
For translation: Top-Left = (0.0, 0.0), Bottom-right = (1.0, 1.0)
For scaling: 0.0=zero size 1.0=full screen width/height.
Type: Rectfoptional
Default: { "x": 0, "y": 0, "width": 0, "height": 0 }
  "aspectRatioFix": If true then use image aspect ratio when scaling.
Type: boolean optional
Default: true
  "rotation": Rotate the image in given angle in degrees.
Type: number optional
Default: 0.0
  "blend": Which BlendMode to use for rendering the image.
Type: BlendMode optional
Default: "Transcluent"
  "alpha": Global alpha value for the image which is multiplied with the image pixels' alpha values. Note that the effect requires a suitable BlendMode to be set. Value is between [0.0..1.0]. 0.0 = fully transparent, 1.0 = fully visible.
Type: number optional
Default: 1.0
  "minfilter": Which minification filter to use when scaling the image down from it's original resolution.
Type: MinFilter optional
Default: "Linear"
  "magfilter": Which magnication filter to use when scaling the image up from it's original resolution.
Type: MagFilter optional
Default: "Linear"
}  

Example: Show image with default settings


    { "name": "My image", "type": "verso.imageviewer",
      "start": 0, "duration": 3000, "priority": 0, "params": {
         "source": "quote.png"
    }},

Example: Render acid logo with


    { "name": "aciid1", "type": "verso.imageviewer",
      "start": 0, "duration": 3000, "priority": 0, "params": {
         "source": "acid.png",
         "align": {
             "horizontal": "Right",
             "vertical": "Bottom"
         },
         "relative": {
             "height": 0.1
         },
         "aspectRatioFix": true,
         "rotation": 45,
         "blend": "Transcluent",
         "alpha": 0.25,
         "magfilter": "Nearest"
    }},

Example: Render acid logo with


    { "name": "aciid2", "type": "verso.imageviewer",
      "start": 0, "duration": 3000, "priority": 0, "params": {
         "clear": {
             "color": [0.45, 0.56, 0.60, 1],
             "clearFlag": "SolidColor"
         },
         "source": "acid.png",
         "align": {
             "horizontal": "Left",
             "vertical": "Top"
         },
         "relative": {
             "x": 0.1,
             "y": 0.01,
             "width": 0.4
         },
         "rotation": 210,
         "blend": "Transcluent",
         "alpha": 0.7
    }},

5.4. verso.imguitest params

A Dear ImGui demo / sandbox part for exercising the in-app UI. No own params — just the shared clear / depth set.

5.5. verso.particles params

{  
  "particles": { Defines a particle system.
Type: ParticlesParamrequired
  },  
  "behaviour": { Defines how particles react when time goes on.
Type: BehaviourParamrequired
  },  
  "emitter": { Defines what kind of particles are emitted.
Type: EmitterParamrequired
  },  
  "sequencer": { Defines when and how many particles are emitted.
Type: SequencerParamrequired
  },  
}  

5.6. verso.randombackground params

{  
  "sourcePath": Directory path where a random image is chosen during load. Relative to "paths.textures" directory.
Type: string optional
Default: "paths.textures" directory
Example: "some/directory"
Supported file extensions: .jpg | .jpeg | .png | .tga | .bmp | .psd | .gif | .hdr | .pic | .pnm
  "clear": { Set how to clear the screen.
Type: ClearParamoptional
Default: Clear screen with "SolidColor" and color [ 0.2235, 0.2235, 0.2235 ].
  },  
}  

Example: Simple random background

Clear screen with default color and show random background from backgrounds directory relative to "paths.textures" directory.


    { "name": "My image", "type": "verso.randombackground",
      "start": 0, "duration": 3000, "priority": 0, "params": {
         "sourcePath": "backgrounds"
    }},

Example: Simple random background with custom clear color

Clear screen with color [ 1, 0.5, 0.5 ] and show random background from backgrounds directory relative to "paths.textures" directory.


    { "name": "My image", "type": "verso.randombackground",
      "start": 0, "duration": 3000, "priority": 0, "params": {
         "sourcePath": "backgrounds",
         "clear": {
             "color": [ 1, 0.5, 0.5 ]
         }
    }},

5.7. verso.shadertoy params

{  
  "shader": { Set shadertoy compatible shader to use.
Type: ShaderParamoptional
Default: { "frag": "shadertoy/shadertoy.default.330.frag", "vert": "shadertoy/shadertoy.default.330.vert" }
  },  
  "channel": { Set shadertoy channels. List of file names to textures relative to "paths.textures" directory.
Type: ChannelParamoptional
Default: []
Supported file extensions: .jpg | .jpeg | .png | .tga | .bmp | .psd | .gif | .hdr | .pic | .pnm | animation TODO | video files TODO
  }  
}  

Example: Simple vortex effect

Shows a Shadertoy converted simple vortex effect.


    { "name": "2d vortex effect test", "type": "verso.shadertoy",
      "start": 0, "duration": 3000, "priority": 0, "params": {
        "shader": { "frag": "shadertoy/external/simple_vortex_effect_2d_by_public_int_i.frag" },
        "channel": [ "shadertoy/tex07.jpg", "shadertoy/tex07.jpg" ] }}
    }},

Example: Simple eye blower

Shows a Shadertoy converted simple eye blower effect.


    { "name": "Interference shader test", "type": "verso.shadertoy",
      "start": 0, "duration": 3000, "priority": 0, "params": {
        "shader": { "frag": "shadertoy/external/simple_eye_blower_by_lanza.frag" }
    }},

5.8. verso.truetype params

SDF TrueType text — stays crisp at any scale. Own params:

Plus the shared params (alpha, angleZ, blend, …). Worked example: data/example-hello.json.

5.9. verso.text params

Bitmap-font text (fixed-width glyph sheet). Own params:

Plus the shared params (keyframable alpha, angleZ).

5.10. verso.simplescroller params

A horizontally scrolling bitmap-font text line. Own params:

Plus the shared params (keyframable alpha, angleZ).

5.11. verso.object3d params

Renders a 3D object into a named scene — either a built-in primitive or a loaded model. Own params:

Plus the shared params. Worked examples: data/example-3d.json, data/example-3d-postfx.json.

5.12. verso.sprites3d params

Camera-facing textured sprites (billboards) placed in 3D. Own params:

Plus the shared params.

5.13. verso.gpuparticles params

A GPU particle system — fast, large counts, rendered into a named scene. Own params:

Plus the shared params. Worked example: data/example-particles.json.

6. Types

Note: the composite param types (BehaviourParam, EmitterParam, SequencerParam, ParticlesParam, Material3dParam, PhongMaterialParam, CameraParam/CameraTimeline) are documented below from the *Param sources; the shared render-part params and the consolidated keyframable list live in §2 · Shared params & keyframable values.

6.1. null type

null is a JSON basic type which indicates that value does not exist, is unset or empty.
Type: null
Supported values: null

6.2. object type

object is a JSON basic type which consists of any number of fields inside curly brackets.
Type: object
Examples: {} | { "some field": "some value" } | { "some field": "some value", "another field": 100 }

6.3. array type

array is a JSON basic type which consists of zero or more values inside square brackets.
Type: array
Examples: [] | ["first", "second"] | [1.0, 2, 3.5]

6.4. boolean type

boolean is a JSON basic type which is either true or false.
Type: boolean
Supported values: true | false

6.5. number type

number is a JSON basic type which can be any number.
Type: number
Examples: 0 | -5 | 5.0 | 0.2452

6.6. integer number type

integer isn't a JSON basic type but a custom type in DemoKit for values that cannot contain decimals. If decimals are given to an integer value then they're floored (removed) and warning is printed out.
Type: integer
Examples: 0 | -5 | 5.0 | 0.9222 (will be floored to 0 with a warning)

6.7. string type

string is a JSON basic type which is sequence of zero or more characters inside double quotation marks.
Type: string
Examples: "" | "asdf" | "Some words..."

6.8. Align type

Align defines how something should be aligned. It's an object containing two fields: "horizontal"optional and "vertical"optional.
Type: object
Possible values for "horizontal": "Left" | "Center" | "Right" | "Undefined"
Possible values for "vertical": "Top" | "Center" | "Bottom" | "Undefined"
Examples: {} | {"horizontal": "Center"} | {"horizontal": "Left", "vertical": "Bottom"}

6.9. Color type

Color defines a RGB or RGBA color. It's an array containing 3-4 number values between 0 and 1. For color components 1 means full color and zero no color, for alpha 0 means full transparency and 1 no transparency. TODO: support for "#rrggbbaa" values.
Type: array of 3 or 4 numbers
Examples: [0, 1, 0] | [0.52, 0, 0.62, 0.5]

6.10. Rangef type

Rangef defines a range of numbers between given a and b inside an array.
Type: array of two numbers
Examples: [0, 2] | [-5.2, 25.9]

6.11. Rectf type

Rectf defines a rectangle. It's and object containing four number fields: "x"optional, "y"optional, "width"optional and "height"optional. "x" and "y" defines the top-left point for the rectangle and "width" and "height" the size. TODO: add support for array of four values.
Type: object
Examples: {} | {"x": 1.5, "y": 2, "width": 5, "height": 10}

6.12. Vector2f type

Vector2f defines a two-dimensional vector. It's and object containing two number fields: "x"optional, "y"optional. "x" and "y" defines the point for the vector. This can be used depending on the context as a (x,y) position in space or a direction from origo (0,0) to (x,y). TODO: add support for array of two values.
Type: object
Examples: {} | {"x": 1.5, "y": 2}

6.13. Vector2i type

Vector2i defines a two-dimensional vector. It's and object containing two integer numbers fields: "x"optional, "y"optional. "x" and "y" defines the point for the vector. This can be used depending on the context as a (x,y) position in space or a direction from origo (0,0) to (x,y). TODO: add support for array of two values.
Type: object
Examples: {} | {"x": 1, "y": 2} | {"x": 1.9, "y": -2.2} (will be floored to (1, -2 with a warning))

6.14. Vector3f type

Vector3f defines a three-dimensional vector. It's and object containing three number fields: "x"optional, "y"optional, "z"optional. "x", "y" and "z" defines the point for the vector. This can be used depending on the context as a (x,y,z) position in space or a direction from origo (0,0) to (x,y,z). TODO: add support for array of three values.
Type: object
Examples: {} | {"x": 1.5, "y": 2, "y": -3}

6.15. BlendMode type

BlendMode defines which blend mode to use for rendering.
Type: string
Supported values:
  • "None" = No blending used. This also means that there's not alpha transparency.
  • "Transcluent" = Trancluenct blending i.e. combines colors with multiplication.
  • "Additive" = Additive blending i.e. lightens the image under.
  • "Subtractive" = Subtractive blending i.e. darkens the image under.
  • "ReverseSubtractive" = Subtractive blending that works with alpha transparency i.e. darkens the image under.

6.16. ClearFlag type

ClearFlag defines the way screen should be cleared.
Type: string
Supported values:
  • "None" = Do no clear.
  • "ColorBuffer" = Clears only the color buffer but not the depth buffer.
  • "DepthBuffer" = Clears only the depth buffer but not the color buffer.
  • "SolidColor" = Clears both color and depth buffers with a solid color.
  • "Skybox" = Clears both color and depth buffers with a skybox. TODO: Not implemented.

6.17. MinFilter type

MinFilter defines how texture is scaled down. The texture minifying function is used whenever the pixel being textured maps to an area greater than one texture element.
Type: string
Supported values:
  • "Nearest" = Returns the value of the texture element that is nearest (in Manhattan distance) to the specified texture coordinates.
  • "Linear" = Returns the weighted average of the four texture elements that are closest to the specified texture coordinates. These can include items wrapped or repeated from other parts of a texture, depending on the values of GL_TEXTURE_WRAP_S and GL_TEXTURE_WRAP_T, and on the exact mapping.
  • "NearestMipmapNearest" = Chooses the mipmap that most closely matches the size of the pixel being textured and uses the "Nearest" criterion (the texture element closest to the specified texture coordinates) to produce a texture value.
  • "LinearMipmapNearest" = Chooses the mipmap that most closely matches the size of the pixel being textured and uses the "Linear" criterion (a weighted average of the four texture elements that are closest to the specified texture coordinates) to produce a texture value.
  • "NearestMipmapLinear" = Chooses the two mipmaps that most closely match the size of the pixel being textured and uses the "Nearest" criterion (the texture element closest to the specified texture coordinates ) to produce a texture value from each mipmap. The final texture value is a weighted average of those two values.
  • "LinearMipmapLinear" = Chooses the two mipmaps that most closely match the size of the pixel being textured and uses the "Linear" criterion (a weighted average of the texture elements that are closest to the specified texture coordinates) to produce a texture value from each mipmap. The final texture value is a weighted average of those two values.
  • "Default" = same as "NearestMipmapLinear"
Default: "NearestMipmapLinear"

6.18. MagFilter type

MagFilter defines how texture is scaled up. The texture magnification function is used when the pixel being textured maps to an area less than or equal to one texture element.
Type: string
Supported values:
  • "Nearest" = Returns the value of the texture element that is nearest (in Manhattan distance) to the specified texture coordinates.
  • "Linear" = Returns the weighted average of the texture elements that are closest to the specified texture coordinates. These can include items wrapped or repeated from other parts of a texture, depending on the values of GL_TEXTURE_WRAP_S and GL_TEXTURE_WRAP_T, and on the exact mapping.
  • "Default" = same as "Linear"
Default: "Linear"

6.19. PlayMode type

Playmode is a enum like string with two possible values.
Type: string
Supported values: "Production" | "Development"

RefScale type

RefScale chooses the reference frame a relative size/position is measured against (the refScale field on imageviewer / text / simplescroller / sprites3d).
Type: string
Supported values: "ImageSize" | "ImageSize_KeepAspectRatio_FitRect" | "ImageSize_KeepAspectRatio_FromX" | "ImageSize_KeepAspectRatio_FromY" | "ViewportSize" | "ViewportSize_KeepAspectRatio_FitRect" | "ViewportSize_KeepAspectRatio_FromX" | "ViewportSize_KeepAspectRatio_FromY"

6.20. CameraParam / CameraTimeline type

Defines a camera — inline as a part's "camera", or as a named entry under the top-level "cameras". Fields:
Type: object
  • type / projectionType — e.g. "Target", and "Perspective" | "Orthographic".
  • position, target, desiredUp — placement (keyframable).
  • fovY, nearPlane, farPlane — perspective frustum (fovY keyframable).
  • orthographic{isRelative, zoomLevel, rotation, left, right, top, bottom} (zoomLevel, rotation keyframable).

6.21. BehaviourParam type

Defines how particles react when time goes on.

{  
  "type": Choose the type of behaviour to use.
Type: string required
  "..." Other parameters are dependent on the type.
}  

6.21.1. BehaviourParam.type="SimpleGravity"

SimpleGravity defines a behaviour which applied a simple gravity to particles.

{  
  "type": "SimpleGravity"
  "gravity": Gravity vector applied to every particle each step.
Type: Vector3f required
}  

6.22. ChannelParam type

ChannelParam is a list of channel texture file names in defined order.
Type: array

6.23. ClearParam type

ClearParam defines how the screen should be cleared. It is an object with two fields "clearFlag"optional and "color"optional.
Type: object
  • "clearFlag": ClearFlag (default: "SolidColor")
  • "color": Color (default: [ 0.2235, 0.2235, 0.2235 ])

6.24. EmitterParam type

Defines what kind of particles are emitted.

{  
  "type": Choose the type of emitter to use.
Type: string required
  "..." Other parameters are dependent on the type.
}  

6.24.1. EmitterParam.type="SimpleRandom"

SimpleRandom defines a emitter which emits random particles which ranges between values.

{  
  "type": "SimpleRandom"
  "..." Each field is a Rangef (min/max) the emitter samples per particle: x, y, z, xVelocity, yVelocity, zVelocity, angle, angleVelocity, startSize, endSize, totalLifeTime, red, green, blue, startAlpha, endAlpha.
}  

6.24.2. EmitterParam.type="SphericalRandom"

SphericalRandom defines a emitter which emits particles on a randomly on a sphere.

{  
  "type": "SphericalRandom"
  "..." The same common Rangef fields as SimpleRandom (x, y, z, angle, angleVelocity, startSize, endSize, totalLifeTime, red, green, blue, startAlpha, endAlpha), plus radius (emit on a sphere) instead of the per-axis velocities.
}  

6.25. Material3dParam type

Material for 3D / heightmap surfaces.
Type: object
  • type — material variant.
  • color, diffuseColor, normalColor — colours.
  • diffuseTexture, specularTexture, heightmapTexture — texture maps.
  • maxHeight, waveSpeedY, normalLength — displacement / debug-normals controls.

6.26. ParticlesParam type

The particle-pool config for a verso.particles (CPU) system.

Type: object
  • particleLimit — max live particles.
  • position — system origin.
  • texture, animatedTexture — particle sprite (optionally animated).
  • trailSize, trailSaveInterval — motion-trail length / sampling.
  • blendBlendMode for the particles.

6.27. PhongMaterialParam type

Classic Phong lighting coefficients (used by primitive object3d shapes and lights).
Type: object
  • ambient — ambient term.
  • diffuse — diffuse term.
  • specular — specular strength.
  • shininess — specular exponent.

6.28. SequencerParam type

Defines when and how many particles are emitted.

{  
  "type": Choose the type of sequencer to use.
Type: string required
  "..." Other parameters are dependent on the type.
}  

6.28.1. SequencerParam.type="BurstInterval"

BurstInterval defines a sequencer which supports optional initial burst of particles, optional continous burst of particles every n seconds and optional constant rate of particles per second.

{  
  "type": "BurstInterval"
  "..." sequenceType, startTime, endTime, neverEnding, interval (seconds between bursts), duration, burstSize (particles per burst), particlesPerSecond (constant rate).
}  

6.29. ShaderParam type

{  
  "frag": Fragment shader file name to use. Relative to "paths.shaders" directory.
Type: string optional
Example: "normalvisualizer3d.330.frag"
Support: Shader sources should be GLSL and OpenGL 3.3 compatible.
  "vert": Vertex shader file name to use. Relative to "paths.shaders" directory.
Type: string optional
Example: "normalvisualizer3d.330.vert"
  "geom": Geometry shader file name to use. Relative to "paths.shaders" directory.
Type: string optional
Example: "normalvisualizer3d.330.geom"
}