Skip to content

Commit de4abd7

Browse files
committed
Add example boids
1 parent 8fa597d commit de4abd7

25 files changed

+1255
-2
lines changed

examples/boids/boids-cl.ts

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// https://github.com/mrdoob/three.js/blob/master/examples/webgl_gpgpu_birds.html
2+
// http://web.engr.oregonstate.edu/~mjb/cs575/Handouts/opencl.opengl.vbo.1pp.pdf
3+
// https://developer.download.nvidia.com/compute/DevZone/docs/html/OpenCL/doc/OpenCL_Best_Practices_Guide.pdf
4+
5+
import { readFileSync } from 'node:fs';
6+
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
7+
import cl from 'opencl-raub';
8+
import { initCommon } from './utils/init-common.ts';
9+
import { loopCommon } from './utils/loop-common.ts';
10+
import { BirdMeshCl } from './cl/bird-mesh-cl.ts';
11+
import { fillPositionAndPhase, fillVelocity } from './utils/fill-data.ts';
12+
13+
const BIRDS: number = 128 * 128;
14+
const BOUNDS: number = 800;
15+
const IS_PERF_MODE: boolean = true;
16+
17+
const boidsSrc: string = readFileSync('cl/boids.cl').toString();
18+
const { screen, doc, gl } = initCommon(IS_PERF_MODE, 'Boids CL');
19+
const { platform, device } = cl.quickStart(!true);
20+
21+
const context = cl.createContext(
22+
[
23+
cl.GL_CONTEXT_KHR, doc.platformContext,
24+
cl.WGL_HDC_KHR, doc.platformDevice,
25+
cl.CONTEXT_PLATFORM, platform,
26+
],
27+
[device],
28+
);
29+
const queue = cl.createCommandQueue(context, device);
30+
31+
const birdMesh = new BirdMeshCl(BIRDS);
32+
screen.scene.add(birdMesh);
33+
34+
const controls = new OrbitControls(screen.camera, doc as unknown as HTMLElement);
35+
controls.update();
36+
37+
const { offsets, velocity } = birdMesh.vbos;
38+
fillPositionAndPhase(offsets.array, BOUNDS);
39+
gl.bindBuffer(gl.ARRAY_BUFFER, offsets.vbo);
40+
gl.bufferData(gl.ARRAY_BUFFER, offsets.array, gl.STATIC_DRAW);
41+
42+
fillVelocity(velocity.array);
43+
gl.bindBuffer(gl.ARRAY_BUFFER, velocity.vbo);
44+
gl.bufferData(gl.ARRAY_BUFFER, velocity.array, gl.STATIC_DRAW);
45+
46+
const memPos = cl.createFromGLBuffer(context, cl.MEM_READ_WRITE, offsets.vbo._);
47+
const memVel = cl.createFromGLBuffer(context, cl.MEM_READ_WRITE, velocity.vbo._);
48+
49+
cl.enqueueAcquireGLObjects(queue, memPos);
50+
cl.enqueueAcquireGLObjects(queue, memVel);
51+
cl.finish(queue);
52+
53+
// Create a program object
54+
const program = cl.createProgramWithSource(context, boidsSrc);
55+
cl.buildProgram(program, [device], `-cl-fast-relaxed-math -cl-mad-enable`);
56+
57+
const kernelUpdate = cl.createKernel(program, 'update');
58+
59+
const separation = 20.0;
60+
const alignment = 20.0;
61+
const cohesion = 20.0;
62+
63+
cl.setKernelArg(kernelUpdate, 0, 'uint', BIRDS);
64+
cl.setKernelArg(kernelUpdate, 1, 'float', 0.016); // dynamic
65+
cl.setKernelArg(kernelUpdate, 2, 'float', BOUNDS);
66+
cl.setKernelArg(kernelUpdate, 3, 'float', -10000); // dynamic
67+
cl.setKernelArg(kernelUpdate, 4, 'float', -10000); // dynamic
68+
cl.setKernelArg(kernelUpdate, 5, 'float', separation);
69+
cl.setKernelArg(kernelUpdate, 6, 'float', alignment);
70+
cl.setKernelArg(kernelUpdate, 7, 'float', cohesion);
71+
cl.setKernelArg(kernelUpdate, 8, 'float*', memPos);
72+
cl.setKernelArg(kernelUpdate, 9, 'float*', memVel);
73+
74+
75+
loopCommon(IS_PERF_MODE, (_now, delta, mouse) => {
76+
controls.update();
77+
78+
cl.setKernelArg(kernelUpdate, 1, 'float', delta);
79+
cl.setKernelArg(kernelUpdate, 3, 'float', mouse[0] * BOUNDS);
80+
cl.setKernelArg(kernelUpdate, 4, 'float', mouse[1] * BOUNDS);
81+
82+
gl.finish();
83+
84+
cl.enqueueNDRangeKernel(queue, kernelUpdate, 1, null, [BIRDS], [256]);
85+
86+
cl.finish(queue);
87+
88+
screen.draw();
89+
});

