├── .editorconfig ├── .gitattributes ├── .gitignore ├── .nojekyll ├── .npmignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── assets ├── twgsl.js ├── twgsl.wasm └── uv.jpg ├── examples ├── cube-auto.js ├── cube.js ├── full-screen-triangle.js ├── instancing.js ├── material-phong.js └── render-to-texture.js ├── index.html ├── package-lock.json ├── package.json ├── screenshot.jpg ├── src ├── constants.ts ├── core │ ├── Attachment.ts │ ├── Attribute.ts │ ├── BindGroup.ts │ ├── BindGroupLayout.ts │ ├── Buffer.ts │ ├── BuiltIn.ts │ ├── Clock.ts │ ├── Command.ts │ ├── Context.ts │ ├── Pass.ts │ ├── Pipeline.ts │ ├── Program.ts │ ├── Sampler.ts │ ├── Shader.ts │ ├── State.ts │ ├── Struct.ts │ ├── Texture.ts │ ├── Uniform.ts │ └── Variable.ts ├── helpers │ └── Axes.ts ├── index.ts ├── shaders │ ├── constants.glsl.ts │ ├── constants.wgsl.ts │ ├── index.glsl.ts │ ├── index.ts │ ├── lighting │ │ ├── diffuse.glsl.ts │ │ ├── diffuse.wgsl.ts │ │ ├── direct.glsl.ts │ │ ├── direct.wgsl.ts │ │ ├── gamma.glsl.ts │ │ ├── gamma.wgsl.ts │ │ ├── linear.glsl.ts │ │ ├── linear.wgsl.ts │ │ ├── specular.glsl.ts │ │ └── specular.wgsl.ts │ ├── noise │ │ ├── classic.glsl.ts │ │ ├── classic.wgsl.ts │ │ ├── index.glsl.ts │ │ ├── index.wgsl.ts │ │ ├── periodic.glsl.ts │ │ ├── periodic.wgsl.ts │ │ ├── simplex.glsl.ts │ │ ├── simplex.wgsl.ts │ │ ├── utils.glsl.ts │ │ ├── utils.wgsl.ts │ │ └── worley.wgsl.ts │ ├── utils.glsl.ts │ └── utils.wgsl.ts ├── types.ts └── utils.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | web_modules/**/* linguist-generated=true 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | web_modules 3 | .DS_Store 4 | /types 5 | /lib 6 | docs 7 | -------------------------------------------------------------------------------- /.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmnsgn/dgel/0b48231868bfc518e3a0e772859eb374001736cd/.nojekyll -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /web_modules 2 | /examples 3 | /docs 4 | /coverage 5 | /test 6 | /.github 7 | screenshot.* 8 | index.html 9 | tsconfig.json 10 | .editorconfig 11 | .nojekyll 12 | 13 | /assets 14 | /src/* 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines. 4 | 5 | ## [0.17.1](https://github.com/dmnsgn/dgel/compare/v0.17.0...v0.17.1) (2024-06-27) 6 | 7 | 8 | ### Bug Fixes 9 | 10 | * twgsl url ([aa9ad39](https://github.com/dmnsgn/dgel/commit/aa9ad39bb80e316e3570e5d5f49b5be75f7db09b)) 11 | 12 | 13 | 14 | # [0.17.0](https://github.com/dmnsgn/dgel/compare/v0.16.0...v0.17.0) (2024-06-27) 15 | 16 | 17 | 18 | # [0.16.0](https://github.com/dmnsgn/dgel/compare/v0.15.0...v0.16.0) (2023-05-13) 19 | 20 | 21 | ### Features 22 | 23 | * rename dispatch to dispatchWorkgroups ([9676382](https://github.com/dmnsgn/dgel/commit/9676382421ae4db2e602f587860bed261c0a7021)) 24 | 25 | 26 | 27 | # [0.15.0](https://github.com/dmnsgn/dgel/compare/v0.14.0...v0.15.0) (2023-02-19) 28 | 29 | 30 | ### Features 31 | 32 | * make BindGroupeLayout getBindGroupSize return a multiple of 16 bytes ([d48d614](https://github.com/dmnsgn/dgel/commit/d48d6146193fe26f1fe879dc3e3252ed82571b6e)) 33 | 34 | 35 | 36 | # [0.14.0](https://github.com/dmnsgn/dgel/compare/v0.13.0...v0.14.0) (2022-09-17) 37 | 38 | 39 | ### Bug Fixes 40 | 41 | * 'of' is a reserved keyword ([0f31771](https://github.com/dmnsgn/dgel/commit/0f31771ba8a4f9149c5c32f237138ed8e33b098a)) 42 | * module-scope 'let' has been replaced with 'const' ([67d9903](https://github.com/dmnsgn/dgel/commit/67d99032a63fd342c2be940a9a92e56fad49a9c2)) 43 | 44 | 45 | ### Features 46 | 47 | * add willReadFrequently to Texture's canvas 2d ([64e2652](https://github.com/dmnsgn/dgel/commit/64e2652cb8d6cf5c0f703767b9e48cccc52f7699)) 48 | * saturate is now a builtin in wgsl ([7066711](https://github.com/dmnsgn/dgel/commit/70667117c68f48bab9f321e6694fde3d846ab08b)) 49 | 50 | 51 | 52 | # [0.13.0](https://github.com/dmnsgn/dgel/compare/v0.12.0...v0.13.0) (2022-06-27) 53 | 54 | 55 | ### Bug Fixes 56 | 57 | * remove size from configure ([a66b8fd](https://github.com/dmnsgn/dgel/commit/a66b8fd4a1e7eeeb6988b3f95877f35ea2f472e7)) 58 | * use of deprecated language feature: remove stage ([6daa13a](https://github.com/dmnsgn/dgel/commit/6daa13a72215a5cde26aacc3a053727e3c2dcc2a)) 59 | 60 | 61 | ### Features 62 | 63 | * default to layout auto in Pipeline ([8a0c1ed](https://github.com/dmnsgn/dgel/commit/8a0c1edb8d90088a9777fab3628db737e483e9b9)) 64 | * use alphaMode instead of compositingAlphaMode ([2a2853d](https://github.com/dmnsgn/dgel/commit/2a2853d41aa7953a83489d0ba04eea4494400a84)) 65 | * use navigator.gpu.getPreferredCanvasFormat ([4fde41e](https://github.com/dmnsgn/dgel/commit/4fde41e9823bd8bb087b82ac8a2d1b194ded8bf1)) 66 | 67 | 68 | 69 | # [0.12.0](https://github.com/dmnsgn/dgel/compare/v0.11.0...v0.12.0) (2022-04-13) 70 | 71 | 72 | ### Bug Fixes 73 | 74 | * struct members should be separated with commas ([fed3788](https://github.com/dmnsgn/dgel/commit/fed37884b6334b5ce738542bca5f3f66cb303420)) 75 | 76 | 77 | ### Features 78 | 79 | * add compositingAlphaMode ([4bce529](https://github.com/dmnsgn/dgel/commit/4bce5298c731979b20d05a1fb5c67fc26f7b8a69)) 80 | 81 | 82 | 83 | # [0.11.0](https://github.com/dmnsgn/dgel/compare/v0.10.0...v0.11.0) (2022-03-04) 84 | 85 | 86 | ### Features 87 | 88 | * update to new API ([2b288f9](https://github.com/dmnsgn/dgel/commit/2b288f9c5375f10925550c8939662b5f71734196)) 89 | 90 | 91 | 92 | # [0.10.0](https://github.com/dmnsgn/dgel/compare/v0.9.0...v0.10.0) (2021-12-24) 93 | 94 | 95 | ### Bug Fixes 96 | 97 | * texture sample type to binding mapping ([277ecd4](https://github.com/dmnsgn/dgel/commit/277ecd4b210df86250c7e5802cab633321291052)) 98 | 99 | 100 | ### Features 101 | 102 | * add helper pipeline options ([4bef935](https://github.com/dmnsgn/dgel/commit/4bef9357f43b22bcdb499b17b5d10a3307edab3b)) 103 | 104 | 105 | 106 | # [0.9.0](https://github.com/dmnsgn/dgel/compare/v0.8.0...v0.9.0) (2021-12-15) 107 | 108 | 109 | ### Bug Fixes 110 | 111 | * add missing view property to Pass descriptor getter + add new Attachment options ([53e1636](https://github.com/dmnsgn/dgel/commit/53e16369690dde403d7c3247b9767b366429635e)), closes [#10](https://github.com/dmnsgn/dgel/issues/10) 112 | * remove deprecated [[block]] ([24b0e7e](https://github.com/dmnsgn/dgel/commit/24b0e7e74807bc2907faea473d7546c84459319b)), closes [#9](https://github.com/dmnsgn/dgel/issues/9) 113 | 114 | 115 | 116 | # [0.8.0](https://github.com/dmnsgn/dgel/compare/v0.7.0...v0.8.0) (2021-12-12) 117 | 118 | 119 | ### Features 120 | 121 | * add pixelRatio to Context and handle scaled resize ([916c7ab](https://github.com/dmnsgn/dgel/commit/916c7ab2f91d0a44151a11e7c6fbf932b207a620)), closes [#8](https://github.com/dmnsgn/dgel/issues/8) 122 | 123 | 124 | 125 | # [0.7.0](https://github.com/dmnsgn/dgel/compare/v0.6.0...v0.7.0) (2021-10-27) 126 | 127 | 128 | ### Features 129 | 130 | * add wgsl support ([b54a301](https://github.com/dmnsgn/dgel/commit/b54a301fea213db503646bb5f40636f38de2b603)) 131 | 132 | 133 | 134 | # [0.6.0](https://github.com/dmnsgn/dgel/compare/v0.5.0...v0.6.0) (2021-10-02) 135 | 136 | 137 | ### Features 138 | 139 | * add exports field to package.json ([3519074](https://github.com/dmnsgn/dgel/commit/35190747281d82a43b555a2b484898da94e7ee6b)) 140 | 141 | 142 | 143 | # [0.5.0](https://github.com/dmnsgn/dgel/compare/v0.4.1...v0.5.0) (2021-08-26) 144 | 145 | 146 | ### Features 147 | 148 | * update to latest api ([bec8800](https://github.com/dmnsgn/dgel/commit/bec8800795747bb90f742b3d33df595d848a61fc)) 149 | * update to latest api ([4a88011](https://github.com/dmnsgn/dgel/commit/4a88011141001dafaba27bf3fecb2e35c11a8fe3)) 150 | 151 | 152 | 153 | ## [0.4.1](https://github.com/dmnsgn/dgel/compare/v0.4.0...v0.4.1) (2021-04-22) 154 | 155 | 156 | ### Bug Fixes 157 | 158 | * replace attachment with view in render pass ([d5da76f](https://github.com/dmnsgn/dgel/commit/d5da76ff2de359c07404b94164e479374c635db2)) 159 | 160 | 161 | 162 | # [0.4.0](https://github.com/dmnsgn/dgel/compare/v0.3.0...v0.4.0) (2021-04-16) 163 | 164 | 165 | ### Features 166 | 167 | * update to latest api + move to snowdev ([a9a2d1f](https://github.com/dmnsgn/dgel/commit/a9a2d1f6bf6d46c87c4a7cf39bde66666f777c88)) 168 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (C) 2020 Damien Seguin 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dgel 2 | 3 | [![npm version](https://img.shields.io/npm/v/dgel)](https://www.npmjs.com/package/dgel) 4 | [![stability-experimental](https://img.shields.io/badge/stability-experimental-orange.svg)](https://www.npmjs.com/package/dgel) 5 | [![npm minzipped size](https://img.shields.io/bundlephobia/minzip/dgel)](https://bundlephobia.com/package/dgel) 6 | [![dependencies](https://img.shields.io/librariesio/release/npm/dgel)](https://github.com/dmnsgn/dgel/blob/main/package.json) 7 | [![types](https://img.shields.io/npm/types/dgel)](https://github.com/microsoft/TypeScript) 8 | [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-fa6673.svg)](https://conventionalcommits.org) 9 | [![styled with prettier](https://img.shields.io/badge/styled_with-Prettier-f8bc45.svg?logo=prettier)](https://github.com/prettier/prettier) 10 | [![linted with eslint](https://img.shields.io/badge/linted_with-ES_Lint-4B32C3.svg?logo=eslint)](https://github.com/eslint/eslint) 11 | [![license](https://img.shields.io/github/license/dmnsgn/dgel)](https://github.com/dmnsgn/dgel/blob/main/LICENSE.md) 12 | 13 | A WebGPU engine. 14 | 15 | [![paypal](https://img.shields.io/badge/donate-paypal-informational?logo=paypal)](https://paypal.me/dmnsgn) 16 | [![coinbase](https://img.shields.io/badge/donate-coinbase-informational?logo=coinbase)](https://commerce.coinbase.com/checkout/56cbdf28-e323-48d8-9c98-7019e72c97f3) 17 | [![twitter](https://img.shields.io/twitter/follow/dmnsgn?style=social)](https://twitter.com/dmnsgn) 18 | 19 | ![](https://raw.githubusercontent.com/dmnsgn/dgel/main/screenshot.jpg) 20 | 21 | 22 | ## Installation 23 | 24 | ```bash 25 | npm install dgel 26 | ``` 27 | 28 | ## Usage 29 | 30 | - Examples [demo](https://dmnsgn.github.io/dgel/) (you need a browser supporting WebGPU, most likely to work in Chrome Canary) 31 | - Examples [source](examples/) 32 | - [API Docs](https://dmnsgn.github.io/dgel/docs/) 33 | 34 | ## License 35 | 36 | MIT. See [license file](https://github.com/dmnsgn/dgel/blob/main/LICENSE.md). 37 | -------------------------------------------------------------------------------- /assets/twgsl.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmnsgn/dgel/0b48231868bfc518e3a0e772859eb374001736cd/assets/twgsl.wasm -------------------------------------------------------------------------------- /assets/uv.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmnsgn/dgel/0b48231868bfc518e3a0e772859eb374001736cd/assets/uv.jpg -------------------------------------------------------------------------------- /examples/cube-auto.js: -------------------------------------------------------------------------------- 1 | import { 2 | Attachment, 3 | Attribute, 4 | Axes, 5 | BindGroup, 6 | BindGroupLayout, 7 | Buffer, 8 | Clock, 9 | Command, 10 | Context, 11 | GPUIndexFormat, 12 | GPUShaderStage, 13 | Pass, 14 | Pipeline, 15 | Sampler, 16 | State, 17 | Texture, 18 | Uniform, 19 | WGSLBuiltIn, 20 | } from "../lib/index.js"; 21 | 22 | import { mat4 } from "gl-matrix"; 23 | import typedArrayInterleave from "typed-array-interleave"; 24 | import typedArrayConcat from "typed-array-concat"; 25 | import { PerspectiveCamera, Controls } from "cameras"; 26 | import { cube } from "primitive-geometry"; 27 | 28 | State.debug = true; 29 | 30 | const context = new Context(); 31 | if (!(await context.init())) { 32 | const message = "Your browser doesn't support WebGPU."; 33 | document.body.textContent = message; 34 | throw new Error(message); 35 | } 36 | document.body.appendChild(context.canvas); 37 | 38 | const clock = new Clock(); 39 | 40 | // Camera 41 | const camera = new PerspectiveCamera({ 42 | position: [3, 3, 3], 43 | }); 44 | camera.update(); 45 | const controls = new Controls({ 46 | element: context.canvas, 47 | camera, 48 | position: camera.position, 49 | target: camera.target, 50 | distanceBounds: [camera.near, camera.far], 51 | }); 52 | 53 | const onResize = () => { 54 | context.resize(window.innerWidth, window.innerHeight); 55 | camera.aspect = window.innerWidth / window.innerHeight; 56 | camera.updateProjectionMatrix(); 57 | }; 58 | onResize(); 59 | 60 | // Pipeline 61 | const pipeline = new Pipeline({ 62 | // bindGroupLayouts: [systemBindGroupLayout, meshBindGroupLayout], 63 | ins: [ 64 | new Attribute("position", "vec3"), 65 | new Attribute("normal", "vec3"), 66 | new Attribute("uv", "vec2"), 67 | ], 68 | outs: [ 69 | WGSLBuiltIn.Position, 70 | new Attribute("vNormal", "vec3"), 71 | new Attribute("vUv", "vec2"), 72 | ], 73 | 74 | // WGSL 75 | vertexBody: /* wgsl */ ` 76 | struct SystemUniform { 77 | projectionMatrix: mat4x4, 78 | viewMatrix: mat4x4, 79 | }; 80 | 81 | @group(0) @binding(0) var system: SystemUniform; 82 | 83 | struct MeshUniform { 84 | modelMatrix: mat4x4, 85 | }; 86 | 87 | @group(1) @binding(0) var mesh: MeshUniform; 88 | `, 89 | vertex: /* wgsl */ ` 90 | var output: Output; 91 | output.vNormal = normal; 92 | output.vUv = uv; 93 | 94 | output.position = system.projectionMatrix * system.viewMatrix * mesh.modelMatrix * vec4(position, 1.0); 95 | 96 | return output; 97 | `, 98 | fragmentBody: /* wgsl */ ` 99 | struct SystemUniform { 100 | projectionMatrix: mat4x4, 101 | viewMatrix: mat4x4, 102 | }; 103 | 104 | @group(0) @binding(0) var system: SystemUniform; 105 | 106 | struct MeshUniform { 107 | modelMatrix: mat4x4, 108 | }; 109 | 110 | @group(1) @binding(0) var mesh: MeshUniform; 111 | 112 | @group(1) @binding(1) var uSampler: sampler; 113 | @group(1) @binding(2) var uTexture: texture_2d; 114 | `, 115 | fragment: /* wgsl */ ` 116 | // return vec4(vNormal * 0.5 + 0.5, 1.0); 117 | var outColor: vec4; 118 | outColor = textureSample(uTexture, uSampler, vUv); 119 | return outColor; 120 | `, 121 | 122 | // GLSL 123 | glsl: { 124 | language: "glsl", 125 | vertex: /* glsl */ ` 126 | vNormal = normal; 127 | vUv = uv; 128 | 129 | gl_Position = system.projectionMatrix * system.viewMatrix * mesh.modelMatrix * vec4(position, 1.0); 130 | `, 131 | fragment: /* glsl */ ` 132 | // outColor = vec4(vNormal * 0.5 + 0.5, 1.0); 133 | outColor = texture(sampler2D(uTexture, uSampler), vUv); 134 | `, 135 | }, 136 | }); 137 | 138 | // Layouts 139 | // const systemBindGroupLayout = new BindGroupLayout([ 140 | // { 141 | // buffer: {}, 142 | // visibility: GPUShaderStage.VERTEX, 143 | // name: "System", 144 | // uniforms: [ 145 | // new Uniform("projectionMatrix", "mat4"), 146 | // new Uniform("viewMatrix", "mat4"), 147 | // ], 148 | // }, 149 | // ]); 150 | 151 | // const meshBindGroupLayout = new BindGroupLayout([ 152 | // { 153 | // buffer: {}, 154 | // visibility: GPUShaderStage.VERTEX, 155 | // name: "Mesh", 156 | // uniforms: [new Uniform("modelMatrix", "mat4")], 157 | // }, 158 | // { 159 | // sampler: {}, 160 | // visibility: GPUShaderStage.FRAGMENT, 161 | // name: "uSampler", 162 | // }, 163 | // { 164 | // texture: {}, 165 | // visibility: GPUShaderStage.FRAGMENT, 166 | // name: "uTexture", 167 | // dimension: "2d", 168 | // }, 169 | // ]); 170 | 171 | // Buffers 172 | const systemUniformsBuffer = new Buffer(); 173 | // systemUniformsBuffer.uniformBuffer(systemBindGroupLayout.getBindGroupSize()); 174 | systemUniformsBuffer.uniformBuffer(128); 175 | 176 | const meshUniformsBuffer = new Buffer(); 177 | // meshUniformsBuffer.uniformBuffer(meshBindGroupLayout.getBindGroupSize()); 178 | meshUniformsBuffer.uniformBuffer(64); 179 | 180 | // Bindings 181 | const systemUniformBindGroup = new BindGroup({ 182 | // layout: systemBindGroupLayout.gpuBindGroupLayout, 183 | layout: pipeline.gpuPipeline.getBindGroupLayout(0), 184 | resources: [ 185 | { 186 | buffer: systemUniformsBuffer.gpuBuffer, 187 | offset: 0, 188 | // size: systemBindGroupLayout.getBindGroupSize(), 189 | }, 190 | ], 191 | }); 192 | 193 | const uvSampler = new Sampler(); 194 | 195 | const uvImage = document.createElement("img"); 196 | uvImage.src = "assets/uv.jpg"; 197 | await uvImage.decode(); 198 | 199 | const uvTexture = new Texture(null, uvImage); 200 | 201 | const meshUniformBindGroup = new BindGroup({ 202 | // layout: meshBindGroupLayout.gpuBindGroupLayout, 203 | layout: pipeline.gpuPipeline.getBindGroupLayout(1), 204 | resources: [ 205 | { 206 | buffer: meshUniformsBuffer.gpuBuffer, 207 | offset: 0, 208 | // size: meshBindGroupLayout.getBindGroupSize(), 209 | }, 210 | uvSampler.gpuSampler, 211 | uvTexture.gpuTexture.createView(), 212 | ], 213 | }); 214 | 215 | // Geometry 216 | const modelMatrix = mat4.create(); 217 | 218 | const geometry = cube(); 219 | const geometryVertexBuffer = new Buffer(); 220 | const geometryIndicesBuffer = new Buffer(); 221 | 222 | geometryVertexBuffer.vertexBuffer( 223 | typedArrayInterleave( 224 | Float32Array, 225 | [3, 3, 2], 226 | geometry.positions, 227 | geometry.normals, 228 | geometry.uvs 229 | ) 230 | ); 231 | geometryIndicesBuffer.indexBuffer(new Uint16Array(geometry.cells)); 232 | 233 | // Command 234 | const clearCommand = new Command({ 235 | pass: new Pass( 236 | "render", 237 | [new Attachment({ r: 0.07, g: 0.07, b: 0.07, a: 1 })], 238 | new Attachment(1) 239 | ), 240 | }); 241 | const drawGeometryCommand = new Command({ 242 | pipeline, 243 | bindGroups: [systemUniformBindGroup, meshUniformBindGroup], 244 | vertexBuffers: [geometryVertexBuffer], 245 | indexBuffer: geometryIndicesBuffer, 246 | indexFormat: GPUIndexFormat.Uint16, 247 | count: geometry.cells.length, 248 | }); 249 | 250 | // Helpers 251 | // const axes = new Axes(systemBindGroupLayout, systemUniformBindGroup); 252 | 253 | // Frame 254 | requestAnimationFrame(function frame() { 255 | if (State.error) return; 256 | 257 | clock.getDelta(); 258 | 259 | controls.update(); 260 | camera.position = controls.position; 261 | camera.target = controls.target; 262 | camera.update(); 263 | 264 | systemUniformsBuffer.setSubData( 265 | 0, 266 | typedArrayConcat(Float32Array, camera.projectionMatrix, camera.viewMatrix) 267 | ); 268 | meshUniformsBuffer.setSubData(0, modelMatrix); 269 | 270 | context.render(() => { 271 | context.submit(clearCommand, () => { 272 | // context.submit(axes.command); 273 | context.submit(drawGeometryCommand); 274 | }); 275 | }); 276 | requestAnimationFrame(frame); 277 | }); 278 | clock.start(); 279 | 280 | window.addEventListener("resize", onResize); 281 | -------------------------------------------------------------------------------- /examples/cube.js: -------------------------------------------------------------------------------- 1 | import { 2 | Attachment, 3 | Attribute, 4 | Axes, 5 | BindGroup, 6 | BindGroupLayout, 7 | Buffer, 8 | Clock, 9 | Command, 10 | Context, 11 | GPUIndexFormat, 12 | GPUShaderStage, 13 | Pass, 14 | Pipeline, 15 | Sampler, 16 | State, 17 | Texture, 18 | Uniform, 19 | WGSLBuiltIn, 20 | } from "../lib/index.js"; 21 | 22 | import { mat4 } from "gl-matrix"; 23 | import typedArrayInterleave from "typed-array-interleave"; 24 | import typedArrayConcat from "typed-array-concat"; 25 | import { PerspectiveCamera, Controls } from "cameras"; 26 | import { cube } from "primitive-geometry"; 27 | import { Pane } from "tweakpane"; 28 | 29 | State.debug = true; 30 | 31 | const context = new Context(); 32 | if (!(await context.init())) { 33 | const message = "Your browser doesn't support WebGPU."; 34 | document.body.textContent = message; 35 | throw new Error(message); 36 | } 37 | document.body.appendChild(context.canvas); 38 | 39 | const clock = new Clock(); 40 | 41 | // Camera 42 | const camera = new PerspectiveCamera({ 43 | position: [3, 3, 3], 44 | }); 45 | camera.update(); 46 | const controls = new Controls({ 47 | element: context.canvas, 48 | camera, 49 | position: camera.position, 50 | target: camera.target, 51 | distanceBounds: [camera.near, camera.far], 52 | }); 53 | 54 | const onResize = () => { 55 | context.resize(window.innerWidth, window.innerHeight); 56 | camera.aspect = window.innerWidth / window.innerHeight; 57 | camera.updateProjectionMatrix(); 58 | }; 59 | onResize(); 60 | 61 | // Layouts 62 | const systemBindGroupLayout = new BindGroupLayout([ 63 | { 64 | buffer: {}, 65 | visibility: GPUShaderStage.VERTEX, 66 | name: "System", 67 | uniforms: [ 68 | new Uniform("projectionMatrix", "mat4"), 69 | new Uniform("viewMatrix", "mat4"), 70 | ], 71 | }, 72 | ]); 73 | 74 | const meshBindGroupLayout = new BindGroupLayout([ 75 | { 76 | buffer: {}, 77 | visibility: GPUShaderStage.VERTEX, 78 | name: "Mesh", 79 | uniforms: [new Uniform("modelMatrix", "mat4")], 80 | }, 81 | { 82 | sampler: {}, 83 | visibility: GPUShaderStage.FRAGMENT, 84 | name: "uSampler", 85 | }, 86 | { 87 | texture: {}, 88 | visibility: GPUShaderStage.FRAGMENT, 89 | name: "uTexture", 90 | dimension: "2d", 91 | }, 92 | ]); 93 | 94 | // Buffers 95 | const systemUniformsBuffer = new Buffer(); 96 | systemUniformsBuffer.uniformBuffer(systemBindGroupLayout.getBindGroupSize()); 97 | 98 | const meshUniformsBuffer = new Buffer(); 99 | meshUniformsBuffer.uniformBuffer(meshBindGroupLayout.getBindGroupSize()); 100 | 101 | // Bindings 102 | const systemUniformBindGroup = new BindGroup({ 103 | layout: systemBindGroupLayout.gpuBindGroupLayout, 104 | resources: [ 105 | { 106 | buffer: systemUniformsBuffer.gpuBuffer, 107 | offset: 0, 108 | size: systemBindGroupLayout.getBindGroupSize(), 109 | }, 110 | ], 111 | }); 112 | 113 | const uvSampler = new Sampler(); 114 | 115 | const uvImage = document.createElement("img"); 116 | uvImage.src = "assets/uv.jpg"; 117 | await uvImage.decode(); 118 | 119 | const uvTexture = new Texture(null, uvImage); 120 | 121 | const meshUniformBindGroup = new BindGroup({ 122 | layout: meshBindGroupLayout.gpuBindGroupLayout, 123 | resources: [ 124 | { 125 | buffer: meshUniformsBuffer.gpuBuffer, 126 | offset: 0, 127 | size: meshBindGroupLayout.getBindGroupSize(), 128 | }, 129 | uvSampler.gpuSampler, 130 | uvTexture.gpuTexture.createView(), 131 | ], 132 | }); 133 | 134 | // Geometry 135 | const modelMatrix = mat4.create(); 136 | 137 | const geometry = cube(); 138 | const geometryVertexBuffer = new Buffer(); 139 | const geometryIndicesBuffer = new Buffer(); 140 | 141 | geometryVertexBuffer.vertexBuffer( 142 | typedArrayInterleave( 143 | Float32Array, 144 | [3, 3, 2], 145 | geometry.positions, 146 | geometry.normals, 147 | geometry.uvs 148 | ) 149 | ); 150 | geometryIndicesBuffer.indexBuffer(new Uint16Array(geometry.cells)); 151 | 152 | // Pipeline 153 | const pipeline = new Pipeline({ 154 | bindGroupLayouts: [systemBindGroupLayout, meshBindGroupLayout], 155 | ins: [ 156 | new Attribute("position", "vec3"), 157 | new Attribute("normal", "vec3"), 158 | new Attribute("uv", "vec2"), 159 | ], 160 | outs: [ 161 | WGSLBuiltIn.Position, 162 | new Attribute("vNormal", "vec3"), 163 | new Attribute("vUv", "vec2"), 164 | ], 165 | 166 | // WGSL 167 | vertex: /* wgsl */ ` 168 | var output: Output; 169 | output.vNormal = normal; 170 | output.vUv = uv; 171 | 172 | output.position = system.projectionMatrix * system.viewMatrix * mesh.modelMatrix * vec4(position, 1.0); 173 | 174 | return output; 175 | `, 176 | fragment: /* wgsl */ ` 177 | // return vec4(vNormal * 0.5 + 0.5, 1.0); 178 | var outColor: vec4; 179 | outColor = textureSample(uTexture, uSampler, vUv); 180 | return outColor; 181 | `, 182 | 183 | // GLSL 184 | glsl: { 185 | language: "glsl", 186 | vertex: /* glsl */ ` 187 | vNormal = normal; 188 | vUv = uv; 189 | 190 | gl_Position = system.projectionMatrix * system.viewMatrix * mesh.modelMatrix * vec4(position, 1.0); 191 | `, 192 | fragment: /* glsl */ ` 193 | // outColor = vec4(vNormal * 0.5 + 0.5, 1.0); 194 | outColor = texture(sampler2D(uTexture, uSampler), vUv); 195 | `, 196 | }, 197 | }); 198 | 199 | // Command 200 | const clearCommand = new Command({ 201 | pass: new Pass( 202 | "render", 203 | [new Attachment({ r: 0.07, g: 0.07, b: 0.07, a: 1 })], 204 | new Attachment(1) 205 | ), 206 | }); 207 | const drawGeometryCommand = new Command({ 208 | pipeline, 209 | bindGroups: [systemUniformBindGroup, meshUniformBindGroup], 210 | vertexBuffers: [geometryVertexBuffer], 211 | indexBuffer: geometryIndicesBuffer, 212 | indexFormat: GPUIndexFormat.Uint16, 213 | count: geometry.cells.length, 214 | }); 215 | 216 | // Helpers 217 | // const axes = new Axes(systemBindGroupLayout, systemUniformBindGroup); 218 | 219 | // Frame 220 | requestAnimationFrame(function frame() { 221 | if (State.error) return; 222 | 223 | clock.getDelta(); 224 | 225 | controls.update(); 226 | camera.position = controls.position; 227 | camera.target = controls.target; 228 | camera.update(); 229 | 230 | systemUniformsBuffer.setSubData( 231 | 0, 232 | typedArrayConcat(Float32Array, camera.projectionMatrix, camera.viewMatrix) 233 | ); 234 | meshUniformsBuffer.setSubData(0, modelMatrix); 235 | 236 | context.render(() => { 237 | context.submit(clearCommand, () => { 238 | // context.submit(axes.command); 239 | context.submit(drawGeometryCommand); 240 | }); 241 | }); 242 | requestAnimationFrame(frame); 243 | }); 244 | clock.start(); 245 | 246 | window.addEventListener("resize", onResize); 247 | 248 | // GUI 249 | const pane = new Pane({ title: "Parameters" }); 250 | pane.addButton({ title: "Use GLSL (see in console)" }).on("click", () => { 251 | Object.assign(pipeline, pipeline.glsl); 252 | pipeline.init(); 253 | }); 254 | -------------------------------------------------------------------------------- /examples/full-screen-triangle.js: -------------------------------------------------------------------------------- 1 | import { 2 | Attachment, 3 | Attribute, 4 | BindGroup, 5 | BindGroupLayout, 6 | Buffer, 7 | Clock, 8 | Command, 9 | Context, 10 | GPUIndexFormat, 11 | GPUShaderStage, 12 | Pass, 13 | Pipeline, 14 | Shaders, 15 | State, 16 | Uniform, 17 | WGSLBuiltIn, 18 | } from "../lib/index.js"; 19 | 20 | import typedArrayInterleave from "typed-array-interleave"; 21 | 22 | State.debug = true; 23 | 24 | const context = new Context(); 25 | if (!(await context.init())) { 26 | const message = "Your browser doesn't support WebGPU."; 27 | document.body.textContent = message; 28 | throw new Error(message); 29 | } 30 | document.body.appendChild(context.canvas); 31 | 32 | const clock = new Clock(); 33 | 34 | // Layouts 35 | const systemBindGroupLayout = new BindGroupLayout([ 36 | { 37 | buffer: {}, 38 | visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, 39 | name: "System", 40 | uniforms: [new Uniform("resolution", "vec2"), new Uniform("time", "float")], 41 | }, 42 | ]); 43 | 44 | // Buffers 45 | const systemUniformsBuffer = new Buffer(); 46 | systemUniformsBuffer.uniformBuffer(systemBindGroupLayout.getBindGroupSize()); 47 | 48 | // Bindings 49 | const systemUniformBindGroup = new BindGroup({ 50 | layout: systemBindGroupLayout.gpuBindGroupLayout, 51 | resources: [ 52 | { 53 | buffer: systemUniformsBuffer.gpuBuffer, 54 | offset: 0, 55 | size: systemBindGroupLayout.getBindGroupSize(), 56 | }, 57 | ], 58 | }); 59 | 60 | // Geometry 61 | const geometryVertexBuffer = new Buffer(); 62 | const geometryIndicesBuffer = new Buffer(); 63 | 64 | geometryVertexBuffer.vertexBuffer( 65 | typedArrayInterleave( 66 | Float32Array, 67 | [2, 2], 68 | new Float32Array([-1, -1, 3, -1, -1, 3]), 69 | new Float32Array([0, 0, 2, 0, 0, 2]) 70 | ) 71 | ); 72 | geometryIndicesBuffer.indexBuffer(Uint32Array.of(0, 1, 2)); 73 | 74 | // Pipeline 75 | const pipeline = new Pipeline({ 76 | bindGroupLayouts: [systemBindGroupLayout], 77 | ins: [ 78 | new Attribute("position", "vec2"), 79 | new Attribute("uv", "vec2"), 80 | ], 81 | outs: [WGSLBuiltIn.Position, new Attribute("vUv", "vec2")], 82 | 83 | // WGSL 84 | vertex: /* wgsl */ ` 85 | var output: Output; 86 | output.vUv = uv; 87 | output.vUv.x = output.vUv.x * (system.resolution.x / system.resolution.y); 88 | output.position = vec4(position, 0.0, 1.0); 89 | 90 | return output; 91 | `, 92 | fragmentBody: /* wgsl */ `${Shaders.NOISE.WORLEY.WORLEY2D.replace( 93 | "let jitter = 1.0;", 94 | "let jitter = 1.0 + 0.5 * sin(2.0 * system.time);" 95 | )} 96 | `, 97 | fragment: /* wgsl */ ` 98 | var p = vUv; 99 | p.x = p.x + system.time * 0.01; 100 | p.y = p.y + system.time * 0.01; 101 | let uv = 1.2 * worley2d(p * 20.0); 102 | 103 | var color = mix(vec3(0.5, 0.0, 0.0), vec3(0.9, 0.0, 0.0), uv.y); 104 | color = mix(color, vec3(1.0), uv.x); 105 | 106 | return vec4(color.x, color.y, color.z, 1.0); 107 | `, 108 | }); 109 | 110 | // Command 111 | const clearCommand = new Command({ 112 | pass: new Pass( 113 | "render", 114 | [new Attachment({ r: 0.07, g: 0.07, b: 0.07, a: 1 })], 115 | new Attachment(1) 116 | ), 117 | }); 118 | const drawTriangleCommand = new Command({ 119 | pipeline, 120 | bindGroups: [systemUniformBindGroup], 121 | vertexBuffers: [geometryVertexBuffer], 122 | indexBuffer: geometryIndicesBuffer, 123 | indexFormat: GPUIndexFormat.Uint32, 124 | count: 3, 125 | }); 126 | 127 | const onResize = () => { 128 | context.resize(window.innerWidth, window.innerHeight); 129 | 130 | systemUniformsBuffer.setSubData( 131 | 0, 132 | Float32Array.of(window.innerWidth, window.innerHeight) 133 | ); 134 | }; 135 | onResize(); 136 | 137 | // Frame 138 | requestAnimationFrame(function frame() { 139 | if (State.error) return; 140 | 141 | clock.getDelta(); 142 | 143 | systemUniformsBuffer.setSubData( 144 | 2 * Float32Array.BYTES_PER_ELEMENT, 145 | Float32Array.of(clock.time) 146 | ); 147 | 148 | context.render(() => { 149 | context.submit(clearCommand, () => { 150 | context.submit(drawTriangleCommand); 151 | }); 152 | }); 153 | requestAnimationFrame(frame); 154 | }); 155 | clock.start(); 156 | 157 | window.addEventListener("resize", onResize); 158 | -------------------------------------------------------------------------------- /examples/instancing.js: -------------------------------------------------------------------------------- 1 | import { 2 | Attachment, 3 | Attribute, 4 | BindGroup, 5 | BindGroupLayout, 6 | Buffer, 7 | Clock, 8 | Command, 9 | Context, 10 | Pass, 11 | Pipeline, 12 | GPUShaderStage, 13 | Uniform, 14 | State, 15 | GPUIndexFormat, 16 | WGSLBuiltIn, 17 | Shaders, 18 | } from "../lib/index.js"; 19 | 20 | import { mat4 } from "gl-matrix"; 21 | import typedArrayInterleave from "typed-array-interleave"; 22 | import typedArrayConcat from "typed-array-concat"; 23 | import { OrthographicCamera } from "cameras"; 24 | import { cube } from "primitive-geometry"; 25 | import { Pane } from "tweakpane"; 26 | 27 | State.debug = true; 28 | 29 | const GRID_SIZE = 16; 30 | const INSTANCES_COUNT = GRID_SIZE * GRID_SIZE; 31 | 32 | const context = new Context(); 33 | if (!(await context.init())) { 34 | const message = "Your browser doesn't support WebGPU."; 35 | document.body.textContent = message; 36 | throw new Error(message); 37 | } 38 | document.body.appendChild(context.canvas); 39 | 40 | const clock = new Clock(); 41 | 42 | // Camera 43 | const camera = new OrthographicCamera({ 44 | position: [3, 1.5, 3], 45 | }); 46 | camera.update(); 47 | 48 | const onResize = () => { 49 | context.resize(window.innerWidth, window.innerHeight); 50 | const aspect = window.innerWidth / window.innerHeight; 51 | const viewSize = 10; 52 | Object.assign(camera, { 53 | near: -2000, 54 | far: 1000, 55 | left: (-0.5 * viewSize * aspect) / 2, 56 | right: (0.5 * viewSize * aspect) / 2, 57 | top: (0.5 * viewSize) / 2, 58 | bottom: (-0.5 * viewSize) / 2, 59 | zoom: 10, 60 | }); 61 | 62 | camera.updateProjectionMatrix(); 63 | }; 64 | onResize(); 65 | 66 | // Layouts 67 | const systemBindGroupLayout = new BindGroupLayout([ 68 | { 69 | buffer: {}, 70 | visibility: GPUShaderStage.VERTEX, 71 | name: "System", 72 | uniforms: [ 73 | new Uniform("projectionMatrix", "mat4"), 74 | new Uniform("viewMatrix", "mat4"), 75 | ], 76 | }, 77 | ]); 78 | 79 | const instancesBindGroupLayout = new BindGroupLayout([ 80 | { 81 | buffer: {}, 82 | visibility: GPUShaderStage.VERTEX, 83 | name: "Mesh", 84 | uniforms: [new Uniform("modelMatrix", "mat4", null, INSTANCES_COUNT)], 85 | }, 86 | { 87 | buffer: {}, 88 | visibility: GPUShaderStage.VERTEX, 89 | name: "Custom", 90 | uniforms: [ 91 | new Uniform("time", "float"), 92 | new Uniform("amplitude", "float"), 93 | new Uniform("frequency", "float"), 94 | ], 95 | }, 96 | ]); 97 | 98 | // Buffers 99 | const systemUniformsBuffer = new Buffer(); 100 | systemUniformsBuffer.uniformBuffer(systemBindGroupLayout.getBindGroupSize()); 101 | 102 | const meshUniformsBuffer = new Buffer(); 103 | meshUniformsBuffer.uniformBuffer(instancesBindGroupLayout.getBindGroupSize()); 104 | 105 | const meshData = new Float32Array(16 * INSTANCES_COUNT + 3); 106 | const spacing = 1.1; 107 | let offset = 0; 108 | for (let x = 0; x < GRID_SIZE; x++) { 109 | for (let z = 0; z < GRID_SIZE; z++) { 110 | const m = mat4.create(); 111 | mat4.translate(m, m, [ 112 | x * spacing - GRID_SIZE * spacing * 0.5, 113 | -2, 114 | z * spacing - GRID_SIZE * spacing * 0.5, 115 | ]); 116 | 117 | meshData.set(m, 16 * offset); 118 | offset++; 119 | } 120 | } 121 | meshData[16 * INSTANCES_COUNT + 0] = 0.0; 122 | meshData[16 * INSTANCES_COUNT + 1] = 4.0; 123 | meshData[16 * INSTANCES_COUNT + 2] = 0.05; 124 | meshUniformsBuffer.setSubData(0, meshData); 125 | 126 | // Bindings 127 | const systemUniformBindGroup = new BindGroup({ 128 | layout: systemBindGroupLayout.gpuBindGroupLayout, 129 | resources: [ 130 | { 131 | buffer: systemUniformsBuffer.gpuBuffer, 132 | offset: 0, 133 | size: systemBindGroupLayout.getBindGroupSize(), 134 | }, 135 | ], 136 | }); 137 | 138 | const meshUniformBindGroup = new BindGroup({ 139 | layout: instancesBindGroupLayout.gpuBindGroupLayout, 140 | resources: [ 141 | { 142 | buffer: meshUniformsBuffer.gpuBuffer, 143 | offset: 0, 144 | size: instancesBindGroupLayout.getBindingSize( 145 | instancesBindGroupLayout.entries[0] 146 | ), 147 | }, 148 | { 149 | buffer: meshUniformsBuffer.gpuBuffer, 150 | offset: 4 * 16 * INSTANCES_COUNT, 151 | size: instancesBindGroupLayout.getBindingSize( 152 | instancesBindGroupLayout.entries[1] 153 | ), 154 | }, 155 | ], 156 | }); 157 | 158 | // Geometry 159 | const geometry = cube(); 160 | const geometryVertexBuffer = new Buffer(); 161 | const geometryIndicesBuffer = new Buffer(); 162 | 163 | geometryVertexBuffer.vertexBuffer( 164 | typedArrayInterleave( 165 | Float32Array, 166 | [3, 3, 2], 167 | geometry.positions, 168 | geometry.normals, 169 | geometry.uvs 170 | ) 171 | ); 172 | geometryIndicesBuffer.indexBuffer(new Uint16Array(geometry.cells)); 173 | 174 | // Pipeline 175 | const pipeline = new Pipeline({ 176 | bindGroupLayouts: [systemBindGroupLayout, instancesBindGroupLayout], 177 | ins: [ 178 | WGSLBuiltIn.InstanceIndex, 179 | new Attribute("position", "vec3"), 180 | new Attribute("normal", "vec3"), 181 | new Attribute("uv", "vec2"), 182 | ], 183 | outs: [WGSLBuiltIn.Position, new Attribute("vNormal", "vec3")], 184 | 185 | // WGSL 186 | vertexBody: /* wgsl */ `${Shaders.NOISE.CLASSIC.CNOISE3D}`, 187 | vertex: /* wgsl */ ` 188 | var output: Output; 189 | output.vNormal = normal; 190 | 191 | let modelMatrix = mesh.modelMatrix[instance_index]; 192 | let worldPosition = modelMatrix * vec4(position, 1.0); 193 | 194 | let displacement = custom.amplitude * cnoise3d(custom.frequency * worldPosition.xyz + custom.time); 195 | let offset = vec3(worldPosition.x, worldPosition.y + displacement, worldPosition.z); 196 | 197 | output.position = system.projectionMatrix * system.viewMatrix * modelMatrix * vec4(position + offset, 1.0); 198 | 199 | return output; 200 | `, 201 | fragment: /* wgsl */ ` 202 | return vec4(vNormal * 0.5 + 0.5, 1.0); 203 | `, 204 | 205 | // GLSL 206 | glsl: { 207 | language: "glsl", 208 | vertexBody: /* wgsl */ `${Shaders.GLSL.NOISE.CLASSIC.CNOISE3D}`, 209 | vertex: /* glsl */ ` 210 | vNormal = normal; 211 | 212 | mat4 modelMatrix = mesh.modelMatrix[gl_InstanceIndex]; 213 | vec4 worldPosition = modelMatrix * vec4(position, 1.0); 214 | 215 | float displacement = custom.amplitude * cnoise3d(custom.frequency * worldPosition.xyz + custom.time); 216 | vec3 offset = vec3(worldPosition.x, worldPosition.y + displacement, worldPosition.z); 217 | 218 | gl_Position = system.projectionMatrix * system.viewMatrix * modelMatrix * vec4(position + offset, 1.0); 219 | `, 220 | fragment: /* glsl */ ` 221 | outColor = vec4(vNormal * 0.5 + 0.5, 1.0); 222 | `, 223 | }, 224 | }); 225 | 226 | // Command 227 | const drawGeometryCommand = new Command({ 228 | pass: new Pass( 229 | "render", 230 | [new Attachment({ r: 0.07, g: 0.07, b: 0.07, a: 1 })], 231 | new Attachment(1) 232 | ), 233 | pipeline, 234 | bindGroups: [systemUniformBindGroup, meshUniformBindGroup], 235 | vertexBuffers: [geometryVertexBuffer], 236 | indexBuffer: geometryIndicesBuffer, 237 | indexFormat: GPUIndexFormat.Uint16, 238 | count: geometry.cells.length, 239 | instances: INSTANCES_COUNT, 240 | }); 241 | 242 | // Frame 243 | requestAnimationFrame(function frame() { 244 | if (State.error) return; 245 | 246 | clock.getDelta(); 247 | 248 | systemUniformsBuffer.setSubData( 249 | 0, 250 | typedArrayConcat(Float32Array, camera.projectionMatrix, camera.viewMatrix) 251 | ); 252 | 253 | meshUniformsBuffer.setSubData( 254 | 4 * (16 * INSTANCES_COUNT), 255 | Float32Array.of(clock.time) 256 | ); 257 | 258 | context.render(() => { 259 | context.submit(drawGeometryCommand); 260 | }); 261 | requestAnimationFrame(frame); 262 | }); 263 | clock.start(); 264 | 265 | window.addEventListener("resize", onResize); 266 | 267 | // GUI 268 | const pane = new Pane({ title: "Parameters" }); 269 | pane.addButton({ title: "Use GLSL (see in console)" }).on("click", (event) => { 270 | Object.assign(pipeline, pipeline.glsl); 271 | pipeline.init(); 272 | }); 273 | -------------------------------------------------------------------------------- /examples/render-to-texture.js: -------------------------------------------------------------------------------- 1 | import { 2 | Attachment, 3 | Attribute, 4 | Axes, 5 | BindGroup, 6 | BindGroupLayout, 7 | Buffer, 8 | Clock, 9 | Command, 10 | Context, 11 | GPUIndexFormat, 12 | GPUShaderStage, 13 | Pass, 14 | Pipeline, 15 | Sampler, 16 | State, 17 | Texture, 18 | Uniform, 19 | WGSLBuiltIn, 20 | GPUPrimitiveTopology, 21 | } from "../lib/index.js"; 22 | 23 | import { mat4 } from "gl-matrix"; 24 | import typedArrayInterleave from "typed-array-interleave"; 25 | import typedArrayConcat from "typed-array-concat"; 26 | import { PerspectiveCamera, Controls } from "cameras"; 27 | import { cube } from "primitive-geometry"; 28 | import { Pane } from "tweakpane"; 29 | 30 | State.debug = true; 31 | 32 | const context = new Context(); 33 | if (!(await context.init())) { 34 | const message = "Your browser doesn't support WebGPU."; 35 | document.body.textContent = message; 36 | throw new Error(message); 37 | } 38 | document.body.appendChild(context.canvas); 39 | 40 | const CONFIG = { 41 | debug: false, 42 | rotate: true, 43 | }; 44 | const rttFormat = "rgba32float"; 45 | 46 | const clock = new Clock(); 47 | 48 | // Camera 49 | const camera = new PerspectiveCamera({ 50 | position: [3, 3, 3], 51 | }); 52 | camera.update(); 53 | const controls = new Controls({ 54 | element: context.canvas, 55 | camera, 56 | position: camera.position, 57 | target: camera.target, 58 | distanceBounds: [camera.near, camera.far], 59 | }); 60 | 61 | // Layouts 62 | const systemBindGroupLayout = new BindGroupLayout([ 63 | { 64 | buffer: {}, 65 | visibility: GPUShaderStage.VERTEX, 66 | name: "System", 67 | uniforms: [ 68 | new Uniform("projectionMatrix", "mat4"), 69 | new Uniform("viewMatrix", "mat4"), 70 | ], 71 | }, 72 | ]); 73 | 74 | const meshBindGroupLayout = new BindGroupLayout([ 75 | { 76 | buffer: {}, 77 | visibility: GPUShaderStage.VERTEX, 78 | name: "Mesh", 79 | uniforms: [new Uniform("modelMatrix", "mat4")], 80 | }, 81 | { 82 | sampler: {}, 83 | visibility: GPUShaderStage.FRAGMENT, 84 | name: "uSampler", 85 | }, 86 | { 87 | texture: {}, 88 | visibility: GPUShaderStage.FRAGMENT, 89 | name: "uTexture", 90 | dimension: "2d", 91 | }, 92 | ]); 93 | const meshBindGroupLayoutRTT = new BindGroupLayout([ 94 | { 95 | buffer: {}, 96 | visibility: GPUShaderStage.VERTEX, 97 | name: "Mesh", 98 | uniforms: [new Uniform("modelMatrix", "mat4")], 99 | }, 100 | { 101 | texture: { sampleType: "unfilterable-float" }, 102 | visibility: GPUShaderStage.FRAGMENT, 103 | name: "uTexture", 104 | dimension: "2d", 105 | }, 106 | ]); 107 | 108 | // Buffers 109 | const systemUniformsBuffer = new Buffer(); 110 | systemUniformsBuffer.uniformBuffer(systemBindGroupLayout.getBindGroupSize()); 111 | 112 | const meshUniformsBuffer = new Buffer(); 113 | meshUniformsBuffer.uniformBuffer(meshBindGroupLayout.getBindGroupSize()); 114 | 115 | const meshUniformsBufferRTT = new Buffer(); 116 | meshUniformsBufferRTT.uniformBuffer(meshBindGroupLayoutRTT.getBindGroupSize()); 117 | 118 | // Bindings 119 | const systemUniformBindGroup = new BindGroup({ 120 | layout: systemBindGroupLayout.gpuBindGroupLayout, 121 | resources: [ 122 | { 123 | buffer: systemUniformsBuffer.gpuBuffer, 124 | offset: 0, 125 | size: systemBindGroupLayout.getBindGroupSize(), 126 | }, 127 | ], 128 | }); 129 | 130 | const uvImage = document.createElement("img"); 131 | uvImage.src = "assets/uv.jpg"; 132 | await uvImage.decode(); 133 | 134 | const meshUniformBindGroup = new BindGroup({ 135 | layout: meshBindGroupLayout.gpuBindGroupLayout, 136 | resources: [ 137 | { 138 | buffer: meshUniformsBuffer.gpuBuffer, 139 | offset: 0, 140 | size: meshBindGroupLayout.getBindGroupSize(), 141 | }, 142 | new Sampler().gpuSampler, 143 | new Texture(null, uvImage).gpuTexture.createView(), 144 | ], 145 | }); 146 | 147 | // Geometry 148 | const modelMatrix = mat4.create(); 149 | const modelMatrixRTT = mat4.create(); 150 | 151 | const geometry = cube(); 152 | const geometryVertexBuffer = new Buffer(); 153 | const geometryIndicesBuffer = new Buffer(); 154 | 155 | geometryVertexBuffer.vertexBuffer( 156 | typedArrayInterleave( 157 | Float32Array, 158 | [3, 3, 2], 159 | geometry.positions, 160 | geometry.normals, 161 | geometry.uvs 162 | ) 163 | ); 164 | geometryIndicesBuffer.indexBuffer(new Uint16Array(geometry.cells)); 165 | 166 | // Command 167 | const rttCommand = new Command({ 168 | pass: new Pass( 169 | "render", 170 | [new Attachment({ r: 0.14, g: 0.14, b: 0.14, a: 1 }, { view: null })], 171 | new Attachment(1) 172 | ), 173 | }); 174 | const drawGeometryToTextureCommand = new Command({ 175 | pipeline: new Pipeline({ 176 | bindGroupLayouts: [systemBindGroupLayout, meshBindGroupLayout], 177 | ins: [ 178 | new Attribute("position", "vec3"), 179 | new Attribute("normal", "vec3"), 180 | new Attribute("uv", "vec2"), 181 | ], 182 | outs: [ 183 | WGSLBuiltIn.Position, 184 | new Attribute("vNormal", "vec3"), 185 | new Attribute("vUv", "vec2"), 186 | ], 187 | fragmentOuts: [new Attribute("color", "vec4")], 188 | fragmentTargets: [{ format: rttFormat }], 189 | 190 | // WGSL 191 | vertex: /* wgsl */ ` 192 | var output: Output; 193 | output.vNormal = normal; 194 | output.vUv = uv; 195 | 196 | output.position = system.projectionMatrix * system.viewMatrix * mesh.modelMatrix * vec4(position, 1.0); 197 | 198 | return output; 199 | `, 200 | fragment: /* wgsl */ ` 201 | var output: Output; 202 | output.color = textureSample(uTexture, uSampler, vUv); 203 | return output; 204 | `, 205 | }), 206 | bindGroups: [systemUniformBindGroup, meshUniformBindGroup], 207 | vertexBuffers: [geometryVertexBuffer], 208 | indexBuffer: geometryIndicesBuffer, 209 | indexFormat: GPUIndexFormat.Uint16, 210 | count: geometry.cells.length, 211 | }); 212 | const clearCommand = new Command({ 213 | pass: new Pass( 214 | "render", 215 | [new Attachment({ r: 0.07, g: 0.07, b: 0.07, a: 1 })], 216 | new Attachment(1) 217 | ), 218 | }); 219 | const drawGeometryCommand = new Command({ 220 | pipeline: new Pipeline({ 221 | bindGroupLayouts: [systemBindGroupLayout, meshBindGroupLayoutRTT], 222 | ins: [ 223 | new Attribute("position", "vec3"), 224 | new Attribute("normal", "vec3"), 225 | new Attribute("uv", "vec2"), 226 | ], 227 | outs: [ 228 | WGSLBuiltIn.Position, 229 | new Attribute("vNormal", "vec3"), 230 | new Attribute("vUv", "vec2"), 231 | ], 232 | 233 | // WGSL 234 | vertex: /* wgsl */ ` 235 | var output: Output; 236 | output.vNormal = normal; 237 | output.vUv = uv; 238 | 239 | output.position = system.projectionMatrix * system.viewMatrix * mesh.modelMatrix * vec4(position, 1.0); 240 | 241 | return output; 242 | `, 243 | fragment: /* wgsl */ ` 244 | let textureSize = textureDimensions(uTexture); 245 | 246 | return textureLoad( 247 | uTexture, 248 | vec2(vec2(vUv.x, 1.0 - vUv.y) * vec2(textureSize)), 249 | 0 250 | ); 251 | `, 252 | }), 253 | bindGroups: [systemUniformBindGroup, null], 254 | vertexBuffers: [geometryVertexBuffer], 255 | indexBuffer: geometryIndicesBuffer, 256 | indexFormat: GPUIndexFormat.Uint16, 257 | count: geometry.cells.length, 258 | }); 259 | 260 | // RTT 261 | const updateRTT = () => { 262 | const rttTexture = new Texture({ 263 | format: rttFormat, 264 | size: { width: context.canvas.width, height: context.canvas.height }, 265 | usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING, 266 | }); 267 | 268 | const rttTextureView = rttTexture.gpuTexture.createView({ 269 | format: rttFormat, 270 | }); 271 | 272 | rttCommand.pass.colorAttachments[0].view = rttTextureView; 273 | 274 | drawGeometryCommand.bindGroups[1] = new BindGroup({ 275 | layout: meshBindGroupLayoutRTT.gpuBindGroupLayout, 276 | resources: [ 277 | { 278 | buffer: meshUniformsBufferRTT.gpuBuffer, 279 | offset: 0, 280 | size: meshBindGroupLayoutRTT.getBindGroupSize(), 281 | }, 282 | rttTextureView, 283 | ], 284 | }); 285 | }; 286 | 287 | const onResize = () => { 288 | context.resize(window.innerWidth, window.innerHeight); 289 | camera.aspect = window.innerWidth / window.innerHeight; 290 | camera.updateProjectionMatrix(); 291 | 292 | updateRTT(); 293 | }; 294 | onResize(); 295 | 296 | // Helpers 297 | // const axes = new Axes(systemBindGroupLayout, systemUniformBindGroup, { 298 | // fragmentTargets: [{ format: "rgba32float" }], 299 | // }); 300 | 301 | // Frame 302 | requestAnimationFrame(function frame() { 303 | if (State.error) return; 304 | 305 | clock.getDelta(); 306 | 307 | controls.update(); 308 | camera.position = controls.position; 309 | camera.target = controls.target; 310 | camera.update(); 311 | 312 | mat4.identity(modelMatrix); 313 | if (CONFIG.rotate) { 314 | mat4.rotateX(modelMatrix, modelMatrix, clock.time * 0.25); 315 | mat4.rotateY(modelMatrix, modelMatrix, clock.time); 316 | mat4.rotateZ(modelMatrix, modelMatrix, clock.time * 0.75); 317 | } 318 | 319 | systemUniformsBuffer.setSubData( 320 | 0, 321 | typedArrayConcat(Float32Array, camera.projectionMatrix, camera.viewMatrix) 322 | ); 323 | meshUniformsBuffer.setSubData(0, modelMatrix); 324 | meshUniformsBufferRTT.setSubData(0, modelMatrixRTT); 325 | 326 | context.render(() => { 327 | if (!CONFIG.debug) { 328 | context.submit(rttCommand, () => { 329 | // context.submit(axes.command); 330 | context.submit(drawGeometryToTextureCommand); 331 | }); 332 | context.submit(clearCommand, () => { 333 | context.submit(drawGeometryCommand); 334 | }); 335 | } else { 336 | context.submit(clearCommand, () => { 337 | context.submit(drawGeometryToTextureCommand); 338 | }); 339 | } 340 | }); 341 | 342 | requestAnimationFrame(frame); 343 | }); 344 | clock.start(); 345 | 346 | window.addEventListener("resize", onResize); 347 | 348 | // GUI 349 | const pane = new Pane({ title: "Parameters" }); 350 | pane.addBinding(CONFIG, "debug").on("change", (event) => { 351 | drawGeometryToTextureCommand.pipeline.fragmentTargets = !event.value 352 | ? [{ format: rttFormat }] 353 | : [{ format: "bgra8unorm" }]; 354 | drawGeometryToTextureCommand.pipeline.init(); 355 | }); 356 | pane.addBinding(CONFIG, "rotate"); 357 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | dgel 7 | 11 | 76 | 77 | 78 | 97 | 98 | 99 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dgel", 3 | "version": "0.17.1", 4 | "description": "A WebGPU engine.", 5 | "keywords": [ 6 | "webgpu", 7 | "engine", 8 | "3d", 9 | "opengl", 10 | "webgl" 11 | ], 12 | "homepage": "https://github.com/dmnsgn/dgel", 13 | "bugs": "https://github.com/dmnsgn/dgel/issues", 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/dmnsgn/dgel.git" 17 | }, 18 | "funding": [ 19 | { 20 | "type": "individual", 21 | "url": "https://paypal.me/dmnsgn" 22 | }, 23 | { 24 | "type": "individual", 25 | "url": "https://commerce.coinbase.com/checkout/56cbdf28-e323-48d8-9c98-7019e72c97f3" 26 | } 27 | ], 28 | "license": "MIT", 29 | "author": "Damien Seguin (https://github.com/dmnsgn)", 30 | "type": "module", 31 | "exports": { 32 | ".": { 33 | "types": "./types/index.d.ts", 34 | "default": "./lib/index.js" 35 | } 36 | }, 37 | "main": "lib/index.js", 38 | "module": "lib/index.js", 39 | "types": "types/index.d.ts", 40 | "scripts": { 41 | "build": "npx snowdev build", 42 | "clean": "rm -rf lib && rm -rf docs", 43 | "dev": "npx snowdev dev" 44 | }, 45 | "dependencies": { 46 | "gl-matrix": "^3.4.3", 47 | "tslib": "^2.6.3" 48 | }, 49 | "devDependencies": { 50 | "@types/gl-matrix": "^2.4.5", 51 | "@webgpu/glslang": "0.0.15", 52 | "@webgpu/types": "^0.1.43", 53 | "cameras": "^3.1.1", 54 | "canvas-record": "^5.0.1", 55 | "es-module-shims": "^1.10.0", 56 | "primitive-geometry": "^2.10.1", 57 | "seedrandom": "^3.0.5", 58 | "tweakpane": "^4.0.3", 59 | "typed-array-concat": "^3.0.0", 60 | "typed-array-interleave": "^2.0.0", 61 | "typescript": "^5.5.2" 62 | }, 63 | "engines": { 64 | "node": ">=22.0.0", 65 | "npm": ">=10.5.1", 66 | "snowdev": ">=2.1.x" 67 | }, 68 | "snowdev": { 69 | "dependencies": [ 70 | "@webgpu/glslang/dist/web-devel/glslang.js", 71 | "@webgpu/glslang/dist/web-devel/glslang.wasm", 72 | "es-module-shims", 73 | "typed-array-concat", 74 | "canvas-record", 75 | "cameras", 76 | "gl-matrix", 77 | "typed-array-interleave", 78 | "primitive-geometry", 79 | "seedrandom", 80 | "tweakpane" 81 | ] 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmnsgn/dgel/0b48231868bfc518e3a0e772859eb374001736cd/screenshot.jpg -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | import BuiltIn from "./core/BuiltIn.js"; 2 | import { ShaderStageName } from "./types.js"; 3 | 4 | // Bit fields 5 | // https://github.com/gpuweb/gpuweb/blob/main/spec/index.bs 6 | export const GPUBufferUsage: { [key: string]: GPUBufferUsageFlags } = { 7 | MAP_READ: 0x0001, 8 | MAP_WRITE: 0x0002, 9 | COPY_SRC: 0x0004, 10 | COPY_DST: 0x0008, 11 | INDEX: 0x0010, 12 | VERTEX: 0x0020, 13 | UNIFORM: 0x0040, 14 | STORAGE: 0x0080, 15 | INDIRECT: 0x0100, 16 | }; 17 | export const GPUTextureUsage: { [key: string]: GPUTextureUsageFlags } = { 18 | COPY_SRC: 0x01, 19 | COPY_DST: 0x02, 20 | SAMPLED: 0x04, 21 | STORAGE: 0x08, 22 | RENDER_ATTACHMENT: 0x10, 23 | }; 24 | export const GPUShaderStage: { [key: string]: GPUShaderStageFlags } = { 25 | VERTEX: 0x1, 26 | FRAGMENT: 0x2, 27 | COMPUTE: 0x4, 28 | }; 29 | 30 | // WebGPU 31 | // Strings 32 | // https://gpuweb.github.io/gpuweb/#binding-type 33 | export const BindingType: { [key: string]: string } = { 34 | // buffer 35 | Uniform: "uniform", 36 | Storage: "storage", 37 | ReadonlyStorage: "read-only-storage", 38 | // sampler 39 | Filtering: "filtering", 40 | NonFiltering: "non-filtering", 41 | Comparison: "comparison", 42 | // texture 43 | Float: "float", 44 | UnFilterableFloat: "unfilterable-float", 45 | Depth: "depth", 46 | Sint: "sint", 47 | Uint: "uint", 48 | ReadOnly: "read-only", 49 | // storageTexture 50 | WriteOnly: "write-only", 51 | }; 52 | // https://gpuweb.github.io/gpuweb/#vertex-state 53 | export const GPUIndexFormat: { [key: string]: GPUIndexFormat } = { 54 | Uint16: "uint16", 55 | Uint32: "uint32", 56 | }; 57 | // https://gpuweb.github.io/gpuweb/#canvas-configuration 58 | export const GPUCanvasCompositingAlphaMode: { 59 | [key: string]: GPUCanvasAlphaMode; 60 | } = { 61 | Opaque: "opaque", 62 | Premultiplied: "premultiplied", 63 | }; 64 | // https://gpuweb.github.io/gpuweb/#primitive-state 65 | export const GPUPrimitiveTopology: { [key: string]: GPUPrimitiveTopology } = { 66 | PointList: "point-list", 67 | LineList: "line-list", 68 | LineStrip: "line-strip", 69 | TriangleList: "triangle-list", 70 | TriangleStrip: "triangle-strip", 71 | }; 72 | export const GPUShaderStageName: { 73 | [key: GPUShaderStageFlags]: ShaderStageName; 74 | } = { 75 | [GPUShaderStage.VERTEX]: "vertex", 76 | [GPUShaderStage.FRAGMENT]: "fragment", 77 | [GPUShaderStage.COMPUTE]: "compute", 78 | }; 79 | export const GPUTextureSampleType: { [BindingType: string]: string } = { 80 | [BindingType.Float]: "f32", 81 | [BindingType.UnFilterableFloat]: "f32", 82 | [BindingType.Sint]: "i32", 83 | [BindingType.Uint]: "u32", 84 | }; 85 | 86 | // WGSL 87 | export const StorageClass: { [key: string]: string } = { 88 | Function: "function", 89 | Private: "private", 90 | Workgroup: "workgroup", 91 | Uniform: "uniform", 92 | Storage: "storage", 93 | Handle: "handle", 94 | }; 95 | export type StorageClass = (typeof StorageClass)[keyof typeof StorageClass]; 96 | 97 | export const AccessMode: { [key: string]: string } = { 98 | Read: "read", 99 | Write: "write", 100 | ReadWrite: "read-write", 101 | }; 102 | export type AccessMode = (typeof AccessMode)[keyof typeof AccessMode]; 103 | 104 | export const GLSL_SCALARS_TO_WGSL: { [key: string]: string } = { 105 | float: "f32", 106 | int: "i32", 107 | uint: "u32", 108 | bool: "bool", 109 | }; 110 | 111 | export const WGSL_SCALARS_TO_GLSL_PREFIX: { [key: string]: string } = { 112 | f32: "", 113 | i32: "i", 114 | u32: "u", 115 | bool: "b", 116 | }; 117 | 118 | export const WGSL_MATRIX_TO_GLSL: { [key: string]: string } = { 119 | mat2x2: "mat2", 120 | mat3x3: "mat3", 121 | mat4x4: "mat4", 122 | }; 123 | 124 | export const WGSL_TYPE_TO_GPU_VERTEX_FORMAT: { 125 | [key: string]: GPUVertexFormat; 126 | } = { 127 | f32: "float32", 128 | "vec2": "float32x2", 129 | "vec3": "float32x3", 130 | "vec4": "float32x4", 131 | 132 | u32: "uint32", 133 | "vec2": "uint32x2", 134 | "vec3": "uint32x3", 135 | "vec4": "uint32x4", 136 | 137 | i32: "sint32", 138 | "vec2": "sint32x2", 139 | "vec3": "sint32x3", 140 | "vec4": "sint32x4", 141 | }; 142 | 143 | // https://www.w3.org/TR/WGSL/#builtin-variables 144 | export const WGSLBuiltIn: { [key: string]: BuiltIn } = { 145 | VertexIndex: new BuiltIn("vertex_index", "u32", GPUShaderStage.VERTEX, "in"), 146 | InstanceIndex: new BuiltIn( 147 | "instance_index", 148 | "u32", 149 | GPUShaderStage.VERTEX, 150 | "in", 151 | ), 152 | Position: new BuiltIn("position", "vec4", GPUShaderStage.VERTEX, "out"), 153 | PositionOut: new BuiltIn( 154 | "position", 155 | "vec4", 156 | GPUShaderStage.FRAGMENT, 157 | "in", 158 | ), 159 | // Frag 160 | FrontFacing: new BuiltIn( 161 | "front_facing", 162 | "bool", 163 | GPUShaderStage.FRAGMENT, 164 | "in", 165 | ), 166 | FragDepth: new BuiltIn("frag_depth", "f32", GPUShaderStage.FRAGMENT, "out"), 167 | SampleIndex: new BuiltIn( 168 | "sample_index", 169 | "u32", 170 | GPUShaderStage.FRAGMENT, 171 | "in", 172 | ), 173 | SampleMask: new BuiltIn("sample_mask", "u32", GPUShaderStage.FRAGMENT, "in"), 174 | SampleMaskOut: new BuiltIn( 175 | "sample_mask", 176 | "u32", 177 | GPUShaderStage.FRAGMENT, 178 | "out", 179 | ), 180 | // Compute 181 | LocalInvocationId: new BuiltIn( 182 | "local_invocation_id", 183 | "vec3", 184 | GPUShaderStage.COMPUTE, 185 | "in", 186 | ), 187 | LocalInvocationIndex: new BuiltIn( 188 | "local_invocation_index", 189 | "u32", 190 | GPUShaderStage.COMPUTE, 191 | "in", 192 | ), 193 | GlobalInvocationId: new BuiltIn( 194 | "global_invocation_id", 195 | "vec3", 196 | GPUShaderStage.COMPUTE, 197 | "in", 198 | ), 199 | WorkgroupId: new BuiltIn( 200 | "workgroup_id", 201 | "vec3", 202 | GPUShaderStage.COMPUTE, 203 | "in", 204 | ), 205 | NumWorkgroups: new BuiltIn( 206 | "num_workgroups", 207 | "vec3", 208 | GPUShaderStage.COMPUTE, 209 | "in", 210 | ), 211 | }; 212 | -------------------------------------------------------------------------------- /src/core/Attachment.ts: -------------------------------------------------------------------------------- 1 | import { AttachmentOptions } from "../types.js"; 2 | 3 | class Attachment { 4 | public op: GPULoadOp = "clear"; 5 | public storeOp: GPUStoreOp = "store"; 6 | 7 | public view?: GPUTextureView; 8 | public resolveTarget?: GPUTextureView; 9 | 10 | constructor( 11 | public value: GPUColorDict | GPUColor | number, 12 | options: AttachmentOptions, 13 | ) { 14 | Object.assign(this, options); 15 | } 16 | } 17 | 18 | export default Attachment; 19 | -------------------------------------------------------------------------------- /src/core/Attribute.ts: -------------------------------------------------------------------------------- 1 | import Variable from "./Variable.js"; 2 | 3 | class Attribute extends Variable { 4 | constructor( 5 | public name: string, 6 | public type: string, 7 | ) { 8 | super(name, type); 9 | } 10 | 11 | public getWGSLString(index: number): string { 12 | return /* wgsl */ `@location(${index}) ${this.name}: ${ 13 | this.arrayCount ? `array<` : "" 14 | }${this.wgslType}${this.arrayCount ? `, ${this.arrayCount}>` : ""}`; 15 | } 16 | } 17 | 18 | export default Attribute; 19 | -------------------------------------------------------------------------------- /src/core/BindGroup.ts: -------------------------------------------------------------------------------- 1 | import State from "./State.js"; 2 | import { BindGroupOptions } from "../types.js"; 3 | 4 | class BindGroup { 5 | public gpuBindGroup: GPUBindGroup; 6 | 7 | constructor(options: BindGroupOptions) { 8 | this.gpuBindGroup = State.device.createBindGroup({ 9 | layout: options.layout, 10 | entries: options.resources.map((resource, i) => ({ 11 | binding: i, 12 | resource, 13 | })), 14 | }); 15 | } 16 | } 17 | 18 | export default BindGroup; 19 | -------------------------------------------------------------------------------- /src/core/BindGroupLayout.ts: -------------------------------------------------------------------------------- 1 | import State from "./State.js"; 2 | 3 | import { BindingType } from "../constants.js"; 4 | import { BindGroupLayoutEntry } from "../types.js"; 5 | 6 | class BindGroupLayout { 7 | public gpuBindGroupLayout: GPUBindGroupLayout; 8 | 9 | constructor(public entries: BindGroupLayoutEntry[] = []) { 10 | this.gpuBindGroupLayout = State.device.createBindGroupLayout({ 11 | entries: entries.map( 12 | ( 13 | { visibility, buffer, sampler, texture, storageTexture }, 14 | binding, 15 | ) => ({ 16 | binding, 17 | visibility, 18 | buffer, 19 | sampler, 20 | texture, 21 | storageTexture, 22 | }), 23 | ), 24 | }); 25 | } 26 | 27 | public getBindGroupSize(): number { 28 | let size = 0; 29 | 30 | for (let i = 0; i < this.entries.length; i++) { 31 | const entry = this.entries[i]; 32 | 33 | if ( 34 | entry.buffer && 35 | (!entry.buffer.type || entry.buffer.type === BindingType.Uniform) 36 | ) { 37 | size += this.getBindingSize(entry); 38 | } 39 | } 40 | 41 | return size + (size % 16); 42 | } 43 | 44 | public getBindingSize(entry: BindGroupLayoutEntry): number { 45 | let size = 0; 46 | 47 | if ( 48 | entry.buffer && 49 | (!entry.buffer.type || entry.buffer.type === BindingType.Uniform) 50 | ) { 51 | size += entry.uniforms 52 | .map( 53 | (uniform) => 54 | uniform.getSize(entry.qualifiers?.layout) * 55 | (uniform.arrayCount || 1), 56 | ) 57 | .reduce((a, b) => a + b, 0); 58 | } 59 | 60 | return size; 61 | } 62 | } 63 | 64 | export default BindGroupLayout; 65 | -------------------------------------------------------------------------------- /src/core/Buffer.ts: -------------------------------------------------------------------------------- 1 | import State from "./State.js"; 2 | import { GPUBufferUsage } from "../constants.js"; 3 | 4 | class Buffer { 5 | public gpuBuffer: GPUBuffer; 6 | 7 | public create( 8 | usage: GPUBufferUsageFlags, 9 | data: ArrayBufferView | null, 10 | size?: number, 11 | ): Buffer { 12 | this.gpuBuffer = State.device.createBuffer({ 13 | size: size || data.byteLength, 14 | usage, 15 | }); 16 | if (data) this.setSubData(0, data); 17 | 18 | return this; 19 | } 20 | 21 | public vertexBuffer(data: ArrayBufferView): Buffer { 22 | return this.create(GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST, data); 23 | } 24 | 25 | public indexBuffer(data: ArrayBufferView): Buffer { 26 | return this.create(GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST, data); 27 | } 28 | 29 | public uniformBuffer(size: number): Buffer { 30 | return this.create( 31 | GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, 32 | null, 33 | size, 34 | ); 35 | } 36 | 37 | // https://github.com/gpuweb/gpuweb/blob/main/design/BufferOperations.md 38 | public setSubData(offset: number, data: ArrayBufferView): void { 39 | const srcArrayBuffer = data.buffer; 40 | const byteCount = srcArrayBuffer.byteLength; 41 | const srcBuffer = State.device.createBuffer({ 42 | mappedAtCreation: true, 43 | size: byteCount, 44 | usage: GPUBufferUsage.COPY_SRC, 45 | }); 46 | const arrayBuffer = srcBuffer.getMappedRange(); 47 | 48 | new Uint8Array(arrayBuffer).set(new Uint8Array(srcArrayBuffer)); // memcpy 49 | srcBuffer.unmap(); 50 | 51 | this.copyToBuffer(srcBuffer, offset, byteCount); 52 | 53 | srcBuffer.destroy(); 54 | } 55 | 56 | public copyToBuffer( 57 | srcBuffer: GPUBuffer, 58 | offset: number, 59 | byteCount: number, 60 | ): void { 61 | const commandEncoder = State.device.createCommandEncoder(); 62 | commandEncoder.copyBufferToBuffer( 63 | srcBuffer, 64 | 0, 65 | this.gpuBuffer, 66 | offset, 67 | byteCount, 68 | ); 69 | State.device.queue.submit([commandEncoder.finish()]); 70 | } 71 | 72 | public copyToTexture( 73 | bytesPerRow: number, 74 | rowsPerImage: number, 75 | destination: GPUImageCopyTexture, 76 | extent: GPUExtent3D, 77 | ): void { 78 | const commandEncoder = State.device.createCommandEncoder(); 79 | commandEncoder.copyBufferToTexture( 80 | { 81 | buffer: this.gpuBuffer, 82 | bytesPerRow, 83 | rowsPerImage, 84 | }, 85 | destination, 86 | extent, 87 | ); 88 | State.device.queue.submit([commandEncoder.finish()]); 89 | } 90 | 91 | public destroy(): void { 92 | this.gpuBuffer.destroy(); 93 | } 94 | } 95 | 96 | export default Buffer; 97 | -------------------------------------------------------------------------------- /src/core/BuiltIn.ts: -------------------------------------------------------------------------------- 1 | import Variable from "./Variable.js"; 2 | 3 | class BuiltIn extends Variable { 4 | constructor( 5 | public name: string, 6 | public type: string, 7 | public visibility?: GPUShaderStageFlags, 8 | public io?: string, 9 | ) { 10 | super(name, type); 11 | } 12 | 13 | public getWGSLString(): string { 14 | return /* wgsl */ `@builtin(${this.name}) ${this.name}: ${this.wgslType}`; 15 | } 16 | } 17 | 18 | export default BuiltIn; 19 | -------------------------------------------------------------------------------- /src/core/Clock.ts: -------------------------------------------------------------------------------- 1 | class Clock { 2 | public startTime = 0; 3 | public prevTime = 0; 4 | public time = 0; 5 | 6 | public reset(): void { 7 | this.startTime = 0; 8 | this.prevTime = 0; 9 | this.time = 0; 10 | } 11 | 12 | public start(): void { 13 | this.startTime = performance.now(); 14 | this.prevTime = this.startTime; 15 | } 16 | 17 | public getDelta(): number { 18 | const newTime = performance.now(); 19 | const delta = (newTime - this.prevTime) / 1000; 20 | 21 | this.prevTime = newTime; 22 | this.time += delta; 23 | 24 | return delta; 25 | } 26 | } 27 | 28 | export default Clock; 29 | -------------------------------------------------------------------------------- /src/core/Command.ts: -------------------------------------------------------------------------------- 1 | import Pass from "./Pass.js"; 2 | import Pipeline from "./Pipeline.js"; 3 | import Buffer from "./Buffer.js"; 4 | import BindGroup from "./BindGroup.js"; 5 | 6 | import { GPUIndexFormat } from "../constants.js"; 7 | 8 | class Command { 9 | public pass?: Pass; 10 | public pipeline?: Pipeline; 11 | 12 | public vertexBuffers?: Buffer[]; 13 | public indexBuffer?: Buffer; 14 | public indexFormat?: GPUIndexFormat = GPUIndexFormat.Uint32; 15 | public bindGroups?: BindGroup[]; 16 | 17 | public count?: number; 18 | public instances?: number; 19 | 20 | public dispatchWorkgroups?: number | [number, number?, number?]; 21 | 22 | constructor(options: Command) { 23 | Object.assign(this, options); 24 | } 25 | } 26 | 27 | export default Command; 28 | -------------------------------------------------------------------------------- /src/core/Context.ts: -------------------------------------------------------------------------------- 1 | import State from "./State.js"; 2 | import Command from "./Command.js"; 3 | 4 | import { 5 | GPUCanvasCompositingAlphaMode, 6 | GPUTextureUsage, 7 | } from "../constants.js"; 8 | import { ContextOptions } from "../types.js"; 9 | 10 | class Context { 11 | public canvas: HTMLCanvasElement; 12 | public context: GPUCanvasContext; 13 | public pixelRatio: number; 14 | 15 | private adapter: GPUAdapter; 16 | 17 | private commandEncoder: GPUCommandEncoder | null; 18 | private passEncoder: GPURenderPassEncoder | GPUComputePassEncoder | null; 19 | 20 | private defaultDepthStencilAttachment: GPUTextureView | null; 21 | 22 | constructor({ canvas, context, pixelRatio }: ContextOptions = {}) { 23 | this.canvas = canvas || document.createElement("canvas"); 24 | this.context = 25 | context || (this.canvas.getContext("webgpu") as GPUCanvasContext); 26 | this.pixelRatio = pixelRatio || window.devicePixelRatio || 1; 27 | } 28 | 29 | public async init( 30 | requestAdapter = {}, 31 | deviceDescriptor = {}, 32 | presentationContextDescriptor = {}, 33 | { glslangPath = "", twgslPath = "" } = {}, 34 | ): Promise { 35 | try { 36 | if (!this.context) { 37 | throw new Error(`Failed to instantiate "webgpu" context.`); 38 | } 39 | if (!navigator.gpu) { 40 | throw new Error(`Missing "navigator.gpu".`); 41 | } 42 | 43 | this.adapter = await navigator.gpu.requestAdapter(requestAdapter); 44 | State.device = await this.adapter.requestDevice(deviceDescriptor); 45 | State.device.addEventListener("uncapturederror", (error) => { 46 | console.log(error); 47 | State.error = true; 48 | }); 49 | 50 | this.context.configure({ 51 | device: State.device, 52 | format: navigator.gpu.getPreferredCanvasFormat(), 53 | usage: GPUTextureUsage.RENDER_ATTACHMENT, 54 | alphaMode: GPUCanvasCompositingAlphaMode.Premultiplied, 55 | ...presentationContextDescriptor, 56 | }); 57 | 58 | State.glslang = await ( 59 | await import( 60 | /* webpackIgnore: true */ glslangPath || 61 | "@webgpu/glslang/dist/web-devel/glslang.js" 62 | ) 63 | ).default(); 64 | 65 | // https://github.com/BabylonJS/Babylon.js/tree/fe4df00abcd88585dc3249acb01633e67c2e7969/packages/tools/babylonServer/public/twgsl 66 | const twgslUrl = 67 | twgslPath || 68 | new URL("assets/twgsl.js", window.location.href).toString(); 69 | await import(/* webpackIgnore: true */ twgslUrl); 70 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 71 | State.twgsl = await (window as any).twgsl( 72 | twgslUrl.replace(".js", ".wasm"), 73 | ); 74 | } catch (error) { 75 | console.error(error); 76 | return false; 77 | } 78 | 79 | return true; 80 | } 81 | 82 | public resize( 83 | width: number, 84 | height: number, 85 | presentationContextDescriptor = {}, 86 | ): void { 87 | const w = width * this.pixelRatio; 88 | const h = height * this.pixelRatio; 89 | this.canvas.width = w; 90 | this.canvas.height = h; 91 | Object.assign(this.canvas.style, { width, height }); 92 | 93 | this.context.configure({ 94 | device: State.device, 95 | format: navigator.gpu.getPreferredCanvasFormat(), 96 | usage: GPUTextureUsage.RENDER_ATTACHMENT, 97 | alphaMode: GPUCanvasCompositingAlphaMode.Premultiplied, 98 | ...presentationContextDescriptor, 99 | }); 100 | 101 | this.defaultDepthStencilAttachment = State.device 102 | .createTexture({ 103 | size: { width: w, height: h, depthOrArrayLayers: 1 }, 104 | mipLevelCount: 1, 105 | sampleCount: 1, 106 | dimension: "2d", 107 | format: "depth24plus-stencil8", 108 | usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, 109 | }) 110 | .createView(); 111 | } 112 | 113 | public submit(command: Command, subcommand?: () => unknown): void { 114 | if (!this.commandEncoder) { 115 | console.warn("You need to submit commands inside the render callback."); 116 | return; 117 | } 118 | 119 | const currentTexture = this.context.getCurrentTexture(); 120 | 121 | if (command.pass) { 122 | if (command.pass.type === "render") { 123 | const descriptor = { ...command.pass.descriptor }; 124 | 125 | if (descriptor.colorAttachments) { 126 | const currentView = currentTexture.createView(); 127 | const views = 128 | descriptor.colorAttachments as Array; 129 | for (let i = 0; i < views.length; i++) { 130 | views[i].view ||= currentView; 131 | } 132 | } 133 | if (descriptor.depthStencilAttachment) { 134 | ( 135 | descriptor.depthStencilAttachment as GPURenderPassDepthStencilAttachment 136 | ).view ||= this.defaultDepthStencilAttachment; 137 | } 138 | 139 | this.passEncoder = this.commandEncoder.beginRenderPass(descriptor); 140 | } else if (command.pass.type === "compute") { 141 | this.passEncoder = this.commandEncoder.beginComputePass(); 142 | } 143 | } 144 | 145 | if (command.pipeline) { 146 | this.passEncoder.setPipeline( 147 | command.pipeline.gpuPipeline as GPURenderPipeline & GPUComputePipeline, 148 | ); 149 | } 150 | 151 | if (command.vertexBuffers) { 152 | for (let i = 0; i < command.vertexBuffers.length; i++) { 153 | (this.passEncoder as GPURenderPassEncoder).setVertexBuffer( 154 | i, 155 | command.vertexBuffers[i].gpuBuffer, 156 | ); 157 | } 158 | } 159 | 160 | if (command.indexBuffer) { 161 | (this.passEncoder as GPURenderPassEncoder).setIndexBuffer( 162 | command.indexBuffer.gpuBuffer, 163 | command.indexFormat, 164 | ); 165 | } 166 | 167 | if (command.bindGroups) { 168 | for (let i = 0; i < command.bindGroups.length; i++) { 169 | this.passEncoder.setBindGroup(i, command.bindGroups[i].gpuBindGroup); 170 | } 171 | } 172 | 173 | if (command.indexBuffer) { 174 | (this.passEncoder as GPURenderPassEncoder).drawIndexed( 175 | command.count || 0, 176 | command.instances || 1, 177 | 0, 178 | 0, 179 | 0, 180 | ); 181 | } else if (command.count) { 182 | (this.passEncoder as GPURenderPassEncoder).draw( 183 | command.count, 184 | command.instances || 1, 185 | 0, 186 | 0, 187 | ); 188 | } else if (command.dispatchWorkgroups) { 189 | (this.passEncoder as GPUComputePassEncoder).dispatchWorkgroups( 190 | ...((Array.isArray(command.dispatchWorkgroups) 191 | ? command.dispatchWorkgroups 192 | : [command.dispatchWorkgroups]) as [number, number?, number?]), 193 | ); 194 | } 195 | 196 | if (subcommand) subcommand(); 197 | 198 | if (command.pass) { 199 | this.passEncoder.end(); 200 | this.passEncoder = null; 201 | } 202 | } 203 | 204 | public render(cb: () => unknown): void { 205 | this.commandEncoder = State.device.createCommandEncoder(); 206 | // Submit commands 207 | cb(); 208 | State.device.queue.submit([this.commandEncoder.finish()]); 209 | this.commandEncoder = null; 210 | } 211 | } 212 | 213 | export default Context; 214 | -------------------------------------------------------------------------------- /src/core/Pass.ts: -------------------------------------------------------------------------------- 1 | import Attachment from "./Attachment.js"; 2 | import { PassType } from "../types.js"; 3 | 4 | class Pass { 5 | constructor( 6 | public type: PassType, 7 | public colorAttachments?: Attachment[], 8 | public depthAttachment?: Attachment, 9 | public stencilAttachment?: Attachment, 10 | ) {} 11 | 12 | public get descriptor(): GPURenderPassDescriptor | null { 13 | if (this.type === "render") { 14 | return { 15 | ...(this.colorAttachments && { 16 | colorAttachments: this.colorAttachments.map( 17 | (colorAttachment) => 18 | ({ 19 | view: colorAttachment.view, 20 | resolveTarget: colorAttachment.resolveTarget, 21 | clearValue: colorAttachment.value, 22 | loadOp: colorAttachment.op, 23 | storeOp: colorAttachment.storeOp, 24 | }) as GPURenderPassColorAttachment, 25 | ), 26 | }), 27 | ...((this.depthAttachment || this.stencilAttachment) && { 28 | depthStencilAttachment: { 29 | view: this.depthAttachment.view, 30 | depthLoadOp: this.depthAttachment?.op || "clear", 31 | depthClearValue: this.depthAttachment?.value || 0, 32 | depthStoreOp: this.depthAttachment?.storeOp || "store", 33 | stencilLoadOp: this.stencilAttachment?.op || "clear", 34 | stencilClearValue: this.stencilAttachment?.value || 0, 35 | stencilStoreOp: this.stencilAttachment?.storeOp || "store", 36 | } as GPURenderPassDepthStencilAttachment, 37 | }), 38 | }; 39 | } 40 | // else if (this.type === "compute") { 41 | // } 42 | return null; 43 | } 44 | } 45 | 46 | export default Pass; 47 | -------------------------------------------------------------------------------- /src/core/Pipeline.ts: -------------------------------------------------------------------------------- 1 | import State from "./State.js"; 2 | import Attribute from "./Attribute.js"; 3 | import Program from "./Program.js"; 4 | import Shader from "./Shader.js"; 5 | 6 | import { GPUPrimitiveTopology } from "../constants.js"; 7 | import { 8 | Language, 9 | PipelineOptions, 10 | PipelineVertexBufferIns, 11 | } from "../types.js"; 12 | 13 | const mapAttributes = ( 14 | attributes: Attribute[], 15 | locationOffset = 0, 16 | ): GPUVertexBufferLayout[] => { 17 | let prevAttributeSize: number; 18 | 19 | return attributes.reduce((currentAttributes, attribute, index) => { 20 | currentAttributes.push({ 21 | shaderLocation: index + locationOffset, 22 | size: attribute.getSize(), 23 | offset: currentAttributes.length 24 | ? currentAttributes[currentAttributes.length - 1].offset + 25 | prevAttributeSize 26 | : 0, 27 | format: attribute.format, 28 | }); 29 | 30 | prevAttributeSize = attribute.getSize(); 31 | return currentAttributes; 32 | }, []); 33 | }; 34 | 35 | // eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-unsafe-declaration-merging 36 | interface Pipeline extends PipelineOptions {} 37 | 38 | const DEFAULT_GLSL_FRAGMENT_OUT_COLOR: Attribute[] = [ 39 | new Attribute("outColor", "vec4"), 40 | ]; 41 | 42 | // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging 43 | class Pipeline { 44 | public gpuPipeline: GPUPipelineBase; 45 | public program: Program; 46 | public language: Language = "wgsl"; 47 | public fragmentTargets: GPUColorTargetState[] = [ 48 | { 49 | format: "bgra8unorm", 50 | blend: { 51 | color: { 52 | srcFactor: "src-alpha", 53 | dstFactor: "one-minus-src-alpha", 54 | operation: "add", 55 | }, 56 | alpha: { 57 | srcFactor: "src-alpha", 58 | dstFactor: "one-minus-src-alpha", 59 | operation: "add", 60 | }, 61 | }, 62 | }, 63 | ]; 64 | 65 | constructor(options: PipelineOptions) { 66 | Object.assign(this, options); 67 | 68 | this.init(); 69 | } 70 | 71 | public init(): void { 72 | let insAttributes: Attribute[]; 73 | let hasArrayOfAttributes = false; 74 | if (this.ins?.length > 0) { 75 | hasArrayOfAttributes = this.ins.some( 76 | (variable) => variable instanceof Attribute, 77 | ); 78 | insAttributes = hasArrayOfAttributes 79 | ? (this.ins as Attribute[]) 80 | : (this.ins as PipelineVertexBufferIns[]) 81 | .map((vertexBuffer) => vertexBuffer.attributes) 82 | .flat(); 83 | } 84 | 85 | if (this.compute) { 86 | this.program = new Program( 87 | this.bindGroupLayouts, 88 | { 89 | compute: new Shader({ 90 | type: GPUShaderStage.COMPUTE, 91 | main: this.compute, 92 | body: this.computeBody, 93 | ins: insAttributes, 94 | outs: this.outs, 95 | structs: this.structs, 96 | language: this.language, 97 | }), 98 | }, 99 | this.language, 100 | ); 101 | this.program.init(); 102 | 103 | this.gpuPipeline = State.device.createComputePipeline({ 104 | layout: State.device.createPipelineLayout({ 105 | bindGroupLayouts: this.bindGroupLayouts?.map( 106 | (bindGroupLayout) => bindGroupLayout.gpuBindGroupLayout, 107 | ), 108 | }), 109 | compute: { 110 | module: this.program.shaders.compute.shaderModule, 111 | entryPoint: "main", 112 | }, 113 | }); 114 | } else { 115 | const vertexBuffers = hasArrayOfAttributes 116 | ? ([ 117 | { 118 | stepMode: this.stepMode || "vertex", 119 | arrayStride: (this.ins as Attribute[]) 120 | .filter((variable) => variable instanceof Attribute) 121 | .map((attribute) => attribute.getSize()) 122 | .reduce((a, b) => a + b, 0), 123 | attributes: mapAttributes( 124 | (this.ins as Attribute[]).filter( 125 | (variable) => variable instanceof Attribute, 126 | ), 127 | ), 128 | }, 129 | ] as GPUVertexBufferLayout[]) 130 | : // When stepMode needs to be specified 131 | ((this.ins as PipelineVertexBufferIns[]).map( 132 | ({ stepMode, attributes }, index) => ({ 133 | stepMode, 134 | arrayStride: attributes 135 | .map((attribute) => attribute.getSize()) 136 | .reduce((a, b) => a + b, 0), 137 | attributes: mapAttributes( 138 | attributes, 139 | (this.ins as PipelineVertexBufferIns[])[index - 1]?.attributes 140 | .length || 0, 141 | ), 142 | }), 143 | ) as GPUVertexBufferLayout[]); 144 | 145 | this.program = new Program( 146 | this.bindGroupLayouts, 147 | { 148 | vertex: new Shader({ 149 | type: GPUShaderStage.VERTEX, 150 | main: this.vertex, 151 | body: this.vertexBody, 152 | ins: insAttributes, 153 | outs: this.outs, 154 | structs: this.structs, 155 | language: this.language, 156 | }), 157 | fragment: new Shader({ 158 | type: GPUShaderStage.FRAGMENT, 159 | main: this.fragment, 160 | body: this.fragmentBody, 161 | ins: this.outs, 162 | outs: 163 | this.fragmentOuts || 164 | (this.language === "glsl" ? DEFAULT_GLSL_FRAGMENT_OUT_COLOR : []), 165 | structs: this.structs, 166 | language: this.language, 167 | }), 168 | }, 169 | this.language, 170 | ); 171 | this.program.init(); 172 | 173 | this.gpuPipeline = State.device.createRenderPipeline({ 174 | layout: this.bindGroupLayouts 175 | ? State.device.createPipelineLayout({ 176 | bindGroupLayouts: 177 | this.bindGroupLayouts.map( 178 | (bindGroupLayout) => bindGroupLayout.gpuBindGroupLayout, 179 | ) || [], 180 | }) 181 | : "auto", 182 | vertex: { 183 | buffers: vertexBuffers, 184 | module: this.program.shaders.vertex.shaderModule, 185 | entryPoint: "main", 186 | }, 187 | fragment: { 188 | module: this.program.shaders.fragment.shaderModule, 189 | entryPoint: "main", 190 | targets: this.fragmentTargets, 191 | }, 192 | primitive: { topology: GPUPrimitiveTopology.TriangleList }, 193 | depthStencil: { 194 | depthWriteEnabled: true, 195 | depthCompare: "less", 196 | format: "depth24plus-stencil8", 197 | }, 198 | ...(this.descriptor || {}), 199 | }); 200 | } 201 | } 202 | } 203 | 204 | export default Pipeline; 205 | -------------------------------------------------------------------------------- /src/core/Program.ts: -------------------------------------------------------------------------------- 1 | import Shader from "./Shader"; 2 | import BindGroupLayout from "./BindGroupLayout.js"; 3 | import Uniform from "./Uniform.js"; 4 | import Struct from "./Struct.js"; 5 | 6 | import { formatLowerFirst, formatUpperFirst } from "../utils.js"; 7 | import { 8 | AccessMode, 9 | BindingType, 10 | StorageClass, 11 | GPUShaderStage, 12 | GPUTextureSampleType, 13 | } from "../constants.js"; 14 | import { 15 | ShaderStageName, 16 | BindGroupLayoutEntry, 17 | Language, 18 | GLSLStorageQualifier, 19 | } from "../types.js"; 20 | 21 | const isBindingVisible = ( 22 | uniformOrBinding: Uniform | BindGroupLayoutEntry, 23 | stage: GPUShaderStageFlags, 24 | ): boolean => 25 | !uniformOrBinding.visibility || uniformOrBinding.visibility === stage; 26 | 27 | class Program { 28 | constructor( 29 | public bindGroupLayouts: BindGroupLayout[], 30 | public shaders: { [key in ShaderStageName]?: Shader }, 31 | public language: Language, 32 | ) {} 33 | 34 | public init(): void { 35 | const headers = this.bindGroupLayouts?.map((bindGroupLayout, index) => 36 | this[`get${this.language.toUpperCase()}Headers`]( 37 | index, 38 | bindGroupLayout.entries, 39 | ), 40 | ); 41 | 42 | for (const [key, shader] of Object.entries(this.shaders)) { 43 | shader.init( 44 | headers?.map((header) => header[key as ShaderStageName]).join("\n"), 45 | ); 46 | } 47 | } 48 | 49 | public getWGSLBufferString( 50 | binding: BindGroupLayoutEntry, 51 | bindingIndex: number, 52 | uniforms: Uniform[], 53 | set: number, 54 | storageClass: StorageClass, 55 | accesMode?: AccessMode, 56 | ): string { 57 | const structName = `${binding.name}${formatUpperFirst(storageClass)}`; 58 | const params = new Struct(structName, uniforms).getWGSLString(); 59 | const referenceType = [storageClass, accesMode].filter(Boolean).join(","); 60 | return /* wgsl */ `${params} 61 | @group(${set}) @binding(${bindingIndex}) var<${referenceType}> ${formatLowerFirst( 62 | binding.name, 63 | )}: ${structName}; 64 | `; 65 | } 66 | 67 | public getWGSLHeaders( 68 | set: number, 69 | entries: BindGroupLayoutEntry[], 70 | ): { [key in ShaderStageName]?: string } { 71 | let vertex = ""; 72 | let fragment = ""; 73 | let compute = ""; 74 | 75 | for (let i = 0; i < entries.length; i++) { 76 | const binding = entries[i]; 77 | 78 | if (binding.buffer) { 79 | if ( 80 | !binding.buffer.type || 81 | binding.buffer.type === BindingType.Uniform 82 | ) { 83 | let vertexUniforms; 84 | let fragmentUniforms; 85 | if ( 86 | isBindingVisible( 87 | binding, 88 | GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, 89 | ) 90 | ) { 91 | vertexUniforms = binding.uniforms; 92 | fragmentUniforms = binding.uniforms; 93 | } else { 94 | vertexUniforms = binding.uniforms.filter(() => 95 | isBindingVisible(binding, GPUShaderStage.VERTEX), 96 | ); 97 | fragmentUniforms = binding.uniforms.filter(() => 98 | isBindingVisible(binding, GPUShaderStage.FRAGMENT), 99 | ); 100 | } 101 | 102 | const computeUniforms = binding.uniforms.filter(() => 103 | isBindingVisible(binding, GPUShaderStage.COMPUTE), 104 | ); 105 | 106 | if (vertexUniforms.length) { 107 | vertex += this.getWGSLBufferString( 108 | binding, 109 | i, 110 | vertexUniforms, 111 | set, 112 | StorageClass.Uniform, 113 | ); 114 | } 115 | 116 | if (fragmentUniforms.length) { 117 | fragment += this.getWGSLBufferString( 118 | binding, 119 | i, 120 | fragmentUniforms, 121 | set, 122 | StorageClass.Uniform, 123 | ); 124 | } 125 | 126 | if (computeUniforms.length) { 127 | compute += this.getWGSLBufferString( 128 | binding, 129 | i, 130 | computeUniforms, 131 | set, 132 | StorageClass.Uniform, 133 | ); 134 | } 135 | } else if ( 136 | ( 137 | [ 138 | BindingType.Storage, 139 | BindingType.ReadonlyStorage, 140 | ] as GPUBufferBindingType[] 141 | ).includes(binding.buffer.type) 142 | ) { 143 | const computeVariables = binding.members.filter(() => 144 | isBindingVisible(binding, GPUShaderStage.COMPUTE), 145 | ) as Uniform[]; 146 | 147 | compute += this.getWGSLBufferString( 148 | binding, 149 | i, 150 | computeVariables, 151 | set, 152 | StorageClass.Storage, 153 | binding.buffer.type === BindingType.ReadonlyStorage 154 | ? AccessMode.Read 155 | : AccessMode.Write, 156 | ); 157 | } 158 | } else if (binding.sampler) { 159 | const samplerLayout = /* wgsl*/ `@group(${set}) @binding(${i}) var ${binding.name}: sampler;`; 160 | 161 | if (isBindingVisible(binding, GPUShaderStage.VERTEX)) { 162 | vertex += `${samplerLayout}\n`; 163 | } 164 | if (isBindingVisible(binding, GPUShaderStage.FRAGMENT)) { 165 | fragment += `${samplerLayout}\n`; 166 | } 167 | } else if (binding.texture) { 168 | const referenceType = 169 | GPUTextureSampleType[binding.texture.sampleType] || "f32"; 170 | const textureLayout = /* wgsl*/ `@group(${set}) @binding(${i}) var ${binding.name}: texture_${binding.dimension}<${referenceType}>;`; 171 | 172 | if (isBindingVisible(binding, GPUShaderStage.VERTEX)) { 173 | vertex += `${textureLayout}\n`; 174 | } 175 | if (isBindingVisible(binding, GPUShaderStage.FRAGMENT)) { 176 | fragment += `${textureLayout}\n`; 177 | } 178 | } else if (binding.storageTexture) { 179 | const referenceType = [ 180 | binding.storageTexture.format, 181 | binding.storageTexture.access, 182 | ] 183 | .filter(Boolean) 184 | .join(","); 185 | const storageTextureLayout = /* wgsl*/ `@group(${set}) @binding(${i}) var ${binding.name}: texture_storage_${binding.dimension}<${referenceType}>;`; 186 | 187 | if (isBindingVisible(binding, GPUShaderStage.VERTEX)) { 188 | vertex += `${storageTextureLayout}\n`; 189 | } 190 | if (isBindingVisible(binding, GPUShaderStage.FRAGMENT)) { 191 | fragment += `${storageTextureLayout}\n`; 192 | } 193 | } 194 | } 195 | 196 | return { vertex, fragment, compute }; 197 | } 198 | 199 | public getGLSLBufferString( 200 | binding: BindGroupLayoutEntry, 201 | bindingIndex: number, 202 | uniforms: Uniform[], 203 | set: number, 204 | bindingType: GLSLStorageQualifier, 205 | layoutQualifierString: string, 206 | ): string { 207 | const structName = `${binding.name}${formatUpperFirst(bindingType)}`; 208 | 209 | return `layout(${layoutQualifierString}set = ${set}, binding = ${bindingIndex}) ${bindingType} ${structName} { 210 | ${uniforms 211 | .map( 212 | (uniform) => 213 | `${uniform.glslType} ${formatLowerFirst(uniform.name)}${ 214 | uniform.arrayCount ? `[${uniform.arrayCount}]` : "" 215 | };`, 216 | ) 217 | .join("\n ")} 218 | } ${formatLowerFirst(binding.name)};\n\n`; 219 | } 220 | 221 | public getGLSLHeaders( 222 | set: number, 223 | entries: BindGroupLayoutEntry[], 224 | ): { [key in ShaderStageName]?: string } { 225 | let vertex = ""; 226 | let fragment = ""; 227 | let compute = ""; 228 | 229 | for (let i = 0; i < entries.length; i++) { 230 | const binding = entries[i]; 231 | 232 | const layoutQualifierString = binding.qualifiers?.layout 233 | ? `${binding.qualifiers.layout || ""}, ` 234 | : ""; 235 | 236 | // GPUBufferBinding 237 | if (binding.buffer) { 238 | // uniform-buffer 239 | if ( 240 | !binding.buffer.type || 241 | binding.buffer.type === StorageClass.Uniform 242 | ) { 243 | let vertexUniforms; 244 | let fragmentUniforms; 245 | if ( 246 | isBindingVisible( 247 | binding, 248 | GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, 249 | ) 250 | ) { 251 | vertexUniforms = binding.uniforms; 252 | fragmentUniforms = binding.uniforms; 253 | } else { 254 | vertexUniforms = binding.uniforms.filter(() => 255 | isBindingVisible(binding, GPUShaderStage.VERTEX), 256 | ); 257 | fragmentUniforms = binding.uniforms.filter(() => 258 | isBindingVisible(binding, GPUShaderStage.FRAGMENT), 259 | ); 260 | } 261 | 262 | const computeUniforms = binding.uniforms.filter(() => 263 | isBindingVisible(binding, GPUShaderStage.COMPUTE), 264 | ); 265 | 266 | // Vertex 267 | if (vertexUniforms.length) { 268 | vertex += this.getGLSLBufferString( 269 | binding, 270 | i, 271 | vertexUniforms, 272 | set, 273 | "uniform", 274 | layoutQualifierString, 275 | ); 276 | } 277 | 278 | // Fragment 279 | if (fragmentUniforms.length) { 280 | fragment += this.getGLSLBufferString( 281 | binding, 282 | i, 283 | fragmentUniforms, 284 | set, 285 | "uniform", 286 | layoutQualifierString, 287 | ); 288 | } 289 | 290 | if (computeUniforms.length) { 291 | compute += this.getGLSLBufferString( 292 | binding, 293 | i, 294 | computeUniforms, 295 | set, 296 | "uniform", 297 | layoutQualifierString, 298 | ); 299 | } 300 | } else if (binding.buffer.type === StorageClass.Storage) { 301 | const computeVariables = binding.members.filter(() => 302 | isBindingVisible(binding, GPUShaderStage.COMPUTE), 303 | ) as Uniform[]; 304 | 305 | compute += this.getGLSLBufferString( 306 | binding, 307 | i, 308 | computeVariables, 309 | set, 310 | "buffer", 311 | layoutQualifierString, 312 | ); 313 | } 314 | } else if (binding.sampler) { 315 | const samplerLayout = `layout(set = ${set}, binding = ${i}) uniform sampler${ 316 | binding.samplerType || "" 317 | } ${binding.name};`; 318 | 319 | if (isBindingVisible(binding, GPUShaderStage.VERTEX)) { 320 | vertex += `${samplerLayout}\n`; 321 | } 322 | if (isBindingVisible(binding, GPUShaderStage.FRAGMENT)) { 323 | fragment += `${samplerLayout}\n`; 324 | } 325 | } else if (binding.texture) { 326 | const textureLayout = `layout(set = ${set}, binding = ${i}) uniform texture${binding.dimension.toUpperCase()} ${ 327 | binding.name 328 | };`; 329 | 330 | if (isBindingVisible(binding, GPUShaderStage.VERTEX)) { 331 | vertex += `${textureLayout}\n`; 332 | } 333 | if (isBindingVisible(binding, GPUShaderStage.FRAGMENT)) { 334 | fragment += `${textureLayout}\n`; 335 | } 336 | } else if (binding.storageTexture) { 337 | // TODO: 338 | } 339 | } 340 | 341 | return { vertex, fragment, compute }; 342 | } 343 | } 344 | 345 | export default Program; 346 | -------------------------------------------------------------------------------- /src/core/Sampler.ts: -------------------------------------------------------------------------------- 1 | import State from "./State.js"; 2 | 3 | class Sampler { 4 | public gpuSampler: GPUSampler; 5 | 6 | constructor( 7 | descriptor: GPUSamplerDescriptor = { 8 | magFilter: "linear", 9 | minFilter: "linear", 10 | mipmapFilter: "linear", 11 | }, 12 | ) { 13 | this.gpuSampler = State.device.createSampler(descriptor); 14 | } 15 | } 16 | 17 | export default Sampler; 18 | -------------------------------------------------------------------------------- /src/core/Shader.ts: -------------------------------------------------------------------------------- 1 | import State from "./State.js"; 2 | 3 | import Uniform from "./Uniform.js"; 4 | import Attribute from "./Attribute.js"; 5 | import BuiltIn from "./BuiltIn.js"; 6 | import Struct from "./Struct.js"; 7 | 8 | import { addLineNumbers, TAB } from "../utils.js"; 9 | import { GPUShaderStageName } from "../constants.js"; 10 | import { ShaderOptions } from "../types.js"; 11 | 12 | // eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-unsafe-declaration-merging 13 | interface Shader extends ShaderOptions {} 14 | 15 | // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging 16 | class Shader { 17 | public shaderModule: GPUShaderModule; 18 | 19 | private source: string; 20 | 21 | constructor(options: ShaderOptions) { 22 | Object.assign(this, options); 23 | } 24 | 25 | public init(uniformHeaders: string): void { 26 | const isWGSL = this.language === "wgsl"; 27 | const { structs, ins, outs } = isWGSL 28 | ? this.getWGSLHeaders() 29 | : this.getGLSLHeaders(); 30 | 31 | const stage = GPUShaderStageName[this.type]; 32 | 33 | this.source = isWGSL 34 | ? /* wgsl */ `${[structs, uniformHeaders, outs] 35 | .filter(Boolean) 36 | .join("\n")} 37 | ${this.body || ""} 38 | @${stage} 39 | fn main( 40 | ${TAB}${ins} 41 | ) -> ${outs ? "Output" : "@location(0) vec4"} {${this.main}}` 42 | : /* glsl */ `#version 450 43 | ${[structs, uniformHeaders, ins, outs].filter(Boolean).join("\n")} 44 | ${this.body || ""} 45 | void main() {${this.main}}`; 46 | 47 | if (State.debug) console.log(addLineNumbers(this.source)); 48 | 49 | this.shaderModule = State.device.createShaderModule({ 50 | code: isWGSL 51 | ? this.source 52 | : State.twgsl.convertSpirV2WGSL( 53 | State.glslang.compileGLSL(this.source, stage), 54 | ), 55 | }); 56 | } 57 | 58 | public getWGSLHeaders(): { structs?: string; ins?: string; outs?: string } { 59 | const structs = 60 | this.structs 61 | ?.filter((struct) => 62 | struct.visibility !== undefined 63 | ? struct.visibility === this.type 64 | : true, 65 | ) 66 | .map((struct) => struct.getWGSLString()) 67 | .join("\n") || ""; 68 | 69 | const ins = ( 70 | this.ins 71 | ? [ 72 | ...this.ins 73 | .filter((member) => member instanceof BuiltIn) 74 | .filter((member) => member.visibility === this.type) 75 | .map((variable) => (variable as BuiltIn).getWGSLString()), 76 | ...this.ins 77 | .filter((member) => member instanceof Attribute) 78 | .map((variable, index) => 79 | (variable as Attribute).getWGSLString(index), 80 | ), 81 | ...this.ins 82 | .filter((member) => member instanceof Uniform) 83 | .map((variable) => (variable as Uniform).getWGSLString()), 84 | ] 85 | : [] 86 | ).join(`,\n${TAB}`); 87 | 88 | const outs = this.outs?.filter((member) => { 89 | if ((member as BuiltIn).io) { 90 | return ( 91 | member.visibility === this.type && (member as BuiltIn).io === "out" 92 | ); 93 | } 94 | return true; 95 | }); 96 | 97 | return { 98 | structs, 99 | ins, 100 | outs: outs?.length ? new Struct("Output", outs).getWGSLString() : "", 101 | }; 102 | } 103 | 104 | public getGLSLHeaders(): { structs?: string; ins?: string; outs?: string } { 105 | const structs = 106 | this.structs 107 | ?.filter((struct) => 108 | struct.visibility !== undefined 109 | ? struct.visibility === this.type 110 | : true, 111 | ) 112 | .map((struct) => struct.getGLSLString()) 113 | .join("\n") || ""; 114 | const ins = 115 | this.ins 116 | ?.filter((member) => !(member instanceof BuiltIn)) 117 | .map( 118 | (attribute, index) => 119 | `layout(location = ${index}) in ${attribute.type} ${attribute.name};`, 120 | ) 121 | .join("\n") || ""; 122 | const outs = 123 | this.outs 124 | ?.filter((member) => !(member instanceof BuiltIn)) 125 | .map( 126 | (attribute, index) => 127 | `layout(location = ${index}) out ${attribute.type} ${attribute.name};`, 128 | ) 129 | .join("\n") || ""; 130 | 131 | return { 132 | structs, 133 | ins, 134 | outs, 135 | }; 136 | } 137 | } 138 | 139 | export default Shader; 140 | -------------------------------------------------------------------------------- /src/core/State.ts: -------------------------------------------------------------------------------- 1 | import { ContextState } from "../types.js"; 2 | 3 | const State: ContextState = { 4 | device: null, 5 | glslang: null, 6 | twgsl: null, 7 | debug: false, 8 | error: false, 9 | }; 10 | 11 | export default State; 12 | -------------------------------------------------------------------------------- /src/core/Struct.ts: -------------------------------------------------------------------------------- 1 | import Variable from "./Variable.js"; 2 | import Uniform from "./Uniform.js"; 3 | import Attribute from "./Attribute.js"; 4 | import BuiltIn from "./BuiltIn.js"; 5 | 6 | import { formatLowerFirst, TAB } from "../utils.js"; 7 | 8 | // https://www.w3.org/TR/WGSL/#struct-types 9 | // A structure member type must be one of: 10 | // - a scalar type 11 | // - a vector type 12 | // - a matrix type 13 | // - an atomic type 14 | // - an array type 15 | // - a structure type 16 | class Struct extends Variable { 17 | public memberName?: string; 18 | 19 | constructor( 20 | public name: string, 21 | public members: (Variable | Struct)[], 22 | public visibility?: GPUShaderStageFlags, 23 | public arrayCount?: number, 24 | ) { 25 | super(name); 26 | } 27 | 28 | public get wgslType(): string { 29 | return this.name; 30 | } 31 | 32 | public get glslType(): string { 33 | return this.name; 34 | } 35 | 36 | public get format(): string { 37 | return "float32"; 38 | } 39 | 40 | public getSize(): number { 41 | return this.members 42 | .map((member) => member.getSize() * (member.arrayCount || 1)) 43 | .reduce((a, b) => a + b, 0); 44 | } 45 | 46 | // TODO: 47 | // stride 48 | // align 49 | // size 50 | public getWGSLMemberString(): string { 51 | return `${this.wgslType} ${this.memberName || formatLowerFirst(this.name)}${ 52 | this.arrayCount ? `[${this.arrayCount}]` : "" 53 | }`; 54 | } 55 | public getGLSLMemberString(): string { 56 | return `${this.glslType} ${this.memberName || formatLowerFirst(this.name)}${ 57 | this.arrayCount ? `[${this.arrayCount}]` : "" 58 | }`; 59 | } 60 | 61 | public getWGSLString(): string { 62 | if (!this.members.length) { 63 | console.error("Struct missing members.", this); 64 | return ""; 65 | } 66 | 67 | const builtIns = this.members 68 | .filter((member) => member instanceof BuiltIn) 69 | .map((variable) => (variable as BuiltIn).getWGSLString() + ","); 70 | const attributes = this.members 71 | .filter((member) => member instanceof Attribute) 72 | .map( 73 | (variable, index) => (variable as Attribute).getWGSLString(index) + ",", 74 | ); 75 | const uniforms = this.members 76 | .filter((member) => member instanceof Uniform) 77 | .map((variable) => (variable as Uniform).getWGSLString() + ","); 78 | const structs = this.members 79 | .filter((member) => member instanceof Struct) 80 | .map((variable) => (variable as Struct).getWGSLMemberString() + ","); 81 | const variables = this.members 82 | .filter((member) => member.constructor.name === "Variable") 83 | .map((variable) => (variable as BuiltIn).getWGSLString() + ","); 84 | 85 | return /* wgsl */ `struct ${this.name} { 86 | ${TAB}${[...builtIns, ...attributes, ...uniforms, ...structs, ...variables] 87 | .filter(Boolean) 88 | .join(`\n${TAB}`)} 89 | }; 90 | `; 91 | } 92 | 93 | public getGLSLString(): string { 94 | const attributes = this.members 95 | .filter((member) => member instanceof Attribute) 96 | .map((variable) => (variable as Attribute).getGLSLString() + ";"); 97 | const uniforms = this.members 98 | .filter((member) => member instanceof Uniform) 99 | .map((variable) => (variable as Uniform).getGLSLString() + ";"); 100 | const structs = this.members 101 | .filter((member) => member instanceof Struct) 102 | .map((variable) => (variable as Struct).getWGSLMemberString() + ";"); 103 | const variables = this.members 104 | .filter((member) => member.constructor.name === "Variable") 105 | .map((variable) => (variable as BuiltIn).getGLSLString() + ";"); 106 | 107 | return /* glsl */ `struct ${this.name} { 108 | ${TAB}${[...attributes, ...uniforms, ...structs, ...variables] 109 | .filter(Boolean) 110 | .join(`\n${TAB}`)} 111 | }; 112 | `; 113 | } 114 | } 115 | 116 | export default Struct; 117 | -------------------------------------------------------------------------------- /src/core/Texture.ts: -------------------------------------------------------------------------------- 1 | import State from "./State.js"; 2 | import Buffer from "./Buffer.js"; 3 | import { GPUTextureUsage, GPUBufferUsage } from "../constants.js"; 4 | 5 | const imageCanvas = document.createElement("canvas"); 6 | const imageCanvasContext = imageCanvas.getContext("2d", { 7 | willReadFrequently: true, 8 | }) as CanvasRenderingContext2D; 9 | // document.body.appendChild(imageCanvas) 10 | 11 | class Texture { 12 | public gpuTexture: GPUTexture; 13 | public mipLevelCount: number; 14 | 15 | constructor( 16 | public descriptor: GPUTextureDescriptor, 17 | public image: HTMLImageElement, 18 | ) { 19 | this.mipLevelCount = image 20 | ? Math.floor(Math.log2(Math.max(image.width, image.height))) + 1 21 | : 1; 22 | 23 | this.gpuTexture = State.device.createTexture({ 24 | dimension: "2d", 25 | format: "rgba8unorm", 26 | mipLevelCount: this.mipLevelCount, 27 | sampleCount: 1, 28 | size: { 29 | width: image?.width || (descriptor.size as GPUExtent3DDict)?.width, 30 | height: image?.height || (descriptor.size as GPUExtent3DDict)?.height, 31 | }, 32 | usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.SAMPLED, 33 | ...(descriptor || {}), 34 | }); 35 | 36 | if (image) { 37 | this.setMipMap(image); 38 | } 39 | } 40 | 41 | public setMipMap(image: HTMLImageElement): void { 42 | let faceWidth = image.width; 43 | let faceHeight = image.height; 44 | 45 | this.update(faceHeight, faceHeight, 0); 46 | 47 | for (let i = 1; i <= this.mipLevelCount - 1; i++) { 48 | faceWidth = Math.max(Math.floor(faceWidth / 2), 1); 49 | faceHeight = Math.max(Math.floor(faceHeight / 2), 1); 50 | this.update(faceHeight, faceHeight, i); 51 | } 52 | } 53 | 54 | public update( 55 | width: number, 56 | height: number, 57 | mipLevel: number, 58 | face = -1, 59 | ): void { 60 | imageCanvas.width = width; 61 | imageCanvas.height = height; 62 | 63 | imageCanvasContext.translate(0, height); 64 | imageCanvasContext.scale(1, -1); 65 | imageCanvasContext.drawImage(this.image, 0, 0, width, height); 66 | 67 | const imageData = imageCanvasContext.getImageData(0, 0, width, height); 68 | 69 | let data = null; 70 | const bytesPerRow = Math.ceil((width * 4) / 256) * 256; 71 | if (bytesPerRow === width * 4) { 72 | data = imageData.data; 73 | } else { 74 | data = new Uint8Array(bytesPerRow * height); 75 | let pixelsIndex = 0; 76 | for (let y = 0; y < height; ++y) { 77 | for (let x = 0; x < width; ++x) { 78 | const i = x * 4 + y * bytesPerRow; 79 | data[i] = imageData.data[pixelsIndex]; 80 | data[i + 1] = imageData.data[pixelsIndex + 1]; 81 | data[i + 2] = imageData.data[pixelsIndex + 2]; 82 | data[i + 3] = imageData.data[pixelsIndex + 3]; 83 | pixelsIndex += 4; 84 | } 85 | } 86 | } 87 | 88 | const textureDataBuffer = new Buffer(); 89 | textureDataBuffer.create( 90 | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC, 91 | data, 92 | ); 93 | textureDataBuffer.copyToTexture( 94 | bytesPerRow, 95 | height, 96 | { 97 | texture: this.gpuTexture, 98 | mipLevel, 99 | origin: { z: Math.max(face, 0) }, 100 | }, 101 | { 102 | width, 103 | height, 104 | }, 105 | ); 106 | textureDataBuffer.destroy(); 107 | } 108 | } 109 | 110 | export default Texture; 111 | -------------------------------------------------------------------------------- /src/core/Uniform.ts: -------------------------------------------------------------------------------- 1 | import Variable from "./Variable.js"; 2 | 3 | class Uniform extends Variable { 4 | constructor( 5 | public name: string, 6 | public type: string, 7 | public visibility?: GPUShaderStageFlags, 8 | public arrayCount?: number, 9 | ) { 10 | super(name, type); 11 | } 12 | } 13 | 14 | export default Uniform; 15 | -------------------------------------------------------------------------------- /src/core/Variable.ts: -------------------------------------------------------------------------------- 1 | import { formatLowerFirst } from "../utils.js"; 2 | import { 3 | GLSL_SCALARS_TO_WGSL, 4 | WGSL_SCALARS_TO_GLSL_PREFIX, 5 | WGSL_MATRIX_TO_GLSL, 6 | WGSL_TYPE_TO_GPU_VERTEX_FORMAT, 7 | } from "../constants.js"; 8 | import { GLSLLayoutQualifier } from "../types.js"; 9 | 10 | // TODO: parameter 11 | const dataType = "32"; 12 | 13 | class Variable { 14 | static isWgsl(type: string): boolean { 15 | return ( 16 | type.includes("<") || 17 | Object.keys(WGSL_SCALARS_TO_GLSL_PREFIX).includes(type) 18 | ); 19 | } 20 | 21 | constructor( 22 | public name: string, 23 | public type?: string, 24 | public visibility?: GPUShaderStageFlags, 25 | public arrayCount?: number, 26 | ) {} 27 | 28 | // Type conversion 29 | // https://www.w3.org/TR/WGSL/#alignment-and-size 30 | public get wgslType(): string { 31 | if (Variable.isWgsl(this.type)) { 32 | return this.type; 33 | } 34 | 35 | // Scalars 36 | if (Object.keys(GLSL_SCALARS_TO_WGSL).includes(this.type)) { 37 | return GLSL_SCALARS_TO_WGSL[this.type]; 38 | } 39 | // Vectors 40 | else if (this.type.startsWith("vec")) { 41 | return `${this.type}`; 42 | } else if (this.type.substring(1, 4).startsWith("dvec")) { 43 | return `${this.type}`; 44 | // return `${this.type}`; 45 | } else if (this.type.startsWith("ivec")) { 46 | return `${this.type.substring(1, 4)}`; 47 | } else if (this.type.startsWith("uvec")) { 48 | return `${this.type.substring(1, 4)}`; 49 | } else if (this.type.startsWith("bvec")) { 50 | return `${this.type.substring(1, 4)}`; 51 | } 52 | // Matrices 53 | else if (this.type.startsWith("mat")) { 54 | const [col, row] = this.type.substring(3).split("x").map(parseInt); 55 | return `${row ? this.type : `${this.type}x${col}`}`; 56 | } else if (this.type.startsWith("dmat")) { 57 | const [col, row] = this.type.substring(3).split("x").map(parseInt); 58 | return `${row ? this.type : `${this.type}x${col}`}`; 59 | } 60 | 61 | console.error(`Unsupported variable type ${this.type}`); 62 | 63 | return ""; 64 | } 65 | 66 | public get glslType(): string { 67 | if (Variable.isWgsl(this.type)) { 68 | // Scalars 69 | if (Object.values(GLSL_SCALARS_TO_WGSL).includes(this.type)) { 70 | return Object.keys(GLSL_SCALARS_TO_WGSL).find( 71 | (key) => GLSL_SCALARS_TO_WGSL[key] === this.type, 72 | ); 73 | } 74 | // Vectors 75 | else if (this.type.startsWith("vec")) { 76 | const prefix = 77 | WGSL_SCALARS_TO_GLSL_PREFIX[ 78 | this.type.substring( 79 | this.type.indexOf("<") + 1, 80 | this.type.lastIndexOf(">"), 81 | ) 82 | ]; 83 | 84 | return `${prefix}${this.type.substring(0, 4)}`; 85 | } 86 | // Matrices 87 | else if (this.type.startsWith("mat")) { 88 | return WGSL_MATRIX_TO_GLSL[this.type.substring(0, 6)]; 89 | } else { 90 | console.error(`Unsupported variable type ${this.type}`); 91 | } 92 | } 93 | 94 | return this.type; 95 | } 96 | 97 | // WebGPU vertex format 98 | // https://www.w3.org/TR/webgpu/#vertex-formats 99 | public get format(): string { 100 | if (Variable.isWgsl(this.type)) { 101 | return WGSL_TYPE_TO_GPU_VERTEX_FORMAT[this.type]; 102 | } 103 | 104 | // Scalars 105 | if (this.type === "float") { 106 | return `float${dataType}`; 107 | } else if (this.type === "double") { 108 | return "double"; 109 | } else if (this.type === "int") { 110 | return `sint${dataType}`; 111 | } else if (this.type === "uint") { 112 | return `uint${dataType}`; 113 | } else if (this.type === "bool") { 114 | return ""; // TODO: ?? 115 | } 116 | // Vectors 117 | else if (this.type.startsWith("vec")) { 118 | return `float${dataType}x${this.type.substring(3)}`; 119 | } else if (this.type.startsWith("dvec")) { 120 | return `double${this.type.substring(4)}`; 121 | } else if (this.type.startsWith("ivec")) { 122 | return `sint${dataType}x${this.type.substring(4)}`; 123 | } else if (this.type.startsWith("uvec")) { 124 | return `uint${dataType}x${this.type.substring(4)}`; 125 | } else if (this.type.startsWith("bvec")) { 126 | return ""; // TODO: ?? 127 | } 128 | // Matrices 129 | else if (this.type.startsWith("mat")) { 130 | return `float${dataType}`; 131 | } else if (this.type.startsWith("dmat")) { 132 | return "double"; 133 | } 134 | 135 | console.error(`Unsupported variable type ${this.type}`); 136 | 137 | return ""; 138 | } 139 | 140 | // https://www.khronos.org/registry/OpenGL/specs/gl/glspec45.core.pdf#page=159 141 | public static getAlignement( 142 | size: number, 143 | qualifier?: GLSLLayoutQualifier, 144 | ): number { 145 | // Shader storage blocks only 146 | if (!qualifier || qualifier === "std430") { 147 | return size; 148 | } 149 | // Standard Uniform Block Layout and Shader storage blocks 150 | else if (qualifier === "std140") { 151 | switch (size) { 152 | case 0: 153 | return 1; 154 | case 1: 155 | return 1; 156 | case 2: 157 | return 2; 158 | case 3: 159 | return 4; 160 | case 4: 161 | return 4; 162 | default: 163 | console.error(`Unsupported variable size ${size}`); 164 | } 165 | } 166 | } 167 | 168 | public getSize(qualifier?: GLSLLayoutQualifier): number { 169 | // Scalars 170 | if (this.type === "float" || this.type === "f32") { 171 | return Float32Array.BYTES_PER_ELEMENT; 172 | } else if (this.type === "double" || this.type === "f64") { 173 | return Float64Array.BYTES_PER_ELEMENT; 174 | } else if (this.type === "int" || this.type === "i32") { 175 | return Int16Array.BYTES_PER_ELEMENT; 176 | } else if (this.type === "uint") { 177 | return Uint16Array.BYTES_PER_ELEMENT; 178 | } else if (this.type === "u32") { 179 | return Uint32Array.BYTES_PER_ELEMENT; 180 | } else if (this.type === "bool") { 181 | return 1; 182 | } 183 | // Vectors 184 | else if (this.type.startsWith("vec")) { 185 | return ( 186 | Float32Array.BYTES_PER_ELEMENT * 187 | Variable.getAlignement(parseInt(this.type.substring(3)), qualifier) 188 | ); 189 | } else if (this.type.startsWith("dvec")) { 190 | return ( 191 | Float64Array.BYTES_PER_ELEMENT * 192 | Variable.getAlignement(parseInt(this.type.substring(4)), qualifier) 193 | ); 194 | } else if (this.type.startsWith("ivec")) { 195 | return ( 196 | Int16Array.BYTES_PER_ELEMENT * 197 | Variable.getAlignement(parseInt(this.type.substring(4)), qualifier) 198 | ); 199 | } else if (this.type.startsWith("uvec")) { 200 | return ( 201 | Uint16Array.BYTES_PER_ELEMENT * 202 | Variable.getAlignement(parseInt(this.type.substring(4)), qualifier) 203 | ); 204 | } else if (this.type.startsWith("bvec")) { 205 | return ( 206 | 1 * Variable.getAlignement(parseInt(this.type.substring(4)), qualifier) 207 | ); 208 | } 209 | // Matrices 210 | else if (this.type.startsWith("mat")) { 211 | const [col, row] = this.type.substring(3).split("x").map(parseInt); 212 | return ( 213 | Float32Array.BYTES_PER_ELEMENT * 214 | Variable.getAlignement(col, qualifier) * 215 | Variable.getAlignement(row || col, qualifier) 216 | ); 217 | } else if (this.type.startsWith("dmat")) { 218 | const [col, row] = this.type.substring(4).split("x").map(parseInt); 219 | return ( 220 | Float64Array.BYTES_PER_ELEMENT * 221 | Variable.getAlignement(col, qualifier) * 222 | Variable.getAlignement(row || col, qualifier) 223 | ); 224 | } 225 | 226 | console.error(`Unsupported variable type ${this.type}`); 227 | 228 | return -1; 229 | } 230 | 231 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 232 | public getWGSLString(_index?: number): string { 233 | return /* wgsl */ `${this.name}: ${this.arrayCount ? `array<` : ""}${ 234 | this.wgslType 235 | }${this.arrayCount ? `, ${this.arrayCount}>` : ""}`; 236 | } 237 | 238 | public getGLSLString(): string { 239 | return /* glsl */ `${this.glslType} ${formatLowerFirst(this.name)}${ 240 | this.arrayCount ? `[${this.arrayCount}]` : "" 241 | }`; 242 | } 243 | } 244 | 245 | export default Variable; 246 | -------------------------------------------------------------------------------- /src/helpers/Axes.ts: -------------------------------------------------------------------------------- 1 | import Command from "../core/Command.js"; 2 | import Pipeline from "../core/Pipeline.js"; 3 | import Buffer from "../core/Buffer.js"; 4 | import Attribute from "../core/Attribute.js"; 5 | import BindGroupLayout from "../core/BindGroupLayout.js"; 6 | import BindGroup from "../core/BindGroup.js"; 7 | 8 | import { GPUIndexFormat, GPUPrimitiveTopology } from "../constants.js"; 9 | import { PipelineOptions } from "../types.js"; 10 | 11 | // prettier-ignore 12 | const data = new Float32Array([ 13 | /* position */ 0, 0, 0, /* color */ 1, 0, 0, 1, 14 | /* position */ 1, 0, 0, /* color */ 1, 0.5, 0.5, 1, 15 | /* position */ 0, 0, 0, /* color */ 0, 1, 0, 1, 16 | /* position */ 0, 1, 0, /* color */ 0.5, 1, 0.5, 1, 17 | /* position */ 0, 0, 0, /* color */ 0, 0, 1, 1, 18 | /* position */ 0, 0, 1, /* color */ 0.5, 0.5, 1, 1, 19 | ]) 20 | 21 | // prettier-ignore 22 | const indices = new Uint16Array([ 23 | 0, 1, 24 | 2, 3, 25 | 4, 5 26 | ]) 27 | 28 | class Axes { 29 | public command: Command; 30 | public buffer: Buffer; 31 | 32 | constructor( 33 | systemBindGroupLayout: BindGroupLayout, 34 | systemUniformBindGroup: BindGroup, 35 | pipelineOptions: PipelineOptions = {}, 36 | ) { 37 | // TODO: make generic draw line pipeline 38 | const pipeline = new Pipeline({ 39 | bindGroupLayouts: [systemBindGroupLayout], 40 | ins: [new Attribute("position", "vec3"), new Attribute("color", "vec4")], 41 | outs: [new Attribute("vColor", "vec4")], 42 | language: "glsl", 43 | vertex: /* glsl */ ` 44 | vColor = color; 45 | 46 | gl_Position = system.projectionMatrix * system.viewMatrix * vec4(position, 1.0); 47 | `, 48 | fragment: /* glsl */ ` 49 | outColor = vColor; 50 | `, 51 | descriptor: { 52 | primitive: { topology: GPUPrimitiveTopology.LineList }, 53 | }, 54 | ...pipelineOptions, 55 | }); 56 | 57 | this.buffer = new Buffer().vertexBuffer(data); 58 | 59 | this.command = new Command({ 60 | pipeline, 61 | bindGroups: [systemUniformBindGroup], 62 | vertexBuffers: [this.buffer], 63 | indexBuffer: new Buffer().indexBuffer(indices), 64 | indexFormat: GPUIndexFormat.Uint16, 65 | count: indices.length, 66 | }); 67 | } 68 | 69 | public setScale(scale: number): void { 70 | this.buffer.setSubData(4 * (7 * 1 + 0), Float32Array.of(scale)); 71 | this.buffer.setSubData(4 * (7 * 3 + 1), Float32Array.of(scale)); 72 | this.buffer.setSubData(4 * (7 * 5 + 2), Float32Array.of(scale)); 73 | } 74 | } 75 | 76 | export default Axes; 77 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import * as Types from "./types.js"; 4 | export { Types }; 5 | 6 | export * from "./constants.js"; 7 | 8 | // Core 9 | export { default as Context } from "./core/Context.js"; 10 | export { default as State } from "./core/State.js"; 11 | export { default as Command } from "./core/Command.js"; 12 | 13 | export { default as Pass } from "./core/Pass.js"; 14 | export { default as Attachment } from "./core/Attachment.js"; 15 | 16 | export { default as Pipeline } from "./core/Pipeline.js"; 17 | export { default as Program } from "./core/Program.js"; 18 | export { default as Shader } from "./core/Shader.js"; 19 | export { default as Variable } from "./core/Variable.js"; 20 | export { default as Attribute } from "./core/Attribute.js"; 21 | export { default as Uniform } from "./core/Uniform.js"; 22 | export { default as Struct } from "./core/Struct.js"; 23 | 24 | export { default as Buffer } from "./core/Buffer.js"; 25 | 26 | export { default as Texture } from "./core/Texture.js"; 27 | export { default as Sampler } from "./core/Sampler.js"; 28 | 29 | export { default as BindGroupLayout } from "./core/BindGroupLayout.js"; 30 | export { default as BindGroup } from "./core/BindGroup.js"; 31 | 32 | export { default as Clock } from "./core/Clock.js"; 33 | 34 | // Helpers 35 | export { default as Axes } from "./helpers/Axes.js"; 36 | 37 | // Shaders 38 | import * as Shaders from "./shaders/index.js"; 39 | export { Shaders }; 40 | -------------------------------------------------------------------------------- /src/shaders/constants.glsl.ts: -------------------------------------------------------------------------------- 1 | const PI = /* glsl */ `const float PI = 3.14159265359;`; 2 | const EPSILON = /* glsl */ `const float EPSILON = 0.00000000001;`; 3 | const GAMMA = /* glsl */ `const float GAMMA = 2.2;`; 4 | 5 | export default { PI, EPSILON, GAMMA }; 6 | -------------------------------------------------------------------------------- /src/shaders/constants.wgsl.ts: -------------------------------------------------------------------------------- 1 | const PI = /* wgsl */ `const PI: f32 = 3.14159265359;`; 2 | const EPSILON = /* wgsl */ `const EPSILON: f32 = 0.00000000001;`; 3 | const GAMMA = /* wgsl */ `const GAMMA: f32 = 2.2;`; 4 | 5 | export default { PI, EPSILON, GAMMA }; 6 | -------------------------------------------------------------------------------- /src/shaders/index.glsl.ts: -------------------------------------------------------------------------------- 1 | export { default as CONSTANTS } from "./constants.glsl.js"; 2 | export { default as UTILS } from "./utils.glsl.js"; 3 | 4 | export { default as LINEAR } from "./lighting/linear.glsl.js"; 5 | export { default as GAMMA } from "./lighting/gamma.glsl.js"; 6 | export { default as DIFFUSE } from "./lighting/diffuse.glsl.js"; 7 | export { default as SPECULAR } from "./lighting/specular.glsl.js"; 8 | export { default as DIRECT } from "./lighting/direct.glsl.js"; 9 | 10 | export * as NOISE from "./noise/index.glsl.js"; 11 | -------------------------------------------------------------------------------- /src/shaders/index.ts: -------------------------------------------------------------------------------- 1 | export { default as CONSTANTS } from "./constants.wgsl.js"; 2 | export { default as UTILS } from "./utils.wgsl.js"; 3 | 4 | export { default as LINEAR } from "./lighting/linear.wgsl.js"; 5 | export { default as GAMMA } from "./lighting/gamma.wgsl.js"; 6 | export { default as DIFFUSE } from "./lighting/diffuse.wgsl.js"; 7 | export { default as SPECULAR } from "./lighting/specular.wgsl.js"; 8 | export { default as DIRECT } from "./lighting/direct.wgsl.js"; 9 | 10 | export * as NOISE from "./noise/index.wgsl.js"; 11 | 12 | export * as GLSL from "./index.glsl.js"; 13 | -------------------------------------------------------------------------------- /src/shaders/lighting/diffuse.glsl.ts: -------------------------------------------------------------------------------- 1 | const OREN_NAYAR = /* glsl */ ` 2 | #define DIFFUSE_OREN_NAYAR 3 | float Diffuse( 4 | vec3 lightDirection, 5 | vec3 viewDirection, 6 | vec3 surfaceNormal, 7 | Material material 8 | ) { 9 | float LdotV = dot(lightDirection, viewDirection); 10 | float NdotL = dot(lightDirection, surfaceNormal); 11 | float NdotV = dot(surfaceNormal, viewDirection); 12 | 13 | float s = LdotV - NdotL * NdotV; 14 | float t = mix(1.0, max(NdotL, NdotV), step(0.0, s)); 15 | 16 | float sigma2 = material.roughness * material.roughness; 17 | float A = 1.0 + sigma2 * (material.albedo / (sigma2 + 0.13) + 0.5 / (sigma2 + 0.33)); 18 | float B = 0.45 * sigma2 / (sigma2 + 0.09); 19 | 20 | return material.albedo * max(0.0, NdotL) * (A + B * s / t) / PI; 21 | } 22 | `; 23 | 24 | const LAMBERT = /* glsl */ ` 25 | #define DIFFUSE_LAMBERT 26 | float Diffuse( 27 | vec3 lightDirection, 28 | vec3 surfaceNormal 29 | ) { 30 | return max(0.0, dot(surfaceNormal, lightDirection)); 31 | } 32 | `; 33 | 34 | export default { 35 | OREN_NAYAR, 36 | LAMBERT, 37 | }; 38 | -------------------------------------------------------------------------------- /src/shaders/lighting/diffuse.wgsl.ts: -------------------------------------------------------------------------------- 1 | const OREN_NAYAR = /* wgsl */ ` 2 | // #define DIFFUSE_OREN_NAYAR 3 | fn Diffuse( 4 | lightDirection: vec3, 5 | viewDirection: vec3, 6 | surfaceNormal: vec3, 7 | material: Material 8 | ) -> f32 { 9 | let LdotV = dot(lightDirection, viewDirection); 10 | let NdotL = dot(lightDirection, surfaceNormal); 11 | let NdotV = dot(surfaceNormal, viewDirection); 12 | 13 | let s = LdotV - NdotL * NdotV; 14 | let t = mix(1.0, max(NdotL, NdotV), step(0.0, s)); 15 | 16 | let sigma2 = material.roughness * material.roughness; 17 | let A = 1.0 + sigma2 * (material.albedo / (sigma2 + 0.13) + 0.5 / (sigma2 + 0.33)); 18 | let B = 0.45 * sigma2 / (sigma2 + 0.09); 19 | 20 | return material.albedo * max(0.0, NdotL) * (A + B * s / t) / PI; 21 | } 22 | `; 23 | 24 | const LAMBERT = /* wgsl */ ` 25 | // #define DIFFUSE_LAMBERT 26 | fn Diffuse( 27 | lightDirection: vec3, 28 | surfaceNormal: vec3 29 | ) -> f32 { 30 | return max(0.0, dot(surfaceNormal, lightDirection)); 31 | } 32 | `; 33 | 34 | export default { 35 | OREN_NAYAR, 36 | LAMBERT, 37 | }; 38 | -------------------------------------------------------------------------------- /src/shaders/lighting/direct.glsl.ts: -------------------------------------------------------------------------------- 1 | const PHONG = /* glsl */ ` 2 | vec3 getDirectShading( 3 | const in Material material, 4 | const in Light light, 5 | const in vec3 surfaceNormal, 6 | const in vec3 viewDirection, 7 | const in vec3 positionToLight, 8 | const in bool distanceAttenuation, 9 | const in bool angleAttenuation 10 | ) { 11 | vec3 lightDirection = normalize(positionToLight); 12 | 13 | // Diffuse reflection (Lambertial reflectance) 14 | #ifdef DIFFUSE_LAMBERT 15 | float diff = Diffuse(lightDirection, surfaceNormal); 16 | #elif defined(DIFFUSE_OREN_NAYAR) 17 | float diff = Diffuse( 18 | lightDirection, 19 | viewDirection, 20 | surfaceNormal, 21 | material 22 | ); 23 | #endif 24 | 25 | if (diff <= 0.0) return vec3(0.0); 26 | 27 | // Specular relection 28 | // #ifdef WARD 29 | // float spec = Specular(lightDirection, viewDirection, surfaceNormal, material, fiberParallel, fiberPerpendicular, shinyParallel, shinyPerpendicular); 30 | // #elif defined(HEIDRICH_SEIDEL) 31 | // float spec = Specular(lightDirection, viewDirection, surfaceNormal, material, anisoRoughness, specDirection); 32 | // #else 33 | float spec = Specular(lightDirection, viewDirection, surfaceNormal, material); 34 | // #endif 35 | 36 | // Combine results 37 | vec3 diffuse = material.diffuseColor * diff; 38 | vec3 specular = material.specularColor * spec; 39 | 40 | // Attenuation 41 | float attenuation = 1.0; 42 | 43 | if (distanceAttenuation) { 44 | attenuation *= pow(saturate(1.0 - length(positionToLight) / light.range), 2.0); 45 | } 46 | 47 | if (angleAttenuation) { 48 | float theta = dot(lightDirection, normalize(-light.direction)); 49 | 50 | if (theta > light.innerAngle) { 51 | float epsilon = light.innerAngle - light.angle; 52 | attenuation *= clamp((theta - light.angle) / epsilon, 0.0, 1.0); 53 | } 54 | } 55 | 56 | return attenuation * (light.color.xyz * (material.ambientColor + diffuse + specular)) * light.color.a; 57 | } 58 | `; 59 | 60 | export default { 61 | PHONG, 62 | }; 63 | -------------------------------------------------------------------------------- /src/shaders/lighting/direct.wgsl.ts: -------------------------------------------------------------------------------- 1 | const PHONG = /* wgsl */ ` 2 | fn getDirectShading( 3 | material: Material, 4 | light: Light, 5 | surfaceNormal: vec3, 6 | viewDirection: vec3, 7 | positionToLight: vec3, 8 | distanceAttenuation: bool, 9 | angleAttenuation: bool 10 | ) -> vec3 { 11 | let lightDirection = normalize(positionToLight); 12 | 13 | // Diffuse reflection (Lambertial reflectance) 14 | // #ifdef DIFFUSE_LAMBERT 15 | // let diff = Diffuse(lightDirection, surfaceNormal); 16 | // #elif defined(DIFFUSE_OREN_NAYAR) 17 | let diff = Diffuse( 18 | lightDirection, 19 | viewDirection, 20 | surfaceNormal, 21 | material 22 | ); 23 | // #endif 24 | 25 | if (diff <= 0.0) { 26 | return vec3(0.0); 27 | } 28 | 29 | // Specular relection 30 | // #ifdef WARD 31 | // float spec = Specular(lightDirection, viewDirection, surfaceNormal, material, fiberParallel, fiberPerpendicular, shinyParallel, shinyPerpendicular); 32 | // #elif defined(HEIDRICH_SEIDEL) 33 | // float spec = Specular(lightDirection, viewDirection, surfaceNormal, material, anisoRoughness, specDirection); 34 | // #else 35 | let spec = Specular(lightDirection, viewDirection, surfaceNormal, material); 36 | // #endif 37 | 38 | // Combine results 39 | let diffuse = material.diffuseColor * diff; 40 | let specular = material.specularColor * spec; 41 | 42 | // Attenuation 43 | var attenuation = 1.0; 44 | 45 | if (distanceAttenuation) { 46 | attenuation = attenuation * pow(saturate(1.0 - length(positionToLight) / light.range), 2.0); 47 | } 48 | 49 | if (angleAttenuation) { 50 | let theta = dot(lightDirection, normalize(-light.direction)); 51 | 52 | if (theta > light.innerAngle) { 53 | let epsilon = light.innerAngle - light.angle; 54 | attenuation = attenuation * clamp((theta - light.angle) / epsilon, 0.0, 1.0); 55 | } 56 | } 57 | 58 | return attenuation * (light.color.xyz * (material.ambientColor + diffuse + specular)) * light.color.a; 59 | } 60 | `; 61 | 62 | export default { 63 | PHONG, 64 | }; 65 | -------------------------------------------------------------------------------- /src/shaders/lighting/gamma.glsl.ts: -------------------------------------------------------------------------------- 1 | export default /* glsl */ ` 2 | float toGamma(float v) { 3 | return pow(v, 1.0 / GAMMA); 4 | } 5 | vec2 toGamma(vec2 v) { 6 | return pow(v, vec2(1.0 / GAMMA)); 7 | } 8 | vec3 toGamma(vec3 v) { 9 | return pow(v, vec3(1.0 / GAMMA)); 10 | } 11 | vec4 toGamma(vec4 v) { 12 | return vec4(toGamma(v.rgb), v.a); 13 | }`; 14 | -------------------------------------------------------------------------------- /src/shaders/lighting/gamma.wgsl.ts: -------------------------------------------------------------------------------- 1 | export default /* wgsl */ ` 2 | fn toGamma(v: f32) -> f32 { 3 | return pow(v, 1.0 / GAMMA); 4 | } 5 | fn toGamma(v: vec2) -> vec2 { 6 | return pow(v, vec2(1.0 / GAMMA)); 7 | } 8 | fn toGamma(v: vec3) -> vec3 { 9 | return pow(v, vec3(1.0 / GAMMA)); 10 | } 11 | fn toGamma(v: vec4) -> vec4 { 12 | return vec4(toGamma(v.rgb), v.a); 13 | }`; 14 | -------------------------------------------------------------------------------- /src/shaders/lighting/linear.glsl.ts: -------------------------------------------------------------------------------- 1 | export default /* glsl */ ` 2 | float toLinear(float v) { 3 | return pow(v, GAMMA); 4 | } 5 | vec2 toLinear(vec2 v) { 6 | return pow(v, vec2(GAMMA)); 7 | } 8 | vec3 toLinear(vec3 v) { 9 | return pow(v, vec3(GAMMA)); 10 | } 11 | vec4 toLinear(vec4 v) { 12 | return vec4(toLinear(v.rgb), v.a); 13 | }`; 14 | -------------------------------------------------------------------------------- /src/shaders/lighting/linear.wgsl.ts: -------------------------------------------------------------------------------- 1 | export default /* wgsl */ ` 2 | fn toLinear(v: f32) -> f32 { 3 | return pow(v, GAMMA); 4 | } 5 | fn toLinear(v: vec2) -> vec2 { 6 | return pow(v, vec2(GAMMA)); 7 | } 8 | fn toLinear(v: vec3) -> vec3 { 9 | return pow(v, vec3(GAMMA)); 10 | } 11 | fn toLinear(v: vec4) -> vec4 { 12 | return vec4(toLinear(v.rgb), v.a); 13 | }`; 14 | -------------------------------------------------------------------------------- /src/shaders/lighting/specular.glsl.ts: -------------------------------------------------------------------------------- 1 | const BLINNPHONG = /* glsl */ ` 2 | #define SPECULAR_BLINNPHONG 3 | float Specular(vec3 lightDirection, vec3 viewDirection, vec3 surfaceNormal, Material material) { 4 | vec3 halfwayVector = normalize(viewDirection + lightDirection); 5 | 6 | return pow(max(0.0, dot(surfaceNormal, halfwayVector)), material.shininess); 7 | } 8 | `; 9 | 10 | const PHONG = /* glsl */ ` 11 | #define SPECULAR_PHONG 12 | float Specular(vec3 lightDirection, vec3 viewDirection, vec3 surfaceNormal, Material material) { 13 | vec3 reflectDirection = -reflect(lightDirection, surfaceNormal); 14 | 15 | return pow(max(0.0, dot(viewDirection, reflectDirection)), material.shininess); 16 | } 17 | `; 18 | 19 | const GAUSSIAN = /* glsl */ ` 20 | #define SPECULAR_GAUSSIAN 21 | float Specular(vec3 lightDirection, vec3 viewDirection, vec3 surfaceNormal, Material material) { 22 | vec3 H = normalize(lightDirection + viewDirection); 23 | float theta = acos(dot(H, surfaceNormal)); 24 | float w = theta / material.shininess; 25 | 26 | return exp(-w * w); 27 | } 28 | `; 29 | 30 | const BECKMANN_DISTRIBUTION = /* glsl */ ` 31 | float beckmannDistribution(float x, float roughness) { 32 | float NdotH = max(x, EPSILON); 33 | float cos2Alpha = NdotH * NdotH; 34 | float tan2Alpha = (cos2Alpha - 1.0) / cos2Alpha; 35 | float roughness2 = roughness * roughness; 36 | float denom = PI * roughness2 * cos2Alpha * cos2Alpha; 37 | 38 | return exp(tan2Alpha / roughness2) / denom; 39 | } 40 | `; 41 | 42 | const BECKMANN = /* glsl */ ` 43 | #define SPECULAR_BECKMANN 44 | ${BECKMANN_DISTRIBUTION} 45 | float Specular(vec3 lightDirection, vec3 viewDirection, vec3 surfaceNormal, Material material) { 46 | return beckmannDistribution(dot(surfaceNormal, normalize(lightDirection + viewDirection)), material.roughness); 47 | } 48 | `; 49 | 50 | const COOK_TORRANCE = /* glsl */ ` 51 | #define SPECULAR_COOK_TORRANCE 52 | ${BECKMANN_DISTRIBUTION} 53 | float Specular(vec3 lightDirection, vec3 viewDirection, vec3 surfaceNormal, Material material) { 54 | float VdotN = max(dot(viewDirection, surfaceNormal), 0.0); 55 | float LdotN = max(dot(lightDirection, surfaceNormal), 0.0); 56 | 57 | // Half angle vector 58 | vec3 H = normalize(lightDirection + viewDirection); 59 | 60 | // Geometric term 61 | float NdotH = max(dot(surfaceNormal, H), 0.0); 62 | float VdotH = max(dot(viewDirection, H), EPSILON); 63 | float x = 2.0 * NdotH / VdotH; 64 | float G = min(1.0, min(x * VdotN, x * LdotN)); 65 | 66 | // Distribution term 67 | float D = beckmannDistribution(NdotH, material.roughness); 68 | 69 | // Fresnel term 70 | float F = pow(1.0 - VdotN, material.fresnel); 71 | 72 | return G * F * D / max(PI * VdotN * LdotN, EPSILON); 73 | } 74 | `; 75 | 76 | // const WARD = /* glsl */ ` 77 | // #define SPECULAR_WARD 78 | // float Specular(vec3 lightDirection, vec3 viewDirection, vec3 surfaceNormal, vec3 fiberParallel, vec3 fiberPerpendicular, float shinyParallel, float shinyPerpendicular) { 79 | // float NdotL = dot(surfaceNormal, lightDirection); 80 | // float NdotR = dot(surfaceNormal, viewDirection); 81 | 82 | // if (NdotL < 0.0 || NdotR < 0.0) { 83 | // return 0.0; 84 | // } 85 | 86 | // vec3 H = normalize(lightDirection + viewDirection); 87 | 88 | // float NdotH = dot(surfaceNormal, H); 89 | // float XdotH = dot(fiberParallel, H); 90 | // float YdotH = dot(fiberPerpendicular, H); 91 | 92 | // float coeff = sqrt(NdotL / NdotR) / (4.0 * PI * shinyParallel * shinyPerpendicular); 93 | // float theta = (pow(XdotH / shinyParallel, 2.0) + pow(YdotH / shinyPerpendicular, 2.0)) / (1.0 + NdotH); 94 | 95 | // return coeff * exp(-2.0 * theta); 96 | // } 97 | // `; 98 | 99 | // const HEIDRICH_SEIDEL = /* glsl */ ` 100 | // float Specular(vec3 lightDirection, vec3 viewDirection, vec3 surfaceNormal, Material material, float anisoRoughness, vec3 specDirection) { 101 | // vec3 threadDirection = cross(surfaceNormal, normalize(specDirection)); 102 | 103 | // float LdotT = dot(lightDirection, threadDirection); 104 | // float vDotT = dot(viewDirection, threadDirection); 105 | 106 | // // Half angle vector 107 | // vec3 H = normalize(lightDirection + viewDirection); 108 | 109 | // float kSpec = pow(dot(surfaceNormal, H), 1.0 / material.roughness); 110 | 111 | // float aniso = pow(sin(LdotT) * sin(vDotT) + cos(LdotT) * cos(vDotT), 1.0 / material.roughness); 112 | 113 | // return kSpec * aniso; 114 | // } 115 | // `; 116 | 117 | export default { 118 | BLINNPHONG, 119 | PHONG, 120 | GAUSSIAN, 121 | BECKMANN, 122 | COOK_TORRANCE, 123 | // WARD, 124 | // HEIDRICH_SEIDEL, 125 | }; 126 | -------------------------------------------------------------------------------- /src/shaders/lighting/specular.wgsl.ts: -------------------------------------------------------------------------------- 1 | const BLINNPHONG = /* wgsl */ ` 2 | // #define SPECULAR_BLINNPHONG 3 | fn Specular(lightDirection: vec3, viewDirection: vec3, surfaceNormal: vec3, material: Material) -> f32 { 4 | let halfwayVector = normalize(viewDirection + lightDirection); 5 | 6 | return pow(max(0.0, dot(surfaceNormal, halfwayVector)), material.shininess); 7 | } 8 | `; 9 | 10 | const PHONG = /* wgsl */ ` 11 | // #define SPECULAR_PHONG 12 | fn Specular(lightDirection: vec3, viewDirection: vec3, surfaceNormal: vec3, material: Material) -> f32 { 13 | let reflectDirection = -reflect(lightDirection, surfaceNormal); 14 | 15 | return pow(max(0.0, dot(viewDirection, reflectDirection)), material.shininess); 16 | } 17 | `; 18 | 19 | const GAUSSIAN = /* wgsl */ ` 20 | // #define SPECULAR_GAUSSIAN 21 | fn Specular(lightDirection: vec3, viewDirection: vec3, surfaceNormal: vec3, material: Material) -> f32 { 22 | let H = normalize(lightDirection + viewDirection); 23 | let theta = acos(dot(H, surfaceNormal)); 24 | let w = theta / material.shininess; 25 | 26 | return exp(-w * w); 27 | } 28 | `; 29 | 30 | const BECKMANN_DISTRIBUTION = /* wgsl */ ` 31 | fn beckmannDistribution(x: f32, roughness: f32) -> f32 { 32 | let NdotH = max(x, EPSILON); 33 | let cos2Alpha = NdotH * NdotH; 34 | let tan2Alpha = (cos2Alpha - 1.0) / cos2Alpha; 35 | let roughness2 = roughness * roughness; 36 | let denom = PI * roughness2 * cos2Alpha * cos2Alpha; 37 | 38 | return exp(tan2Alpha / roughness2) / denom; 39 | } 40 | `; 41 | 42 | const BECKMANN = /* wgsl */ ` 43 | // #define SPECULAR_BECKMANN 44 | ${BECKMANN_DISTRIBUTION} 45 | fn Specular(lightDirection: vec3, viewDirection: vec3, surfaceNormal: vec3, material: Material) -> f32 { 46 | return beckmannDistribution(dot(surfaceNormal, normalize(lightDirection + viewDirection)), material.roughness); 47 | } 48 | `; 49 | 50 | const COOK_TORRANCE = /* wgsl */ ` 51 | // #define SPECULAR_COOK_TORRANCE 52 | ${BECKMANN_DISTRIBUTION} 53 | fn Specular(lightDirection: vec3, viewDirection: vec3, surfaceNormal: vec3, material: Material) -> f32 { 54 | let VdotN = max(dot(viewDirection, surfaceNormal), 0.0); 55 | let LdotN = max(dot(lightDirection, surfaceNormal), 0.0); 56 | 57 | // Half angle vector 58 | let H = normalize(lightDirection + viewDirection); 59 | 60 | // Geometric term 61 | let NdotH = max(dot(surfaceNormal, H), 0.0); 62 | let VdotH = max(dot(viewDirection, H), EPSILON); 63 | let x = 2.0 * NdotH / VdotH; 64 | let G = min(1.0, min(x * VdotN, x * LdotN)); 65 | 66 | // Distribution term 67 | let D = beckmannDistribution(NdotH, material.roughness); 68 | 69 | // Fresnel term 70 | let F = pow(1.0 - VdotN, material.fresnel); 71 | 72 | return G * F * D / max(PI * VdotN * LdotN, EPSILON); 73 | } 74 | `; 75 | 76 | // const WARD = /* wgsl */ ` 77 | // #define SPECULAR_WARD 78 | // fn Specular(vec3 lightDirection, vec3 viewDirection, vec3 surfaceNormal, vec3 fiberParallel, vec3 fiberPerpendicular, float shinyParallel, float shinyPerpendicular) -> f32 { 79 | // float NdotL = dot(surfaceNormal, lightDirection); 80 | // float NdotR = dot(surfaceNormal, viewDirection); 81 | 82 | // if (NdotL < 0.0 || NdotR < 0.0) { 83 | // return 0.0; 84 | // } 85 | 86 | // vec3 H = normalize(lightDirection + viewDirection); 87 | 88 | // float NdotH = dot(surfaceNormal, H); 89 | // float XdotH = dot(fiberParallel, H); 90 | // float YdotH = dot(fiberPerpendicular, H); 91 | 92 | // float coeff = sqrt(NdotL / NdotR) / (4.0 * PI * shinyParallel * shinyPerpendicular); 93 | // float theta = (pow(XdotH / shinyParallel, 2.0) + pow(YdotH / shinyPerpendicular, 2.0)) / (1.0 + NdotH); 94 | 95 | // return coeff * exp(-2.0 * theta); 96 | // } 97 | // `; 98 | 99 | // const HEIDRICH_SEIDEL = /* wgsl */ ` 100 | // fn Specular(lightDirection: vec3, viewDirection: vec3, surfaceNormal: vec3, material: Material, float anisoRoughness, vec3 specDirection) -> f32 { 101 | // vec3 threadDirection = cross(surfaceNormal, normalize(specDirection)); 102 | 103 | // float LdotT = dot(lightDirection, threadDirection); 104 | // float vDotT = dot(viewDirection, threadDirection); 105 | 106 | // // Half angle vector 107 | // vec3 H = normalize(lightDirection + viewDirection); 108 | 109 | // float kSpec = pow(dot(surfaceNormal, H), 1.0 / material.roughness); 110 | 111 | // float aniso = pow(sin(LdotT) * sin(vDotT) + cos(LdotT) * cos(vDotT), 1.0 / material.roughness); 112 | 113 | // return kSpec * aniso; 114 | // } 115 | // `; 116 | 117 | export default { 118 | BLINNPHONG, 119 | PHONG, 120 | GAUSSIAN, 121 | BECKMANN, 122 | COOK_TORRANCE, 123 | // WARD, 124 | // HEIDRICH_SEIDEL, 125 | }; 126 | -------------------------------------------------------------------------------- /src/shaders/noise/classic.glsl.ts: -------------------------------------------------------------------------------- 1 | import { FUNCTIONS, FADE2, FADE3, FADE4 } from "./utils.glsl.js"; 2 | 3 | const CNOISE2D = /* glsl */ ` 4 | // https://github.com/ashima/webgl-noise 5 | ${FUNCTIONS} 6 | ${FADE2} 7 | 8 | // Classic Perlin noise 9 | float cnoise2d(vec2 P) 10 | { 11 | vec4 Pi = floor(P.xyxy) + vec4(0.0, 0.0, 1.0, 1.0); 12 | vec4 Pf = fract(P.xyxy) - vec4(0.0, 0.0, 1.0, 1.0); 13 | Pi = mod289(Pi); // To avoid truncation effects in permutation 14 | vec4 ix = Pi.xzxz; 15 | vec4 iy = Pi.yyww; 16 | vec4 fx = Pf.xzxz; 17 | vec4 fy = Pf.yyww; 18 | 19 | vec4 i = permute(permute(ix) + iy); 20 | 21 | vec4 gx = fract(i * (1.0 / 41.0)) * 2.0 - 1.0 ; 22 | vec4 gy = abs(gx) - 0.5 ; 23 | vec4 tx = floor(gx + 0.5); 24 | gx = gx - tx; 25 | 26 | vec2 g00 = vec2(gx.x,gy.x); 27 | vec2 g10 = vec2(gx.y,gy.y); 28 | vec2 g01 = vec2(gx.z,gy.z); 29 | vec2 g11 = vec2(gx.w,gy.w); 30 | 31 | vec4 norm = taylorInvSqrt(vec4(dot(g00, g00), dot(g01, g01), dot(g10, g10), dot(g11, g11))); 32 | g00 *= norm.x; 33 | g01 *= norm.y; 34 | g10 *= norm.z; 35 | g11 *= norm.w; 36 | 37 | float n00 = dot(g00, vec2(fx.x, fy.x)); 38 | float n10 = dot(g10, vec2(fx.y, fy.y)); 39 | float n01 = dot(g01, vec2(fx.z, fy.z)); 40 | float n11 = dot(g11, vec2(fx.w, fy.w)); 41 | 42 | vec2 fade_xy = fade(Pf.xy); 43 | vec2 n_x = mix(vec2(n00, n01), vec2(n10, n11), fade_xy.x); 44 | float n_xy = mix(n_x.x, n_x.y, fade_xy.y); 45 | return 2.3 * n_xy; 46 | } 47 | `; 48 | 49 | const CNOISE3D = /* glsl */ ` 50 | // https://github.com/ashima/webgl-noise 51 | vec3 mod289(vec3 x) 52 | { 53 | return x - floor(x * (1.0 / 289.0)) * 289.0; 54 | } 55 | 56 | ${FUNCTIONS} 57 | ${FADE3} 58 | 59 | // Classic Perlin noise 60 | float cnoise3d(vec3 P) 61 | { 62 | vec3 Pi0 = floor(P); // Integer part for indexing 63 | vec3 Pi1 = Pi0 + vec3(1.0); // Integer part + 1 64 | Pi0 = mod289(Pi0); 65 | Pi1 = mod289(Pi1); 66 | vec3 Pf0 = fract(P); // Fractional part for interpolation 67 | vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0 68 | vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x); 69 | vec4 iy = vec4(Pi0.yy, Pi1.yy); 70 | vec4 iz0 = Pi0.zzzz; 71 | vec4 iz1 = Pi1.zzzz; 72 | 73 | vec4 ixy = permute(permute(ix) + iy); 74 | vec4 ixy0 = permute(ixy + iz0); 75 | vec4 ixy1 = permute(ixy + iz1); 76 | 77 | vec4 gx0 = ixy0 * (1.0 / 7.0); 78 | vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5; 79 | gx0 = fract(gx0); 80 | vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0); 81 | vec4 sz0 = step(gz0, vec4(0.0)); 82 | gx0 -= sz0 * (step(0.0, gx0) - 0.5); 83 | gy0 -= sz0 * (step(0.0, gy0) - 0.5); 84 | 85 | vec4 gx1 = ixy1 * (1.0 / 7.0); 86 | vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5; 87 | gx1 = fract(gx1); 88 | vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1); 89 | vec4 sz1 = step(gz1, vec4(0.0)); 90 | gx1 -= sz1 * (step(0.0, gx1) - 0.5); 91 | gy1 -= sz1 * (step(0.0, gy1) - 0.5); 92 | 93 | vec3 g000 = vec3(gx0.x,gy0.x,gz0.x); 94 | vec3 g100 = vec3(gx0.y,gy0.y,gz0.y); 95 | vec3 g010 = vec3(gx0.z,gy0.z,gz0.z); 96 | vec3 g110 = vec3(gx0.w,gy0.w,gz0.w); 97 | vec3 g001 = vec3(gx1.x,gy1.x,gz1.x); 98 | vec3 g101 = vec3(gx1.y,gy1.y,gz1.y); 99 | vec3 g011 = vec3(gx1.z,gy1.z,gz1.z); 100 | vec3 g111 = vec3(gx1.w,gy1.w,gz1.w); 101 | 102 | vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110))); 103 | g000 *= norm0.x; 104 | g010 *= norm0.y; 105 | g100 *= norm0.z; 106 | g110 *= norm0.w; 107 | vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111))); 108 | g001 *= norm1.x; 109 | g011 *= norm1.y; 110 | g101 *= norm1.z; 111 | g111 *= norm1.w; 112 | 113 | float n000 = dot(g000, Pf0); 114 | float n100 = dot(g100, vec3(Pf1.x, Pf0.yz)); 115 | float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z)); 116 | float n110 = dot(g110, vec3(Pf1.xy, Pf0.z)); 117 | float n001 = dot(g001, vec3(Pf0.xy, Pf1.z)); 118 | float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z)); 119 | float n011 = dot(g011, vec3(Pf0.x, Pf1.yz)); 120 | float n111 = dot(g111, Pf1); 121 | 122 | vec3 fade_xyz = fade(Pf0); 123 | vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z); 124 | vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y); 125 | float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x); 126 | return 2.2 * n_xyz; 127 | } 128 | `; 129 | 130 | const CNOISE4D = /* glsl */ ` 131 | // https://github.com/ashima/webgl-noise 132 | ${FUNCTIONS} 133 | ${FADE4} 134 | 135 | // Classic Perlin noise 136 | float cnoise4d(vec4 P) 137 | { 138 | vec4 Pi0 = floor(P); // Integer part for indexing 139 | vec4 Pi1 = Pi0 + 1.0; // Integer part + 1 140 | Pi0 = mod289(Pi0); 141 | Pi1 = mod289(Pi1); 142 | vec4 Pf0 = fract(P); // Fractional part for interpolation 143 | vec4 Pf1 = Pf0 - 1.0; // Fractional part - 1.0 144 | vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x); 145 | vec4 iy = vec4(Pi0.yy, Pi1.yy); 146 | vec4 iz0 = vec4(Pi0.zzzz); 147 | vec4 iz1 = vec4(Pi1.zzzz); 148 | vec4 iw0 = vec4(Pi0.wwww); 149 | vec4 iw1 = vec4(Pi1.wwww); 150 | 151 | vec4 ixy = permute(permute(ix) + iy); 152 | vec4 ixy0 = permute(ixy + iz0); 153 | vec4 ixy1 = permute(ixy + iz1); 154 | vec4 ixy00 = permute(ixy0 + iw0); 155 | vec4 ixy01 = permute(ixy0 + iw1); 156 | vec4 ixy10 = permute(ixy1 + iw0); 157 | vec4 ixy11 = permute(ixy1 + iw1); 158 | 159 | vec4 gx00 = ixy00 * (1.0 / 7.0); 160 | vec4 gy00 = floor(gx00) * (1.0 / 7.0); 161 | vec4 gz00 = floor(gy00) * (1.0 / 6.0); 162 | gx00 = fract(gx00) - 0.5; 163 | gy00 = fract(gy00) - 0.5; 164 | gz00 = fract(gz00) - 0.5; 165 | vec4 gw00 = vec4(0.75) - abs(gx00) - abs(gy00) - abs(gz00); 166 | vec4 sw00 = step(gw00, vec4(0.0)); 167 | gx00 -= sw00 * (step(0.0, gx00) - 0.5); 168 | gy00 -= sw00 * (step(0.0, gy00) - 0.5); 169 | 170 | vec4 gx01 = ixy01 * (1.0 / 7.0); 171 | vec4 gy01 = floor(gx01) * (1.0 / 7.0); 172 | vec4 gz01 = floor(gy01) * (1.0 / 6.0); 173 | gx01 = fract(gx01) - 0.5; 174 | gy01 = fract(gy01) - 0.5; 175 | gz01 = fract(gz01) - 0.5; 176 | vec4 gw01 = vec4(0.75) - abs(gx01) - abs(gy01) - abs(gz01); 177 | vec4 sw01 = step(gw01, vec4(0.0)); 178 | gx01 -= sw01 * (step(0.0, gx01) - 0.5); 179 | gy01 -= sw01 * (step(0.0, gy01) - 0.5); 180 | 181 | vec4 gx10 = ixy10 * (1.0 / 7.0); 182 | vec4 gy10 = floor(gx10) * (1.0 / 7.0); 183 | vec4 gz10 = floor(gy10) * (1.0 / 6.0); 184 | gx10 = fract(gx10) - 0.5; 185 | gy10 = fract(gy10) - 0.5; 186 | gz10 = fract(gz10) - 0.5; 187 | vec4 gw10 = vec4(0.75) - abs(gx10) - abs(gy10) - abs(gz10); 188 | vec4 sw10 = step(gw10, vec4(0.0)); 189 | gx10 -= sw10 * (step(0.0, gx10) - 0.5); 190 | gy10 -= sw10 * (step(0.0, gy10) - 0.5); 191 | 192 | vec4 gx11 = ixy11 * (1.0 / 7.0); 193 | vec4 gy11 = floor(gx11) * (1.0 / 7.0); 194 | vec4 gz11 = floor(gy11) * (1.0 / 6.0); 195 | gx11 = fract(gx11) - 0.5; 196 | gy11 = fract(gy11) - 0.5; 197 | gz11 = fract(gz11) - 0.5; 198 | vec4 gw11 = vec4(0.75) - abs(gx11) - abs(gy11) - abs(gz11); 199 | vec4 sw11 = step(gw11, vec4(0.0)); 200 | gx11 -= sw11 * (step(0.0, gx11) - 0.5); 201 | gy11 -= sw11 * (step(0.0, gy11) - 0.5); 202 | 203 | vec4 g0000 = vec4(gx00.x,gy00.x,gz00.x,gw00.x); 204 | vec4 g1000 = vec4(gx00.y,gy00.y,gz00.y,gw00.y); 205 | vec4 g0100 = vec4(gx00.z,gy00.z,gz00.z,gw00.z); 206 | vec4 g1100 = vec4(gx00.w,gy00.w,gz00.w,gw00.w); 207 | vec4 g0010 = vec4(gx10.x,gy10.x,gz10.x,gw10.x); 208 | vec4 g1010 = vec4(gx10.y,gy10.y,gz10.y,gw10.y); 209 | vec4 g0110 = vec4(gx10.z,gy10.z,gz10.z,gw10.z); 210 | vec4 g1110 = vec4(gx10.w,gy10.w,gz10.w,gw10.w); 211 | vec4 g0001 = vec4(gx01.x,gy01.x,gz01.x,gw01.x); 212 | vec4 g1001 = vec4(gx01.y,gy01.y,gz01.y,gw01.y); 213 | vec4 g0101 = vec4(gx01.z,gy01.z,gz01.z,gw01.z); 214 | vec4 g1101 = vec4(gx01.w,gy01.w,gz01.w,gw01.w); 215 | vec4 g0011 = vec4(gx11.x,gy11.x,gz11.x,gw11.x); 216 | vec4 g1011 = vec4(gx11.y,gy11.y,gz11.y,gw11.y); 217 | vec4 g0111 = vec4(gx11.z,gy11.z,gz11.z,gw11.z); 218 | vec4 g1111 = vec4(gx11.w,gy11.w,gz11.w,gw11.w); 219 | 220 | vec4 norm00 = taylorInvSqrt(vec4(dot(g0000, g0000), dot(g0100, g0100), dot(g1000, g1000), dot(g1100, g1100))); 221 | g0000 *= norm00.x; 222 | g0100 *= norm00.y; 223 | g1000 *= norm00.z; 224 | g1100 *= norm00.w; 225 | 226 | vec4 norm01 = taylorInvSqrt(vec4(dot(g0001, g0001), dot(g0101, g0101), dot(g1001, g1001), dot(g1101, g1101))); 227 | g0001 *= norm01.x; 228 | g0101 *= norm01.y; 229 | g1001 *= norm01.z; 230 | g1101 *= norm01.w; 231 | 232 | vec4 norm10 = taylorInvSqrt(vec4(dot(g0010, g0010), dot(g0110, g0110), dot(g1010, g1010), dot(g1110, g1110))); 233 | g0010 *= norm10.x; 234 | g0110 *= norm10.y; 235 | g1010 *= norm10.z; 236 | g1110 *= norm10.w; 237 | 238 | vec4 norm11 = taylorInvSqrt(vec4(dot(g0011, g0011), dot(g0111, g0111), dot(g1011, g1011), dot(g1111, g1111))); 239 | g0011 *= norm11.x; 240 | g0111 *= norm11.y; 241 | g1011 *= norm11.z; 242 | g1111 *= norm11.w; 243 | 244 | float n0000 = dot(g0000, Pf0); 245 | float n1000 = dot(g1000, vec4(Pf1.x, Pf0.yzw)); 246 | float n0100 = dot(g0100, vec4(Pf0.x, Pf1.y, Pf0.zw)); 247 | float n1100 = dot(g1100, vec4(Pf1.xy, Pf0.zw)); 248 | float n0010 = dot(g0010, vec4(Pf0.xy, Pf1.z, Pf0.w)); 249 | float n1010 = dot(g1010, vec4(Pf1.x, Pf0.y, Pf1.z, Pf0.w)); 250 | float n0110 = dot(g0110, vec4(Pf0.x, Pf1.yz, Pf0.w)); 251 | float n1110 = dot(g1110, vec4(Pf1.xyz, Pf0.w)); 252 | float n0001 = dot(g0001, vec4(Pf0.xyz, Pf1.w)); 253 | float n1001 = dot(g1001, vec4(Pf1.x, Pf0.yz, Pf1.w)); 254 | float n0101 = dot(g0101, vec4(Pf0.x, Pf1.y, Pf0.z, Pf1.w)); 255 | float n1101 = dot(g1101, vec4(Pf1.xy, Pf0.z, Pf1.w)); 256 | float n0011 = dot(g0011, vec4(Pf0.xy, Pf1.zw)); 257 | float n1011 = dot(g1011, vec4(Pf1.x, Pf0.y, Pf1.zw)); 258 | float n0111 = dot(g0111, vec4(Pf0.x, Pf1.yzw)); 259 | float n1111 = dot(g1111, Pf1); 260 | 261 | vec4 fade_xyzw = fade(Pf0); 262 | vec4 n_0w = mix(vec4(n0000, n1000, n0100, n1100), vec4(n0001, n1001, n0101, n1101), fade_xyzw.w); 263 | vec4 n_1w = mix(vec4(n0010, n1010, n0110, n1110), vec4(n0011, n1011, n0111, n1111), fade_xyzw.w); 264 | vec4 n_zw = mix(n_0w, n_1w, fade_xyzw.z); 265 | vec2 n_yzw = mix(n_zw.xy, n_zw.zw, fade_xyzw.y); 266 | float n_xyzw = mix(n_yzw.x, n_yzw.y, fade_xyzw.x); 267 | return 2.2 * n_xyzw; 268 | } 269 | `; 270 | 271 | export default { 272 | CNOISE2D, 273 | CNOISE3D, 274 | CNOISE4D, 275 | }; 276 | -------------------------------------------------------------------------------- /src/shaders/noise/classic.wgsl.ts: -------------------------------------------------------------------------------- 1 | import { 2 | FADE_2, 3 | FADE_3, 4 | FADE_4, 5 | MOD289_3, 6 | MOD289_4, 7 | PERMUTE_4, 8 | TAYLOR_INV_SQRT_4, 9 | } from "./utils.wgsl.js"; 10 | 11 | const CNOISE2D = /* wgsl */ ` 12 | // https://github.com/ashima/webgl-noise 13 | ${MOD289_4} 14 | ${PERMUTE_4} 15 | ${TAYLOR_INV_SQRT_4} 16 | ${FADE_2} 17 | 18 | // Classic Perlin noise 19 | fn cnoise2d(P: vec2) -> f32 { 20 | var Pi = floor(P.xyxy) + vec4(0.0, 0.0, 1.0, 1.0); 21 | let Pf = fract(P.xyxy) - vec4(0.0, 0.0, 1.0, 1.0); 22 | Pi = mod289_4(Pi); // To avoid truncation effects in permutation 23 | let ix = Pi.xzxz; 24 | let iy = Pi.yyww; 25 | let fx = Pf.xzxz; 26 | let fy = Pf.yyww; 27 | 28 | let i = permute_4(permute_4(ix) + iy); 29 | 30 | var gx = fract(i * (1.0 / 41.0)) * 2.0 - 1.0 ; 31 | let gy = abs(gx) - 0.5 ; 32 | let tx = floor(gx + 0.5); 33 | gx = gx - tx; 34 | 35 | var g00 = vec2(gx.x,gy.x); 36 | var g10 = vec2(gx.y,gy.y); 37 | var g01 = vec2(gx.z,gy.z); 38 | var g11 = vec2(gx.w,gy.w); 39 | 40 | let norm = taylorInvSqrt_4(vec4(dot(g00, g00), dot(g01, g01), dot(g10, g10), dot(g11, g11))); 41 | g00 = g00 * norm.x; 42 | g01 = g01 * norm.y; 43 | g10 = g10 * norm.z; 44 | g11 = g11 * norm.w; 45 | 46 | let n00 = dot(g00, vec2(fx.x, fy.x)); 47 | let n10 = dot(g10, vec2(fx.y, fy.y)); 48 | let n01 = dot(g01, vec2(fx.z, fy.z)); 49 | let n11 = dot(g11, vec2(fx.w, fy.w)); 50 | 51 | let fade_xy = fade_2(Pf.xy); 52 | let n_x = mix(vec2(n00, n01), vec2(n10, n11), fade_xy.x); 53 | let n_xy = mix(n_x.x, n_x.y, fade_xy.y); 54 | return 2.3 * n_xy; 55 | } 56 | `; 57 | 58 | const CNOISE3D = /* wgsl */ ` 59 | // https://github.com/ashima/webgl-noise 60 | ${MOD289_3} 61 | ${MOD289_4} 62 | ${PERMUTE_4} 63 | ${TAYLOR_INV_SQRT_4} 64 | ${FADE_3} 65 | 66 | // Classic Perlin noise 67 | fn cnoise3d(P: vec3) -> f32 { 68 | var Pi0 = floor(P); // Integer part for indexing 69 | var Pi1 = Pi0 + vec3(1.0); // Integer part + 1 70 | Pi0 = mod289_3(Pi0); 71 | Pi1 = mod289_3(Pi1); 72 | let Pf0 = fract(P); // Fractional part for interpolation 73 | let Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0 74 | let ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x); 75 | let iy = vec4(Pi0.yy, Pi1.yy); 76 | let iz0 = Pi0.zzzz; 77 | let iz1 = Pi1.zzzz; 78 | 79 | let ixy = permute_4(permute_4(ix) + iy); 80 | let ixy0 = permute_4(ixy + iz0); 81 | let ixy1 = permute_4(ixy + iz1); 82 | 83 | var gx0 = ixy0 * (1.0 / 7.0); 84 | var gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5; 85 | gx0 = fract(gx0); 86 | let gz0 = vec4(0.5) - abs(gx0) - abs(gy0); 87 | let sz0 = step(gz0, vec4(0.0)); 88 | gx0 = gx0 - (sz0 * (step(vec4(0.0), gx0) - 0.5)); 89 | gy0 = gy0 - (sz0 * (step(vec4(0.0), gy0) - 0.5)); 90 | 91 | var gx1 = ixy1 * (1.0 / 7.0); 92 | var gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5; 93 | gx1 = fract(gx1); 94 | let gz1 = vec4(0.5) - abs(gx1) - abs(gy1); 95 | let sz1 = step(gz1, vec4(0.0)); 96 | gx1 = gx1 - (sz1 * (step(vec4(0.0), gx1) - 0.5)); 97 | gy1 = gy1 - (sz1 * (step(vec4(0.0), gy1) - 0.5)); 98 | 99 | var g000 = vec3(gx0.x,gy0.x,gz0.x); 100 | var g100 = vec3(gx0.y,gy0.y,gz0.y); 101 | var g010 = vec3(gx0.z,gy0.z,gz0.z); 102 | var g110 = vec3(gx0.w,gy0.w,gz0.w); 103 | var g001 = vec3(gx1.x,gy1.x,gz1.x); 104 | var g101 = vec3(gx1.y,gy1.y,gz1.y); 105 | var g011 = vec3(gx1.z,gy1.z,gz1.z); 106 | var g111 = vec3(gx1.w,gy1.w,gz1.w); 107 | 108 | let norm0 = taylorInvSqrt_4(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110))); 109 | g000 = g000 * norm0.x; 110 | g010 = g010 * norm0.y; 111 | g100 = g100 * norm0.z; 112 | g110 = g110 * norm0.w; 113 | let norm1 = taylorInvSqrt_4(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111))); 114 | g001 = g001 * norm1.x; 115 | g011 = g011 * norm1.y; 116 | g101 = g101 * norm1.z; 117 | g111 = g111 * norm1.w; 118 | 119 | let n000 = dot(g000, Pf0); 120 | let n100 = dot(g100, vec3(Pf1.x, Pf0.yz)); 121 | let n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z)); 122 | let n110 = dot(g110, vec3(Pf1.xy, Pf0.z)); 123 | let n001 = dot(g001, vec3(Pf0.xy, Pf1.z)); 124 | let n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z)); 125 | let n011 = dot(g011, vec3(Pf0.x, Pf1.yz)); 126 | let n111 = dot(g111, Pf1); 127 | 128 | let fade_xyz = fade_3(Pf0); 129 | let n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z); 130 | let n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y); 131 | let n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x); 132 | return 2.2 * n_xyz; 133 | } 134 | `; 135 | 136 | const CNOISE4D = /* wgsl */ ` 137 | // https://github.com/ashima/webgl-noise 138 | ${MOD289_4} 139 | ${PERMUTE_4} 140 | ${TAYLOR_INV_SQRT_4} 141 | ${FADE_4} 142 | 143 | // Classic Perlin noise 144 | fn cnoise4d(P: vec4) -> f32 { 145 | var Pi0 = floor(P); // Integer part for indexing 146 | var Pi1 = Pi0 + 1.0; // Integer part + 1 147 | Pi0 = mod289_4(Pi0); 148 | Pi1 = mod289_4(Pi1); 149 | let Pf0 = fract(P); // Fractional part for interpolation 150 | let Pf1 = Pf0 - 1.0; // Fractional part - 1.0 151 | let ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x); 152 | let iy = vec4(Pi0.yy, Pi1.yy); 153 | let iz0 = vec4(Pi0.zzzz); 154 | let iz1 = vec4(Pi1.zzzz); 155 | let iw0 = vec4(Pi0.wwww); 156 | let iw1 = vec4(Pi1.wwww); 157 | 158 | let ixy = permute_4(permute_4(ix) + iy); 159 | let ixy0 = permute_4(ixy + iz0); 160 | let ixy1 = permute_4(ixy + iz1); 161 | let ixy00 = permute_4(ixy0 + iw0); 162 | let ixy01 = permute_4(ixy0 + iw1); 163 | let ixy10 = permute_4(ixy1 + iw0); 164 | let ixy11 = permute_4(ixy1 + iw1); 165 | 166 | var gx00 = ixy00 * (1.0 / 7.0); 167 | var gy00 = floor(gx00) * (1.0 / 7.0); 168 | var gz00 = floor(gy00) * (1.0 / 6.0); 169 | gx00 = fract(gx00) - 0.5; 170 | gy00 = fract(gy00) - 0.5; 171 | gz00 = fract(gz00) - 0.5; 172 | var gw00 = vec4(0.75) - abs(gx00) - abs(gy00) - abs(gz00); 173 | var sw00 = step(gw00, vec4(0.0)); 174 | gx00 = gx00 - (sw00 * (step(vec4(0.0), gx00) - 0.5)); 175 | gy00 = gy00 - (sw00 * (step(vec4(0.0), gy00) - 0.5)); 176 | 177 | var gx01 = ixy01 * (1.0 / 7.0); 178 | var gy01 = floor(gx01) * (1.0 / 7.0); 179 | var gz01 = floor(gy01) * (1.0 / 6.0); 180 | gx01 = fract(gx01) - 0.5; 181 | gy01 = fract(gy01) - 0.5; 182 | gz01 = fract(gz01) - 0.5; 183 | var gw01 = vec4(0.75) - abs(gx01) - abs(gy01) - abs(gz01); 184 | var sw01 = step(gw01, vec4(0.0)); 185 | gx01 = gx01 - (sw01 * (step(vec4(0.0), gx01) - 0.5)); 186 | gy01 = gy01 - (sw01 * (step(vec4(0.0), gy01) - 0.5)); 187 | 188 | var gx10 = ixy10 * (1.0 / 7.0); 189 | var gy10 = floor(gx10) * (1.0 / 7.0); 190 | var gz10 = floor(gy10) * (1.0 / 6.0); 191 | gx10 = fract(gx10) - 0.5; 192 | gy10 = fract(gy10) - 0.5; 193 | gz10 = fract(gz10) - 0.5; 194 | var gw10 = vec4(0.75) - abs(gx10) - abs(gy10) - abs(gz10); 195 | var sw10 = step(gw10, vec4(0.0)); 196 | gx10 = gx10 - (sw10 * (step(vec4(0.0), gx10) - 0.5)); 197 | gy10 = gy10 - (sw10 * (step(vec4(0.0), gy10) - 0.5)); 198 | 199 | var gx11 = ixy11 * (1.0 / 7.0); 200 | var gy11 = floor(gx11) * (1.0 / 7.0); 201 | var gz11 = floor(gy11) * (1.0 / 6.0); 202 | gx11 = fract(gx11) - 0.5; 203 | gy11 = fract(gy11) - 0.5; 204 | gz11 = fract(gz11) - 0.5; 205 | var gw11 = vec4(0.75) - abs(gx11) - abs(gy11) - abs(gz11); 206 | var sw11 = step(gw11, vec4(0.0)); 207 | gx11 = gx11 - (sw11 * (step(vec4(0.0), gx11) - 0.5)); 208 | gy11 = gy11 - (sw11 * (step(vec4(0.0), gy11) - 0.5)); 209 | 210 | var g0000 = vec4(gx00.x,gy00.x,gz00.x,gw00.x); 211 | var g1000 = vec4(gx00.y,gy00.y,gz00.y,gw00.y); 212 | var g0100 = vec4(gx00.z,gy00.z,gz00.z,gw00.z); 213 | var g1100 = vec4(gx00.w,gy00.w,gz00.w,gw00.w); 214 | var g0010 = vec4(gx10.x,gy10.x,gz10.x,gw10.x); 215 | var g1010 = vec4(gx10.y,gy10.y,gz10.y,gw10.y); 216 | var g0110 = vec4(gx10.z,gy10.z,gz10.z,gw10.z); 217 | var g1110 = vec4(gx10.w,gy10.w,gz10.w,gw10.w); 218 | var g0001 = vec4(gx01.x,gy01.x,gz01.x,gw01.x); 219 | var g1001 = vec4(gx01.y,gy01.y,gz01.y,gw01.y); 220 | var g0101 = vec4(gx01.z,gy01.z,gz01.z,gw01.z); 221 | var g1101 = vec4(gx01.w,gy01.w,gz01.w,gw01.w); 222 | var g0011 = vec4(gx11.x,gy11.x,gz11.x,gw11.x); 223 | var g1011 = vec4(gx11.y,gy11.y,gz11.y,gw11.y); 224 | var g0111 = vec4(gx11.z,gy11.z,gz11.z,gw11.z); 225 | var g1111 = vec4(gx11.w,gy11.w,gz11.w,gw11.w); 226 | 227 | let norm00 = taylorInvSqrt_4(vec4(dot(g0000, g0000), dot(g0100, g0100), dot(g1000, g1000), dot(g1100, g1100))); 228 | g0000 = g0000 * norm00.x; 229 | g0100 = g0100 * norm00.y; 230 | g1000 = g1000 * norm00.z; 231 | g1100 = g1100 * norm00.w; 232 | 233 | let norm01 = taylorInvSqrt_4(vec4(dot(g0001, g0001), dot(g0101, g0101), dot(g1001, g1001), dot(g1101, g1101))); 234 | g0001 = g0001 * norm01.x; 235 | g0101 = g0101 * norm01.y; 236 | g1001 = g1001 * norm01.z; 237 | g1101 = g1101 * norm01.w; 238 | 239 | let norm10 = taylorInvSqrt_4(vec4(dot(g0010, g0010), dot(g0110, g0110), dot(g1010, g1010), dot(g1110, g1110))); 240 | g0010 = g0010 * norm10.x; 241 | g0110 = g0110 * norm10.y; 242 | g1010 = g1010 * norm10.z; 243 | g1110 = g1110 * norm10.w; 244 | 245 | let norm11 = taylorInvSqrt_4(vec4(dot(g0011, g0011), dot(g0111, g0111), dot(g1011, g1011), dot(g1111, g1111))); 246 | g0011 = g0011 * norm11.x; 247 | g0111 = g0111 * norm11.y; 248 | g1011 = g1011 * norm11.z; 249 | g1111 = g1111 * norm11.w; 250 | 251 | let n0000 = dot(g0000, Pf0); 252 | let n1000 = dot(g1000, vec4(Pf1.x, Pf0.yzw)); 253 | let n0100 = dot(g0100, vec4(Pf0.x, Pf1.y, Pf0.zw)); 254 | let n1100 = dot(g1100, vec4(Pf1.xy, Pf0.zw)); 255 | let n0010 = dot(g0010, vec4(Pf0.xy, Pf1.z, Pf0.w)); 256 | let n1010 = dot(g1010, vec4(Pf1.x, Pf0.y, Pf1.z, Pf0.w)); 257 | let n0110 = dot(g0110, vec4(Pf0.x, Pf1.yz, Pf0.w)); 258 | let n1110 = dot(g1110, vec4(Pf1.xyz, Pf0.w)); 259 | let n0001 = dot(g0001, vec4(Pf0.xyz, Pf1.w)); 260 | let n1001 = dot(g1001, vec4(Pf1.x, Pf0.yz, Pf1.w)); 261 | let n0101 = dot(g0101, vec4(Pf0.x, Pf1.y, Pf0.z, Pf1.w)); 262 | let n1101 = dot(g1101, vec4(Pf1.xy, Pf0.z, Pf1.w)); 263 | let n0011 = dot(g0011, vec4(Pf0.xy, Pf1.zw)); 264 | let n1011 = dot(g1011, vec4(Pf1.x, Pf0.y, Pf1.zw)); 265 | let n0111 = dot(g0111, vec4(Pf0.x, Pf1.yzw)); 266 | let n1111 = dot(g1111, Pf1); 267 | 268 | let fade_xyzw = fade_4(Pf0); 269 | let n_0w = mix(vec4(n0000, n1000, n0100, n1100), vec4(n0001, n1001, n0101, n1101), fade_xyzw.w); 270 | let n_1w = mix(vec4(n0010, n1010, n0110, n1110), vec4(n0011, n1011, n0111, n1111), fade_xyzw.w); 271 | let n_zw = mix(n_0w, n_1w, fade_xyzw.z); 272 | let n_yzw = mix(n_zw.xy, n_zw.zw, fade_xyzw.y); 273 | let n_xyzw = mix(n_yzw.x, n_yzw.y, fade_xyzw.x); 274 | return 2.2 * n_xyzw; 275 | } 276 | `; 277 | 278 | export default { 279 | CNOISE2D, 280 | CNOISE3D, 281 | CNOISE4D, 282 | }; 283 | -------------------------------------------------------------------------------- /src/shaders/noise/index.glsl.ts: -------------------------------------------------------------------------------- 1 | export { default as CLASSIC } from "./classic.glsl.js"; 2 | export { default as PERIODIC } from "./periodic.glsl.js"; 3 | export { default as SIMPLEX } from "./simplex.glsl.js"; 4 | -------------------------------------------------------------------------------- /src/shaders/noise/index.wgsl.ts: -------------------------------------------------------------------------------- 1 | export { default as CLASSIC } from "./classic.wgsl.js"; 2 | export { default as PERIODIC } from "./periodic.wgsl.js"; 3 | export { default as SIMPLEX } from "./simplex.wgsl.js"; 4 | export { default as WORLEY } from "./worley.wgsl.js"; 5 | -------------------------------------------------------------------------------- /src/shaders/noise/periodic.glsl.ts: -------------------------------------------------------------------------------- 1 | import { FUNCTIONS, FADE2, FADE3, FADE4 } from "./utils.glsl.js"; 2 | 3 | const PNOISE2D = /* glsl */ ` 4 | // https://github.com/ashima/webgl-noise 5 | ${FUNCTIONS} 6 | ${FADE2} 7 | 8 | // Classic Perlin noise, periodic variant 9 | float pnoise2d(vec2 P, vec2 rep) 10 | { 11 | vec4 Pi = floor(P.xyxy) + vec4(0.0, 0.0, 1.0, 1.0); 12 | vec4 Pf = fract(P.xyxy) - vec4(0.0, 0.0, 1.0, 1.0); 13 | Pi = mod(Pi, rep.xyxy); // To create noise with explicit period 14 | Pi = mod289(Pi); // To avoid truncation effects in permutation 15 | vec4 ix = Pi.xzxz; 16 | vec4 iy = Pi.yyww; 17 | vec4 fx = Pf.xzxz; 18 | vec4 fy = Pf.yyww; 19 | 20 | vec4 i = permute(permute(ix) + iy); 21 | 22 | vec4 gx = fract(i * (1.0 / 41.0)) * 2.0 - 1.0 ; 23 | vec4 gy = abs(gx) - 0.5 ; 24 | vec4 tx = floor(gx + 0.5); 25 | gx = gx - tx; 26 | 27 | vec2 g00 = vec2(gx.x,gy.x); 28 | vec2 g10 = vec2(gx.y,gy.y); 29 | vec2 g01 = vec2(gx.z,gy.z); 30 | vec2 g11 = vec2(gx.w,gy.w); 31 | 32 | vec4 norm = taylorInvSqrt(vec4(dot(g00, g00), dot(g01, g01), dot(g10, g10), dot(g11, g11))); 33 | g00 *= norm.x; 34 | g01 *= norm.y; 35 | g10 *= norm.z; 36 | g11 *= norm.w; 37 | 38 | float n00 = dot(g00, vec2(fx.x, fy.x)); 39 | float n10 = dot(g10, vec2(fx.y, fy.y)); 40 | float n01 = dot(g01, vec2(fx.z, fy.z)); 41 | float n11 = dot(g11, vec2(fx.w, fy.w)); 42 | 43 | vec2 fade_xy = fade(Pf.xy); 44 | vec2 n_x = mix(vec2(n00, n01), vec2(n10, n11), fade_xy.x); 45 | float n_xy = mix(n_x.x, n_x.y, fade_xy.y); 46 | return 2.3 * n_xy; 47 | } 48 | `; 49 | 50 | const PNOISE3D = /* glsl */ ` 51 | // https://github.com/ashima/webgl-noise 52 | vec3 mod289(vec3 x) 53 | { 54 | return x - floor(x * (1.0 / 289.0)) * 289.0; 55 | } 56 | 57 | ${FUNCTIONS} 58 | ${FADE3} 59 | 60 | // Classic Perlin noise, periodic variant 61 | float pnoise3d(vec3 P, vec3 rep) 62 | { 63 | vec3 Pi0 = mod(floor(P), rep); // Integer part, modulo period 64 | vec3 Pi1 = mod(Pi0 + vec3(1.0), rep); // Integer part + 1, mod period 65 | Pi0 = mod289(Pi0); 66 | Pi1 = mod289(Pi1); 67 | vec3 Pf0 = fract(P); // Fractional part for interpolation 68 | vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0 69 | vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x); 70 | vec4 iy = vec4(Pi0.yy, Pi1.yy); 71 | vec4 iz0 = Pi0.zzzz; 72 | vec4 iz1 = Pi1.zzzz; 73 | 74 | vec4 ixy = permute(permute(ix) + iy); 75 | vec4 ixy0 = permute(ixy + iz0); 76 | vec4 ixy1 = permute(ixy + iz1); 77 | 78 | vec4 gx0 = ixy0 * (1.0 / 7.0); 79 | vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5; 80 | gx0 = fract(gx0); 81 | vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0); 82 | vec4 sz0 = step(gz0, vec4(0.0)); 83 | gx0 -= sz0 * (step(0.0, gx0) - 0.5); 84 | gy0 -= sz0 * (step(0.0, gy0) - 0.5); 85 | 86 | vec4 gx1 = ixy1 * (1.0 / 7.0); 87 | vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5; 88 | gx1 = fract(gx1); 89 | vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1); 90 | vec4 sz1 = step(gz1, vec4(0.0)); 91 | gx1 -= sz1 * (step(0.0, gx1) - 0.5); 92 | gy1 -= sz1 * (step(0.0, gy1) - 0.5); 93 | 94 | vec3 g000 = vec3(gx0.x,gy0.x,gz0.x); 95 | vec3 g100 = vec3(gx0.y,gy0.y,gz0.y); 96 | vec3 g010 = vec3(gx0.z,gy0.z,gz0.z); 97 | vec3 g110 = vec3(gx0.w,gy0.w,gz0.w); 98 | vec3 g001 = vec3(gx1.x,gy1.x,gz1.x); 99 | vec3 g101 = vec3(gx1.y,gy1.y,gz1.y); 100 | vec3 g011 = vec3(gx1.z,gy1.z,gz1.z); 101 | vec3 g111 = vec3(gx1.w,gy1.w,gz1.w); 102 | 103 | vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110))); 104 | g000 *= norm0.x; 105 | g010 *= norm0.y; 106 | g100 *= norm0.z; 107 | g110 *= norm0.w; 108 | vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111))); 109 | g001 *= norm1.x; 110 | g011 *= norm1.y; 111 | g101 *= norm1.z; 112 | g111 *= norm1.w; 113 | 114 | float n000 = dot(g000, Pf0); 115 | float n100 = dot(g100, vec3(Pf1.x, Pf0.yz)); 116 | float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z)); 117 | float n110 = dot(g110, vec3(Pf1.xy, Pf0.z)); 118 | float n001 = dot(g001, vec3(Pf0.xy, Pf1.z)); 119 | float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z)); 120 | float n011 = dot(g011, vec3(Pf0.x, Pf1.yz)); 121 | float n111 = dot(g111, Pf1); 122 | 123 | vec3 fade_xyz = fade(Pf0); 124 | vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z); 125 | vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y); 126 | float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x); 127 | return 2.2 * n_xyz; 128 | } 129 | `; 130 | 131 | const PNOISE4D = /* glsl */ ` 132 | // https://github.com/ashima/webgl-noise 133 | ${FUNCTIONS} 134 | ${FADE4} 135 | 136 | // Classic Perlin noise, periodic version 137 | float pnoise4d(vec4 P, vec4 rep) 138 | { 139 | vec4 Pi0 = mod(floor(P), rep); // Integer part modulo rep 140 | vec4 Pi1 = mod(Pi0 + 1.0, rep); // Integer part + 1 mod rep 141 | Pi0 = mod289(Pi0); 142 | Pi1 = mod289(Pi1); 143 | vec4 Pf0 = fract(P); // Fractional part for interpolation 144 | vec4 Pf1 = Pf0 - 1.0; // Fractional part - 1.0 145 | vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x); 146 | vec4 iy = vec4(Pi0.yy, Pi1.yy); 147 | vec4 iz0 = vec4(Pi0.zzzz); 148 | vec4 iz1 = vec4(Pi1.zzzz); 149 | vec4 iw0 = vec4(Pi0.wwww); 150 | vec4 iw1 = vec4(Pi1.wwww); 151 | 152 | vec4 ixy = permute(permute(ix) + iy); 153 | vec4 ixy0 = permute(ixy + iz0); 154 | vec4 ixy1 = permute(ixy + iz1); 155 | vec4 ixy00 = permute(ixy0 + iw0); 156 | vec4 ixy01 = permute(ixy0 + iw1); 157 | vec4 ixy10 = permute(ixy1 + iw0); 158 | vec4 ixy11 = permute(ixy1 + iw1); 159 | 160 | vec4 gx00 = ixy00 * (1.0 / 7.0); 161 | vec4 gy00 = floor(gx00) * (1.0 / 7.0); 162 | vec4 gz00 = floor(gy00) * (1.0 / 6.0); 163 | gx00 = fract(gx00) - 0.5; 164 | gy00 = fract(gy00) - 0.5; 165 | gz00 = fract(gz00) - 0.5; 166 | vec4 gw00 = vec4(0.75) - abs(gx00) - abs(gy00) - abs(gz00); 167 | vec4 sw00 = step(gw00, vec4(0.0)); 168 | gx00 -= sw00 * (step(0.0, gx00) - 0.5); 169 | gy00 -= sw00 * (step(0.0, gy00) - 0.5); 170 | 171 | vec4 gx01 = ixy01 * (1.0 / 7.0); 172 | vec4 gy01 = floor(gx01) * (1.0 / 7.0); 173 | vec4 gz01 = floor(gy01) * (1.0 / 6.0); 174 | gx01 = fract(gx01) - 0.5; 175 | gy01 = fract(gy01) - 0.5; 176 | gz01 = fract(gz01) - 0.5; 177 | vec4 gw01 = vec4(0.75) - abs(gx01) - abs(gy01) - abs(gz01); 178 | vec4 sw01 = step(gw01, vec4(0.0)); 179 | gx01 -= sw01 * (step(0.0, gx01) - 0.5); 180 | gy01 -= sw01 * (step(0.0, gy01) - 0.5); 181 | 182 | vec4 gx10 = ixy10 * (1.0 / 7.0); 183 | vec4 gy10 = floor(gx10) * (1.0 / 7.0); 184 | vec4 gz10 = floor(gy10) * (1.0 / 6.0); 185 | gx10 = fract(gx10) - 0.5; 186 | gy10 = fract(gy10) - 0.5; 187 | gz10 = fract(gz10) - 0.5; 188 | vec4 gw10 = vec4(0.75) - abs(gx10) - abs(gy10) - abs(gz10); 189 | vec4 sw10 = step(gw10, vec4(0.0)); 190 | gx10 -= sw10 * (step(0.0, gx10) - 0.5); 191 | gy10 -= sw10 * (step(0.0, gy10) - 0.5); 192 | 193 | vec4 gx11 = ixy11 * (1.0 / 7.0); 194 | vec4 gy11 = floor(gx11) * (1.0 / 7.0); 195 | vec4 gz11 = floor(gy11) * (1.0 / 6.0); 196 | gx11 = fract(gx11) - 0.5; 197 | gy11 = fract(gy11) - 0.5; 198 | gz11 = fract(gz11) - 0.5; 199 | vec4 gw11 = vec4(0.75) - abs(gx11) - abs(gy11) - abs(gz11); 200 | vec4 sw11 = step(gw11, vec4(0.0)); 201 | gx11 -= sw11 * (step(0.0, gx11) - 0.5); 202 | gy11 -= sw11 * (step(0.0, gy11) - 0.5); 203 | 204 | vec4 g0000 = vec4(gx00.x,gy00.x,gz00.x,gw00.x); 205 | vec4 g1000 = vec4(gx00.y,gy00.y,gz00.y,gw00.y); 206 | vec4 g0100 = vec4(gx00.z,gy00.z,gz00.z,gw00.z); 207 | vec4 g1100 = vec4(gx00.w,gy00.w,gz00.w,gw00.w); 208 | vec4 g0010 = vec4(gx10.x,gy10.x,gz10.x,gw10.x); 209 | vec4 g1010 = vec4(gx10.y,gy10.y,gz10.y,gw10.y); 210 | vec4 g0110 = vec4(gx10.z,gy10.z,gz10.z,gw10.z); 211 | vec4 g1110 = vec4(gx10.w,gy10.w,gz10.w,gw10.w); 212 | vec4 g0001 = vec4(gx01.x,gy01.x,gz01.x,gw01.x); 213 | vec4 g1001 = vec4(gx01.y,gy01.y,gz01.y,gw01.y); 214 | vec4 g0101 = vec4(gx01.z,gy01.z,gz01.z,gw01.z); 215 | vec4 g1101 = vec4(gx01.w,gy01.w,gz01.w,gw01.w); 216 | vec4 g0011 = vec4(gx11.x,gy11.x,gz11.x,gw11.x); 217 | vec4 g1011 = vec4(gx11.y,gy11.y,gz11.y,gw11.y); 218 | vec4 g0111 = vec4(gx11.z,gy11.z,gz11.z,gw11.z); 219 | vec4 g1111 = vec4(gx11.w,gy11.w,gz11.w,gw11.w); 220 | 221 | vec4 norm00 = taylorInvSqrt(vec4(dot(g0000, g0000), dot(g0100, g0100), dot(g1000, g1000), dot(g1100, g1100))); 222 | g0000 *= norm00.x; 223 | g0100 *= norm00.y; 224 | g1000 *= norm00.z; 225 | g1100 *= norm00.w; 226 | 227 | vec4 norm01 = taylorInvSqrt(vec4(dot(g0001, g0001), dot(g0101, g0101), dot(g1001, g1001), dot(g1101, g1101))); 228 | g0001 *= norm01.x; 229 | g0101 *= norm01.y; 230 | g1001 *= norm01.z; 231 | g1101 *= norm01.w; 232 | 233 | vec4 norm10 = taylorInvSqrt(vec4(dot(g0010, g0010), dot(g0110, g0110), dot(g1010, g1010), dot(g1110, g1110))); 234 | g0010 *= norm10.x; 235 | g0110 *= norm10.y; 236 | g1010 *= norm10.z; 237 | g1110 *= norm10.w; 238 | 239 | vec4 norm11 = taylorInvSqrt(vec4(dot(g0011, g0011), dot(g0111, g0111), dot(g1011, g1011), dot(g1111, g1111))); 240 | g0011 *= norm11.x; 241 | g0111 *= norm11.y; 242 | g1011 *= norm11.z; 243 | g1111 *= norm11.w; 244 | 245 | float n0000 = dot(g0000, Pf0); 246 | float n1000 = dot(g1000, vec4(Pf1.x, Pf0.yzw)); 247 | float n0100 = dot(g0100, vec4(Pf0.x, Pf1.y, Pf0.zw)); 248 | float n1100 = dot(g1100, vec4(Pf1.xy, Pf0.zw)); 249 | float n0010 = dot(g0010, vec4(Pf0.xy, Pf1.z, Pf0.w)); 250 | float n1010 = dot(g1010, vec4(Pf1.x, Pf0.y, Pf1.z, Pf0.w)); 251 | float n0110 = dot(g0110, vec4(Pf0.x, Pf1.yz, Pf0.w)); 252 | float n1110 = dot(g1110, vec4(Pf1.xyz, Pf0.w)); 253 | float n0001 = dot(g0001, vec4(Pf0.xyz, Pf1.w)); 254 | float n1001 = dot(g1001, vec4(Pf1.x, Pf0.yz, Pf1.w)); 255 | float n0101 = dot(g0101, vec4(Pf0.x, Pf1.y, Pf0.z, Pf1.w)); 256 | float n1101 = dot(g1101, vec4(Pf1.xy, Pf0.z, Pf1.w)); 257 | float n0011 = dot(g0011, vec4(Pf0.xy, Pf1.zw)); 258 | float n1011 = dot(g1011, vec4(Pf1.x, Pf0.y, Pf1.zw)); 259 | float n0111 = dot(g0111, vec4(Pf0.x, Pf1.yzw)); 260 | float n1111 = dot(g1111, Pf1); 261 | 262 | vec4 fade_xyzw = fade(Pf0); 263 | vec4 n_0w = mix(vec4(n0000, n1000, n0100, n1100), vec4(n0001, n1001, n0101, n1101), fade_xyzw.w); 264 | vec4 n_1w = mix(vec4(n0010, n1010, n0110, n1110), vec4(n0011, n1011, n0111, n1111), fade_xyzw.w); 265 | vec4 n_zw = mix(n_0w, n_1w, fade_xyzw.z); 266 | vec2 n_yzw = mix(n_zw.xy, n_zw.zw, fade_xyzw.y); 267 | float n_xyzw = mix(n_yzw.x, n_yzw.y, fade_xyzw.x); 268 | return 2.2 * n_xyzw; 269 | } 270 | `; 271 | 272 | export default { 273 | PNOISE2D, 274 | PNOISE3D, 275 | PNOISE4D, 276 | }; 277 | -------------------------------------------------------------------------------- /src/shaders/noise/periodic.wgsl.ts: -------------------------------------------------------------------------------- 1 | import { 2 | FADE_2, 3 | FADE_3, 4 | FADE_4, 5 | MOD289_4, 6 | MOD289_3, 7 | PERMUTE_4, 8 | TAYLOR_INV_SQRT_4, 9 | } from "./utils.wgsl.js"; 10 | 11 | const PNOISE2D = /* wgsl */ ` 12 | // https://github.com/ashima/webgl-noise 13 | ${MOD289_4} 14 | ${PERMUTE_4} 15 | ${TAYLOR_INV_SQRT_4} 16 | ${FADE_2} 17 | 18 | fn pnoise2d(P: vec2, rep: vec2) -> f32 { 19 | var Pi = floor(P.xyxy) + vec4(0.0, 0.0, 1.0, 1.0); 20 | let Pf = fract(P.xyxy) - vec4(0.0, 0.0, 1.0, 1.0); 21 | Pi = Pi % rep.xyxy; // To create noise with explicit period 22 | Pi = mod289_4(Pi); // To avoid truncation effects in permutation 23 | let ix = Pi.xzxz; 24 | let iy = Pi.yyww; 25 | let fx = Pf.xzxz; 26 | let fy = Pf.yyww; 27 | 28 | let i = permute_4(permute_4(ix) + iy); 29 | 30 | var gx = fract(i * (1.0 / 41.0)) * 2.0 - 1.0 ; 31 | let gy = abs(gx) - 0.5 ; 32 | let tx = floor(gx + 0.5); 33 | gx = gx - tx; 34 | 35 | var g00 = vec2(gx.x,gy.x); 36 | var g10 = vec2(gx.y,gy.y); 37 | var g01 = vec2(gx.z,gy.z); 38 | var g11 = vec2(gx.w,gy.w); 39 | 40 | let norm = taylorInvSqrt_4(vec4(dot(g00, g00), dot(g01, g01), dot(g10, g10), dot(g11, g11))); 41 | g00 = g00 * norm.x; 42 | g01 = g01 * norm.y; 43 | g10 = g10 * norm.z; 44 | g11 = g11 * norm.w; 45 | 46 | let n00 = dot(g00, vec2(fx.x, fy.x)); 47 | let n10 = dot(g10, vec2(fx.y, fy.y)); 48 | let n01 = dot(g01, vec2(fx.z, fy.z)); 49 | let n11 = dot(g11, vec2(fx.w, fy.w)); 50 | 51 | let fade_xy = fade_2(Pf.xy); 52 | let n_x = mix(vec2(n00, n01), vec2(n10, n11), fade_xy.x); 53 | let n_xy = mix(n_x.x, n_x.y, fade_xy.y); 54 | return 2.3 * n_xy; 55 | } 56 | `; 57 | 58 | const PNOISE3D = /* wgsl */ ` 59 | // https://github.com/ashima/webgl-noise 60 | ${MOD289_3} 61 | ${MOD289_4} 62 | ${PERMUTE_4} 63 | ${TAYLOR_INV_SQRT_4} 64 | ${FADE_3} 65 | 66 | fn pnoise3d(P: vec3, rep: vec3)-> f32 { 67 | var Pi0 = floor(P) % rep; // Integer part, modulo period 68 | var Pi1 = (Pi0 + vec3(1.0)) % rep; // Integer part + 1, mod period 69 | Pi0 = mod289_3(Pi0); 70 | Pi1 = mod289_3(Pi1); 71 | let Pf0 = fract(P); // Fractional part for interpolation 72 | let Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0 73 | let ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x); 74 | let iy = vec4(Pi0.yy, Pi1.yy); 75 | let iz0 = Pi0.zzzz; 76 | let iz1 = Pi1.zzzz; 77 | 78 | let ixy = permute_4(permute_4(ix) + iy); 79 | let ixy0 = permute_4(ixy + iz0); 80 | let ixy1 = permute_4(ixy + iz1); 81 | 82 | var gx0 = ixy0 * (1.0 / 7.0); 83 | var gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5; 84 | gx0 = fract(gx0); 85 | var gz0 = vec4(0.5) - abs(gx0) - abs(gy0); 86 | var sz0 = step(gz0, vec4(0.0)); 87 | gx0 = gx0 - (sz0 * (step(vec4(0.0), gx0) - 0.5)); 88 | gy0 = gy0 - (sz0 * (step(vec4(0.0), gy0) - 0.5)); 89 | 90 | var gx1 = ixy1 * (1.0 / 7.0); 91 | var gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5; 92 | gx1 = fract(gx1); 93 | var gz1 = vec4(0.5) - abs(gx1) - abs(gy1); 94 | var sz1 = step(gz1, vec4(0.0)); 95 | gx1 = gx1 - (sz1 * (step(vec4(0.0), gx1) - 0.5)); 96 | gy1 = gy1 - (sz1 * (step(vec4(0.0), gy1) - 0.5)); 97 | 98 | var g000 = vec3(gx0.x,gy0.x,gz0.x); 99 | var g100 = vec3(gx0.y,gy0.y,gz0.y); 100 | var g010 = vec3(gx0.z,gy0.z,gz0.z); 101 | var g110 = vec3(gx0.w,gy0.w,gz0.w); 102 | var g001 = vec3(gx1.x,gy1.x,gz1.x); 103 | var g101 = vec3(gx1.y,gy1.y,gz1.y); 104 | var g011 = vec3(gx1.z,gy1.z,gz1.z); 105 | var g111 = vec3(gx1.w,gy1.w,gz1.w); 106 | 107 | var norm0 = taylorInvSqrt_4(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110))); 108 | g000 = g000 * norm0.x; 109 | g010 = g010 * norm0.y; 110 | g100 = g100 * norm0.z; 111 | g110 = g110 * norm0.w; 112 | var norm1 = taylorInvSqrt_4(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111))); 113 | g001 = g001 * norm1.x; 114 | g011 = g011 * norm1.y; 115 | g101 = g101 * norm1.z; 116 | g111 = g111 * norm1.w; 117 | 118 | let n000 = dot(g000, Pf0); 119 | let n100 = dot(g100, vec3(Pf1.x, Pf0.yz)); 120 | let n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z)); 121 | let n110 = dot(g110, vec3(Pf1.xy, Pf0.z)); 122 | let n001 = dot(g001, vec3(Pf0.xy, Pf1.z)); 123 | let n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z)); 124 | let n011 = dot(g011, vec3(Pf0.x, Pf1.yz)); 125 | let n111 = dot(g111, Pf1); 126 | 127 | let fade_xyz = fade_3(Pf0); 128 | let n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z); 129 | let n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y); 130 | let n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x); 131 | return 2.2 * n_xyz; 132 | } 133 | `; 134 | 135 | const PNOISE4D = /* wgsl */ ` 136 | // https://github.com/ashima/webgl-noise 137 | ${MOD289_4} 138 | ${PERMUTE_4} 139 | ${TAYLOR_INV_SQRT_4} 140 | ${FADE_4} 141 | 142 | fn pnoise4d(P: vec4, rep: vec4) -> f32 { 143 | var Pi0 = floor(P) % rep; // Integer part modulo rep 144 | var Pi1 = (Pi0 + 1.0) % rep; // Integer part + 1 mod rep 145 | Pi0 = mod289_4(Pi0); 146 | Pi1 = mod289_4(Pi1); 147 | let Pf0 = fract(P); // Fractional part for interpolation 148 | let Pf1 = Pf0 - 1.0; // Fractional part - 1.0 149 | let ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x); 150 | let iy = vec4(Pi0.yy, Pi1.yy); 151 | let iz0 = vec4(Pi0.zzzz); 152 | let iz1 = vec4(Pi1.zzzz); 153 | let iw0 = vec4(Pi0.wwww); 154 | let iw1 = vec4(Pi1.wwww); 155 | 156 | let ixy = permute_4(permute_4(ix) + iy); 157 | let ixy0 = permute_4(ixy + iz0); 158 | let ixy1 = permute_4(ixy + iz1); 159 | let ixy00 = permute_4(ixy0 + iw0); 160 | let ixy01 = permute_4(ixy0 + iw1); 161 | let ixy10 = permute_4(ixy1 + iw0); 162 | let ixy11 = permute_4(ixy1 + iw1); 163 | 164 | var gx00 = ixy00 * (1.0 / 7.0); 165 | var gy00 = floor(gx00) * (1.0 / 7.0); 166 | var gz00 = floor(gy00) * (1.0 / 6.0); 167 | gx00 = fract(gx00) - 0.5; 168 | gy00 = fract(gy00) - 0.5; 169 | gz00 = fract(gz00) - 0.5; 170 | var gw00 = vec4(0.75) - abs(gx00) - abs(gy00) - abs(gz00); 171 | var sw00 = step(gw00, vec4(0.0)); 172 | gx00 = gx00 - (sw00 * (step(vec4(0.0), gx00) - 0.5)); 173 | gy00 = gy00 - (sw00 * (step(vec4(0.0), gy00) - 0.5)); 174 | 175 | var gx01 = ixy01 * (1.0 / 7.0); 176 | var gy01 = floor(gx01) * (1.0 / 7.0); 177 | var gz01 = floor(gy01) * (1.0 / 6.0); 178 | gx01 = fract(gx01) - 0.5; 179 | gy01 = fract(gy01) - 0.5; 180 | gz01 = fract(gz01) - 0.5; 181 | var gw01 = vec4(0.75) - abs(gx01) - abs(gy01) - abs(gz01); 182 | var sw01 = step(gw01, vec4(0.0)); 183 | gx01 = gx01 - (sw01 * (step(vec4(0.0), gx01) - 0.5)); 184 | gy01 = gy01 - (sw01 * (step(vec4(0.0), gy01) - 0.5)); 185 | 186 | var gx10 = ixy10 * (1.0 / 7.0); 187 | var gy10 = floor(gx10) * (1.0 / 7.0); 188 | var gz10 = floor(gy10) * (1.0 / 6.0); 189 | gx10 = fract(gx10) - 0.5; 190 | gy10 = fract(gy10) - 0.5; 191 | gz10 = fract(gz10) - 0.5; 192 | var gw10 = vec4(0.75) - abs(gx10) - abs(gy10) - abs(gz10); 193 | var sw10 = step(gw10, vec4(0.0)); 194 | gx10 = gx10 - (sw10 * (step(vec4(0.0), gx10) - 0.5)); 195 | gy10 = gy10 - (sw10 * (step(vec4(0.0), gy10) - 0.5)); 196 | 197 | var gx11 = ixy11 * (1.0 / 7.0); 198 | var gy11 = floor(gx11) * (1.0 / 7.0); 199 | var gz11 = floor(gy11) * (1.0 / 6.0); 200 | gx11 = fract(gx11) - 0.5; 201 | gy11 = fract(gy11) - 0.5; 202 | gz11 = fract(gz11) - 0.5; 203 | var gw11 = vec4(0.75) - abs(gx11) - abs(gy11) - abs(gz11); 204 | var sw11 = step(gw11, vec4(0.0)); 205 | gx11 = gx11 - (sw11 * (step(vec4(0.0), gx11) - 0.5)); 206 | gy11 = gy11 - (sw11 * (step(vec4(0.0), gy11) - 0.5)); 207 | 208 | var g0000 = vec4(gx00.x,gy00.x,gz00.x,gw00.x); 209 | var g1000 = vec4(gx00.y,gy00.y,gz00.y,gw00.y); 210 | var g0100 = vec4(gx00.z,gy00.z,gz00.z,gw00.z); 211 | var g1100 = vec4(gx00.w,gy00.w,gz00.w,gw00.w); 212 | var g0010 = vec4(gx10.x,gy10.x,gz10.x,gw10.x); 213 | var g1010 = vec4(gx10.y,gy10.y,gz10.y,gw10.y); 214 | var g0110 = vec4(gx10.z,gy10.z,gz10.z,gw10.z); 215 | var g1110 = vec4(gx10.w,gy10.w,gz10.w,gw10.w); 216 | var g0001 = vec4(gx01.x,gy01.x,gz01.x,gw01.x); 217 | var g1001 = vec4(gx01.y,gy01.y,gz01.y,gw01.y); 218 | var g0101 = vec4(gx01.z,gy01.z,gz01.z,gw01.z); 219 | var g1101 = vec4(gx01.w,gy01.w,gz01.w,gw01.w); 220 | var g0011 = vec4(gx11.x,gy11.x,gz11.x,gw11.x); 221 | var g1011 = vec4(gx11.y,gy11.y,gz11.y,gw11.y); 222 | var g0111 = vec4(gx11.z,gy11.z,gz11.z,gw11.z); 223 | var g1111 = vec4(gx11.w,gy11.w,gz11.w,gw11.w); 224 | 225 | var norm00 = taylorInvSqrt_4(vec4(dot(g0000, g0000), dot(g0100, g0100), dot(g1000, g1000), dot(g1100, g1100))); 226 | g0000 = g0000 * norm00.x; 227 | g0100 = g0100 * norm00.y; 228 | g1000 = g1000 * norm00.z; 229 | g1100 = g1100 * norm00.w; 230 | 231 | var norm01 = taylorInvSqrt_4(vec4(dot(g0001, g0001), dot(g0101, g0101), dot(g1001, g1001), dot(g1101, g1101))); 232 | g0001 = g0001 * norm01.x; 233 | g0101 = g0101 * norm01.y; 234 | g1001 = g1001 * norm01.z; 235 | g1101 = g1101 * norm01.w; 236 | 237 | var norm10 = taylorInvSqrt_4(vec4(dot(g0010, g0010), dot(g0110, g0110), dot(g1010, g1010), dot(g1110, g1110))); 238 | g0010 = g0010 * norm10.x; 239 | g0110 = g0110 * norm10.y; 240 | g1010 = g1010 * norm10.z; 241 | g1110 = g1110 * norm10.w; 242 | 243 | var norm11 = taylorInvSqrt_4(vec4(dot(g0011, g0011), dot(g0111, g0111), dot(g1011, g1011), dot(g1111, g1111))); 244 | g0011 = g0011 * norm11.x; 245 | g0111 = g0111 * norm11.y; 246 | g1011 = g1011 * norm11.z; 247 | g1111 = g1111 * norm11.w; 248 | 249 | let n0000 = dot(g0000, Pf0); 250 | let n1000 = dot(g1000, vec4(Pf1.x, Pf0.yzw)); 251 | let n0100 = dot(g0100, vec4(Pf0.x, Pf1.y, Pf0.zw)); 252 | let n1100 = dot(g1100, vec4(Pf1.xy, Pf0.zw)); 253 | let n0010 = dot(g0010, vec4(Pf0.xy, Pf1.z, Pf0.w)); 254 | let n1010 = dot(g1010, vec4(Pf1.x, Pf0.y, Pf1.z, Pf0.w)); 255 | let n0110 = dot(g0110, vec4(Pf0.x, Pf1.yz, Pf0.w)); 256 | let n1110 = dot(g1110, vec4(Pf1.xyz, Pf0.w)); 257 | let n0001 = dot(g0001, vec4(Pf0.xyz, Pf1.w)); 258 | let n1001 = dot(g1001, vec4(Pf1.x, Pf0.yz, Pf1.w)); 259 | let n0101 = dot(g0101, vec4(Pf0.x, Pf1.y, Pf0.z, Pf1.w)); 260 | let n1101 = dot(g1101, vec4(Pf1.xy, Pf0.z, Pf1.w)); 261 | let n0011 = dot(g0011, vec4(Pf0.xy, Pf1.zw)); 262 | let n1011 = dot(g1011, vec4(Pf1.x, Pf0.y, Pf1.zw)); 263 | let n0111 = dot(g0111, vec4(Pf0.x, Pf1.yzw)); 264 | let n1111 = dot(g1111, Pf1); 265 | 266 | let fade_xyzw = fade_4(Pf0); 267 | let n_0w = mix(vec4(n0000, n1000, n0100, n1100), vec4(n0001, n1001, n0101, n1101), fade_xyzw.w); 268 | let n_1w = mix(vec4(n0010, n1010, n0110, n1110), vec4(n0011, n1011, n0111, n1111), fade_xyzw.w); 269 | let n_zw = mix(n_0w, n_1w, fade_xyzw.z); 270 | let n_yzw = mix(n_zw.xy, n_zw.zw, fade_xyzw.y); 271 | let n_xyzw = mix(n_yzw.x, n_yzw.y, fade_xyzw.x); 272 | return 2.2 * n_xyzw; 273 | } 274 | `; 275 | 276 | export default { 277 | PNOISE2D, 278 | PNOISE3D, 279 | PNOISE4D, 280 | }; 281 | -------------------------------------------------------------------------------- /src/shaders/noise/simplex.glsl.ts: -------------------------------------------------------------------------------- 1 | import { FUNCTIONS } from "./utils.glsl.js"; 2 | 3 | const SNOISE2D = /* glsl */ ` 4 | // https://github.com/ashima/webgl-noise 5 | vec3 mod289(vec3 x) { 6 | return x - floor(x * (1.0 / 289.0)) * 289.0; 7 | } 8 | 9 | vec2 mod289(vec2 x) { 10 | return x - floor(x * (1.0 / 289.0)) * 289.0; 11 | } 12 | 13 | vec3 permute(vec3 x) { 14 | return mod289(((x*34.0)+1.0)*x); 15 | } 16 | 17 | float snoise2d(vec2 v) 18 | { 19 | const vec4 C = vec4(0.211324865405187, // (3.0-sqrt(3.0))/6.0 20 | 0.366025403784439, // 0.5*(sqrt(3.0)-1.0) 21 | -0.577350269189626, // -1.0 + 2.0 * C.x 22 | 0.024390243902439); // 1.0 / 41.0 23 | // First corner 24 | vec2 i = floor(v + dot(v, C.yy) ); 25 | vec2 x0 = v - i + dot(i, C.xx); 26 | 27 | // Other corners 28 | vec2 i1; 29 | //i1.x = step( x0.y, x0.x ); // x0.x > x0.y ? 1.0 : 0.0 30 | //i1.y = 1.0 - i1.x; 31 | i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); 32 | // x0 = x0 - 0.0 + 0.0 * C.xx ; 33 | // x1 = x0 - i1 + 1.0 * C.xx ; 34 | // x2 = x0 - 1.0 + 2.0 * C.xx ; 35 | vec4 x12 = x0.xyxy + C.xxzz; 36 | x12.xy -= i1; 37 | 38 | // Permutations 39 | i = mod289(i); // Avoid truncation effects in permutation 40 | vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 )) 41 | + i.x + vec3(0.0, i1.x, 1.0 )); 42 | 43 | vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0); 44 | m = m*m ; 45 | m = m*m ; 46 | 47 | // Gradients: 41 points uniformly over a line, mapped onto a diamond. 48 | // The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287) 49 | 50 | vec3 x = 2.0 * fract(p * C.www) - 1.0; 51 | vec3 h = abs(x) - 0.5; 52 | vec3 ox = floor(x + 0.5); 53 | vec3 a0 = x - ox; 54 | 55 | // Normalise gradients implicitly by scaling m 56 | // Approximation of: m *= inversesqrt( a0*a0 + h*h ); 57 | m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h ); 58 | 59 | // Compute final noise value at P 60 | vec3 g; 61 | g.x = a0.x * x0.x + h.x * x0.y; 62 | g.yz = a0.yz * x12.xz + h.yz * x12.yw; 63 | return 130.0 * dot(m, g); 64 | } 65 | `; 66 | 67 | const SNOISE3D = /* glsl */ ` 68 | // https://github.com/ashima/webgl-noise 69 | vec3 mod289(vec3 x) 70 | { 71 | return x - floor(x * (1.0 / 289.0)) * 289.0; 72 | } 73 | 74 | ${FUNCTIONS} 75 | 76 | float snoise3d(vec3 v) 77 | { 78 | const vec2 C = vec2(1.0/6.0, 1.0/3.0) ; 79 | const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); 80 | 81 | // First corner 82 | vec3 i = floor(v + dot(v, C.yyy) ); 83 | vec3 x0 = v - i + dot(i, C.xxx) ; 84 | 85 | // Other corners 86 | vec3 g = step(x0.yzx, x0.xyz); 87 | vec3 l = 1.0 - g; 88 | vec3 i1 = min( g.xyz, l.zxy ); 89 | vec3 i2 = max( g.xyz, l.zxy ); 90 | 91 | // x0 = x0 - 0.0 + 0.0 * C.xxx; 92 | // x1 = x0 - i1 + 1.0 * C.xxx; 93 | // x2 = x0 - i2 + 2.0 * C.xxx; 94 | // x3 = x0 - 1.0 + 3.0 * C.xxx; 95 | vec3 x1 = x0 - i1 + C.xxx; 96 | vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y 97 | vec3 x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y 98 | 99 | // Permutations 100 | i = mod289(i); 101 | vec4 p = permute( permute( permute( 102 | i.z + vec4(0.0, i1.z, i2.z, 1.0 )) 103 | + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) 104 | + i.x + vec4(0.0, i1.x, i2.x, 1.0 )); 105 | 106 | // Gradients: 7x7 points over a square, mapped onto an octahedron. 107 | // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294) 108 | float n_ = 0.142857142857; // 1.0/7.0 109 | vec3 ns = n_ * D.wyz - D.xzx; 110 | 111 | vec4 j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p,7*7) 112 | 113 | vec4 x_ = floor(j * ns.z); 114 | vec4 y_ = floor(j - 7.0 * x_ ); // mod(j,N) 115 | 116 | vec4 x = x_ *ns.x + ns.yyyy; 117 | vec4 y = y_ *ns.x + ns.yyyy; 118 | vec4 h = 1.0 - abs(x) - abs(y); 119 | 120 | vec4 b0 = vec4( x.xy, y.xy ); 121 | vec4 b1 = vec4( x.zw, y.zw ); 122 | 123 | //vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0; 124 | //vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0; 125 | vec4 s0 = floor(b0)*2.0 + 1.0; 126 | vec4 s1 = floor(b1)*2.0 + 1.0; 127 | vec4 sh = -step(h, vec4(0.0)); 128 | 129 | vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ; 130 | vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ; 131 | 132 | vec3 p0 = vec3(a0.xy,h.x); 133 | vec3 p1 = vec3(a0.zw,h.y); 134 | vec3 p2 = vec3(a1.xy,h.z); 135 | vec3 p3 = vec3(a1.zw,h.w); 136 | 137 | //Normalise gradients 138 | vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3))); 139 | p0 *= norm.x; 140 | p1 *= norm.y; 141 | p2 *= norm.z; 142 | p3 *= norm.w; 143 | 144 | // Mix final noise value 145 | vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0); 146 | m = m * m; 147 | return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), 148 | dot(p2,x2), dot(p3,x3) ) ); 149 | } 150 | `; 151 | 152 | const SNOISE4D = /* glsl */ ` 153 | // https://github.com/ashima/webgl-noise 154 | ${FUNCTIONS} 155 | 156 | float mod289(float x) { 157 | return x - floor(x * (1.0 / 289.0)) * 289.0; 158 | } 159 | float permute(float x) { 160 | return mod289(((x*34.0)+1.0)*x); 161 | } 162 | float taylorInvSqrt(float r) { 163 | return 1.79284291400159 - 0.85373472095314 * r; 164 | } 165 | vec4 grad4(float j, vec4 ip) 166 | { 167 | const vec4 ones = vec4(1.0, 1.0, 1.0, -1.0); 168 | vec4 p,s; 169 | 170 | p.xyz = floor( fract (vec3(j) * ip.xyz) * 7.0) * ip.z - 1.0; 171 | p.w = 1.5 - dot(abs(p.xyz), ones.xyz); 172 | s = vec4(lessThan(p, vec4(0.0))); 173 | p.xyz = p.xyz + (s.xyz*2.0 - 1.0) * s.www; 174 | 175 | return p; 176 | } 177 | 178 | // (sqrt(5) - 1)/4 = F4, used once below 179 | #define F4 0.309016994374947451 180 | 181 | float snoise4d(vec4 v) 182 | { 183 | const vec4 C = vec4( 0.138196601125011, // (5 - sqrt(5))/20 G4 184 | 0.276393202250021, // 2 * G4 185 | 0.414589803375032, // 3 * G4 186 | -0.447213595499958); // -1 + 4 * G4 187 | 188 | // First corner 189 | vec4 i = floor(v + dot(v, vec4(F4)) ); 190 | vec4 x0 = v - i + dot(i, C.xxxx); 191 | 192 | // Other corners 193 | 194 | // Rank sorting originally contributed by Bill Licea-Kane, AMD (formerly ATI) 195 | vec4 i0; 196 | vec3 isX = step( x0.yzw, x0.xxx ); 197 | vec3 isYZ = step( x0.zww, x0.yyz ); 198 | // i0.x = dot( isX, vec3( 1.0 ) ); 199 | i0.x = isX.x + isX.y + isX.z; 200 | i0.yzw = 1.0 - isX; 201 | // i0.y += dot( isYZ.xy, vec2( 1.0 ) ); 202 | i0.y += isYZ.x + isYZ.y; 203 | i0.zw += 1.0 - isYZ.xy; 204 | i0.z += isYZ.z; 205 | i0.w += 1.0 - isYZ.z; 206 | 207 | // i0 now contains the unique values 0,1,2,3 in each channel 208 | vec4 i3 = clamp( i0, 0.0, 1.0 ); 209 | vec4 i2 = clamp( i0-1.0, 0.0, 1.0 ); 210 | vec4 i1 = clamp( i0-2.0, 0.0, 1.0 ); 211 | 212 | // x0 = x0 - 0.0 + 0.0 * C.xxxx 213 | // x1 = x0 - i1 + 1.0 * C.xxxx 214 | // x2 = x0 - i2 + 2.0 * C.xxxx 215 | // x3 = x0 - i3 + 3.0 * C.xxxx 216 | // x4 = x0 - 1.0 + 4.0 * C.xxxx 217 | vec4 x1 = x0 - i1 + C.xxxx; 218 | vec4 x2 = x0 - i2 + C.yyyy; 219 | vec4 x3 = x0 - i3 + C.zzzz; 220 | vec4 x4 = x0 + C.wwww; 221 | 222 | // Permutations 223 | i = mod289(i); 224 | float j0 = permute( permute( permute( permute(i.w) + i.z) + i.y) + i.x); 225 | vec4 j1 = permute( permute( permute( permute ( 226 | i.w + vec4(i1.w, i2.w, i3.w, 1.0 )) 227 | + i.z + vec4(i1.z, i2.z, i3.z, 1.0 )) 228 | + i.y + vec4(i1.y, i2.y, i3.y, 1.0 )) 229 | + i.x + vec4(i1.x, i2.x, i3.x, 1.0 )); 230 | 231 | // Gradients: 7x7x6 points over a cube, mapped onto a 4-cross polytope 232 | // 7*7*6 = 294, which is close to the ring size 17*17 = 289. 233 | vec4 ip = vec4(1.0/294.0, 1.0/49.0, 1.0/7.0, 0.0) ; 234 | 235 | vec4 p0 = grad4(j0, ip); 236 | vec4 p1 = grad4(j1.x, ip); 237 | vec4 p2 = grad4(j1.y, ip); 238 | vec4 p3 = grad4(j1.z, ip); 239 | vec4 p4 = grad4(j1.w, ip); 240 | 241 | // Normalise gradients 242 | vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3))); 243 | p0 *= norm.x; 244 | p1 *= norm.y; 245 | p2 *= norm.z; 246 | p3 *= norm.w; 247 | p4 *= taylorInvSqrt(dot(p4,p4)); 248 | 249 | // Mix contributions from the five corners 250 | vec3 m0 = max(0.6 - vec3(dot(x0,x0), dot(x1,x1), dot(x2,x2)), 0.0); 251 | vec2 m1 = max(0.6 - vec2(dot(x3,x3), dot(x4,x4) ), 0.0); 252 | m0 = m0 * m0; 253 | m1 = m1 * m1; 254 | return 49.0 * ( dot(m0*m0, vec3( dot( p0, x0 ), dot( p1, x1 ), dot( p2, x2 ))) 255 | + dot(m1*m1, vec2( dot( p3, x3 ), dot( p4, x4 ) ) ) ) ; 256 | 257 | } 258 | `; 259 | 260 | export default { 261 | SNOISE2D, 262 | SNOISE3D, 263 | SNOISE4D, 264 | }; 265 | -------------------------------------------------------------------------------- /src/shaders/noise/simplex.wgsl.ts: -------------------------------------------------------------------------------- 1 | import { 2 | MOD289_1, 3 | MOD289_2, 4 | MOD289_3, 5 | MOD289_4, 6 | PERMUTE_1, 7 | PERMUTE_3, 8 | PERMUTE_4, 9 | TAYLOR_INV_SQRT_1, 10 | TAYLOR_INV_SQRT_4, 11 | } from "./utils.wgsl.js"; 12 | 13 | const SNOISE2D = /* wgsl */ ` 14 | // https://github.com/ashima/webgl-noise 15 | ${MOD289_2} 16 | ${MOD289_3} 17 | ${PERMUTE_3} 18 | 19 | fn snoise2d(v: vec2) -> f32 { 20 | let C = vec4( 21 | 0.211324865405187, // (3.0-sqrt(3.0))/6.0 22 | 0.366025403784439, // 0.5*(sqrt(3.0)-1.0) 23 | -0.577350269189626, // -1.0 + 2.0 * C.x 24 | 0.024390243902439 // 1.0 / 41.0 25 | ); 26 | 27 | // First corner 28 | var i = floor(v + dot(v, C.yy)); 29 | let x0 = v - i + dot(i, C.xx); 30 | 31 | // Other corners 32 | var i1: vec2; 33 | //i1.x = step( x0.y, x0.x ); // x0.x > x0.y ? 1.0 : 0.0 34 | //i1.y = 1.0 - i1.x; 35 | if (x0.x > x0.y) { 36 | i1 = vec2(1.0, 0.0); 37 | } else { 38 | i1 = vec2(0.0, 1.0); 39 | } 40 | // i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); 41 | // x0 = x0 - 0.0 + 0.0 * C.xx ; 42 | // x1 = x0 - i1 + 1.0 * C.xx ; 43 | // x2 = x0 - 1.0 + 2.0 * C.xx ; 44 | var x12 = x0.xyxy + C.xxzz; 45 | // x12.x = x12.x - i1.x; 46 | // x12.y = x12.y - i1.y; 47 | x12 = vec4(x12.x - i1.x, x12.y - i1.y, x12.z, x12.w); 48 | 49 | // Permutations 50 | i = mod289_2(i); // Avoid truncation effects in permutation 51 | let p = permute(permute(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0)); 52 | 53 | var m = max(vec3(0.5) - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), vec3(0.0)); 54 | m = m * m; 55 | m = m * m; 56 | 57 | // Gradients: 41 points uniformly over a line, mapped onto a diamond. 58 | // The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287) 59 | let x = 2.0 * fract(p * C.www) - 1.0; 60 | let h = abs(x) - 0.5; 61 | let ox = floor(x + 0.5); 62 | let a0 = x - ox; 63 | 64 | // Normalise gradients implicitly by scaling m 65 | // Approximation of: m *= inversesqrt( a0*a0 + h*h ); 66 | m = m * (1.79284291400159 - 0.85373472095314 * (a0*a0 + h * h)); 67 | 68 | // Compute final noise value at P 69 | var g: vec3; 70 | g.x = a0.x * x0.x + h.x * x0.y; 71 | let gyz = a0.yz * x12.xz + h.yz * x12.yw; 72 | g = vec3(g.x, gyz.x, gyz.y); 73 | return 130.0 * dot(m, g); 74 | } 75 | `; 76 | 77 | const SNOISE3D = /* wgsl */ ` 78 | // https://github.com/ashima/webgl-noise 79 | ${MOD289_3} 80 | ${MOD289_4} 81 | ${PERMUTE_4} 82 | ${TAYLOR_INV_SQRT_4} 83 | 84 | fn snoise3d(v: vec3) -> f32 { 85 | let C = vec2(1.0 / 6.0, 1.0 / 3.0) ; 86 | let D = vec4(0.0, 0.5, 1.0, 2.0); 87 | 88 | // First corner 89 | var i = floor(v + dot(v, C.yyy)); 90 | let x0 = v - i + dot(i, C.xxx); 91 | 92 | // Other corners 93 | let g = step(x0.yzx, x0.xyz); 94 | let l = 1.0 - g; 95 | let i1 = min( g.xyz, l.zxy ); 96 | let i2 = max( g.xyz, l.zxy ); 97 | 98 | // x0 = x0 - 0.0 + 0.0 * C.xxx; 99 | // x1 = x0 - i1 + 1.0 * C.xxx; 100 | // x2 = x0 - i2 + 2.0 * C.xxx; 101 | // x3 = x0 - 1.0 + 3.0 * C.xxx; 102 | let x1 = x0 - i1 + C.xxx; 103 | let x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y 104 | let x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y 105 | 106 | // Permutations 107 | i = mod289_3(i); 108 | let p = permute_4( 109 | permute_4( 110 | permute_4(i.z + vec4(0.0, i1.z, i2.z, 1.0)) + i.y + vec4(0.0, i1.y, i2.y, 1.0) 111 | ) + i.x + vec4(0.0, i1.x, i2.x, 1.0) 112 | ); 113 | 114 | // Gradients: 7x7 points over a square, mapped onto an octahedron. 115 | // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294) 116 | let n_ = 0.142857142857; // 1.0/7.0 117 | let ns = n_ * D.wyz - D.xzx; 118 | 119 | let j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p, 7 * 7) 120 | 121 | let x_ = floor(j * ns.z); 122 | let y_ = floor(j - 7.0 * x_); // mod(j, N) 123 | 124 | let x = x_ * ns.x + ns.yyyy; 125 | let y = y_ * ns.x + ns.yyyy; 126 | let h = 1.0 - abs(x) - abs(y); 127 | 128 | let b0 = vec4(x.xy, y.xy); 129 | let b1 = vec4(x.zw, y.zw); 130 | 131 | // let s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0; 132 | // let s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0; 133 | let s0 = floor(b0) * 2.0 + 1.0; 134 | let s1 = floor(b1) * 2.0 + 1.0; 135 | let sh = -step(h, vec4(0.0)); 136 | 137 | let a0 = b0.xzyw + s0.xzyw*sh.xxyy; 138 | let a1 = b1.xzyw + s1.xzyw*sh.zzww; 139 | 140 | var p0 = vec3(a0.xy, h.x); 141 | var p1 = vec3(a0.zw, h.y); 142 | var p2 = vec3(a1.xy, h.z); 143 | var p3 = vec3(a1.zw, h.w); 144 | 145 | // Normalise gradients 146 | let norm = taylorInvSqrt_4(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3))); 147 | p0 = p0 * norm.x; 148 | p1 = p1 * norm.y; 149 | p2 = p2 * norm.z; 150 | p3 = p3 * norm.w; 151 | 152 | // Mix final noise value 153 | var m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), vec4(0.0)); 154 | m = m * m; 155 | 156 | return 42.0 * dot( 157 | m * m, 158 | vec4(dot(p0, x0), dot(p1,x1), dot(p2,x2), dot(p3,x3)) 159 | ); 160 | }`; 161 | 162 | const SNOISE4D = /* wgsl */ ` 163 | // https://github.com/ashima/webgl-noise 164 | ${MOD289_1} 165 | ${MOD289_4} 166 | ${PERMUTE_1} 167 | ${PERMUTE_4} 168 | ${TAYLOR_INV_SQRT_1} 169 | ${TAYLOR_INV_SQRT_4} 170 | 171 | fn grad4(j: f32, ip: vec4) -> vec4 { 172 | let ones = vec4(1.0, 1.0, 1.0, -1.0); 173 | var p: vec4; 174 | var s: vec4; 175 | 176 | var pxyz = floor(fract(vec3(j) * ip.xyz) * 7.0) * ip.z - 1.0; 177 | p = vec4(pxyz.x, pxyz.y, pxyz.z, 1.5 - dot(abs(pxyz), ones.xyz)); 178 | s = vec4(f32(p.x < 0.0), f32(p.y < 0.0), f32(p.z < 0.0), f32(p.w < 0.0)); 179 | pxyz = p.xyz + (s.xyz * 2.0 - 1.0) * s.www; 180 | 181 | return vec4(pxyz.x, pxyz.y, pxyz.z, p.w); 182 | } 183 | 184 | // (sqrt(5) - 1)/4 = F4, used once below 185 | let F4 = 0.309016994374947451; 186 | 187 | fn snoise(v: vec4) -> f32 { 188 | let C = vec4( 189 | 0.138196601125011, // (5 - sqrt(5))/20 G4 190 | 0.276393202250021, // 2 * G4 191 | 0.414589803375032, // 3 * G4 192 | -0.447213595499958 // -1 + 4 * G4 193 | ); 194 | 195 | // First corner 196 | var i = floor(v + dot(v, vec4(F4))); 197 | let x0 = v - i + dot(i, C.xxxx); 198 | 199 | // Other corners 200 | // Rank sorting originally contributed by Bill Licea-Kane, AMD (formerly ATI) 201 | var i0: vec4; 202 | let isX = step(x0.yzw, x0.xxx); 203 | let isYZ = step(x0.zww, x0.yyz); 204 | // i0.x = dot(isX, vec3(1.0)); 205 | i0.x = isX.x + isX.y + isX.z; 206 | 207 | i0.y = 1.0 - isX.x; 208 | i0.z = 1.0 - isX.y; 209 | i0.w = 1.0 - isX.z; 210 | 211 | // i0.y +=dot(isYZ.xy, vec2( 1.0 ) ); 212 | i0.y = i0.y + (isYZ.x + isYZ.y); 213 | 214 | i0.z = i0.z + (1.0 - isYZ.x); 215 | i0.w = i0.w + (1.0 - isYZ.y); 216 | 217 | i0.z = i0.z + (isYZ.z); 218 | i0.w = i0.w + (1.0 - isYZ.z); 219 | 220 | // i0 now contains the unique values 0,1,2,3 in each channel 221 | let i3 = clamp(i0, vec4(0.0), vec4(1.0)); 222 | let i2 = clamp(i0 - 1.0, vec4(0.0), vec4(1.0)); 223 | let i1 = clamp(i0 - 2.0, vec4(0.0), vec4(1.0)); 224 | 225 | // x0 = x0 - 0.0 + 0.0 * C.xxxx 226 | // x1 = x0 - i1 + 1.0 * C.xxxx 227 | // x2 = x0 - i2 + 2.0 * C.xxxx 228 | // x3 = x0 - i3 + 3.0 * C.xxxx 229 | // x4 = x0 - 1.0 + 4.0 * C.xxxx 230 | let x1 = x0 - i1 + C.xxxx; 231 | let x2 = x0 - i2 + C.yyyy; 232 | let x3 = x0 - i3 + C.zzzz; 233 | let x4 = x0 + C.wwww; 234 | 235 | // Permutations 236 | i = mod289_4(i); 237 | let j0 = permute_1(permute_1(permute_1(permute_1(i.w) + i.z) + i.y) + i.x); 238 | let j1 = permute_4( 239 | permute_4( 240 | permute_4( 241 | permute_4(i.w + vec4(i1.w, i2.w, i3.w, 1.0)) 242 | + i.z + vec4(i1.z, i2.z, i3.z, 1.0) 243 | ) + i.y + vec4(i1.y, i2.y, i3.y, 1.0) 244 | ) + i.x + vec4(i1.x, i2.x, i3.x, 1.0) 245 | ); 246 | 247 | // Gradients: 7x7x6 points over a cube, mapped onto a 4-cross polytope 248 | // 7*7*6 = 294, which is close to the ring size 17*17 = 289. 249 | let ip = vec4(1.0 / 294.0, 1.0 / 49.0, 1.0 / 7.0, 0.0); 250 | 251 | var p0 = grad4(j0, ip); 252 | var p1 = grad4(j1.x, ip); 253 | var p2 = grad4(j1.y, ip); 254 | var p3 = grad4(j1.z, ip); 255 | var p4 = grad4(j1.w, ip); 256 | 257 | // Normalise gradients 258 | let norm = taylorInvSqrt_4(vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3))); 259 | p0 = p0 * norm.x; 260 | p1 = p1 * norm.y; 261 | p2 = p2 * norm.z; 262 | p3 = p3 * norm.w; 263 | p4 = p4 * taylorInvSqrt_1(dot(p4, p4)); 264 | 265 | // Mix contributions from the five corners 266 | var m0 = max(0.6 - vec3(dot(x0,x0), dot(x1,x1), dot(x2,x2)), vec3(0.0)); 267 | var m1 = max(0.6 - vec2(dot(x3,x3), dot(x4,x4)), vec2(0.0)); 268 | m0 = m0 * m0; 269 | m1 = m1 * m1; 270 | 271 | return 49.0 * ( 272 | dot(m0 * m0, vec3(dot(p0, x0), dot(p1, x1), dot(p2, x2))) + dot(m1 * m1, vec2(dot(p3, x3), dot(p4, x4))) 273 | ); 274 | } 275 | `; 276 | 277 | export default { 278 | SNOISE2D, 279 | SNOISE3D, 280 | SNOISE4D, 281 | }; 282 | -------------------------------------------------------------------------------- /src/shaders/noise/utils.glsl.ts: -------------------------------------------------------------------------------- 1 | const FUNCTIONS = /* glsl */ ` 2 | vec4 mod289(vec4 x) 3 | { 4 | return x - floor(x * (1.0 / 289.0)) * 289.0; 5 | } 6 | 7 | vec4 permute(vec4 x) 8 | { 9 | return mod289(((x*34.0)+1.0)*x); 10 | } 11 | 12 | vec4 taylorInvSqrt(vec4 r) 13 | { 14 | return 1.79284291400159 - 0.85373472095314 * r; 15 | } 16 | `; 17 | 18 | const FADE2 = /* glsl */ ` 19 | vec2 fade(vec2 t) { 20 | return t*t*t*(t*(t*6.0-15.0)+10.0); 21 | } 22 | `; 23 | 24 | const FADE3 = /* glsl */ ` 25 | vec3 fade(vec3 t) { 26 | return t*t*t*(t*(t*6.0-15.0)+10.0); 27 | } 28 | `; 29 | 30 | const FADE4 = /* glsl */ ` 31 | vec4 fade(vec4 t) { 32 | return t*t*t*(t*(t*6.0-15.0)+10.0); 33 | } 34 | `; 35 | 36 | export { FUNCTIONS, FADE2, FADE3, FADE4 }; 37 | -------------------------------------------------------------------------------- /src/shaders/noise/utils.wgsl.ts: -------------------------------------------------------------------------------- 1 | const MOD289_1 = /* wgsl */ ` 2 | fn mod289_1(x: f32) -> f32 { 3 | return x - floor(x * (1.0 / 289.0)) * 289.0; 4 | } 5 | `; 6 | const MOD289_2 = /* wgsl */ ` 7 | fn mod289_2(x: vec2) -> vec2 { 8 | return x - floor(x * (1.0 / 289.0)) * 289.0; 9 | } 10 | `; 11 | const MOD289_3 = /* wgsl */ ` 12 | fn mod289_3(x: vec3) -> vec3 { 13 | return x - floor(x * (1.0 / 289.0)) * 289.0; 14 | } 15 | `; 16 | const MOD289_4 = /* wgsl */ ` 17 | fn mod289_4(x: vec4) -> vec4 { 18 | return x - floor(x * (1.0 / 289.0)) * 289.0; 19 | } 20 | `; 21 | 22 | const PERMUTE_1 = /* wgsl */ ` 23 | fn permute_1(x: f32) -> f32 { 24 | return mod289_1(((x * 34.0) + 10.0) * x); 25 | } 26 | `; 27 | 28 | const PERMUTE_3 = /* wgsl */ ` 29 | fn permute_3(x: vec3) -> vec3 { 30 | return mod289_3(((x * 34.0) + 10.0) * x); 31 | } 32 | `; 33 | 34 | const PERMUTE_4 = /* wgsl */ ` 35 | fn permute_4(x: vec4) -> vec4 { 36 | return mod289_4(((x * 34.0) + 10.0) * x); 37 | } 38 | `; 39 | 40 | const FADE_2 = /* wgsl */ ` 41 | fn fade_2(t: vec2) -> vec2 { 42 | return t * t * t * (t * (t * 6.0 - 15.0) + 10.0); 43 | } 44 | `; 45 | 46 | const FADE_3 = /* wgsl */ ` 47 | fn fade_3(t: vec3) -> vec3 { 48 | return t * t * t * (t * (t * 6.0 - 15.0) + 10.0); 49 | } 50 | `; 51 | 52 | const FADE_4 = /* wgsl */ ` 53 | fn fade_4(t: vec4) -> vec4 { 54 | return t * t * t * (t * (t * 6.0 - 15.0) + 10.0); 55 | } 56 | `; 57 | 58 | const TAYLOR_INV_SQRT_1 = /* wgsl */ ` 59 | fn taylorInvSqrt_1(r: f32) -> f32 { 60 | return 1.79284291400159 - 0.85373472095314 * r; 61 | } 62 | `; 63 | 64 | const TAYLOR_INV_SQRT_4 = /* wgsl */ ` 65 | fn taylorInvSqrt_4(r: vec4) -> vec4 { 66 | return 1.79284291400159 - 0.85373472095314 * r; 67 | } 68 | `; 69 | 70 | export { 71 | MOD289_1, 72 | MOD289_2, 73 | MOD289_3, 74 | MOD289_4, 75 | PERMUTE_1, 76 | PERMUTE_3, 77 | PERMUTE_4, 78 | FADE_2, 79 | FADE_3, 80 | FADE_4, 81 | TAYLOR_INV_SQRT_1, 82 | TAYLOR_INV_SQRT_4, 83 | }; 84 | -------------------------------------------------------------------------------- /src/shaders/noise/worley.wgsl.ts: -------------------------------------------------------------------------------- 1 | import { MOD289_2, MOD289_3, PERMUTE_3 } from "./utils.wgsl.js"; 2 | 3 | const WORLEY2D = /* wgsl */ ` 4 | // https://github.com/ashima/webgl-noise 5 | ${MOD289_2} 6 | ${MOD289_3} 7 | ${PERMUTE_3} 8 | 9 | // Modulo 7 without a division 10 | fn mod7(x: vec3) -> vec3 { 11 | return x - floor(x * (1.0 / 7.0)) * 7.0; 12 | } 13 | 14 | // Cellular noise, returning F1 and F2 in a vec2. 15 | // Standard 3x3 search window for good F1 and F2 values 16 | fn worley2d(P: vec2) -> vec2 { 17 | let K = 0.142857142857; // 1/7 18 | let Ko = 0.428571428571; // 3/7 19 | let jitter = 1.0; // Less gives more regular pattern 20 | 21 | let Pi = mod289_2(floor(P)); 22 | let Pf = fract(P); 23 | 24 | var oi = vec3(-1.0, 0.0, 1.0); 25 | var of_ = vec3(-0.5, 0.5, 1.5); 26 | 27 | var px = permute_3(Pi.x + oi); 28 | var p = permute_3(px.x + Pi.y + oi); // p11, p12, p13 29 | 30 | var ox = fract(p * K) - Ko; 31 | var oy = mod7(floor(p * K)) * K - Ko; 32 | var dx = Pf.x + 0.5 + jitter * ox; 33 | var dy = Pf.y - of_ + jitter * oy; 34 | 35 | var d1 = dx * dx + dy * dy; // d11, d12 and d13, squared 36 | p = permute_3(px.y + Pi.y + oi); // p21, p22, p23 37 | ox = fract(p * K) - Ko; 38 | oy = mod7(floor(p * K)) * K - Ko; 39 | dx = Pf.x - 0.5 + jitter * ox; 40 | dy = Pf.y - of_ + jitter * oy; 41 | var d2 = dx * dx + dy * dy; // d21, d22 and d23, squared 42 | p = permute_3(px.z + Pi.y + oi); // p31, p32, p33 43 | ox = fract(p * K) - Ko; 44 | oy = mod7(floor(p * K)) * K - Ko; 45 | dx = Pf.x - 1.5 + jitter * ox; 46 | dy = Pf.y - of_ + jitter * oy; 47 | var d3 = dx * dx + dy * dy; // d31, d32 and d33, squared 48 | // Sort out the two smallest distances (F1, F2) 49 | var d1a = min(d1, d2); 50 | d2 = max(d1, d2); // Swap to keep candidates for F2 51 | d2 = min(d2, d3); // neither F1 nor F2 are now in d3 52 | d1 = min(d1a, d2); // F1 is now in d1 53 | d2 = max(d1a, d2); // Swap to keep candidates for F2 54 | 55 | // Swap if smaller 56 | if (d1.x >= d1.y) { 57 | let x = d1.x; 58 | d1.x = d1.y; 59 | d1.y = x; 60 | } 61 | 62 | // F1 is in d1.x 63 | if (d1.x >= d1.z) { 64 | let x = d1.x; 65 | d1.x = d1.z; 66 | d1.z = x; 67 | } 68 | 69 | d1.y = min(d1.y, d2.y); // F2 is now not in d2.yz 70 | d1.z = min(d1.z, d2.z); // F2 is now not in d2.yz 71 | d1.y = min(d1.y, d1.z); // nor in d1.z 72 | d1.y = min(d1.y, d2.x); // F2 is in d1.y, we're done. 73 | 74 | return sqrt(d1.xy); 75 | } 76 | `; 77 | 78 | export default { 79 | WORLEY2D, 80 | }; 81 | -------------------------------------------------------------------------------- /src/shaders/utils.glsl.ts: -------------------------------------------------------------------------------- 1 | const SATURATE = /* glsl */ ` 2 | #define saturate(x) clamp(x, 0.0, 1.0) 3 | `; 4 | 5 | export default { 6 | SATURATE, 7 | }; 8 | -------------------------------------------------------------------------------- /src/shaders/utils.wgsl.ts: -------------------------------------------------------------------------------- 1 | const RANDOM = /* wgsl */ ` 2 | fn random(st: vec2) -> f32 { 3 | return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123); 4 | } 5 | `; 6 | 7 | export default { 8 | RANDOM, 9 | }; 10 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import Variable from "./core/Variable.js"; 2 | import Uniform from "./core/Uniform.js"; 3 | import Attribute from "./core/Attribute.js"; 4 | import Struct from "./core/Struct.js"; 5 | import BindGroupLayout from "./core/BindGroupLayout.js"; 6 | 7 | // GLSL 8 | export type GLSLStorageQualifier = 9 | | "const" 10 | | "in" 11 | | "out" 12 | | "inout" 13 | | "centroid" 14 | | "patch" 15 | | "sample" 16 | | "uniform" 17 | | "buffer" 18 | | "shared" 19 | | "coherent" 20 | | "volatile" 21 | | "restrict" 22 | | "readonly" 23 | | "writeonly"; 24 | 25 | export type GLSLLayoutQualifier = "std140" | "std430"; 26 | export type GLSLSamplerType = 27 | | "1D" 28 | | "2D" 29 | | "3D" 30 | | "Cube" 31 | | "2DRect" 32 | | "1DArray" 33 | | "2DArray" 34 | | "CubeArray" 35 | | "Buffer" 36 | | "2DMS" 37 | | "2DMSArray"; 38 | export type GLSLShadowSamplerType = 39 | | "1DShadow" 40 | | "2DShadow" 41 | | "CubeShadow" 42 | | "2DRectShadow" 43 | | "1DArrayShadow" 44 | | "2DArrayShadow" 45 | | "CubeArrayShadow"; 46 | export interface GLSLTypeQualifiers { 47 | layout?: GLSLLayoutQualifier; 48 | storage?: GLSLStorageQualifier; 49 | } 50 | 51 | // DGEL 52 | export type Language = "glsl" | "wgsl"; 53 | 54 | export interface ContextState { 55 | device: GPUDevice; 56 | glslang: { 57 | compileGLSL: (source: string, type: string) => string; 58 | }; 59 | twgsl: { 60 | convertSpirV2WGSL: (source: string) => string; 61 | }; 62 | debug: boolean; 63 | error: boolean; 64 | } 65 | 66 | export interface ContextOptions { 67 | canvas?: HTMLCanvasElement; 68 | context?: GPUCanvasContext; 69 | pixelRatio?: number; 70 | } 71 | 72 | export interface BindGroupLayoutEntry extends GPUBindGroupLayoutEntry { 73 | name: string; 74 | uniforms?: Uniform[]; 75 | members?: (Variable | Struct)[]; 76 | dimension?: GPUTextureDimension; 77 | qualifiers?: GLSLTypeQualifiers; 78 | samplerType?: GLSLSamplerType | GLSLShadowSamplerType; 79 | } 80 | 81 | export interface BindGroupOptions extends GPUBindGroupDescriptor { 82 | resources: GPUBindingResource[]; 83 | } 84 | 85 | export type ShaderStageName = "vertex" | "fragment" | "compute"; 86 | 87 | export type ShaderStageNameObjectKeys = { 88 | [key in ShaderStageName]?: string; 89 | }; 90 | export type ShaderStageBodyName = "vertexBody" | "fragmentBody" | "computeBody"; 91 | export type ShaderStageBodyNameObjectKeys = { 92 | [key in ShaderStageBodyName]?: string; 93 | }; 94 | 95 | export type PipelineVertexBufferIns = { 96 | stepMode: GPUVertexStepMode; 97 | attributes: Attribute[]; 98 | }; 99 | 100 | export interface PipelineOptions 101 | extends ShaderStageNameObjectKeys, 102 | ShaderStageBodyNameObjectKeys { 103 | bindGroupLayouts?: BindGroupLayout[]; 104 | ins?: Attribute[] | PipelineVertexBufferIns[]; 105 | outs?: Attribute[]; 106 | structs?: Struct[]; 107 | fragmentOuts?: Attribute[]; 108 | fragmentTargets?: GPUColorTargetState[]; 109 | descriptor?: Partial; 110 | stepMode?: GPUVertexStepMode; 111 | language?: Language; 112 | } 113 | 114 | export interface ShaderOptions { 115 | type: GPUShaderStageFlags; 116 | main: string; 117 | body?: string; 118 | ins?: Attribute[]; 119 | outs?: Attribute[]; 120 | structs?: Struct[]; 121 | language?: Language; 122 | } 123 | 124 | export interface AttachmentOptions { 125 | op?: GPUStoreOp; 126 | view?: GPUTextureView; 127 | resolveTarget?: GPUTextureView; 128 | } 129 | 130 | export type PassType = "render" | "compute"; 131 | 132 | export type GPUBindingType = 133 | | GPUBufferBindingType 134 | | GPUSamplerBindingType 135 | | GPUTextureSampleType 136 | | GPUStorageTextureAccess; 137 | 138 | // type FormatType = 139 | // | "uchar" // unsigned 8-bit value 140 | // | "char" // signed 8-bit value 141 | // | "ushort" // unsigned 16-bit value 142 | // | "short" // signed 16-bit value 143 | // | "half" // half-precision 16-bit floating point value 144 | // | "float" // 32-bit floating point value 145 | // | "uint" // unsigned 32-bit integer value 146 | // | "int"; // signed 32-bit integer value 147 | 148 | // type FormatValue = "" | "2" | "3" | "4"; 149 | 150 | // interface Format { 151 | // type: FormatType; 152 | // value?: FormatValue; 153 | // normalized?: boolean; 154 | // } 155 | 156 | export default null; 157 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | const TAB = ` `; 2 | 3 | const formatLowerFirst = (string: string): string => 4 | `${string[0].toLowerCase()}${string.slice(1)}`; 5 | 6 | const formatUpperFirst = (string: string): string => 7 | `${string[0].toUpperCase()}${string.slice(1)}`; 8 | 9 | const getIntegerDigitCount = (integer: number): number => { 10 | return integer.toString().length; 11 | }; 12 | 13 | const addLineNumbers = (shaderSource: string): string => { 14 | const lines = shaderSource.split("\n"); 15 | const maxDigitSize = getIntegerDigitCount(lines.length - 1); 16 | 17 | return lines 18 | .map((line, index) => { 19 | const lineNumber = `${index + 1}`.padStart(maxDigitSize, " "); 20 | return `${lineNumber}: ${line}`; 21 | }) 22 | .join("\n"); 23 | }; 24 | 25 | export { 26 | TAB, 27 | formatLowerFirst, 28 | formatUpperFirst, 29 | getIntegerDigitCount, 30 | addLineNumbers, 31 | }; 32 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "allowSyntheticDefaultImports": true, 5 | "declaration": true, 6 | "declarationDir": "types", 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "importHelpers": true, 10 | "lib": ["DOM", "ES2015", "ES2016", "ES2017", "ES2018", "ES2019", "ES2020"], 11 | "module": "ES2020", 12 | "moduleResolution": "node", 13 | "outDir": "lib", 14 | "sourceMap": true, 15 | "strictFunctionTypes": true, 16 | "target": "ES2020", 17 | "typeRoots": [ "./node_modules/@webgpu/types", "./node_modules/@types"] 18 | }, 19 | "include": [ 20 | "src/**/*" 21 | ], 22 | "exclude": [ 23 | "node_modules", 24 | "web_modules", 25 | "**/*.spec.ts" 26 | ], 27 | "typeRoots": [ 28 | "./node_modules/@webgpu/types/" 29 | ] 30 | } 31 | --------------------------------------------------------------------------------