• About Us
  • Privacy Policy
  • Disclaimer
  • Contact Us
AimactGrow
  • Home
  • Technology
  • AI
  • SEO
  • Coding
  • Gaming
  • Cybersecurity
  • Digital marketing
No Result
View All Result
  • Home
  • Technology
  • AI
  • SEO
  • Coding
  • Gaming
  • Cybersecurity
  • Digital marketing
No Result
View All Result
AimactGrow
No Result
View All Result

Creating Wavy Infinite Carousels in React Three Fiber with GLSL Shaders

Admin by Admin
November 27, 2025
Home Coding
Share on FacebookShare on Twitter




Free course advice: Grasp JavaScript animation with GSAP via 34 free video classes, step-by-step tasks, and hands-on demos. Enroll now →

After coming throughout varied infinite carousel results on X created by some friends, I made a decision to provide it a attempt to create my very own. The concept right here is to apply R3F and shader strategies whereas making one thing that may be simply reused in different tasks.


Free GSAP 3 Express Course


Study trendy internet animation utilizing GSAP 3 with 34 hands-on video classes and sensible tasks — good for all talent ranges.


Test it out

Starter challenge

The bottom challenge is a straightforward Vite+React+TS software with an R3F Canvas, together with the next packages put in:

three
@react-three/fiber
@react-three/drei

Now that we’re all set, we are able to begin writing our first shader.

Creating the GLImage part

We need to show our photographs on planes, in order that we are able to put them in our R3F scene and play with them, add displacement results with shader and so forth.

To start with, let’s create our vertex.glsl and fragment.glsl recordsdata:

// vertex.glsl
various vec2 vUv;

void predominant() {
  vec3 pos = place;
  gl_Position = projectionMatrix * modelViewMatrix * vec4( pos, 1.0 );

  // VARYINGS
  vUv = uv;
}
// fragment.glsl
precision highp float;

uniform sampler2D uTexture;
uniform vec2 uPlaneSizes;
uniform vec2 uImageSizes;

various vec2 vUv;

void predominant() {
  vec2 ratio = vec2(
    min((uPlaneSizes.x / uPlaneSizes.y) / (uImageSizes.x / uImageSizes.y), 1.0),
    min((uPlaneSizes.y / uPlaneSizes.x) / (uImageSizes.y / uImageSizes.x), 1.0)
  );

  vec2 uv = vec2(
    vUv.x * ratio.x + (1.0 - ratio.x) * 0.5,
    vUv.y * ratio.y + (1.0 - ratio.y) * 0.5
  );

  vec4 finalColor = texture2D(uTexture, uv);  
  gl_FragColor = finalColor;
}

Right here, we’re passing the UVs of our mesh to the fragment shader and utilizing them within the texture2D operate to use the picture texture to our fragments.

We’re additionally utilizing two uniforms : uPlaneSizes and uImageSizes, which permit us to recalculate the UV coordinates and have an “object-fit cowl like” impact on our aircraft. This can be very helpful later if we need to change our aircraft sizes with out distorting the photographs.

Now that our shaders are prepared, let’s create our part:

import { useTexture } from "@react-three/drei";
import { forwardRef, useMemo, useRef } from "react";
import * as THREE from "three";
import imageFragmentShader from "../shaders/picture/fragment.glsl?uncooked";
import imageImageVertexShader from "../shaders/picture/vertex.glsl?uncooked";

interface GLImageProps {
  imageUrl: string;
  scale: [number, number, number];
  geometry: THREE.PlaneGeometry;
}

const GLImage = forwardRef(
  (
    {
      imageUrl,
      scale,
      geometry,
    },
    forwardedRef
  ) => {
    const localRef = useRef(null);
    const imageRef = forwardedRef || localRef;
    const texture = useTexture(imageUrl);

    const imageSizes = useMemo(() => {
      if (!texture) return [1, 1];
      return [texture.image.width, texture.image.height];
    }, [texture]);

    const shaderArgs = useMemo(
      () => ({
        uniforms: {
          uTexture: { worth: texture },
          uPlaneSizes: { worth: new THREE.Vector2(scale[0], scale[1]) },
          uImageSizes: {
            worth: new THREE.Vector2(imageSizes[0], imageSizes[1]),
          },
        },
        vertexShader: imageImageVertexShader,
        fragmentShader: imageFragmentShader,
      }),
      [texture, scale, imageSizes]
    );

    return (
      
        
        
      
    );
  }
);

export default GLImage;

Here, we’re loading the image texture with useTexture from @react-three/drei.

