前端调用后端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> ))} );
当前渲染效果

问题分析
- 画布尺寸与容器不匹配:容器固定为
h-24,但渲染器直接使用窗口尺寸,导致画布溢出或显示异常 - 缺少光源:THREE.js场景默认无光源,GLB模型没有光照会完全黑屏
- 相机位置未适配模型:固定
camera.position.z=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
相关产品推荐
相关产品推荐

