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:
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.
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.
| Platform | Bare | With 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 |
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
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.jsondata/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:
general— title, author, music, duration, aspect ratio, target FPS…paths— where assets load from (shaders, textures, music, fonts…), relative to the file.setupDialog— the start-up window (title, logo, buttons).debug— dev conveniences: play mode, on-screen editor, playback range.demoparts— the timeline itself (the only strictly required section; the array may be empty, and so may averso.group's, which is what an editor session that deleted everything writes).cameras/scenes(optional) — named 3D cameras/scenes shared by parts.postfx(optional) — a fragment-shader graph applied over the finished frame.
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:
Key concepts
- Timeline & layering — every part has
start,durationandpriority; parts active at the same moment are drawn from lowest to highest priority. - Keyframed values — most number / vector / colour params accept an animated object
(
{easingType, interpolationType, keyframes:[{time, value}]}) instead of a plain literal, likealphaabove. Keyframe times are relative to the part's ownstart. - Grouping & sharing — a
verso.groupbundles parts so you can move/retime them together; 3D parts can reference a sharedcamera/scene; andpostfxpost-processes the final image (e.g. beat-reactive feedback).
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:
-wwindowed ·-ffullscreen (default).-w -ktogether is the usual edit-and-rerun setup: a window, no dialog in the way.-d 1/-d "LG"picks the display by index or name — handy on a multi-monitor desk when the demo should open on the second screen.-Dlists what is connected and exits;-Mdoes the same for display modes.-b/--debugmode Editor|TimeControls|Fullscreen— how much UI comes up over the demo, overriding the demo'sdebug.debugMode. See the editor below.-r/--playbackrange 30plays from 30s to the end;--playbackrange 30:60plays only that slice — the quickest way to jump to and tune one spot (same as the demo'sdebug.playbackRange).-apicks the audio device (-Alists them),-Umutes,-s 1:2renders at half resolution.-e/--record W Hrenders a frame sequence (mutes audio, skips the dialog) for video capture.
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:
- Fullscreen — clean playback, no UI. This is what a released demo ships with, and what F11 toggles to and from.
- TimeControls — adds the transport bar (play / pause / rewind and the playback position) over the demo. F12 drops the full editor down to this.
- Editor — the full editor: the timeline, the parts list, and the property panels that edit the demo — see below.
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).
- Parts list (left panel) — every part in file order, the active ones highlighted; select one for the DemoPart tab. + adds a part after the selected one (inside its group): pick the type — every registered type, built-in or your own — and it starts at the playhead from the type's template; dup, up/down (file order, which is also the draw order among equal priorities) and remove.
- DemoPart tab — name, start, duration, priority (a change that would overlap another part of the
same priority is refused and says so), then Parameters: a widget for every key of the part's
params— checkboxes, number drags, colour pickers, vectors, enum combos, text, nested objects and arrays with + key / x to add and remove keys, and keyframe tracks as a key table (time, value, + key at playhead, to constant); the ~ button next to a number, vector or colour turns it into a two-key track spanning the part. Keys are shown in a defined order — the part type's own (hint) order first, anything unknown a-to-z after it — and + key offers the part's known-but-absent keys as a list (kind and description included, complex keys arrive as a full default object) and files a new key into that order in the saved JSON too. A drag commits when you let go: one undo step, one rebuild. Raw JSON below is the same object as text, in the code editor's widget with JSON highlighting — Apply (Ctrl+Enter) for anything the widgets do not cover. - Timeline (bottom panel) — the parts laid out over time. Click or drag the time bar to scrub; click a part to select it, double-click one to open its shader in the code editor. The selected part gets grab handles: dragging the left edge moves its start, the right edge sets its duration (0.05 s steps; a change that would overlap a part of the same priority is refused) — one undo step per drag. Right-click a part for Delete (asks first); right-click empty space for Add… — the dialog is prefilled with the start, priority and a fitting duration deduced from the click position (10 s, cut short if the next part on that priority starts sooner). When the rows outgrow the panel, Shift+wheel or the scrollbar at the right edge scrolls them vertically; Ctrl+wheel zooms, and an unmodified wheel — either axis, so a sideways trackpad swipe counts — pans in time. Deleting is allowed all the way down to no parts at all: an empty demo plays as a black frame, and + or the timeline's Add… brings the first part back.
- Transport & playback range — the bar under the picture:
go-to-start, back 5 s, play/pause, forward 5 s, go-to-end, then the range pins — set the
start or end of the playback range at the playhead, a loop toggle and a clear button — and
the position bar (the range drawn in it) with mute at the right. The range set here is session only (an
override like
-r, never written into the JSON — the Demo tab's sliders are the document edit); an active range loops by default, with looping off playback and the music pause at the range end, and every seek is clamped inside it. The range is also drawn on the timeline (everything outside dimmed). Without a range the loop toggle drives the demo's ownloopflag instead — a document edit like the Demo tab's checkbox: undoable and written to the JSON on save. - Demo tab — name/version/authors, play mode, duration, loop, the debug playback range, target FPS and the music file.
- File — Save (Ctrl+S) writes the document to the demo's file (atomically, and the hot-reload watcher knows it was you); Save As… (Ctrl+Shift+S) makes another file the demo's file. Edit — Undo/Redo (Ctrl+Z / Ctrl+Shift+Z) step through document snapshots and rebuild the demo from them, keeping the time and play state. An edit the part rejects (a missing shader, a malformed keyframe list) is undone at once and the reason shown in the menu bar; * unsaved marks a dirty document, and a file that changes on disk while you have unsaved edits is reported rather than reloaded over them.
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.
new-demo— start a new demo project. Copies the template (or downloads the source release when run standalone, as in way 1) into a fresh folder and names the default demo after you.--no-examplesdrops the bundled example demos;--fetch-libsgit-clones thelib/dependencies instead of the default git-less snapshot, so they keep pulling verso updates (--sshfor push-capable clones — both need git). The new project has no.git;git initit yourself. The twin here isnew-demo.ps1, not a.cmd.run— build & run. Configures, builds and launches, with logs live in the terminal.--fetchclones/updates thelib/libraries first (needed once),--rebuildwipesbuild/,--release/--debugpick the build type,--testruns the test suite, and everything after--is forwarded to the app (e.g.-- --input data/<demo>.json --skipdialog).release— stage a release. A Release build into its ownbuild-release/, mirrored torelease/<name>-<platform>/with only the demo's.assetsand its requiredINFOFILE.txt. macOS is a universal arm64 + x86_64 build by default;--archivealso packs a zip / tar.release-appimage— portable Linux build. Builds inside an old-glibc container (rootless podman or docker) so the resulting.AppImageruns on essentially any current distro.capture+encode— demo → video.capturerenders the demo to a frame sequence (1080p–8K);encodemuxes those frames with the audio throughffmpeg(VP9 / H.264…). Split so you can re-encode without re-rendering the frames.watch-logs— stream logs. Only needed for launches outside the terminal (macOSrun.sh --useopen, or Windows launches outside the script) — a plainrunalready prints logs.
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 buildscripts\run.cmd --fetch
scripts\run.cmd
scripts\run.cmd --rebuild --releaseThe 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
demopartslist; each part has astart,durationandpriority. - Priority
- Draw order for parts active at the same moment — higher draws on top.
- Keyframe
- A
{time, value}point; a value with akeyframesarray animates over time (times relative to the part'sstart). - 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, 0–1.
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:
clear(ClearParam —color+clearFlag) — optionally clear before drawing.depth({test, write}booleans) — depth-buffer test / write.blend(BlendMode) — how the part composites over what is beneath it.alpha(keyframable float) — part-wide opacity.refScale(RefScale, e.g. "ViewportSize") — what relative coordinates are measured against.angle/angleZ(keyframable) — 2D rotation.camera/scene— names referencing the top-levelcameras/scenes(3D parts).
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:
verso.clearscreen—clear.color.verso.imageviewer—position,relSize,alpha,angle,textureOffset.verso.shadertoy—alpha.verso.text—relativePosition,alpha,angleZ.verso.simplescroller—relativePositionY,relativeCharacterSize,relativeSpacing,alpha,angleZ.verso.truetype—relativePosition,alpha,angleZ,weight,italic,outlineWidth,fillColor,letterSpacing.verso.object3d—position,rotation,scale,clipTime.verso.gpuparticles—emitterPosition,velocity.- CameraParam (
camera/cameras) —position,target,desiredUp,fovY,rotation,zoomLevel. Sprite3dParam(sprites3d[]) —position,relSize,alpha,yaw,pitch,roll,currentAnimationIndex.
Part types at a glance
Every built-in demopart "type" and what it renders. The
full parameters for each are in §5 · Demopart parameters.
| Type | What it does |
|---|---|
verso.clearscreen | Clear the colour and/or depth buffer. |
verso.imageviewer | Draw a texture / image — move, scale, rotate, fade it. |
verso.randombackground | Show a random image picked from a folder. |
verso.shadertoy | Full-screen fragment-shader effect (Shadertoy-style). |
verso.truetype | Crisp SDF TrueType text. |
verso.text | Bitmap-font text. |
verso.simplescroller | Horizontally scrolling bitmap-font line. |
verso.object3d | A 3D primitive or a glTF / OBJ model in a scene. |
verso.sprites3d | Camera-facing textured billboards in 3D. |
verso.gpuparticles | GPU particle system (large counts). |
verso.particles | CPU particle system (emitter / behaviour / sequencer). |
verso.heightmap | Grid mesh displaced by a heightmap. |
verso.imguitest | Dear ImGui sandbox for UI testing. |
verso.group | Timeline 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:
frag— a<name>.330.fragunderpaths.shaders. Omit it (or leave it empty) for a passthrough node that just copies its first input.in— the node's input(s): a single name or an array. Order matters — input i is bound to sampleriChanneli. Three names are built in:"demo"(the rendered demo frame),"depth"(the frame's depth buffer as a plainsampler2D— window-space depth in.r,1.0where nothing 3D was drawn; linearise with the camera's near/far for depth of field or fog) and"feedback"(the graph's previous-frame output — what makes trails/feedback possible). Any other name refers to another node's output; nodes are ordered automatically so inputs are ready first.start/end— seconds; the pass only runs inside this window (outside it, the node passes its first input through unchanged). Omit both (or setend ≤ start) to always run.alpha— keyframable float, handed to the shader as thealphauniform.uniforms— extra shader constants:{ "name": value }where value is a number or an array of 2–4 numbers (afloat/vec2…vec4uniform) — fixed, read once from JSON — or a keyframes object ({ "interpolationType", "keyframes": [{ "time", "value" }] }, a keyframablefloatevaluated every frame at the demo time, e.g. an animated depth-of-fieldfocusDistance). Effects can also be driven fromiTime, the keyframablealpha, or the beat uniforms below.
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]):
| uKick | low-band energy, fast-attack / slow-release smoothed — the usual kick pulse. |
|---|---|
| uKickRaw | low-band energy, unsmoothed. |
| uMid | mid-band energy. |
| uHigh | high-band energy. |
| uBeatPulse | 1.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. |
| uBar | int — bar index since the first downbeat. |
| uBeatInBar | int — beat within the bar (0 = downbeat). |
Two kinds of consumer read this state:
- Post-processing shaders receive the values above as uniforms on every active
postfxnode (see Post-processing above). - Beat-reactive demoparts read
BeatClockdirectly. The app-sideosmium.beatsyncpart is the reference: it exposeskickPulse,strobeandcolorShiftparams and, if the demo has nogeneral.beats, bootstraps the clock from its ownparams.beats.
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):
data/<demo>.assets— a plain-text manifest of what the release bundles: onedata/-relative path per line (a directory line is copied recursively);#comments and blank lines are ignored; the demo's own.jsonis copied automatically. A missing entry fails the release, and an asset the demo references but doesn't list simply ships nothing — so when you add an asset to a demo, add it here too. Seedata/example-*.assets.data/<demo>.txt— a per-demo info file, shipped asINFOFILE.txtnext to the build (title, group, credits). Copydata/example.txtas a template.
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.
gui/— everything the setup dialog and demo editor draw, needed even when the demo renders no text of its own: both load a UI font fromgui/fonts/(viapaths.guiFonts; the dialog'ssetupDialog.fontand the editor'sdebug.fontname it, each defaulting toRobotoCondensed-Regular.ttf), alongside the icon sets (gui/icons/,gui/iconsttf/) and colour themes (gui/styles/).shaders/verso/— the engine's built-in shaders, loaded on demand: SDF text (verso.truetype), PBR models (verso.object3d), theverso.shadertoyscaffold, thepostfxpasses and GPU particles. Ship the whole tree if the demo touches any of them.<paths.textures>/fallback.png— the placeholder the engine substitutes for any texture that fails to load. It resolves under the demo's ownpaths.textures, so each demo carries its own copy (e.g.textures/examples/fallback.png,textures/versodemo/fallback.png).fonts/— the demo's own text faces, separate from that UI font: the TrueType/SDF fontsverso.truetyperenders underpaths.fonts. Needed whenever the demo draws its own text (which is nearly always).
.assets — data/example-hello.assets (minimal) or data/osmium.assets
(a full production) — and swap in your demo's own shaders, music, textures and models.Shaders
- Naming:
<name>.<glslversion>.vert/.frag; the.330.infix = GLSL#version 330(the GL context is 4.1 core). shaders/verso/holds engine built-ins; each production has its own folder.- Shadertoy uniforms a
verso.shadertoyfragment shader receives:iResolution,iTime,iTimeDelta,iFrame,iChannelTime[4],iChannelResolution[4],iMouse,iDate,iSampleRate,iChannel0..3(samplers), plus a demoalpha. In/out:in vec2 fragCoord; out vec4 fragColor;.
Music & textures
music/— one soundtrack per production (mp3/wav), played via BASS;general.musicnames it.textures/— afallback.pngat the root plus a folder per production (splash-*.pngintros,backgrounds/,shadertoy/channel 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.
- Generate it once per track:
VersoDemo --analyze <in.wav> <out.beats.json> [bpmHint]. Writes aVersoBeats01JSON next to the music; a fine refine pass locks the exact BPM. - Sidecar fields:
bpm,beatOffset,beatsPerBar,frameRate,frameCount,duration,kick/mid/high(per-frame band-energy envelopes in [0,1]), andbeats(beat times in seconds). - A demo names its sidecar with top-level
general.beats(resolved underpaths.music);DemoKitDemoloads it into the sharedBeatClockand evaluates it once per frame, so beat-reactive parts and the post-processing graph see the same beat. List the sidecar in the demo's.assetsor the release ships no beat data. - The loaded beat drives both beat-reactive demoparts and the
postfxgraph — see Beat sync for the values it exposes and how to consume them.
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.json — three 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.
| Type | What it does |
|---|---|
verso.clearscreen | Clear the colour and/or depth buffer. |
verso.imageviewer | Draw a texture / image — move, scale, rotate, fade it. |
verso.randombackground | Show a random image picked from a folder. |
verso.shadertoy | Full-screen fragment-shader effect (Shadertoy-style). |
verso.truetype | Crisp SDF TrueType text. |
verso.text | Bitmap-font text. |
verso.simplescroller | Horizontally scrolling bitmap-font line. |
verso.object3d | A 3D primitive or a glTF / OBJ model in a scene. |
verso.sprites3d | Camera-facing textured billboards in 3D. |
verso.gpuparticles | GPU particle system (large counts). |
verso.particles | CPU particle system (emitter / behaviour / sequencer). |
verso.heightmap | Grid mesh displaced by a heightmap. |
verso.imguitest | Dear ImGui sandbox for UI testing. |
verso.group | Timeline 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:
unitSize— world size of one grid cell.gridSize— grid resolution (cells per side).offsetPosition— origin offset of the grid.source— heightmap texture (currently a codeTODO, not yet read).
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
- 0.25x transcluent
- scaled 0.1x height
- aspect ratio fixed scaling
- aligned Bottom-Right
- with 45 degree rotation
- "pixeled" magnification.
{ "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
- screen & depth buffer cleared to color [0.45, 0.56, 0.60, 1]
- aligned Top-Left
- scaled 0.4x width and relative screen position of 0.1x width and 0.01x height
- with 210 degree rotation
- 0.7x transcluent
{ "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:
font—{source, pixelHeight, sdfPadding, firstCodepoint, codepointCount, tabSize}(sourceis a.ttfunderpaths.fonts).text— the string to render.style— "fill" | "outline" | "fillOutline".weight,italic,outlineWidth— glyph weight / synthetic slant / outline thickness keyframable.fillColorkeyframable,outlineColor.relativePosition,relativeCharacterSize— placement & size in refScale units (position keyframable).lineSpacing,letterSpacingkeyframable,revealCharactersPerSecond— typewriter reveal.fillShader,outlineShader,visibilityShader— optional custom shaders.
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:
text,relativePositionkeyframable,relativeCharacterSize,relativeSpacing.revealCharactersPerSecond— typewriter reveal.fontFixedWidth—{firstFontCharacterIndex, tabSize}.
Plus the shared params (keyframable alpha, angleZ).
5.10. verso.simplescroller params
A horizontally scrolling bitmap-font text line. Own params:
text,relativePositionYkeyframable,relativeCharacterSizekeyframable,relativeSpacingkeyframable.scrollPosX,scrollSpeedX— start offset and scroll speed.fontFixedWidth—{firstFontCharacterIndex, tabSize}.
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:
scene— name of thescenesentry (camera + lights) to render into.- Primitive:
shape(e.g. "cube" / "sphere"),size,color,specular,shininess,ambient(phong material). - Model:
model(a.gltf/.glb/.objunderpaths.models; glTF uses its PBR metallic-roughness maps, orKHR_materials_pbrSpecularGlossinessones; textures may be external files or embedded in the GLB; skins (JOINTS_0/WEIGHTS_0, up to 4 influences) are applied on the CPU each frame, rigid nodes follow their node transform),clip(animation clip by exact name, "none" = rest pose),clipSync("time" default | "beat" | "bar" — lock the clip to the beats sidecar: one cycle perclipBeatsbeats /clipBarsbars, exact when scrubbing and in--record),clipSpeed,clipOffset(seconds),clipLoop(default true; false clamps),clipTime(keyframable; overrides the clock),tonemap(e.g. "aces"),textureSwaps([{time, source}]). position,rotation,scale— all keyframable.
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:
camera— name of thecamerasentry the sprites face.sprites3d— array of sprite defs (Sprite3dParam); each hastexture,blend,alpha,position,refScale,relSize,yaw/pitch/roll,relOffset,currentAnimationIndex,animations. Keyframable per sprite:position,relSize,alpha,yaw,pitch,roll,currentAnimationIndex.
Plus the shared params.
5.13. verso.gpuparticles params
A GPU particle system — fast, large counts, rendered into a named scene. Own params:
scene— name of thescenesentry (camera) to render into.maxParticles— pool size.emitterPositionkeyframable,emitterPositionSpread— emit point & random spread.velocitykeyframable,velocitySpread,gravity— initial velocity & forces.lifeMin/lifeMax— particle lifetime range (seconds).sizeStart/sizeEnd,colorStart/colorEnd— size & colour over life.texture— optional sprite texture.
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:
|
6.16. ClearFlag type
|
ClearFlag defines the way screen should be cleared. Type: string Supported values:
|
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:
|
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:
|
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
|
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 |
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
|
6.26. ParticlesParam type
The particle-pool config for a verso.particles (CPU) system.
Type: object
|
6.27. PhongMaterialParam type
|
Classic Phong lighting coefficients (used by primitive object3d shapes and lights). Type: object
|
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" |
||||||
| } | |||||||