examples/boids/boids-gl.ts

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// https://github.com/mrdoob/three.js/blob/master/examples/webgl_gpgpu_birds.html
2+
3+
import { readFileSync } from 'node:fs';
4+
import * as THREE from 'three';
5+
import {
6+
GPUComputationRenderer, type Variable,
7+
} from 'three/addons/misc/GPUComputationRenderer.js';
8+
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
9+
import { initCommon } from './utils/init-common.ts';
10+
import { fillPositionAndPhase, fillVelocity } from './utils/fill-data.ts';
11+
import { loopCommon } from './utils/loop-common.ts';
12+
import { BirdMesh } from './gl/bird-mesh.ts';
13+
14+
15+
const IS_PERF_MODE: boolean = true;
16+
const { screen, doc } = initCommon(IS_PERF_MODE, 'Boids GL');
17+
18+
const fragmentShaderPosition: string = readFileSync('gl/position-fs.glsl').toString();
19+
const fragmentShaderVelocity: string = readFileSync('gl/velocity-fs.glsl').toString();
20+
21+
/* Texture size for simulation */
22+
const WIDTH: number = 128; // 128^2 = 16384 birds. It ain't much, but it's honest work
23+
const BIRDS: number = WIDTH * WIDTH;
24+
25+
const BOUNDS: number = 800;
26+
27+
type TPositionUniforms = {
28+
time: THREE.Uniform,
29+
delta: THREE.Uniform,
30+
};
31+
32+
type TVelocityUniforms = TPositionUniforms & {
33+
testing: THREE.Uniform,
34+
separationDistance: THREE.Uniform,
35+
alignmentDistance: THREE.Uniform,
36+
cohesionDistance: THREE.Uniform,
37+
freedomFactor: THREE.Uniform,
38+
predator: THREE.Uniform,
39+
};
40+
41+
const controls = new OrbitControls(screen.camera, doc as unknown as HTMLElement);
42+
controls.update();
43+
44+
const gpuCompute = new GPUComputationRenderer(WIDTH, WIDTH, screen.renderer);
45+
const dtPosition = gpuCompute.createTexture();
46+
fillPositionAndPhase(dtPosition.image.data, BOUNDS);
47+
const dtVelocity = gpuCompute.createTexture();
48+
fillVelocity(dtVelocity.image.data);
49+
50+
const velocityVariable: Variable = gpuCompute.addVariable(
51+
'textureVelocity', fragmentShaderVelocity, dtVelocity,
52+
);
53+
const positionVariable: Variable = gpuCompute.addVariable(
54+
'texturePosition', fragmentShaderPosition, dtPosition,
55+
);
56+
57+
gpuCompute.setVariableDependencies(velocityVariable, [positionVariable, velocityVariable]);
58+
gpuCompute.setVariableDependencies(positionVariable, [positionVariable, velocityVariable]);
59+
60+
const positionUniforms: TPositionUniforms = (positionVariable.material.uniforms as TPositionUniforms);
61+
const velocityUniforms: TVelocityUniforms = (velocityVariable.material.uniforms as TVelocityUniforms);
62+
63+
positionUniforms.delta = new THREE.Uniform(0.0);
64+
65+
velocityUniforms.delta = new THREE.Uniform(0.0)
66+
velocityUniforms.separationDistance = new THREE.Uniform(20.0);
67+
velocityUniforms.alignmentDistance = new THREE.Uniform(20.0);
68+
velocityUniforms.cohesionDistance = new THREE.Uniform(20.0);
69+
velocityUniforms.predator = new THREE.Uniform(new THREE.Vector3());
70+
71+
velocityVariable.material.defines['BOUNDS'] = BOUNDS.toFixed(2);
72+
velocityVariable.wrapS = THREE.RepeatWrapping;
73+
velocityVariable.wrapT = THREE.RepeatWrapping;
74+
positionVariable.wrapS = THREE.RepeatWrapping;
75+
positionVariable.wrapT = THREE.RepeatWrapping;
76+
77+
const error = gpuCompute.init();
78+
if (error) {
79+
console.error(error);
80+
}
81+
82+
const birdMesh = new BirdMesh(BIRDS, WIDTH);
83+
screen.scene.add(birdMesh);
84+
85+
loopCommon(IS_PERF_MODE, (_now, delta, mouse) => {
86+
controls.update();
87+
88+
positionUniforms.delta.value = delta;
89+
90+
velocityUniforms.delta.value = delta;
91+
velocityUniforms.predator.value.set(mouse[0], mouse[1], 0);
92+
93+
gpuCompute.compute();
94+
95+
birdMesh.uniforms.texturePosition.value = gpuCompute.getCurrentRenderTarget(positionVariable).texture;
96+
birdMesh.uniforms.textureVelocity.value = gpuCompute.getCurrentRenderTarget(velocityVariable).texture;
97+
98+
screen.draw();
99+
});

