https://github.com/google/swissgl
Skip to content Toggle navigation
Sign up
* Product
+
Actions
Automate any workflow
+
Packages
Host and manage packages
+
Security
Find and fix vulnerabilities
+
Codespaces
Instant dev environments
+
Copilot
Write better code with AI
+
Code review
Manage code changes
+
Issues
Plan and track work
+
Discussions
Collaborate outside of code
+ Explore
+ All features
+ Documentation
+ GitHub Skills
+ Blog
* Solutions
+ For
+ Enterprise
+ Teams
+ Startups
+ Education
+ By Solution
+ CI/CD & Automation
+ DevOps
+ DevSecOps
+ Case Studies
+ Customer Stories
+ Resources
* Open Source
+
GitHub Sponsors
Fund open source developers
+
The ReadME Project
GitHub community articles
+ Repositories
+ Topics
+ Trending
+ Collections
* Pricing
[ ]
*
#
In this repository All GitHub |
Jump to |
* No suggested jump to results
*
#
In this repository All GitHub |
Jump to |
*
#
In this organization All GitHub |
Jump to |
*
#
In this repository All GitHub |
Jump to |
Sign in
Sign up
{{ message }}
google / swissgl Public
* Notifications
* Fork 11
* Star 352
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.
swiss.gl
License
Apache-2.0 license
352 stars 11 forks
Star
Notifications
* Code
* Issues 2
* Pull requests 2
* Actions
* Projects 0
* Security
* Insights
More
* Code
* Issues
* Pull requests
* Actions
* Projects
* Security
* Insights
google/swissgl
This commit does not belong to any branch on this repository, and may
belong to a fork outside of the repository.
main
Switch branches/tags
[ ]
Branches Tags
Could not load branches
Nothing to show
{{ refName }} default View all branches
Could not load tags
Nothing to show
{{ refName }} default
View all tags
Name already in use
A tag already exists with the provided branch name. Many Git commands
accept both tag and branch names, so creating this branch may cause
unexpected behavior. Are you sure you want to create this branch?
Cancel Create
1 branch 0 tags
Code
* Local
* Codespaces
*
Clone
HTTPS GitHub CLI
[https://github.com/g]
Use Git or checkout with SVN using the web URL.
[gh repo clone google]
Work fast with our official CLI. Learn more.
* Open with GitHub Desktop
* Download ZIP
Sign In Required
Please sign in to use Codespaces.
Launching GitHub Desktop
If nothing happens, download GitHub Desktop and try again.
Launching GitHub Desktop
If nothing happens, download GitHub Desktop and try again.
Launching Xcode
If nothing happens, download Xcode and try again.
Launching Visual Studio Code
Your codespace will open once ready.
There was a problem preparing your codespace, please try again.
Latest commit
@znah
znah MeshGrid, UV/XY, isoline
...
de4d545 Feb 25, 2023
MeshGrid, UV/XY, isoline
de4d545
Git stats
* 14 commits
Files
Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
demo
docs
images
.gitignore
CONTRIBUTING.md
LICENSE
README.md
index.html
swissgl.js
View code
SwissGL: Swiss Army knife for WebGL2 Quickstart Particle Life Links
README.md
SwissGL: Swiss Army knife for WebGL2
DEMO | API | Changelog
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 GPGPU-style
procedural visualizations or simulations. See the demos for examples
of using SwissGL. As of now the library consists of a standalone ~500
loc .js file.
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.
Quickstart
As of now, the library API consists of a single function object that
does everything (like a Swiss Army knife). Here is a minimal example
of using it to draw an animated gradient quad:
SwissGL quad gradient
This line creates a SwissGL library instance that wraps a WebGL2
context of a given canvas element (you can also pass a WebGL2
context):
const glsl = SwissGL(canvas);
Now we can already draw something:
glsl({t}, `UV,cos(t*10.0),1`);
This line, called during the animation loop, creates a shader that
evaluates the given expression string into RGBA-color for every pixel
on the canvas. vec2 UV variable provides [0,1]-range normalized
coordinates of the current pixel, and ivec2 I can be used to get
integer coordinates. The shader is compiled and cached during the
first call and reused later.
glsl function has at most three arguments, some of which can be
omitted:
glsl(params, code, target);
Please refer to the API reference for the detailed explanation of
their function. Let's now have a look at the more elaborate example
of using SwissGL to implement a particle simulation.
Particle Life
Inspired by the beautiful video 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 GPU:
const K = 6; // number of particle types
const F = glsl(`
float(I.x==I.y) + 0.1*float(I.x==I.y+1)`,
{size:[K,K], format:'r16f'});
This creates a single channel float16 texture of size [width,height]=
=[6,6] and populates its values by evaluating the expression. I is a
special variable of type ivec2 that contains coordinates of the pixel
being evaluated.
We can easily visualize the resulting texture to make sure everything
is ok:
glsl({F}, `F(I/20).x*3.0`);
[F]
Uniform 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.
The 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.
const points = glsl({size:[30,10], story:3, format:'rgba32f', tag:'points'});
We 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 shader code to
the SwissGL call, so we must specify the storage tag. Now we can
initialize these textures:
for (let i=0; i<2; ++i) {
glsl({K, seed:123}, `
vec2 pos = (hash(ivec3(I, seed)).xy-0.5)*10.0;
float color = floor(UV.x*K);
out0 = vec4(pos, 0.0, color);`,
points);
}
The shader code above uses "multiline" shader code format instead of
a single expression. The output must be written to a global variable
out0. 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 function vec3 hash(ivec3) that can be used as a deterministic
random number generator.
Note 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.
Before 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:
glsl({K, worldExtent, // uniforms
// reading the last state of 'points' texture
points: points[0],
// render a quad instance for every 'points' texel
Grid: points[0].size,
// preserve the scale of xy-axes by fitting
// [-1..1]x[-1..1] box into the view
Aspect:'fit',
// blend primitives using alpha transparency
Blend: 'd*(1-sa)+s*sa'}, `
// the code below is available in both
// vertex and fragment shaders
varying vec3 color;
//VERT start of vertex-only section
// vertex function is called
vec4 vertex() {
// get current particle data
vec4 d = points(ID);
// populate varyings to use in fragment shader
color = cos((d.w/K+vec3(0,0.33,0.66))*TAU)*0.5+0.5;
// emit normalized on-screen vertex position
// 'vec2 XY' is contains coordinates of the quad vertex in -1..1 range
return vec4(2.0*(d.xy+XY/8.0)/worldExtent, 0.0, 1.0);
}
//FRAG start of fragment-only section
void fragment() {
// Compute the fragment transparency depending
// on the distance from the quad center.
// Interpolated XY is also available in the fragment shader.
float alpha = smoothstep(1.0, 0.6, length(XY));
// set the fragment color
out0 = vec4(color, alpha);
}`); // 'target' is omitted, so rendering to canvas
Running this code in the drawing loop produces the following image:
Initial particles
The vertex shader computes WebGL 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 and passes it to
the fragment shader along with the corner offset vector. The fragment
shader calculates pixel opacity alpha using the distance form the
particle center and sets out0 variable. This way we can use low-level
GLSL as an expressive, flexible and performant tool to render large
numbers of primitives.
Now we can set particles in motion by writing the update shader that
computes new particle positions each frame.
glsl({F, worldExtent, repulsion, inertia, dt, // uniforms
// The current state of the system is implicitly
// available to the shader as 'Src' uniform if
// the target has history (is an array of textures).
// Here we explicitly pass the state one step at the past
past:points[1]}, `
// this function wraps positions and velocities to
// [-worldExtent/2, worldExtent/2] range
vec3 wrap(vec3 p) {
return (fract(p/worldExtent+0.5)-0.5)*worldExtent;
}
void fragment() {
// read the current particle state
out0 = Src(I);
vec3 force=vec3(0); // force accumulator
// iterate over particles
for (int y=0; y3.0) continue;
dpos /= r+1e-8;
// calculate repulsion and interaction forces
float rep = max(1.0-r, 0.0)*repulsion;
float f = F(ivec2(out0.w, data1.w)).x;
float inter = f*max(1.0-abs(r-2.0), 0.0);
force += dpos*(inter-rep);
}
// fetch the past state to compute velocity
vec3 vel = wrap(out0.xyz-past(I).xyz)*pow(inertia, dt);
// update particle position
out0.xyz = wrap(out0.xyz+vel+0.5*force*(dt*dt));
}
`, points); // using 'points' as the target
Soon 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.
Partilce Snake
Links
Sources of wisdom:
* Inigo Quilez
* Steven Wittens
* WebGL / WebGL2 fundamentals
Playgrounds:
* ShaderToy
* twigl
* vertexshaderart
Libraries
* three.js
* Use.GPU
* MathBox
* twgljs
* regl
* gpu-io
* luma.gl
About
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.
swiss.gl
Resources
Readme
License
Apache-2.0 license
Code of conduct
Code of conduct
Security policy
Security policy
Stars
352 stars
Watchers
5 watching
Forks
11 forks
Languages
* JavaScript 75.0%
* HTML 25.0%
Footer
(c) 2023 GitHub, Inc.
Footer navigation
* Terms
* Privacy
* Security
* Status
* Docs
* Contact GitHub
* Pricing
* API
* Training
* Blog
* About
You can't perform that action at this time.
You signed in with another tab or window. Reload to refresh your
session. You signed out in another tab or window. Reload to refresh
your session.