Then, we create our shader material arguments (the uniforms and shader files used in it) and add it to our plane mesh with shaderMaterial.

We should obtain something like this:

Displaying an image on a plane geometry with shaders

Displaying multiple images

Now that our base GLImage component is ready, we will create a simple component that will map an image list and display them in a column shape, taking an image size and a gap as props:

import { useMemo, useRef } from "react";
import * as THREE from "three";
import { IMAGE_LIST } from "../constants";
import GLImage from "./GLImage";

interface CarouselProps {
  position?: [number, number, number];
  imageSize: [number, number];
  hole: quantity;
}

const Carousel = ({ place, imageSize, hole }: CarouselProps) => {
  const imageRefs = useRef([]);

  const planeGeometry = useMemo(() => {
    return new THREE.PlaneGeometry(1, 1, 16, 16);
  }, []);

  return (
    
      {IMAGE_LIST.map((url, index) => (
         {
            if (el) imageRefs.present[index] = el;
          }}
        />
      ))}
    
  );
};

export default Carousel;

Right here, we’re mapping our picture checklist and utilizing the index of every picture to set a brand new prop named place in order that our photographs appear to be this:

Column format for our photographs

Infinite impact on scroll

For the infinite scroll impact, we’re going to maneuver all our planes alongside the Y-axis based mostly on the wheel velocity (utilizing Lenis in my case for simplicity). On every body, we apply a modulo operate to the aircraft positions to wrap them again to the highest or backside relying on their present Y place:

// Carousel.tsx
const totalHeight = IMAGE_LIST.size * hole + IMAGE_LIST.size * imageSize[1];

useFrame(() => {
  imageRefs.present.forEach((ref) => {
    if (!ref) return;
    ref.place.y =
      mod(ref.place.y + totalHeight / 2, totalHeight) - totalHeight / 2;
  });
});

useLenis(({ velocity }) => {
  imageRefs.present.forEach((ref) => {
    if (ref) {
      ref.place.y -= velocity * 0.005;
    }
  });
});

The mod() operate right here is used to wrap every aircraft again into the legitimate vary in order that, every time a aircraft strikes too far up or down, its place is recalculated and it re-enters the loop seamlessly, maintaining the carousel infinite:

operate mod(n: quantity, m: quantity) {
  return ((n % m) + m) % m;
}

We should always now have one thing like this:

Infinite impact on scroll

Displacement impact on the planes

First, we would like our planes to stretch vertically relying on the wheel velocity. To do that, we’re going to add a uScrollSpeed uniform to the fabric arguments and replace this uniform on scroll in our Carousel part:

// GLImage.tsx
const shaderArgs = useMemo(
  () => ({
    uniforms: {
      // ...
      uScrollSpeed: { worth: 0.0 },
    },
    vertexShader: imageImageVertexShader,
    fragmentShader: imageFragmentShader,
  }),
  [texture, scale, imageSizes]
);

// Carousel.tsx
useLenis(({ velocity }) => {
  imageRefs.present.forEach((ref) => {
    if (ref) {
      ref.place.y -= velocity * 0.005;
      ref.materials.uniforms.uScrollSpeed.worth = velocity * 0.005;
    }
  });
});

Then in our vertex.glsl shader, we’re going to make use of this uniform and displace our vertex on the Y axis with PI and a sin() operate which is able to permit us to have that “rounded” displacement:

uniform float uScrollSpeed;

various vec2 vUv;

#outline PI 3.141592653

void predominant() {
  vec3 pos = place;

  // Y Displacement in response to the scroll pace
  float yDisplacement = -sin(uv.x * PI) * uScrollSpeed;
  pos.y += yDisplacement;

  gl_Position = projectionMatrix * modelViewMatrix * vec4( pos, 1.0 );

  // VARYINGS
  vUv = uv;
}

We should always now have one thing like this:

Displacement on our planes relying on wheel velocity

Wavy impact on the carousel

Now, let’s add a wavy impact to our carousel, I need the planes to be displaced in a curved form in response to their place in my 3D scene.

To do that, we’re going so as to add two extra uniforms to our shader and use them with the world place of our planes similar to this:

// vertex.glsl
uniform float uScrollSpeed;
uniform float uCurveStrength;
uniform float uCurveFrequency;

various vec2 vUv;

#outline PI 3.141592653

