Places clones at per-point transforms a GPU compute pass writes into a buffer — the way to drive a cloner from positions computed on the GPU each frame (a physics step, a flocking simulation, a compute-shader layout).
JavaScript API:
ComputeBufferPointsSource
This is a data-driven member of the points-source family (see
ShaderFunctionPointsSource): a points-source defines the set of
positions at which a sk-cloner places copies of its content, so it lives
as a child of a <sk-cloner> and the cloner's other child is the template
cloned at each point. Unlike the procedural sources — which generate their
points from a formula — this one takes its placements from a
sk-compute-node: the cloner binds the producer's GPU buffer slice
directly (zero-copy, nothing read back to the CPU), so the layout can be
regenerated on the GPU every frame at no transfer cost.
Key attributes. compute-node references (by #id) the
sk-compute-node whose output supplies the placements; that node must be
declared with output-kind="point-matrices" so it emits PointData records
(one local matrix per point). compute-property-key selects which output to
read when a node exposes several — it defaults to output.0, so a single-output
producer needs no key. The source stays inert (zero points) until the referenced
producer is resolved and GPU-ready.
Related. For placements written as a per-index function instead of a compute
pass, see sk-custom-wgsl-points-source; both pair naturally with a
sk-compute-node. For object/data-driven placements see
sk-mesh-vertex-points-source, sk-mesh-surface-points-source,
sk-buffer-points-source, and sk-cloner-points-source; for
built-in procedural shapes see sk-grid-points-source and
sk-sphere-points-source. Its host is always a sk-cloner.
Serve these docs to run the live example.
WebGPU needs a secure context — open this page via grunt serve
rather than double-clicking the file. The Markup tab works from disk.
<!-- The placements are a real SURFACE evaluated on the GPU — something a
spline or a built-in shape can't give you. The <sk-compute-node> samples
Boy's surface (an immersion of the projective plane RP^2) on a res x res
grid of its (u, v) parameters, writing one PointData transform per sample
(output-kind="point-matrices"). The cloner drops a small sphere at each.
The surface is STATIC, so the compute node runs ONCE (not `continuous`):
the expensive per-point evaluation happens a single time, and a cheap
transform animation on the cloner spins the finished cloud as rigid
geometry. `res` (grid resolution) and `scale` are uniforms; `output-size`
must equal res*res. Swap the parametric formula and any surface appears. -->
<sk-scene>
<sk-compute-node id="boy" output-kind="point-matrices" output-size="1600"
wgsl="
const PI = 3.14159265;
const SQRT2 = 1.41421356;
// Apery's parametrization of Boy's surface over (u, v) in
// [0, PI] x [0, PI]. Pure sin/cos — no singularities on the domain.
fn boy(u: f32, v: f32) -> vec3<f32> {
let cv = cos(v);
let s2v = sin(2.0 * v);
let denom = 2.0 - SQRT2 * sin(3.0 * u) * s2v;
let x = (SQRT2 * cv * cv * cos(2.0 * u) + cos(u) * s2v) / denom;
let y = (SQRT2 * cv * cv * sin(2.0 * u) - sin(u) * s2v) / denom;
let z = (3.0 * cv * cv) / denom;
// Recentre on the surface's bounding-box centre so it sits and
// rotates around the origin.
return vec3<f32>(x, y, z) - vec3<f32>(0.375, -0.146, 1.473);
}
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) gid: vec3u) {
let i = gid.x;
if (i >= params.output_size) { return; }
// Unravel the flat index into a (u, v) grid cell.
let res = u32(skUniform_res());
let uu = f32(i % res) / f32(res - 1u) * PI;
let vv = f32(i / res) / f32(res - 1u) * PI;
let p = boy(uu, vv) * skUniform_scale();
// Pure translation matrix to p (columns 0-2 identity basis).
var pt: PointData;
pt.localMatrix = mat4x4<f32>(
vec4<f32>(1.0, 0.0, 0.0, 0.0),
vec4<f32>(0.0, 1.0, 0.0, 0.0),
vec4<f32>(0.0, 0.0, 1.0, 0.0),
vec4<f32>(p, 1.0));
output[i] = pt;
}">
<sk-wgsl-uniform name="res" value="40"></sk-wgsl-uniform>
<sk-wgsl-uniform name="scale" value="3.0"></sk-wgsl-uniform>
</sk-compute-node>
<sk-directional-light p="42deg" h="130deg" intensity="1.0"></sk-directional-light>
<sk-ambient-light intensity="0.4"></sk-ambient-light>
<!-- Colour the point cloud by height: a purple -> pink -> gold ramp driven
up the surface by a vertical linear field (+Z ramp rotated to +Y with
p="-90deg", spanning the surface's ~-6..+6 world height). -->
<sk-linear-gradient-field id="up" y="-6" extent="12" p="-90deg"></sk-linear-gradient-field>
<sk-ramp-color-map id="ramp">
<sk-color-stop stop-offset="0" stop-color="#833AB4"></sk-color-stop>
<sk-color-stop stop-offset="0.5" stop-color="#FD1D89"></sk-color-stop>
<sk-color-stop stop-offset="1" stop-color="#FCB045"></sk-color-stop>
</sk-ramp-color-map>
<sk-color-map-source id="src" intensity-field="#up" color-map="#ramp"></sk-color-map-source>
<sk-plain-material id="mat" base-color="white" base-color-darken-factor="0"
base-color-source="#src"></sk-plain-material>
<!-- The surface is computed once; the cloner spins the finished cloud as
rigid geometry — a cheap transform animation, no per-frame recompute. -->
<sk-cloner mode="vertex">
<sk-compute-buffer-points-source
compute-node="#boy"></sk-compute-buffer-points-source>
<sk-sphere radius="0.06" segments="2">
<sk-surface-paint material="#mat"></sk-surface-paint>
</sk-sphere>
<sk-animation duration="20000ms" iterations="Infinity">
<sk-keyframe offset="0" h="0deg"></sk-keyframe>
<sk-keyframe offset="1" h="360deg"></sk-keyframe>
</sk-animation>
</sk-cloner>
</sk-scene>
| Attribute | Type | Default | Description |
|---|---|---|---|
id |
<id> |
— | The element's unique identifier — the standard HTML global id attribute. |
compute-nodeanimatable |
<id-ref> |
— | The producing compute node (instance, wired at connect via its id). reference by id |
compute-property-key |
<property-name> |
"output.0" |
The output key to read (output.N). |