vue3 + three.js + gltf-transform 实现3D文件展示

版本

three.js ^0.163.0
@gltf-transform/core @gltf-transform/extensions @gltf-transform/functions   ^3.10.1

three.js 147版之前的版本支持KHR_materials_pbrSpecularGlossiness扩展,可不用gltf-transform

参考文档

three.js文档 – three.js docs (threejs.org)
glTF Transform 文档 (gltf-transform.dev)
three.js - THREE.GLTFLoader: Unknown extension "KHR_materials_pbrSpecularGlossiness - Stack Overflow
three.js/examples/jsm/loaders at dev · mrdoob/three.js 查询加载器(github.com)

代码

自定义方法(three_3d.js)
// 根据后缀创建loader
export const three_3d_loader_init = (suffix) => {
  switch (suffix) {
    case 'fbx':
      return new FBXLoader()
    case 'obj':
      return new OBJLoader()
    case 'gltf':
    case 'glb':
      return new GLTFLoader()
    default:
      return null
  }
}

// 调整模型大小不超出展示容器
export const three_3d_model_self_adaption = (model) => {
  const box = new THREE.Box3().setFromObject(model);
  const center = new THREE.Vector3();
  box.getCenter(center);

  const size = box.getSize(new THREE.Vector3());
  // console.log(size)
  // const maxSize = Math.max(size.x, size.y, size.z);

  const scaleX = size.x > 1 ? 1 / size.x : size.x ;
  const scaleY = size.y > 1 ? 1 / size.y : size.y ;
  const scaleZ = size.z > 1 ? 1 / size.z : size.z ;

  // 因为是展示容器为600*600,为了模型不太小所以乘以5,
  const a = Math.max(scaleX, scaleY, scaleZ) * 5
  model.scale.set(a, a, a); // 模型缩放比例
}
实现代码
<div ref="three_3d"></div>
<script setup>
const boxWidth = 600
const boxHeight = 600

// 创建3D场景对象Scene
const scene = new THREE.Scene();
// scene.background = new THREE.Color(165,165,165); // 设置背景颜色为红色

const camera = new THREE.PerspectiveCamera(45, boxWidth / boxHeight, 0.1, 20000);
camera.position.set(-15, 5, 15);
scene.add(camera)

//添加光源
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
// ambientLight.position.set(-1, -1, 0); // 设置环境光源的位置
scene.add(ambientLight);

// 平行光
//右上角
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(1, 1, 0);
scene.add(directionalLight);
//左下角
const directionalLight2 = new THREE.DirectionalLight(0xffffff, 1);
directionalLight2.position.set(-1, -1, 0);
scene.add(directionalLight2);
//右下角
const directionalLight3 = new THREE.DirectionalLight(0xffffff, 1);
directionalLight3.position.set(1, -1, 0);
scene.add(directionalLight3);
//左上角
const directionalLight4 = new THREE.DirectionalLight(0xffffff, 1);
directionalLight4.position.set(-1, 1, 0);
scene.add(directionalLight4);

// const pointLight = new THREE.PointLight(0xffffff, 1.0);
// pointLight.position.set(0, 0, 0); // 设置点光源的位置
// scene.add(pointLight);

// 创建渲染器对象
const renderer = new THREE.WebGLRenderer({
  antialias: true,
  alpha: true, //完全透明,没有背景色
});
renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.setSize(boxWidth, boxHeight);
//three_3d为展示的容器
three_3d.value.innerHTML = ''
three_3d.value.appendChild(renderer.domElement)

//添加轨道控制器--鼠标的一些操作缩放旋转等
const controls = new OrbitControls(camera, renderer.domElement);
let mixer
const suffix = el.getAttribute('data-suffix')
const loader = three_3d_loader_init(suffix)
if (!loader) {
  await MessagePlugin.warning('3d加载器初始化失败')
  return
}
if (['glb'].includes(suffix)) {
  const io = new WebIO().registerExtensions(ALL_EXTENSIONS);
  let doc = await io.read(el.getAttribute('data-url'));
  await doc.transform(metalRough());
  const glb = await io.writeBinary(doc);
  loader.parse(glb.buffer, '', (obj) => {
    let object_scene = obj.scene
    // 调整模型大小
    three_3d_model_self_adaption(object_scene, renderer)
    object_scene.position.set(0, -0.2, 0)
    scene.add(object_scene)

    if (obj.animations && obj.animations.length !== 0) {
      mixer = new THREE.AnimationMixer(object_scene)
      const action = mixer.clipAction(obj.animations[0])
      action.play()
    }
  }, (err) => {
    console.error('An error happened', err)
  })
} else {
  loader.load(el.getAttribute('data-url'), (obj) => {
    let object_scene
    if (['gltf', 'glb'].includes(suffix)) {
      object_scene = obj.scene
    } else {
      object_scene = obj
    }
    // 调整模型大小
    three_3d_model_self_adaption(object_scene, renderer)
    object_scene.position.set(0, -0.2, 0)
    scene.add(object_scene)

    if (obj.animations && obj.animations.length !== 0) {
      mixer = new THREE.AnimationMixer(object_scene)
      const action = mixer.clipAction(obj.animations[0])
      action.play()
    }
  }, (xhr) => {
    console.log((xhr.loaded / xhr.total * 100) + '% loaded')
  }, (err) => {
    console.error('An error happened', err)
  })
}

let previousTime = performance.now()
function renderFn() {
  // 动画
  if (mixer) {
    let time = performance.now()
    const timeInSeconds = (time - previousTime) / 1000
    previousTime = time
    mixer.update(timeInSeconds)
  }
  //更新轨道控制器
  controls.update();
  //开始渲染模型和dom元素
  renderer.render(scene, camera);
  //浏览器自带的函数 每秒60帧更新
  requestAnimationFrame(renderFn);
}

renderFn();
</script>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容