three.js变形动画-使用MorphAnimMesh制作奔跑的小马动画(vue中使用three.js67)

2023-10-13 06:50

本文主要是介绍three.js变形动画-使用MorphAnimMesh制作奔跑的小马动画(vue中使用three.js67),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

使用MorphAnimMesh制作动画

  • 1.demo效果
  • 2.变形动画介绍
  • 3. 实现要点
    • 3.1 加载模型文件
    • 3.2 创建MorphAnimMesh变形动画网格
    • 3.3 render中更新动画
  • 4. demo代码

1.demo效果

在这里插入图片描述

2.变形动画介绍

变形动画就像老实的胶片电影,每一个动作有若干个分步帧,把这些帧串联起来,不断切换就实现动画效果,这里给每个分步起了一个名字叫 关键帧 或者 变形目标,使用这种方法制作动画的好处是,可以为所有顶点指定每个关键位置,但同时会带来不足,对于大型网格制作的动画,模型文件会非常大,因为每个关键帧上所有的顶点都要保存下来

3. 实现要点

3.1 加载模型文件

这里需要说明一下,最新的three.js没有JSONLoader加载器,这个demo需要用到,于是在 public目录下的index.html 文件中引入旧版three.js

<script type="text/javascript" src="./libs/three.js"></script>

在当前vue文件中注释掉引入本地依赖的代码

// import * as THREE from 'three'

然后就可以愉快的使用了

const publicPath = process.env.BASE_URL
const loader = new THREE.JSONLoader()
loader.load(`${publicPath}models/horse.js`,(geometry, mat) => {//加载完成后回调处理
})

3.2 创建MorphAnimMesh变形动画网格

创建变形动画网格,这里需要注意几点

  • 保证使用的材质中的 morphTargets 属性为true,否则网格不会动
  • 创建MorphAnimMesh对象前,一定要调用几何体的 computeMorphNormals() 方法,用来计算变形目标的法向量
  • 计算变形目标的颜色,使用辅助函数 morphColorsToFaceColors
//创建右侧奔跑的马
const mat1 = new THREE.MeshLambertMaterial({morphTargets: true,vertexColors: THREE.FaceColors
})THIS.morphColorsToFaceColors(geometry) //计算几何体表面颜色
geometry.computeVertexNormals() //计算顶点法向量
geometry.computeFaceNormals() //计算表面法向量
geometry.computeMorphNormals() //计算变形目标法向量// 创建变形动画网格对象并添加到场景
THIS.meshAnim = new THREE.MorphAnimMesh(geometry, mat1)
THIS.meshAnim.duration = 1000
THIS.meshAnim.position.x = 200
THIS.meshAnim.position.z = 0
this.scene.add(THIS.meshAnim)
// 取morph颜色设置到几何体面颜色
morphColorsToFaceColors(geometry) {if (geometry.morphColors && geometry.morphColors.length) {const colorMap = geometry.morphColors[0]for (let i = 0; i < colorMap.colors.length; i++) {geometry.faces[i].color = colorMap.colors[i]geometry.faces[i].color.offsetHSL(0.1, 0.1, 0)}}
}

3.3 render中更新动画

render() {this.showFrame(this.properties.keyframe.value) // 分步动画刷新if (this.meshAnim) {const delta = this.clock.getDelta() // 获取自上次调用的时间差this.meshAnim.updateAnimation(delta * 1000)this.meshAnim.rotation.y += 0.01}this.renderer.render(this.scene, this.camera)requestAnimationFrame(this.render)
}

4. demo代码

