如何在React应用中使用Three.js实现管道水流模拟功能
React + Three.js 管道水流模拟实现方案
前置依赖
首先安装需要的第三方包:
npm install three @react-three/fiber @react-three/drei
核心实现逻辑
- 管道绘制:使用双层圆柱实现,外层为半透明灰色管壁,内层为空心区域承载水体,默认水平放置
- 蓄水百分比控制:根据传入的百分比值动态调整内层水体圆柱的轴向长度,水体使用半透蓝色材质模拟真实水效
- 水流方向动画:通过动态偏移水体表面的箭头纹理实现流动效果,方向切换时直接调整纹理偏移的正负值即可
完整组件代码
import { useRef, useState, useMemo } from 'react' import { Canvas, useFrame } from '@react-three/fiber' import { OrbitControls } from '@react-three/drei' import * as THREE from 'three' // 本地生成箭头纹理,无需引入外部资源 const createArrowTexture = () => { const canvas = document.createElement('canvas') canvas.width = 512 canvas.height = 128 const ctx = canvas.getContext('2d') ctx.fillStyle = 'rgba(255,255,255,0.8)' ctx.fillRect(0, 0, 512, 128) ctx.fillStyle = '#1e88e5' // 绘制重复箭头图案 for (let i = 0; i < 8; i++) { const x = i * 64 + 10 ctx.beginPath() ctx.moveTo(x, 64) ctx.lineTo(x + 30, 24) ctx.lineTo(x + 30, 44) ctx.lineTo(x + 50, 44) ctx.lineTo(x + 50, 84) ctx.lineTo(x + 30, 84) ctx.lineTo(x + 30, 104) ctx.closePath() ctx.fill() } const texture = new THREE.CanvasTexture(canvas) texture.wrapS = THREE.RepeatWrapping texture.wrapT = THREE.RepeatWrapping texture.repeat.set(4, 1) return texture } const Pipe = ({ waterPercent = 70, flowDirection = 'right' }) => { const pipeLength = 10 const pipeRadius = 1 const waterMaterialRef = useRef() const arrowTexture = useMemo(() => createArrowTexture(), []) // 按百分比计算水体长度 const waterLength = useMemo(() => { return (waterPercent / 100) * pipeLength }, [waterPercent, pipeLength]) // 逐帧更新纹理偏移实现流动动画 useFrame((state, delta) => { if (waterMaterialRef.current) { const offsetSpeed = flowDirection === 'right' ? -delta * 0.5 : delta * 0.5 waterMaterialRef.current.map.offset.x += offsetSpeed } }) return ( <group rotation={[0, 0, Math.PI / 2]}> {/* 外层管壁 */} <mesh> <cylinderGeometry args={[pipeRadius, pipeRadius, pipeLength, 32]} /> <meshStandardMaterial color="#90a4ae" transparent opacity={0.6} side={THREE.BackSide} /> </mesh> {/* 管壁外层边框 */} <mesh> <cylinderGeometry args={[pipeRadius + 0.05, pipeRadius + 0.05, pipeLength, 32]} /> <meshStandardMaterial color="#546e7a" transparent opacity={0.8} /> </mesh> {/* 内部水体 */} {waterPercent > 0 && ( <mesh position={[- (pipeLength - waterLength) / 2, 0, 0]}> <cylinderGeometry args={[pipeRadius - 0.1, pipeRadius - 0.1, waterLength, 32]} /> <meshStandardMaterial ref={waterMaterialRef} map={arrowTexture} transparent opacity={0.7} color="#42a5f5" /> </mesh> )} </group> ) } // 组件调用示例 const PipeWaterFlowDemo = () => { const [percent, setPercent] = useState(70) const [direction, setDirection] = useState('right') return ( <div style={{ width: '100%', height: '500px' }}> <div style={{ marginBottom: '16px' }}> <label>蓄水百分比:{percent}% </label> <input type="range" min="0" max="100" value={percent} onChange={(e) => setPercent(Number(e.target.value))} style={{ marginRight: '24px' }} /> <label>水流方向:</label> <button onClick={() => setDirection(direction === 'right' ? 'left' : 'right')} style={{ padding: '4px 12px', marginLeft: '8px' }} > 切换为{direction === 'right' ? '从右到左' : '从左到右'} </button> </div> <Canvas camera={{ position: [0, 5, 10] }}> <ambientLight intensity={0.5} /> <directionalLight position={[10, 10, 5]} intensity={1} /> <Pipe waterPercent={percent} flowDirection={direction} /> <OrbitControls /> </Canvas> </div> ) } export default PipeWaterFlowDemo
效果验证
上述代码实现了全部需求:
- 自动生成带透明管壁的3D管道模型,支持鼠标拖拽旋转查看
- 拖动滑块可实时调整管道蓄水占比,支持0-100%任意数值设置
- 点击切换按钮可调整水流方向,箭头会对应朝对应方向流畅移动

内容的提问来源于stack exchange,提问作者Jerry
相关产品推荐
相关产品推荐

