在Next.js中使用@react-three/fiber无法渲染Three.js线框对象
问题描述
我参考一个CodeSandbox示例,尝试在Next.js环境中使用Three.js结合@react-three/fiber创建可移动的线框平面,但适配到自己的环境后,对象无法渲染且未抛出任何报错。
我已尝试更换多种材质、添加灯光对象、改用mesh替代group、调整相机的不同配置,同时试过已废弃的'react-three-fiber'库和当前版本,但问题仍未解决。请问可能是什么原因?
我的代码
import React, { useRef, useMemo } from "react"; import { Canvas, useFrame } from "@react-three/fiber"; import * as THREE from "three"; const size = 50; const divisions = 50; const halfsize = size / 2; const vertices = new Float32Array((divisions + 1) * 12).map((v, i) => { const step = -halfsize + (Math.trunc(i / 12) * size) / divisions; switch (i % 12) { case 0: return -halfsize; case 1: return 0; case 2: return step; case 3: return halfsize; case 4: return 0; case 5: return step; case 6: return step; case 7: return 0; case 8: return -halfsize; case 9: return step; case 10: return 0; case 11: return halfsize; default: return null; } }); const colors = new Float32Array((divisions + 1) * 12).map(() => 0.3); const Obj = () => { const meshRef = useRef(); useFrame(() => { if (meshRef.current) { meshRef.current.position.z = ((meshRef.current.position.z + 0.05) % 2) - 20; meshRef.current.position.y = -2; meshRef.current.rotation.y = 0; //Math.PI / 4 meshRef.current.rotation.x = 0; //Math.PI / 8 meshRef.current.rotation.z = 0; //Math.PI / 4 } }); return ( <group ref={meshRef}> <lineSegments> <bufferGeometry attach="geometry"> <bufferAttribute attachObject={["attributes", "position"]} count={vertices.length / 3} array={vertices} itemSize={3} /> </bufferGeometry> <lineBasicMaterial attach="material" color="orange" /> </lineSegments> </group> ); }; const SynthwaveScene = () => ( <div className="canvas-container"> <Canvas> <Obj /> </Canvas> </div> );
问题排查与解决
1. 顶点数组类型错误
new Float32Array(...).map()会返回普通JavaScript数组,而Three.js的BufferAttribute要求传入TypedArray(如Float32Array)作为array参数。直接使用map会导致类型不匹配,需改用循环直接操作Float32Array:
const vertices = useMemo(() => { const arr = new Float32Array((divisions + 1) * 12); for (let i = 0; i < arr.length; i++) { const step = -halfsize + (Math.trunc(i / 12) * size) / divisions; switch (i % 12) { case 0: arr[i] = -halfsize; break; case 1: arr[i] = 0; break; case 2: arr[i] = step; break; case 3: arr[i] = halfsize; break; case 4: arr[i] = 0; break; case 5: arr[i] = step; break; case 6: arr[i] = step; break; case 7: arr[i] = 0; break; case 8: arr[i] = -halfsize; break; case 9: arr[i] = step; break; case 10: arr[i] = 0; break; case 11: arr[i] = halfsize; break; } } return arr; }, []);
2. 对象位置超出相机视野
useFrame中设置的position.z = ((...)-20)会让对象初始z轴位置约为-19.95,而R3F默认相机位置是(0,0,5)且看向(0,0,0),此时对象处于相机极远视野外,加上y=-2的偏移,会完全被视锥体裁切。
可以调整对象z轴偏移,或显式设置相机参数:
// 调整对象z轴位置,靠近相机 meshRef.current.position.z = ((meshRef.current.position.z + 0.05) % 2) - 5; // 或在Canvas中设置相机 <Canvas camera={{ position: [0, 3, 10], fov: 60 }}> <Obj /> </Canvas>
3. BufferAttribute用法不规范
R3F中推荐直接通过Three.js API创建并缓存BufferGeometry,比JSX嵌套更可靠:
const geometry = useMemo(() => { const geo = new THREE.BufferGeometry(); geo.setAttribute('position', new THREE.BufferAttribute(vertices, 3)); return geo; }, [vertices]); // 组件中使用 <lineSegments geometry={geometry}> <lineBasicMaterial color="orange" /> </lineSegments>
4. 容器无明确尺寸
如果canvas-container未设置宽高,Canvas会被渲染为0尺寸,导致看不到内容,需添加样式:
<div className="canvas-container" style={{ width: '100vw', height: '100vh' }}> <Canvas>...</Canvas> </div>
修复后完整代码示例
import React, { useRef, useMemo } from "react"; import { Canvas, useFrame } from "@react-three/fiber"; import * as THREE from "three"; const size = 50; const divisions = 50; const halfsize = size / 2; const Obj = () => { const meshRef = useRef(); const vertices = useMemo(() => { const arr = new Float32Array((divisions + 1) * 12); for (let i = 0; i < arr.length; i++) { const step = -halfsize + (Math.trunc(i / 12) * size) / divisions; switch (i % 12) { case 0: arr[i] = -halfsize; break; case 1: arr[i] = 0; break; case 2: arr[i] = step; break; case 3: arr[i] = halfsize; break; case 4: arr[i] = 0; break; case 5: arr[i] = step; break; case 6: arr[i] = step; break; case 7: arr[i] = 0; break; case 8: arr[i] = -halfsize; break; case 9: arr[i] = step; break; case 10: arr[i] = 0; break; case 11: arr[i] = halfsize; break; } } return arr; }, []); const geometry = useMemo(() => { const geo = new THREE.BufferGeometry(); geo.setAttribute('position', new THREE.BufferAttribute(vertices, 3)); return geo; }, [vertices]); useFrame(() => { if (meshRef.current) { meshRef.current.position.z = ((meshRef.current.position.z + 0.05) % 2) - 5; meshRef.current.position.y = -2; } }); return ( <group ref={meshRef}> <lineSegments geometry={geometry}> <lineBasicMaterial color="orange" /> </lineSegments> </group> ); }; const SynthwaveScene = () => ( <div className="canvas-container" style={{ width: '100vw', height: '100vh' }}> <Canvas camera={{ position: [0, 3, 10], fov: 60 }}> <ambientLight intensity={0.5} /> <Obj /> </Canvas> </div> ); export default SynthwaveScene;
内容的提问来源于stack exchange,提问作者John Smith
相关产品推荐
相关产品推荐