<template><div><div id="container" /><div class="controls-box"><section><el-row><div v-for="(item,key) in properties" :key="key"><div v-if="item&&item.name!=undefined"><el-col :span="8"><span class="vertice-span">{{ item.name }}</span></el-col><el-col :span="13"><el-slider v-model="item.value" :min="item.min" :max="item.max" :step="item.step" :format-tooltip="formatTooltip" /></el-col><el-col :span="3"><span class="vertice-span">{{ item.value }}</span></el-col></div></div></el-row></section></div></div>
</template><script>
// import * as THREE from 'three'
export default {data() {return {properties: {keyframe: {name: 'keyframe',value: 0,min: 0,max: 15,step: 1}},clock: new THREE.Clock(),currentMesh: null,frames: [],camera: null,scene: null,renderer: null,controls: null}},mounted() {this.init()},methods: {formatTooltip(val) {return val},// 初始化init() {this.createScene() // 创建场景this.createModels() // 创建模型this.createLight() // 创建光源this.createCamera() // 创建相机this.createRender() // 创建渲染器this.render() // 渲染},// 创建场景createScene() {this.scene = new THREE.Scene()},// 创建模型createModels() {const THIS = thisconst publicPath = process.env.BASE_URLconst loader = new THREE.JSONLoader()loader.load(`${publicPath}models/horse.js`,(geometry, mat) => {//创建右侧奔跑的马const mat1 = new THREE.MeshLambertMaterial({morphTargets: true,vertexColors: THREE.FaceColors})THIS.morphColorsToFaceColors(geometry) //计算几何体表面颜色geometry.computeVertexNormals() //计算顶点法向量geometry.computeFaceNormals() //计算表面法向量geometry.computeMorphNormals() //计算变形目标法向量// 创建变形动画网格对象并添加到场景THIS.meshAnim = new THREE.MorphAnimMesh(geometry, mat1)THIS.meshAnim.duration = 1000THIS.meshAnim.position.x = 200THIS.meshAnim.position.z = 0this.scene.add(THIS.meshAnim)//创建左侧关键帧-静止的马运动的分步变形目标const mat2 = new THREE.MeshLambertMaterial({color: 0xffffff,vertexColors: THREE.FaceColors})const mesh = new THREE.Mesh(geometry, mat1)mesh.position.x = -100THIS.frames.push(mesh)THIS.currentMesh = mesh// 创建分步网格对象mesh.geometry.morphTargets.forEach(e => {const geom = new THREE.Geometry()geom.vertices = e.vertices // 设置顶点geom.faces = geometry.faces // 设置表面const morpMesh = new THREE.Mesh(geom, mat2)THIS.frames.push(morpMesh)morpMesh.position.x = -100 // 向左平移100})this.showFrame(0) // 显示分步动画0},`${publicPath}models`)},showFrame(e) {this.scene.remove(this.currentMesh)this.scene.add(this.frames[e])this.currentMesh = this.frames[e]},// 取morph颜色设置到几何体面颜色morphColorsToFaceColors(geometry) {if (geometry.morphColors && geometry.morphColors.length) {const colorMap = geometry.morphColors[0]for (let i = 0; i < colorMap.colors.length; i++) {geometry.faces[i].color = colorMap.colors[i]geometry.faces[i].color.offsetHSL(0.1, 0.1, 0)}}},// 创建光源createLight() {// 环境光const ambientLight = new THREE.AmbientLight(0x111111) // 创建环境光this.scene.add(ambientLight) // 将环境光添加到场景const directionLight = new THREE.DirectionalLight(0xffffff)directionLight.position.set(300, 200, 300)directionLight.intensity = 1.5this.scene.add(directionLight)},// 创建相机createCamera() {const element = document.getElementById('container')const width = element.clientWidth // 窗口宽度const height = element.clientHeight // 窗口高度const k = width / height // 窗口宽高比// PerspectiveCamera( fov, aspect, near, far )this.camera = new THREE.PerspectiveCamera(45, k, 0.1, 1000)this.camera.position.set(450, 450, 450) // 设置相机位置this.camera.lookAt(new THREE.Vector3(100, 50, 0)) // 设置相机方向this.scene.add(this.camera)},// 创建渲染器createRender() {const element = document.getElementById('container')this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })this.renderer.setSize(element.clientWidth, element.clientHeight) // 设置渲染区域尺寸this.renderer.setClearColor(0xeeeeee, 1) // 设置背景颜色element.appendChild(this.renderer.domElement)},render() {this.showFrame(this.properties.keyframe.value) // 分步动画刷新if (this.meshAnim) {const delta = this.clock.getDelta() // 获取自上次调用的时间差this.meshAnim.updateAnimation(delta * 1000)this.meshAnim.rotation.y += 0.01}this.renderer.render(this.scene, this.camera)requestAnimationFrame(this.render)}}
}
</script><style>
#container {position: absolute;width: 100%;height: 100%;
}
.controls-box {position: absolute;right: 5px;top: 5px;width: 300px;padding: 10px;background-color: #fff;border: 1px solid #c3c3c3;
}
.vertice-span {line-height: 38px;padding: 0 2px 0 10px;
}
</style>

