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截图如下:

naval_craft naval_craft

原模型截图:

原模型

在我们写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文件以后,我们首先要做的事创建一个加载器。

1let 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)是失败回调处理方法
一般我们只需要使用前两个参数就可以完成工作。

1let loader = new THREE.STLLoader();//创建stl的加载器,用加载器来加载stl模型
2let loader.load("../../../asset/ship.stl"function (bufferGeometry{//加载模型的方法,
3//第一个参数是模型的路径,第二个参数时候我们定义的回调函数,一旦模型加载成功,回调函数就会被调用
4let material = new THREE.MeshBasicMaterial();
5let mesh = new THREE.Mesh(bufferGeometry,material);
6scene.add(mesh);
7}
复制代码

一般只需要这样写回调函数,模型就可以成功加载。
但我在这里想根据该模型去创建一个粒子系统,像本文开头的那样,所以我们需要改一下代码。

 1        let loader = new THREE.STLLoader();//创建stl的加载器,用加载器来加载stl模型
2        group = new THREE.Object3D();
3        loader.load("../../../asset/ship.stl"function (bufferGeometry{//加载模型的方法,第一个参数是模型的路径,第二个参数时候我们定义的回调函数,一旦模型加载成功,回调函数就会被调用
4            let geometry = new THREE.Geometry().fromBufferGeometry(bufferGeometry);//stl模型加载到js里就会变成bufferGeometry类型,我们先用一个方法把它变成Geometry类型
5            loadGeometry = geometry.clone();//创建该geometry的克隆体,后面会用到
6            let material = new THREE.PointsMaterial({//点云的材质
7                color: 0xffffff,
8                transparenttrue,
9                opacity1,
10                size0.5,//可自由修改看看效果
11                blending: THREE.AdditiveBlending,
12                map: generateSprite()//自定义画布图案来充当每一个粒子的材质
13            });
14            //创建点云,以及设置它的位置及旋转角度,调整到最好看的地方
15            group = new THREE.Points(geometry, material);
16            group.sortParticles = true;
17            group.position.set(0,0,0);
18            group.position.x -=70;
19            group.rotation.x = Math.PI*3/2;
复制代码

其中: 我们使用THREE.Geometry().fromBufferGeometry(bufferGeometry)函数把bufferGeometry类型改为geometry类型,因为该类型我们更加熟悉,后面使用起来也比较方便。

generateSprite()函数是在之前的文章也介绍过的,创建一个颜色渐变的画布,来充当粒子系统纹理,这里就不再赘述了。具体代码如下:

 1//自定义渐变颜色的画布,前面的文章有介绍,这个方法在写three.js程序很常用
2    function generateSprite({
3
4        var canvas = document.createElement('canvas');
5        canvas.width = 16;
6        canvas.height = 16;
7
8        var context = canvas.getContext('2d');
9        var gradient = context.createRadialGradient(canvas.width / 2, canvas.height / 20, canvas.width / 2, canvas.height / 2, canvas.width / 2);
10        gradient.addColorStop(0'rgba(255,255,255,1)');
11        gradient.addColorStop(0.2'rgba(0,255,255,1)');
12        gradient.addColorStop(0.4'rgba(0,0,255,1)');
13        gradient.addColorStop(1'rgba(0,0,0,1)');
14
15        context.fillStyle = gradient;
16        context.fillRect(00, canvas.width, canvas.height);
17
18        var texture = new THREE.Texture(canvas);
19        texture.needsUpdate = true;
20        return texture;
21    }
复制代码

Tween.js动画

tweenjs 是使用 JavaScript 中的一个简单的补间动画库,支持数字、对象的属性和 CSS 样式属性的赋值。
tweenjs 以平滑的方式修改元素的属性值,需要传递给 tween 要修改的值、动画结束时的最终值和动画花费时间,之后 tween 引擎就可以计算从开始动画点到结束动画点之间值,从而产生平滑的动画效果。

我们首先需要引入tween.js文件,该文件的路径是“three.js\examples\js\libs\tween.min.js”,也可以直接百度搜索tween.js去下载。
具体的用法是:

1let posSrc = {pos0};//创建一个posSrc的对象,该对象里面有pos的属性,并初始化该属性为0
2let tween = new TWEEN.Tween(posSrc).to({pos1}, 5000);//创建tween的补间动画,使posSrc中的pos属性的值在5000ms内从0到1变化
3tween.easing(TWEEN.Easing.Sinusoidal.InOut);//配置缓动效果
复制代码

我们创建了TWEEN.Tween对象,这个对象会确保x属性值在5000毫秒内从0变化到1。通过Tweenjs,你还可以指定属性值是如何变化的,是线性的、指数性的,还是其他任何可能的方式。属性值在指定时间内的变化被称为easing(缓动),在Tween.js中你可以使用easing()方法来配置缓动效果。我们还可以创建更多的TWEEN.Tween对象,并使用chain(TWEEN.Tween)函数链接多个补间动画。

我们还需要一个update的函数,在每次更新补间的时候,都可以去更新每个粒子的位置,来实现的动画效果。

 1            let posSrc = {pos0};//创建一个posSrc的对象,该对象里面有pos的属性
2            //并初始化该属性为0
3            let tween = new TWEEN.Tween(posSrc).to({pos1}, 5000);//创建tween的补间动画
4            //使posSrc中的pos属性的值在5000ms内从0到1变化
5            tween.easing(TWEEN.Easing.Sinusoidal.InOut);//配置缓动效果
6            let tweenStand = new TWEEN.Tween(posSrc).to({pos1}, 2000);//让动画在pos的值
7            //变为1后停止一段时间,方便我们观察,所以再创建一个tween,让pos从1到1(即不变)
8            tween.easing(TWEEN.Easing.Sinusoidal.InOut);//配置缓动效果
9            let tweenBack = new TWEEN.Tween(posSrc).to({pos0}, 5000);//创建tweenBack的
10            //补间动画,和初始相反,使posSrc中的pos属性的值在5000ms内从1到0变化
11            tweenBack.easing(TWEEN.Easing.Sinusoidal.InOut);//配置缓动效果
12            //每一个补间动画之间使用chain()连接起来
13            tween.chain(tweenStand);
14            tweenStand.chain(tweenBack);
15            tweenBack.chain(tween);
16
17            //在补间的过程中,让所有的粒子开始移动
18            let onUpdate = function ({
19                let pos = posSrc.pos;//定义一个pos,赋值为posSrc对象的pos属性
20                let count = 0;
21                loadGeometry.vertices.forEach(function (e{//遍历每个顶点
22                //这里需要遍历刚刚克隆的geometry
23                //(暂时不是很明白这点,反正如果遍历group.geometry.vertices
24                //动画系统会让整个物体一起移动,没有伸展开来的效果)。
25                    var newZ = e.z * pos;//得到新的Z值,根据当前的pos值去改变
26                    group.geometry.vertices[count++].set(e.x, e.y, newZ);//设置每个顶点的位置
27                    //group.geometry.vertices是数组类型,所以用count作为索引
28                    group.geometry.verticesNeedUpdate = true;//重要,不然会没有动画效果
29                });
30                group.sortParticles = true;
31            };
32            //tween在每次更新后会执行tween.onUpdate()函数
33            //里面的参数就是我们自定义要让它如果去运动的函数,即上面写的onUpdate
34            tween.onUpdate(onUpdate);
35            tweenStand.onUpdate(onUpdate);
36            tweenBack.onUpdate(onUpdate);
37
38            tween.start();//开启tween
复制代码

在这段代码中,我们创建了三个个补间: tween、tweenStand、tweenBack。第一个补间定义了position属性如何从1过渡到0,第三个刚好相反,第二个是让动画暂时停下。通过chain(方法可以将这三个补间衔接起来,这样当动画启动之后,程序就会在这三个补间循环。代码最后定义的是onUpdate()方法,这个方法遍历粒子系统中的所有顶点,并使用补间(this.pos)提供的位置更新顶点的位置。

补间动画需要在模型加载完成后就启动,所以我们在下面的函数末尾调用tween.start()方法:

如果之前没有把bufferGeometry转化为Geometry类型,要去更改每个顶点的位置会变得比较麻烦。

最后还需要告知three.js什么时候刷新所有的补间动画,所以在render()函数里加上TWEEN.update();

1 function render({
2        TWEEN.update();//通知TWEEN在什么时候去刷新补间动画,重要,否则会没有动画
3        //性能监控器的更新
4        stats.update();
5        renderer.clear();
6        requestAnimationFrame(render);
7        renderer.render(scene, camera);
8    }
复制代码

到了这里,程序的大体就已经完成,剩下的就是创建场景,摄像机,渲染器等等东西以及调整模型的位置。这里不再赘述。

完整的代码如下:

  1
2<html lang="en">
3<head>
4    <meta charset="UTF-8">
5    <title>Naval Craft Spritetitle>
6    <script src="../../import/three.js">script>
7    <script src="../../import/stats.js">script>
8    <script src="../../import/Setting.js">script>
9    <script src="../../import/OrbitControls.js">script>
10    <script src="../../import/tween.min.js">script>
11    <script src="../../import/STLLoader.js">script>
12    <style type="text/css">
13        div#WebGL-output {
14            border: none;
15            cursor: pointer;
16            width100%;
17            height850px;
18            background-color#333333;
19        }
20    
style>
21head>
22<body onload="threeStart()">
23<div id="WebGL-output">div>
24<script>
25    let camera, renderer, scene,controller;
26    function initThree({
27        //渲染器初始化
28        renderer = new THREE.WebGLRenderer({
29            antialiastrue//抗锯齿开启
30        });
31        //设置渲染的大小
32        renderer.setSize(window.innerWidth, window.innerHeight);
33        //设置渲染的颜色
34        renderer.setClearColor(0x333333);
35        renderer.shadowMapEnabled = true;//开启阴影的渲染
36        renderer.shadowMapType = THREE.PCFSoftShadowMap;//设置阴影类型为柔和
37        document.getElementById("WebGL-output").appendChild(renderer.domElement);//将渲染添加到div中
38
39        //初始化摄像机,这里使用透视投影摄像机
40        camera = new THREE.PerspectiveCamera(50window.innerWidth / window.innerHeight, 0.0110000);
41        camera.position.set(353575);//相机的位置,自由调整
42        camera.up.x = 0;//设置摄像机的上方向为哪个方向,这里定义摄像的上方为Y轴正方向
43        camera.up.y = 1;
44        camera.up.z = 0;
45        //摄像机对准的地方
46        camera.lookAt(000);
47
48        //初始化场景
49        scene = new THREE.Scene();
50
51        //相机的移动
52        controller = new THREE.OrbitControls(camera, renderer.domElement);
53        //相机围绕旋转的目标,设置为原点
54        controller.target = new THREE.Vector3(000);
55
56    }
57
58    let loadGeometry;
59    let group;
60
61    function initObject({
62        let loader = new THREE.STLLoader();//创建stl的加载器,用加载器来加载stl模型
63        group = new THREE.Object3D();
64        loader.load("../../asset/naval_craft.stl"function (bufferGeometry{//加载模型的方法,第一个参数是模型的路径,第二个参数时候我们定义的回调函数,一旦模型加载成功,回调函数就会被调用
65            let geometry = new THREE.Geometry().fromBufferGeometry(bufferGeometry);//stl模型加载到js里就会变成bufferGeometry类型,我们先用一个方法把它变成Geometry类型
66            loadGeometry = geometry.clone();//创建该geometry的克隆体,后面会用到
67            let material = new THREE.PointsMaterial({//点云的材质
68                color: 0xffffff,
69                transparenttrue,
70                opacity1,
71                size0.5,//可自由修改看看效果
72                blending: THREE.AdditiveBlending,
73                map: generateSprite()//自定义画布图案来充当每一个粒子的材质
74            });
75            //创建点云,以及设置它的位置及旋转角度,调整到最好看的地方
76            group = new THREE.Points(geometry, material);
77            group.sortParticles = true;
78            group.position.set(0,0,0);
79            group.position.x -=70;
80            group.rotation.x = Math.PI*3/2;
81
82            let posSrc = {pos0};//创建一个posSrc的对象,该对象里面有pos的属性,并初始化该属性为0
83            let tween = new TWEEN.Tween(posSrc).to({pos1}, 5000);//创建tween的补间动画,使posSrc中的pos属性的值在5000ms内从0到1变化
84            tween.easing(TWEEN.Easing.Sinusoidal.InOut);//配置缓动效果
85            let tweenStand = new TWEEN.Tween(posSrc).to({pos1}, 2000);//让动画在pos的值变为1后停止一段时间,方便我们观察,所以再创建一个tween,让pos从1到1(即不变)
86            tween.easing(TWEEN.Easing.Sinusoidal.InOut);//配置缓动效果
87            let tweenBack = new TWEEN.Tween(posSrc).to({pos0}, 5000);//创建tweenBack的补间动画,和初始相反,使posSrc中的pos属性的值在5000ms内从1到0变化
88            tweenBack.easing(TWEEN.Easing.Sinusoidal.InOut);//配置缓动效果
89            //每一个补间动画之间使用chain()连接起来
90            tween.chain(tweenStand);
91            tweenStand.chain(tweenBack);
92            tweenBack.chain(tween);
93
94            //在补间的过程中,让所有的粒子开始移动
95            let onUpdate = function ({
96                let pos = posSrc.pos;//定义一个pos,赋值为posSrc对象的pos属性
97                let count = 0;
98                loadGeometry.vertices.forEach(function (e{//遍历每个顶点,这里需要遍历刚刚克隆的geometry
99                    var newZ = e.z * pos;//得到新的Z值,根据当前的pos值去改变
100                    group.geometry.vertices[count++].set(e.x, e.y, newZ);//设置每个顶点的位置,group.geometry.vertices是数组类型,所以用count作为索引
101                    group.geometry.verticesNeedUpdate = true;//重要,不然会没有动画效果
102                });
103                group.sortParticles = true;
104            };
105            //tween在每次更新后会执行tween.onUpdate()函数,里面的参数就是我们自定义要让它如果去运动的函数,即上面写的onUpdate
106            tween.onUpdate(onUpdate);
107            tweenStand.onUpdate(onUpdate);
108            tweenBack.onUpdate(onUpdate);
109
110            tween.start();//开启tween
111            scene.add(group);
112        });
113    }
114
115    //自定义渐变颜色的画布,前面的文章有介绍,这个方法在写three.js程序很常用
116    function generateSprite({
117
118        var canvas = document.createElement('canvas');
119        canvas.width = 16;
120        canvas.height = 16;
121
122        var context = canvas.getContext('2d');
123        var gradient = context.createRadialGradient(canvas.width / 2, canvas.height / 20, canvas.width / 2, canvas.height / 2, canvas.width / 2);
124        gradient.addColorStop(0'rgba(255,255,255,1)');
125        gradient.addColorStop(0.2'rgba(0,255,255,1)');
126        gradient.addColorStop(0.4'rgba(0,0,255,1)');
127        gradient.addColorStop(1'rgba(0,0,0,1)');
128
129        context.fillStyle = gradient;
130        context.fillRect(00, canvas.width, canvas.height);
131
132        var texture = new THREE.Texture(canvas);
133        texture.needsUpdate = true;
134        return texture;
135
136    }
137
138    //渲染函数
139    function render({
140        TWEEN.update();//通知TWEEN在什么时候去刷新补间动画,重要,否则会没有动画
141        //性能监控器的更新
142        stats.update();
143        renderer.clear();
144        requestAnimationFrame(render);
145        renderer.render(scene, camera);
146    }
147
148    //功能函数
149    function setting({
150        loadFullScreen();
151        loadAutoScreen(camera, renderer);
152        loadStats();
153    }
154
155    //运行主函数
156    function threeStart({
157        initThree();
158        initObject();
159        setting();
160        render();
161    }
162
script>
163body>
164html>
复制代码

你可能感兴趣的:(WebGL three.js学习笔记 加载外部模型以及Tween.js动画)