Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,7 @@
"webgpu_sky",
"webgpu_sprites",
"webgpu_storage_buffer",
"webgpu_struct_drawIndirect",
"webgpu_texturegrad",
"webgpu_textures_2d-array",
"webgpu_textures_2d-array_compressed",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
267 changes: 267 additions & 0 deletions examples/webgpu_struct_drawIndirect.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgpu - gltf loader</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
</head>
<body>

<div id="info">
<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> webgpu - struct drawIndirect<br />
</div>

<script type="importmap">
{
"imports": {
"three": "../build/three.webgpu.js",
"three/tsl": "../build/three.webgpu.js",
"three/addons/": "./jsm/"
}
}
</script>

<script type="module">

import * as THREE from 'three';
import { struct, storageStruct, wgslFn, instanceIndex, time, varyingProperty, attribute } from 'three/tsl';

import { OrbitControls } from 'three/addons/controls/OrbitControls.js';


const renderer = new THREE.WebGPURenderer({ antialias: true });
renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor( 0x000000 );
renderer.setClearAlpha( 0 );
document.body.appendChild( renderer.domElement );

const aspect = window.innerWidth / window.innerHeight;

const camera = new THREE.PerspectiveCamera( 50.0, aspect, 0.1, 10000 );
const scene = new THREE.Scene();

scene.background = new THREE.Color( 0x00001f );
camera.position.set( 1, 1, 1 );
const controls = new OrbitControls( camera, renderer.domElement );


let computeDrawBuffer, computeInitDrawBuffer;

await init();
await render();

async function init() {

await renderer.init();

// geometry

const vector = new THREE.Vector4();

const instances = 100000;

const positions = [];
const offsets = [];
const colors = [];
const orientationsStart = [];
const orientationsEnd = [];

positions.push( 0.025, - 0.025, 0 );
positions.push( - 0.025, 0.025, 0 );
positions.push( 0, 0, 0.025 );

// instanced attributes

for ( let i = 0; i < instances; i ++ ) {

// offsets

offsets.push( Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5 );

// colors

colors.push( Math.random(), Math.random(), Math.random(), Math.random() );

// orientation start

vector.set( Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1 );
vector.normalize();

orientationsStart.push( vector.x, vector.y, vector.z, vector.w );

// orientation end

vector.set( Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1 );
vector.normalize();

orientationsEnd.push( vector.x, vector.y, vector.z, vector.w );

}

const geometry = new THREE.InstancedBufferGeometry();
geometry.instanceCount = instances;

geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
geometry.setAttribute( 'offset', new THREE.InstancedBufferAttribute( new Float32Array( offsets ), 3 ) );
geometry.setAttribute( 'color', new THREE.InstancedBufferAttribute( new Float32Array( colors ), 4 ) );
geometry.setAttribute( 'orientationStart', new THREE.InstancedBufferAttribute( new Float32Array( orientationsStart ), 4 ) );
geometry.setAttribute( 'orientationEnd', new THREE.InstancedBufferAttribute( new Float32Array( orientationsEnd ), 4 ) );


const drawBuffer = new THREE.IndirectStorageBufferAttribute( new Uint32Array( 5 ), 5 );
geometry.setIndirect( drawBuffer );

const drawBufferStruct = struct( {
vertexCount: 'uint',
instanceCount: { type: 'uint', atomic: true },
firstVertex: 'uint',
firstInstance: 'uint',
offset: 'uint'
} );

const writeDrawBuffer = wgslFn(`
fn compute(
index: u32,
drawBuffer: ptr<storage, DrawBuffer, read_write>,
instances: f32,
time: f32,
) -> void {

var instanceCount = instances * pow( sin( time * 0.25 ), 4.0 );

atomicStore( &drawBuffer.instanceCount, u32( instanceCount ) );
}
`);

computeDrawBuffer = writeDrawBuffer( {
drawBuffer: storageStruct( drawBuffer, drawBufferStruct, drawBuffer.count ),
instances: instances,
index: instanceIndex,
time: time
} ).compute( instances ); //not neccessary in this case but normally one wants to run through all instances



const initDrawBuffer = wgslFn(`
fn compute(
drawBuffer: ptr<storage, DrawBuffer, read_write>,
) -> void {

drawBuffer.vertexCount = 3u;
atomicStore(&drawBuffer.instanceCount, 0u);
drawBuffer.firstVertex = 0u;
drawBuffer.firstInstance = 0u;
drawBuffer.offset = 0u;
}
`);

computeInitDrawBuffer = initDrawBuffer( {
drawBuffer: storageStruct( drawBuffer, drawBufferStruct, drawBuffer.count ),
} ).compute( 1 );



const vPosition = varyingProperty( "vec3", "vPosition" );
const vColor = varyingProperty( "vec4", "vColor" );

const positionShaderParams = {
position: attribute( "position" ),
offset: attribute( "offset" ),
color: attribute( "color" ),
orientationStart: attribute( "orientationStart" ),
orientationEnd: attribute( "orientationEnd" ),
time: time
};

const positionShader = wgslFn(`
fn main_vertex(
position: vec3<f32>,
offset: vec3<f32>,
color: vec4<f32>,
orientationStart: vec4<f32>,
orientationEnd: vec4<f32>,
time: f32
) -> vec4<f32> {

var vPosition = offset * max( abs( sin( time * 0.5 ) * 2.0 + 1.0 ), 0.5 ) + position;
var orientation = normalize( mix( orientationStart, orientationEnd, sin( time * 0.5 ) ) );
var vcV = cross( orientation.xyz, vPosition );
vPosition = vcV * ( 2.0 * orientation.w ) + ( cross( orientation.xyz, vcV ) * 2.0 + vPosition );

var vColor = color;

var outPosition = vec4f(vPosition, 1);

varyings.vPosition = vPosition;
varyings.vColor = vColor;

return outPosition;
}
`, [vPosition, vColor] );


const fragmentShaderParams = {
time: time,
vPosition: vPosition,
vColor: vColor
};

const fragmentShader = wgslFn( `
fn main_fragment(
time: f32,
vPosition: vec3<f32>,
vColor: vec4<f32>
) -> vec4<f32> {

var color = vec4f( vColor );
color.r += sin( vPosition.x * 10.0 + time ) * 0.5;

return color;
}
`);

const material = new THREE.MeshBasicNodeMaterial( {
side: THREE.DoubleSide,
forceSinglePass: true,
transparent: true
} );

material.positionNode = positionShader( positionShaderParams );
material.fragmentNode = fragmentShader( fragmentShaderParams );

const mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );

