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

Three.js中代码仅加载单个3D模型却渲染出两个的问题求助

Three.js中代码仅加载单个3D模型却渲染出两个的问题求助

嘿,刚看完你的代码,立马就找到问题根源啦!你现在相当于同时开了两套完全独立的Three.js渲染系统,自然会出现两个模型的视觉效果(准确说是两个叠加的渲染画布,其中一个加载了模型,看起来像是重复渲染了)。

具体来说:

  • 你在useEffect里手动写了一套完整的Three.js流程:新建场景、相机、渲染器,加载模型并添加到场景,最后把渲染器的画布塞进容器里。
  • 同时你又在组件的return里渲染了<Canvas>组件——这是react-three-fiber提供的封装组件,它内部会自动创建属于自己的场景、相机、渲染器,相当于又开了一个新的画布。

这两套系统完全独立,叠加在页面上,就导致你看到了“两个模型”的效果。

给你两个解决方案,选其中一个就行:


方案一:改用react-three-fiber的组件式写法(更符合React生态)

删掉useEffect里的所有手动Three.js代码,用react-three-fiber的钩子和组件来实现,代码会更简洁:

import { Canvas, useGLTF } from '@react-three/fiber';
import { OrbitControls } from '@react-three/drei';
import { useRef } from 'react';

// 封装模型组件
function PhoneModel() {
  const gltf = useGLTF('/phone_port.gltf');
  return <primitive object={gltf.scene} scale={[0.5, 0.5, 0.5]} />;
}

function Mobile() {
  const controls = useRef();
  const cameraProps = { /* 填入你的相机参数 */ };

  return (
    <div>
      <Canvas
        camera={{
          fov: cameraProps.fov,
          aspect: cameraProps.aspect,
          near: cameraProps.near,
          far: cameraProps.far,
          position: cameraProps.position,
        }}
        gl={{ alpha: true }}
        onCreated={({ camera }) => {
          camera.lookAt(0.5, 0, 0);
        }}
        onPointerEnter={() => {
          controls.current.enabled = true;
        }}
        onPointerLeave={() => {
          controls.current.enabled = false;
        }}
      >
        {/* 添加灯光 */}
        <ambientLight intensity={0.5} />
        <directionalLight position={[5, 5, 5]} intensity={0.5} />
        {/* 加载模型 */}
        <PhoneModel />
        {/* 轨道控制器 */}
        <OrbitControls ref={controls} enabled={false} />
      </Canvas>
    </div>
  );
}

export default Mobile;

方案二:完全用手动Three.js写法(适合纯Three.js场景)

保留useEffect里的代码,但要补上渲染循环(你之前的代码没写,模型其实不会实时渲染),同时删掉return里的<Canvas>组件,只留容器:

import { useEffect, useRef } from 'react';
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; // 如果需要控制器

function Mobile() {
  const containerRef = useRef();
  const cameraProps = { /* 填入你的相机参数 */ };
  const controls = useRef();

  useEffect(() => {
    const scene = new THREE.Scene();
    
    // 添加灯光
    const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
    scene.add(ambientLight);
    const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
    directionalLight.position.set(5, 5, 5);
    scene.add(directionalLight);

    const camera = new THREE.PerspectiveCamera(cameraProps.fov, cameraProps.aspect, cameraProps.near, cameraProps.far);
    camera.position.copy(cameraProps.position);
    camera.lookAt(0.5, 0, 0);

    const renderer = new THREE.WebGLRenderer({ alpha: true });
    const containerWidth = window.innerWidth;
    const containerHeight = window.innerHeight;
    renderer.setSize(containerWidth, containerHeight);
    renderer.setClearColor(0x000000, 0);
    containerRef.current.appendChild(renderer.domElement);

    // 添加轨道控制器(如果需要)
    controls.current = new OrbitControls(camera, renderer.domElement);
    controls.current.enabled = false;

    // 加载模型
    let phoneModel = null;
    const gltfLoader = new GLTFLoader();
    gltfLoader.load("/phone_port.gltf", (gltfScene) => {
      phoneModel = gltfScene.scene;
      phoneModel.scale.set(0.5, 0.5, 0.5);
      scene.add(phoneModel);
    });

    // 渲染循环
    const animate = () => {
      requestAnimationFrame(animate);
      controls.current?.update(); // 更新控制器
      renderer.render(scene, camera);
    };
    animate();

    // 窗口大小适配
    const handleResize = () => {
      const newWidth = window.innerWidth;
      const newHeight = window.innerHeight;
      camera.aspect = newWidth / newHeight;
      camera.updateProjectionMatrix();
      renderer.setSize(newWidth, newHeight);
    };
    window.addEventListener('resize', handleResize);

    // 清理资源
    return () => {
      window.removeEventListener('resize', handleResize);
      renderer.dispose();
      phoneModel && scene.remove(phoneModel);
      controls.current?.dispose();
    };
  }, []);

  return <div ref={containerRef}></div>;
}

export default Mobile;

只要选其中一种方式,把另一套渲染逻辑删掉,就能解决“两个模型”的问题啦!

备注:内容来源于stack exchange,提问作者Sunny Dowari

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.17 12:53:13