examples/boids/cl/bird-fs.glsl

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
in vec4 vColor;
2+
in float vZ;
3+
4+
out vec4 fragColor;
5+
6+
uniform vec3 color;
7+
8+
9+
void main() {
10+
fragColor = vec4(mix(vColor.rgb, color, pow(vZ, 1.618)), 1.0);
11+
}

examples/boids/cl/bird-geometry-cl.ts

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import * as THREE from 'three';
2+
import { type WebGLBuffer } from 'webgl-raub';
3+
import node3d from '../../../index.js';
4+
5+
export type TVboInfo = {
6+
vbo: WebGLBuffer,
7+
array: Float32Array,
8+
attribute: THREE.BufferAttribute,
9+
};
10+
11+
export type TBirdVbos = {
12+
velocity: TVboInfo,
13+
offsets: TVboInfo,
14+
};
15+
16+
const createVbo = (count: number, elements: number): TVboInfo => {
17+
const { gl } = node3d.init();
18+
const array = new Float32Array(count * elements);
19+
const vbo = gl.createBuffer();
20+
const attribute = new THREE.GLBufferAttribute(vbo, gl.FLOAT, elements, 4, count);
21+
22+
// HACK: instancing support
23+
const iattr = attribute as unknown as THREE.InstancedBufferAttribute;
24+
(iattr as { isInstancedBufferAttribute: boolean }).isInstancedBufferAttribute = true;
25+
iattr.meshPerAttribute = 1;
26+
27+
return {
28+
vbo,
29+
array,
30+
attribute: iattr,
31+
};
32+
};
33+
34+
35+
// Custom Geometry - 3 triangles and instancing data.
36+
export class BirdGeometryCl extends THREE.InstancedBufferGeometry {
37+
vbos: TBirdVbos;
38+
39+
constructor(population: number) {
40+
super();
41+
42+
this.instanceCount = population;
43+
44+
const wingsSpan = 20;
45+
const vertData = [
46+
// Body
47+
0, -0, -wingsSpan,
48+
0, 4, -wingsSpan,
49+
0, 0, 30,
50+
51+
// Wings
52+
0, 0, -15,
53+
-wingsSpan, 0, 0,
54+
0, 0, 15,
55+
0, 0, 15,
56+
wingsSpan, 0, 0,
57+
0, 0, -15,
58+
].map(x => (x * 0.2));
59+
const idxData = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
60+
61+
const vertices = new THREE.BufferAttribute(new Float32Array(vertData), 3);
62+
const vertidx = new THREE.BufferAttribute(new Uint32Array(idxData), 1);
63+
64+
const velocity = createVbo(population, 4);
65+
const offsets = createVbo(population, 4);
66+
67+
this.vbos = { velocity, offsets };
68+
69+
// Non-instanced part, one bird
70+
this.setAttribute('position', vertices);
71+
this.setAttribute('vertidx', vertidx);
72+
73+
// Per-instance items
74+
this.setAttribute('velocity', velocity.attribute);
75+
this.setAttribute('offset', offsets.attribute);
76+
77+
this.computeBoundingSphere = (() => {
78+
this.boundingSphere = new THREE.Sphere(undefined, Infinity);
79+
});
80+
this.computeBoundingSphere();
81+
}
82+
}

