├── .DS_Store ├── assets ├── .DS_Store ├── drop.png ├── drop2.png ├── codrops.png ├── drop120.png └── codropsFont.ttf ├── dist ├── .DS_Store ├── assets │ └── codropsFont-CTCx6u_f.ttf └── index.html ├── vite.config.js ├── simulation ├── textureClear.wgsl ├── PBF_integrateVelocity.wgsl ├── PBF_calculateDisplacements.wgsl ├── PBF_applyForces.wgsl └── PBF.js ├── marchingCubes ├── EncodeBuffer.wgsl ├── MarchCase.wgsl ├── GenerateTriangles.wgsl ├── TrianglesGenerator.js └── marchingCubesTables.js ├── README.md ├── mipmaps ├── MipMapCompute.wgsl └── CalculateMipMap.js ├── package.json ├── unrealBloom ├── UnrealBloomLuminosity.wgsl ├── UnrealBloomGaussian.wgsl ├── UnrealBloomComposite.wgsl └── UnrealBloom.js ├── rendering ├── LetterGenerator.js ├── BlurOffset.wgsl ├── Postprocessing.wgsl ├── RenderBackground.wgsl ├── RenderComposite.wgsl └── RenderMC.wgsl ├── blur3D ├── Blur3D.wgsl └── Blur3D.js ├── index.html ├── utils ├── shaderParser.js ├── camera.js └── utils.js └── Main.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HectorArellanoDev/WebGPUFluids/HEAD/.DS_Store -------------------------------------------------------------------------------- /assets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HectorArellanoDev/WebGPUFluids/HEAD/assets/.DS_Store -------------------------------------------------------------------------------- /assets/drop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HectorArellanoDev/WebGPUFluids/HEAD/assets/drop.png -------------------------------------------------------------------------------- /assets/drop2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HectorArellanoDev/WebGPUFluids/HEAD/assets/drop2.png -------------------------------------------------------------------------------- /dist/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HectorArellanoDev/WebGPUFluids/HEAD/dist/.DS_Store -------------------------------------------------------------------------------- /assets/codrops.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HectorArellanoDev/WebGPUFluids/HEAD/assets/codrops.png -------------------------------------------------------------------------------- /assets/drop120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HectorArellanoDev/WebGPUFluids/HEAD/assets/drop120.png -------------------------------------------------------------------------------- /assets/codropsFont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HectorArellanoDev/WebGPUFluids/HEAD/assets/codropsFont.ttf -------------------------------------------------------------------------------- /dist/assets/codropsFont-CTCx6u_f.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HectorArellanoDev/WebGPUFluids/HEAD/dist/assets/codropsFont-CTCx6u_f.ttf -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | 2 | // import glsl from 'vite-plugin-glsl'; 3 | 4 | // export default { 5 | // esbuild: { 6 | // supported: { 7 | // 'top-level-await': true //browsers can handle top-level-await features 8 | // }, 9 | // }, 10 | // plugins: [glsl()] 11 | // } -------------------------------------------------------------------------------- /simulation/textureClear.wgsl: -------------------------------------------------------------------------------- 1 | 2 | 3 | struct Uniforms { 4 | lightIntensity: f32 5 | } 6 | 7 | @group(0) @binding(0) var texture3D: texture_storage_3d; 8 | @group(0) @binding(1) var uniforms: Uniforms; 9 | 10 | 11 | @compute @workgroup_size(1, 1, 1) fn main( @builtin(global_invocation_id) id: vec3 ) { 12 | 13 | textureStore(texture3D, id, vec4f(0.)); 14 | 15 | } 16 | 17 | -------------------------------------------------------------------------------- /marchingCubes/EncodeBuffer.wgsl: -------------------------------------------------------------------------------- 1 | @group(0) @binding(0) var voxelsBufferInput: array; 2 | @group(0) @binding(1) var voxelsBuffer: array; 3 | 4 | @compute @workgroup_size(1) fn main(@builtin(global_invocation_id) id: vec3u) { 5 | 6 | let totalVoxels = f32(voxelsBufferInput[0]); 7 | voxelsBuffer[0] = i32(ceil( totalVoxels / 15. )); 8 | voxelsBuffer[1] = 1; 9 | voxelsBuffer[2] = 1; 10 | 11 | voxelsBuffer[3] = i32(15. * totalVoxels); 12 | voxelsBuffer[4] = 1; 13 | voxelsBuffer[5] = 0; 14 | voxelsBuffer[6] = 0; 15 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebGPU Fluid Demo for Codrops 2 | 3 | A demo that accompanies the article about the reflective journey through years of experimentation to create fluid simulations. 4 | 5 | ![Image](https://tympanus.net/codrops/wp-content/uploads/2025/01/image-2025-01-29-at-10.30.25.jpg) 6 | 7 | [Article on Codrops](https://tympanus.net/codrops/?p=84103) 8 | 9 | [Demo](https://tympanus.net/Tutorials/WebGPUFluid/) 10 | 11 | There is also a [basic version](https://github.com/HectorArellanoDev/CodropsBasic) that runs faster on less powerful machines. 12 | 13 | ## Installation 14 | 15 | Install dependencies: 16 | 17 | ``` 18 | npm install 19 | ``` 20 | 21 | Compile the code for development and start a local server: 22 | 23 | ``` 24 | npm run dev 25 | ``` 26 | 27 | 28 | -------------------------------------------------------------------------------- /mipmaps/MipMapCompute.wgsl: -------------------------------------------------------------------------------- 1 | 2 | @group(0) @binding(0) var textureRead: texture_3d; 3 | @group(0) @binding(1) var textureSave: texture_storage_3d; 4 | 5 | @compute @workgroup_size(1) fn main( @builtin(global_invocation_id) id: vec3 ) { 6 | 7 | var result = vec4f(0., 0., 0., 0.); 8 | 9 | for(var i = 0; i < 2; i ++) { 10 | for(var j = 0; j < 2; j ++) { 11 | for(var k = 0; k < 2; k ++) { 12 | result += textureLoad(textureRead, 2 * vec3(id) + vec3(i, j, k), 0); 13 | } 14 | } 15 | } 16 | 17 | result.x /= 8.; 18 | result.y /= 8.; 19 | result.z /= 8.; 20 | result.w /= 8.; 21 | 22 | let tSize = f32(textureDimensions(textureSave).x); 23 | textureStore(textureSave, id, result ); 24 | 25 | } 26 | 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webgpu-exploration", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "Main.js", 6 | "directories": { 7 | "test": "tests" 8 | }, 9 | "scripts": { 10 | "dev": "npx serve .", 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/activetheory/webgpu-exploration.git" 16 | }, 17 | "author": "", 18 | "license": "ISC", 19 | "bugs": { 20 | "url": "https://github.com/activetheory/webgpu-exploration/issues" 21 | }, 22 | "homepage": "https://github.com/activetheory/webgpu-exploration#readme", 23 | "dependencies": { 24 | "dat.gui": "^0.7.9", 25 | "gl-matrix": "^3.4.3", 26 | "serve": "^14.2.0" 27 | }, 28 | "devDependencies": { 29 | "vite-plugin-glsl": "^1.3.1" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /unrealBloom/UnrealBloomLuminosity.wgsl: -------------------------------------------------------------------------------- 1 | 2 | struct Uniforms { 3 | defaultColor: vec3f, 4 | defaultOpacity: f32, 5 | luminosityThreshold: f32, 6 | smoothWidth: f32 7 | } 8 | 9 | @group(0) @binding(0) var uniforms: Uniforms; 10 | @group(0) @binding(1) var tDiffuse: texture_2d; 11 | @group(0) @binding(2) var textureSampler: sampler; 12 | @group(0) @binding(3) var outputTexture: texture_storage_2d; 13 | 14 | fn luma(color: vec3f) -> f32 { 15 | return dot(color, vec3f(0.299, 0.587, 0.114)); 16 | } 17 | 18 | @compute @workgroup_size(1, 1) fn main(@builtin(global_invocation_id) id: vec3u) { 19 | 20 | var textDim = textureDimensions(tDiffuse).xy; 21 | var st = vec2f(id.xy) / vec2f(textDim); 22 | var texel = textureSampleLevel(tDiffuse, textureSampler, st, 0); 23 | var v = luma(texel.xyz); 24 | var outputColor = vec4f(uniforms.defaultColor, uniforms.defaultOpacity); 25 | var alpha = smoothstep(uniforms.luminosityThreshold, uniforms.luminosityThreshold + uniforms.smoothWidth, v); 26 | 27 | textureStore(outputTexture, id.xy, mix(outputColor, texel, vec4f(alpha))); 28 | 29 | } 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /rendering/LetterGenerator.js: -------------------------------------------------------------------------------- 1 | var canvas = document.createElement("canvas"); 2 | var ctx = canvas.getContext("2d", { 3 | willReadFrequently: true 4 | }); 5 | canvas.style.position = "absolute"; 6 | canvas.style.top = "0px"; 7 | 8 | // document.body.appendChild(canvas); 9 | 10 | function getLetter(letter, fontSize, resolution, verticalOffset = 0.56, image = null) { 11 | 12 | canvas.width = resolution; 13 | canvas.height = resolution; 14 | canvas.style.width = `${resolution}px`; 15 | canvas.style.height = `${resolution}px`; 16 | 17 | if(image == null) { 18 | 19 | ctx.fillStyle = "black"; 20 | ctx.fillRect(0, 0, canvas.width, canvas.height); 21 | 22 | ctx.fillStyle = "white"; 23 | ctx.textAlign = "center"; 24 | ctx.font = `${fontSize}px codrops`; 25 | ctx.fillText(letter, resolution * 0.5, resolution * 0.5 + fontSize * verticalOffset); 26 | 27 | } else { 28 | 29 | ctx.drawImage(image, 0, 0); 30 | } 31 | 32 | return ctx.getImageData(0, 0, resolution, resolution).data; 33 | } 34 | 35 | export {getLetter} -------------------------------------------------------------------------------- /blur3D/Blur3D.wgsl: -------------------------------------------------------------------------------- 1 | 2 | struct Uniforms { 3 | axis: vec3f, 4 | steps: f32 5 | } 6 | 7 | @group(0) @binding(0) var textureRead: texture_3d; 8 | @group(0) @binding(1) var textureSave: texture_storage_3d; 9 | @group(0) @binding(2) var uniforms: Uniforms; 10 | 11 | 12 | @compute @workgroup_size(1) fn main( @builtin(global_invocation_id) ud: vec3 ) { 13 | 14 | 15 | var tSize = vec3f(textureDimensions(textureRead)); 16 | var id = ud + vec3u( u32(tSize.x * 0.2), 0, 0); 17 | 18 | 19 | var blend = vec4f(0.); 20 | var blend2 = vec4f(0.); 21 | var sum = 1.; 22 | var sum2 = 0.; 23 | var m = 1.; 24 | var n = uniforms.steps; 25 | for(var i = 0.; i < uniforms.steps; i += 1.) { 26 | var j = i - 0.5 * uniforms.steps; 27 | var tRead = textureLoad(textureRead, vec3(id) + vec3(j * uniforms.axis), 0); 28 | blend += m * tRead; 29 | blend2 += tRead; 30 | m *= (n - i) / (i + 1.); 31 | sum += m; 32 | sum2 += 1.; 33 | } 34 | 35 | blend /= sum; 36 | blend2 /= sum2; 37 | 38 | var mixer = 1.; 39 | blend = mixer * blend + (1. - mixer) * blend2; 40 | 41 | textureStore(textureSave, id, blend ); 42 | 43 | } 44 | 45 | -------------------------------------------------------------------------------- /rendering/BlurOffset.wgsl: -------------------------------------------------------------------------------- 1 | struct Uniforms { 2 | direction: vec2f 3 | } 4 | 5 | @group(0) @binding(0) var uniforms: Uniforms; 6 | @group(0) @binding(1) var textureBase: texture_2d; 7 | @group(0) @binding(2) var textureIn: texture_2d; 8 | @group(0) @binding(3) var textureOut: texture_storage_2d; 9 | 10 | 11 | @compute @workgroup_size(16, 16) fn main(@builtin(global_invocation_id) id: vec3u) { 12 | 13 | var data = textureLoad(textureIn, id.xy, 0); 14 | 15 | if(length(data.rgb) == 0.) { 16 | 17 | var h = 0.; 18 | var mirror = 0.; 19 | for(var i : u32 = 1; i <= 40; i ++) { 20 | 21 | var data1 = textureLoad(textureIn, id.xy + vec2u(uniforms.direction) * i, 0); 22 | var data2 = textureLoad(textureIn, id.xy - vec2u(uniforms.direction) * i, 0); 23 | 24 | if((data1.r > 0. || data2.r > 0.) && (data1.g > 0. || data2.g > 0.) && i32(id.y) - i32(i) > 0) { 25 | h = max(data1.r, data2.r); 26 | mirror = 1.; 27 | break; 28 | } 29 | } 30 | 31 | textureStore(textureOut, id.xy, vec4f(h, mirror, 0., 1.) ); 32 | 33 | } else { 34 | 35 | textureStore(textureOut, id.xy, data ); 36 | 37 | } 38 | 39 | 40 | } -------------------------------------------------------------------------------- /unrealBloom/UnrealBloomGaussian.wgsl: -------------------------------------------------------------------------------- 1 | 2 | struct Uniforms { 3 | textSize: vec2f, 4 | direction: vec2f, 5 | sigma: f32 6 | } 7 | 8 | @group(0) @binding(0) var uniforms: Uniforms; 9 | @group(0) @binding(1) var colorTexture: texture_2d; 10 | @group(0) @binding(2) var textureSampler: sampler; 11 | @group(0) @binding(3) var outputTexture: texture_storage_2d; 12 | 13 | 14 | fn gaussianPdf(x: f32, sigma: f32) -> f32{ 15 | return 0.39894 * exp(-0.5 * x * x / (sigma * sigma)) / sigma; 16 | } 17 | 18 | @compute @workgroup_size(1, 1) fn main(@builtin(global_invocation_id) id: vec3u) { 19 | 20 | var st = vec2f(id.xy) / uniforms.textSize; 21 | 22 | var invSize = 1.0 / uniforms.textSize; 23 | var fSigma = uniforms.sigma; 24 | var weightSum = gaussianPdf(0.0, fSigma); 25 | var diffuseSum = textureSampleLevel( colorTexture, textureSampler, st, 0).rgb * weightSum; 26 | 27 | for(var i = 1; i < i32(uniforms.sigma); i ++) { 28 | var x = f32(i); 29 | var w = gaussianPdf(x, fSigma); 30 | var uvOffset = uniforms.direction * invSize * x; 31 | var sample1 = textureSampleLevel( colorTexture, textureSampler, st + uvOffset, 0).rgb; 32 | var sample2 = textureSampleLevel( colorTexture, textureSampler, st - uvOffset, 0).rgb; 33 | diffuseSum += (sample1 + sample2) * w; 34 | weightSum += 2.0 * w; 35 | } 36 | 37 | textureStore(outputTexture, id.xy, vec4f(diffuseSum / weightSum, 1.0) ); 38 | 39 | } 40 | -------------------------------------------------------------------------------- /rendering/Postprocessing.wgsl: -------------------------------------------------------------------------------- 1 | struct Uniforms { 2 | direction: vec2f, 3 | deltaTime: f32, 4 | motionBlur: f32 5 | } 6 | 7 | @group(0) @binding(0) var texture: texture_2d; 8 | @group(0) @binding(1) var textureVel: texture_2d; 9 | @group(0) @binding(2) var textureSampler: sampler; 10 | @group(0) @binding(3) var uniforms: Uniforms; 11 | @group(0) @binding(4) var outputTexture: texture_storage_2d; 12 | 13 | 14 | @compute @workgroup_size(16, 16) fn main(@builtin(global_invocation_id) id: vec3u) { 15 | 16 | var dimensions = textureDimensions(texture).xy; 17 | var tSize = vec2f(f32(dimensions.x), f32(dimensions.y)); 18 | var uv = vec2f(id.xy) / tSize; 19 | 20 | var data = textureSampleLevel(textureVel, textureSampler, uv, 0); 21 | var color = vec4(0.); 22 | var color2 = vec4(0.); 23 | var sum = 1.; 24 | var sum2 = 0.; 25 | var m = 1.; 26 | var n = 2. + data.g * min(floor(200. * data.r), 100.); 27 | var steps = i32(n); 28 | 29 | for(var i = 0; i <= steps; i ++) { 30 | var k = f32(i); 31 | var j = k - 0.5 * f32(steps); 32 | var tRead = textureSampleLevel(texture, textureSampler, uv + uniforms.direction * j / tSize, 0); 33 | color += m * tRead; 34 | color2 += tRead; 35 | m *= (n - k) / (k + 1.); 36 | sum += m; 37 | sum2 += 1.; 38 | } 39 | 40 | color /= sum; 41 | color2 /= sum2; 42 | 43 | var mixer = select(.1, 0.5, data.g > 0.); 44 | color = mixer * color + (1. - mixer) * color2; 45 | 46 | textureStore(outputTexture, id.xy, color ); 47 | } -------------------------------------------------------------------------------- /unrealBloom/UnrealBloomComposite.wgsl: -------------------------------------------------------------------------------- 1 | struct Uniforms { 2 | bloomTintColor: vec3f, 3 | bloomStrength: f32, 4 | bloomRadius: f32, 5 | } 6 | 7 | @group(0) @binding(0) var uniforms: Uniforms; 8 | @group(0) @binding(1) var textureSampler: sampler; 9 | 10 | //Depends on the NMips... 11 | @group(0) @binding(2) var blurTexture1: texture_2d; 12 | @group(0) @binding(3) var blurTexture2: texture_2d; 13 | @group(0) @binding(4) var blurTexture3: texture_2d; 14 | 15 | @group(0) @binding(5) var inputTexture: texture_2d; 16 | @group(0) @binding(6) var outputTexture: texture_storage_2d; 17 | 18 | 19 | fn lerpBloomFactor(factor: f32) -> f32 { 20 | var mirrorFactor = 1.2 - factor; 21 | return mix(factor, mirrorFactor, uniforms.bloomRadius); 22 | } 23 | 24 | @compute @workgroup_size(1, 1) fn main(@builtin(global_invocation_id) id: vec3u) { 25 | 26 | var textDim = textureDimensions(outputTexture).xy; 27 | var uv = vec2f(id.xy) / vec2f(textDim); 28 | var color = vec4f(0.); 29 | 30 | color += vec4f(uniforms.bloomTintColor, 1.) * lerpBloomFactor(1.0) * uniforms.bloomStrength * textureSampleLevel(blurTexture1, textureSampler, uv, 0); 31 | color += vec4f(uniforms.bloomTintColor, 1.) * lerpBloomFactor(0.8) * uniforms.bloomStrength * textureSampleLevel(blurTexture2, textureSampler, uv, 0); 32 | color += vec4f(uniforms.bloomTintColor, 1.) * lerpBloomFactor(0.6) * uniforms.bloomStrength * textureSampleLevel(blurTexture3, textureSampler, uv, 0); 33 | color = max(color, vec4f(0.)); 34 | color += textureSampleLevel(inputTexture, textureSampler, uv, 0); 35 | 36 | textureStore(outputTexture, id.xy, color ); 37 | 38 | } 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Codrops demo 7 | 8 | 9 | 10 | 11 | 43 | 44 | 45 | 46 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | webGPU compute 7 | 8 | 9 | 10 | 11 | 43 | 44 | 45 | 46 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /simulation/PBF_integrateVelocity.wgsl: -------------------------------------------------------------------------------- 1 | struct Uniforms { 2 | deltaTime: f32, 3 | textureSize: f32, 4 | scatter: f32, 5 | dampening: f32 6 | } 7 | 8 | const EPSILON: f32 = 0.001; 9 | 10 | 11 | @group(0) @binding(0) var positionBufferOLD: array; 12 | @group(0) @binding(1) var positionBufferUPDATED: array; 13 | @group(0) @binding(2) var velocityBuffer: array; 14 | @group(0) @binding(3) var uniforms: Uniforms; 15 | @group(0) @binding(4) var texture3D: texture_storage_3d; 16 | 17 | 18 | @compute @workgroup_size(256) fn main( @builtin(global_invocation_id) id: vec3 ) { 19 | 20 | let index1D = id.x; 21 | 22 | var velocity = positionBufferUPDATED[index1D].rgb - positionBufferOLD[index1D].rgb; 23 | velocity /= (max(uniforms.deltaTime, EPSILON)); 24 | var speed = length(velocity); 25 | if(speed > 0.) { 26 | speed = min(40., speed); 27 | velocity = speed * normalize(velocity); 28 | } 29 | 30 | var dampening = 3. * uniforms.dampening; 31 | if(dampening > 2.) { 32 | velocity *= 1. - 0.8 * fract(dampening); 33 | } 34 | 35 | 36 | 37 | velocityBuffer[index1D] = vec4f(velocity, 1.); 38 | 39 | //Filling the information of the texture3D 40 | var tSize = f32(textureDimensions(texture3D).x); 41 | var normalizedPosition = positionBufferUPDATED[index1D].rgb / uniforms.textureSize; 42 | normalizedPosition *= tSize; 43 | 44 | let size: u32 = 2; 45 | for(var i: u32 = 0; i < size; i ++) { 46 | for(var j: u32 = 0; j < size; j ++) { 47 | for(var k: u32 = 0; k < size; k ++) { 48 | textureStore(texture3D, vec3(floor(normalizedPosition)) + vec3u(i, j , k), vec4f(1., velocity) ); 49 | } 50 | } 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /rendering/RenderBackground.wgsl: -------------------------------------------------------------------------------- 1 | struct Uniforms { 2 | inColor: vec3f, 3 | currentFrame: f32, 4 | outColor: vec3f 5 | } 6 | 7 | struct VertexOutput { 8 | @builtin(position) position: vec4f, 9 | @location(0) uv: vec2f 10 | }; 11 | 12 | @group(0) @binding(0) var uniforms: Uniforms; 13 | 14 | @vertex fn vs( @builtin(vertex_index) vertexIndex: u32) -> VertexOutput { 15 | 16 | let pos = array( 17 | // 1st triangle 18 | vec2f( -1, 1), // center 19 | vec2f( 1, -1), // right, center 20 | vec2f( -1, -1), // center, top 21 | 22 | // 2st triangle 23 | // center, top 24 | vec2f( -1, 1), 25 | vec2f( 1, 1), // right, center 26 | vec2f( 1, -1) // right, top 27 | ); 28 | 29 | var output: VertexOutput; 30 | output.position = vec4(pos[vertexIndex], 0., 1.); 31 | output.uv = 0.5 * pos[vertexIndex] + 0.5; 32 | return output; 33 | } 34 | 35 | struct FragmentOutput { 36 | @location(0) color1: vec4f, 37 | @location(1) color2: vec4f 38 | } 39 | 40 | fn lowerBound(t: f32) -> f32 { 41 | 42 | var amp = 1.; 43 | var freq = t; 44 | var output = 0.; 45 | 46 | for(var i = 0; i < 2; i ++) { 47 | output += amp * cos(freq); 48 | freq *= 2.; 49 | amp /= 2.; 50 | } 51 | 52 | return output; 53 | 54 | } 55 | 56 | @fragment fn fs(input: VertexOutput) -> FragmentOutput { 57 | 58 | var uv = vec2f(input.uv.x, input.uv.y); 59 | 60 | var transition = uniforms.currentFrame * 2.; 61 | transition = pow(min(max(0., transition - 0.6), 1.), 2.); 62 | var bars = 10.; 63 | var _x = (floor(bars * pow(uv.x, .8) )) % bars; 64 | 65 | var color = mix(uniforms.inColor, uniforms.outColor, vec3f(transition)); 66 | 67 | 68 | color += vec3(.4) * clamp(1. - length(uv - 0.5), 0., 1.); 69 | 70 | var output: FragmentOutput; 71 | output.color1 = vec4f(color, 1.); 72 | output.color2 = vec4f(0.); 73 | 74 | return output; 75 | 76 | } 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /marchingCubes/MarchCase.wgsl: -------------------------------------------------------------------------------- 1 | struct Uniforms { 2 | texture3DSize: f32, 3 | texture2DSize: f32, 4 | mipmapLevels: f32, 5 | range: f32 6 | } 7 | 8 | @group(0) @binding(0) var uniforms: Uniforms; 9 | @group(0) @binding(1) var textureRead: texture_3d; 10 | @group(0) @binding(2) var amountOfTriangles: array; 11 | @group(0) @binding(3) var texture_vct: texture_storage_3d; 12 | 13 | @group(0) @binding(4) var voxelsEnabled:array ; 14 | @group(0) @binding(5) var voxelsIndex: array>; 15 | 16 | 17 | const HIGH_TO_LOW = 4; 18 | 19 | @compute @workgroup_size(1) fn main(@builtin(global_invocation_id) ud: vec3) { 20 | 21 | var tSize = vec3f(textureDimensions(textureRead)); 22 | var id = ud + vec3u( u32(tSize.x * 0.3), 0, 0); 23 | 24 | var range = uniforms.range; 25 | var center = textureLoad(textureRead, id + vec3u(0, 0, 0), 0).r; 26 | var c = step(center, range); 27 | var vct = c; 28 | c += 2. * step(textureLoad(textureRead, id + vec3u(1, 0, 0), 0).r, range); 29 | c += 4. * step(textureLoad(textureRead, id + vec3u(1, 1, 0), 0).r, range); 30 | c += 8. * step(textureLoad(textureRead, id + vec3u(0, 1, 0), 0).r, range); 31 | c += 16. * step(textureLoad(textureRead, id + vec3u(0, 0, 1), 0).r, range); 32 | c += 32. * step(textureLoad(textureRead, id + vec3u(1, 0, 1), 0).r, range); 33 | c += 64. * step(textureLoad(textureRead, id + vec3u(1, 1, 1), 0).r, range); 34 | c += 128. * step(textureLoad(textureRead, id + vec3u(0, 1, 1), 0).r, range); 35 | c *= step(c, 254.); 36 | var totalTriangles = amountOfTriangles[i32(c)]; 37 | var value = f32(totalTriangles > 0.); 38 | 39 | //For the voxel cone tracing 40 | textureStore(texture_vct, id, vec4f(vec3(0., 0., 0.), value)); 41 | 42 | if(id.y < 3) { 43 | textureStore(texture_vct, id, vec4f(vec3f(.0), .5)); 44 | } 45 | 46 | //To group the voxels together 47 | if(value > 0.) { 48 | var index = atomicAdd(&voxelsIndex[0], 1); 49 | voxelsEnabled[index] = vec4f(vec3f(id), c); 50 | } 51 | } -------------------------------------------------------------------------------- /mipmaps/CalculateMipMap.js: -------------------------------------------------------------------------------- 1 | import * as Utils from "../utils/utils.js"; 2 | 3 | import mipmapComputeShader from "../mipmaps/MipMapCompute.wgsl?raw"; 4 | 5 | let ready = Promise.create(); 6 | let started = false; 7 | let pipeline, bindGroups; 8 | 9 | 10 | async function generateMipMap(texture, device) { 11 | 12 | if(!started) { 13 | let pipelineReady = Promise.create(); 14 | 15 | Utils.getPipeline(mipmapComputeShader).then( 16 | response => { 17 | pipeline = response.pipeline; 18 | pipelineReady.resolve(); 19 | } 20 | ); 21 | 22 | await pipelineReady; 23 | 24 | bindGroups = []; 25 | for(let i = 0; i < texture.mipLevelCount - 1; i ++) { 26 | 27 | const bindGroup = device.createBindGroup( { 28 | label: "bind group for mipmap", 29 | layout: pipeline.getBindGroupLayout(0), 30 | entries:[ 31 | {binding: 0, resource: texture.createView( 32 | { 33 | baseMipLevel: i, 34 | mipLevelCount: 1 35 | } 36 | )}, 37 | {binding: 1, resource: texture.createView( 38 | { 39 | baseMipLevel: i + 1, 40 | mipLevelCount: 1 41 | } 42 | )}, 43 | ] 44 | }) 45 | 46 | bindGroups.push(bindGroup) 47 | 48 | } 49 | 50 | started = true; 51 | } 52 | 53 | //make a command enconder to start encoding thigns 54 | const encoder = device.createCommandEncoder({ label: 'encoder'}); 55 | 56 | //Run the simulation 57 | const computePass = encoder.beginComputePass({ 58 | label: "mipmap pass" 59 | }) 60 | 61 | computePass.setPipeline(pipeline); 62 | 63 | for(let i = 0; i < texture.mipLevelCount - 1; i ++) { 64 | 65 | let size = Math.pow(2, texture.mipLevelCount - i - 1); 66 | computePass.setBindGroup(0, bindGroups[i]); 67 | computePass.dispatchWorkgroups(size, size, size); 68 | 69 | } 70 | 71 | computePass.end(); 72 | 73 | const commandBuffer = encoder.finish(); 74 | device.queue.submit([commandBuffer]); 75 | 76 | } 77 | 78 | export {generateMipMap} -------------------------------------------------------------------------------- /rendering/RenderComposite.wgsl: -------------------------------------------------------------------------------- 1 | 2 | struct VertexOutput { 3 | @builtin(position) position: vec4f, 4 | @location(0) uv: vec2f 5 | }; 6 | 7 | struct Uniforms { 8 | resolution: vec2f, 9 | relativeFrame: f32, 10 | animationFrame: f32, 11 | 12 | currentLetter: f32, 13 | brightness: f32, 14 | contrast: f32, 15 | gamma: f32, 16 | } 17 | 18 | @group(0) @binding(0) var uniforms: Uniforms; 19 | @group(0) @binding(1) var inputTexture: texture_2d; 20 | @group(0) @binding(2) var textureSampler: sampler; 21 | @group(0) @binding(3) var logoTexture: texture_2d; 22 | 23 | 24 | @vertex fn vs( @builtin(vertex_index) vertexIndex: u32) -> VertexOutput { 25 | 26 | let pos = array( 27 | // 1st triangle 28 | vec2f( -1, 1), // center 29 | vec2f( 1, -1), // right, center 30 | vec2f( -1, -1), // center, top 31 | 32 | // 2st triangle 33 | // center, top 34 | vec2f( -1, 1), 35 | vec2f( 1, 1), // right, center 36 | vec2f( 1, -1) // right, top 37 | ); 38 | 39 | var output: VertexOutput; 40 | output.position = vec4(pos[vertexIndex], 0., 1.); 41 | output.uv = 0.5 * pos[vertexIndex] + 0.5; 42 | return output; 43 | } 44 | 45 | @fragment fn fs(input: VertexOutput) -> @location(0) vec4f { 46 | 47 | var uv = vec2f(input.uv.x, 1. - input.uv.y); 48 | var tDim = vec2f(textureDimensions(logoTexture)); 49 | var tAspectRatio = tDim.y / tDim.x; 50 | 51 | var st = 2. * uv - 1.; 52 | st *= 10. * vec2f(tAspectRatio * uniforms.resolution.x / uniforms.resolution.y, 1.); 53 | st = 0.5 * st + 0.5; 54 | 55 | var logo = textureSampleLevel(logoTexture, textureSampler, st, 0.); 56 | var colorLogo = vec4(logo.a); 57 | 58 | //Apply the gamma correction, brightness and contrast 59 | var color = textureSampleLevel(inputTexture, textureSampler, uv, 0); 60 | 61 | //brightness 62 | color = color + vec4f(uniforms.brightness); 63 | 64 | //contrast 65 | var t = (1. - uniforms.contrast) / 2.; 66 | color = color * uniforms.contrast + vec4(t); 67 | 68 | //gamma 69 | color = pow(color, vec4(uniforms.gamma)); 70 | 71 | color = vec4f(color.rgb, 1.); 72 | 73 | // var transition = 2.5 * uniforms.relativeFrame; 74 | // transition = pow(min(max(0., transition - 1.), 1.), 2.); 75 | 76 | // if(uniforms.currentLetter == -2.) { 77 | 78 | // var transition2 = pow(min(max(0., transition + 0.1), 1.), 2.); 79 | 80 | // colorLogo = select(colorLogo, vec4(0.), uv.x > transition2); 81 | 82 | // color = select(colorLogo, color, uv.y > transition); 83 | 84 | // } 85 | 86 | // if(uniforms.currentLetter == -1.) { 87 | // color = select(color, colorLogo, uv.y > transition); 88 | // } 89 | 90 | return color; 91 | 92 | 93 | } 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /utils/shaderParser.js: -------------------------------------------------------------------------------- 1 | function parseBindings(shader, visibility) { 2 | 3 | shader = shader.split(';'); 4 | let groups = []; 5 | 6 | const groupIdRegex = /group\(([^)]+)\)/; 7 | const bindingIdRegex = /binding\(([^)]+)\)/; 8 | 9 | shader.forEach((line, i) => { 10 | if(line.includes("@group")) { 11 | 12 | const groupId = Number(groupIdRegex.exec(line)[1]); 13 | const bindingId = Number(bindingIdRegex.exec(line)[1]); 14 | 15 | if(groups[groupId] == undefined) groups[groupId] = []; 16 | 17 | //Buffer type 18 | if(line.includes("uniform") || line.includes("storage")) { 19 | 20 | let _type = ""; 21 | if(line.includes("storage")) _type = line.includes("read_write") ? "storage" : "read-only-storage"; 22 | if(line.includes("uniform")) _type = "uniform"; 23 | 24 | groups[groupId][bindingId] = { 25 | binding: bindingId, 26 | visibility: visibility, 27 | buffer: { 28 | type: _type 29 | } 30 | } 31 | 32 | } 33 | 34 | //Textures type 35 | if(line.includes("texture_")) { 36 | 37 | let dimension = "2d"; 38 | if(line.includes("_3d")) dimension = "3d"; 39 | if(line.includes("_1d")) dimension = "1d"; 40 | if(line.includes("_2d") && line.includes("array")) dimension = "2d-array"; 41 | 42 | //Texture storage type 43 | if(line.includes("texture_storage")) { 44 | groups[groupId][bindingId] = { 45 | binding: bindingId, 46 | visibility: visibility, 47 | storageTexture: { 48 | format: line.includes("unorm")? "rgba8unorm" : "rgba32float", 49 | viewDimension: dimension, 50 | } 51 | } 52 | } 53 | 54 | //Texture type 55 | else { 56 | 57 | let _type = "float"; 58 | let dimension = "2d"; 59 | if(line.includes("texture_3d")) dimension = "3d"; 60 | if(line.includes("texture_1d")) dimension = "1d"; 61 | if(line.includes("_2d") && line.includes("array")) dimension = "2d-array"; 62 | 63 | groups[groupId][bindingId] = { 64 | binding: bindingId, 65 | visibility: visibility, 66 | texture: { 67 | sampleType: _type, 68 | viewDimension: dimension 69 | } 70 | } 71 | 72 | } 73 | 74 | } 75 | 76 | //Smpler type 77 | if(line.includes("sampler")) { 78 | groups[groupId][bindingId] = { 79 | binding: bindingId, 80 | visibility: visibility, 81 | sampler: { 82 | type: "filtering", 83 | } 84 | } 85 | } 86 | } 87 | }) 88 | 89 | return groups; 90 | } 91 | 92 | export {parseBindings} -------------------------------------------------------------------------------- /simulation/PBF_calculateDisplacements.wgsl: -------------------------------------------------------------------------------- 1 | struct Uniforms { 2 | uResolution: f32, 3 | uSearchRadius: f32, 4 | separation: f32 5 | } 6 | 7 | var deltaPosition: vec3 = vec3f(0.); 8 | var h2: f32 = 0.; 9 | 10 | @group(0) @binding(0) var positionBufferIN: array; 11 | @group(0) @binding(1) var positionBufferOUT: array; 12 | @group(0) @binding(2) var indicesBuffer: array>; 13 | @group(0) @binding(3) var uniforms: Uniforms; 14 | 15 | fn addToSum(particlePosition: vec3f, nParticlePosition: vec3f) { 16 | 17 | let distance = (particlePosition - nParticlePosition) ; 18 | let r = length(distance); 19 | 20 | let separation = 1. + uniforms.separation; 21 | 22 | if(r > 0. && r < separation) { 23 | 24 | deltaPosition -= 0.5 * (r - separation) * normalize(distance) ; 25 | } 26 | 27 | } 28 | 29 | @compute @workgroup_size(256) fn main( @builtin(global_invocation_id) id: vec3 ) { 30 | 31 | var index1D = id.x; 32 | 33 | h2 = uniforms.uSearchRadius * uniforms.uSearchRadius; 34 | 35 | let particlePosition = positionBufferIN[index1D].rgb; 36 | let lambdaPressure = positionBufferIN[index1D].a; 37 | let gridPosition = vec3(floor(particlePosition)); 38 | let resolution = i32(uniforms.uResolution); 39 | 40 | var neighborsVoxel = gridPosition ; 41 | var voxelIndex = neighborsVoxel.x + neighborsVoxel.y * resolution + neighborsVoxel.z * resolution * resolution; 42 | var indices = indicesBuffer[u32(voxelIndex)]; 43 | if(indices.x > 0) {addToSum(particlePosition, positionBufferIN[indices.x].rgb);} 44 | if(indices.y > 0) {addToSum(particlePosition, positionBufferIN[indices.y].rgb);} 45 | if(indices.z > 0) {addToSum(particlePosition, positionBufferIN[indices.z].rgb);} 46 | if(indices.w > 0) {addToSum(particlePosition, positionBufferIN[indices.w].rgb);} 47 | 48 | var offsets = array, 26>(); 49 | 50 | //Faces 51 | offsets[0] = vec3(0, 0, 1); 52 | offsets[1] = vec3(0, 0, -1); 53 | offsets[2] = vec3(0, 1, 0); 54 | offsets[3] = vec3(0, -1, 0); 55 | offsets[4] = vec3(1, 0, 0); 56 | offsets[5] = vec3(-1, 0, 0); 57 | 58 | //Aristas 59 | offsets[6] = vec3(0, 1, 1); 60 | offsets[7] = vec3(1, 0, 1); 61 | offsets[8] = vec3(1, 1, 0); 62 | offsets[9] = vec3(0, 1, -1); 63 | offsets[10] = vec3(1, 0, -1); 64 | offsets[11] = vec3(1, -1, 0); 65 | offsets[12] = vec3(0, -1, 1); 66 | offsets[13] = vec3(-1, 0, 1); 67 | offsets[14] = vec3(-1, 1, 0); 68 | offsets[15] = vec3(0, -1, -1); 69 | offsets[16] = vec3(-1, 0, -1); 70 | offsets[17] = vec3(-1, -1, 0); 71 | 72 | //Corners 73 | offsets[18] = vec3(1, 1, 1); 74 | offsets[19] = vec3(1, 1, -1); 75 | offsets[20] = vec3(1, -1, 1); 76 | offsets[21] = vec3(-1, 1, 1); 77 | offsets[22] = vec3(1, -1, -1); 78 | offsets[23] = vec3(-1, -1, 1); 79 | offsets[24] = vec3(-1, 1, -1); 80 | offsets[25] = vec3(-1, -1, -1); 81 | 82 | 83 | 84 | for(var i = 0; i < 26; i ++) { 85 | 86 | var average = vec3f(0); 87 | var counter = 0.; 88 | let neighborsVoxel = gridPosition + offsets[i]; 89 | let voxelIndex = neighborsVoxel.x + neighborsVoxel.y * resolution + neighborsVoxel.z * resolution * resolution; 90 | let indices = indicesBuffer[u32(voxelIndex)]; 91 | 92 | if(indices.x > 0) {addToSum(particlePosition, positionBufferIN[indices.x].rgb);} 93 | if(indices.y > 0) {addToSum(particlePosition, positionBufferIN[indices.y].rgb);} 94 | if(indices.z > 0) {addToSum(particlePosition, positionBufferIN[indices.z].rgb);} 95 | if(indices.w > 0) {addToSum(particlePosition, positionBufferIN[indices.w].rgb);} 96 | 97 | } 98 | 99 | var endPosition = particlePosition + deltaPosition; 100 | 101 | //Collision handling 102 | let center = uniforms.uResolution * vec3f(0.5, 0.5, 0.5); 103 | let boxSize = uniforms.uResolution * vec3f(0.2, 0.48, 0.48); 104 | let xLocal = endPosition - center; 105 | let contactPointLocal = min(boxSize, max(-boxSize, xLocal)); 106 | let contactPoint = contactPointLocal + center; 107 | let distance = length(contactPoint - particlePosition); 108 | 109 | if(distance > 0.0) {endPosition = contactPoint;}; 110 | 111 | positionBufferOUT[index1D] = vec4f(endPosition, lambdaPressure); 112 | } 113 | -------------------------------------------------------------------------------- /blur3D/Blur3D.js: -------------------------------------------------------------------------------- 1 | import * as Utils from "../utils/utils.js"; 2 | 3 | import blur3DShader from "../blur3D/Blur3D.wgsl?raw"; 4 | 5 | let started = false; 6 | let pipeline; 7 | let uniformsForBindings = []; 8 | 9 | const gerenteUniforms = device => { 10 | 11 | let axis = [[1, 0, 0, 1], [0, 0, 1, 1], [0, 1, 0, 1]]; 12 | 13 | for(let i = 0; i < 3; i ++) { 14 | 15 | let uniforms = new Float32Array(axis[i]); 16 | let uniformsBuffer = device.createBuffer( 17 | { 18 | label: "uniforms buffer", 19 | 20 | size: uniforms.byteLength, 21 | usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST 22 | } 23 | ) 24 | 25 | device.queue.writeBuffer(uniformsBuffer, 0, uniforms); 26 | uniformsForBindings.push(uniformsBuffer); 27 | } 28 | } 29 | 30 | async function calculateBlur3D(texture, textureOut, steps) { 31 | 32 | let device = Utils.device; 33 | 34 | if(!started) { 35 | 36 | // capacity = 2;//Max number of timestamps we can store 37 | 38 | // querySet = device.createQuerySet({ 39 | // type: "timestamp", 40 | // count: capacity, 41 | // }); 42 | 43 | // queryBuffer = device.createBuffer({ 44 | // size: 8 * capacity, 45 | // usage: GPUBufferUsage.QUERY_RESOLVE 46 | // | GPUBufferUsage.STORAGE 47 | // | GPUBufferUsage.COPY_SRC 48 | // | GPUBufferUsage.COPY_DST, 49 | // }); 50 | 51 | gerenteUniforms(device); 52 | 53 | let pipelineReady = Promise.create(); 54 | 55 | Utils.getPipeline(blur3DShader).then( 56 | response => { 57 | pipeline = response.pipeline; 58 | pipelineReady.resolve(); 59 | } 60 | ); 61 | 62 | await pipelineReady; 63 | 64 | started = true; 65 | } 66 | 67 | let ss = steps * 2; 68 | let axis = [[1, 0, 0, ss], [0, 0, 1, ss], [0, 1, 0, ss]]; 69 | 70 | for(let i = 0; i < 3; i ++) { 71 | let uniforms = new Float32Array(axis[i]); 72 | device.queue.writeBuffer(uniformsForBindings[i], 0, uniforms); 73 | } 74 | 75 | //make a command enconder to start encoding thigns 76 | const encoder = device.createCommandEncoder({ label: 'encoder'}); 77 | 78 | 79 | //Run the simulation 80 | const computePass = encoder.beginComputePass({ 81 | label: "blur3D pass", 82 | // timestampWrites: { 83 | // querySet, 84 | // beginningOfPassWriteIndex: 0, // Write timestamp in index 0 when pass begins. 85 | // endOfPassWriteIndex: 1, // Write timestamp in index 1 when pass ends. 86 | // } 87 | }) 88 | 89 | computePass.setPipeline(pipeline); 90 | let size = texture.width; 91 | 92 | let texIn, texOut; 93 | 94 | for(let i = 0; i < 3; i ++) { 95 | 96 | //Weird enough... but it is what it is... ping pong on 97 | //textures. 98 | let even = i % 2 == 0; 99 | texIn = even ? texture : textureOut; 100 | texOut = even ? textureOut : texture; 101 | 102 | const bindGroup = device.createBindGroup( { 103 | label: "bind group for blur3D", 104 | layout: pipeline.getBindGroupLayout(0), 105 | entries:[ 106 | {binding: 0, resource: texIn.createView( 107 | { 108 | baseMipLevel: 0, 109 | mipLevelCount: 1 110 | } 111 | )}, 112 | {binding: 1, resource: texOut.createView( 113 | { 114 | baseMipLevel: 0, 115 | mipLevelCount: 1 116 | } 117 | )}, 118 | {binding: 2, resource: {buffer: uniformsForBindings[i]}} 119 | ] 120 | }) 121 | 122 | computePass.setBindGroup(0, bindGroup); 123 | computePass.dispatchWorkgroups(size * 0.6, size, size); 124 | 125 | } 126 | 127 | computePass.end(); 128 | 129 | // encoder.resolveQuerySet( 130 | // querySet, 131 | // 0,// index of first query to resolve 132 | // capacity,//number of queries to resolve 133 | // queryBuffer, 134 | // 0);// destination offset 135 | 136 | const commandBuffer = encoder.finish(); 137 | device.queue.submit([commandBuffer]); 138 | 139 | // const arrayBuffer = await Utils.readBuffer(device, queryBuffer); 140 | // Decode it into an array of timestamps in nanoseconds 141 | // const timingsNanoseconds = new BigInt64Array(arrayBuffer); 142 | // let timing = timingsNanoseconds[1] - timingsNanoseconds[0]; 143 | // console.log("potential time: " + Math.ceil(Number(timing) / 1000000)); 144 | 145 | } 146 | 147 | export {calculateBlur3D} -------------------------------------------------------------------------------- /marchingCubes/GenerateTriangles.wgsl: -------------------------------------------------------------------------------- 1 | struct Uniforms { 2 | texture3DSize: f32, 3 | texture2DSize: f32, 4 | mipmapLevels: f32, 5 | range: f32 6 | } 7 | 8 | @group(0) @binding(0) var uniforms: Uniforms; 9 | @group(0) @binding(1) var voxelsBuffer: array; 10 | @group(0) @binding(2) var indicesBuffer:array; 11 | @group(0) @binding(3) var potentialTexture: texture_3d; 12 | @group(0) @binding(4) var positionBuffer: array; 13 | @group(0) @binding(5) var normalBuffer: array; 14 | @group(0) @binding(6) var velocityBuffer: array; 15 | 16 | 17 | @compute @workgroup_size(225, 1, 1) fn main( @builtin(local_invocation_id) l_id: vec3, 18 | @builtin(workgroup_id) workgroup_id: vec3, 19 | @builtin(local_invocation_index) local_invocation_index: u32, 20 | @builtin(num_workgroups) num_workgroups: vec3 21 | ) { 22 | 23 | let workgroup_index = workgroup_id.x + workgroup_id.y * num_workgroups.x + workgroup_id.z * num_workgroups.x * num_workgroups.y; 24 | let global_invocation_index = workgroup_index * 225 + local_invocation_index; 25 | 26 | var p0 = vec3i(1, 0, 0); 27 | var p1 = vec3i(1, 1, 0); 28 | var p2 = vec3i(0, 1, 0); 29 | var p3 = vec3i(0, 0, 1); 30 | var p4 = vec3i(1, 0, 1); 31 | var p5 = vec3i(1, 1, 1); 32 | var p6 = vec3i(0, 1, 1); 33 | 34 | var currentVoxel = floor(f32(global_invocation_index) / 15.); 35 | var currentIndex = global_invocation_index % 15; 36 | 37 | var voxelData = voxelsBuffer[i32(currentVoxel)]; 38 | var position3D = voxelData.xyz; 39 | 40 | //Marching cubes case * 15 indices + local index 41 | var currentVertex = indicesBuffer[i32(voxelData.w) * 15 + i32(currentIndex)]; 42 | 43 | if(currentVertex == -1.) { 44 | positionBuffer[global_invocation_index] = vec4f(0, 0, 0, 0); 45 | return; 46 | } 47 | 48 | var m0 = vec4f(currentVertex, currentVertex, currentVertex, currentVertex); 49 | var m1 = vec4i(m0 == vec4f(0, 1, 2, 3)); 50 | var m2 = vec4i(m0 == vec4f(4, 5, 6, 7)); 51 | var m3 = vec4i(m0 == vec4f(8, 9, 10, 11)); 52 | 53 | //Get the corners for the edge where the vertex is allocated 54 | var corner0 = vec3i(position3D) + m1.y * p0 + m1.z * p1 + m1.w * p2 + m2.x * p3 + m2.y * p4 + m2.z * p5 + m2.w * p6 + m3.y * p0 + m3.z * p1 + m3.w * p2; 55 | var corner1 = vec3i(position3D) + m1.x * p0 + m1.y * p1 + m1.z * p2 + m2.x * p4 + m2.y * p5 + m2.z * p6 + m2.w * p3 + m3.x * p3 + m3.y * p4 + m3.z * p5 + m3.w * p6; 56 | 57 | var b0 = vec3f(corner0); 58 | var b1 = vec3f(corner1); 59 | 60 | //Potential values in the corresponding corners 61 | var n0 = textureLoad(potentialTexture, corner0, 0).r; 62 | var n1 = textureLoad(potentialTexture, corner1, 0).r; 63 | 64 | //Define the position of the corresponding vertex 65 | var diff = vec2f(uniforms.range - n0, n1 - n0); 66 | var vertexPosition = b0 + diff.x * (b1 - b0) / diff.y; 67 | 68 | //Define the normal 69 | var plusX = corner0 + vec3i(1, 0, 0); 70 | var plusY = corner0 + vec3i(0, 1, 0); 71 | var plusZ = corner0 + vec3i(0, 0, 1); 72 | 73 | var minusX = corner0 - vec3i(1, 0, 0); 74 | var minusY = corner0 - vec3i(0, 1, 0); 75 | var minusZ = corner0 - vec3i(0, 0, 1); 76 | 77 | var normal0 = vec3f(textureLoad(potentialTexture, plusX, 0).r - textureLoad(potentialTexture, minusX, 0).r, 78 | textureLoad(potentialTexture, plusY, 0).r - textureLoad(potentialTexture, minusY, 0).r, 79 | textureLoad(potentialTexture, plusZ, 0).r - textureLoad(potentialTexture, minusZ, 0).r); 80 | 81 | normal0 = normalize(normal0); 82 | 83 | plusX = corner1 + vec3i(1, 0, 0); 84 | plusY = corner1 + vec3i(0, 1, 0); 85 | plusZ = corner1 + vec3i(0, 0, 1); 86 | 87 | minusX = corner1 - vec3i(1, 0, 0); 88 | minusY = corner1 - vec3i(0, 1, 0); 89 | minusZ = corner1 - vec3i(0, 0, 1); 90 | 91 | var normal1 = vec3f(textureLoad(potentialTexture, plusX, 0).r - textureLoad(potentialTexture, minusX, 0).r, 92 | textureLoad(potentialTexture, plusY, 0).r - textureLoad(potentialTexture, minusY, 0).r, 93 | textureLoad(potentialTexture, plusZ, 0).r - textureLoad(potentialTexture, minusZ, 0).r); 94 | 95 | normal1 = normalize(normal1); 96 | 97 | var normal = normal0 + diff.x * (normal1 - normal0) / diff.y; 98 | 99 | //velocities values in the corresponding corners 100 | var vel0 = textureLoad(potentialTexture, corner0, 0).gba; 101 | var vel1 = textureLoad(potentialTexture, corner1, 0).gba; 102 | var velocity = vel0 + diff.x * (vel1 - vel0) / diff.y; 103 | 104 | positionBuffer[global_invocation_index] = vec4f( vertexPosition / uniforms.texture3DSize, 1.); 105 | normalBuffer[global_invocation_index] = vec4f(-normal, 1.); 106 | velocityBuffer[global_invocation_index] = vec4f(velocity, 1.); 107 | 108 | } -------------------------------------------------------------------------------- /rendering/RenderMC.wgsl: -------------------------------------------------------------------------------- 1 | struct VertexOutput { 2 | @builtin(position) position: vec4f, 3 | @location(0) position3D: vec3f, 4 | @location(1) normal: vec3f, 5 | @location(2) velocity: vec3f, 6 | @location(3) position2D: vec2f 7 | }; 8 | 9 | struct Uniforms { 10 | perspectiveMatrix: mat4x4, 11 | cameraPosition: vec3f, 12 | voxelsSize: f32, 13 | 14 | coneAngle: f32, 15 | coneRotation: f32, 16 | currentFrame: f32, 17 | voxelWorldSize: f32, 18 | 19 | inColor: vec3f, 20 | mirror: f32, 21 | 22 | outColor: vec3f, 23 | thickness: f32 24 | }; 25 | 26 | 27 | @group(0) @binding(0) var positionBuffer: array; 28 | @group(0) @binding(1) var uniforms: Uniforms; 29 | @group(0) @binding(2) var normalBuffer: array; 30 | @group(0) @binding(3) var texture3D: texture_3d; 31 | @group(0) @binding(4) var textureSampler: sampler; 32 | @group(0) @binding(5) var velocityBuffer: array; 33 | @group(0) @binding(6) var potentialTexture: texture_3d; 34 | 35 | 36 | @vertex fn vs( @builtin(vertex_index) vertexIndex: u32) -> VertexOutput { 37 | 38 | var position3D = positionBuffer[vertexIndex].rgb; 39 | var thickness = 0.01 * (1 - clamp(uniforms.thickness, 0., 1.)); 40 | var pos = position3D - thickness * normalBuffer[vertexIndex].rgb ; 41 | var projection = uniforms.perspectiveMatrix * vec4f(pos, 1.); 42 | var output: VertexOutput; 43 | output.position = projection; 44 | output.position3D = position3D; 45 | output.normal = normalBuffer[vertexIndex].rgb; 46 | output.velocity = velocityBuffer[vertexIndex].rgb; 47 | output.position2D = 0.5 * projection.xy / projection.w + 0.5; 48 | return output; 49 | } 50 | 51 | const MAX_DISTANCE = 1.0; 52 | const MAX_ALPHA = 0.95; 53 | 54 | fn sampleVoxels(pos: vec3, lod: f32) -> vec4 { 55 | return textureSampleLevel(texture3D, textureSampler, pos, lod); 56 | } 57 | 58 | fn voxelConeTracing(startPos: vec3f, direction: vec3f, tanHalfAngle: f32) -> vec4 { 59 | var lod = 0.; 60 | var color = vec3f(0.); 61 | var alpha = 0.; 62 | var occlusion = 0.; 63 | 64 | var voxelWorldSize = uniforms.voxelWorldSize; 65 | var dist = voxelWorldSize; 66 | 67 | while(dist < MAX_DISTANCE && alpha < MAX_ALPHA) { 68 | let diameter = max(voxelWorldSize, 2. * tanHalfAngle * dist); 69 | let lodLevel = log2( diameter / voxelWorldSize); 70 | var voxelColor = sampleVoxels(startPos + dist * direction, lodLevel); 71 | var sub = 1. - alpha; 72 | var aa = voxelColor.a; 73 | alpha += sub * aa; 74 | occlusion += sub * aa / (1. + 0.03 * diameter); 75 | color += sub * voxelColor.rgb; 76 | dist += diameter; 77 | } 78 | 79 | return vec4f(color, clamp(1. - occlusion, 0., 1.) ); 80 | } 81 | 82 | fn getOcclusion(ro: vec3f, rd: vec3f, scaler: f32) -> vec4f{ 83 | var totao = vec4f(0.); 84 | var sca = 1.; 85 | var steps = 100.; 86 | for(var aoi = 1.; aoi < steps; aoi+= 1.) { 87 | var hr = 0.03 + 2. * aoi * aoi / (steps * steps); 88 | var p = ro + rd * hr; 89 | var dd = textureSampleLevel(potentialTexture, textureSampler, p, 0).x; 90 | var ao = 0.; 91 | if(dd <= hr) { 92 | ao = clamp((hr - dd), 0., 1.); 93 | } 94 | totao += ao * sca * vec4(1.); 95 | sca *= scaler; 96 | } 97 | var aoCoef = 1.; 98 | totao = vec4f(totao.rgb, clamp(aoCoef * totao.w, 0., 1.)); 99 | return totao; 100 | } 101 | 102 | 103 | struct FragmentOutput { 104 | @location(0) color: vec4f, 105 | @location(1) velocity: vec4f, 106 | } 107 | 108 | 109 | @fragment fn fs(input: VertexOutput) -> FragmentOutput { 110 | 111 | var eye = normalize(input.position3D - uniforms.cameraPosition); 112 | 113 | var pp = input.position3D; 114 | var direction = input.normal; 115 | 116 | var ang = radians(uniforms.coneRotation); 117 | let s = sin(ang); 118 | let c = cos(ang); 119 | 120 | var dir1 = vec3f(0, 0, 1); 121 | var dir2 = vec3f(c, 0, s); 122 | var dir3 = vec3f(-c, 0, s); 123 | var dir4 = vec3f(0, c, s); 124 | var dir5 = vec3f(0, -c, s); 125 | 126 | var zAxis = normalize(direction); 127 | var xAxis = vec3f(1, 0, 0); 128 | var yAxis = vec3f(0, 1, 0); 129 | var UP = vec3f(0, 1, 0); 130 | var rot = mat3x3f(0, 0, 0, 0, 0, 0, 0, 0, 0); 131 | 132 | if( abs(dot(direction, UP)) > 0.9 ) { 133 | 134 | UP = vec3f(1, 0, 0); 135 | 136 | } 137 | 138 | xAxis = normalize(cross(UP, zAxis)); 139 | yAxis = normalize(cross(zAxis, xAxis)); 140 | rot = mat3x3f(xAxis, yAxis, zAxis); 141 | 142 | 143 | dir1 = rot * dir1; 144 | dir2 = rot * dir2; 145 | dir3 = rot * dir3; 146 | dir4 = rot * dir4; 147 | dir5 = rot * dir5; 148 | 149 | var cone = voxelConeTracing(pp, dir1, uniforms.coneAngle); 150 | var color = cone.rgba; 151 | 152 | cone = voxelConeTracing(pp, dir2, uniforms.coneAngle); 153 | color += cone.rgba; 154 | 155 | cone = voxelConeTracing(pp, dir3, uniforms.coneAngle); 156 | color += cone.rgba; 157 | 158 | cone = voxelConeTracing(pp, dir4, uniforms.coneAngle); 159 | color += cone.rgba; 160 | 161 | cone = voxelConeTracing(pp, dir5, uniforms.coneAngle); 162 | color += cone.rgba; 163 | 164 | color /= 5.; 165 | color = pow(color, vec4f(0.4545)); 166 | 167 | var thickness = getOcclusion(input.position3D, eye, 0.89); 168 | 169 | var thicknessPower = 1.; 170 | var thicknessScale = 1.; 171 | 172 | var transition = uniforms.currentFrame * 2.; 173 | transition = pow(min(max(0., transition - .6), 1.), 2.); 174 | var bars = 10.; 175 | var _x = (floor(bars * pow(input.position2D.x, .8) )) % bars; 176 | 177 | var thicknessAmbient = 0.8 * mix(uniforms.inColor, uniforms.outColor, vec3f(transition)); 178 | 179 | 180 | // var thicknessAmbient = 0.8 * select(uniforms.inColor, uniforms.outColor, input.position2D.y > 1. - transition); 181 | var subsurfaceSpread = .4; 182 | var subsurfaceIntensity = 8.; 183 | var subsurfacePower = 10.; 184 | 185 | var lightDirection = -eye; 186 | var scatteringHalf = normalize(lightDirection + (input.normal * subsurfaceSpread)); 187 | var scatteringDot = pow(clamp(dot(eye, -scatteringHalf), 0., 1.), subsurfacePower) * subsurfaceIntensity; 188 | var scatteringIllu = thicknessAmbient * (scatteringDot) * pow(thickness.a * thicknessScale, thicknessPower) + thicknessAmbient; 189 | 190 | var specular = pow(max(dot(reflect(input.normal, normalize(vec3(-1., 1., 0.))), eye), 0.), 20.); 191 | 192 | var output: FragmentOutput; 193 | var decay = max(pow(max(1. - input.position3D.y, 1. - uniforms.mirror), 2.), 0.); 194 | 195 | output.color = vec4( pow((vec3f(specular) * vec3f(0.1) + scatteringIllu + color.rgb), vec3(1.)) * color.a, 1.); 196 | output.velocity = vec4(pow(input.position3D.y, 2.), uniforms.mirror, 0., 1.); 197 | 198 | return output; 199 | 200 | } -------------------------------------------------------------------------------- /simulation/PBF_applyForces.wgsl: -------------------------------------------------------------------------------- 1 | struct Uniforms { 2 | cameraOrientation: mat4x4f, 3 | 4 | acceleration: vec3f, 5 | deltaTime: f32, 6 | 7 | mousePosition: vec3f, 8 | gridResolution: f32, 9 | 10 | mouseDirection: vec3f, 11 | currentFrame: f32, 12 | 13 | transition: f32, 14 | totalParticles: f32 15 | } 16 | 17 | @group(0) @binding(0) var resetBuffer: array; 18 | @group(0) @binding(1) var positionBuffer: array; 19 | @group(0) @binding(2) var velocityBuffer: array; 20 | @group(0) @binding(3) var counterBuffer: array>; 21 | @group(0) @binding(4) var indicesBuffer: array; 22 | @group(0) @binding(5) var uniforms: Uniforms; 23 | 24 | 25 | //Analitic derivatives of the potentials for the curl noise, based on: http://weber.itn.liu.se/~stegu/TNM084-2019/bridson-siggraph2007-curlnoise.pdf 26 | 27 | fn t1() -> f32 { 28 | return uniforms.currentFrame * 10.5432895; 29 | } 30 | 31 | fn t2() -> f32 { 32 | return uniforms.currentFrame * 20.5432895; 33 | } 34 | 35 | fn t3() -> f32 { 36 | return uniforms.currentFrame * 5.535463; 37 | } 38 | 39 | fn t4() -> f32 { 40 | return -uniforms.currentFrame * 13.534534; 41 | } 42 | 43 | fn t5() -> f32 { 44 | return uniforms.currentFrame * 54.42345; 45 | } 46 | 47 | fn t6() -> f32 { 48 | return - uniforms.currentFrame * 23.53450; 49 | } 50 | 51 | fn t7() -> f32 { 52 | return - uniforms.currentFrame * 45.5345354313; 53 | } 54 | 55 | fn t8() -> f32 { 56 | return uniforms.currentFrame * 23.4234521243; 57 | } 58 | 59 | fn dP3dY( v: vec3) -> f32 { 60 | var noise = 0.0; 61 | noise += 3. * cos(v.z * 1.8 + v.y * 3. - 194.58 + t1() ) + 4.5 * cos(v.z * 4.8 + v.y * 4.5 - 83.13 + t2() ) + 1.2 * cos(v.z * -7.0 + v.y * 1.2 -845.2 + t3() ) + 2.13 * cos(v.z * -5.0 + v.y * 2.13 - 762.185 + t4() ); 62 | noise += 5.4 * cos(v.x * -0.48 + v.y * 5.4 - 707.916 + t5() ) + 5.4 * cos(v.x * 2.56 + v.y * 5.4 + -482.348 + t6() ) + 2.4 * cos(v.x * 4.16 + v.y * 2.4 + 9.872 + t7() ) + 1.35 * cos(v.x * -4.16 + v.y * 1.35 - 476.747 + t8() ); 63 | return noise; 64 | } 65 | 66 | fn dP2dZ( v: vec3) -> f32 { 67 | return -0.48 * cos(v.z * -0.48 + v.x * 5.4 -125.796 + t5() ) + 2.56 * cos(v.z * 2.56 + v.x * 5.4 + 17.692 + t6() ) + 4.16 * cos(v.z * 4.16 + v.x * 2.4 + 150.512 + t7() ) -4.16 * cos(v.z * -4.16 + v.x * 1.35 - 222.137 + t8() ); 68 | } 69 | 70 | fn dP1dZ( v: vec3) -> f32 { 71 | var noise = 0.0; 72 | noise += 3. * cos(v.x * 1.8 + v.z * 3. + t1() ) + 4.5 * cos(v.x * 4.8 + v.z * 4.5 + t2() ) + 1.2 * cos(v.x * -7.0 + v.z * 1.2 + t3() ) + 2.13 * cos(v.x * -5.0 + v.z * 2.13 + t4() ); 73 | noise += 5.4 * cos(v.y * -0.48 + v.z * 5.4 + t5() ) + 5.4 * cos(v.y * 2.56 + v.z * 5.4 + t6() ) + 2.4 * cos(v.y * 4.16 + v.z * 2.4 + t7() ) + 1.35 * cos(v.y * -4.16 + v.z * 1.35 + t8() ); 74 | return noise; 75 | } 76 | 77 | fn dP3dX( v: vec3) -> f32 { 78 | return -0.48 * cos(v.x * -0.48 + v.y * 5.4 - 707.916 + t5() ) + 2.56 * cos(v.x * 2.56 + v.y * 5.4 + -482.348 + t6() ) + 4.16 * cos(v.x * 4.16 + v.y * 2.4 + 9.872 + t7() ) -4.16 * cos(v.x * -4.16 + v.y * 1.35 - 476.747 + t8() ); 79 | } 80 | 81 | fn dP2dX( v: vec3) -> f32 { 82 | var noise = 0.0; 83 | noise += 3. * cos(v.y * 1.8 + v.x * 3. - 2.82 + t1() ) + 4.5 * cos(v.y * 4.8 + v.x * 4.5 + 74.37 + t2() ) + 1.2 * cos(v.y * -7.0 + v.x * 1.2 - 256.72 + t3() ) + 2.13 * cos(v.y * -5.0 + v.x * 2.13 - 207.683 + t4() ); 84 | noise += 5.4 * cos(v.z * -0.48 + v.x * 5.4 -125.796 + t5() ) + 5.4 * cos(v.z * 2.56 + v.x * 5.4 + 17.692 + t6() ) + 2.4 * cos(v.z * 4.16 + v.x * 2.4 + 150.512 + t7() ) + 1.35 * cos(v.z * -4.16 + v.x * 1.35 - 222.137 + t8() ); 85 | return noise; 86 | } 87 | 88 | fn dP1dY( v: vec3) -> f32 { 89 | return -0.48 * cos(v.y * -0.48 + v.z * 5.4 + t5() ) + 2.56 * cos(v.y * 2.56 + v.z * 5.4 + t6() ) + 4.16 * cos(v.y * 4.16 + v.z * 2.4 + t7() ) -4.16 * cos(v.y * -4.16 + v.z * 1.35 + t8()); 90 | } 91 | 92 | fn curlNoise(p : vec3 ) -> vec3 { 93 | let x = dP3dY(p) - dP2dZ(p); 94 | let y = dP1dZ(p) - dP3dX(p); 95 | let z = dP2dX(p) - dP1dY(p); 96 | return normalize(vec3(x, y, z)); 97 | } 98 | 99 | 100 | @compute @workgroup_size(256) fn main( @builtin(global_invocation_id) id: vec3 ) { 101 | 102 | let i = id.x; 103 | var ii = id.x; 104 | let tt = u32(uniforms.totalParticles); 105 | if(ii >= tt) { 106 | ii = tt - i % tt; 107 | } 108 | 109 | //Apply the forces 110 | var planeIndex = positionBuffer[i].a; 111 | var position = positionBuffer[i].rgb; 112 | var velocity = velocityBuffer[i].rgb; 113 | var origin = resetBuffer[ii].rgb; 114 | 115 | //Apply different noise function 116 | //position -= vec3f(0.16) * curlNoise( .0125 * pos ); 117 | var dt = uniforms.deltaTime; 118 | var acceleration = vec3f(0.); 119 | var noiseAcceleration = uniforms.acceleration; 120 | var transition = uniforms.transition * 3.; 121 | var delta = 1.; 122 | 123 | var amp = 150.; 124 | var freq = .01; 125 | for(var k = 0; k < 2; k ++) { 126 | var c = curlNoise(freq * position ); 127 | // c.x *= 0.1; 128 | noiseAcceleration += amp * c; 129 | amp /= 2.; 130 | freq *= 2.; 131 | } 132 | 133 | // noiseAcceleration += .01 * (vec3(uniforms.gridResolution * 0.5) - position); 134 | 135 | var resetAcceleration = 20. * (origin - position); 136 | 137 | if(transition < 1.) { 138 | acceleration = noiseAcceleration; 139 | } else { 140 | var transitionIndex = min(max(transition - planeIndex / uniforms.gridResolution - 1., 0.), 1.); 141 | // transitionIndex = pow(transitionIndex, 4.); 142 | acceleration = mix(noiseAcceleration, resetAcceleration + noiseAcceleration * (1. - transitionIndex), vec3f(transitionIndex)); 143 | } 144 | 145 | var p1 = uniforms.cameraOrientation * vec4f(position, 1.); 146 | var p2 = uniforms.cameraOrientation * vec4f(uniforms.mousePosition, 1.); 147 | var intensity = 1. - length(p2.xy - p1.xy) / (5. + 10. * clamp(length(uniforms.mouseDirection), 0, 1) ); 148 | intensity = clamp(intensity, 0., 1.); 149 | acceleration += 0.1 * uniforms.mouseDirection * intensity / (dt * dt); 150 | 151 | 152 | position = position + dt * (velocity + dt * acceleration); 153 | 154 | 155 | //Save back the position 156 | positionBuffer[i] = vec4f(position, planeIndex); 157 | 158 | //Place particles inside the grid acceleration 159 | 160 | let textureSize = u32(uniforms.gridResolution); 161 | 162 | //3d index for the grid acceleration 163 | let voxelPosition = vec3( floor(position) ); 164 | 165 | //1d index for the atomic buffer 166 | let index1D = voxelPosition.x + textureSize * voxelPosition.y + textureSize * textureSize * voxelPosition.z; 167 | 168 | //Increase the counter and set the index for the 3d indices buffer 169 | let amountOfParticlesInVoxel = atomicAdd(&counterBuffer[index1D], 1); 170 | if(amountOfParticlesInVoxel < 4) { 171 | indicesBuffer[ u32( u32(4 * index1D) + u32(amountOfParticlesInVoxel) )] = i; 172 | } 173 | 174 | } -------------------------------------------------------------------------------- /utils/camera.js: -------------------------------------------------------------------------------- 1 | import { vec3, mat4 } from "gl-matrix"; 2 | 3 | class Camera { 4 | 5 | constructor(canvas) { 6 | 7 | this.block = false; 8 | this.position = vec3.create(); 9 | this.down = false; 10 | this.prevMouseX = 0; 11 | this.prevMouseY = 0; 12 | this.currentMouseX = 0; 13 | this.currentMouseY = 0; 14 | 15 | this.alpha = Math.PI * 0.5; 16 | this.beta = -Math.PI * 0.5; 17 | this._alpha = this.alpha; 18 | this._beta = this.beta; 19 | 20 | this._alpha2 = this.alpha; 21 | this._beta2 = this.beta; 22 | 23 | this.gaze = true; 24 | 25 | this.ratio = 1; 26 | this.init = true; 27 | this.target = [0.5, 0.35, 0.5]; 28 | 29 | this.lerp = 0.1; 30 | this.lerp2 = 0.1; 31 | 32 | this.perspectiveMatrix = mat4.create(); 33 | this.cameraTransformMatrix = mat4.create(); 34 | this.orientationMatrix = mat4.create(); 35 | this.transformMatrix = mat4.create(); 36 | this.transformMatrixReflection = mat4.create(); 37 | 38 | 39 | canvas.style.cursor = "-moz-grab"; 40 | canvas.style.cursor = " -webkit-grab"; 41 | 42 | 43 | document.addEventListener('mousemove', (e) => { 44 | this.currentMouseX = e.clientX; 45 | this.currentMouseY = e.clientY; 46 | }, false); 47 | 48 | document.addEventListener('mousedown', (e) => { 49 | canvas.style.cursor = "-moz-grabbing"; 50 | canvas.style.cursor = " -webkit-grabbing"; 51 | this.down = true; 52 | }, false); 53 | 54 | document.addEventListener('mouseup', (e) => { 55 | canvas.style.cursor = "-moz-grab"; 56 | canvas.style.cursor = " -webkit-grab"; 57 | this.down = false; 58 | }, false); 59 | } 60 | 61 | 62 | 63 | updateCamera(perspective, aspectRatio, radius) { 64 | 65 | this.ratio = radius; 66 | 67 | mat4.perspective(this.perspectiveMatrix, perspective * Math.PI / 180, aspectRatio, 0.01, 1000); 68 | 69 | if (!this.block) { 70 | 71 | if (this.down) { 72 | this.alpha -= 0.1 * (this.currentMouseY - this.prevMouseY) * Math.PI / 180; 73 | this.beta += 0.1 * (this.currentMouseX - this.prevMouseX) * Math.PI / 180; 74 | } 75 | 76 | // if(this.gaze && !this.down) { 77 | // this.alpha = Math.PI / 2 - 1.0 * (this.currentMouseY - this.prevMouseY) * Math.PI / 180; 78 | // this.beta = Math.PI / 2 + 1.0 * (this.currentMouseX - this.prevMouseX) * Math.PI / 180; 79 | // } 80 | 81 | 82 | if (this.alpha <= 0.3 * Math.PI) this.alpha = 0.3 * Math.PI; 83 | if (this.alpha >= 0.52 * Math.PI) this.alpha = 0.52 * Math.PI; 84 | 85 | if (this.beta > -0.1 * Math.PI) this.beta = -0.1 * Math.PI; 86 | if (this.beta < -0.9 * Math.PI) this.beta = -0.9 * Math.PI; 87 | 88 | } 89 | 90 | this.lerp = this.down ? 0.2 : 0.05; 91 | this.lerp2 += (this.lerp - this.lerp2) * 0.15; 92 | 93 | 94 | if (this._alpha != this.alpha || this._beta != this.beta || this.init) { 95 | this._alpha += (this.alpha - this._alpha) * this.lerp2; 96 | this._beta += (this.beta - this._beta) * this.lerp2; 97 | 98 | this._alpha2 += (this._alpha - this._alpha2) * this.lerp2; 99 | this._beta2 += (this._beta - this._beta2) * this.lerp2; 100 | 101 | this.position[0] = this.ratio * Math.sin(this._alpha2) * Math.sin(this._beta2) + this.target[0]; 102 | this.position[1] = this.ratio * Math.cos(this._alpha2) + this.target[1]; 103 | this.position[2] = this.ratio * Math.sin(this._alpha2) * Math.cos(this._beta2) + this.target[2]; 104 | this.cameraTransformMatrix = this.defineTransformMatrix(this.position, this.target, [0, 1, 0]); 105 | for(let i = 0; i < 16; i++) { 106 | this.orientationMatrix[i] = this.cameraTransformMatrix[i]; 107 | } 108 | this.orientationMatrix[12] = 0; 109 | this.orientationMatrix[13] = 0; 110 | this.orientationMatrix[14] = 0; 111 | } 112 | this.prevMouseX = this.currentMouseX; 113 | this.prevMouseY = this.currentMouseY; 114 | 115 | mat4.multiply(this.transformMatrix, this.perspectiveMatrix, this.cameraTransformMatrix); 116 | } 117 | 118 | calculateReflection(pos, normal) { 119 | 120 | //𝑟=𝑑−2(𝑑⋅𝑛)𝑛 121 | let viewVec = vec3.fromValues(pos[0], pos[1], pos[2]); 122 | vec3.sub(viewVec, viewVec, this.position); 123 | let n1 = vec3.create(); 124 | vec3.scale(n1, normal, 2 * vec3.dot(viewVec, normal)); 125 | vec3.sub(viewVec, viewVec, n1); 126 | vec3.negate(viewVec, viewVec); 127 | vec3.add(viewVec, viewVec, pos); 128 | 129 | let targetVec = vec3.fromValues(pos[0], pos[1], pos[2]); 130 | vec3.sub(targetVec, targetVec, this.target); 131 | vec3.scale(n1, normal, 2 * vec3.dot(targetVec, normal)); 132 | vec3.sub(targetVec, targetVec, n1); 133 | vec3.negate(targetVec, targetVec); 134 | vec3.add(targetVec, targetVec, pos); 135 | 136 | let up = vec3.fromValues(0, -1, 0); 137 | this.reflectionPosition = viewVec; 138 | this.cameraReflectionMatrix = this.defineTransformMatrix2(viewVec, targetVec, up); 139 | 140 | mat4.multiply(this.transformMatrixReflection, this.perspectiveMatrix, this.cameraReflectionMatrix); 141 | 142 | } 143 | 144 | defineTransformMatrix(objectVector, targetVector, up) { 145 | let matrix = mat4.create(); 146 | let eyeVector = vec3.create(); 147 | let normalVector = vec3.create(); 148 | let upVector = vec3.create(); 149 | let rightVector = vec3.create(); 150 | let yVector = vec3.create(); 151 | 152 | yVector[0] = up[0]; 153 | yVector[1] = up[1]; 154 | yVector[2] = up[2]; 155 | 156 | vec3.subtract(eyeVector, objectVector, targetVector); 157 | 158 | vec3.normalize(normalVector, eyeVector); 159 | 160 | let reference = vec3.dot(normalVector, yVector); 161 | let reference2 = vec3.create(); 162 | 163 | vec3.scale(reference2, normalVector, reference); 164 | vec3.subtract(upVector, yVector, reference2); 165 | vec3.normalize(upVector, upVector); 166 | vec3.cross(rightVector, normalVector, upVector); 167 | 168 | matrix[0] = rightVector[0]; 169 | matrix[1] = upVector[0]; 170 | matrix[2] = normalVector[0]; 171 | matrix[3] = 0; 172 | matrix[4] = rightVector[1]; 173 | matrix[5] = upVector[1]; 174 | matrix[6] = normalVector[1]; 175 | matrix[7] = 0; 176 | matrix[8] = rightVector[2]; 177 | matrix[9] = upVector[2]; 178 | matrix[10] = normalVector[2]; 179 | matrix[11] = 0; 180 | matrix[12] = -vec3.dot(objectVector, rightVector); 181 | matrix[13] = -vec3.dot(objectVector, upVector); 182 | matrix[14] = -vec3.dot(objectVector, normalVector); 183 | matrix[15] = 1; 184 | return matrix; 185 | } 186 | 187 | defineTransformMatrix2(objectVector, targetVector, up) { 188 | let matrix = mat4.create(); 189 | let eyeVector = vec3.create(); 190 | let normalVector = vec3.create(); 191 | let upVector = vec3.create(); 192 | let rightVector = vec3.create(); 193 | let yVector = vec3.create(); 194 | 195 | yVector[0] = up[0]; 196 | yVector[1] = up[1]; 197 | yVector[2] = up[2]; 198 | 199 | vec3.subtract(eyeVector, objectVector, targetVector); 200 | 201 | vec3.normalize(normalVector, eyeVector); 202 | 203 | let reference = vec3.dot(normalVector, yVector); 204 | let reference2 = vec3.create(); 205 | 206 | vec3.scale(reference2, normalVector, reference); 207 | vec3.subtract(upVector, yVector, reference2); 208 | vec3.normalize(upVector, upVector); 209 | vec3.cross(rightVector, upVector, normalVector); 210 | 211 | matrix[0] = rightVector[0]; 212 | matrix[1] = upVector[0]; 213 | matrix[2] = normalVector[0]; 214 | matrix[3] = 0; 215 | matrix[4] = rightVector[1]; 216 | matrix[5] = upVector[1]; 217 | matrix[6] = normalVector[1]; 218 | matrix[7] = 0; 219 | matrix[8] = rightVector[2]; 220 | matrix[9] = upVector[2]; 221 | matrix[10] = normalVector[2]; 222 | matrix[11] = 0; 223 | matrix[12] = -vec3.dot(objectVector, rightVector); 224 | matrix[13] = -vec3.dot(objectVector, upVector); 225 | matrix[14] = -vec3.dot(objectVector, normalVector); 226 | matrix[15] = 1; 227 | return matrix; 228 | } 229 | } 230 | 231 | export {Camera} -------------------------------------------------------------------------------- /unrealBloom/UnrealBloom.js: -------------------------------------------------------------------------------- 1 | import * as Utils from "../utils/utils.js"; 2 | 3 | import luminosityShader from "../unrealBloom/UnrealBloomLuminosity.wgsl?raw"; 4 | import gaussianBlurShader from "../unrealBloom/UnrealBloomGaussian.wgsl?raw"; 5 | import bloomCompositeShader from "../unrealBloom/UnrealBloomComposite.wgsl?raw"; 6 | 7 | var _renderTargetsHorizontal = []; 8 | var _renderTargetsVertical = []; 9 | var _nMips = 3; 10 | var _kernelSizeArray = [3, 5, 7, 9, 11]; 11 | var _bloomFactors = [1.0, 0.8, 0.6, 0.4, 0.2]; 12 | 13 | var resolution; 14 | 15 | var device; 16 | 17 | var luminosityData, blurData, compositeData; 18 | 19 | var luminosityBuffer, compositeBuffer; 20 | var luminosityUniforms, compositeUniforms; 21 | 22 | var luminosityBindings, blurBindings, compositeBindings; 23 | 24 | var ready = false; 25 | 26 | var blurBuffers = null; 27 | 28 | async function initShaders() { 29 | 30 | luminosityUniforms = new Float32Array(8); 31 | luminosityBuffer = device.createBuffer({ 32 | label: "luminosity buffer", 33 | size: luminosityUniforms.byteLength, 34 | usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST 35 | }) 36 | 37 | 38 | luminosityData = await Utils.setupPipeline("luminosity", 39 | luminosityShader, 40 | ); 41 | 42 | 43 | blurData = await Utils.setupPipeline("gaussian blur", 44 | gaussianBlurShader, 45 | ); 46 | 47 | 48 | compositeUniforms = new Float32Array(8); 49 | compositeBuffer = device.createBuffer({ 50 | label: "blur buffer", 51 | size: luminosityUniforms.byteLength, 52 | usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST 53 | }) 54 | 55 | compositeData = await Utils.setupPipeline("gaussian blur", 56 | bloomCompositeShader 57 | ); 58 | } 59 | 60 | async function setup(width, height, inputTexture, outputTexture, sampler) { 61 | 62 | device = Utils.device; 63 | resolution = {x: width, y: height}; 64 | 65 | if(!ready) { 66 | await initShaders(); 67 | 68 | if(blurBuffers == null) { 69 | 70 | blurBuffers = []; 71 | 72 | //Generate the uniforms for the blur 73 | for(let i = 0; i < _nMips; i ++) { 74 | 75 | let blurBufferX = device.createBuffer({ 76 | size: 32, 77 | usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST 78 | }) 79 | 80 | let blurBufferY = device.createBuffer({ 81 | size: 32, 82 | usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST 83 | }) 84 | 85 | blurBuffers.push([blurBufferX, blurBufferY]); 86 | 87 | } 88 | 89 | } 90 | 91 | ready = true; 92 | } 93 | 94 | 95 | if(_renderTargetsHorizontal[0] != null) { 96 | _renderTargetsHorizontal.map(text => text.destroy()); 97 | _renderTargetsVertical.map(text => text.destroy()); 98 | } 99 | 100 | let resx = Math.round(resolution.x / 2); 101 | let resy = Math.round(resolution.y / 2); 102 | 103 | for (let i = 0; i < _nMips; i++) { 104 | 105 | let renderTargetHorizonal = device.createTexture({ 106 | label: `mip horizontal ${i}`, 107 | size: [resx, resy], 108 | format: 'rgba8unorm', 109 | dimension: '2d', 110 | usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING 111 | }) 112 | 113 | _renderTargetsHorizontal[i] = renderTargetHorizonal; 114 | 115 | let renderTargetVertical = device.createTexture({ 116 | label: `mip vertical ${i}`, 117 | size: [resx, resy], 118 | format: 'rgba8unorm', 119 | dimension: '2d', 120 | usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING 121 | }) 122 | 123 | _renderTargetsVertical[i] = renderTargetVertical; 124 | 125 | resx = Math.round(resx / 2); 126 | resy = Math.round(resy / 2); 127 | } 128 | 129 | 130 | luminosityBindings = device.createBindGroup({ 131 | label: "luminosity bind group", 132 | layout: luminosityData.pipeline.getBindGroupLayout(0), 133 | entries: [ 134 | {binding: 0, resource: {buffer: luminosityBuffer}}, 135 | {binding: 1, resource: inputTexture.createView()}, 136 | {binding: 2, resource: sampler}, 137 | {binding: 3, resource: outputTexture.createView()}, 138 | ] 139 | }) 140 | 141 | 142 | resx = Math.round(resolution.x / 2); 143 | resy = Math.round(resolution.y / 2); 144 | 145 | blurBindings = []; 146 | var inputBlurTexture = outputTexture; 147 | 148 | for(let i = 0; i < _nMips; i ++) { 149 | 150 | let bindingUniforms = new Float32Array([resx, resy, 1, 0, _kernelSizeArray[i], 0, 0, 0]); 151 | device.queue.writeBuffer(blurBuffers[i][0], 0, bindingUniforms); 152 | 153 | bindingUniforms = new Float32Array([resx, resy, 0, 1, _kernelSizeArray[i], 0, 0, 0]); 154 | device.queue.writeBuffer(blurBuffers[i][1], 0, bindingUniforms); 155 | 156 | let bindingX = device.createBindGroup({ 157 | layout: blurData.pipeline.getBindGroupLayout(0), 158 | entries: [ 159 | {binding: 0, resource: {buffer: blurBuffers[i][0]}}, 160 | {binding: 1, resource: inputBlurTexture.createView() }, 161 | {binding: 2, resource: sampler}, 162 | {binding: 3, resource: _renderTargetsVertical[i].createView()} 163 | ] 164 | }) 165 | 166 | let bindingY = device.createBindGroup({ 167 | layout: blurData.pipeline.getBindGroupLayout(0), 168 | entries: [ 169 | {binding: 0, resource: {buffer: blurBuffers[i][1]}}, 170 | {binding: 1, resource: _renderTargetsVertical[i].createView() }, 171 | {binding: 2, resource: sampler}, 172 | {binding: 3, resource: _renderTargetsHorizontal[i].createView()} 173 | ] 174 | }) 175 | 176 | inputBlurTexture = _renderTargetsHorizontal[i]; 177 | blurBindings.push([bindingX, bindingY]); 178 | 179 | resx = Math.round(resx / 2); 180 | resy = Math.round(resy / 2); 181 | 182 | } 183 | 184 | compositeBindings = device.createBindGroup({ 185 | label: "bind group for composite", 186 | layout: compositeData.pipeline.getBindGroupLayout(0), 187 | entries: [ 188 | {binding: 0, resource: {buffer: compositeBuffer}}, 189 | {binding: 1, resource: sampler}, 190 | {binding: 2, resource: _renderTargetsHorizontal[0].createView() }, 191 | {binding: 3, resource: _renderTargetsHorizontal[1].createView() }, 192 | {binding: 4, resource: _renderTargetsHorizontal[2].createView() }, 193 | {binding: 5, resource: inputTexture.createView() }, 194 | {binding: 6, resource: outputTexture.createView() } 195 | 196 | ] 197 | }) 198 | } 199 | 200 | 201 | 202 | function applyBloom(encoder) { 203 | 204 | if(!ready) return; 205 | 206 | //luminosity color 207 | luminosityUniforms[0] = 0; 208 | luminosityUniforms[1] = 0; 209 | luminosityUniforms[2] = 0; 210 | 211 | //luminosity opacity 212 | luminosityUniforms[3] = 1; 213 | 214 | //Luminosity threshold 215 | luminosityUniforms[4] = 0.799; 216 | 217 | //smoothWidth 218 | luminosityUniforms[5] = 1.; 219 | 220 | device.queue.writeBuffer(luminosityBuffer, 0, luminosityUniforms); 221 | 222 | const pass = encoder.beginComputePass(luminosityData.passDescriptor); 223 | pass.setPipeline(luminosityData.pipeline); 224 | pass.setBindGroup(0, luminosityBindings); 225 | pass.dispatchWorkgroups(window.innerWidth, window.innerHeight); 226 | pass.end(); 227 | 228 | 229 | 230 | //Make the different blur passes 231 | let resx = Math.round(resolution.x / 2); 232 | let resy = Math.round(resolution.y / 2); 233 | 234 | const blurPass = encoder.beginComputePass(blurData.passDescriptor); 235 | blurPass.setPipeline(blurData.pipeline); 236 | 237 | for(let i = 0; i < blurBindings.length; i ++) { 238 | 239 | for(let j = 0; j < 2; j ++) { 240 | 241 | blurPass.setBindGroup(0, blurBindings[i][j]); 242 | blurPass.dispatchWorkgroups(resx, resy); 243 | 244 | } 245 | 246 | resx = Math.round(resx / 2); 247 | resy = Math.round(resy / 2); 248 | } 249 | 250 | blurPass.end(); 251 | 252 | 253 | //Color for the composite 254 | compositeUniforms[0] = 1.; 255 | compositeUniforms[1] = 1.; 256 | compositeUniforms[2] = 1.; 257 | 258 | //Bloom stregth 259 | compositeUniforms[3] = 1.8; 260 | 261 | //Bloom radius 262 | compositeUniforms[4] = .1; 263 | 264 | device.queue.writeBuffer(compositeBuffer, 0, compositeUniforms); 265 | 266 | //Dispatch the composite pass 267 | const compositePass = encoder.beginComputePass(compositeData.passDescriptor); 268 | compositePass.setPipeline(compositeData.pipeline); 269 | compositePass.setBindGroup(0, compositeBindings); 270 | compositePass.dispatchWorkgroups(window.innerWidth, window.innerHeight); 271 | compositePass.end(); 272 | 273 | } 274 | 275 | export { 276 | setup, 277 | applyBloom 278 | } -------------------------------------------------------------------------------- /utils/utils.js: -------------------------------------------------------------------------------- 1 | 2 | import { parseBindings } from "./shaderParser"; 3 | 4 | Promise.create = function() { 5 | const promise = new Promise((resolve, reject) => { 6 | this.temp_resolve = resolve; 7 | this.temp_reject = reject; 8 | }); 9 | promise.resolve = this.temp_resolve; 10 | promise.reject = this.temp_reject; 11 | delete this.temp_resolve; 12 | delete this.temp_reject; 13 | return promise; 14 | }; 15 | 16 | var device = null; 17 | 18 | let getDevice = async _ => { 19 | 20 | const twoGig = 2147483648; 21 | const requiredLimits = {}; 22 | requiredLimits.maxStorageBufferBindingSize = twoGig; 23 | requiredLimits.maxBufferSize = twoGig; 24 | 25 | const adapter = await navigator.gpu?.requestAdapter(); 26 | device = await adapter?.requestDevice({ 27 | requiredFeatures: ["float32-filterable"], 28 | // requiredLimits 29 | } 30 | ); 31 | if(!device) { 32 | console.log("error finding device"); 33 | return null; 34 | } 35 | return device; 36 | } 37 | 38 | 39 | let getShader = async path => { 40 | return path; 41 | } 42 | 43 | let getPipeline = async (path) => { 44 | 45 | //let ready = Promise.create(); 46 | 47 | const shader = await getShader(path); 48 | const module = device.createShaderModule({ 49 | label: `${path} module`, 50 | code: shader 51 | }) 52 | 53 | let groups = parseBindings(shader, GPUShaderStage.COMPUTE); 54 | const layoutEntries = groups.map(_layout => device.createBindGroupLayout({entries: _layout})); 55 | const _layout = device.createPipelineLayout({ 56 | bindGroupLayouts: layoutEntries, 57 | }); 58 | 59 | let pipeline = device.createComputePipeline( 60 | { 61 | label: `${path} pipeline`, 62 | layout: _layout, 63 | compute: { 64 | module: module, 65 | entryPoint: "main" 66 | } 67 | } 68 | ) 69 | 70 | //ready.resolve(); 71 | 72 | return { 73 | pipeline 74 | } 75 | } 76 | 77 | const random = (min, max) => { 78 | if(min === undefined) { 79 | min = 0; 80 | max = 1; 81 | } else { 82 | if(max === undefined) { 83 | max = min; 84 | min = 0; 85 | } 86 | } 87 | 88 | return min + Math.random() * (max - min); 89 | } 90 | 91 | class PipelineData { 92 | constructor() { 93 | this.label = null; 94 | this.passDescriptor = null; 95 | this.pipeline = null; 96 | this.bindGroup = null; 97 | this.uniformsData = null; 98 | this.uniformsBuffer = null; 99 | } 100 | 101 | setBindGroup = entries => { 102 | this.bindGroup = device.createBindGroup( { 103 | label:`${this.label} bind group`, 104 | layout: this.pipeline.getBindGroupLayout(0), 105 | entries 106 | }) 107 | } 108 | } 109 | 110 | 111 | async function setupRenderingPipeline(label, shaderPath, sampleCount = 1, targets, depthEnabled = true) { 112 | 113 | let pipelineData = new PipelineData(); 114 | 115 | const shader = await getShader(shaderPath); 116 | const module = device.createShaderModule( 117 | { 118 | label: `${label} module`, 119 | code: shader 120 | } 121 | ) 122 | 123 | let groups = parseBindings(shader, GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT); 124 | const layoutEntries = groups.map(_layout => device.createBindGroupLayout({entries: _layout})); 125 | const _layout = device.createPipelineLayout({ 126 | bindGroupLayouts: layoutEntries, 127 | }); 128 | 129 | let pipelineObject = { 130 | label: `${label} pipeline`, 131 | layout: _layout, 132 | vertex: { 133 | module, 134 | entryPoint: 'vs' 135 | }, 136 | fragment: { 137 | module, 138 | entryPoint: 'fs', 139 | targets 140 | }, 141 | primitive: { 142 | topology: 'triangle-list', 143 | 144 | // Backface culling since the cube is solid piece of geometry. 145 | // Faces pointing away from the camera will be occluded by faces 146 | // pointing toward the camera. 147 | cullMode: 'none', 148 | }, 149 | multisample: { 150 | count: sampleCount 151 | } 152 | } 153 | 154 | const renderPassDescriptor = { 155 | label: `${label} rendering pass descriptor`, 156 | colorAttachments: [] 157 | 158 | } 159 | 160 | if(depthEnabled) { 161 | pipelineObject.depthStencil = { 162 | depthWriteEnabled: true, 163 | depthCompare: 'less', 164 | format: 'depth32float' 165 | } 166 | renderPassDescriptor.depthStencilAttachment = { 167 | depthClearValue: 1.0, 168 | depthStoreOp: 'store' 169 | } 170 | } 171 | 172 | const pipeline = device.createRenderPipeline(pipelineObject); 173 | 174 | 175 | 176 | targets.map(_ => { 177 | renderPassDescriptor.colorAttachments.push( 178 | { 179 | clearValue: [0, 0, 0, 0], 180 | storeOp: "store" 181 | } 182 | ) 183 | }) 184 | 185 | pipelineData.label = label; 186 | pipelineData.pipeline = pipeline; 187 | pipelineData.passDescriptor = renderPassDescriptor; 188 | 189 | return pipelineData; 190 | } 191 | 192 | 193 | async function setupPipeline(label, 194 | shaderPath, 195 | uniforms = null, 196 | bindingBuffers = null) { 197 | 198 | let pipelineData = new PipelineData(); 199 | let pipelineReady = Promise.create(); 200 | 201 | getPipeline(shaderPath).then( 202 | response => { 203 | pipelineData.pipeline = response.pipeline; 204 | pipelineReady.resolve(); 205 | } 206 | ); 207 | 208 | await pipelineReady; 209 | 210 | pipelineData.label = label; 211 | 212 | if(uniforms) { 213 | pipelineData.uniformsData = new Float32Array(uniforms); 214 | 215 | pipelineData.uniformsBuffer = device.createBuffer( 216 | { 217 | label:`${label} uniforms buffer`, 218 | size: pipelineData.uniformsData.byteLength, 219 | usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST 220 | } 221 | ) 222 | 223 | device.queue.writeBuffer(pipelineData.uniformsBuffer, 0, pipelineData.uniformsData); 224 | } 225 | 226 | 227 | if(bindingBuffers) { 228 | let entries = bindingBuffers.map( (data, index) => {return {binding: index, resource: {buffer: data == "uniforms" ? pipelineData.uniformsBuffer : data}} }); 229 | 230 | pipelineData.bindGroup = device.createBindGroup( { 231 | label:`${label} bind group`, 232 | layout: pipelineData.pipeline.getBindGroupLayout(0), 233 | entries 234 | }) 235 | } 236 | 237 | console.log(`${label} ready`); 238 | 239 | return pipelineData; 240 | 241 | } 242 | 243 | async function get(path) { 244 | 245 | let result; 246 | let ready = Promise.create(); 247 | fetch(path).then(data => { 248 | data.json().then( response => { 249 | result = response; 250 | ready.resolve(); 251 | }) 252 | }) 253 | 254 | await ready; 255 | return result; 256 | } 257 | 258 | async function loadGeometry(label, path) { 259 | 260 | let result = await get(path); 261 | 262 | let buffersData = {} 263 | let buffers = {}; 264 | 265 | for(let id in result) { 266 | const data = new Float32Array(result[id]); 267 | let orderedData = data; 268 | 269 | if(id == "position" || id == "normal") { 270 | 271 | orderedData = []; 272 | for(let i = 0; i < data.length; i += 3) { 273 | orderedData.push(data[i + 0]); 274 | orderedData.push(data[i + 1]); 275 | orderedData.push(data[i + 2]); 276 | orderedData.push(1); 277 | } 278 | buffers.length = orderedData.length / 4; 279 | orderedData = new Float32Array(orderedData); 280 | } 281 | 282 | buffersData[id] = orderedData; 283 | 284 | } 285 | 286 | //Encode the UV into the position and normal 287 | 288 | let posIndex = 0; 289 | for(let i = 0; i < buffersData.uv.length; i += 2) { 290 | buffersData.position[posIndex + 3] = buffersData.uv[i + 0]; 291 | buffersData.normal[posIndex + 3] = buffersData.uv[i + 1]; 292 | posIndex += 4; 293 | } 294 | 295 | let ids = {position: "", normal: ""} 296 | 297 | for(let id in ids) { 298 | 299 | buffers[id] = device.createBuffer({ 300 | label: `${label} ${id} buffer`, 301 | size: buffersData[id].byteLength, 302 | usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC 303 | }); 304 | 305 | device.queue.writeBuffer(buffers[id], 0, buffersData[id]); 306 | } 307 | 308 | return buffers; 309 | } 310 | 311 | async function readBuffer(device, buffer) { 312 | const size = buffer.size; 313 | const gpuReadBuffer = device.createBuffer({size, usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ }); 314 | const copyEncoder = device.createCommandEncoder(); 315 | copyEncoder.copyBufferToBuffer(buffer, 0, gpuReadBuffer, 0, size); 316 | const copyCommands = copyEncoder.finish(); 317 | device.queue.submit([copyCommands]); 318 | await gpuReadBuffer.mapAsync(GPUMapMode.READ); 319 | return gpuReadBuffer.getMappedRange(); 320 | } 321 | 322 | export { 323 | getDevice, 324 | device, 325 | getShader, 326 | random, 327 | getPipeline, 328 | setupPipeline, 329 | setupRenderingPipeline, 330 | loadGeometry, 331 | readBuffer 332 | } 333 | -------------------------------------------------------------------------------- /marchingCubes/TrianglesGenerator.js: -------------------------------------------------------------------------------- 1 | import * as Utils from "../utils/utils.js"; 2 | import {ti5, trianglesOnVoxels} from './marchingCubesTables.js'; 3 | 4 | import marchCaseShader from "./MarchCase.wgsl?raw"; 5 | import checkPyramidShader from "./EncodeBuffer.wgsl?raw"; 6 | import trianglesShader from "./GenerateTriangles.wgsl?raw"; 7 | 8 | 9 | let marchCasePipeline, marchCaseBindGroup; 10 | let checkPipeline, checkBindGroup; 11 | let parsePipeline, parseBindGroup; 12 | let textureSize; 13 | let amountOfTriangles, amountOfTrianglesBuffer; 14 | let indicesArray, indicesBuffer; 15 | 16 | let marchCaseReady = Promise.create(); 17 | let checkReady = Promise.create(); 18 | let parseReady = Promise.create(); 19 | 20 | let texture, 21 | texture_vct, 22 | verticesBuffer, 23 | normalBuffer, 24 | velocityBuffer, 25 | checkBuffer; 26 | 27 | let testBuffer1, testBuffer2; 28 | 29 | let uniforms, uniformsBuffer; 30 | 31 | let device; 32 | 33 | 34 | async function setupMarchingCubes( vertexMemory, 35 | _texture, 36 | _texture_vct) { 37 | 38 | 39 | device = Utils.device; 40 | 41 | texture = _texture; 42 | texture_vct = _texture_vct; 43 | 44 | const TOTAL_VOXELS = Math.pow(texture.width, 3); 45 | 46 | verticesBuffer = device.createBuffer( 47 | { 48 | label: "vertices buffer", 49 | size: 4 * vertexMemory, 50 | usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC 51 | } 52 | ) 53 | 54 | normalBuffer = device.createBuffer( 55 | { 56 | label: "normals buffer", 57 | size: 4 * vertexMemory, 58 | usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC 59 | } 60 | ) 61 | 62 | velocityBuffer = device.createBuffer( 63 | { 64 | label: "velocity buffer", 65 | size: 4 * vertexMemory, 66 | usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC 67 | } 68 | ) 69 | 70 | checkBuffer = device.createBuffer( 71 | { 72 | label: "check buffer", 73 | size: 4 * 7, 74 | usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC | GPUBufferUsage.INDIRECT 75 | } 76 | ) 77 | 78 | testBuffer1 = device.createBuffer( 79 | { 80 | label: "test buffer", 81 | size: 4 * vertexMemory, 82 | usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC 83 | } 84 | ) 85 | 86 | testBuffer2 = device.createBuffer( 87 | { 88 | label: "test 2 buffer", 89 | size: 4 * 7, 90 | usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC | GPUBufferUsage.INDIRECT 91 | } 92 | ) 93 | 94 | //Amount of triangles for each voxel 95 | amountOfTriangles = []; 96 | for(let i = 0; i < trianglesOnVoxels.length; i++) { 97 | let u = trianglesOnVoxels[i].length / 3; 98 | amountOfTriangles.push(u); 99 | } 100 | amountOfTriangles = new Float32Array(amountOfTriangles); 101 | amountOfTrianglesBuffer = device.createBuffer( 102 | { 103 | label: "uniforms buffer", 104 | size: amountOfTriangles.byteLength, 105 | usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST 106 | } 107 | ) 108 | device.queue.writeBuffer(amountOfTrianglesBuffer, 0, amountOfTriangles); 109 | 110 | //Indices for the triangles 111 | indicesArray = new Float32Array(ti5); 112 | indicesBuffer = device.createBuffer( 113 | { 114 | label: "indices buffer", 115 | size: indicesArray.byteLength, 116 | usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST 117 | } 118 | ) 119 | device.queue.writeBuffer(indicesBuffer, 0, indicesArray); 120 | 121 | 122 | textureSize = Math.ceil(Math.sqrt(Math.pow(texture.width, 3))); 123 | let power = Math.ceil(Math.log2(textureSize)); 124 | textureSize = Math.pow(2, power); 125 | power += 1; 126 | 127 | 128 | Utils.getPipeline(marchCaseShader).then( response => { 129 | marchCasePipeline = response.pipeline; 130 | marchCaseReady.resolve(); 131 | }); 132 | 133 | Utils.getPipeline(checkPyramidShader).then( response => { 134 | checkPipeline = response.pipeline; 135 | checkReady.resolve(); 136 | }); 137 | 138 | Utils.getPipeline(trianglesShader).then( response => { 139 | parsePipeline = response.pipeline; 140 | parseReady.resolve(); 141 | }) 142 | 143 | await marchCaseReady; 144 | await checkReady; 145 | await parseReady; 146 | 147 | 148 | uniforms = new Float32Array([texture.width, textureSize, power, 0.5]); 149 | 150 | uniformsBuffer = device.createBuffer( 151 | { 152 | label: "uniforms buffer", 153 | size: uniforms.byteLength, 154 | usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST 155 | } 156 | ) 157 | 158 | device.queue.writeBuffer(uniformsBuffer, 0, uniforms); 159 | 160 | marchCaseBindGroup = device.createBindGroup({ 161 | label: "bind group for march case", 162 | layout: marchCasePipeline.getBindGroupLayout(0), 163 | entries: [ 164 | { 165 | binding: 0, resource: {buffer: uniformsBuffer} 166 | }, 167 | { 168 | binding: 1, resource: texture.createView({ 169 | baseMipLevel: 0, 170 | mipLevelCount: 1 171 | } 172 | )}, 173 | { 174 | binding: 2, resource: {buffer: amountOfTrianglesBuffer} 175 | }, 176 | { 177 | binding: 3, resource: texture_vct.createView({ 178 | baseMipLevel: 0, 179 | mipLevelCount: 1 180 | }) 181 | }, 182 | { 183 | binding: 4, resource: {buffer: testBuffer1} 184 | }, 185 | { 186 | binding: 5, resource: {buffer: testBuffer2} 187 | }, 188 | ] 189 | }) 190 | 191 | 192 | checkBindGroup = device.createBindGroup({ 193 | label: "bind group for check case", 194 | layout: checkPipeline.getBindGroupLayout(0), 195 | entries: [ 196 | { 197 | binding: 0, 198 | resource: {buffer: testBuffer2} 199 | }, 200 | { 201 | binding: 1, 202 | resource: {buffer: checkBuffer} 203 | } 204 | ] 205 | }) 206 | 207 | 208 | parseBindGroup = device.createBindGroup({ 209 | label: "bind group for pyramid parsing", 210 | layout: parsePipeline.getBindGroupLayout(0), 211 | entries: [ 212 | { 213 | binding: 0, 214 | resource: {buffer: uniformsBuffer} 215 | }, 216 | { 217 | binding: 1, 218 | resource: {buffer: testBuffer1} 219 | }, 220 | { 221 | binding: 2, 222 | resource: {buffer: indicesBuffer} 223 | }, 224 | { 225 | binding: 3, 226 | resource: texture.createView({ 227 | baseMipLevel: 0, 228 | mipLevelCount: 1 229 | }) 230 | }, 231 | { 232 | binding: 4, 233 | resource: {buffer: verticesBuffer} 234 | }, 235 | { 236 | binding: 5, 237 | resource: {buffer: normalBuffer} 238 | }, 239 | { 240 | binding: 6, 241 | resource: {buffer: velocityBuffer} 242 | } 243 | ] 244 | }) 245 | 246 | return [ 247 | verticesBuffer, 248 | normalBuffer, 249 | velocityBuffer, 250 | checkBuffer 251 | ] 252 | } 253 | 254 | function generateTriangles(range) { 255 | 256 | uniforms[3] = range; 257 | device.queue.writeBuffer(uniformsBuffer, 0, uniforms); 258 | 259 | //make a command enconder to start encoding thigns 260 | const encoder = device.createCommandEncoder({ label: 'encoder'}); 261 | 262 | //Clear the corresponding buffers 263 | encoder.clearBuffer(verticesBuffer, 0, verticesBuffer.size); 264 | encoder.clearBuffer(normalBuffer, 0, normalBuffer.size); 265 | encoder.clearBuffer(testBuffer1, 0, testBuffer1.size); 266 | encoder.clearBuffer(testBuffer2, 0, testBuffer2.size); 267 | 268 | 269 | //Check which voxels require to generate triangles 270 | let size = texture.width; 271 | const marchPass = encoder.beginComputePass({ label: "march case pass" }) 272 | marchPass.setPipeline(marchCasePipeline); 273 | marchPass.setBindGroup(0, marchCaseBindGroup); 274 | marchPass.dispatchWorkgroups(size * 0.4, size, size); 275 | marchPass.end(); 276 | 277 | 278 | //Pass the sum from the pyramid to the buffer for further read 279 | const checkPass = encoder.beginComputePass({ 280 | label: "check pass" 281 | }) 282 | checkPass.setPipeline(checkPipeline); 283 | checkPass.setBindGroup(0, checkBindGroup); 284 | checkPass.dispatchWorkgroups(1); 285 | checkPass.end(); 286 | 287 | //Pass used to generate the triangles 288 | const parsePass = encoder.beginComputePass({ 289 | label: "parse pass" 290 | }) 291 | parsePass.setPipeline(parsePipeline); 292 | parsePass.setBindGroup(0, parseBindGroup); 293 | parsePass.dispatchWorkgroupsIndirect(checkBuffer, 0 * 4); 294 | parsePass.end(); 295 | 296 | 297 | const commandBuffer = encoder.finish(); 298 | device.queue.submit([commandBuffer]); 299 | 300 | // let totalVoxels = await Utils.readBuffer(device, lowGridBuffer); 301 | // totalVoxels = new Int32Array(totalVoxels); 302 | // console.log(totalVoxels); 303 | 304 | // let voxels = await Utils.readBuffer(device, verticesBuffer); 305 | 306 | 307 | 308 | // let dataArray = []; 309 | // let voxelsData = new Float32Array(voxels); 310 | // console.log(voxelsData); 311 | // for(let i = 0; i < vertexMemory; i ++) dataArray.push(voxelsData[i]); 312 | // console.log("the data is: " + totalVoxels, dataArray.filter(el => el == 1).length, totalTriangles, totalTriangles < 65000); 313 | 314 | } 315 | 316 | export {setupMarchingCubes, 317 | generateTriangles} -------------------------------------------------------------------------------- /simulation/PBF.js: -------------------------------------------------------------------------------- 1 | 2 | import { getLetter } from "../rendering/LetterGenerator.js"; 3 | import * as Utils from "../utils/utils.js"; 4 | 5 | import { vec3, mat4 } from "gl-matrix"; 6 | 7 | //Shaders 8 | import forcesShader from "../simulation/PBF_applyForces.wgsl?raw"; 9 | import displacementShader from "../simulation/PBF_calculateDisplacements.wgsl?raw"; 10 | import velocityShader from "../simulation/PBF_integrateVelocity.wgsl?raw"; 11 | import textureFillShader from "../simulation/textureClear.wgsl?raw"; 12 | 13 | const TRANSITION_FRAMES = 120; 14 | 15 | ///////////////////////////////////////////////////////////////////////////////////////// 16 | //Parameters for the PBF 17 | ///////////////////////////////////////////////////////////////////////////////////////// 18 | 19 | let searchRadius = 1.8; 20 | let constrainsIterations = 3; 21 | let pbfResolution = null; 22 | 23 | let _device = null; 24 | let _bufferSize = null; 25 | 26 | let _amountOfParticles; 27 | 28 | let currentFrame = 0; 29 | 30 | ///////////////////////////////////////////////////////////////////////////////////////// 31 | //Buffers for the PBF 32 | ///////////////////////////////////////////////////////////////////////////////////////// 33 | 34 | let positionBuffer, 35 | positionBuffer1, 36 | positionBuffer2, 37 | velocityBuffer, 38 | counterBuffer, 39 | indicesBuffer; 40 | 41 | ///////////////////////////////////////////////////////////////////////////////////////// 42 | //Pipelines, uniforms and bindings 43 | ///////////////////////////////////////////////////////////////////////////////////////// 44 | 45 | let forcesData; 46 | let displacementData; 47 | let velocityData; 48 | let textureData; 49 | let texture; 50 | 51 | let _camera; 52 | let mouse = vec3.create(); 53 | let prevMouse = vec3.create(); 54 | let mouseDirection = vec3.create(); 55 | let mouseForce = vec3.create(); 56 | 57 | let arriveBuffer = []; 58 | 59 | 60 | const queryString = window.location.search; 61 | const urlParams = new URLSearchParams(queryString); 62 | const customPhrase = urlParams.get('word') || "CODROPS"; 63 | 64 | let phrase = (customPhrase.toUpperCase()).split(""); 65 | let offsets = [0.57, 0.56, 0.583, 0.59, 0.56, 0.59, 0.56]; 66 | let currentLetter = 0; 67 | let currentAmountOfParticles = 0; 68 | let prevAmountOfParticles = 0; 69 | let maxAmountOfParticles = 0; 70 | 71 | let logoImage = new Image(); 72 | let logoImageReady = Promise.create(); 73 | 74 | function createLetter() { 75 | 76 | if(currentLetter > phrase.length - 1) { 77 | currentLetter = urlParams.get('word') ? 0 : -1; 78 | } 79 | 80 | // let letter = String.fromCharCode(index); 81 | let letter = phrase[Math.max(currentLetter, 0)]; 82 | let verticalOffset = offsets[Math.max(currentLetter, 0)]; 83 | 84 | //positions for the particles 85 | let letterArray = []; 86 | var r = 0.000; 87 | 88 | let resultArray = new Float32Array(_amountOfParticles * 4); 89 | 90 | const data = getLetter(letter, pbfResolution * 0.8, pbfResolution, verticalOffset, currentLetter == -1 ? logoImage : null); 91 | 92 | let planeIndex = 0; 93 | 94 | let totalParticlesAdded = 0; 95 | let i = 0.43 * pbfResolution; 96 | 97 | if(currentLetter > -1 ) { 98 | 99 | while (totalParticlesAdded < _amountOfParticles) { 100 | 101 | planeIndex = 0; 102 | 103 | for(let j = 0; j < pbfResolution; j ++) { 104 | 105 | planeIndex = 0; 106 | 107 | for(let k = 0; k < pbfResolution; k ++) { 108 | 109 | let index = pbfResolution - k + pbfResolution * pbfResolution - j * pbfResolution; 110 | let mask = data[4 * index] > 10; 111 | 112 | if(mask && totalParticlesAdded < _amountOfParticles) { 113 | var point = [i + Math.random() * r, j + Math.random() * r, k + Math.random() * r, planeIndex]; 114 | letterArray.push(point); 115 | totalParticlesAdded++; 116 | } 117 | 118 | planeIndex ++; 119 | } 120 | } 121 | 122 | i++; 123 | 124 | } 125 | 126 | } else { 127 | 128 | for(let j = 0; j < pbfResolution; j ++) { 129 | 130 | for(let i = 0; i < pbfResolution; i ++) { 131 | 132 | for(let k = 0; k < pbfResolution; k ++) { 133 | 134 | let x = Math.abs(i - pbfResolution * 0.5); 135 | let z = Math.abs(k - pbfResolution * 0.5); 136 | let rr = pbfResolution * 0.5 - Math.floor(Math.sqrt(x * x + z * z)); 137 | let index = pbfResolution - rr + pbfResolution * pbfResolution - j * pbfResolution; 138 | let mask = data[4 * index] > 10; 139 | 140 | if(mask && totalParticlesAdded < _amountOfParticles) { 141 | var point = [k + Math.random() * r + 0. * pbfResolution, j + Math.random() * r, i + Math.random() * r, planeIndex]; 142 | letterArray.push(point); 143 | totalParticlesAdded++; 144 | planeIndex ++; 145 | } 146 | 147 | } 148 | 149 | planeIndex = 0; 150 | } 151 | } 152 | } 153 | 154 | function compareFn(a, b) { 155 | return a[3] - b[3]; 156 | } 157 | 158 | letterArray = letterArray.sort(compareFn); 159 | letterArray = letterArray.flat(Infinity); 160 | 161 | currentAmountOfParticles = letterArray.length / 4; 162 | 163 | maxAmountOfParticles = Math.max(currentAmountOfParticles, prevAmountOfParticles); 164 | prevAmountOfParticles = currentAmountOfParticles; 165 | 166 | resultArray.set(letterArray, 0); 167 | return resultArray; 168 | } 169 | 170 | function generateBuffers() { 171 | 172 | let startLetter = createLetter(); 173 | 174 | positionBuffer = _device.createBuffer({ 175 | label: "position buffer", 176 | size: _bufferSize, 177 | usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC 178 | }) 179 | 180 | _device.queue.writeBuffer(positionBuffer, 0, startLetter); 181 | 182 | arriveBuffer = _device.createBuffer({ 183 | label: "next letter buffer", 184 | size: _bufferSize, 185 | usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC 186 | }) 187 | 188 | currentLetter ++; 189 | let newLetter = createLetter(); 190 | 191 | _device.queue.writeBuffer(arriveBuffer, 0, newLetter); 192 | 193 | positionBuffer1 = _device.createBuffer({ 194 | label: "position buffer 1", 195 | size: _bufferSize, 196 | usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC 197 | }) 198 | 199 | positionBuffer2 = _device.createBuffer({ 200 | label: "position buffer 2", 201 | size: _bufferSize, 202 | usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC 203 | }) 204 | 205 | velocityBuffer = _device.createBuffer({ 206 | label: "velocity buffer 1", 207 | size: _bufferSize, 208 | usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC 209 | }) 210 | 211 | indicesBuffer = _device.createBuffer( 212 | { 213 | label: "indices buffer data", 214 | size: Math.pow(pbfResolution, 3) * 4 * 4,//4 * 4 bytes = 4 * 32 bits single channel --> 4 channels 215 | usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC 216 | } 217 | ) 218 | 219 | counterBuffer = _device.createBuffer( 220 | { 221 | label: "counterBuffer buffer", 222 | size: Math.pow(pbfResolution, 3) * 4,//4 bytes = 32 bits single channel 223 | usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC 224 | } 225 | ) 226 | 227 | } 228 | 229 | async function setupPBF(resolution, amountOfParticles, texture3D, camera) { 230 | 231 | pbfResolution = resolution; 232 | _amountOfParticles = amountOfParticles; 233 | _bufferSize = amountOfParticles * 4 * 4; 234 | _device = Utils.device; 235 | texture = texture3D; 236 | _camera = camera; 237 | 238 | logoImage.addEventListener("load", _=> { 239 | console.log("image loaded"); 240 | logoImageReady.resolve(); 241 | }) 242 | 243 | logoImage.src = "./assets/drop120.png"; 244 | 245 | await logoImageReady; 246 | 247 | generateBuffers(); 248 | 249 | forcesData = await Utils.setupPipeline("forces", 250 | forcesShader, 251 | new Array(80).fill(0)); 252 | 253 | 254 | displacementData = await Utils.setupPipeline("displacement", 255 | displacementShader, 256 | [pbfResolution, searchRadius, 0], 257 | [positionBuffer1, positionBuffer2, indicesBuffer, "uniforms"]); 258 | 259 | 260 | velocityData = await Utils.setupPipeline("velocity", 261 | velocityShader, 262 | [0, pbfResolution, 0, 1]); 263 | 264 | 265 | forcesData.setBindGroup( 266 | [ 267 | {binding: 0, resource: {buffer: arriveBuffer}}, 268 | {binding: 1, resource: {buffer: positionBuffer1}}, 269 | {binding: 2, resource: {buffer: velocityBuffer}}, 270 | {binding: 3, resource: {buffer: counterBuffer}}, 271 | {binding: 4, resource: {buffer: indicesBuffer}}, 272 | {binding: 5, resource: {buffer: forcesData.uniformsBuffer}} 273 | 274 | ] 275 | ) 276 | 277 | 278 | velocityData.setBindGroup( 279 | [ 280 | {binding: 0, resource: {buffer: positionBuffer}}, 281 | {binding: 1, resource: {buffer: positionBuffer2}}, 282 | {binding: 2, resource: {buffer: velocityBuffer}}, 283 | {binding: 3, resource: {buffer: velocityData.uniformsBuffer}}, 284 | {binding: 4, resource: texture.createView( 285 | { 286 | baseMipLevel: 0, 287 | mipLevelCount: 1 288 | } 289 | )} 290 | ] 291 | ) 292 | 293 | 294 | textureData = await Utils.setupPipeline("fill texture", 295 | textureFillShader, 296 | [0]); 297 | 298 | textureData.setBindGroup([ 299 | {binding: 0, resource: texture.createView( 300 | { 301 | baseMipLevel: 0, 302 | mipLevelCount: 1 303 | } 304 | )}, 305 | {binding: 1, resource: {buffer: textureData.uniformsBuffer}}, 306 | ] 307 | ) 308 | 309 | document.addEventListener("mousemove", updateProjection); 310 | 311 | return positionBuffer; 312 | 313 | } 314 | 315 | function updateProjection(e) { 316 | 317 | let _x = 2 * e.clientX / window.innerWidth - 1; 318 | let _y = 1 - 2 * e.clientY / window.innerHeight; 319 | let _vNear = vec3.fromValues(_x, _y, 0); 320 | let _vFar = vec3.fromValues(_x, _y, 1); 321 | 322 | let inverseCamera = mat4.create(); 323 | let inversePerspective = mat4.create(); 324 | 325 | mat4.invert(inverseCamera, _camera.cameraTransformMatrix); 326 | mat4.invert(inversePerspective, _camera.perspectiveMatrix); 327 | 328 | let transform = mat4.create(); 329 | mat4.multiply(transform, inverseCamera, inversePerspective); 330 | 331 | vec3.transformMat4(_vNear, _vNear, transform); 332 | vec3.transformMat4(_vFar, _vFar, transform); 333 | 334 | vec3.scale(_vNear, _vNear, pbfResolution); 335 | vec3.scale(_vFar, _vFar, pbfResolution); 336 | 337 | let direction = vec3.create(); 338 | vec3.sub(direction, _vFar, _vNear); 339 | vec3.normalize(direction, direction); 340 | 341 | let planeNormal = vec3.fromValues(0, 0, -1); 342 | let planeOrigin = vec3.fromValues(0, 0, 0.35 * pbfResolution); 343 | 344 | vec3.transformMat4(planeNormal, planeNormal, _camera.orientationMatrix); 345 | vec3.transformMat4(planeOrigin, planeOrigin, _camera.orientationMatrix); 346 | 347 | let t = 0; 348 | const denom = vec3.dot(direction, planeNormal); 349 | if(denom > 0.0001) { 350 | vec3.sub(planeOrigin, planeOrigin, _vNear); 351 | t = vec3.dot(planeOrigin, planeNormal) / denom; 352 | } 353 | 354 | vec3.scale(direction, direction, t); 355 | vec3.add(_vNear, _vNear, direction); 356 | 357 | mouse[0] = _vNear[0]; 358 | mouse[1] = _vNear[1]; 359 | mouse[2] = _vNear[2]; 360 | } 361 | 362 | function updateFrame(acceleration = {x: 0, y: -10, z: 0}, deltaTime = 0.01, lightIntensity, separation, camera) { 363 | 364 | var totalFrames = TRANSITION_FRAMES; 365 | var animationFrame = currentFrame % totalFrames; 366 | 367 | const encoder = _device.createCommandEncoder({ label: 'encoder'}); 368 | 369 | //generate the new letter 370 | if(animationFrame == 0 && currentFrame > 10) { 371 | 372 | currentLetter++; 373 | let newLetter = createLetter(); 374 | _device.queue.writeBuffer(arriveBuffer, 0, newLetter); 375 | 376 | if(!camera.down) { 377 | camera.alpha = Math.PI * 0.5 - Math.random() * 0.1 * Math.PI; 378 | camera.beta = -Math.PI * 0.5 + (2 * Math.random() - 1) * Math.PI * 0.4; 379 | } 380 | 381 | } 382 | 383 | var relativeFrame = animationFrame / ( totalFrames); 384 | 385 | 386 | //Abstract compute pass generator 387 | function setupComputePass(pipelineData) { 388 | const pass = encoder.beginComputePass({ 389 | label: pipelineData.label 390 | }); 391 | 392 | pass.setPipeline(pipelineData.pipeline); 393 | pass.setBindGroup(0, pipelineData.bindGroup); 394 | pass.dispatchWorkgroups(maxAmountOfParticles / 256); 395 | pass.end(); 396 | } 397 | 398 | // //Abstract compute pass generator 399 | // function setupComputeTimestampPass(pipelineData) { 400 | // const pass = encoder.beginComputePass({ 401 | // label: pipelineData.label, 402 | // timestampWrites: { 403 | // querySet, 404 | // beginningOfPassWriteIndex: 0, // Write timestamp in index 0 when pass begins. 405 | // endOfPassWriteIndex: 1, // Write timestamp in index 1 when pass ends. 406 | // } 407 | // }); 408 | 409 | // pass.setPipeline(pipelineData.pipeline); 410 | // pass.setBindGroup(0, pipelineData.bindGroup); 411 | // pass.dispatchWorkgroups(currentAmountOfParticles / 256); 412 | // pass.end(); 413 | // } 414 | 415 | vec3.sub(mouseDirection, mouse, prevMouse); 416 | 417 | mouseForce[0] += (mouseDirection[0] - mouseForce[0]) * 0.1; 418 | mouseForce[1] += (mouseDirection[1] - mouseForce[1]) * 0.1; 419 | mouseForce[2] += (mouseDirection[2] - mouseForce[2]) * 0.1; 420 | 421 | //Sets the uniforms for the forces 422 | //Sets the uniforms for the forces 423 | for(let i = 0; i < 16; i ++) { 424 | forcesData.uniformsData[i] = _camera.orientationMatrix[i]; 425 | } 426 | 427 | forcesData.uniformsData[16] = acceleration.x; 428 | forcesData.uniformsData[17] = acceleration.y; 429 | forcesData.uniformsData[18] = acceleration.z; 430 | forcesData.uniformsData[19] = deltaTime; 431 | 432 | forcesData.uniformsData[20] = mouse[0]; 433 | forcesData.uniformsData[21] = mouse[1]; 434 | forcesData.uniformsData[22] = mouse[2]; 435 | forcesData.uniformsData[23] = pbfResolution; 436 | 437 | forcesData.uniformsData[24] = mouseForce[0]; 438 | forcesData.uniformsData[25] = mouseForce[1]; 439 | forcesData.uniformsData[26] = mouseForce[2]; 440 | forcesData.uniformsData[27] = currentFrame; 441 | forcesData.uniformsData[28] = relativeFrame; 442 | forcesData.uniformsData[29] = currentAmountOfParticles; 443 | 444 | 445 | _device.queue.writeBuffer(forcesData.uniformsBuffer, 0, forcesData.uniformsData); 446 | currentFrame += 1; 447 | 448 | 449 | //Sets the uniforms for the velocity integration 450 | velocityData.uniformsData[0] = deltaTime; 451 | velocityData.uniformsData[2] = lightIntensity; 452 | velocityData.uniformsData[3] = relativeFrame; 453 | _device.queue.writeBuffer(velocityData.uniformsBuffer, 0, velocityData.uniformsData); 454 | 455 | 456 | //Sets the uniforms for the velocity integration 457 | displacementData.uniformsData[2] = separation; 458 | _device.queue.writeBuffer(displacementData.uniformsBuffer, 0, displacementData.uniformsData); 459 | 460 | //Encoder for the PBF steps 461 | 462 | //Pass the actual frame to the helper buffer--> source, sourceOffset, destination, destinationOffset 463 | encoder.copyBufferToBuffer(positionBuffer, 0, positionBuffer1, 0, _bufferSize); 464 | 465 | 466 | //Set the counter and indices to 0 467 | encoder.clearBuffer(counterBuffer); 468 | encoder.clearBuffer(indicesBuffer); 469 | 470 | textureData.uniformsData[0] = lightIntensity; 471 | _device.queue.writeBuffer(textureData.uniformsBuffer, 0, textureData.uniformsData); 472 | 473 | //Apply forces over particles 474 | const pass = encoder.beginComputePass({label: textureData.label}); 475 | pass.setPipeline(textureData.pipeline); 476 | pass.setBindGroup(0, textureData.bindGroup); 477 | pass.dispatchWorkgroups(texture.width, texture.width, texture.width); 478 | pass.end(); 479 | 480 | 481 | //Apply forces over particles 482 | setupComputePass(forcesData); 483 | 484 | 485 | //Calculate the iterations 486 | for(let i = 0; i < constrainsIterations; i ++) { 487 | 488 | //Calculate the displacements 489 | setupComputePass(displacementData); 490 | 491 | //Sets the two helpers with the same data 492 | encoder.copyBufferToBuffer(positionBuffer2, 0, positionBuffer1, 0, _bufferSize); 493 | 494 | } 495 | 496 | //Integrate the velocity 497 | setupComputePass(velocityData); 498 | 499 | 500 | //Update the position 501 | encoder.copyBufferToBuffer(positionBuffer1, 0, positionBuffer, 0, _bufferSize); 502 | 503 | 504 | //Send the data to the GPU 505 | const commandBuffer = encoder.finish(); 506 | 507 | _device.queue.submit([commandBuffer]); 508 | 509 | return { 510 | animationFrame, 511 | relativeFrame, 512 | currentLetter 513 | }; 514 | 515 | } 516 | 517 | 518 | 519 | 520 | export{ 521 | setupPBF, 522 | updateFrame, 523 | positionBuffer, 524 | velocityBuffer, 525 | indicesBuffer 526 | } 527 | -------------------------------------------------------------------------------- /marchingCubes/marchingCubesTables.js: -------------------------------------------------------------------------------- 1 | //Indexes for the marching cubes 2 | const ti5 = [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, 9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, 3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, 3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, 9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, 4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, 1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, 3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, 9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, 2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, 8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, 11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, 9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, 4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, 3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, 1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, 4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, 4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, 8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, 1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, 3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, 5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, 2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, 9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, 0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, 2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, 10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, 4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, 5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, 5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, 9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, 9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, 0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, 1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, 9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, 10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, 8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, 2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, 7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, 9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, 2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, 11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, 9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, 5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, 11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, 11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, 9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, 1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, 9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, 5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, 2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, 11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, 0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, 5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, 6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, 0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, 3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, 6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, 5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, 4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, 1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, 10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, 6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, 1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, 8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, 7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, 3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, 5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, 0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, 9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, 8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, 5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, 0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, 6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, 10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, 4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, 10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, 8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, 1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, 3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, 0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, 8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, 10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, 0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, 3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, 6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, 9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, 8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, 3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, 6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, 7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, 0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, 10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, 10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, 1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, 2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, 7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, 7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, 2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, 1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, 11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, 8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, 0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, 7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, 7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, 8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, 10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, 2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, 6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, 7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, 7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, 2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, 1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, 10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, 10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, 0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, 7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, 6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, 3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, 8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, 9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, 6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, 1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, 4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, 10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, 8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, 0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, 1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, 8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, 10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, 4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, 10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, 4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, 5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, 11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, 9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, 6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, 7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, 3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, 7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, 9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, 3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, 6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, 9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, 1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, 4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, 7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, 6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, 3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, 0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, 6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, 1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, 0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, 11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, 6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, 5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, 9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, 1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, 1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, 10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, 0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, 10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, 11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, 5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, 10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, 11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, 0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, 9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, 7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, 2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, 8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, 9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, 9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, 1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, 9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, 9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, 5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, 5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, 0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, 10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, 2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, 0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, 0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, 9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, 5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, 3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, 5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, 8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, 0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, 8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, 9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, 0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, 1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, 3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, 4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, 9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, 11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, 11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, 2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, 9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, 3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, 1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, 4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, 4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, 4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, 4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, 3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, 0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, 3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, 3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, 0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, 3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, 9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, 1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]; 3 | 4 | const trianglesOnVoxels = [[], [0, 8, 3], [0, 1, 9], [1, 8, 3, 9, 8, 1], [1, 2, 10], [0, 8, 3, 1, 2, 10], [9, 2, 10, 0, 2, 9], [2, 8, 3, 2, 10, 8, 10, 9, 8], [3, 11, 2], [0, 11, 2, 8, 11, 0], [1, 9, 0, 2, 3, 11], [1, 11, 2, 1, 9, 11, 9, 8, 11], [3, 10, 1, 11, 10, 3], [0, 10, 1, 0, 8, 10, 8, 11, 10], [3, 9, 0, 3, 11, 9, 11, 10, 9], [9, 8, 10, 10, 8, 11], [4, 7, 8], [4, 3, 0, 7, 3, 4], [0, 1, 9, 8, 4, 7], [4, 1, 9, 4, 7, 1, 7, 3, 1], [1, 2, 10, 8, 4, 7], [3, 4, 7, 3, 0, 4, 1, 2, 10], [9, 2, 10, 9, 0, 2, 8, 4, 7], [2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4], [8, 4, 7, 3, 11, 2], [11, 4, 7, 11, 2, 4, 2, 0, 4], [9, 0, 1, 8, 4, 7, 2, 3, 11], [4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1], [3, 10, 1, 3, 11, 10, 7, 8, 4], [1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4], [4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3], [4, 7, 11, 4, 11, 9, 9, 11, 10], [9, 5, 4], [9, 5, 4, 0, 8, 3], [0, 5, 4, 1, 5, 0], [8, 5, 4, 8, 3, 5, 3, 1, 5], [1, 2, 10, 9, 5, 4], [3, 0, 8, 1, 2, 10, 4, 9, 5], [5, 2, 10, 5, 4, 2, 4, 0, 2], [2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8], [9, 5, 4, 2, 3, 11], [0, 11, 2, 0, 8, 11, 4, 9, 5], [0, 5, 4, 0, 1, 5, 2, 3, 11], [2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5], [10, 3, 11, 10, 1, 3, 9, 5, 4], [4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10], [5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3], [5, 4, 8, 5, 8, 10, 10, 8, 11], [9, 7, 8, 5, 7, 9], [9, 3, 0, 9, 5, 3, 5, 7, 3], [0, 7, 8, 0, 1, 7, 1, 5, 7], [1, 5, 3, 3, 5, 7], [9, 7, 8, 9, 5, 7, 10, 1, 2], [10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3], [8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2], [2, 10, 5, 2, 5, 3, 3, 5, 7], [7, 9, 5, 7, 8, 9, 3, 11, 2], [9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11], [2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7], [11, 2, 1, 11, 1, 7, 7, 1, 5], [9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11], [5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0], [11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0], [11, 10, 5, 7, 11, 5], [10, 6, 5], [0, 8, 3, 5, 10, 6], [9, 0, 1, 5, 10, 6], [1, 8, 3, 1, 9, 8, 5, 10, 6], [1, 6, 5, 2, 6, 1], [1, 6, 5, 1, 2, 6, 3, 0, 8], [9, 6, 5, 9, 0, 6, 0, 2, 6], [5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8], [2, 3, 11, 10, 6, 5], [11, 0, 8, 11, 2, 0, 10, 6, 5], [0, 1, 9, 2, 3, 11, 5, 10, 6], [5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11], [6, 3, 11, 6, 5, 3, 5, 1, 3], [0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6], [3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9], [6, 5, 9, 6, 9, 11, 11, 9, 8], [5, 10, 6, 4, 7, 8], [4, 3, 0, 4, 7, 3, 6, 5, 10], [1, 9, 0, 5, 10, 6, 8, 4, 7], [10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4], [6, 1, 2, 6, 5, 1, 4, 7, 8], [1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7], [8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6], [7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9], [3, 11, 2, 7, 8, 4, 10, 6, 5], [5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11], [0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6], [9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6], [8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6], [5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11], [0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7], [6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9], [10, 4, 9, 6, 4, 10], [4, 10, 6, 4, 9, 10, 0, 8, 3], [10, 0, 1, 10, 6, 0, 6, 4, 0], [8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10], [1, 4, 9, 1, 2, 4, 2, 6, 4], [3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4], [0, 2, 4, 4, 2, 6], [8, 3, 2, 8, 2, 4, 4, 2, 6], [10, 4, 9, 10, 6, 4, 11, 2, 3], [0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6], [3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10], [6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1], [9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3], [8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1], [3, 11, 6, 3, 6, 0, 0, 6, 4], [6, 4, 8, 11, 6, 8], [7, 10, 6, 7, 8, 10, 8, 9, 10], [0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10], [10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0], [10, 6, 7, 10, 7, 1, 1, 7, 3], [1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7], [2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9], [7, 8, 0, 7, 0, 6, 6, 0, 2], [7, 3, 2, 6, 7, 2], [2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7], [2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7], [1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11], [11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1], [8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6], [0, 9, 1, 11, 6, 7], [7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0], [7, 11, 6], [7, 6, 11], [3, 0, 8, 11, 7, 6], [0, 1, 9, 11, 7, 6], [8, 1, 9, 8, 3, 1, 11, 7, 6], [10, 1, 2, 6, 11, 7], [1, 2, 10, 3, 0, 8, 6, 11, 7], [2, 9, 0, 2, 10, 9, 6, 11, 7], [6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8], [7, 2, 3, 6, 2, 7], [7, 0, 8, 7, 6, 0, 6, 2, 0], [2, 7, 6, 2, 3, 7, 0, 1, 9], [1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6], [10, 7, 6, 10, 1, 7, 1, 3, 7], [10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8], [0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7], [7, 6, 10, 7, 10, 8, 8, 10, 9], [6, 8, 4, 11, 8, 6], [3, 6, 11, 3, 0, 6, 0, 4, 6], [8, 6, 11, 8, 4, 6, 9, 0, 1], [9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6], [6, 8, 4, 6, 11, 8, 2, 10, 1], [1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6], [4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9], [10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3], [8, 2, 3, 8, 4, 2, 4, 6, 2], [0, 4, 2, 4, 6, 2], [1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8], [1, 9, 4, 1, 4, 2, 2, 4, 6], [8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1], [10, 1, 0, 10, 0, 6, 6, 0, 4], [4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3], [10, 9, 4, 6, 10, 4], [4, 9, 5, 7, 6, 11], [0, 8, 3, 4, 9, 5, 11, 7, 6], [5, 0, 1, 5, 4, 0, 7, 6, 11], [11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5], [9, 5, 4, 10, 1, 2, 7, 6, 11], [6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5], [7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2], [3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6], [7, 2, 3, 7, 6, 2, 5, 4, 9], [9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7], [3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0], [6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8], [9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7], [1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4], [4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10], [7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10], [6, 9, 5, 6, 11, 9, 11, 8, 9], [3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5], [0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11], [6, 11, 3, 6, 3, 5, 5, 3, 1], [1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6], [0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10], [11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5], [6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3], [5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2], [9, 5, 6, 9, 6, 0, 0, 6, 2], [1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8], [1, 5, 6, 2, 1, 6], [1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6], [10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0], [0, 3, 8, 5, 6, 10], [10, 5, 6], [11, 5, 10, 7, 5, 11], [11, 5, 10, 11, 7, 5, 8, 3, 0], [5, 11, 7, 5, 10, 11, 1, 9, 0], [10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1], [11, 1, 2, 11, 7, 1, 7, 5, 1], [0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11], [9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7], [7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2], [2, 5, 10, 2, 3, 5, 3, 7, 5], [8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5], [9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2], [9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2], [1, 3, 5, 3, 7, 5], [0, 8, 7, 0, 7, 1, 1, 7, 5], [9, 0, 3, 9, 3, 5, 5, 3, 7], [9, 8, 7, 5, 9, 7], [5, 8, 4, 5, 10, 8, 10, 11, 8], [5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0], [0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5], [10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4], [2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8], [0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11], [0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5], [9, 4, 5, 2, 11, 3], [2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4], [5, 10, 2, 5, 2, 4, 4, 2, 0], [3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9], [5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2], [8, 4, 5, 8, 5, 3, 3, 5, 1], [0, 4, 5, 1, 0, 5], [8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5], [9, 4, 5], [4, 11, 7, 4, 9, 11, 9, 10, 11], [0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11], [1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11], [3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4], [4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2], [9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3], [11, 7, 4, 11, 4, 2, 2, 4, 0], [11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4], [2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9], [9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7], [3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10], [1, 10, 2, 8, 7, 4], [4, 9, 1, 4, 1, 7, 7, 1, 3], [4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1], [4, 0, 3, 7, 4, 3], [4, 8, 7], [9, 10, 8, 10, 11, 8], [3, 0, 9, 3, 9, 11, 11, 9, 10], [0, 1, 10, 0, 10, 8, 8, 10, 11], [3, 1, 10, 11, 3, 10], [1, 2, 11, 1, 11, 9, 9, 11, 8], [3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9], [0, 2, 11, 8, 0, 11], [3, 2, 11], [2, 3, 8, 2, 8, 10, 10, 8, 9], [9, 10, 2, 0, 9, 2], [2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8], [1, 10, 2], [1, 3, 8, 9, 1, 8], [0, 9, 1], [0, 3, 8], []]; 5 | 6 | export {ti5, trianglesOnVoxels} -------------------------------------------------------------------------------- /Main.js: -------------------------------------------------------------------------------- 1 | import * as Utils from "./utils/utils.js"; 2 | import {calculateBlur3D} from './blur3D/Blur3D.js' 3 | import {Camera} from './utils/camera.js'; 4 | 5 | import * as PBF from "./simulation/PBF.js"; 6 | import { generateMipMap } from "./mipmaps/CalculateMipMap.js"; 7 | import * as MarchingCubes from "./marchingCubes/TrianglesGenerator.js"; 8 | 9 | import * as Bloom from "./unrealBloom/UnrealBloom.js"; 10 | import * as dat from 'dat.gui'; 11 | 12 | import { vec3 } from "gl-matrix"; 13 | 14 | 15 | //Shaders 16 | import marchingCubesShader from "./rendering/RenderMC.wgsl?raw"; 17 | import postProcessingShader from "./rendering/Postprocessing.wgsl?raw"; 18 | import backgroundShader from "./rendering/RenderBackground.wgsl?raw"; 19 | import blurOffsetsShader from "./rendering/BlurOffset.wgsl?raw"; 20 | import compositeShader from "./rendering/RenderComposite.wgsl?raw" 21 | 22 | 23 | 24 | const device = await Utils.getDevice(); 25 | 26 | const PBF_RESOLUTION = 120; //More than 203 this breaks the buffer 27 | const MC_RESOLUTION = PBF_RESOLUTION; //Maximum resolution available 28 | const VERTEX_MEMORY = 20000000; 29 | const totalParticles = 80000; 30 | const MULTI_SAMPLER = 1; 31 | 32 | //Load the logo image 33 | async function loadImageBitmap(url) { 34 | const res = await fetch(url); 35 | const blob = await res.blob(); 36 | return await createImageBitmap(blob, { colorSpaceConversion: 'none' }); 37 | } 38 | 39 | let letters = document.body.querySelectorAll(".letter"); 40 | letters = Array.from(letters); 41 | 42 | 43 | const logoImage = await loadImageBitmap("./assets/codrops.png"); 44 | 45 | const logoTexture = device.createTexture({ 46 | size: [1650, 200], 47 | format: 'rgba8unorm', 48 | dimension: '2d', 49 | usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT, 50 | }) 51 | 52 | device.queue.copyExternalImageToTexture( 53 | { source: logoImage}, 54 | { texture: logoTexture }, 55 | { width: logoTexture.width, 56 | height: logoTexture.height }, 57 | ); 58 | 59 | 60 | 61 | //Get the context to render 62 | const canvas = document.querySelector("canvas"); 63 | canvas.width = window.innerWidth; 64 | canvas.height = window.innerHeight; 65 | const context = canvas.getContext('webgpu'); 66 | const presentationFormat = navigator.gpu.getPreferredCanvasFormat(); 67 | context.configure({ 68 | device, 69 | format: presentationFormat 70 | }); 71 | 72 | let textureSize = MC_RESOLUTION; 73 | 74 | let currentFrame = 0; 75 | 76 | // let querySet, queryBuffer, capacity; 77 | 78 | // capacity = 2;//Max number of timestamps we can store 79 | 80 | // querySet = device.createQuerySet({ 81 | // type: "timestamp", 82 | // count: capacity, 83 | // }); 84 | 85 | // queryBuffer = device.createBuffer({ 86 | // size: 8 * capacity, 87 | // usage: GPUBufferUsage.QUERY_RESOLVE 88 | // | GPUBufferUsage.STORAGE 89 | // | GPUBufferUsage.COPY_SRC 90 | // | GPUBufferUsage.COPY_DST, 91 | // }); 92 | 93 | let colors = [ 94 | [18, 97, 115], 95 | [78, 157, 166], 96 | [217, 166, 121], 97 | [191, 116, 73], 98 | [140, 68, 42], 99 | ] 100 | 101 | let colorsId = 0 ; 102 | let colorIdOut = 0; 103 | let colorIdIn = 0; 104 | 105 | //Define the GUI 106 | var params = { 107 | depthTest: 1, 108 | mixAlpha: 1, 109 | size: 5, 110 | deltaTime: 0.05, 111 | coneAngle: 0.83, 112 | coneRotation: 45, 113 | coneAngle2: 0.76, 114 | coneRotation2: 64, 115 | gridRadius: 5, 116 | lightIntensity: 14, 117 | separation: 0., 118 | voxelWorldSize: 0.022, 119 | smoothness: 12, 120 | mc_range: 0.6, 121 | thickness: 0.5, 122 | gamma: 1, 123 | brightness: 0, 124 | contrast: 1 125 | } 126 | 127 | const queryString = window.location.search; 128 | const urlParams = new URLSearchParams(queryString); 129 | const guiMenu = urlParams.get('ui') == "true"; 130 | 131 | if(guiMenu) { 132 | var gui = new dat.GUI(); 133 | 134 | var postFolder = gui.addFolder("postprocessing"); 135 | postFolder.add(params, "gamma", -1, 1).name("gamma").step(0.01); 136 | postFolder.add(params, "brightness", -1, 1).name("brightness").step(0.01); 137 | postFolder.add(params, "contrast", -1, 3).name("contrast").step(0.01); 138 | 139 | 140 | var mcFolder = gui.addFolder('marchingCubes'); 141 | mcFolder.add(params, "smoothness", 1, 30).name("smoothness").step(1); 142 | mcFolder.add(params, "mc_range", 0.001, 1).name("range").step(0.001); 143 | mcFolder.add(params, "thickness", 0.001, 1).name("thickness").step(0.001); 144 | mcFolder.add(params, "voxelWorldSize", 0.001, 0.1).name("voxel size ").step(0.0001); 145 | mcFolder.add(params, "coneAngle2", 0.1, 1, 1).name("cone angle ").step(0.01); 146 | mcFolder.add(params, "coneRotation2", 0, 90, 1).name("cone rotation ").step(1); 147 | 148 | var simulationFolder = gui.addFolder('simulation'); 149 | simulationFolder.add(params, "deltaTime", 0, 0.05, 0).name("delta time").step(0.001); 150 | simulationFolder.add(params, "separation", 0, 0.4, 0).name("separation").step(0.01); 151 | } 152 | 153 | 154 | const texturePotential = device.createTexture({ 155 | size: [textureSize, textureSize, textureSize], 156 | format: 'rgba32float', 157 | dimension: '3d', 158 | usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.COPY_DST | GPUBufferUsage.COPY_SRC, 159 | }) 160 | 161 | 162 | const texture_VCT = device.createTexture({ 163 | size: [textureSize, textureSize, textureSize], 164 | format: 'rgba32float', 165 | dimension: '3d', 166 | mipLevelCount: Math.ceil(Math.log2(textureSize)), 167 | usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.COPY_DST | GPUBufferUsage.COPY_SRC, 168 | }) 169 | 170 | const texturePotentialOut = device.createTexture({ 171 | size: [textureSize, textureSize, textureSize], 172 | format: 'rgba32float', 173 | dimension: '3d', 174 | usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.COPY_DST | GPUBufferUsage.COPY_SRC, 175 | }) 176 | 177 | const sampler = device.createSampler({ 178 | magFilter: "linear", 179 | minFilter: "linear", 180 | mipmapFilter: "linear", 181 | addressModeU: 'clamp-to-edge', 182 | addressModeV: 'clamp-to-edge', 183 | addressModeW: 'clamp-to-edge' 184 | }); 185 | 186 | 187 | 188 | 189 | console.log("the PBF resolution is: " + PBF_RESOLUTION); 190 | console.log("the amount of parrticles are: " + totalParticles); 191 | 192 | 193 | //Define the camera 194 | let camera = new Camera(canvas); 195 | let cameraDistance = 3; 196 | let FOV = 30; 197 | 198 | 199 | //Setup the position based fluids 200 | await PBF.setupPBF(PBF_RESOLUTION, totalParticles, texturePotential, camera); 201 | 202 | //Setup the marching cubes 203 | const [verticesBuffer, 204 | normalsBuffer, 205 | velocityBuffer, 206 | checkBuffer] = await MarchingCubes.setupMarchingCubes(VERTEX_MEMORY, 207 | texturePotentialOut, 208 | texture_VCT); 209 | 210 | //transform matrix 211 | let uniformsData = new Array(64).fill(0); 212 | 213 | let uniforms = new Float32Array(uniformsData); 214 | 215 | const uniformsBuffer = device.createBuffer( 216 | { 217 | label: "uniforms buffer", 218 | size: uniforms.byteLength, 219 | usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST 220 | } 221 | ) 222 | 223 | const uniformsMirrorBuffer = device.createBuffer( 224 | { 225 | label: "uniforms mirror buffer", 226 | size: uniforms.byteLength, 227 | usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST 228 | } 229 | ) 230 | 231 | const marchingCubesData = await Utils.setupRenderingPipeline("marching cubes", 232 | marchingCubesShader, 233 | MULTI_SAMPLER, 234 | [{format: "rgba8unorm"}, 235 | {format: "rgba8unorm"} 236 | ] 237 | ); 238 | 239 | const marchingCubesBindings = []; 240 | marchingCubesBindings[0] = device.createBindGroup({ 241 | label: "binding for non reflective shape", 242 | layout: marchingCubesData.pipeline.getBindGroupLayout(0), 243 | entries: [ 244 | {binding: 0, resource: {buffer: verticesBuffer}}, 245 | {binding: 1, resource: {buffer: uniformsBuffer}}, 246 | {binding: 2, resource: {buffer: normalsBuffer}}, 247 | {binding: 3, resource: texture_VCT.createView()}, 248 | {binding: 4, resource: sampler}, 249 | {binding: 5, resource: {buffer: velocityBuffer}}, 250 | {binding: 6, resource: texturePotentialOut.createView()}, 251 | ] 252 | } 253 | ) 254 | 255 | marchingCubesBindings[1] = device.createBindGroup({ 256 | label: "binding for reflective shape", 257 | layout: marchingCubesData.pipeline.getBindGroupLayout(0), 258 | entries: [ 259 | {binding: 0, resource: {buffer: verticesBuffer}}, 260 | {binding: 1, resource: {buffer: uniformsMirrorBuffer}}, 261 | {binding: 2, resource: {buffer: normalsBuffer}}, 262 | {binding: 3, resource: texture_VCT.createView()}, 263 | {binding: 4, resource: sampler}, 264 | {binding: 5, resource: {buffer: velocityBuffer}}, 265 | {binding: 6, resource: texturePotentialOut.createView()}, 266 | ] 267 | } 268 | ) 269 | 270 | 271 | const postProData = await Utils.setupPipeline("post processing", postProcessingShader); 272 | 273 | 274 | const uniformsXData = new Float32Array([0, 1, params.deltaTime, 1]); 275 | const uniformsYData = new Float32Array([1, 0, params.deltaTime, 1]); 276 | 277 | const uniformsXBuffer = device.createBuffer({ 278 | label: "uniforms X buffer", 279 | size: uniformsXData.byteLength, 280 | usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST 281 | }) 282 | 283 | const uniformsYBuffer = device.createBuffer({ 284 | label: "uniforms Y buffer", 285 | size: uniformsYData.byteLength, 286 | usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST 287 | }) 288 | 289 | device.queue.writeBuffer(uniformsXBuffer, 0, uniformsXData); 290 | device.queue.writeBuffer(uniformsYBuffer, 0, uniformsYData); 291 | 292 | 293 | const backgroundData = await Utils.setupRenderingPipeline("background quad", 294 | backgroundShader, 295 | MULTI_SAMPLER, 296 | [{format: "rgba8unorm"}, 297 | {format: "rgba8unorm"} 298 | ], 299 | false 300 | ); 301 | 302 | const backgroundUniforms = new Float32Array(8); 303 | const backgroundBuffer = device.createBuffer({ 304 | label: "background buffer", 305 | size: backgroundUniforms.byteLength, 306 | usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST 307 | }) 308 | 309 | const backogroundBindGroup = device.createBindGroup({ 310 | label: "background bind group", 311 | layout: backgroundData.pipeline.getBindGroupLayout(0), 312 | entries: [ 313 | {binding: 0, resource: {buffer: backgroundBuffer}} 314 | ] 315 | }) 316 | 317 | const offsetData = await Utils.setupPipeline("offset pass", blurOffsetsShader); 318 | 319 | const quadUniforms = new Float32Array(8); 320 | const quadBuffer = device.createBuffer({ 321 | label: "quad buffer", 322 | size: quadUniforms.byteLength, 323 | usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST 324 | }) 325 | 326 | const quadData = await Utils.setupRenderingPipeline("screen quad", 327 | compositeShader, 328 | 1, 329 | [{format: navigator.gpu.getPreferredCanvasFormat()}], 330 | false); 331 | 332 | 333 | let defaultDepthTexture, 334 | singleColorTexture, 335 | singleMirrorTexture, 336 | singleScreenTexture, 337 | singleMirrorTexture2, 338 | singleMirrorBase; 339 | 340 | let multiColorTexture, 341 | multiMirrorTexture, 342 | multiDepthTexture; 343 | 344 | const postProcessingBindings = []; 345 | const offsetsBindings = []; 346 | 347 | function updateRenderTexures() { 348 | 349 | if(defaultDepthTexture != null) { 350 | defaultDepthTexture.destroy(); 351 | singleColorTexture.destroy(); 352 | singleMirrorTexture.destroy(); 353 | singleMirrorTexture2.destroy(); 354 | singleMirrorBase.destroy(); 355 | singleScreenTexture.destroy(); 356 | multiColorTexture.destroy(); 357 | multiMirrorTexture.destroy(); 358 | multiDepthTexture.destroy(); 359 | } 360 | 361 | defaultDepthTexture = device.createTexture({ 362 | size: [window.innerWidth, window.innerHeight], 363 | sampleCount: 1, 364 | format: 'depth32float', 365 | usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING 366 | }); 367 | 368 | multiDepthTexture = device.createTexture({ 369 | size: [window.innerWidth, window.innerHeight], 370 | sampleCount: MULTI_SAMPLER, 371 | format: 'depth32float', 372 | usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING 373 | }); 374 | 375 | singleColorTexture = device.createTexture({ 376 | size: [window.innerWidth, window.innerHeight], 377 | sampleCount: 1, 378 | format: 'rgba8unorm', 379 | usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING 380 | }); 381 | 382 | singleMirrorTexture = device.createTexture({ 383 | size: [window.innerWidth, window.innerHeight], 384 | sampleCount: 1, 385 | format: 'rgba8unorm', 386 | usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.COPY_SRC 387 | }); 388 | 389 | multiColorTexture = device.createTexture({ 390 | size: [window.innerWidth, window.innerHeight], 391 | sampleCount: MULTI_SAMPLER, 392 | format: 'rgba8unorm', 393 | usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING 394 | }); 395 | 396 | multiMirrorTexture = device.createTexture({ 397 | size: [window.innerWidth, window.innerHeight], 398 | sampleCount: MULTI_SAMPLER, 399 | format: 'rgba8unorm', 400 | usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC 401 | }); 402 | 403 | singleMirrorTexture2 = device.createTexture({ 404 | size: [window.innerWidth, window.innerHeight], 405 | sampleCount: 1, 406 | format: 'rgba8unorm', 407 | usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING 408 | }); 409 | 410 | singleMirrorBase = device.createTexture({ 411 | size: [window.innerWidth, window.innerHeight], 412 | sampleCount: 1, 413 | format: 'rgba8unorm', 414 | usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.COPY_DST 415 | }); 416 | 417 | singleScreenTexture = device.createTexture({ 418 | size: [window.innerWidth, window.innerHeight], 419 | sampleCount: 1, 420 | format: 'rgba8unorm', 421 | usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING 422 | }); 423 | 424 | Bloom.setup(window.innerWidth, window.innerHeight, singleColorTexture, singleScreenTexture, sampler); 425 | 426 | postProcessingBindings[0] = device.createBindGroup( { 427 | label:`post processing pass X bind group`, 428 | layout: postProData.pipeline.getBindGroupLayout(0), 429 | entries: [ 430 | {binding: 0, resource: singleColorTexture.createView()}, 431 | {binding: 1, resource: singleMirrorTexture.createView()}, 432 | {binding: 2, resource: sampler}, 433 | {binding: 3, resource: {buffer: uniformsXBuffer}}, 434 | {binding: 4, resource: singleScreenTexture.createView()}, 435 | 436 | ] 437 | }) 438 | 439 | postProcessingBindings[1] = device.createBindGroup( { 440 | label:`post processing pass Y bind group`, 441 | layout: postProData.pipeline.getBindGroupLayout(0), 442 | entries: [ 443 | {binding: 0, resource: singleScreenTexture.createView()}, 444 | {binding: 1, resource: singleMirrorTexture.createView()}, 445 | {binding: 2, resource: sampler}, 446 | {binding: 3, resource: {buffer: uniformsYBuffer}}, 447 | {binding: 4, resource: singleColorTexture.createView()}, 448 | ] 449 | }) 450 | 451 | 452 | 453 | offsetsBindings[0] = device.createBindGroup( { 454 | label:`offset X bind group`, 455 | layout: offsetData.pipeline.getBindGroupLayout(0), 456 | entries: [ 457 | {binding: 0, resource: {buffer: uniformsXBuffer}}, 458 | {binding: 1, resource: singleMirrorBase.createView()}, 459 | {binding: 2, resource: singleMirrorTexture.createView()}, 460 | {binding: 3, resource: singleMirrorTexture2.createView()}, 461 | 462 | ] 463 | }) 464 | 465 | 466 | offsetsBindings[1] = device.createBindGroup( { 467 | label:`offset Y bind group`, 468 | layout: offsetData.pipeline.getBindGroupLayout(0), 469 | entries: [ 470 | {binding: 0, resource: {buffer: uniformsYBuffer}}, 471 | {binding: 1, resource: singleMirrorBase.createView()}, 472 | {binding: 2, resource: singleMirrorTexture2.createView()}, 473 | {binding: 3, resource: singleMirrorTexture.createView()}, 474 | ] 475 | }) 476 | 477 | 478 | 479 | quadData.setBindGroup( 480 | [ 481 | {binding: 0, resource: {buffer: quadBuffer}}, 482 | {binding: 1, resource: singleScreenTexture.createView()}, 483 | {binding: 2, resource: sampler}, 484 | {binding: 3, resource: logoTexture.createView()} 485 | ] 486 | ) 487 | 488 | } 489 | 490 | window.onresize = updateRenderTexures; 491 | updateRenderTexures(); 492 | 493 | 494 | 495 | //Rendering 496 | async function render() { 497 | 498 | canvas.width = window.innerWidth; 499 | canvas.height = window.innerHeight; 500 | 501 | //Quick performance test to update a frame 502 | let frameData = PBF.updateFrame({x: 0 , y: -18 , z: 0}, params.deltaTime, params.lightIntensity, params.separation, camera); 503 | 504 | if(frameData.relativeFrame == 0) { 505 | 506 | 507 | colorIdIn = (colorsId) % colors.length; 508 | colorIdOut = (colorsId + 1) % colors.length; 509 | colorsId ++; 510 | 511 | } 512 | 513 | 514 | //Generate the potential for the marching cubes. 515 | calculateBlur3D(texturePotential, texturePotentialOut, params.smoothness); 516 | 517 | //Calculate the triangles using the marching cubes. 518 | MarchingCubes.generateTriangles(params.mc_range); 519 | 520 | //Calculate the mipmap of the texture for the voxel cone tracing 521 | generateMipMap(texture_VCT, device); 522 | 523 | //Update the camera 524 | camera.updateCamera(FOV, window.innerWidth / window.innerHeight, cameraDistance); 525 | var n = vec3.fromValues(0, 1, 0); 526 | camera.calculateReflection([0., 0.03, 0], n); 527 | 528 | //make a command enconder to start encoding thigns 529 | const encoder = device.createCommandEncoder({ label: 'rendering encoder'}); 530 | 531 | 532 | //Render the background 533 | 534 | backgroundUniforms[0] = colors[colorIdIn][0] / 255; 535 | backgroundUniforms[1] = colors[colorIdIn][1] /255; 536 | backgroundUniforms[2] = colors[colorIdIn][2] /255; 537 | backgroundUniforms[3] = frameData.relativeFrame; 538 | backgroundUniforms[4] = colors[colorIdOut][0] / 255; 539 | backgroundUniforms[5] = colors[colorIdOut][1] /255; 540 | backgroundUniforms[6] = colors[colorIdOut][2] /255; 541 | 542 | device.queue.writeBuffer(backgroundBuffer, 0, backgroundUniforms); 543 | 544 | backgroundData.passDescriptor.colorAttachments[0].view = MULTI_SAMPLER == 4 ? multiColorTexture.createView() : singleColorTexture.createView(); 545 | if(MULTI_SAMPLER == 4) backgroundData.passDescriptor.colorAttachments[0].resolveTarget = singleColorTexture.createView(); 546 | backgroundData.passDescriptor.colorAttachments[0].loadOp = 'clear'; 547 | 548 | backgroundData.passDescriptor.colorAttachments[1].view = MULTI_SAMPLER == 4 ? multiMirrorTexture.createView() : singleMirrorTexture.createView(); 549 | if(MULTI_SAMPLER == 4) backgroundData.passDescriptor.colorAttachments[1].resolveTarget = singleMirrorTexture.createView(); 550 | backgroundData.passDescriptor.colorAttachments[1].loadOp = 'clear'; 551 | 552 | 553 | const backgroundPass = encoder.beginRenderPass(backgroundData.passDescriptor); 554 | backgroundPass.setPipeline(backgroundData.pipeline); 555 | backgroundPass.setBindGroup(0, backogroundBindGroup); 556 | backgroundPass.draw(6, 1); 557 | backgroundPass.end(); 558 | 559 | 560 | 561 | const uniformsOffset = 16; 562 | for(let i = 0; i < 16; i ++) { 563 | uniforms[i] = camera.transformMatrix[i]; 564 | } 565 | 566 | uniforms[uniformsOffset] = camera.position[0]; 567 | uniforms[uniformsOffset + 1] = camera.position[1]; 568 | uniforms[uniformsOffset + 2] = camera.position[2]; 569 | uniforms[uniformsOffset + 3] = PBF_RESOLUTION; 570 | uniforms[uniformsOffset + 4] = params.coneAngle2; 571 | uniforms[uniformsOffset + 5] = params.coneRotation2; 572 | uniforms[uniformsOffset + 6] = frameData.relativeFrame; 573 | uniforms[uniformsOffset + 7] = params.voxelWorldSize; 574 | 575 | uniforms[uniformsOffset + 8] = colors[colorIdIn][0] / 255; 576 | uniforms[uniformsOffset + 9] = colors[colorIdIn][1] / 255; 577 | uniforms[uniformsOffset + 10] = colors[colorIdIn][2] / 255; 578 | uniforms[uniformsOffset + 11] = 0; 579 | 580 | uniforms[uniformsOffset + 12] = colors[colorIdOut][0] / 255; 581 | uniforms[uniformsOffset + 13] = colors[colorIdOut][1] / 255; 582 | uniforms[uniformsOffset + 14] = colors[colorIdOut][2] / 255; 583 | uniforms[uniformsOffset + 15] = params.thickness; 584 | 585 | device.queue.writeBuffer(uniformsBuffer, 0, uniforms); 586 | 587 | //For the mirror 588 | for(let i = 0; i < 16; i ++) { 589 | uniforms[i] = camera.transformMatrixReflection[i]; 590 | } 591 | uniforms[uniformsOffset + 11] = 1; 592 | device.queue.writeBuffer(uniformsMirrorBuffer, 0, uniforms); 593 | 594 | 595 | //Render the fluid 596 | marchingCubesData.passDescriptor.colorAttachments[0].view = MULTI_SAMPLER == 4? multiColorTexture.createView() : singleColorTexture.createView(); 597 | if(MULTI_SAMPLER == 4) marchingCubesData.passDescriptor.colorAttachments[0].resolveTarget = singleColorTexture.createView(); 598 | marchingCubesData.passDescriptor.colorAttachments[0].loadOp = 'load'; 599 | 600 | marchingCubesData.passDescriptor.colorAttachments[1].view = MULTI_SAMPLER == 4? multiMirrorTexture.createView() : singleMirrorTexture.createView(); 601 | if(MULTI_SAMPLER == 4) marchingCubesData.passDescriptor.colorAttachments[1].resolveTarget = singleMirrorTexture.createView(); 602 | marchingCubesData.passDescriptor.colorAttachments[1].loadOp = 'load'; 603 | 604 | marchingCubesData.passDescriptor.depthStencilAttachment.view = multiDepthTexture.createView(); 605 | marchingCubesData.passDescriptor.depthStencilAttachment.depthLoadOp = 'clear'; 606 | 607 | 608 | const pass = encoder.beginRenderPass(marchingCubesData.passDescriptor); 609 | pass.setPipeline(marchingCubesData.pipeline); 610 | marchingCubesBindings.map(bindGroup => { 611 | pass.setBindGroup(0, bindGroup); 612 | pass.drawIndirect(checkBuffer, 4 * 3); 613 | }) 614 | pass.end(); 615 | 616 | 617 | encoder.copyTextureToTexture( 618 | { 619 | texture: singleMirrorTexture, 620 | }, 621 | { 622 | texture: singleMirrorBase, 623 | }, 624 | { 625 | width: window.innerWidth, 626 | height: window.innerHeight 627 | }, 628 | ); 629 | 630 | let postX = Math.ceil(window.innerWidth / 16); 631 | let postY = Math.ceil(window.innerHeight / 16); 632 | 633 | //Offset passes 634 | const offsetPass = encoder.beginComputePass(offsetData.passDescriptor); 635 | offsetPass.setPipeline(offsetData.pipeline); 636 | offsetPass.setBindGroup(0, offsetsBindings[0]); 637 | offsetPass.dispatchWorkgroups(postX, postY); 638 | offsetPass.setBindGroup(0, offsetsBindings[1]); 639 | offsetPass.dispatchWorkgroups(postX, postY); 640 | offsetPass.end(); 641 | 642 | 643 | //Post processing passes 644 | const postProPass = encoder.beginComputePass(postProData.passDescriptor); 645 | postProPass.setPipeline(postProData.pipeline); 646 | postProPass.setBindGroup(0, postProcessingBindings[0]); 647 | postProPass.dispatchWorkgroups(postX, postY); 648 | postProPass.setBindGroup(0, postProcessingBindings[1]); 649 | postProPass.dispatchWorkgroups(postX, postY); 650 | postProPass.end(); 651 | 652 | Bloom.applyBloom(encoder); 653 | 654 | //Render the quad 655 | quadData.passDescriptor.colorAttachments[0].view = context.getCurrentTexture().createView(); 656 | quadData.passDescriptor.colorAttachments[0].loadOp = "clear"; 657 | 658 | quadUniforms[0] = window.innerWidth; 659 | quadUniforms[1] = window.innerHeight; 660 | 661 | quadUniforms[2] = frameData.relativeFrame; 662 | quadUniforms[3] = frameData.animationFrame; 663 | quadUniforms[4] = frameData.currentLetter; 664 | quadUniforms[5] = params.brightness; 665 | 666 | quadUniforms[6] = params.contrast; 667 | quadUniforms[7] = params.gamma; 668 | 669 | 670 | device.queue.writeBuffer(quadBuffer, 0, quadUniforms); 671 | 672 | const screenPass = encoder.beginRenderPass(quadData.passDescriptor); 673 | screenPass.setPipeline(quadData.pipeline); 674 | screenPass.setBindGroup(0, quadData.bindGroup); 675 | screenPass.draw(6, 1); 676 | screenPass.end(); 677 | 678 | const commandBuffer = encoder.finish(); 679 | device.queue.submit([commandBuffer]); 680 | 681 | currentFrame ++; 682 | 683 | } 684 | 685 | 686 | // set the expected frame rate 687 | let frames_per_second = 60; 688 | 689 | let interval = Math.floor(1000 / frames_per_second); 690 | let startTime = performance.now(); 691 | let previousTime = startTime; 692 | 693 | let currentTime = 0; 694 | let deltaTime = 0; 695 | 696 | function animationLoop(timestamp) { 697 | currentTime = timestamp; 698 | deltaTime = currentTime - previousTime; 699 | 700 | if (deltaTime > interval) { 701 | previousTime = currentTime - (deltaTime % interval); 702 | render(); 703 | } 704 | 705 | requestAnimationFrame(animationLoop); 706 | } 707 | 708 | requestAnimationFrame(animationLoop); 709 | 710 | 711 | 712 | 713 | --------------------------------------------------------------------------------