本文的程序实现了加载外部stl格式的模型,以及学习了如何把加载的模型变为一个粒子系统,并使用Tween.js对该粒子系统进行动画设置javascript
demo地址:https://nsytsqdtn.github.io/demo/naval_craft/naval_craftcss
demo截图以下:
html
原模型截图:
java
在咱们写three.js的网页的时候,大多时候并不须要咱们去手动创建模型,一些复杂的模型都是经过建模软件去完成,因此在这里去学习如何去将外部的模型加载到咱们的网页中来。git
three.js支持导入的模型有不少,包括咱们常见的OBJ、FBX、STL、PLY、JSON等等格式,在这个程序中,我选择了使用STL模型来进行学习。github
.stl 文件是在计算机图形应用系统中,用于表示三角形网格的一种文件格式,经常使用于3d打印技术使用,由于STL格式的文件在网上能够免费不用注册的下载,比较方便。这里推荐一个还不错的网站,http://www.3dhoo.com/model ,里面有不少免费直接下载STL格式的模型。web
在three.js中,咱们要加载外部模型,就须要引入相应的js文件。好比我须要引入STL格式的文件,我就引入“three.js\examples\js\loaders\STLLoader.js”,其余格式的js文件在loaders文件夹也都能找到,若是是three.js没有支持导入的模型格式,就须要本身写一个加载器,网上也有许多的教程。canvas
引入相应js文件之后,咱们首先要作的事建立一个加载器。数组
let loader = new THREE.STLLoader();//建立stl的加载器,用加载器来加载stl模型
咱们要使用该加载器加载模型,就须要调用loader .load(filename,onSuccess(bufferGeometry),onProgress(xhr),onError(error))这个方法
其中:
filename是模型的路径
onSuccess(bufferGeometry)是加载成功后回调处理(参数为生成的模型的几何体),app
注意:这里的几何体不是咱们经常使用的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; }
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>