window.addEventListener( "resize", onWindowResize, false );

}


async function render() {

requestAnimationFrame( render );

controls.update();

renderer.render( scene, camera );

await renderer.computeAsync( computeInitDrawBuffer );
await renderer.computeAsync( computeDrawBuffer );

}

function onWindowResize() {

camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );

}

</script>

</body>
</html>
1 change: 1 addition & 0 deletions src/nodes/TSL.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export * from './core/IndexNode.js';
export * from './core/ParameterNode.js';
export * from './core/PropertyNode.js';
export * from './core/StackNode.js';
export * from './core/StructTypeNode.js';
export * from './core/UniformGroupNode.js';
export * from './core/UniformNode.js';
export * from './core/VaryingNode.js';
Expand Down
10 changes: 10 additions & 0 deletions src/nodes/accessors/StorageBufferNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class StorageBufferNode extends BufferNode {
this.isAtomic = false;

this.bufferObject = false;
this.bufferStruct = false;
this.bufferCount = bufferCount;

this._attribute = null;
Expand Down Expand Up @@ -84,6 +85,14 @@ class StorageBufferNode extends BufferNode {

}

setBufferStruct( value ) {

this.bufferStruct = value;

return this;

}

setAccess( value ) {

this.access = value;
Expand Down Expand Up @@ -166,4 +175,5 @@ export default StorageBufferNode;

// Read-Write Storage
export const storage = ( value, type, count ) => nodeObject( new StorageBufferNode( value, type, count ) );
export const storageStruct = ( value, type, count ) => nodeObject( new StorageBufferNode( value, type, count ).setBufferStruct( true ) );
export const storageObject = ( value, type, count ) => nodeObject( new StorageBufferNode( value, type, count ).setBufferObject( true ) );
16 changes: 16 additions & 0 deletions src/nodes/core/StructTypeNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,19 @@ class StructTypeNode extends Node {
}

export default StructTypeNode;

export const struct = ( members ) => {

return Object.entries( members ).map( ( [ name, value ] ) => {

if ( typeof value === 'string' ) {

return { name, type: value, isAtomic: false };

}

return { name, type: value.type, isAtomic: value.atomic || false };

} );

};
Loading