{"id":13418840,"url":"https://github.com/google/swissgl","last_synced_at":"2025-05-14T21:10:08.729Z","repository":{"id":65981558,"uuid":"598237502","full_name":"google/swissgl","owner":"google","description":"SwissGL is a minimalistic wrapper on top of WebGL2 JS API. It's designed to reduce the amount of boilerplate code required to manage GLSL shaders, textures and framebuffers when making procedural visualizations or simulations.","archived":false,"fork":false,"pushed_at":"2025-04-06T09:06:24.000Z","size":1470,"stargazers_count":1203,"open_issues_count":13,"forks_count":45,"subscribers_count":10,"default_branch":"main","last_synced_at":"2025-04-13T18:44:34.101Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://swiss.gl","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/google.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-02-06T17:33:50.000Z","updated_at":"2025-04-09T15:37:25.000Z","dependencies_parsed_at":"2025-02-28T16:05:46.355Z","dependency_job_id":"563226bb-81be-4b05-84e2-b3cff6d9700f","html_url":"https://github.com/google/swissgl","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/google%2Fswissgl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/google%2Fswissgl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/google%2Fswissgl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/google%2Fswissgl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/google","download_url":"https://codeload.github.com/google/swissgl/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254227631,"owners_count":22035671,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-30T22:01:07.748Z","updated_at":"2025-05-14T21:10:03.702Z","avatar_url":"https://github.com/google.png","language":"JavaScript","readme":"# SwissGL: Swiss Army knife for WebGL2\n\n**[DEMO](http://google.github.io/swissgl)** | **[API](docs/API.md)** | **[Changelog](docs/CHANGELOG.md)**\n\nSwissGL is a minimalistic wrapper on top of WebGL2 JS API. It's designed to reduce the amount of boilerplate code required to manage GLSL shaders, textures and framebuffers when making GPGPU-style procedural visualizations or simulations. See the [demos](demo/) for examples of using SwissGL. As of now the library consists of a standalone \u003c1000 loc .js file.\n\n**Disclaimer** This is not an officially supported Google product. SwissGL is an early stage experiment, incomplete and unstable. It's an invitation to discuss compact and expressive graphics library design, which I hope is relevant in light of the upcoming arrival of WebGPU.\n\n## Quickstart\n\nAs of now, the library API consists of a single function object that does everything (like a Swiss Army knife). Here is a tiny example of using it to draw an animated gradient quad:\n\n```HTML\n\u003cscript src=\"swissgl.js\"\u003e\u003c/script\u003e\n\u003ccanvas id=\"c\" width=\"400\" height=\"300\"\u003e\u003c/canvas\u003e\n\u003cscript\u003e\n    const canvas = document.getElementById('c');\n    // create WebGL2 context end SwissGL \n    const glsl = SwissGL(canvas);\n    function render(t) {\n        t /= 1000; // ms to sec\n        glsl({t, // pass uniform 't' to GLSL\n            Mesh:[10, 10],  // draw a 10x10 tessellated plane mesh\n            // Vertex shader expression returns vec4 vertex position in\n            // WebGL clip space. 'XY' and 'UV' are vec2 input vertex \n            // coordinates in [-1,1] and [0,1] ranges.\n            VP:`XY*0.8+sin(t+XY.yx*2.0)*0.2,0,1`,\n            // Fragment shader returns 'RGBA'\n            FP:`UV,0.5,1`});\n        requestAnimationFrame(render);\n    }\n    requestAnimationFrame(render);\n\u003c/script\u003e\n```\n![SwissGL quad gradient](images/quad.png)\n\n`glsl` function has the following signature:\n```js\nglsl(params, target);\n```\nAll it can do is to draw instanced, tessellated plane primitives into the specified (may be created in-place) target buffer using the provided vertex and fragment shaders. This may sound unimpressive, but we'll see that it's possible to do some pretty complex things with such a simple tool! Please refer to the [API reference](docs/API.md) for the detailed explanation `glsl` arguments. Let's now have a look at the more elaborate example of using SwissGL to implement a particle simulation.\n\n## Particle Life\n\nInspired by the [beautiful video](https://youtu.be/p4YirERTVF0?t=481) by Tom Mohr, let's try reproduce the \"snake\" pattern shown there. Particle Life is made of particles of a few different types. All particles repel when they are closer than some distance $r$, but at around $2r$ the resulting (potentially non-symmetric) force is described by the special force matrix $F_{i,j}$, where $i,j$ are types of two particles. Positive $F$ corresponds to attraction and negative to repulsion. Let's create a texture that stores such a matrix. We can create an array on the JS side and pass it to SwissGL, but it's even easier to populate matrix values right on the GPU:\n```js\nconst K = 6; // number of particle types\nconst F = glsl({K, FP:\n    `float(I.x==I.y) + 0.1*float(I.x==(I.y+1)%int(K))`},\n    {size:[K,K], format:'r16f', tag:'F'});\n```\n\nThis creates a single channel float16 texture of size `[width,height]==[6,6]` and populates its values by evaluating the `FP` expression in a newly created fragment shader. `I` is a special variable of type `ivec2` that contains coordinates of the pixel being evaluated. The second (`target`) object must contain the `tag` parameter that is used to store the newly created render target in the `glsl` object for later use.\n\nWe can easily visualize the resulting texture to make sure everything is ok:\n```js\nglsl({F, FP:`F(I/20).x*3.0`});\n```\n![](images/F.png)\n\nUniform textures can be accessed with usual GLSL functions, or with a helper macro that has the same name as the texture uniform. Passing `ivec2` as parameter makes it call `texelFetch()` to get a texel using the integer coordinates, passing `vec2` uses `texture()`, with filtering and wrapping.\n\nThe next step is to create a list of textures that is going to contain particle positions. Each pixel will contain a single particle position and type.\n```js\nconst points = glsl({}, {size:[30,10], story:3, format:'rgba32f', tag:'points'});\n```\nWe are going to simulate 30*10=300 particles. Textures will have 4 channels (RGBA) of type float32. The `story:3` argument says that we need to create a cyclic buffer of three textures of the same format, so that we can read two consecutive states of the particle system (for momentum) to produce the third. We don't provide any shader code in the first argument here, but we can initialize these textures later by passing the returned object as a `target`:\n```js\nfor (let i=0; i\u003c2; ++i) {\n    glsl({K, seed:123, FP:`\n        vec2 pos = (hash(ivec3(I, seed)).xy-0.5)*10.0;\n        float color = floor(UV.x*K);\n        FOut = vec4(pos, 0.0, color);`},\n        points);\n}\n```\nThe shader code above uses \"multiline\" shader `code` format instead of a single expression. The output must be written to a global variable `FOut`. Variable `UV` has type `vec2` and provides `[0,1]`-range normalized coordinates of the current pixel. It is used to assign one of `K` \"colors\" to each particle. For convenience SwissGL provides a [simple hash](https://github.com/google/swissgl/blob/536c9f43c9f7a7bc59646d6fe1f3cb89bb5862b8/swissgl.js#L157) function `vec3 hash(ivec3)` that can be used as a deterministic random number generator.\n\nNote that we are writing the same particle positions two times, which means that particles have zero velocity at initialization. Now `points[0]` and `points[1]` contain the same values, and `points[2]` is uninitialized and is going to be overwritten at the first simulation step.\n\nBefore we start modeling the particle dynamics it's a good idea to implement visualization. So far we've already seen \"expression\" and \"multiline\" shortcut `code` formats. Now we are going to write a `full` vertex-fragment shader pair:\n```js\nglsl({K, worldExtent, // uniforms\n    // reading the last state of 'points' texture\n    points: points[0],\n    // render a quad instance for every 'points' texel\n    Grid: points[0].size,\n    // preserve the scale of xy-axes by fitting\n    // [-1..1]x[-1..1] box into the view\n    Aspect:'fit', \n    // blend primitives using alpha transparency\n    Blend: 'd*(1-sa)+s*sa', \n    // vertex shader that defines where to draw\n    // the quad primitives\n    VP:`\n    // fetch the current particle data\n    vec4 d = points(ID.xy);\n    // populate color varying to use in fragment shader\n    varying vec3 color = cos((d.w/K+vec3(0,0.33,0.66))*TAU)*0.5+0.5;\n    // set the clip-space vertex position, 'vec2 XY' contains\n    // coordinates of the quad vertex in -1..1 range\n    VPos.xy = 2.0*(d.xy+XY/8.0)/worldExtent;`, \n    // Set the fragment color and transparency \n    // depending on the distance from the quad center.\n    // Interpolated XY values are also available \n    // in the fragment shader.\n    FP:`color, smoothstep(1.0, 0.6, length(XY))`});\n    // 'target' argument is omitted, so rendering to canvas\n```\n\nRunning this code in the drawing loop produces the following image:\n\n![Initial particles](images/init_particles.png)\n\nThe vertex shader computes WebGL [Clip Space](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_model_view_projection#clip_space) coordinates for each corner of each particle quad. We map particle positions from `[-worldExtent/2, worldExtent/2]` range to `[-1,1]` box. This shader also computes particle color using [cosine palettes trick](https://iquilezles.org/articles/palettes/) and passes it to the fragment shader along with the corner offset vector. The fragment shader calculates pixel opacity using the distance form the particle center. This way we can use low-level GLSL as an expressive, flexible and performant tool to render large numbers of primitives.\n\nNow we can set particles in motion by writing the update shader that computes new particle positions each frame.\n```js\nglsl({F, worldExtent, repulsion, inertia, dt, // uniforms\n      // The current state of the system is implicitly\n      // available to the shader as 'Src' uniform if\n      // the target has history (is an array of textures).\n      // Here we explicitly pass the state one step at the past\n      past:points[1], FP:`\n// this function wraps positions and velocities to\n// [-worldExtent/2, worldExtent/2] range\nvec3 wrap(vec3 p) {\n    return (fract(p/worldExtent+0.5)-0.5)*worldExtent;\n}\nvoid fragment() {\n    // read the current particle state\n    FOut = Src(I);\n    vec3 force=vec3(0); // force accumulator\n    // iterate over particles\n    for (int y=0; y\u003cViewSize.y; ++y)\n    for (int x=0; x\u003cViewSize.x; ++x) {\n        // reading the state of another particle\n        vec4 data1 = Src(ivec2(x,y));\n        vec3 dpos = wrap(data1.xyz-FOut.xyz);\n        // calculate distance\n        float r = length(dpos);\n        if (r\u003e3.0) continue;\n        dpos /= r+1e-8;\n        // calculate repulsion and interaction forces\n        float rep = max(1.0-r, 0.0)*repulsion;\n        float f = F(ivec2(FOut.w, data1.w)).x;\n        float inter = f*max(1.0-abs(r-2.0), 0.0);\n        force += dpos*(inter-rep);\n    }\n    // fetch the past state to compute velocity\n    vec3 vel = wrap(FOut.xyz-past(I).xyz)*pow(inertia, dt);\n    // update particle position\n    FOut.xyz = wrap(FOut.xyz+vel+0.5*force*(dt*dt));\n}\n`}, points);  // using 'points' as the target\n```\n\nSoon randomly scattered particles self-assemble into a nice colorful snake! The simulation is happening on the GPU and is quite fast for the quadratic complexity algorithm (that iterates all particle pairs). Even mobile phones can run hundreds of steps per second. Thanks to SwissGL, orchestrating this computation, managing shaders and framebuffers takes minimal amount of boilerplate code.\n\n![Particle Snake](images/particle_snake.png)\n\n## Links\n\nSources of wisdom:\n* [Inigo Quilez](https://iquilezles.org/)\n* [Steven Wittens](https://acko.net/)\n* [WebGL](https://webglfundamentals.org/) / [WebGL2](https://webgl2fundamentals.org/) fundamentals\n\nPlaygrounds:\n* [ShaderToy](https://www.shadertoy.com/)\n* [twigl](https://twigl.app/)\n* [vertexshaderart](https://www.vertexshaderart.com/)\n\nLibraries\n* [three.js](https://threejs.org/)\n* [Use.GPU](https://usegpu.live/)\n* [MathBox](https://github.com/unconed/mathbox)\n* [twgljs](https://twgljs.org/)\n* [regl](https://github.com/regl-project/regl)\n* [gpu-io](https://github.com/amandaghassaei/gpu-io)\n* [luma.gl](https://luma.gl/)\n* [CindyJS](https://cindyjs.org/)\n* [PicoGL.js](https://github.com/tsherif/picogl.js)\n* [pex-context](https://github.com/pex-gl/pex-context)\n","funding_links":[],"categories":["JavaScript","Libraries"],"sub_categories":["JavaScript"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgoogle%2Fswissgl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgoogle%2Fswissgl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgoogle%2Fswissgl/lists"}