vue3 + three.js + gltf-transform 實現(xiàn)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)
// 根據(jù)后綴創(chuàng)建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
  }
}

// 調(diào)整模型大小不超出展示容器
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); // 模型縮放比例
}
實現(xiàn)代碼
<div ref="three_3d"></div>
<script setup>
const boxWidth = 600
const boxHeight = 600

// 創(chuàng)建3D場景對象Scene
const scene = new THREE.Scene();
// scene.background = new THREE.Color(165,165,165); // 設(shè)置背景顏色為紅色

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); // 設(shè)置環(huán)境光源的位置
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); // 設(shè)置點光源的位置
// scene.add(pointLight);

// 創(chuàng)建渲染器對象
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)

//添加軌道控制器--鼠標(biāo)的一些操作縮放旋轉(zhuǎn)等
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
    // 調(diào)整模型大小
    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
    }
    // 調(diào)整模型大小
    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);
  //瀏覽器自帶的函數(shù) 每秒60幀更新
  requestAnimationFrame(renderFn);
}

renderFn();
</script>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末邮丰,一起剝皮案震驚了整個濱河市剪廉,隨后出現(xiàn)的幾起案子炕檩,更是在濱河造成了極大的恐慌,老刑警劉巖吹泡,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件经瓷,死亡現(xiàn)場離奇詭異,居然都是意外死亡舆吮,警方通過查閱死者的電腦和手機队贱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進店門柱嫌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來编丘,“玉大人彤悔,你說我怎么就攤上這事∫制” “怎么了?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵敞斋,是天一觀的道長植捎。 經(jīng)常有香客問我阳柔,道長,這世上最難降的妖魔是什么医咨? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任架诞,我火速辦了婚禮谴忧,結(jié)果婚禮上角虫,老公的妹妹穿的比我還像新娘。我一直安慰自己戳鹅,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布妇穴。 她就那樣靜靜地躺著,像睡著了一般跑筝。 火紅的嫁衣襯著肌膚如雪曲梗。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天虏两,我揣著相機與錄音碘举,去河邊找鬼搁廓。 笑死,一個胖子當(dāng)著我的面吹牛境蜕,可吹牛的內(nèi)容都是我干的粱年。 我是一名探鬼主播,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼完箩,長吁一口氣:“原來是場噩夢啊……” “哼弊知!你這毒婦竟也來了粱快?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤漫雷,失蹤者是張志新(化名)和其女友劉穎降盹,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蓄坏,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年干旧,在試婚紗的時候發(fā)現(xiàn)自己被綠了妹蔽。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡编整,死狀恐怖乳丰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情汞斧,我是刑警寧澤什燕,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布屎即,位于F島的核電站,受9級特大地震影響技俐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜啡邑,卻給世界環(huán)境...
    茶點故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一谣拣、第九天 我趴在偏房一處隱蔽的房頂上張望族展。 院中可真熱鬧拔鹰,春花似錦、人聲如沸恰画。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽片林。三九已至,卻和暖如春焕妙,著一層夾襖步出監(jiān)牢的瞬間弓摘,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留锤窑,地道東北人。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓陈莽,卻偏偏與公主長得像虽抄,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子私植,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,947評論 2 355

推薦閱讀更多精彩內(nèi)容