webgl之3d动画

时间:2023-08-23 23:04:55

  之前的几篇文章都是静态的,而这里主要介绍如何使物体动起来,并且学会使用性能监视器来监测性能。

  而如果要让物体动起来,实际上我们是有两种方法的,第一种是让物体真的动起来,另外一种是让摄像机动起来这样物体相对来说也就动起来了。另外,实际上在让物体动起来的过程中,我们是不断通过调用 renderer.render(scene, camera)这个函数实现的,那么怎么才能不断调用这个函数呢?这就需要用到 requestAnimationFrame函数了,这个函数接受一个函数作为参数,并且会在每秒内调用60次,那么最终屏幕就会在一秒内渲染60次,这样就可以形成动画了。

一、物体运动

  首先,我们先让物体动起来,如下所示,就是一个让物体运动起来的动画:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>three.js</title>
<style>
* {
margin: ;
padding: ;
}
</style>
<script src="./three.js"></script>
</head> <body>
<script>
var scene = new THREE.Scene(); var axes = new THREE.AxesHelper();
scene.add(axes); var camera = new THREE.PerspectiveCamera(, window.innerWidth/window.innerHeight, , );
camera.position.x = ;
camera.position.y = ;
camera.position.z = ;
camera.up.x = ;
camera.up.y = ;
camera.up.z = ; // camera.up.z = 1, 所以渲染出来的结果是z轴朝上。
camera.lookAt(scene.position); var renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0x111111);
renderer.setSize(window.innerWidth, window.innerHeight); var cubeGeometry = new THREE.CubeGeometry(, , );
var meshCube = new THREE.MeshBasicMaterial({color: 0xff0000});
var cube = new THREE.Mesh(cubeGeometry, meshCube);
cube.position.x = ;
cube.position.y = ;
cube.position.z = ;
scene.add(cube);
document.body.append(renderer.domElement); var isDestination = false; function animation() {
var interval = ;
if (!isDestination) {
cube.position.x = cube.position.x + interval;
} else {
cube.position.x = cube.position.x - interval;
}
if (cube.position.x == ) {
isDestination = true;
}
if (cube.position.x == ) {
isDestination = false;
}
renderer.render(scene, camera);
requestAnimationFrame(animation);
} animation();
</script>
</body>
</html>

  即首先创建场景,然后创建坐标轴并加入到场景中,接着创建相机,注意相机所接受的参数比较多,且相机需要指定position位置以及up位置,且使用lookAt函数,接下来创建一个渲染器,指定背景颜色和宽、高,然后创建一个物体,最后需要将渲染器加入到document.body中,接着是一个动画,然后运行即可。但是,我们可以发现虽然上面代码完成了,但是封装的不好,我们可以尝试着将其用函数封装,如下:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>three.js</title>
<style>
* {
margin: ;
padding: ;
}
</style>
<script src="./three.js"></script>
</head> <body>
<script>
var scene;
function initScene() {
scene = new THREE.Scene();
} var axes;
function initAxes() {
axes = new THREE.AxesHelper();
scene.add(axes);
} var camera;
function initCamera() {
camera = new THREE.PerspectiveCamera(, window.innerWidth/window.innerHeight, , ); camera.position.x = ;
camera.position.y = ;
camera.position.z = ; camera.up.x = ;
camera.up.y = ;
camera.up.z = ; camera.lookAt(scene.position);
} var renderer;
function initRenderer() {
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x111111);
document.body.append(renderer.domElement);
} var cube;
function initObject() {
var cubeGeometry = new THREE.CubeGeometry(, , );
var meshCube = new THREE.MeshBasicMaterial({color: 0xff0000});
cube = new THREE.Mesh(cubeGeometry, meshCube);
cube.position.x = ;
cube.position.y = ;
cube.position.z = ;
scene.add(cube);
} var isDestination = false;
function animation() {
var interval = ;
var destination = ;
var direction = "y"; if (!isDestination) {
cube.position[direction] += interval;
} else {
cube.position[direction] -= interval;
}
if (cube.position[direction] == destination) {
isDestination = true;
}
if (cube.position[direction] == ) {
isDestination = false;
}
renderer.render(scene, camera);
requestAnimationFrame(animation);
} function threeStart() {
initScene();
initAxes();
initCamera();
initRenderer();
initObject();
animation();
} threeStart();
</script>
</body>
</html>

  如上所示,通过函数封装,程序的逻辑性更好了一些,并且仅仅暴露了比如scene、camera、axes、renderer等必要的变量,而其他的变量不会对全局造成污染,而最后的animation函数,我们定义了direction为"x",这样就可以通过这里的修改控制cube运动的坐标轴了,这一点利用的就是JavaScript调用属性的特点。最后我们统一将初始化调用函数写在了threeStart中,这样,就可以通过threeStart函数调用开启这个项目了,最终得到的效果如下所示:

webgl之3d动画

二、相机运动

如下所示:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>three.js</title>
<style>
* {
margin: ;
padding: ;
}
</style>
<script src="./three.js"></script>
</head> <body>
<script>
var scene;
function initScene() {
scene = new THREE.Scene();
} var axes;
function initAxes() {
axes = new THREE.AxesHelper();
scene.add(axes);
} var camera;
function initCamera() {
camera = new THREE.PerspectiveCamera(, window.innerWidth/window.innerHeight, , ); camera.position.x = ;
camera.position.y = ;
camera.position.z = ; camera.up.x = ;
camera.up.y = ;
camera.up.z = ; camera.lookAt(scene.position);
} var renderer;
function initRenderer() {
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x111111);
document.body.append(renderer.domElement);
} var cube;
function initObject() {
var cubeGeometry = new THREE.CubeGeometry(, , );
var meshCube = new THREE.MeshBasicMaterial({color: 0xff0000});
cube = new THREE.Mesh(cubeGeometry, meshCube);
cube.position.x = ;
cube.position.y = ;
cube.position.z = ;
scene.add(cube);
} var isDestination = false;
function animation() { var interval = ;
if (!isDestination) {
camera.position.x -= interval;
camera.position.y -= interval;
camera.position.z -= interval;
} else {
camera.position.x += interval;
camera.position.y += interval;
camera.position.z += interval;
} if (camera.position.x == ) {
isDestination = true;
}
if (camera.position.x == ) {
isDestination = false;
} renderer.render(scene, camera);
requestAnimationFrame(animation);
} function threeStart() {
initScene();
initAxes();
initCamera();
initRenderer();
initObject();
animation();
} threeStart();
</script>
</body>
</html>

这里的思路也非常简单,就是给camera做了一个动画,效果如下所示:

webgl之3d动画

ok,到这里,我们就了解了使得场景运动起来的两种方法,但是,我们应该如何监测他们的性能呢,下面来说一说。

三、性能评估

   在3D世界里,我们经常用的是帧数来评价性能,帧数就是图形处理器每秒钟可以刷新的次数,用fps(Frames Per Second)来表示,毫无疑问,帧数越高,那么动画就会越流畅,为了监视FPS,就需要学习性能监视器。通常,我们使用stats(github地址/stats.min.js)进行监视,而stats是非常有名的JavaScript性能监视库,它提供了一些简单的信息来帮助你检测你的代码性能:

  1. FPS 即上一秒渲染的帧数(Frames),显然这个值越大,说明上一秒钟内刷新的次数越多,那么性能越好。
  2. MS 即渲染一帧需要的毫秒数,显然,MS越小越好。
  3. MB 是分配内存的字节数。
  4. CUSTOM是指用户自定义的面板。

    如下所示:

webgl之3d动画

    那么如何使用呢?如下所示:

  

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>stats</title>
<script src="./stats.js"></script>
</head>
<body>
<script>
var stats = new Stats();
stats.showPanel( ); // 0: fps, 1: ms, 2: mb, 3+ custom
document.body.appendChild( stats.dom ) function animate() {
stats.begin(); // monitored code gose here stats.end(); requestAnimationFrame(animate); } animate();
</script>
</body>
</html>

  即首先引入stats.js库文件,然后实例化得到一个stats,接着通过stats.showPanel()显示我们希望看到的面板,然后添加到dom中,最后,我们就可以将动画相关代码写在stats.begin()和stats.end()之间,打开浏览器就可以看到效果了。

  另外,stats.js库的github中说明,可以直接将下面的js代码粘贴到任何html网页的script标签中,然后就可以使用了,如下:

    <script>
(function () {
var script = document.createElement('script'); script.onload = function () {
var stats = new Stats(); document.body.appendChild(stats.dom); requestAnimationFrame(function
loop() { stats.update(); requestAnimationFrame(loop) });
}; script.src = './stats.js'; document.head.appendChild(script);
})()
</script>

   这段代码非常好理解,就是创建了一个script标签,然后在script标签加载完成之后创建了stats实例,接着添加到dom中,最后检测性能。注意,其中设定了script标签的src属性,根据需要自己设定即可。

    

  这样,我们只要将上述代码加到之前我们写的动画页面中即可看到评估性能了,如下:

webgl之3d动画

  如上所示,我们可以看到在左上角就进行了性能检测,上一秒的FPS为60。而后面括号内的(60 - 60)说明FPS的变化范围在60 - 60之间,因为我们使用的requestAnimationFrame,所以FPS几乎始终为60。

四、游戏循环、帧循环、渲染循环

  游戏循环、帧循环和渲染循环都是同一种循环。

  我国早期的葫芦娃动画片,这种动画片不是3D引擎做的,而是画家做的剪纸,然后拍下的照片进行播放,然后再通过连续翻动的形式,就可以形成动画了,比如在抖音上,我们可以看到有人在本子上每一页都画了画,然后不停的翻动本子,然后这个动画场景就出来了。

  游戏循环就是如下所示的方式:

while (true)
{
updateStatus();
draw();
}

  即先更新状态,然后再draw就可以了。 更新状态的步骤中主要做的就是比如控制游戏中人物的位置以及背景的颜色等等。

  而draw()的主要步骤就是先清空场景,然后再绘制,显然,我们是不可能在不清空这一帧就画下一帧。

  通用的代码形式如下所示:

function animate() {
render();
requestAnimationFrame( animate );
}

  其中render过程就做了更新状态以及draw的工作,然后在使用requestAnimationFrame调用这个函数,达成这个死循环,但是不会卡死,因为计算机只是会在空闲的时候来执行这些函数。并且这里requestAnimationFrame是每秒更新60帧。

  注意:一般电影的播放在24帧每秒就可以做到不卡,而游戏需要做到48 - 60帧每秒才会不卡。这是因为电影的胶片会有一定的曝光,导致残影的存在,这样就可以使得其在24帧每秒保持不卡。

  

  

五、动画引擎 tween.js

  上面介绍了通过移动相机或者移动物体来产生动画的效果,但是如果动画再复杂一些,我们用原生实现就回去比较麻烦,所以这里我么可以借助动画引擎tween.js来实现动画效果,它一般是和three.js结合比较紧密的。

  tween.js的github中star在5k多一些,也是比较流行的,使用起来也比较方便,我们可以在tween.js的raw中下载,然后通过script标签引入就可以使用了,如下所示:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>tween</title>
<script src="./tween.js"></script>
</head>
<body>
<script>
var box = document.createElement('div');
box.style.setProperty('background-color', '#008800');
box.style.setProperty('width', '100px');
box.style.setProperty('height', '100px');
document.body.appendChild(box); function animate(time) {
requestAnimationFrame(animate);
TWEEN.update(time);
}
requestAnimationFrame(animate); var coords = {
x: ,
y:
}; var tween = new TWEEN.Tween(coords)
.to({x: , y: }, )
.easing(TWEEN.Easing.Quadratic.Out)
.onUpdate(function () {
box.style.setProperty('transform', 'translate(' + coords.x + 'px, ' + coords.y + 'px)');
})
.start();
</script>
</body>
</html>

  如上所示,我们创建了一个div,然后创建了animate动画,接着我们指定原点在(0, 0)处,最后我们构建了一个Tween对象,然后指定它在1000ms时移动到(300, 200)坐标处,且指定了移动的动画方式,然后在update时不断改变其位置。

  ok,这一部分的内容就到这里了。