Skip to content

Commit

Permalink
add webgpu node based vfx renderer demo
Browse files Browse the repository at this point in the history
  • Loading branch information
Alchemist0823 committed May 4, 2024
1 parent 80f7d4d commit 1f26ab7
Show file tree
Hide file tree
Showing 13 changed files with 854 additions and 33 deletions.
14 changes: 6 additions & 8 deletions examples/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="utf-8">
<title>three.quarks – particle system library for three.js</title>
<title>three.quarks – particle system / VFX library for three.js</title>
<link rel="shortcut icon" href="./favicon.png"/>
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<meta name="theme-color" content="#000000"/>
Expand All @@ -13,13 +13,7 @@
<link rel="manifest" href="./manifest.json"/>
<title>Three.quarks Particle System Examples</title>
<meta name="description" content="A ThreeJS / WebGL Particle System for Creators.">
<meta name="og:title" property="og:title" content="Three.quarks Particle System">
<!--script type="module" src="//cdnjs.cloudflare.com/ajax/libs/three.js/r110/three.module.js" integrity="sha512-8Q+LHYWnd5k/kWiIDvLd+FKKCQUjxhnnWa29z9xnQZcEmrI/sM/ONo2NJtN4UIHY91/jEZArn+AFI3oySADsug==" crossorigin="anonymous"></script>
<script type="module" src="//cdnjs.cloudflare.com/ajax/libs/stats.js/r17/Stats.min.js" integrity="sha512-mfOs9z5Hk96xJH71l0ptzjgGvflNJRnHA7brsEwqDZf7mJa8QDfUtcHICKMXq4Ys80g5HKQMD9rsY3R44ZlEug==" crossorigin="anonymous"></script>
<script type="module" src="//threejs.org/examples/jsm/controls/OrbitControls.js"></script-->
<!--script type="module" src="three"></script>
<script type="module" src="./js/OrbitControls.js"></script>
<script type="module" src="./three.quarks.esm.js"></script-->
<meta name="og:title" property="og:title" content="Three.quarks Particle System - Demo">
<script async src="https://unpkg.com/es-module-shims@1.8.0/dist/es-module-shims.js"></script>
<script type="importmap">
{
Expand All @@ -31,6 +25,10 @@
</script>

<style>
:root {
color-scheme: light dark;
}

body {
margin: 0;
padding: 0;
Expand Down
3 changes: 1 addition & 2 deletions examples/nodeBasedVFXDemo.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {BatchedParticleRenderer, Node, NodeGraph, NodeTypes, NodeVFX, RenderMode, Wire} from 'three.quarks';
import {BatchedParticleRenderer, Node, NodeGraph, NodeTypes, NodeVFX, RenderMode, Wire, NodeValueType} from 'three.quarks';
import {Demo} from './demo.js';
import {AdditiveBlending, MeshBasicMaterial, NormalBlending, TextureLoader, Vector3, Vector4} from 'three';
import {NodeValueType} from "./js/three.quarks.esm.js";

export class NodeBasedVFXDemo extends Demo {
name = 'Node Based VFX (Experimental)';
Expand Down
72 changes: 72 additions & 0 deletions examples/webgpu.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>three.quarks – particle system / VFX library for three.js</title>
<link rel="shortcut icon" href="./favicon.png"/>
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<meta name="theme-color" content="#000000"/>
<link rel="manifest" href="./manifest.json"/>
<title>Three.quarks Particle System WebGPU Examples</title>
<meta name="description" content="A ThreeJS / WebGL Particle System for Creators.">
<meta name="og:title" property="og:title" content="Three.quarks Particle System - WebGPU Demo">
<script async src="https://unpkg.com/es-module-shims@1.8.0/dist/es-module-shims.js"></script>
<script type="importmap">
{
"imports": {
"three": "./js/three.module.js",
"three.quarks": "./js/three.quarks.esm.js"
}
}
</script>
<style>
:root {
color-scheme: light dark;
}

body {
margin: 0;
padding: 0;
}

#demo-name {
font-weight: bold;
font-family: sans-serif;
color: white;
z-index: 10;
position: relative;
text-align: center;
margin: 0 auto;
padding: 16px;
}

#renderer-canvas {
position: absolute;
width: 100%;
height: 100%;
}

.btn {
color: white;
border: white 1px;
font-size: 3em;
cursor: pointer;
}

.next {
position: absolute;
top: 50%;
right: 0;
}

.previous {
position: absolute;
top: 50%;
}
</style>
<script defer src="webgpu.js" type="module"></script>
</head>
<body id="home">
<canvas id="renderer-canvas"></canvas>
</body>
</html>
182 changes: 182 additions & 0 deletions examples/webgpu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import {WebGPURenderer, initWebGPU, NodeGraph, Node, NodeTypes, NodeValueType, Wire, WebGPUCompiler} from "three.quarks";
import {PerspectiveCamera, Vector3} from "three";


const renderCode = `
////////////////////////////////////////////////////////////////////////////////
// Vertex shader
////////////////////////////////////////////////////////////////////////////////
struct RenderParams {
modelViewMatrix : mat4x4f,
projectionMatrix : mat4x4f,
}
struct Particle {
color: vec4<f32>,
position: vec3<f32>,
velocity: vec3<f32>,
size: f32,
rotation: f32,
life: f32,
age: f32,
}
@group(0) @binding(0) var<uniform> render_params : RenderParams;
@group(0) @binding(1) var<storage, read> particles: array<Particle>;
@group(0) @binding(2) var<storage, read> particleIndices: array<u32>;
struct VertexInput {
/*@location(0) position : vec3f,
@location(1) color : vec4f,*/
@builtin(instance_index) instanceIndex: u32,
@location(0) quad_pos : vec2f, // -1..+1
}
struct VertexOutput {
@builtin(position) position : vec4f,
@location(0) color : vec4f,
@location(1) quad_pos : vec2f, // -1..+1
}
@vertex
fn vs_main(in : VertexInput) -> VertexOutput {
var particle = particles[particleIndices[in.instanceIndex]];
var alignedPosition = ( in.quad_pos.xy ) * particle.size;
var rotatedPosition = vec2f(
cos( particle.rotation ) * alignedPosition.x - sin( particle.rotation ) * alignedPosition.y,
sin( particle.rotation ) * alignedPosition.x + cos( particle.rotation ) * alignedPosition.y,
);
var mvPosition = render_params.modelViewMatrix * vec4f(particle.position, 1.0);
var out : VertexOutput;
out.position = render_params.projectionMatrix * vec4(mvPosition.xy + rotatedPosition, mvPosition.zw);
out.color = particle.color;
out.quad_pos = in.quad_pos;
return out;
}
////////////////////////////////////////////////////////////////////////////////
// Fragment shader
////////////////////////////////////////////////////////////////////////////////
@fragment
fn fs_main(in : VertexOutput) -> @location(0) vec4f {
var color = in.color;
// Apply a circular particle alpha mask
// color.a = color.a * max(1.0 - length(in.quad_pos), 0.0);
return color;
}
`;
initWebGPU().then((context) => {
const graph = new NodeGraph('test');

const sizeVal = new Node(NodeTypes['number'], 0);
const size = new Node(NodeTypes['particleProperty'], 0, {property: 'size', type: NodeValueType.Number});
sizeVal.inputs[0] = {getValue: () => 1};
graph.addNode(sizeVal);
graph.addNode(size);
graph.addWire(new Wire(sizeVal, 0, size, 0));

const rotationValue = new Node(NodeTypes['number'], 0);
const rotation = new Node(NodeTypes['particleProperty'], 0, {property: 'rotation', type: NodeValueType.Number});
rotationValue.inputs[0] = {getValue: () => 1};
graph.addNode(rotationValue);
graph.addNode(rotation);
graph.addWire(new Wire(rotationValue, 0, rotation, 0));

const colorVec = new Node(NodeTypes['vec4'], 0);
const color = new Node(NodeTypes['particleProperty'], 0, {property: 'color', type: NodeValueType.Vec4});
colorVec.inputs[0] = {getValue: () => 1};
colorVec.inputs[1] = {getValue: () => 1};
colorVec.inputs[2] = {getValue: () => 1};
colorVec.inputs[3] = {getValue: () => 0.5};
graph.addNode(colorVec);
graph.addNode(color);
graph.addWire(new Wire(colorVec, 0, color, 0));

const age = new Node(NodeTypes['particleProperty'], 0, {property: 'age', type: NodeValueType.Number});
graph.addNode(age);
const cos = new Node(NodeTypes['cos'], 0);
graph.addNode(cos);
const pos = new Node(NodeTypes['vec3'], 0);
pos.inputs[0] = {getValue: () => 0};
pos.inputs[1] = {getValue: () => 1};
pos.inputs[2] = {getValue: () => 1};
graph.addNode(pos);
graph.addWire(new Wire(age, 0, cos, 0));
graph.addWire(new Wire(cos, 0, pos, 0));

const pos2 = new Node(NodeTypes['vec3'], 0);
pos2.inputs[0] = {getValue: () => 1};
pos2.inputs[1] = {getValue: () => 0.5};
pos2.inputs[2] = {getValue: () => 0.5};
graph.addNode(pos2);
const add = new Node(NodeTypes['add'], 2);
graph.addNode(add);
graph.addWire(new Wire(pos, 0, add, 0));
graph.addWire(new Wire(pos2, 0, add, 1));

const ppos = new Node(NodeTypes['particleProperty'], 0, {property: 'position', type: NodeValueType.Vec3});
graph.addNode(ppos);
const pvel = new Node(NodeTypes['particleProperty'], 0, {property: 'velocity', type: NodeValueType.Vec3});
graph.addNode(pvel);
graph.addWire(new Wire(add, 0, ppos, 0));
graph.addWire(new Wire(pos2, 0, pvel, 0));

const compiler = new WebGPUCompiler();
const particle = { position: new Vector3(), velocity: new Vector3(), age: 10 };
const graphContext = { particle: particle };

const code = compiler.build(graph, graphContext);
console.log(code);
console.log(compiler.particleInstanceByteSize);

const debug = false;
const count = 64;
const canvas = document.getElementById('renderer-canvas');
const renderer = new WebGPURenderer(
context,
canvas,
count,
compiler.particleInstanceByteSize,
code,
debug,
renderCode
);
const camera = new PerspectiveCamera(
(2 * Math.PI) / 5 * 180 / Math.PI,
canvas.width / canvas.height,
1,
100.0
);
camera.position.z = 10;
camera.rotateX(Math.PI * 0.05);
camera.updateMatrixWorld(true);
camera.updateProjectionMatrix();
console.log(camera.matrixWorldInverse.elements);
console.log(camera.projectionMatrix.elements);
console.log(renderer.mvp.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse).elements);

let frameCount = 0;
function animate() {
renderer.frame(camera);

if (frameCount % 20 === 0 && debug) {
renderer.cpuReadableBuffer.mapAsync(
GPUMapMode.READ,
0, // Offset
count * compiler.particleInstanceByteSize, // Length
).then(() => {
const copyArrayBuffer = renderer.cpuReadableBuffer.getMappedRange(0, count * compiler.particleInstanceByteSize);
const data = copyArrayBuffer.slice(0);
renderer.cpuReadableBuffer.unmap();
console.log(new Float32Array(data))
});
}
frameCount ++;
requestAnimationFrame(animate);
};
requestAnimationFrame(animate);
//document.body.appendChild(renderer.domElement);
});
14 changes: 14 additions & 0 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,25 @@ const production = process.env.NODE_ENV === 'production';
const globals = {three: 'THREE'};
const extensions = ['.js', '.jsx', '.ts', '.tsx'];

function wgslPlugin() {
return {
name: 'wgsl-plugin',
transform(code, id) {
if (id.endsWith('.wgsl')) {
return {
code: `export default \`${code}\`;`,
map: { mappings: '' },
};
}
},
};
}
export const lib = {
main: {
input: 'src/index.ts',
external: Object.keys(globals),
plugins: [
//wgslPlugin(),
resolve({
extensions: extensions,
customResolveOptions: {
Expand Down
8 changes: 0 additions & 8 deletions src/WebGPUDriver.ts

This file was deleted.

Loading

0 comments on commit 1f26ab7

Please sign in to comment.