WebGL three.js学习笔记 加载外部模型以及Tween.js动画
WebGL three.js学习笔记 加载外部模型以及Tween.js动画
本文的程序实现了加载外部stl格式的模型,以及学习了如何把加载的模型变为一个粒子系统,并使用Tween.js对该粒子系统进行动画设置
模型动画demo演示(网页加载速度可能会比较慢)
demo地址:https://nsytsqdtn.github.io/demo/naval_craft/naval_craft
demo截图如下:
原模型截图:
在我们写three.js的网页的时候,大多时候并不需要我们去手动建立模型,一些复杂的模型都是通过建模软件去完成,所以在这里去学习如何去将外部的模型加载到我们的网页中来。
three.js支持导入的模型有很多,包括我们常见的OBJ、FBX、STL、PLY、JSON等等格式,在这个程序中,我选择了使用STL模型来进行学习。
.stl 文件是在计算机图形应用系统中,用于表示三角形网格的一种文件格式,常用于3d打印技术使用,因为STL格式的文件在网上可以免费不用注册的下载,比较方便。这里推荐一个还不错的网站,http://www.3dhoo.com/model ,里面有很多免费直接下载STL格式的模型。
加载外部模型
在three.js中,我们要加载外部模型,就需要引入相应的js文件。比如我需要引入STL格式的文件,我就引入“three.js\examples\js\loaders\STLLoader.js”,其他格式的js文件在loaders文件夹也都能找到,如果是three.js没有支持导入的模型格式,就需要自己写一个加载器,网上也有许多的教程。
引入相应js文件以后,我们首先要做的事创建一个加载器。
let loader = new THREE.STLLoader();//创建stl的加载器,用加载器来加载stl模型
我们要使用该加载器加载模型,就需要调用loader .load(filename,onSuccess(bufferGeometry),onProgress(xhr),onError(error))这个方法
其中:
filename是模型的路径
onSuccess(bufferGeometry)是加载成功后回调处理(参数为生成的模型的几何体),
注意:这里的几何体不是我们常用的geometry,而是bufferGeometry,它和geometry还是有一些的区别,但是也都可以作为THREE.Mesh()的第一个参数穿进去。具体可以进行百度。
onProgress(xhr)是加载过程中回调处理(xhr对象属性可计算出已完成加载百分比)
onError(error)是失败回调处理方法
一般我们只需要使用前两个参数就可以完成工作。
let loader = new THREE.STLLoader();//创建stl的加载器,用加载器来加载stl模型
let loader.load("../../../asset/ship.stl", function (bufferGeometry) {//加载模型的方法,
//第一个参数是模型的路径,第二个参数时候我们定义的回调函数,一旦模型加载成功,回调函数就会被调用
let material = new THREE.MeshBasicMaterial();
let mesh = new THREE.Mesh(bufferGeometry,material);
scene.add(mesh);
}
一般只需要这样写回调函数,模型就可以成功加载。
但我在这里想根据该模型去创建一个粒子系统,像本文开头的那样,所以我们需要改一下代码。
let loader = new THREE.STLLoader();//创建stl的加载器,用加载器来加载stl模型
group = new THREE.Object3D();
loader.load("../../../asset/ship.stl", function (bufferGeometry) {//加载模型的方法,第一个参数是模型的路径,第二个参数时候我们定义的回调函数,一旦模型加载成功,回调函数就会被调用
let geometry = new THREE.Geometry().fromBufferGeometry(bufferGeometry);//stl模型加载到js里就会变成bufferGeometry类型,我们先用一个方法把它变成Geometry类型
loadGeometry = geometry.clone();//创建该geometry的克隆体,后面会用到
let material = new THREE.PointsMaterial({//点云的材质
color: 0xffffff,
transparent: true,
opacity: 1,
size: 0.5,//可自由修改看看效果
blending: THREE.AdditiveBlending,
map: generateSprite()//自定义画布图案来充当每一个粒子的材质
});
//创建点云,以及设置它的位置及旋转角度,调整到最好看的地方
group = new THREE.Points(geometry, material);
group.sortParticles = true;
group.position.set(0,0,0);
group.position.x -=70;
group.rotation.x = Math.PI*3/2;
其中:
我们使用THREE.Geometry().fromBufferGeometry(bufferGeometry)函数把bufferGeometry类型改为geometry类型,因为该类型我们更加熟悉,后面使用起来也比较方便。
generateSprite()函数是在之前的文章也介绍过的,创建一个颜色渐变的画布,来充当粒子系统纹理,这里就不再赘述了。具体代码如下:
//自定义渐变颜色的画布,前面的文章有介绍,这个方法在写three.js程序很常用
function generateSprite() {
var canvas = document.createElement('canvas');
canvas.width = 16;
canvas.height = 16;
var context = canvas.getContext('2d');
var gradient = context.createRadialGradient(canvas.width / 2, canvas.height / 2, 0, canvas.width / 2, canvas.height / 2, canvas.width / 2);
gradient.addColorStop(0, 'rgba(255,255,255,1)');
gradient.addColorStop(0.2, 'rgba(0,255,255,1)');
gradient.addColorStop(0.4, 'rgba(0,0,255,1)');
gradient.addColorStop(1, 'rgba(0,0,0,1)');
context.fillStyle = gradient;
context.fillRect(0, 0, canvas.width, canvas.height);
var texture = new THREE.Texture(canvas);
texture.needsUpdate = true;
return texture;
}
Tween.js动画
tweenjs 是使用 JavaScript 中的一个简单的补间动画库,支持数字、对象的属性和 CSS 样式属性的赋值。
tweenjs 以平滑的方式修改元素的属性值,需要传递给 tween 要修改的值、动画结束时的最终值和动画花费时间,之后 tween 引擎就可以计算从开始动画点到结束动画点之间值,从而产生平滑的动画效果。
我们首先需要引入tween.js文件,该文件的路径是“three.js\examples\js\libs\tween.min.js”,也可以直接百度搜索tween.js去下载。
具体的用法是:
let posSrc = {pos: 0};//创建一个posSrc的对象,该对象里面有pos的属性,并初始化该属性为0
let tween = new TWEEN.Tween(posSrc).to({pos: 1}, 5000);//创建tween的补间动画,使posSrc中的pos属性的值在5000ms内从0到1变化
tween.easing(TWEEN.Easing.Sinusoidal.InOut);//配置缓动效果
我们创建了TWEEN.Tween对象,这个对象会确保x属性值在5000毫秒内从0变化到1。通过Tweenjs,你还可以指定属性值是如何变化的,是线性的、指数性的,还是其他任何可能的方式。属性值在指定时间内的变化被称为easing(缓动),在Tween.js中你可以使用easing()方法来配置缓动效果。我们还可以创建更多的TWEEN.Tween对象,并使用chain(TWEEN.Tween)函数链接多个补间动画。
我们还需要一个update的函数,在每次更新补间的时候,都可以去更新每个粒子的位置,来实现的动画效果。
let posSrc = {pos: 0};//创建一个posSrc的对象,该对象里面有pos的属性
//并初始化该属性为0
let tween = new TWEEN.Tween(posSrc).to({pos: 1}, 5000);//创建tween的补间动画
//使posSrc中的pos属性的值在5000ms内从0到1变化
tween.easing(TWEEN.Easing.Sinusoidal.InOut);//配置缓动效果
let tweenStand = new TWEEN.Tween(posSrc).to({pos: 1}, 2000);//让动画在pos的值
//变为1后停止一段时间,方便我们观察,所以再创建一个tween,让pos从1到1(即不变)
tween.easing(TWEEN.Easing.Sinusoidal.InOut);//配置缓动效果
let tweenBack = new TWEEN.Tween(posSrc).to({pos: 0}, 5000);//创建tweenBack的
//补间动画,和初始相反,使posSrc中的pos属性的值在5000ms内从1到0变化
tweenBack.easing(TWEEN.Easing.Sinusoidal.InOut);//配置缓动效果
//每一个补间动画之间使用chain()连接起来
tween.chain(tweenStand);
tweenStand.chain(tweenBack);
tweenBack.chain(tween);
//在补间的过程中,让所有的粒子开始移动
let onUpdate = function () {
let pos = posSrc.pos;//定义一个pos,赋值为posSrc对象的pos属性
let count = 0;
loadGeometry.vertices.forEach(function (e) {//遍历每个顶点
//这里需要遍历刚刚克隆的geometry
//(暂时不是很明白这点,反正如果遍历group.geometry.vertices
//动画系统会让整个物体一起移动,没有伸展开来的效果)。
var newZ = e.z * pos;//得到新的Z值,根据当前的pos值去改变
group.geometry.vertices[count++].set(e.x, e.y, newZ);//设置每个顶点的位置
//group.geometry.vertices是数组类型,所以用count作为索引
group.geometry.verticesNeedUpdate = true;//重要,不然会没有动画效果
});
group.sortParticles = true;
};
//tween在每次更新后会执行tween.onUpdate()函数
//里面的参数就是我们自定义要让它如果去运动的函数,即上面写的onUpdate
tween.onUpdate(onUpdate);
tweenStand.onUpdate(onUpdate);
tweenBack.onUpdate(onUpdate);
tween.start();//开启tween
在这段代码中,我们创建了三个个补间: tween、tweenStand、tweenBack。第一个补间定义了position属性如何从1过渡到0,第三个刚好相反,第二个是让动画暂时停下。通过chain(方法可以将这三个补间衔接起来,这样当动画启动之后,程序就会在这三个补间循环。代码最后定义的是onUpdate()方法,这个方法遍历粒子系统中的所有顶点,并使用补间(this.pos)提供的位置更新顶点的位置。
补间动画需要在模型加载完成后就启动,所以我们在下面的函数末尾调用tween.start()方法:
如果之前没有把bufferGeometry转化为Geometry类型,要去更改每个顶点的位置会变得比较麻烦。
最后还需要告知three.js什么时候刷新所有的补间动画,所以在render()函数里加上TWEEN.update();
function render() {
TWEEN.update();//通知TWEEN在什么时候去刷新补间动画,重要,否则会没有动画
//性能监控器的更新
stats.update();
renderer.clear();
requestAnimationFrame(render);
renderer.render(scene, camera);
}
到了这里,程序的大体就已经完成,剩下的就是创建场景,摄像机,渲染器等等东西以及调整模型的位置。这里不再赘述。
完整的代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Naval Craft Sprite</title>
<script src="../../import/three.js"></script>
<script src="../../import/stats.js"></script>
<script src="../../import/Setting.js"></script>
<script src="../../import/OrbitControls.js"></script>
<script src="../../import/tween.min.js"></script>
<script src="../../import/STLLoader.js"></script>
<style type="text/css">
div#WebGL-output {
border: none;
cursor: pointer;
width: 100%;
height: 850px;
background-color: #333333;
}
</style>
</head>
<body onload="threeStart()">
<div id="WebGL-output"></div>
<script>
let camera, renderer, scene,controller;
function initThree() {
//渲染器初始化
renderer = new THREE.WebGLRenderer({
antialias: true//抗锯齿开启
});
//设置渲染的大小
renderer.setSize(window.innerWidth, window.innerHeight);
//设置渲染的颜色
renderer.setClearColor(0x333333);
renderer.shadowMapEnabled = true;//开启阴影的渲染
renderer.shadowMapType = THREE.PCFSoftShadowMap;//设置阴影类型为柔和
document.getElementById("WebGL-output").appendChild(renderer.domElement);//将渲染添加到div中
//初始化摄像机,这里使用透视投影摄像机
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.01, 10000);
camera.position.set(35, 35, 75);//相机的位置,自由调整
camera.up.x = 0;//设置摄像机的上方向为哪个方向,这里定义摄像的上方为Y轴正方向
camera.up.y = 1;
camera.up.z = 0;
//摄像机对准的地方
camera.lookAt(0, 0, 0);
//初始化场景
scene = new THREE.Scene();
//相机的移动
controller = new THREE.OrbitControls(camera, renderer.domElement);
//相机围绕旋转的目标,设置为原点
controller.target = new THREE.Vector3(0, 0, 0);
}
let loadGeometry;
let group;
function initObject() {
let loader = new THREE.STLLoader();//创建stl的加载器,用加载器来加载stl模型
group = new THREE.Object3D();
loader.load("../../asset/naval_craft.stl", function (bufferGeometry) {//加载模型的方法,第一个参数是模型的路径,第二个参数时候我们定义的回调函数,一旦模型加载成功,回调函数就会被调用
let geometry = new THREE.Geometry().fromBufferGeometry(bufferGeometry);//stl模型加载到js里就会变成bufferGeometry类型,我们先用一个方法把它变成Geometry类型
loadGeometry = geometry.clone();//创建该geometry的克隆体,后面会用到
let material = new THREE.PointsMaterial({//点云的材质
color: 0xffffff,
transparent: true,
opacity: 1,
size: 0.5,//可自由修改看看效果
blending: THREE.AdditiveBlending,
map: generateSprite()//自定义画布图案来充当每一个粒子的材质
});
//创建点云,以及设置它的位置及旋转角度,调整到最好看的地方
group = new THREE.Points(geometry, material);
group.sortParticles = true;
group.position.set(0,0,0);
group.position.x -=70;
group.rotation.x = Math.PI*3/2;
let posSrc = {pos: 0};//创建一个posSrc的对象,该对象里面有pos的属性,并初始化该属性为0
let tween = new TWEEN.Tween(posSrc).to({pos: 1}, 5000);//创建tween的补间动画,使posSrc中的pos属性的值在5000ms内从0到1变化
tween.easing(TWEEN.Easing.Sinusoidal.InOut);//配置缓动效果
let tweenStand = new TWEEN.Tween(posSrc).to({pos: 1}, 2000);//让动画在pos的值变为1后停止一段时间,方便我们观察,所以再创建一个tween,让pos从1到1(即不变)
tween.easing(TWEEN.Easing.Sinusoidal.InOut);//配置缓动效果
let tweenBack = new TWEEN.Tween(posSrc).to({pos: 0}, 5000);//创建tweenBack的补间动画,和初始相反,使posSrc中的pos属性的值在5000ms内从1到0变化
tweenBack.easing(TWEEN.Easing.Sinusoidal.InOut);//配置缓动效果
//每一个补间动画之间使用chain()连接起来
tween.chain(tweenStand);
tweenStand.chain(tweenBack);
tweenBack.chain(tween);
//在补间的过程中,让所有的粒子开始移动
let onUpdate = function () {
let pos = posSrc.pos;//定义一个pos,赋值为posSrc对象的pos属性
let count = 0;
loadGeometry.vertices.forEach(function (e) {//遍历每个顶点,这里需要遍历刚刚克隆的geometry
var newZ = e.z * pos;//得到新的Z值,根据当前的pos值去改变
group.geometry.vertices[count++].set(e.x, e.y, newZ);//设置每个顶点的位置,group.geometry.vertices是数组类型,所以用count作为索引
group.geometry.verticesNeedUpdate = true;//重要,不然会没有动画效果
});
group.sortParticles = true;
};
//tween在每次更新后会执行tween.onUpdate()函数,里面的参数就是我们自定义要让它如果去运动的函数,即上面写的onUpdate
tween.onUpdate(onUpdate);
tweenStand.onUpdate(onUpdate);
tweenBack.onUpdate(onUpdate);
tween.start();//开启tween
scene.add(group);
});
}
//自定义渐变颜色的画布,前面的文章有介绍,这个方法在写three.js程序很常用
function generateSprite() {
var canvas = document.createElement('canvas');
canvas.width = 16;
canvas.height = 16;
var context = canvas.getContext('2d');
var gradient = context.createRadialGradient(canvas.width / 2, canvas.height / 2, 0, canvas.width / 2, canvas.height / 2, canvas.width / 2);
gradient.addColorStop(0, 'rgba(255,255,255,1)');
gradient.addColorStop(0.2, 'rgba(0,255,255,1)');
gradient.addColorStop(0.4, 'rgba(0,0,255,1)');
gradient.addColorStop(1, 'rgba(0,0,0,1)');
context.fillStyle = gradient;
context.fillRect(0, 0, canvas.width, canvas.height);
var texture = new THREE.Texture(canvas);
texture.needsUpdate = true;
return texture;
}
//渲染函数
function render() {
TWEEN.update();//通知TWEEN在什么时候去刷新补间动画,重要,否则会没有动画
//性能监控器的更新
stats.update();
renderer.clear();
requestAnimationFrame(render);
renderer.render(scene, camera);
}
//功能函数
function setting() {
loadFullScreen();
loadAutoScreen(camera, renderer);
loadStats();
}
//运行主函数
function threeStart() {
initThree();
initObject();
setting();
render();
}
</script>
</body>
</html>
WebGL three.js学习笔记 加载外部模型以及Tween.js动画的更多相关文章
- cesium 学习(五) 加载场景模型
cesium 学习(五) 加载场景模型 一.前言 现在开始实际的看看效果,目前我所接触到基本上都是使用Cesium加载模型这个内容,以及在模型上进行操作.So,现在进行一些加载模型的学习,数据的话可以 ...
- cesium js学习一加载三维模型【转】
http://blog.csdn.net/tangyajun_168/article/details/50936698 最近项目中用到室外三维模型与室内三维地图交互,室外三维模型的加载我们采用了ces ...
- 深度学习之加载VGG19模型分类识别
主要参考博客: https://blog.csdn.net/u011046017/article/details/80672597#%E8%AE%AD%E7%BB%83%E4%BB%A3%E7%A0% ...
- AS 学习笔记 加载数据
AS2 加载本地(外部)数据.swf .png .jpg 等资源使用loadMovie() 加载库里面的mc 用 attachMovie AS3 加载本地(外部)数据 用 Loader 类来完成这个操 ...
- 深度学习之加载VGG19模型获取特征图
1.加载VGG19获取图片特征图 # coding = utf-8 import tensorflow as tf import numpy as np import matplotlib.pyplo ...
- Node.js学习笔记2(安装和配置Node.js)
1.安装 windows下安装,在http://nodejs.org下载安装包进行安装即可. linux下安装,使用yum或者下载源码进行编译. ...
- 浏览器加载外部js 的顺序,以及处理顺序。
问题, 有事候按F12打开 google的调试台后,查看network下面加载过来的资源是, 有些资源的状态处于 pending.. 个人理解: 浏览器是可以同时开启多个http 请求去加载外部的资源 ...
- 无阻塞加载外部js(动态脚本元素,XMLHttpRequest注入,LazyLoad)
动态脚本元素即在js中去创建<script>标签加载外部js并执行,这样加载的好处是文件的下载和执行过程不会阻塞页面的其他进程.通过下面两个例子对比出效果 <!DOCTYPE htm ...
- JS和jquery加载的区别
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
随机推荐
- Effective Modern C++ 条款2:理解auto型别推导
在条款1中,我们已经了解了有关模板型别的推导的一切必要知识,那么也就意味着基本上了解了auto型别推导的一切必要知识. 因为,除了一个奇妙的例外情况,auto型别推导就是模板型别推导.尽管和模板型别推 ...
- JDK8日期时间操作小汇总
统一使用java.time.*包下的类 1.获取当前的日期.时间.日期加时间 LocalDate todayDate = LocalDate.now(); //今天的日期 LocalTime now ...
- [Array]268. Missing Number
Given an array containing n distinct numbers taken from 0, 1, 2, ..., n, find the one that is missin ...
- 【noip】跟着洛谷刷noip题2
noip好难呀. 上一个感觉有点长了,重开一个. 36.Vigenère 密码 粘个Openjudge上的代码 #include<cstdio> #include<iostream& ...
- TZ_10_spring-sucrity 服务器和页面的权限控制
1.在服务器端我们可以通过Spring security提供的注解对方法来进行权限控制. Spring Security在方法的权限控制上支持三种类型的注解,JSR-250注解.@Secured注解和 ...
- angular依赖注入(2)——注入器的使用
一.显示注入器 injector = ReflectiveInjector.resolveAndCreate([Car, Engine, Tires]); let car = injector.get ...
- ConcurrentDictionary让你的多线程代码更优美
ConcurrentDictionary是.net4.0推出的一套线程安全集合里的其中一个,和它一起被发行的还有ConcurrentStack,ConcurrentQueue等类型,它们的单线程版本( ...
- python OneHot编码
- 根据一个分类id 获取这个分类底下所有子分类的商品信息,根据下面方法查询出所有有关分类id 再 根据这些id去商品表里查询所有商品信息
/** * 检测该分类下所有子分类,并输出ID(包括自己) * 数据库字段 catid pid */ function getChildrenIds ($sort_id){ include_once ...
- git解决冲突的最佳方法2
1.冲突后和远程仓库的文件进行比对的时候,善于用上图所示的按钮,会提高效率 copy all-nonconflicting changes from right to left next differ ...