GL Studio — the verve.gl capabilities showcase

GL Studio (/examples/gl-studio) is the flagship "what you can build with verve.gl" page. It is deliberately built from one declarative ctx.glScene(...) hero — the page's single GL island — followed by a pure-HTML tour that links every other GL feature to its own working demo.

Two rules shape the page:

  1. One GL island per page. A GL island owns a fixed static-data region, so only one may hydrate per page. The hero is that one island; the capability tour below it is plain links, not extra scenes.
  2. Build the hero on the declarative builder path. ctx.glScene(...) + .build() is the proven, reliable way to put a scene on a page. GL Studio does not hand-roll a bespoke GL island.

The hero scene

The hero is a GPU-instanced floor-slab-and-pillars field (cubeshadow.vmesh under studio.venv) lit by three simultaneous shadow casters and sliced open by a live world-space cross-section. The slab is a guaranteed shadow receiver, so the multi-light shadows and the cross-section cutaway are always visible. GL Studio layers its multi-light rig on top of the instanced field. The full builder chain:

const studio_lights = [_]verve.GlLight{
    // Key spot CASTER (type 2) — perspective cone shadow from above-forward.
    .{ .kind = .spot, .pos = .{ 0.6, 6.5, 2.6 }, .dir = .{ -0.06, -1, -0.34 },
       .color = .{ 1, 0.96, 0.9 }, .intensity = 58,
       .inner_deg = 15, .outer_deg = 25, .range = 24, .casts_shadow = true },
    // Fill directional CASTER (type 0) raking from the upper-left.
    .{ .kind = .directional, .dir = .{ -0.55, -0.78, -0.30 },
       .color = .{ 0.92, 0.96, 1.0 }, .intensity = 1.7, .casts_shadow = true },
    // Cool rim directional (no shadow) — separates the model from the backdrop.
    .{ .kind = .directional, .dir = .{ 0.5, -0.15, 0.6 },
       .color = .{ 0.35, 0.5, 0.95 }, .intensity = 0.5, .casts_shadow = false },
};

const scene = ctx.glScene(.{ .src = "/gl/cubeshadow.vmesh", .env = "/gl/studio.venv", .poster = poster })
    .camera(.{ .distance = 20, .pitch = 0.62, .yaw = 0.4 })
    .lights(&studio_lights)
    .areaLight(.{ .pos = .{ 0, 3.4, 0.4 }, .ex = .{ 0.7, 0, 0 }, .ey = .{ 0, 0, 0.7 },
                  .color = .{ 1, 0.98, 0.95 }, .intensity = 5, .casts_shadow = true })
    .clipPlanes(&.{.{ .normal = .{ 1, 0, 0 }, .constant = 0 }})
    .clipShadows(true)
    .onPickExport("CubeShadowMesh", "verve:studio-pick")
    .autoRotate(0.18)
    .scrub(false)
    .build();

What is live in the hero

Every chained feature is verifiable at build time in two ways: zig build passes, and the server-rendered scene emits the out-of-band transport attribute that carries that feature to the client chunk. The builder keeps the hydration Props struct at a frozen 14 fields, so anything that would grow it travels as a data-gl* canvas attribute instead:

FeatureBuilder callSSR attributePayload shape
Multi-light PBR.lights(&studio_lights)data-gllightsone CSV record per light; the casts_shadow flag is field 15 (0/1)
LTC area light.areaLight(...)data-glarealightsone record per rect area light (records ;-separated)
Cross-section.clipPlanes(&.{ ... })data-glclipnx,ny,nz,constant per plane (planes ;-separated)
Clip the shadow pass.clipShadows(true)data-glclipshadows="1"present only when clipping is active
Auto-rotate.autoRotate(0.18)(in data-props)rad/s spin; 0 = off
Click-to-inspect.onPickExport("CubeShadowMesh", "verve:studio-pick")(pick slot in data-props)name → bubbling CustomEvent name

The camera, image-based lighting (from studio.venv), orbit-drag, wheel-zoom, and click-pick are all built in — no extra wiring.

Interactivity

The control bar lives inside the island subtree so z-on-click resolves against the GlScene chunk's exports (it walks target.closest("verve-island")):

  • Freeze / Unfreezeglscene_freeze / glscene_unfreeze pin and release the auto-rotate.
  • Click a part — dispatches a bubbling CustomEvent("verve:studio-pick", { detail: { name } }) from the canvas, so a host page can react to which mesh was clicked.

Why no custom material or post-processing in the hero

Two capabilities are deliberately linked, not live, in the hero — because they do not compose with the multi-shadow + clip centerpiece:

  • Custom materials. A custom material substitutes a single fixed shader handle for every submesh, which bypasses both the clip-discard shader variant and the 2D shadow-receiver bake. So a custom material would silently cancel the clip cutaway and the received shadows. The hero leads with those, so custom materials are showcased on their own page (/examples/gl-material, with live tint buttons) instead.
  • Post-processing (tone-mapping, SSAO, SSR, depth-of-field, OIT) runs on the dedicated image-quality demos, not on the declarative scene path. See the Post-processing group in the tour.

Likewise, fog, a point-light shadow caster, and morph targets each silently disable clipping (there is no combined shader handle for those in v1), so the hero uses none of them — keeping the cross-section genuinely active. The one caster type it avoids is the point light; the spot, directional, and area casters all coexist with clipping.

The capability tour

Below the hero, a grouped set of links covers the whole surface. Every link points at a real, working demo:

  • Geometry & primitives — points & sprites, fat lines, decals, wireframe, distance LOD, morph targets, 16-target morph.
  • Lighting & shadows — shadow map, spot, point, area (LTC), multi-light shadows, cascaded shadow maps, distance fog.
  • Camera & clipping — orthographic, orthographic CSM, clip planes, clip shadows.
  • Materials & textures — custom shader materials, compressed textures, alpha-test cutout, mixed materials, double-sided.
  • Instancing — GPU instancing (culling, multi-submesh, instanced shadows).
  • Post-processing — tone-mapping, SSAO, SSR, depth of field, OIT.
  • Scenes & product — declarative scene, product viewer, skinned + morph, two scenes on one page.
  • Beyond GL — multi-instance graphs, the animation-engine landing page.

Open the WebGL guide for the builder API, or the GL feature matrix for the full support grid.