如何为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
相关产品推荐
相关产品推荐

