使用环境参考
Node.js v16.19.1
正文
独立功能文件
我们不可能一直在 index.ts 中写代码,分离文件:
// init.ts
import * as THREE from 'three'export const initScene = () => {const scene = new THREE.Scene()scene.background = new THREE.Color('white')const light = new THREE.AmbientLight('white', 1.3)scene.add(light)return scene
}export const initCamera = () => {const camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, 0.1, 1000)camera.position.set(0, 3, 5)camera.lookAt(new THREE.Vector3(0, 0, 0))return camera
}export const initWebGLRenderer = () => {const renderer = new THREE.WebGLRenderer({ antialias: true })renderer.setSize(window.innerWidth, window.innerHeight)document.body.appendChild(renderer.domElement)return renderer
}
// load.ts
import * as THREE from 'three'
import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'export const loadGLTF = (url: string) => new Promise<THREE.Group>((resolve, reject) => {const loader = new GLTFLoader()loader.load(url, (gltf: GLTF) => {console.log(gltf)resolve(gltf.scene)})
})
// index.ts
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { initCamera, initScene, initWebGLRenderer } from './init'
import { loadGLTF } from './load'class Game {scene: THREE.Scenecamera: THREE.PerspectiveCamerarenderer: THREE.WebGLRendererorbitControls: OrbitControlsconstructor() {this.scene = initScene()this.camera = initCamera()this.scene.add(this.camera)this.renderer = initWebGLRenderer()this.orbitControls = this.addOrbitControls(this.camera, this.renderer)this.addModel()this.addResizeEventListener()}addOrbitControls(camera: THREE.Camera, renderer: THREE.WebGLRenderer) {const controls = new OrbitControls(camera, renderer.domElement)controls.autoRotate = truecontrols.enableDamping = truecontrols.update()return controls}async addModel() {const model = await loadGLTF('gltf/SheenChair.glb')this.scene.add(model)}addResizeEventListener() {window.addEventListener('resize', () => {this.camera.aspect = window.innerWidth / window.innerHeightthis.camera.updateProjectionMatrix()this.renderer.setSize(window.innerWidth, window.innerHeight)})}startMainLoop() {// 等待一帧用于初始化Promise.resolve().then(() => {this.step()})}step() {requestAnimationFrame(this.step.bind(this))this.orbitControls && this.orbitControls.update()this.renderer.render(this.scene, this.camera)}
}const game = new Game()
game.startMainLoop()
射线检测
鼠标点击物体是最常见的一个需求,对 dom 新增点击事件,然后计算相对于 canvas 的坐标比例,计算进 three 的坐标系(-1 ~ 1)。
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { initCamera, initScene, initWebGLRenderer } from './init'
import { loadGLTF } from './load'class Game {scene: THREE.Scenecamera: THREE.PerspectiveCamerarenderer: THREE.WebGLRendererorbitControls: OrbitControlsraycaster = new THREE.Raycaster()mouse = new THREE.Vector2()constructor() {this.scene = initScene()this.camera = initCamera()this.scene.add(this.camera)this.renderer = initWebGLRenderer()this.orbitControls = this.addOrbitControls(this.camera, this.renderer)this.addModel()this.addResizeEventListener()this.addClickEvent()}addClickEvent() {this.renderer.domElement.addEventListener('click', (ev) => {this.mouse.x = (ev.clientX / window.innerWidth) * 2 - 1this.mouse.y = -(ev.clientY / window.innerHeight) * 2 + 1this.raycaster.setFromCamera(this.mouse, this.camera)const intersects = this.raycaster.intersectObject(this.scene, true)console.log(intersects)})}addOrbitControls(camera: THREE.Camera, renderer: THREE.WebGLRenderer) {const controls = new OrbitControls(camera, renderer.domElement)controls.autoRotate = truecontrols.enableDamping = truecontrols.update()return controls}async addModel() {const model = await loadGLTF('gltf/SheenChair.glb')this.scene.add(model)}addResizeEventListener() {window.addEventListener('resize', () => {this.camera.aspect = window.innerWidth / window.innerHeightthis.camera.updateProjectionMatrix()this.renderer.setSize(window.innerWidth, window.innerHeight)})}startMainLoop() {// 等待一帧用于初始化Promise.resolve().then(() => {this.step()})}step() {requestAnimationFrame(this.step.bind(this))this.orbitControls && this.orbitControls.update()this.renderer.render(this.scene, this.camera)}
}const game = new Game()
game.startMainLoop()
动画轨道
鼠标点击某个模型,对这个模型进行动画处理。
新建一个 animation.ts 负责处理动画
// animation.ts
import * as THREE from 'three'export class AnimationManager {mixers: THREE.AnimationMixer[] = []clock = new THREE.Clock()addOnePosAnima(obj: THREE.Object3D) {const positionTimes = [0, 1, 2]const positionArr = [0, 0, 0,0, 1, 0,0, 0, 0]const track = new THREE.VectorKeyframeTrack(`${obj.name}.position`,positionTimes,positionArr,THREE.InterpolateSmooth)// 动画名称,持续时间,trackconst clip = new THREE.AnimationClip('clip1', 2, [track])const mixer = new THREE.AnimationMixer(obj)const action = mixer.clipAction(clip)action.play()this.mixers.push(mixer)}step() {const dt = this.clock.getDelta()this.mixers.forEach(m => m.update(dt))}
}
在 index.ts 中,点击到哪儿模型就为其添加一个动画。
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { initCamera, initScene, initWebGLRenderer } from './init'
import { loadGLTF } from './load'
import { AnimationManager } from './animation'class Game {scene: THREE.Scenecamera: THREE.PerspectiveCamerarenderer: THREE.WebGLRendererorbitControls: OrbitControlsraycaster = new THREE.Raycaster()mouse = new THREE.Vector2()animationManager = new AnimationManager()constructor() {this.scene = initScene()this.camera = initCamera()this.scene.add(this.camera)this.renderer = initWebGLRenderer()this.orbitControls = this.addOrbitControls(this.camera, this.renderer)this.addModel()this.addResizeEventListener()this.addClickEvent()}addClickEvent() {this.renderer.domElement.addEventListener('click', (ev) => {this.mouse.x = (ev.clientX / window.innerWidth) * 2 - 1this.mouse.y = -(ev.clientY / window.innerHeight) * 2 + 1this.raycaster.setFromCamera(this.mouse, this.camera)const intersects = this.raycaster.intersectObject(this.scene, true)console.log(intersects)if (intersects[0]) {this.animationManager.addOnePosAnima(intersects[0].object)}})}addOrbitControls(camera: THREE.Camera, renderer: THREE.WebGLRenderer) {const controls = new OrbitControls(camera, renderer.domElement)controls.autoRotate = truecontrols.enableDamping = truecontrols.update()return controls}async addModel() {const model = await loadGLTF('gltf/SheenChair.glb')this.scene.add(model)}addResizeEventListener() {window.addEventListener('resize', () => {this.camera.aspect = window.innerWidth / window.innerHeightthis.camera.updateProjectionMatrix()this.renderer.setSize(window.innerWidth, window.innerHeight)})}startMainLoop() {// 等待一帧用于初始化Promise.resolve().then(() => {this.step()})}step() {requestAnimationFrame(this.step.bind(this))this.orbitControls && this.orbitControls.update()this.animationManager.step()this.renderer.render(this.scene, this.camera)}
}const game = new Game()
game.startMainLoop()
要注意模型是由多个子 Mesh 组成,如果想动整体,查询 parent 或者提前用变量存好或者记录 name 进行查找。
更多文章与分享
Three 学习项目链接:https://github.com/KuoKuo666/threejs-study
个人网站:www.kuokuo666.com
2023!Day Day Up!