Skip to content

GLTFLoader 和 DRACOLoader

这是一个可以直接在 VitePress 页面中运行的 Three.js 示例。上方显示运行效果,下方展示对应源码。

运行效果

源码

js
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js'
import { GUI } from 'three/addons/libs/lil-gui.module.min.js'

const canvas = document.querySelector('canvas')
const demo = canvas.parentElement

// 浏览器不能直接读取 public/models 目录,所以这里用数组维护可选择模型。
const modelOptions = [
	{
		name: 'ironman_mk44.glb',
		path: withBase('/models/ironman_mk44.glb')
	},
	{
		name: 'porsche_911.glb',
		path: withBase('/models/porsche_911.glb')
	},
	{
		name: 'drogon.glb',
		path: withBase('/models/drogon.glb')
	},
	{
		name: 'gun.glb',
		path: withBase('/models/gun.glb')
	},
	{
		name: 'city_pack.glb',
		path: withBase('/models/city_pack.glb')
	}
]

const scene = new THREE.Scene()
scene.background = new THREE.Color('#111827')

const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000)
camera.position.set(0, 1.8, 4.5)

const renderer = new THREE.WebGLRenderer({
	canvas,
	antialias: true
})

renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
renderer.outputColorSpace = THREE.SRGBColorSpace
renderer.setSize(window.innerWidth, window.innerHeight)

const ambientLight = new THREE.AmbientLight('#ffffff', 1.1)
scene.add(ambientLight)

const directionalLight = new THREE.DirectionalLight('#ffffff', 2)
directionalLight.position.set(4, 6, 5)
scene.add(directionalLight)

const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
controls.target.set(0, 0.8, 0)

const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath('/draco/')

const gltfLoader = new GLTFLoader()
gltfLoader.setDRACOLoader(dracoLoader)

let currentModel = null

const removeCurrentModel = () => {
	if (!currentModel) {
		return
	}

	scene.remove(currentModel)
	currentModel = null
}

const moveModelCenterToOrigin = (model) => {
	const box = new THREE.Box3().setFromObject(model)
	const center = box.getCenter(new THREE.Vector3())

	model.position.sub(center)
}

const loadModel = (modelName) => {
	const option = modelOptions.find((item) => item.name === modelName)

	if (!option) {
		return
	}

	gltfLoader.load(option.path, (gltf) => {
		removeCurrentModel()
		currentModel = gltf.scene
		moveModelCenterToOrigin(currentModel)
		scene.add(currentModel)
	})
}

const guiState = {
	model: modelOptions[0].name
}

const gui = new GUI({
	title: '模型控制',
	autoPlace: false
})

gui.domElement.classList.add('three-gui')
demo.appendChild(gui.domElement)

gui
	.add(guiState, 'model', modelOptions.map((item) => item.name))
	.name('模型')
	.onChange(loadModel)

loadModel(guiState.model)

function animate() {
	requestAnimationFrame(animate)

	controls.update()
	renderer.render(scene, camera)
}

animate()