版本
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>