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

前端调用后端GLB文件显示黑屏,求可交互渲染的代码修改方案

解决GLB模型渲染黑屏问题

问题描述

我尝试像加载2D图片一样从后端加载GLB文件,但前端渲染时出现黑屏。原代码遇到GLB文件只会显示文本"Sorry GLB File",我想要实现可交互的GLB渲染效果,已经写了基于THREE.js的GLBModelRenderer组件,但还是无法正常显示,求代码修改方案。

原代码片段

{!!images?.length &&
  images.map((link) => (
    <div key={link} className="h-24 bg-white p-4 shadow-sm rounded-sm border border-gray-200">
      {link.endsWith(".glb") ? (
        <div>Sorry GLB File</div>
      ) : (
        <img src={link} alt="" className="rounded-lg" />
      )}
    </div>
  ))}

已编写的GLBModelRenderer组件代码

const GLBModelRenderer = ({ link }) => {
  const canvasRef = useRef(null);

  useEffect(() => {
    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
    const renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(window.innerWidth, window.innerHeight);
    canvasRef.current.appendChild(renderer.domElement);

    const loader = new GLTFLoader();
    const dracoLoader = new DRACOLoader();
    dracoLoader.setDecoderPath('https://www.gstatic.com/draco/v1/decoders/'); 
    loader.setDRACOLoader(dracoLoader); 

    loader.load(
      link,
      (gltf) => {
        scene.add(gltf.scene);
        camera.position.z = 5;

        const animate = function () {
          requestAnimationFrame(animate);
          renderer.render(scene, camera);
        };

        animate();
      },
      (xhr) => {
        console.log(`${(xhr.loaded / xhr.total) * 100}% loaded`);
      },
      (error) => {
        console.error('Error loading GLB model:', error);
      }
    );

    return () => {
      // Clean up resources
      renderer.dispose();
    };
  }, [link]);

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

// 使用组件的代码
return (
  {!!images?.length &&
    images.map((link) => (
      <div key={link} className="h-24 bg-white p-4 shadow-sm rounded-sm border border-gray-200">
        {link.endsWith(".glb") ? (
          <GLBModelRenderer link={link} />
        ) : (
          <img src={link} alt="" className="rounded-lg" />
        )}
      </div>
    ))}
);

当前渲染效果

渲染黑屏效果

问题分析

  1. 画布尺寸与容器不匹配:容器固定为h-24,但渲染器直接使用窗口尺寸,导致画布溢出或显示异常
  2. 缺少光源:THREE.js场景默认无光源,GLB模型没有光照会完全黑屏
  3. 相机位置未适配模型:固定camera.position.z=5可能无法让模型完全出现在视野中
  4. 资源清理不彻底:仅释放渲染器资源,未取消动画帧请求和清理场景对象
  5. 无交互控制:没有实现模型旋转、缩放等交互功能,达不到示例效果

修改后的代码

1. 完善GLBModelRenderer组件

import { useEffect, useRef } from 'react';
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

const GLBModelRenderer = ({ link }) => {
  const containerRef = useRef(null);
  const sceneRef = useRef(null);
  const rendererRef = useRef(null);
  const controlsRef = useRef(null);
  const animationIdRef = useRef(null);

  useEffect(() => {
    if (!containerRef.current) return;

    // 初始化场景、相机、渲染器
    const scene = new THREE.Scene();
    sceneRef.current = scene;
    scene.background = new THREE.Color(0xf5f5f5);

    const container = containerRef.current;
    const aspectRatio = container.clientWidth / container.clientHeight;
    const camera = new THREE.PerspectiveCamera(75, aspectRatio, 0.1, 1000);
    
    const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
    rendererRef.current = renderer;
    renderer.setSize(container.clientWidth, container.clientHeight);
    renderer.setPixelRatio(window.devicePixelRatio);
    container.appendChild(renderer.domElement);

    // 添加光源(必须)
    const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
    scene.add(ambientLight);
    const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
    directionalLight.position.set(5, 10, 7.5);
    scene.add(directionalLight);

    // 添加交互控制
    const controls = new OrbitControls(camera, renderer.domElement);
    controlsRef.current = controls;
    controls.enableDamping = true;
    controls.dampingFactor = 0.05;
    controls.minDistance = 1;
    controls.maxDistance = 20;

    // 加载GLB模型
    const loader = new GLTFLoader();
    const dracoLoader = new DRACOLoader();
    dracoLoader.setDecoderPath('https://www.gstatic.com/draco/v1/decoders/');
    loader.setDRACOLoader(dracoLoader);

    loader.load(
      link,
      (gltf) => {
        scene.add(gltf.scene);

        // 自动调整相机位置适配模型
        const box = new THREE.Box3().setFromObject(gltf.scene);
        const center = box.getCenter(new THREE.Vector3());
        const size = box.getSize(new THREE.Vector3());
        const maxDim = Math.max(size.x, size.y, size.z);
        const fov = camera.fov * (Math.PI / 180);
        const cameraZ = Math.abs(maxDim / (2 * Math.tan(fov / 2))) * 1.2;
        
        camera.position.z = cameraZ;
        controls.target.copy(center);
        controls.update();
      },
      (xhr) => {
        console.log(`${(xhr.loaded / xhr.total) * 100}% loaded`);
      },
      (error) => {
        console.error('GLB模型加载失败:', error);
      }
    );

    // 动画循环
    const animate = () => {
      animationIdRef.current = requestAnimationFrame(animate);
      controls.update();
      renderer.render(scene, camera);
    };
    animate();

    // 窗口大小变化适配
    const handleResize = () => {
      if (!containerRef.current) return;
      const aspect = containerRef.current.clientWidth / containerRef.current.clientHeight;
      camera.aspect = aspect;
      camera.updateProjectionMatrix();
      renderer.setSize(containerRef.current.clientWidth, containerRef.current.clientHeight);
    };
    window.addEventListener('resize', handleResize);

    // 资源清理
    return () => {
      window.removeEventListener('resize', handleResize);
      if (animationIdRef.current) cancelAnimationFrame(animationIdRef.current);
      if (controlsRef.current) controlsRef.current.dispose();
      if (rendererRef.current) rendererRef.current.dispose();
      if (sceneRef.current) {
        sceneRef.current.traverse((obj) => {
          if (obj.geometry) obj.geometry.dispose();
          if (obj.material) {
            Array.isArray(obj.material) ? obj.material.forEach(m => m.dispose()) : obj.material.dispose();
          }
        });
      }
    };
  }, [link]);

  return <div ref={containerRef} className="w-full h-full" />;
};

export default GLBModelRenderer;

2. 使用组件的代码

{!!images?.length &&
  images.map((link) => (
    <div key={link} className="h-24 bg-white p-4 shadow-sm rounded-sm border border-gray-200">
      {link.endsWith(".glb") ? (
        <GLBModelRenderer link={link} />
      ) : (
        <img src={link} alt="" className="w-full h-full object-cover rounded-lg" />
      )}
    </div>
  ))}

关键修改点

  • 添加光源系统:环境光+方向光,确保模型能被照亮,解决黑屏核心问题
  • 适配容器尺寸:渲染器尺寸与父容器h-24完全匹配,避免画布溢出
  • 自动相机适配:通过模型包围盒计算合适的相机位置,确保模型完整显示
  • 加入OrbitControls:实现模型旋转、缩放、平移交互,达到示例效果
  • 完善资源清理:取消动画帧、释放控制器和渲染器资源,避免内存泄漏
  • 响应式处理:监听窗口 resize,动态更新相机和渲染器参数

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 09:25:04