EDIT: this started out with the lofty goals stated below, and quickly just became my scratchpad where I test out random ideas, only most of which are WebGL-related …
If you ask me, any attempt to make an abstraction over WebGL … only makes it more difficult to use.
But don’t take it from me, let’s look at the numbers:
Higher level abstractions suffer from performance issues
Lower level abstractions like picogl, twgl and regl hardly offer enough to be worth the marginally larger bundle size. (coming in at 67k, 52k, and 86k respectively … i fit a minimal minecraft clone in under 96kb and you should really only block on the loading of 650kb max, ever)
WebGPU will be nice, but I’ve been waiting for it since 2018. It’s only just been rolled out in a single browser, it will be forever before it makes it to low-end mobile devices, and that’s where performance (and therefore clever graphics programming) matters the most.
So raw, unadulterated WebGL it is. Webglfundamentals.org is great, but it’s just that: fundamentals. I want scrollbars and dropshadows and examples where those “fundamentals” are used to do real shit.
Ergo: flat, linear WebGL.
Other useful resources in the same vein:
link | desc |
---|---|
tri | triangle; starting-off point; well, I tri-d. |
lines | shows how to do constant-screenspace-thickness 3D-based lines. |
gazebo | simple OBJ model |
link | desc |
---|---|
cube | it’s a cube. |
cube grid | this cube has a grid! |
cube camera | this cube has a grid AND mouse-based camera controls! |
cube gizmos | cube, grid, mouse-based camera controls, and “gizmos” (translate, rotate, scale |
cube editor | everything in gizmos + support for multiple things, undo/redo, etc. |
link | desc |
---|---|
shadow | super simple shadow with no PCF, etc. just reprojection |
shadow_bake | soft shadows + ambient occlusion + shadows dynamically from progressive lightmap |
paint | technically NOT shadows/lighting, but can be helpful to read alongside them. also silly, messy, and fun! |
https://github.com/cedric-h/linear-webgl/assets/25539554/359ae242-8e6a-483d-a227-658fd1cc6fe5
interactive data thing with text, drop shadows, zooming, panning, scrollbars, etc.
also: nodes-rpi, a WebGL 1 version that works (~25 fps) at low resolutions on a raspberry pi Model B that only supports WebGL 1
(this was a rewrite of an SVG-based application that got 200ms frametimes (5 fps) on a $3000 macbook)
simple text demo, based on mapbox’s TinySDF
I’ve tried a lot of different mechanisms for doing text and this one works the best by far with arbitrary zoom
link | desc |
---|---|
text | shows off tinysdf, arbitrarily zoomable text on an infinite canvas |
text-dropin | simpler text (no controls) you can just drop into an existing project |
text-clipped | example of how to clip text without scissor rects, useful for doing a lot of text with few drawcalls |
also: example of how to clip text without scissor rects, useful for doing a lot of text with few drawcalls
link | desc |
---|---|
quad | premultiplied alpha |
quad-blur | separable gaussian blur |
quad-blur-depth | separable gaussian blur + depth texture attached to rendertarget |
quad-shadow | demonstrates prebaking shadow for static geometry so you don’t need to redo every frame – can be easier for layering as well. |
quad-woosh | some fun with feedback transforms |
link | desc |
---|---|
image | emoji -> canvas -> texture -> quad -> screen |
image_atlas | like “image,” but demonstrates how to use several images on a single atlas. |
galaxy | see above + fun with gaussian blur |
skybox | all that, inside a spinning cube! |
shadow | has a one-shot function which adds a (customizable) shadow to a texture. pretty handy! |
link | desc |
---|---|
pulling_texture | useful when your instances need random access to a buffer of data (e.g. compute-type things) |
pulling_buffer | useful when your instances can use sequential data from a buffer; output looks just like pulling_texture |
graph_slow | just calls drawLine; naive, slow, CPU-bound graph |
graph_fast | exactly the same as graph_slow, but it does the slow part on the GPU. |
graph_100k | stress test; graphing 100k datapoints in red, and 2 million in blue. works well on M2 Max |
graph | where I keep the actual good up-to-date graph code. |
super performant graph, outperforms chart.js, echarts, amcharts by about x100
I’ll fix these eventually, but
I say “high-retina” in almost every demo, where I should say “high-resolution retina” (one brain skip, copy/pasted a million times)
paint.html is the only demo which has a touchscreen rotatable camera, and almost the only demo where the camera doesn’t skip if you rotate up too far. (That is just a one-line fix after yaw =
.) More work is necessary for many demos to make sure they work on mobile. EDIT: some issues with the touch controls here, needs a rework. Probably need to completely separate this codepath from mouse controls.
On the topic of camera controls, scroll_thick.html and sphere_texture.html have much smoother camera controls than cube_camera, cube_grid, cube_editor, etc. Also probably some other demos which use that camera code outside of the cube family which I have forgotten.
I say “set up premultiplied alpha,” then do gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_DST_ALPHA)
but the way premultiplied alpha works, the color should be multiplied by … nothing. gl.ONE
. It’s premultiplied. So I need to audit transparency in a lot of places. Alternatively, I could keep that “premultiplied alpha” blend func, and simply divide rgb
by a
in the shader, which may be preferable since premultiplication is lossy. Should note if so though because it’s not obvious.
There are some places where things aren’t especially “linear” or flat. I do have indirection/object orientation/etc. in some instances. I ought to audit myself to see if I’m doing so tastefully. Or perhaps it simply doesn’t matter.
In several .htmls (cube family in particular) input.released
is commented “true for a frame after up,” when it is actually “true for a frame after down.” Not as simple as just updating the comment, because _released
shouldn’t be the field name.
Pretty much all of the files have a let shaders; { }
block, but they switch between vs_shader
/fs_shader
vs. shader_fs
/shader_vs
, and this should be homogenized.
Plenty of matrices are allocated that don’t need to be.
Plenty of buffers are recreated every frame when they could be created once at init, and almost everywhere buffers are created dynamically, the TypedArray is created on the fly every frame, and I would prefer to avoid the allocation if possible.
There are examples which do not perform well on my iPhone 8 (2017). Warrants investigation. I suspect the dampedEvent *= 0.8
could be exacerbating things, should probably use a framerate independent approach there.