mirror of
https://github.com/visgl/luma.gl.git
synced 2025-12-08 17:36:19 +00:00
411 lines
10 KiB
Plaintext
411 lines
10 KiB
Plaintext
import {DeviceTabs} from '@site/src/react-luma';
|
|
import {InstancedTransformExample} from '@site';
|
|
|
|
# Instanced Transform
|
|
|
|
:::info
|
|
|
|
TransformFeedback based examples are temporarily disabled until `Transform` class is ported to luma.gl v9
|
|
|
|
:::
|
|
|
|
<DeviceTabs />
|
|
<InstancedTransformExample />
|
|
|
|
In this final tutorial, we'll pull together almost everything we've learned
|
|
in past tutorials into a single scene: lighing, textures, geometry, shader modules,
|
|
instancing and transform feedback. Whew! This will build on the
|
|
previous tutorial,
|
|
so it might be helpful to start with its source code.
|
|
|
|
We'll be drawing 4 instanced cubes, textured and lit in the same way as in the
|
|
lighting tutorial, but with the animations updated by transform feedback.
|
|
|
|
The `Transform` class is the only addition we need for our imports:
|
|
|
|
```typescript
|
|
import {AnimationLoop, Model, Transform, CubeGeometry} from '@luma.gl/engine';
|
|
import {Buffer, Texture2D, clear, setParameters, isWebGL2} from '@luma.gl/webgl';
|
|
import {phongLighting} from '@luma.gl/shadertools';
|
|
import {Matrix4} from '@math.gl/core';
|
|
```
|
|
|
|
The vertex shader for our transform feedback is quite simple. It just increments a scalar rotation value on each run:
|
|
|
|
```typescript
|
|
const transformVs = `
|
|
attribute float rotations;
|
|
|
|
varying float vRotation;
|
|
|
|
void main() {
|
|
vRotation = rotations + 0.01;
|
|
}
|
|
`;
|
|
```
|
|
|
|
In order to handle rotation updates in the transform feedback, we have to move construction of the rotation matrix into the vertex shader. We'll use an [axis-angle](https://en.wikipedia.org/wiki/Rotation_matrix#Rotation_matrix_from_axis_and_angle), passing the axis and rotation angle as instanced attributes (with the rotation angles being updated by transform feedback):
|
|
|
|
```typescript
|
|
const vs = `\
|
|
attribute vec3 positions;
|
|
attribute vec3 normals;
|
|
attribute vec2 texCoords;
|
|
attribute vec2 offsets;
|
|
attribute vec3 axes;
|
|
attribute float rotations;
|
|
|
|
uniform mat4 uView;
|
|
uniform mat4 uProjection;
|
|
|
|
varying vec3 vPosition;
|
|
varying vec3 vNormal;
|
|
varying vec2 vUV;
|
|
|
|
void main(void) {
|
|
float s = sin(rotations);
|
|
float c = cos(rotations);
|
|
float t = 1.0 - c;
|
|
float xt = axes.x * t;
|
|
float yt = axes.y * t;
|
|
float zt = axes.z * t;
|
|
float xs = axes.x * s;
|
|
float ys = axes.y * s;
|
|
float zs = axes.z * s;
|
|
|
|
mat3 rotationMat = mat3(
|
|
axes.x * xt + c,
|
|
axes.y * xt + zs,
|
|
axes.z * xt - ys,
|
|
axes.x * yt - zs,
|
|
axes.y * yt + c,
|
|
axes.z * yt + xs,
|
|
axes.x * zt + ys,
|
|
axes.y * zt - xs,
|
|
axes.z * zt + c
|
|
);
|
|
|
|
vPosition = rotationMat * positions;
|
|
vPosition.xy += offsets;
|
|
vNormal = rotationMat * normals;
|
|
vUV = texCoords;
|
|
gl_Position = uProjection * uView * vec4(vPosition, 1.0);
|
|
}
|
|
```
|
|
|
|
We also pass an `offsets` instanced attribute to position each cube.
|
|
|
|
Our fragment shader doesn't change at all:
|
|
|
|
```typescript
|
|
const fs = `\
|
|
precision highp float;
|
|
|
|
uniform sampler2D uTexture;
|
|
uniform vec3 uEyePosition;
|
|
|
|
varying vec3 vPosition;
|
|
varying vec3 vNormal;
|
|
varying vec2 vUV;
|
|
|
|
void main(void) {
|
|
vec3 materialColor = texture2D(uTexture, vec2(vUV.x, 1.0 - vUV.y)).rgb;
|
|
vec3 surfaceColor = lighting_getLightColor(materialColor, uEyePosition, vPosition, normalize(vNormal));
|
|
|
|
gl_FragColor = vec4(surfaceColor, 1.0);
|
|
}
|
|
`;
|
|
```
|
|
|
|
Our `onInitialize` method will need several updates. First we create buffers for our instanced data:
|
|
|
|
```typescript
|
|
const offsetBuffer = device.createBuffer(new Float32Array([3, 3, -3, 3, 3, -3, -3, -3]));
|
|
|
|
const axisBufferData = new Float32Array(12);
|
|
for (let i = 0; i < 4; ++i) {
|
|
const vi = i * 3;
|
|
const x = Math.random();
|
|
const y = Math.random();
|
|
const z = Math.random();
|
|
const l = Math.sqrt(x * x + y * y + z * z);
|
|
|
|
axisBufferData[vi] = x / l;
|
|
axisBufferData[vi + 1] = y / l;
|
|
axisBufferData[vi + 2] = z / l;
|
|
}
|
|
const axisBuffer = device.createBuffer(axisBufferData);
|
|
|
|
const rotationBuffer = device.createBuffer(
|
|
new Float32Array([
|
|
Math.random() * Math.PI * 2,
|
|
Math.random() * Math.PI * 2,
|
|
Math.random() * Math.PI * 2,
|
|
Math.random() * Math.PI * 2
|
|
])
|
|
);
|
|
```
|
|
|
|
The `offsetBuffer` sets positions so the cubes will be in a square formation. The `axisBuffer` looks more complicated, but its simply a set of 4 normalized vectors about which we'll rotate our cubes. Finally, the `rotationBuffer` simply starts with 4 random angles between 0 and 2π.
|
|
|
|
The `Transform` is straightforward to set up, simply taking the `rotationBuffer` and our vertex shader as input:
|
|
|
|
```typescript
|
|
const transform = new Transform(device, {
|
|
vs: transformVs,
|
|
sourceBuffers: {
|
|
rotations: rotationBuffer
|
|
},
|
|
feedbackMap: {
|
|
rotations: 'vRotation'
|
|
},
|
|
elementCount: 4
|
|
});
|
|
```
|
|
|
|
And the `Model` needs to be updated to take the instanced attributes and `instanceCount`:
|
|
|
|
```typescript
|
|
const model = new Model(device, {
|
|
vs,
|
|
fs,
|
|
geometry: new CubeGeometry(),
|
|
attributes: {
|
|
offsets: [offsetBuffer, {divisor: 1}],
|
|
axes: [axisBuffer, {divisor: 1}],
|
|
rotations: [rotationBuffer, {divisor: 1}]
|
|
},
|
|
uniforms: {
|
|
uTexture: texture,
|
|
uEyePosition: eyePosition,
|
|
uView: viewMatrix
|
|
},
|
|
modules: [phongLighting],
|
|
moduleSettings: {
|
|
material: {
|
|
specularColor: [255, 255, 255]
|
|
},
|
|
lights: [
|
|
{
|
|
type: 'ambient',
|
|
color: [255, 255, 255]
|
|
},
|
|
{
|
|
type: 'point',
|
|
color: [255, 255, 255],
|
|
position: [4, 8, 4]
|
|
}
|
|
]
|
|
},
|
|
instanceCount: 4
|
|
});
|
|
```
|
|
|
|
Our `onRender` needs an update to perform the transform feedback and pass the transformed rotation buffer to the `Model`:
|
|
|
|
```typescript
|
|
override onRender({device, aspect, model, transform, projectionMatrix}) {
|
|
projectionMatrix.perspective({fovy: Math.PI / 3, aspect});
|
|
|
|
transform.run();
|
|
|
|
clear(device, {color: [0, 0, 0, 1], depth: true});
|
|
model
|
|
.setAttributes({rotations: [transform.getBuffer('vRotation'), {divisor: 1}]})
|
|
.setUniforms({uProjection: projectionMatrix})
|
|
.draw();
|
|
|
|
transform.swap();
|
|
}
|
|
```
|
|
|
|
If all went well, you should see 4 rotating cubes. This scene is significantly more complex than anything we've seen before, so take some time to play around with it and get to know the various parts.
|
|
|
|
```typescript
|
|
import {AnimationLoop, Model, Transform, CubeGeometry} from '@luma.gl/engine';
|
|
import {Buffer, Texture2D, clear, setParameters, isWebGL2} from '@luma.gl/webgl';
|
|
import {phongLighting} from '@luma.gl/shadertools';
|
|
import {Matrix4} from '@math.gl/core';
|
|
|
|
const transformVs = `
|
|
attribute float rotations;
|
|
|
|
varying float vRotation;
|
|
|
|
void main() {
|
|
vRotation = rotations + 0.01;
|
|
}
|
|
`;
|
|
|
|
const vs = `\
|
|
attribute vec3 positions;
|
|
attribute vec3 normals;
|
|
attribute vec2 texCoords;
|
|
attribute vec2 offsets;
|
|
attribute vec3 axes;
|
|
attribute float rotations;
|
|
|
|
uniform mat4 uView;
|
|
uniform mat4 uProjection;
|
|
|
|
varying vec3 vPosition;
|
|
varying vec3 vNormal;
|
|
varying vec2 vUV;
|
|
|
|
void main(void) {
|
|
float s = sin(rotations);
|
|
float c = cos(rotations);
|
|
float t = 1.0 - c;
|
|
float xt = axes.x * t;
|
|
float yt = axes.y * t;
|
|
float zt = axes.z * t;
|
|
float xs = axes.x * s;
|
|
float ys = axes.y * s;
|
|
float zs = axes.z * s;
|
|
|
|
mat3 rotationMat = mat3(
|
|
axes.x * xt + c,
|
|
axes.y * xt + zs,
|
|
axes.z * xt - ys,
|
|
axes.x * yt - zs,
|
|
axes.y * yt + c,
|
|
axes.z * yt + xs,
|
|
axes.x * zt + ys,
|
|
axes.y * zt - xs,
|
|
axes.z * zt + c
|
|
);
|
|
|
|
vPosition = rotationMat * positions;
|
|
vPosition.xy += offsets;
|
|
vNormal = rotationMat * normals;
|
|
vUV = texCoords;
|
|
gl_Position = uProjection * uView * vec4(vPosition, 1.0);
|
|
}
|
|
`;
|
|
|
|
const fs = `\
|
|
precision highp float;
|
|
|
|
uniform sampler2D uTexture;
|
|
uniform vec3 uEyePosition;
|
|
|
|
varying vec3 vPosition;
|
|
varying vec3 vNormal;
|
|
varying vec2 vUV;
|
|
|
|
void main(void) {
|
|
vec3 materialColor = texture2D(uTexture, vec2(vUV.x, 1.0 - vUV.y)).rgb;
|
|
vec3 surfaceColor = lighting_getLightColor(materialColor, uEyePosition, vPosition, normalize(vNormal));
|
|
|
|
gl_FragColor = vec4(surfaceColor, 1.0);
|
|
}
|
|
`;
|
|
|
|
const loop = new AnimationLoop({
|
|
override onInitialize({gl}) {
|
|
setParameters(gl, {
|
|
depthTest: true,
|
|
depthFunc: gl.LEQUAL
|
|
});
|
|
|
|
const offsetBuffer = device.createBuffer(new Float32Array([3, 3, -3, 3, 3, -3, -3, -3]));
|
|
|
|
const axisBufferData = new Float32Array(12);
|
|
for (let i = 0; i < 4; ++i) {
|
|
const vi = i * 3;
|
|
const x = Math.random();
|
|
const y = Math.random();
|
|
const z = Math.random();
|
|
const l = Math.sqrt(x * x + y * y + z * z);
|
|
|
|
axisBufferData[vi] = x / l;
|
|
axisBufferData[vi + 1] = y / l;
|
|
axisBufferData[vi + 2] = z / l;
|
|
}
|
|
const axisBuffer = device.createBuffer(axisBufferData);
|
|
|
|
const rotationBuffer = device.createBuffer(
|
|
new Float32Array([
|
|
Math.random() * Math.PI * 2,
|
|
Math.random() * Math.PI * 2,
|
|
Math.random() * Math.PI * 2,
|
|
Math.random() * Math.PI * 2
|
|
])
|
|
);
|
|
|
|
const texture = device.createTexture({data: 'vis-logo.png'});
|
|
|
|
const eyePosition = [0, 0, 10];
|
|
const viewMatrix = new Matrix4().lookAt({eye: eyePosition});
|
|
const projectionMatrix = new Matrix4();
|
|
|
|
const transform = new Transform(device, {
|
|
vs: transformVs,
|
|
sourceBuffers: {
|
|
rotations: rotationBuffer
|
|
},
|
|
feedbackMap: {
|
|
rotations: 'vRotation'
|
|
},
|
|
elementCount: 4
|
|
});
|
|
|
|
const model = new Model(device, {
|
|
vs,
|
|
fs,
|
|
geometry: new CubeGeometry(),
|
|
attributes: {
|
|
offsets: [offsetBuffer, {divisor: 1}],
|
|
axes: [axisBuffer, {divisor: 1}],
|
|
rotations: [rotationBuffer, {divisor: 1}]
|
|
},
|
|
uniforms: {
|
|
uTexture: texture,
|
|
uEyePosition: eyePosition,
|
|
uView: viewMatrix
|
|
},
|
|
modules: [phongLighting],
|
|
moduleSettings: {
|
|
material: {
|
|
specularColor: [255, 255, 255]
|
|
},
|
|
lights: [
|
|
{
|
|
type: 'ambient',
|
|
color: [255, 255, 255]
|
|
},
|
|
{
|
|
type: 'point',
|
|
color: [255, 255, 255],
|
|
position: [4, 8, 4]
|
|
}
|
|
]
|
|
},
|
|
instanceCount: 4
|
|
});
|
|
|
|
return {
|
|
model,
|
|
transform,
|
|
projectionMatrix
|
|
};
|
|
},
|
|
|
|
override onRender({device, aspect, model, transform, projectionMatrix}) {
|
|
projectionMatrix.perspective({fovy: Math.PI / 3, aspect});
|
|
|
|
transform.run();
|
|
|
|
clear(device, {color: [0, 0, 0, 1], depth: true});
|
|
model
|
|
.setAttributes({rotations: [transform.getBuffer('vRotation'), {divisor: 1}]})
|
|
.setUniforms({uProjection: projectionMatrix})
|
|
.draw();
|
|
|
|
transform.swap();
|
|
}
|
|
});
|
|
|
|
loop.start();
|
|
```
|