Model Texture Transition and Procedural Radial Noise using WebGL

A WebGL experiment that explores two visual effects: a texture transition on a 3D can model and a procedural radial noise field.

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 ✨

References and Credits

Mohammed Amine Bourouis

I am a Creative Frontend Developer with a deep passion for crafting interactive and immersive web experiences. After earning my Master’s degree in Computer Science, I embarked on a self-taught journey into web development, driven by my dual passions for design and coding.

Stay in the loop: Get your dose of frontend twice a week

Fresh news, inspo, code demos, and UI animations—zero fluff, all quality. Make your Mondays and Thursdays creative!