Image Frame transition breakdown

A breakdown of a vertex buffer animation

Happy New Year!

It's me, Daniel! Hoping everyone had a fantastic year. For me, and the newsletter it was incredible. I launched an "Introduction to Shaders" and "Mastering ThreeJS Instancing" afraid that my words only made sense to me, but so many people reached out saying it made it click for them. It was wonderful and joyful to hear your thoughts and see your results.

For the new year, I'm moving Offscreen Canvas from Eleventy to Astro with a slight re-design. A lot of Articles, Courses, YouTube tutorials, and possibly Twitch (making demos live) coming in the next couple of months! And I'm, also doing a bit of freelancing this year, let me know if you need help with anything.

Thanks for being with me on this Journey of learning and exploring 3D, WebGL, and ThreeJS. I would love to know about your dev plans as well What are you up to right now, or what are your plans for this year? If you have anything to share, reply to this email! I'd love to chat!

Similar to instancing, the idea with vertex buffer animations is to send enough data to the GPU so it can do the animation in the shaders instead of doing it in JavaScript. To do this, the geometry is populated with extra information, and the shaders are extended.

I'm curious to see how I can bake these types of animations into textures. For a project I worked on, I made a position baking utility into textures to blend between shapes with this idea in mind. However, I believe cinema4D, if you know more about it let me know!


This demo was inspired by Szenia codepen. It's been one of my all-time favorites for a long time, so I wanted to give it a try and I'm probably going to be making more demos like this!


Splitting triangles

When a geometry is "indexed", it means that multiple triangles share the same vertices. Usually, this is good because you don't need repeated data for triangles that share some positions.

However, this links triangles together. In our case we want them to be independent of each other, so we transform our geometry to non-Indexed.

triangle splitting
triangle splitting

Animation input

To make each triangle start and end at different times, we loop through each vertex and calculate the start/duration for each using the centroid of its triangle. This makes all 3 vertices of the corner move in unison, so we add a bit of randomness to create variation and stretchiness.

let start = centroid.len() * 0.2;
let duration = (Math.random() * 0.3 + 0.3);
start= start + Math.random() * 0.1

Then, on the shader calculate the progress using the attribute and a uniform value that controls the overall animation.

timing glsl code
timing glsl code

Bezier curves

The animation is along a Bezier Curve. This curve needs the start, end, control0, control1 points, and the progress, in the shader to calculate a curve going from the start to the end position.

vec3 cubicBezier(vec3 p0, vec3 c0, vec3 c1, vec3 p1, float t) {
float tn = 1.0 - t;
return tn * tn * tn * p0 + 3.0 * tn * tn * t * c0 + 3.0 * tn * t * t * c1 + t * t * t * p1;

So, to get those values into the shader, I create 3 new buffer attributes for the geometry. The start/end positions are going to be the same, so I just use the centroid.

let aCentroid = new Float32Array(index.count * 3);
let aControl0 = new Float32Array(index.count * 3);
let aControl1 = new Float32Array(index.count * 3);

Then in the shader, I put it all together to get the final position.

transformed += cubicBezier(aCentroid, aControl0, aControl1, aCentroid, progress);
stretch bezier vs centroid only
stretch bezier vs centroid only

The background

Making so many demos constantly can be time-consuming so I often try to keep it simple. At first, the plane was flying in space with nothing around. But after watching Xavier's Awwwards talk I felt inspired to put a little bit more to it.

Found a frame model, and baked the floor/wall in a couple of minutes and the result is much better.

demo with and without background
demo with and without background