mirror of
https://github.com/visgl/luma.gl.git
synced 2025-12-08 17:36:19 +00:00
149 lines
4.0 KiB
Plaintext
149 lines
4.0 KiB
Plaintext
import {DeviceTabs} from '@site/src/react-luma';
|
|
import {TransformFeedbackExample} from '@site/src/examples';
|
|
|
|
# Transform Feedback
|
|
|
|
This tutorial demonstrates animating geometry using transform feedback via the `BufferTransform` class. A compute shader rotates triangle vertices on the GPU each frame.
|
|
|
|
:::caution
|
|
Tutorials are maintained on a best-effort basis and may not be fully up to date (contributions welcome).
|
|
:::
|
|
|
|
<DeviceTabs />
|
|
<TransformFeedbackExample />
|
|
|
|
It is assumed you've set up your development environment as described in [Setup](/docs/tutorials).
|
|
|
|
Transform feedback allows us to capture vertex shader results from one pass and use them in subsequent passes. It is a powerful tool that can be used to set up massively parrallelized animations or data transformations. Note that transform feedback can only be used with WebGL 2.
|
|
|
|
Two buffers store the triangle's positions. `BufferTransform` runs a small
|
|
vertex shader that multiplies each position by a rotation matrix and writes the
|
|
result into the alternate buffer. After the transform step the buffers swap and
|
|
the scene is rendered with the updated positions.
|
|
|
|
|
|
The complete source for this example is shown below:
|
|
|
|
```typescript
|
|
// luma.gl
|
|
// SPDX-License-Identifier: MIT
|
|
// Copyright (c) vis.gl contributors
|
|
|
|
import {Buffer} from '@luma.gl/core';
|
|
import {AnimationLoopTemplate, AnimationProps, Model, Swap, BufferTransform} from '@luma.gl/engine';
|
|
|
|
const transformVs = /* glsl */ `\
|
|
#version 300 es
|
|
#define SIN2 0.03489949
|
|
#define COS2 0.99939082
|
|
|
|
mat2 rotation = mat2(
|
|
COS2, SIN2,
|
|
-SIN2, COS2
|
|
);
|
|
|
|
in vec2 oldPositions;
|
|
out vec2 newPositions;
|
|
|
|
void main() {
|
|
newPositions = rotation * oldPositions;
|
|
}
|
|
`;
|
|
|
|
const renderVs = /* glsl */ `\
|
|
#version 300 es
|
|
|
|
in vec2 position;
|
|
in vec3 color;
|
|
out vec3 vColor;
|
|
|
|
void main() {
|
|
vColor = color;
|
|
gl_Position = vec4(position, 0.0, 1.0);
|
|
}
|
|
`;
|
|
|
|
const renderFs = /* glsl */ `\
|
|
#version 300 es
|
|
precision highp float;
|
|
|
|
in vec3 vColor;
|
|
out vec4 fragColor;
|
|
|
|
void main() {
|
|
fragColor = vec4(vColor, 1.0);
|
|
}
|
|
`;
|
|
|
|
class AppAnimationLoopTemplate extends AnimationLoopTemplate {
|
|
transform: BufferTransform;
|
|
model: Model;
|
|
|
|
positionBuffers: Swap<Buffer>;
|
|
colorBuffer: Buffer;
|
|
|
|
constructor({device, animationLoop}: AnimationProps) {
|
|
super();
|
|
|
|
if (device.type !== 'webgl') {
|
|
throw new Error('This demo is only implemented for WebGL2');
|
|
}
|
|
|
|
this.positionBuffers = new Swap({
|
|
current: device.createBuffer(new Float32Array([-0.5, -0.5, 0.5, -0.5, 0.0, 0.5])),
|
|
next: device.createBuffer(new Float32Array(6))
|
|
});
|
|
|
|
this.colorBuffer = device.createBuffer(
|
|
new Float32Array([1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0])
|
|
);
|
|
|
|
this.transform = new BufferTransform(device, {
|
|
vs: transformVs,
|
|
bufferLayout: [{name: 'oldPositions', format: 'float32x2'}],
|
|
outputs: ['newPositions'],
|
|
vertexCount: 3
|
|
});
|
|
|
|
this.model = new Model(device, {
|
|
vs: renderVs,
|
|
fs: renderFs,
|
|
attributes: {color: this.colorBuffer},
|
|
bufferLayout: [
|
|
{name: 'position', format: 'float32x2'},
|
|
{name: 'color', format: 'float32x3'}
|
|
],
|
|
vertexCount: 3
|
|
});
|
|
}
|
|
|
|
onFinalize() {
|
|
this.transform.destroy();
|
|
this.model.destroy();
|
|
this.positionBuffers.destroy();
|
|
this.colorBuffer.destroy();
|
|
}
|
|
|
|
onRender({device}) {
|
|
// Run a rotation step
|
|
this.transform.run({
|
|
inputBuffers: {oldPositions: this.positionBuffers.current},
|
|
outputBuffers: {newPositions: this.positionBuffers.next}
|
|
});
|
|
this.positionBuffers.swap();
|
|
|
|
// Render with the latest positions
|
|
const renderPass = device.beginRenderPass({clearColor: [0, 0, 0, 1]});
|
|
this.model.setAttributes({position: this.positionBuffers.current});
|
|
this.model.draw(renderPass);
|
|
renderPass.end();
|
|
}
|
|
}
|
|
|
|
const animationLoop = makeAnimationLoop(AnimationLoopTemplate, {adapters: [webgl2Adapter]})
|
|
animationLoop.start();
|
|
```
|
|
|
|
Keeping the rotation logic in a transform feedback pass avoids CPU involvement
|
|
and lets the triangle animate smoothly as GPU buffers swap each frame.
|