这篇关于three.js变形动画-使用MorphAnimMesh制作奔跑的小马动画(vue中使用three.js67)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/201487

相关文章

java图像识别工具类(ImageRecognitionUtils)使用实例详解

《java图像识别工具类(ImageRecognitionUtils)使用实例详解》:本文主要介绍如何在Java中使用OpenCV进行图像识别,包括图像加载、预处理、分类、人脸检测和特征提取等步骤... 目录前言1. 图像识别的背景与作用2. 设计目标3. 项目依赖4. 设计与实现 ImageRecogni

python管理工具之conda安装部署及使用详解

《python管理工具之conda安装部署及使用详解》这篇文章详细介绍了如何安装和使用conda来管理Python环境,它涵盖了从安装部署、镜像源配置到具体的conda使用方法,包括创建、激活、安装包... 目录pytpshheraerUhon管理工具:conda部署+使用一、安装部署1、 下载2、 安装3

Mysql虚拟列的使用场景

《Mysql虚拟列的使用场景》MySQL虚拟列是一种在查询时动态生成的特殊列,它不占用存储空间,可以提高查询效率和数据处理便利性,本文给大家介绍Mysql虚拟列的相关知识,感兴趣的朋友一起看看吧... 目录1. 介绍mysql虚拟列1.1 定义和作用1.2 虚拟列与普通列的区别2. MySQL虚拟列的类型2

使用MongoDB进行数据存储的操作流程

《使用MongoDB进行数据存储的操作流程》在现代应用开发中,数据存储是一个至关重要的部分,随着数据量的增大和复杂性的增加,传统的关系型数据库有时难以应对高并发和大数据量的处理需求,MongoDB作为... 目录什么是MongoDB?MongoDB的优势使用MongoDB进行数据存储1. 安装MongoDB

关于@MapperScan和@ComponentScan的使用问题

《关于@MapperScan和@ComponentScan的使用问题》文章介绍了在使用`@MapperScan`和`@ComponentScan`时可能会遇到的包扫描冲突问题,并提供了解决方法,同时,... 目录@MapperScan和@ComponentScan的使用问题报错如下原因解决办法课外拓展总结@

mysql数据库分区的使用

《mysql数据库分区的使用》MySQL分区技术通过将大表分割成多个较小片段,提高查询性能、管理效率和数据存储效率,本文就来介绍一下mysql数据库分区的使用,感兴趣的可以了解一下... 目录【一】分区的基本概念【1】物理存储与逻辑分割【2】查询性能提升【3】数据管理与维护【4】扩展性与并行处理【二】分区的

使用Python实现在Word中添加或删除超链接

《使用Python实现在Word中添加或删除超链接》在Word文档中,超链接是一种将文本或图像连接到其他文档、网页或同一文档中不同部分的功能,本文将为大家介绍一下Python如何实现在Word中添加或... 在Word文档中,超链接是一种将文本或图像连接到其他文档、网页或同一文档中不同部分的功能。通过添加超

Linux使用fdisk进行磁盘的相关操作

《Linux使用fdisk进行磁盘的相关操作》fdisk命令是Linux中用于管理磁盘分区的强大文本实用程序,这篇文章主要为大家详细介绍了如何使用fdisk进行磁盘的相关操作,需要的可以了解下... 目录简介基本语法示例用法列出所有分区查看指定磁盘的区分管理指定的磁盘进入交互式模式创建一个新的分区删除一个存

C#使用HttpClient进行Post请求出现超时问题的解决及优化

《C#使用HttpClient进行Post请求出现超时问题的解决及优化》最近我的控制台程序发现有时候总是出现请求超时等问题,通常好几分钟最多只有3-4个请求,在使用apipost发现并发10个5分钟也... 目录优化结论单例HttpClient连接池耗尽和并发并发异步最终优化后优化结论我直接上优化结论吧,

SpringBoot使用Apache Tika检测敏感信息

《SpringBoot使用ApacheTika检测敏感信息》ApacheTika是一个功能强大的内容分析工具,它能够从多种文件格式中提取文本、元数据以及其他结构化信息,下面我们来看看如何使用Ap... 目录Tika 主要特性1. 多格式支持2. 自动文件类型检测3. 文本和元数据提取4. 支持 OCR(光学