Look at this cool effect. It's just a fragment shader with the following function: color = texture2D(uScene, uv + vec2(sin(iTime + uv.x * 15.0) * iDistortion, sin(iTime + uv.y * 15.0) * iDistortion)).rgb;
It's just a basic image texture, but we'll display an entire scene as texture onto the shader material. Stick around, it's gonna be fun!
What is this?
Every WebGL scene, 3D or 2D, gets displayed on a screen. So everything that is a 3D scene gets converted into something 2D. Most of the time, this is done by the render engine. But we can draw the scene on to a mesh as a texture, and film it with a orthographic camera. That way we can play around with the texture via fragment shaders. This is super performant and adds a cool effect to a simple scene. It adds cool waves to your scene, and you can even change every single value. For example increase the distortion the faster the user scrolls, change easing functions… you get the idea.
The primitive way
In a plain three.js scene I usually use this file provided by Luruke:
With this file you can "redirect" your renderer into PostFX.js. I just threw the file inside some random three.js vanilla sandbox and that's it!
The Pmndrs way
With React-Three-Fiber it becomes somewhat more difficult, but we'll go through everything. Pmndrs has a huge library of components that work very well out of the box.
Let's start with a basic setup.
Now we need useFBO to display a scene onto a texture. I used Drei's storybook to quickly mesh up the scene.
useFBO alone doesn't work with shaders, or if, then I don't know how to manipulate that. So what we have to do is display the scene as texture onto a shaderMaterial. We can send the texture as uniform. You could also send videos and images to the shader the same way.
With this snippet we have two uniforms, time and texture. Time will be updated via useRef and useFrame, that way we only "re-render" the value as ref, which won't re-render the entire component.
The vertex shader is giving the fragment shader the correct coordination of the mesh, so we don't need resolution or anything. Keep in mind this is just basic C++ (Or was it C#?) and I hope this snippet isn't overwhelming you.
Replace the <meshBasicMaterial map={target.texture} />
with <waveShaderMaterial ref={shader} uTexture={target.texture} />
and add the shader ref. A complete setup is right here:
If CSB is throwing errors, just download the Repo and install it locally. With the shader ref we can update the time value for further shading magic 🪄
Okayyyy
Let's add the magic into the fragment shader.
Create a new vec3:
vec3 color = vec3(vUv, 1.0);
Fragment shader to distort the scene:
color = texture2D(uTexture, vUv + vec2(sin(uTime + vUv.x * 15.0) * 0.2, sin(uTime + vUv.y * 15.0) * 0.02)).rgb;
Replace gl_FragColor = vec4(texture, 1.0);
with
vec3 texture = texture2D(uTexture, vUv).rgb;
And it should display this:
Now all we have to do is get rid of the Controls and mesh the mesh up to the size of the screen.
If you are done, keep experimenting with different values. Replace sin with tan, add more dynamic values, etc.
Hope this cluster tutorial helped someone 👍
Top comments (0)