You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为React Three Fiber滑块的3D模型添加Shader过渡效果?

实现3D模型滑块的WebGL切片过渡效果

当然可以实现,核心是把原效果的Shader切片逻辑迁移到3D模型的材质上,同时调整拖拽交互逻辑来支持实时过渡反馈。以下是具体改造方案:

1. 重构状态与拖拽逻辑

首先需要跟踪拖拽偏移量、过渡进度,同时保留当前和上一个模型的索引,这样过渡时能同时渲染两个模型:

import "./styles.css";
import { Canvas, useFrame } from "@react-three/fiber";
import { useState, useRef } from "react";
import { useGesture } from "react-use-gesture";
import { Box, Ring } from "@react-three/drei";

export default function App() {
  const models = [
    { model: <Box /> },
    { model: <Ring innerRadius={0.5} outerRadius={1} /> },
    { model: <Box /> },
    { model: <Ring innerRadius={0.5} outerRadius={1} /> },
    { model: <Box /> },
    { model: <Ring innerRadius={0.5} outerRadius={1} /> }
  ];

  const modelCount = models.length;
  // 当前模型索引、上一个模型索引
  const [currentIndex, setCurrentIndex] = useState(0);
  const [prevIndex, setPrevIndex] = useState(0);
  // 过渡进度:0-1,0为初始状态,1为完成过渡
  const [transitionProgress, setTransitionProgress] = useState(0);
  // 拖拽方向:1=右滑(切换上一个模型),-1=左滑(切换下一个模型)
  const [dragDirection, setDragDirection] = useState(1);
  const isTransitioning = useRef(false);

  const bind = useGesture({
    onDragStart: () => {
      if (!isTransitioning.current) {
        isTransitioning.current = true;
      }
    },
    onDrag: ({ offset }) => {
      if (!isTransitioning.current) return;
      // 将拖拽X偏移量映射到过渡进度(阈值设为窗口宽度的1/5)
      const maxDrag = window.innerWidth / 5;
      const progress = Math.abs(offset[0] / maxDrag);
      // 判断拖拽方向
      const direction = offset[0] > 0 ? 1 : -1;
      setDragDirection(direction);
      setTransitionProgress(Math.min(progress, 1));
    },
    onDragEnd: ({ offset, velocity }) => {
      const maxDrag = window.innerWidth / 5;
      // 判断是否触发模型切换
      if (Math.abs(offset[0]) > maxDrag || Math.abs(velocity[0]) > 0.5) {
        // 切换模型索引
        setPrevIndex(currentIndex);
        if (dragDirection === 1) {
          setCurrentIndex(prev => (prev - 1 + modelCount) % modelCount);
        } else {
          setCurrentIndex(prev => (prev + 1) % modelCount);
        }
        // 平滑过渡到完成状态
        setTransitionProgress(1);
      } else {
        // 未达阈值,回弹到初始状态
        setTransitionProgress(0);
      }
      isTransitioning.current = false;
    }
  });

  // 使用useFrame处理过渡进度的平滑重置
  useFrame(() => {
    if (transitionProgress === 1) {
      setTransitionProgress(0);
    }
  });

  return (
    <div style={{ height: "100vh" }} {...bind()}>
      <Canvas>
        <ambientLight />
        <pointLight position={[10, 10, 10]} />

        {/* 渲染上一个模型(过渡时的退出模型) */}
        <group>
          <TransitionModel 
            model={models[prevIndex].model} 
            progress={transitionProgress} 
            direction={dragDirection} 
            isExit={true} 
          />
        </group>

        {/* 渲染当前模型(过渡时的进入模型) */}
        <group>
          <TransitionModel 
            model={models[currentIndex].model} 
            progress={transitionProgress} 
            direction={dragDirection} 
            isExit={false} 
          />
        </group>
      </Canvas>
    </div>
  );
}

2. 编写过渡Shader组件

创建TransitionModel组件,给模型添加自定义Shader材质,实现切片位移和淡入淡出效果,对应原WebGL滑块的核心视觉逻辑:

import { useRef, useEffect } from "react";
import { extend } from "@react-three/fiber";
import { shaderMaterial } from "@react-three/drei";

// 自定义过渡Shader材质
const TransitionMaterial = shaderMaterial(
  {
    progress: 0,
    direction: 1,
    sliceCount: 10, // 切片数量,可调整以匹配原效果的分段密度
  },
  // 顶点着色器:传递UV坐标给片元着色器
  `
    varying vec2 vUv;
    void main() {
      vUv = uv;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  // 片元着色器:实现切片位移+透明度过渡
  `
    varying vec2 vUv;
    uniform float progress;
    uniform float direction;
    uniform float sliceCount;

    void main() {
      // 计算当前像素所属的切片索引
      float sliceIndex = floor(vUv.y * sliceCount);
      // 给每个切片添加进度偏移,实现错落的过渡效果
      float sliceProgress = progress + (sliceIndex / sliceCount) * 0.2;
      sliceProgress = clamp(sliceProgress, 0.0, 1.0);

      // 根据方向和切片进度计算UV偏移
      vec2 offset = vec2(direction * sliceProgress, 0.0);
      vec2 newUv = vUv + offset;

      // 裁剪超出范围的UV,实现边缘淡入淡出
      float alpha = 1.0 - smoothstep(0.0, 0.1, abs(newUv.x - 0.5) * 2.0);
      alpha *= 1.0 - smoothstep(0.9, 1.0, sliceProgress);

      // 给不同切片设置区分颜色(可替换为纹理采样)
      vec3 color = vec3(0.8, 0.8, 0.8);
      if (mod(sliceIndex, 2.0) == 0.0) {
        color = vec3(0.3, 0.6, 0.9);
      }

      gl_FragColor = vec4(color, alpha);
    }
  `
);

extend({ TransitionMaterial });

export function TransitionModel({ model, progress, direction, isExit }) {
  const materialRef = useRef();

  useEffect(() => {
    // 更新Shader的uniform参数
    materialRef.current.progress = progress;
    // 退出模型和进入模型的位移方向相反
    materialRef.current.direction = isExit ? direction : -direction;
  }, [progress, direction, isExit]);

  return (
    <primitive
      object={model}
      ref={(el) => {
        if (el) el.material = <transitionMaterial ref={materialRef} />;
      }}
    />
  );
}

关键说明

  • 切片逻辑:通过UV坐标将模型纵向分割为多个切片,每个切片按不同进度位移,实现和原WebGL滑块一致的错落过渡效果。
  • 拖拽反馈:实时根据拖拽偏移量更新过渡进度,拖拽超过阈值后触发完整过渡,未达阈值则回弹到初始状态。
  • 材质扩展:替换原模型的默认材质为自定义Shader材质,你可以修改片元着色器中的颜色、切片数量、位移曲线等参数,精准匹配原效果的视觉风格。

内容的提问来源于stack exchange,提问作者geniar99s

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 01:25:06