实现效果说明
- 调用人物模型内置跑步骨骼动画,循环原地播放跑动动作。
- 巨型草坪平面持续向后平移,视觉上实现人物向前无限奔跑。
- 场景添加线性白雾,远处草地逐步雾化,消除平面硬边界,提升空间纵深感。
- 完整的窗口自适应、光照阴影、纹理平铺、内存释放全套工程化代码。
演示动态图效果如下:
完整代码
1. template 结构:
<template>
<div class="three-container" ref="content" id="three"></div>
</template>
2. script 代码:
<script>
import * as THREE from 'three'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
export default {
beforeDestroy() {
// 组件销毁前释放ThreeJS资源的方法,防止内存泄漏
this.leaveDestory()
},
mounted() {
this.$nextTick(() => {
// 初始化方法
this.init()
// 监听浏览器窗口尺寸变化,自适应画布大小
window.onresize = () => {
this.resize()
}
})
},
data() {
return {
isLoading: false
}
},
methods: {
// 资源销毁函数:销毁动画帧、几何体、材质、渲染器等,避免页面反复挂载造成内存溢出
leaveDestory() {
// 取消动画帧请求,停止渲染循环
if (window.myMar) {
cancelAnimationFrame(window.myMar)
window.myMar = null
}
if (window.scene) {
// 遍历场景所有网格,释放几何体、材质显存资源
window.scene.traverse(function (v) {
if (v.type === 'Mesh') {
if (v.geometry && v.geometry.dispose) {
v.geometry.dispose()
}
if (v.material && v.material.dispose) {
v.material.dispose()
}
}
})
if (window.scene.children.length > 0) {
window.scene.remove(window.scene.children[0])
}
window.renderer.dispose() // 释放渲染器资源
window.renderer.forceContextLoss() // 强制丢失WebGL上下文,彻底释放显存
window.renderer.domElement = null
window.renderer = null
window.camera = null
window.clock = null
if (window.mixer) {
this.stopAnimation()
}
window.gltf = null
window.model = null
window.ground = null
window.onresize = null
window.scene.clear()
window.scene = null
}
},
// 停止人物跑步动画的方法
stopAnimation() {
if (window.mixer) {
window.animationAction.stop() // 停止动画片段播放
window.mixer = null
window.animationAction = null
window.ground.position.z = 0 // 重置地面位置
}
},
// 启动模型自带跑步动画的方法
startAnimation() {
// 创建动画混合器,绑定人物模型,用来解析模型内置骨骼动画
window.mixer = new THREE.AnimationMixer(window.model)
// 获取模型第一个动画片段(跑步动画)
window.animationAction = window.mixer.clipAction(window.gltf.animations[0])
// 开启循环播放跑步动画
window.animationAction.play()
// 启动渲染循环的方法
this.animate()
},
addGround() {
let textureLoader = new THREE.TextureLoader()
textureLoader.load('images/grasslight-big.jpg', (texture) => {
// 创建超大平面几何体,长宽16000,预留足够移动空间
const gg = new THREE.PlaneGeometry(16000, 16000)
const gm = new THREE.MeshPhongMaterial({ color: 0xffffff, map: texture })
window.ground = new THREE.Mesh(gg, gm)
// 平面绕x轴旋转-90度,从竖直状态放平作为地面
ground.rotation.x = -Math.PI / 2
// 纹理横向、纵向重复64次,实现细密草地效果
ground.material.map.repeat.set(64, 64)
// 设置纹理超出范围后重复平铺
ground.material.map.wrapS = THREE.RepeatWrapping
ground.material.map.wrapT = THREE.RepeatWrapping
// 设置纹理色彩空间,保证颜色显示正常
ground.material.map.colorSpace = THREE.SRGBColorSpace
// 地面接收阴影
ground.receiveShadow = true
window.scene.add(window.ground)
})
},
resize() {
let width = this.$refs.content.offsetWidth
let height = this.$refs.content.offsetHeight
window.camera.aspect = width / height
// 更新相机投影矩阵
window.camera.updateProjectionMatrix()
window.renderer.setSize(width, height)
},
async init() {
let width = this.$refs.content.offsetWidth
let height = this.$refs.content.offsetHeight
// 创建三维场景容器
window.scene = new THREE.Scene()
// 创建透视相机,45度视场角
window.camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000)
// 初始化WebGL渲染器,开启抗锯齿
window.renderer = new THREE.WebGLRenderer({ antialias: true })
// 适配设备像素比,解决高清屏模糊
window.renderer.setPixelRatio(window.devicePixelRatio)
// 画布背景设为白色
window.renderer.setClearColor(0xffffff, 1.0)
window.renderer.setSize(width, height)
// 开启阴影
window.renderer.shadowMapEnabled = true
// 设置相机位置,看向场景原点
window.camera.position.x = -72.93
window.camera.position.y = 126.06
window.camera.position.z = 323.85
window.camera.lookAt(window.scene.position)
// 添加半球环境光,柔和照亮整体场景
let hemiLight = new THREE.HemisphereLight(0xffffff, 0xffffff, 0.3)
hemiLight.position.set(0, 500, 0)
window.scene.add(hemiLight)
// 添加聚光灯,产生阴影,增强立体感
let spotLight = new THREE.SpotLight(0xffffff)
spotLight.position.set(-40, 650, -10)
spotLight.castShadow = true
window.scene.add(spotLight)
// 计时器,用来获取帧间隔、驱动骨骼动画更新
window.clock = new THREE.Clock()
// 创建草地地面的方法
this.addGround()
// 添加场景白雾,近实远虚,提升画面真实感,对应效果图雾气效果
window.scene.fog = new THREE.Fog(0xffffff, 280, 1050)
// 将渲染画布挂载到DOM容器中
document.getElementById('three').appendChild(window.renderer.domElement)
// 调用加载跑步人物模型的方法
this.addModel()
},
// 加载跑步人物模型的方法
addModel() {
this.isLoading = true
let loader = new GLTFLoader()
loader.load('static/models/workman.gltf', (gltf) => {
window.gltf = gltf
window.model = gltf.scene
// 缩放模型尺寸
window.model.scale.set(0.55, 0.55, 0.55)
// 调整模型y坐标,贴合地面
window.model.position.y = -2
window.scene.add(window.model)
window.renderer.render(window.scene, window.camera)
this.isLoading = false
// 模型加载完毕启动跑步动画
this.startAnimation();
})
},
animate() {
window.myMar = requestAnimationFrame(this.animate)
// 更新骨骼动画状态
if (window.mixer) {
const delta = window.clock.getDelta()
window.mixer.update(delta)
}
// 地面循环移动:z坐标持续减小向后偏移;到达阈值后重置位置,实现无限滚动
if (window.ground.position.z <= -500) {
window.ground.position.z = 0
} else {
window.ground.position.z = window.ground.position.z - 5
}
window.renderer.render(window.scene, window.camera)
}
}
}
</script>
3. css样式代码:
.three-container {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
}
三大核心实现原理拆解
1. 人物原地跑步:GLTF 骨骼动画驱动
- 使用
GLTFLoader加载模型,通过THREE.AnimationMixer动画混合器解析模型内置动画片段。 clipAction()获取跑步动作,调用play()开启循环播放,人物坐标全程固定,视觉上原地跑动。
2. 无限滚动草地:相对运动视觉欺骗
核心思路:人物不动,地面持续向后移动,利用人眼视觉错觉模拟向前奔跑。
- 创建超大尺寸平面作为地面,避免短距离移动出现边界。
- 渲染循环中每帧修改地面
position.z向后偏移。 - 当地面偏移量达到阈值
-500时,直接重置位置为 0,无缝循环,实现无限跑道。 - 草地纹理使用
RepeatWrapping重复平铺,保证大面积地面纹理不拉伸。
3. 场景雾化景深:线性雾增强真实感
通过 THREE.Fog 给场景添加线性白雾:
- 参数说明:
new THREE.Fog(雾颜色, 近裁剪距离, 远裁剪距离)。 - 距离小于 280:完全无雾,清晰显示近处草地。
- 距离 280 ~ 1050:雾气逐渐变浓。
- 距离超过 1050:完全被白雾覆盖,遮挡远处边界,弱化平面生硬边界,提升空间层次感。
模型说明: 本文演示所用的 3D 模型来源于网络,版权归原作者所有。本文仅做 Three.js 前端技术学习演示,并未对模型本身做任何修改。