examples/boids/cl/bird-mesh-cl.ts

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { readFileSync } from 'node:fs';
2+
import * as THREE from 'three';
3+
import { BirdGeometryCl } from './bird-geometry-cl.ts';
4+
5+
const birdVS: string = readFileSync('cl/bird-vs.glsl').toString();
6+
const birdFS: string = readFileSync('cl/bird-fs.glsl').toString();
7+
8+
export type TBirdUniforms = {
9+
color: THREE.Uniform,
10+
};
11+
12+
// Custom Mesh - BirdGeometry and some point-cloud adjustments.
13+
export class BirdMeshCl extends THREE.Mesh {
14+
get vbos() { return (this.geometry as BirdGeometryCl).vbos; }
15+
uniforms: TBirdUniforms;
16+
17+
constructor(population: number) {
18+
const uniforms = {
19+
color: new THREE.Uniform(new THREE.Color(0)),
20+
} as const;
21+
22+
const material = new THREE.ShaderMaterial({
23+
vertexShader: birdVS,
24+
fragmentShader: birdFS,
25+
side: THREE.DoubleSide,
26+
forceSinglePass: true,
27+
transparent: false,
28+
uniforms,
29+
});
30+
31+
const geometry = new BirdGeometryCl(population);
32+
33+
super(geometry, material);
34+
35+
this.uniforms = uniforms;
36+
37+
this.rotation.y = Math.PI / 2;
38+
this.matrixAutoUpdate = false;
39+
this.updateMatrix();
40+
}
41+
}

examples/boids/cl/bird-vs.glsl

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
attribute uint vertidx;
2+
attribute vec4 offset;
3+
attribute vec4 velocity;
4+
5+
out vec4 vColor;
6+
out float vZ;
7+
8+
9+
void main() {
10+
float phase = offset.w;
11+
vec3 newPosition = position;
12+
13+
if (vertidx == 4U || vertidx == 7U) {
14+
// flap wings
15+
newPosition.y = sin(phase) * 5.0;
16+
}
17+
18+
newPosition = mat3(modelMatrix) * newPosition;
19+
20+
vec3 velTmp = normalize(velocity.xyz);
21+
vColor = vec4(0.5 * velTmp + vec3(0.5), 1.0);
22+
23+
velTmp.z *= -1.0;
24+
float xz = length(velTmp.xz);
25+
float xyz = 1.0;
26+
float x = sqrt(1.0 - velTmp.y * velTmp.y);
27+
28+
float cosry = velTmp.x / xz;
29+
float sinry = velTmp.z / xz;
30+
31+
float cosrz = x / xyz;
32+
float sinrz = velTmp.y / xyz;
33+
34+
mat3 maty = mat3(
35+
cosry, 0.0, -sinry,
36+
0.0, 1.0, 0.0,
37+
sinry, 0.0, cosry
38+
);
39+
40+
mat3 matz = mat3(
41+
cosrz, sinrz, 0.0,
42+
-sinrz, cosrz, 0.0,
43+
0.0, 0.0, 1.0
44+
);
45+
46+
newPosition = maty * matz * newPosition;
47+
newPosition += offset.xyz;
48+
49+
vec4 projected = projectionMatrix * viewMatrix * vec4(newPosition, 1.0);
50+
vZ = projected.z * 0.0005; // z/2000 OR z/far
51+
gl_Position = projected;
52+
}

0 commit comments

Comments
 (0)