SPACE
to play / pauseARROW RIGHT
orL
to go forwardARROW LEFT
orJ
to go backwardARROW UP
to increase volumeARROW DOWN
to decrease volumeF
to toggle fullscreenM
to toggle mute0 to 9
to go to the corresponding part of the videoSHIFT
+,
to decrease playback speedSHIFT
+.
or;
to increase playback speed
Shortcuts ⌨️
Unlock content 🔓
To get access to 93 hours of video, a members-only Discord server, subtitles, lesson resources, future updates and much more join us for only $95!
Want to learn more? 🤘
That's the end of the free part 😔
To get access to 93 hours of video, a members-only Discord server and future updates, join us for only $95!
Introduction 00:00
Let’s continue our particles series with a classic yet difficult exercise. We are going to morph particles:
There are multiple ways of doing it and we could do it in JS by updating the attribute itself. But as we saw in previous lessons, updating the attribute is bad for performance and it would be impossible to maintain a good frame rate with a large amount of particles.
Another solution is by using GPGPU. It’s a good, but difficult solution which is why we won’t use it. Although it’s not for particle morphing, we’ll learn about GPGPU in one of the next lessons.
Finally, there is an easier solution that relies mostly on the vertex shader, and that we are going to discover now.
Setup 01:39
The starter is quite similar to the previous lesson with particles ready to be enhanced:
- A tweakable
clearColor
- Some models in one file named
models.glb
that you can find in thestatic/
folder (you can download the original Blender file using theResources
button) - A
GLTFLoader
instance with aDracoLoader
instance associated with it - A sphere made out of particles using a ShaderMaterial
- The
vite-plugin-glsl
dependency to handle GLSL files OrbitControls
to rotate aroundgsap
in the dependencies
The particle size is already handled in the vertex.glsl
. We use a uSize
uniform to control it from the JS. Then, perspective is applied and the final size is relative to the height of the render, as seen in previous lessons.
Also, note that everything related to particles has been set as properties of the particles
object to keep things organised.
Theory 04:18
The idea is that we are going to send two sets of positions to the vertex shader, one being the initial shape, and another one being the targeted shape. For the initial shape, we can use the classic position
attribute. For the targeted shape, we need to send a new attribute. Let’s call it aPositionTarget
.
Then, we send a uniform which is going to be a float
going from 0
to 1
. Let’s name it uProgress
. And we are going to use that uProgress
to mix between position
and aPositionTarget
.
Transitioning from one shape to another should be as simple as animating the uProgress
.
Obviously, we are going to face difficulties and add cool effects on top of it.
Pattern 06:06
Before tackling the morphing, let’s have some fun and draw a pattern instead of those squares.
We want to draw a bright point at the center of the particle and have that point fade out before it reaches the edges of the particle. We actually did that in the Shader Patterns lesson, but let’s create it from scratch:
In fragment.glsl
, retrieve the particle UV using gl_PointCoord
, save it as uv
and send it to the gl_FragColor
to make sure it’s working:
void main()
{
vec2 uv = gl_PointCoord;
gl_FragColor = vec4(uv, 1.0, 1.0);
// ...
}
Next, we need the distance to the center. We are going to use the technique we saw in one of the previous lessons by using the length of the offset uv
instead of the distance from vec2(0.5)
and send it to gl_FragColor
:
void main()
{
vec2 uv = gl_PointCoord;
float distanceToCenter = length(uv - 0.5);
gl_FragColor = vec4(distanceToCenter, distanceToCenter, distanceToCenter, 1.0);
// ...
}
Using distanceToCenter
, we can calculate the alpha
.
We want it to be very high at first and plunge very fast. We can use the small number division technique.
The idea is that we take a number below 1
and divide it by an increasing value (distanceToCenter
in our case):
Create a float alpha
variable, to which you assign 0.05
, divided by distanceToCenter
. Send it to gl_FragColor
:
void main()
{
// ...
float alpha = 0.05 / distanceToCenter;
gl_FragColor = vec4(alpha, alpha, alpha, 1.0);
// ...
}
Not bad, but alpha
never reaches 0.0
at the edges.
How to use it 🤔
- Download the Starter pack or Final project
- Unzip it
- Open your terminal and go to the unzip folder
-
Run
npm install
to install dependencies
(if your terminal warns you about vulnerabilities, ignore it) -
Run
npm run dev
to launch the local server
(project should open on your default browser automatically) - Start coding
- The JS is located in src/script.js
- The HTML is located in src/index.html
- The CSS is located in src/style.css