You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何实现拖拽上传任意模型文件并通过Three.js在浏览器端展示?

解决Three.js加载用户上传模型的问题

嘿,我懂你的困惑了——你知道怎么用GLTFLoader加载服务器上的模型文件,但用户上传的本地文件没有现成的可访问路径,不知道该怎么喂给加载器对吧?其实你没遗漏核心概念,只是需要调整加载逻辑,把用户上传的File对象转换成Three.js能处理的资源就行。

下面是具体的解决思路和修改后的完整代码:

核心逻辑梳理

用户通过上传按钮选择的文件会变成浏览器里的File对象,我们没法直接用它的本地路径(浏览器出于安全限制也不会暴露),但可以通过两种方式让Three.js加载它:

  • URL.createObjectURL()生成一个临时的Blob URL(这个URL只在当前页面会话有效,不会上传到服务器)
  • FileReader读取文件的ArrayBuffer,再调用加载器的parse()方法解析内容

第一种方式更简单,和你熟悉的loader.load()逻辑几乎一致,我下面就用这种方式来演示。

完整修改后的代码

首先,得确保你引入了对应的模型加载器(比如GLTFLoader,Three.js核心库不包含这个),我用CDN引入方便测试,你也可以换成本地文件:

<!DOCTYPE html>
<html>
<head>
<title>3D Model Viewer</title>
<script src="//code.jquery.com/jquery-1.11.1.min.js"></script>
<link href="//maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet">
<script src="//maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="index.css">
<!-- 引入Three.js核心库和GLTFLoader -->
<script src="https://cdn.jsdelivr.net/npm/three@0.158.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.158.0/examples/js/loaders/GLTFLoader.js"></script>
</head>
<body>
<div class="container mt-4">
  <div class="form-group files">
    <!-- 添加accept属性限制可上传的模型格式,提升用户体验 -->
    <input type="file" class="form-control" accept=".glb,.gltf" multiple>
  </div>
</div>
<div class="container">
  <div class='showObject' id="canvas-container"></div>

<script>
// 初始化Three.js场景、相机、渲染器
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xf0f0f0);
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);
// 把渲染器的canvas放到指定容器里,避免直接加到body影响布局
document.getElementById('canvas-container').appendChild(renderer.domElement);

// 可选:添加轨道控制器,让用户可以拖拽旋转模型
import {OrbitControls} from 'https://cdn.jsdelivr.net/npm/three@0.158.0/examples/jsm/controls/OrbitControls.js';
const controls = new OrbitControls(camera, renderer.domElement);
camera.position.z = 5;
controls.update();

// 动画循环
function animate() {
  requestAnimationFrame(animate);
  controls.update();
  renderer.render(scene, camera);
}
animate();

// 监听文件上传事件
const fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', handleFileUpload);

function handleFileUpload(e) {
  const files = e.target.files;
  if (!files.length) return;

  // 可选:清空场景里已有的模型,避免重叠
  scene.children = scene.children.filter(child => !(child instanceof THREE.Mesh) && !(child instanceof THREE.Group));

  // 遍历所有上传的文件
  for (const file of files) {
    const fileExt = file.name.split('.').pop().toLowerCase();
    if (fileExt === 'glb' || fileExt === 'gltf') {
      loadGLTFModel(file);
    } else {
      alert(`暂时不支持${fileExt}格式的模型哦`);
    }
  }
}

// 核心:加载用户上传的GLB/GLTF模型
function loadGLTFModel(file) {
  const loader = new GLTFLoader();
  // 生成临时的Blob URL,把File对象转换成加载器能识别的URL
  const blobURL = URL.createObjectURL(file);
  
  loader.load(
    blobURL,
    function(gltf) {
      const model = gltf.scene;
      // 可选:调整模型的位置和缩放,适配场景
      model.position.set(0, 0, 0);
      model.scale.set(1, 1, 1);
      scene.add(model);
      
      // 重要:加载完成后释放Blob URL,避免内存泄漏
      URL.revokeObjectURL(blobURL);
    },
    function(xhr) {
      // 可选:显示加载进度
      console.log(`加载中:${Math.round(xhr.loaded / xhr.total * 100)}%`);
    },
    function(error) {
      console.error('模型加载失败:', error);
      // 加载失败也要释放URL
      URL.revokeObjectURL(blobURL);
    }
  );
}

// 窗口大小调整时更新相机和渲染器
window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});
</script>
</div>
</body>
</html>

关键步骤拆解

  1. 监听文件上传:通过change事件获取用户选择的File对象,这是浏览器对本地文件的抽象
  2. 生成Blob URLURL.createObjectURL(file)会生成一个临时的、浏览器内部可访问的URL,相当于给本地文件临时分配了一个“在线地址”
  3. 用加载器加载:把这个Blob URL传给GLTFLoader.load(),和你之前加载服务器路径的逻辑完全一样
  4. 清理资源:加载完成或失败后,一定要用URL.revokeObjectURL(blobURL)释放这个临时URL,不然会占用浏览器内存

扩展提示

  • 如果要支持其他模型格式(比如OBJ、FBX),只需要引入对应的加载器(比如OBJLoaderFBXLoader),然后用同样的Blob URL方式加载就行
  • 可以给文件输入框加accept属性,限制用户只能选择支持的模型格式,减少错误
  • 可以添加加载动画、错误提示,让用户体验更好

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

火山引擎 最新活动