void predominant() {
  vec3 pos = place;
  vec3 worldPosition = (modelMatrix * vec4(place, 1.0)).xyz;

  // X Displacement relying on the world place Y
  float xDisplacement = uCurveStrength * cos(worldPosition.y * uCurveFrequency);
  pos.x += xDisplacement;
  pos.x -= uCurveStrength;

  // Y Displacement in response to the scroll pace
  float yDisplacement = -sin(uv.x * PI) * uScrollSpeed;
  pos.y += yDisplacement;

  gl_Position = projectionMatrix * modelViewMatrix * vec4( pos, 1.0 );

  // VARYINGS
  vUv = uv;
}

Right here, we’re utilizing a cosinus on the worldPosition.y of our planes, in order that the “prime” of the curve will at all times be on the heart of our canvas (as a result of when y=0, cos(y)=1).

And don’t neglect so as to add some props to the GLimage in addition to including the uniforms in our shader materials arguments:

// GLImage.tsx
const shaderArgs = useMemo(
  () => ({
    uniforms: {
      // ...
      uCurveStrength:  0 ,
      uCurveFrequency: ,
    },
    vertexShader: imageImageVertexShader,
    fragmentShader: imageFragmentShader,
  }),
  [texture, curveStrength, curveFrequency, scale, imageSizes]
);

We should always now have one thing like this:

Curved impact on our carousel

Word : as a bonus, you’ll be able to add leva or every other tweaking library in an effort to tweak and discover the perfect values for the curveStrength and curveFrequency of the carousel.

Enjoying round and going additional

Now that we have now an simply reusable and tweakable part, we are able to play with it, add extra of them within the scene, change the wheel path, the wheel pace and so forth.

Listed here are some examples of what you are able to do with it:

Examples out there within the demo

If you wish to go additional, you’ll be able to attempt including some noise displacement to the fragment shader based mostly on the wheel velocity. This might give your carousel a extra natural really feel and can also be an important train.
It’s also possible to add a path prop to the Carousel part to create a horizontal carousel as an alternative of a vertical one, like I did in a number of the examples.

Lastly, should you’d wish to see extra of my work, ensure to observe me on X or Linkedin. I publish all my tasks and experiments there.

Thanks for studying! 🙂

Tags: CarouselsCreatingFiberGLSLInfiniteReactShadersWavy
Admin

Admin

Next Post
Background Video Loading Unlikely To Have an effect on web optimization

Background Video Loading Unlikely To Have an effect on web optimization

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Recommended.

Migrating to Mannequin Context Protocol (MCP): An Adapter-First Playbook

Migrating to Mannequin Context Protocol (MCP): An Adapter-First Playbook

August 20, 2025
Congress Pushes Pause on Superior AI

Congress Pushes Pause on Superior AI

October 23, 2025

Trending.

AI-Assisted Menace Actor Compromises 600+ FortiGate Gadgets in 55 Nations

AI-Assisted Menace Actor Compromises 600+ FortiGate Gadgets in 55 Nations

February 23, 2026
Introducing Sophos Endpoint for Legacy Platforms – Sophos Information

Introducing Sophos Endpoint for Legacy Platforms – Sophos Information

August 28, 2025
How Voice-Enabled NSFW AI Video Turbines Are Altering Roleplay Endlessly

How Voice-Enabled NSFW AI Video Turbines Are Altering Roleplay Endlessly

June 10, 2025
Rogue Planet’ in Growth for Launch on iOS, Android, Change, and Steam in 2025 – TouchArcade

Rogue Planet’ in Growth for Launch on iOS, Android, Change, and Steam in 2025 – TouchArcade

June 19, 2025
10 tricks to begin getting ready! • Yoast

10 tricks to begin getting ready! • Yoast

July 21, 2025

AimactGrow

Welcome to AimactGrow, your ultimate source for all things technology! Our mission is to provide insightful, up-to-date content on the latest advancements in technology, coding, gaming, digital marketing, SEO, cybersecurity, and artificial intelligence (AI).

Categories

  • AI
  • Coding
  • Cybersecurity
  • Digital marketing
  • Gaming
  • SEO
  • Technology

Recent News

What G2’s 2026 Skilled Survey Discovered

What G2’s 2026 Skilled Survey Discovered

February 25, 2026
Ikoku Nikki Is The Should-Watch Anime Of The Season

Ikoku Nikki Is The Should-Watch Anime Of The Season

February 25, 2026
  • About Us
  • Privacy Policy
  • Disclaimer
  • Contact Us

© 2025 https://blog.aimactgrow.com/ - All Rights Reserved

No Result
View All Result
  • Home
  • Technology
  • AI
  • SEO
  • Coding
  • Gaming
  • Cybersecurity
  • Digital marketing

© 2025 https://blog.aimactgrow.com/ - All Rights Reserved