Created
November 14, 2024 23:08
-
-
Save sketchpunk/a282edc9a11fd16233fabb3662e66dd1 to your computer and use it in GitHub Desktop.
Basic Template for using ThreeJS's TSL Compute with WebGL / GSLS Backend
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html><html lang="en"><head><title></title></head> | |
<style>canvas{ display:block; } body, html { padding:0px; margin:0px; width:100%; height:100%; }</style> | |
<body><script type="module"> | |
// #region IMPORTS | |
import useForcedWebGL, { THREE, useDarkScene } from '../lib/useForcedWebGL.js'; | |
// #endregion | |
// #region MAIN | |
let App = useDarkScene( useForcedWebGL() ); | |
let Debug = {}; | |
window.addEventListener( 'load', async ()=>{ | |
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
await App.renderer.init(); | |
App.sphericalLook( 0, 20, 6 ); | |
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
const data = new Float32Array( [ 1,2,3, 4,5,6 ] ); // Raw Data | |
const compCnt = 3; // Component Count: Vec3 has 3 Components | |
const elmCnt = data.length / compCnt; // How many elements in data, Twp Vec3s | |
// Create GPU buffer space to store data | |
const bufAttr = new THREE.StorageInstancedBufferAttribute( data, compCnt ); | |
// Create a TSL node as an Accessor for the buffer | |
// NOTE: .label doesn't work to name variable in GLSL | |
const nBuf = THREE.storage( bufAttr, 'vec3', data.length ); | |
// ----------------------------------------- | |
// Create compute shader using TSL instead of GLSL | |
// Create a pile of GLSL code we want to include | |
const nCode = THREE.code( '#define XX 10.0'); | |
// Create a single GLSL function | |
// NOTE: glslFn allows for importing, we use this function as a way to include a big pile of code | |
// into the compute shader. Its a lil hacky but its the only way I found to do it when using Fn as | |
// your main execution object | |
const nTestFunc = THREE.glslFn(`vec3 testFunc( vec3 a ){ return a + vec3( 1.0 ) * XX; }`, [ nCode ] ); | |
// Create our main compute function | |
const fnCompute = THREE.Fn( ()=>{ | |
// NOTE: Examples use element, but its not really needed. | |
// const attr = nBuf.element( THREE.instanceIndex ).label( 'attr' ); | |
const attr = nBuf; // Dont use ".toVar", assign won't work correctly if so | |
const offset = THREE.vec3( 1,2,3 ).toVar( 'offset' ); // Create const vec3 | |
const val = attr.add( offset ).toVar( 'val'); // Input + Offset | |
const val2 = nTestFunc( val ).toVar( 'val2' ); // Change val with custom GLSL function | |
// The attribute is used as both Input and Output, so save final value | |
// to the buffer/attribute/varying tsl node | |
attr.assign( val2 ); | |
} ); | |
// ----------------------------------------- | |
// Create a Compute Node | |
const nCompute = fnCompute().compute( elmCnt ); | |
// Execute Compute node | |
await App.renderer.computeAsync( nCompute ); | |
// Transfer Byte Array from GPU into a Float Array on the CPU | |
const result = new Float32Array( await App.renderer.getArrayBufferAsync( bufAttr ) ); | |
console.log( result ); | |
// ----------------------------------------- | |
// Hack into THREEJS's new WebGL Backend to extract the GLSL generated from Compute Node | |
console.log( App.renderer._pipelines.nodes.getForCompute( nCompute ).computeShader ); | |
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
// App.createRenderLoop( onPreRender ).start(); | |
App.renderLoop(); | |
}); | |
function onPreRender( dt, et ){} | |
// #endregion | |
</script></body></html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// #region IMPORTS | |
// NOTE: "three" must map to three.webgpu.js or .min.js globally to prevent | |
// certain errors/warnings from showing up | |
import * as THREE from 'three'; | |
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; | |
export { THREE }; | |
// #endregion | |
// #region OPTIONS | |
export function useDarkScene( tjs, props={} ){ | |
const pp = Object.assign( { ambient:0x404040, grid:true }, props ); | |
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
// Light | |
const light = new THREE.DirectionalLight( 0xffffff, 1.0 ); | |
light.position.set( 4, 10, 1 ); | |
tjs.scene.add( light ); | |
tjs.scene.add( new THREE.AmbientLight( pp.ambient ) ); | |
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
// Floor : For grid to work correctly, need import from three needs to globally point to gpu version | |
// else there will be errors about grid material not supporting NodeMaterial | |
if( pp.grid ) tjs.scene.add( new THREE.GridHelper( 20, 20, 0x0c610c, 0x444444 ) ); | |
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
// Renderer | |
tjs.renderer.setClearColor( 0x3a3a3a, 1 ); | |
return tjs; | |
}; | |
// #endregion | |
export default function useForcedWebGL( props ){ | |
props = Object.assign( { | |
// colorMode : false, | |
// shadows : false, | |
preserverBuffer : false, | |
power : '', | |
}, props ); | |
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
// RENDERER | |
const options = { | |
forceWebGL : true, | |
antialias : true, | |
preserveDrawingBuffer : props.preserverBuffer, | |
powerPreference : ( props.power === '') ? 'default' : | |
( props.power === 'high' ) ? 'high-performance' : 'low-power', | |
}; | |
const canvas = document.createElement( 'canvas' ); | |
options.canvas = canvas; | |
options.context = canvas.getContext( 'webgl2', { preserveDrawingBuffer: props.preserverBuffer } ); | |
const renderer = new THREE.WebGPURenderer( options ); | |
renderer.setPixelRatio( Math.max( 1, window.devicePixelRatio ) ); | |
renderer.setClearColor( 0x3a3a3a, 1 ); | |
renderer.toneMapping = THREE.ACESFilmicToneMapping; | |
// renderer.setAnimationLoop( animation ); | |
document.body.appendChild( renderer.domElement ); | |
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
// CORE | |
const scene = new THREE.Scene(); | |
const clock = new THREE.Clock(); | |
clock.start(); | |
const camera = new THREE.PerspectiveCamera( 45, 1.0, 0.01, 1000 ); | |
camera.position.set( 0, 5, 20 ); | |
const camCtrl = new OrbitControls( camera, renderer.domElement ); | |
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
// METHODS | |
let self; // Need to declare before methods for it to be useable | |
const render = ( onPreRender=null, onPostRender=null ) =>{ | |
const deltaTime = clock.getDelta(); | |
const ellapseTime = clock.getElapsedTime(); | |
if( onPreRender ) onPreRender( deltaTime, ellapseTime ); | |
renderer.render( scene, camera ); | |
if( onPostRender ) onPostRender( deltaTime, ellapseTime ); | |
return self; | |
}; | |
const renderLoop = ()=>{ | |
window.requestAnimationFrame( renderLoop ); | |
render(); | |
return self; | |
}; | |
const createRenderLoop = ( fnPreRender=null, fnPostRender=null )=>{ | |
let reqId = 0; | |
const rLoop = { | |
stop : ()=>window.cancelAnimationFrame( reqId ), | |
start : ()=>rLoop.onRender(), | |
onRender : ()=>{ | |
render( fnPreRender, fnPostRender ); | |
reqId = window.requestAnimationFrame( rLoop.onRender ); | |
}, | |
}; | |
return rLoop; | |
}; | |
const sphericalLook = ( lon, lat, radius, target=null )=>{ | |
const phi = ( 90 - lat ) * Math.PI / 180; | |
const theta = ( lon + 180 ) * Math.PI / 180; | |
camera.position.set( | |
-(radius * Math.sin( phi ) * Math.sin(theta)), | |
radius * Math.cos( phi ), | |
-(radius * Math.sin( phi ) * Math.cos(theta)) | |
); | |
if( target ) camCtrl.target.fromArray( target ); | |
camCtrl.update(); | |
return self; | |
}; | |
const resize = ( w=0, h=0 )=>{ | |
const W = w || window.innerWidth; | |
const H = h || window.innerHeight; | |
renderer.setSize( W, H ); // Update Renderer | |
camera.aspect = W / H; // Update Camera | |
camera.updateProjectionMatrix(); | |
return self; | |
}; | |
const getBufferSize = ()=>{ return renderer.getDrawingBufferSize( new THREE.Vector2() ).toArray(); }; | |
const debugMaterial = ( mesh )=>{ return renderer.debug.getShaderAsync( scene, camera, mesh ); } | |
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
window.addEventListener( 'resize', ()=>resize() ); | |
resize(); | |
return self = { | |
renderer, | |
scene, | |
camera, | |
camCtrl, | |
render, | |
renderLoop, | |
createRenderLoop, | |
sphericalLook, | |
resize, | |
getBufferSize, | |
debugMaterial, | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment