Realization of 3D Scene animation based on Threejs + TweenJS (Hand-touch teaching)
The effect
With the front end is more and more volume, the requirements of the front end is also more and more high, from the visual effect of continuous towards 3D has always wanted to learn Threejs, forced to have no time, just idle down a week (record the entry threejs) ✨ to achieve a 3D color car and scene animation ✨Copy the code
Knowledge architecture
├─ ├─ webglesimally, ├─ ├─ webglesimally, └─ Fog ├ ─ uninhibited-press, uninhibited-press, uninhibited-press, uninhibited-press, uninhibited-press, uninhibited-press, uninhibited-press, uninhibited-press, uninhibited-press, uninhibited-press, uninhibited-press, uninhibited-press, uninhibited-press └ └─ MeshLambertMaterial ├ ─ FBXLoader ├ ─ GLTFLoader ├ ─ RaycasterCopy the code
Tweenjs
import TWEEN from '@tweenjs/tween.js'
setTweens(obj, newObj, duration = 1500) {
var ro = new TWEEN.Tween(obj) // Create a tween animation instance
ro.to(newObj, duration) // The changed object and the animation duration
ro.easing(TWEEN.Easing.Sinusoidal.InOut) // Animation buffer function
ro.onUpdate(function() {}) // Perform the callback
ro.start()
},
Copy the code
Threejs
Threejs document
<template>
<div>
<div id="cart"></div>
<div class="main">body<div class="flex">
<img style="width: 100px; height: 50px" src="/glb/cart.png" alt="">
<el-color-picker v-model="mainColor"></el-color-picker>
</div>
</div>
<div class="tirm">interior<div class="flex">
<img style="width: 100px; height: 50px" src="/glb/trim.png" alt="">
<el-color-picker v-model="trimColor"></el-color-picker>
</div>
</div>
<div class="into" @click="intoCart">Get on the bus<img style="width: 50px; height: 50px" src="/glb/into.png" alt="">
</div>
<div class="out" @click.stop="outCart">Get off the bus<img style="width: 50px; height: 50px" src="/glb/out.png" alt="">
</div>
<div v-show="loding" v-loading.fullscreen.lock="loding" class="mask"></div>
<div
v-show="loding"
style="z-index: 9999; position: absolute; top: 53%; left: 50%; width: 300px; transform:translate(-50%,0)"
>
<el-progress :text-inside="true" :stroke-width="26" :percentage="this.percentage"></el-progress>
</div>
</div>
</template>
Copy the code
import * as THREE from '@/plugins/ys3d/threeLibs/three.module'
data() {
return {
loding: true./ / load
mainColor: ' './ / color
trimColor: ' './ / color
marker: ' '.// Open the door
selectedObjects: null.// Click the selected module
renderer: null.main: null./ / body
trim: null./ / interior
cart: null./ / car model
wheel: null./ / wheel
door: null.// The first door
scene: null.camera: null.mixer: null.// Car model animation
mixer1: null.// House model animation
isOpen: false.// Whether to open the door
controlsObj: null.// Manipulate the screen object
plane: null.// Plane model
percentage: 0// Load progress}}async init() {
// Create a scene
this.scene = new THREE.Scene()
/ / camera
this.camera = new THREE.PerspectiveCamera(70.1.1.1000)/ / camera
this.renderer = new THREE.WebGLRenderer({
antialias: true
})
this.renderer.setSize(window.innerWidth, window.innerHeight)
document.getElementById('cart').append(this.renderer.domElement)
this.renderer.setClearColor('# 000000')
// Add atomizer
this.scene.fog = new THREE.Fog(0xffffff.200.1000)
// Create light source
this.lights(this.scene)
// Create plane
this.loadPlan(this.scene)
// Load the model
await this.loadFbx(this.scene, this.camera)
await this.loadGlb(this.scene, this.camera)
// Create an environment map
await this.initSky(this.scene)
// Create a controller
this.controls(this.scene, this.camera, this.renderer)
this.renderer.render(this.scene, this.camera)
}
Copy the code
Scene Scene
const scene = newTHREE.Scene() can be thought of as creating a canvas boxCopy the code
WebGLRenderer renderer
const renderer = new THREE.WebGLRenderer({
antialias: trueAnti-aliasing... Related properties view threejs documentation})Copy the code
Camera Camera
camera = new THREE.PerspectiveCamera(70.window.innerWidth / window.innerHeight, 1.1000) camera. The camera position. Set (30.10.30Camera position camera.lookat (camera.position) means that the camera looks at a position in three dimensionsCopy the code
Fog spray
scene.fog = new THREE.Fog(0xffffff.200.1000) added border effect in the distanceCopy the code
Towns light source
New THREE.SpotLight New THREE.PointLightHelper Light auxiliary containerCopy the code
const sportLight = new THREE.SpotLight(0xffffffSportlight.position.set (sportlight.position.set)0.1200.0) Light source location scene.add(sportLight)const sportLight1 = new THREE.SpotLight(0xffffff) add sportlight1.position.set (-)30.20.10)
scene.add(sportLight1)
const sportLight2 = new THREE.SpotLight(0xffffffSportlight2.position.set ()0.0.300)
scene.add(sportLight2)
const sportLight4 = new THREE.SpotLight(0xffffffSportlight4.position.set ()30.0.0)
scene.add(sportLight4)
// scene.add(new three.pointlighthelper (sportLight1, 1))
Copy the code
OrbitControls controller
New OrbitControls creates the controllerCopy the code
const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
// The dynamic damping coefficient is the mouse drag rotation sensitivity
/ / controls. DampingFactor = 0.5
// Whether it can be scaled
controls.enableZoom = true
// Whether to rotate automatically
controls.autoRotate = true
controls.autoRotateSpeed = 0.3
// Set the maximum distance of the camera from the origin
controls.minDistance = 1
// Set the maximum distance of the camera from the origin
controls.maxDistance = 1200
// Whether to enable right-click drag
controls.enablePan = true
Copy the code
Animate function
New THREE.Clock Clock object clock.getDelta Current number of secondsCopy the code
const that = this
const clock = newTHREE.Clock() Clock objectfunction animate() {
// controls.update() Updates the controllerTween.update () animation update// Plane movement (road driving effect)
if (that.plane.position.z < -600) {
that.plane.position.set(0, that.plane.position.y, 0)}else {
that.plane.position.set(that.plane.position.x, that.plane.position.y, that.plane.position.z - 2)}// Animates the car door
if (that.marker.material.opacity >= 1) {
that.marker.material.opacity = 0.1
} else {
that.marker.material.opacity += 0.01
}
// The wheel turns
that.wheel.map(cell= > {
cell.rotation.set(cell.rotation.x + 1000, -0.5 * Math.PI, cell.rotation.z)
})
renderer.render(scene, camera) // re-render
requestAnimationFrame(animate) // the timer function
const delta = clock.getDelta() // Current number of seconds
if (that.mixer) {
that.mixer.update(delta)
}
if (that.mixer1) {
that.mixer1.update(delta)
}
if (that.mixer2) {
that.mixer2.update(delta)
}
}
animate()
Copy the code
CubeTextureLoader Environment map (Sky Box)
New three. CubeTextureLoader creates the skybox containerCopy the code
initSky(scene) {
return new Promise((resolve, reject) = > {
// load the image of the six faces of the cube (p n front and back xyz 3d axes)
scene.background = new THREE.CubeTextureLoader().setPath('glb/').load(
[
'nx.jpg'.'nz.jpg'.'py.jpg'.'DN.jpg'.'px.jpg'.'pz.jpg'
]
)
resolve()
})
}
Copy the code
Mesh object
THREE. ImageUtils. LoadTexture loading picture new THREE. The Mesh to create new grid object. THREE PlaneGeometry create rectangle new THREE. MeshLambertMaterial texturingCopy the code
loadPlan(scene) {
var texture = THREE.ImageUtils.loadTexture(
'/glb/grasslight-big.jpg'.null.function(t) {})var texture1 = THREE.ImageUtils.loadTexture(
'/glb/water.jpg'.null.function(t) {})this.plane = new THREE.Mesh(// Create plane
new THREE.PlaneGeometry(300.2000),
new THREE.MeshLambertMaterial({
map: texture,
side: THREE.DoubleSide,// Double-sided material
opacity: 0.5./ / transparency
transparent: true // Whether transparent}))const plane = new THREE.Mesh(// Create water
new THREE.PlaneGeometry(2500.2500),
new THREE.MeshLambertMaterial({
map: texture1,
side: THREE.DoubleSide
})
)
this.plane.rotation.x = -0.5 * Math.PI
this.plane.rotation.z = -1 * Math.PI
this.plane.position.set(0, -2.5.0)
plane.rotation.x = -0.5 * Math.PI
plane.rotation.z = -1 * Math.PI
plane.position.set(0, -14.0)
scene.add(this.plane)
scene.add(plane)
}
Copy the code
FBXLoader
Car model
New FBXLoader FBX model loaderCopy the code
// Car model
loadFbx(scene, camera) {
const that = this
const loader = new FBXLoader()
return new Promise(resolve= > {
loader.load('/glb/MBSL.fbx'.function(obj) {
obj.name = 'cart'// Object name
that.cart = obj // Car object
obj.scale.set(0.004.0.008.0.004)/ / size
obj.position.set(0.0, -4)/ / position
obj.rotation.set(0, -41.0)
that.wheel = obj.children[1].children[5].children// Wheel model group
that.wheel.map(cell= > {// Set the wheel position
cell.rotation.set(cell.rotation.x, -0.5 * Math.PI, cell.rotation.z)
})
that.door = obj.children[1].children[3].children[0].children // The door model group
// Body model
that.main = [obj.children[1].children[0], ...obj.children[1].children[1].children, ... obj.children[1].children[3].children]
// Interior model
that.trim = [...obj.children[1].children[2].children, ... obj.children[1].children[4].children, ... obj.children[1].children[6].children]
scene.add(this.sign())
that.domClick(camera, obj, that)// Model selected events
})
resolve()
})
}
Copy the code
Door marker
THREE. ImageUtils. LoadTexture picture loader new THREE. The new Mesh grid object. THREE new PlaneGeometry rectangular object. THREE MeshLambertMaterial new material Group Group objectCopy the code
sign(obj){
var texture = THREE.ImageUtils.loadTexture( // Door marker
'/glb/marker3.png'.null.function(t) {
}
)
that.marker = new THREE.Mesh(// Create a grid object
new THREE.PlaneGeometry(1.1),/ / marked points
new THREE.MeshLambertMaterial({
map: texture,// Mark the dot map
aoMapIntensity: 0.side: THREE.DoubleSide,
opacity: 0.1.transparent: true.depthTest: false
})
)
that.marker.position.set(2.5.3.3)
that.marker.rotation.y = 0.5 * Math.PI
var group = new THREE.Group()// Combine objects
group.add(obj)
group.add(marker)
return group
}
Copy the code
GLTFLoader
New DRACOLoader compacts the GLTF/GLB model. New THREE.AnimationMixer extracts and executes the model animationCopy the code
loadGlb(scene, camera) {
const loaderModule = new GLTFLoader()
const that = this
return new Promise(resolve= > {
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath('glb/draco/') // Set DRACOLoader file path public
loaderModule.setDRACOLoader(dracoLoader)
loaderModule.load('/glb/LittlestTokyo.glb'.function(gltf) {
// House model
const object = gltf.scene
object.position.set(-300.62, -500)
object.rotation.set(0.0.5 * Math.PI, 0)
object.traverse(function(node) {
if (node.isMesh) node.castShadow = true
})
// get the animation
that.mixer1 = new THREE.AnimationMixer(object)
that.mixer1.clipAction(gltf.animations[0]).play()
object.scale.set(0.3.0.3.0.3)
// add object to scene
scene.add(object)
resolve()
}, this.onProgress)
loaderModule.load('/glb/Parrot.glb'.function(gltf) {
// Bird model
const object = gltf.scene
object.position.set(-150.60, -100)
object.traverse(function(node) {
if (node.isMesh) node.castShadow = true
})
// get the animation
that.mixer2 = new THREE.AnimationMixer(object)
that.mixer2.clipAction(gltf.animations[0]).play()
object.scale.set(0.1.0.1.0.1)
// add object to scene
scene.add(object)
})
})
}
Copy the code
Raycaster
New THREE.Raycaster is used to calculate which models the mouse passes overCopy the code
// Model selected events
domClick(camera, fbx, that) {
const raycaster = new THREE.Raycaster()
const mouse = new THREE.Vector2()
document.addEventListener('click'.function(ev) {
mouse.x = (ev.clientX / window.innerWidth) * 2 - 1
mouse.y = -(ev.clientY / window.innerHeight) * 2 + 1
raycaster.setFromCamera(mouse, camera)
// Here we only check the selected model
const intersects = raycaster.intersectObjects(fbx.children, true)
if (intersects.length > 0) {
that.selectedObjects = intersects[0].object
console.log('selectedObjects:', that.selectedObjects)
switch (that.selectedObjects.name) {
case 'DLP_lak':
if (that.isOpen) { // Close the door
that.isOpen = false
that.selectedObjects.parent.children.map(item= > {
/ / animation
that.setTweens(item.rotation, {
x: item.rotation.x,
y: item.rotation.y + 0.5 * Math.PI,
z: item.rotation.z
}, 1000)})}else {
that.isOpen = true
that.selectedObjects.parent.children.map(item= > { // Open the door
that.setTweens(item.rotation, {
x: item.rotation.x,
y: item.rotation.y - 0.5 * Math.PI,
z: item.rotation.z
}, 1000)})}break}}},false)}Copy the code
Color changing function
Model Model object value Color value recursive implementation to modify the materialCopy the code
setColor(model, value) {
model.map(cell= > {
if (cell.children.length === 0) {
const material = cell.material.clone()
material.color = new THREE.Color(value)
cell.material = material
} else {
this.setColor(cell.children, value)
}
})
}
Copy the code
Into the car
Animation coherence is achieved through delayCopy the code
Graph LR Original View --> Open doors --> Expand view --> Change car position --> Driver view --> Close doors
intoCart() {
this.isOpen = true
this.controlsObj.reset()// Go back to the initialization perspective
this.setTweens(this.camera.position, { x: 30.y: 10.z: 30 }, 1000)
this.door.map(item= > { / / open the door
this.setTweens(item.rotation, {
x: item.rotation.x,
y: item.rotation.y - 0.5 * Math.PI,
z: item.rotation.z
}, 1000)})setTimeout(() = > {
this.setTweens(this.camera.position, { x: 35.y: 15.z: 30 }, 1000)// Expand the perspective
setTimeout(() = > {
this.setTweens(this.camera.position, { x: 0.y: 0.z: 1 }, 1000)// Driving Angle
this.setTweens(this.plane.position, { x: this.plane.position.x, y: -10.z: this.plane.position.z }, 500)// Move the plane down
setTimeout(() = > {
this.cart.position.set(1.5, -6.5)// The car position changes
this.cart.rotation.set(0.0.0)
this.camera.lookAt(this.camera.position)// View the view
this.isOpen = false
this.door.map(item= > {// Close the door
this.setTweens(item.rotation, {
x: item.rotation.x,
y: item.rotation.y + 0.5 * Math.PI,
z: item.rotation.z
}, 1000)})this.renderer.render(this.scene, this.camera)// re-render
}, 1000)},1000)},1000)}Copy the code
Out of the car
Graph LR Driver's view --> Open door --> Original view --> Close door
outCart() {
this.isOpen = true
this.door.map(item= > {// Open the door
this.setTweens(item.rotation, {
x: item.rotation.x,
y: item.rotation.y - 0.5 * Math.PI,
z: item.rotation.z
}, 1000)})setTimeout(() = > {
this.setTweens(this.plane.position, { x: this.plane.position.x, y: -2.5.z: this.plane.position.z }, 500)// Return the lawn to its original position
this.setTweens(this.camera.position, { x: 30.y: 10.z: 30 }, 1000)// Return to the original perspective
this.cart.position.set(0.0, -4)// Return the car to its original position
this.cart.rotation.set(0, -41.0)
this.camera.lookAt(this.camera.position)
this.controlsObj.reset()// Go back to the initialization perspective
setTimeout(() = > {
this.door.map(item= > {
this.setTweens(item.rotation, {// Close the door
x: item.rotation.x,
y: item.rotation.y + 0.5 * Math.PI,
z: item.rotation.z
}, 1000)
this.renderer.render(this.scene, this.camera)
})
}, 1000)},1000)}Copy the code