Today, I’d like to share a cool WebGL experiment that draws inspiration from a discovery on Pinterest. The demo highlights two interesting visual effects. Firstly, it features a seamless texture transition on a 3D model of a can, accompanied by a dynamic noise effect. Secondly, it incorporates a radial noise field that pulsates from the center, extending beyond the boundaries of the screen.
While React Three Fiber facilitates the setup, the real magic resides in the shader code, so this shouldn’t be an obstacle.
Background Effect
For this effect we will have a plane geometry that fits the screen. Then we will mostly focus on what happens on the fragmentShader. So we create a ring shape that pulsates on u_progress
transition from 0 to 1:
float radius = 1.5;
float outerProgress = clamp(1.1*u_progress, 0., 1.);
float innerProgress = clamp(1.1*u_progress - 0.05, 0., 1.); float innerCircle = 1. - smoothstep((innerProgress-0.4)*radius, innerProgress*radius, dist);
float outerCircle = 1. - smoothstep((outerProgress-0.1)*radius, innerProgress*radius, dist); float displacement = outerCircle-innerCircle; //... gl_FragColor = vec4(vec3(displacement), 1.0);
We proceed by applying a procedural noise using classic 3D Perlin Noise obtained from here:
vec2 newUv = (vUv - vec2(0.5)) * vec2(u_aspect,1.); float dist = length(newUv); float density = 1.8 - dist; float noise = cnoise(vec4(newUv*40.*density, u_time, 1.));
And to spice it up a little bit we will add some grain effect 🧂:
float grain = (fract(sin(dot(vUv, vec2(12.9898,78.233)*2000.0)) * 43758.5453));
Model Texture Transition
For mimicking the drink flavor change in my demo, I opted for a simple color transition in the can model texture. However, you can achieve the transition using textures instead. To begin, we transform our glb model into declarative and reusable JSX components, a task conveniently handled by gltfjsx tool.
Subsequently, we modify the material of the can body using the onBeforeCompile
method.
useEffect(() => { materials.Body.onBeforeCompile = (shader) => { shader.uniforms = Object.assign(shader.uniforms, uniforms); //... shader.fragmentShader = shader.fragmentShader.replace( `#include <color_fragment>`, ` #include <color_fragment> //... ` ); };
}, [uniforms]);
Then, in the fragmentShader we will mix the the two colors with a noise mask in the seam:
//... diffuseColor.rgb += mix(u_color1,u_color2,mask); //...
Another nice feature provided by drei‘s library is PresentationControls
, which offers us the capability to rotate the model effortlessly right out of the box:
//...
<PresentationControls config={{ mass: 2, tension: 300 }} snap={{ mass: 3, tension: 200 }} polar={[-Math.PI / 4, Math.PI / 4]} azimuth={[-Math.PI / 4, Math.PI / 4]}
> <Model />
</PresentationControls>
//..
Final words
I hope you enjoyed this short walk-through and the demo has been inspiring for you. There’s endless potential for customization and creativity, so feel free to play around with it and adjust values. Who knows, you may end up with something even cooler ✨