【webGl】threejs实现一个简单的动画-弹跳的小球

在这里,我们将动态画面简称为动画(animation)。正如动画片的原理一样,动画的本质是利用了人眼的视觉暂留特性,快速地变换画面,从而产生物体在运动的假象。而对于Three.js程序而言,动画的实现也是通过在每秒中多次重绘画面实现的。

为了衡量画面切换速度,引入了每秒帧数FPS(Frames Per Second)的概念,是指每秒画面重绘的次数。FPS越大,则动画效果越平滑,当FPS小于20时,一般就能明显感受到画面的卡滞现象。

那么FPS是不是越大越好呢?其实也未必。当FPS足够大(比如达到60),再增加帧数人眼也不会感受到明显的变化,反而相应地就要消耗更多资源(比如电影的胶片就需要更长了,或是电脑刷新画面需要消耗计算资源等等)。因此,选择一个适中的FPS即可。

NTSC标准的电视FPS是30,PAL标准的电视FPS是25,电影的FPS标准为24。而对于Three.js动画而言,一般FPS在30到60之间都是可取的。

setInterval方法

如果要设置特定的FPS(虽然严格来说,即使使用这种方法,JavaScript也不能保证帧数精确性),可以使用JavaScript DOM定义的方法:

setInterval(func, msec)

其中,func是每过msec毫秒执行的函数,如果将func定义为重绘画面的函数,就能实现动画效果。setInterval函数返回一个id,如果需要停止重绘,需要使用clearInterval方法,并传入该id,具体的做法为:

requestAnimationFrame方法

大多数时候,我们并不在意多久重绘一次,这时候就适合用requestAnimationFrame方法了。它告诉浏览器在合适的时候调用指定函数,通常可能达到60FPS。

如何取舍

setInterval方法与requestAnimationFrame方法的区别较为微妙。一方面,最明显的差别表现在setInterval可以手动设定FPS,而requestAnimationFrame则会自动设定FPS;但另一方面,即使是setInterval也不能保证按照给定的FPS执行,在浏览器处理繁忙时,很可能低于设定值。当浏览器达不到设定的调用周期时,requestAnimationFrame采用跳过某些帧的方式来表现动画,虽然会有卡滞的效果但是整体速度不会拖慢,而setInterval会因此使整个程序放慢运行,但是每一帧都会绘制出来;

总而言之,requestAnimationFrame适用于对于时间较为敏感的环境(但是动画逻辑更加复杂),而setInterval则可在保证程序的运算不至于导致延迟的情况下提供更加简洁的逻辑(无需自行处理时间)。

开始工作

完成init函数

var requestAnimationFrame = window.requestAnimationFrame
        || window.mozRequestAnimationFrame
        || window.webkitRequestAnimationFrame
        || window.msRequestAnimationFrame;
window.requestAnimationFrame = requestAnimationFrame;

var scene = null;
var camera = null;
var renderer = null;

var id = null;

var stat = null;

function init() {
    stat = new Stats();
    stat.domElement.style.position = ‘absolute‘;
    stat.domElement.style.right = ‘0px‘;
    stat.domElement.style.top = ‘0px‘;
    document.body.appendChild(stat.domElement);

    renderer = new THREE.WebGLRenderer({
        canvas: document.getElementById(‘mainCanvas‘)
    });
    scene = new THREE.Scene();

    id = requestAnimationFrame(draw);
}

function draw() {
    stat.begin();

    renderer.render(scene, camera);

    id = requestAnimationFrame(draw);

    stat.end();
}

function stop() {
    if (id !== null) {
        cancelAnimationFrame(id);
        id = null;
    }
}

然后,为了实现弹球弹动的效果,我们创建一个球体作为弹球模型,创建一个平面作为弹球反弹的平面。为了在draw函数中改变弹球的位置,我们可以声明一个全局变量ballMesh,以及弹球半径ballRadius

var ballMesh = null;
var ballRadius = 0.5;

init函数中添加球体和平面,使弹球位于平面上,平面采用棋盘格图像作材质:

// ball
ballMesh = new THREE.Mesh(new THREE.SphereGeometry(ballRadius, 16, 8),
    new THREE.MeshLambertMaterial({
        color: 0xffff00
}));
ballMesh.position.y = ballRadius;
scene.add(ballMesh);

// plane
var texture = THREE.ImageUtils.loadTexture(‘../img/chess.png‘, {}, function() {
    renderer.render(scene, camera);
});
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(4, 4);
var plane = new THREE.Mesh(new THREE.PlaneGeometry(5, 5),
        new THREE.MeshLambertMaterial({map: texture}));
plane.rotation.x = -Math.PI / 2;
scene.add(plane);

现在,每帧绘制的都是相同的效果:

为了记录弹球的状态,我们至少需要位置、速度、加速度三个矢量,为了简单起见,这里弹球只做竖直方向上的自由落体运动,因此位置、速度、加速度只要各用一个变量表示。其中,位置就是ballMesh.position.y,不需要额外的变量,因此我们在全局声明速度v和加速度a

var v = 0;
var a = -0.1;

这里,a = -0.1代表每帧小球向y方向负方向移动0.1个单位。

一开始,弹球从高度为maxHeight处自由下落,掉落到平面上时会反弹,并且速度有损耗。当速度很小的时候,弹球会在平面上作振幅微小的抖动,所以,当速度足够小时,我们需要让弹球停止跳动。因此,定义一个全局变量表示是否在运动,初始值为false

var isMoving = false;

在HTML中定义一个按钮,点击按钮时,弹球从最高处下落:

function drop() {
    isMoving = true;
    ballMesh.position.y = maxHeight;
    v = 0;
}

下面就是最关键的函数了,在draw函数中,需要判断当前的isMoving值,并且更新小球的速度和位置:

function draw() {
    stat.begin();

    if (isMoving) {
        ballMesh.position.y += v;
        v += a;

        if (ballMesh.position.y <= ballRadius) {
            // hit plane
            v = -v * 0.9;
        }

        if (Math.abs(v) < 0.001) {
            // stop moving
            isMoving = false;
            ballMesh.position.y = ballRadius;
        }
    }

    renderer.render(scene, camera);

    id = requestAnimationFrame(draw);

    stat.end();
}

这样就实现小球的弹动效果了。最终的代码为:

var requestAnimationFrame = window.requestAnimationFrame
        || window.mozRequestAnimationFrame
        || window.webkitRequestAnimationFrame
        || window.msRequestAnimationFrame;
window.requestAnimationFrame = requestAnimationFrame;

var scene = null;
var camera = null;
var renderer = null;

var id = null;

var stat = null;

var ballMesh = null;
var ballRadius = 0.5;
var isMoving = false;
var maxHeight = 5;

var v = 0;
var a = -0.01;

function init() {
    stat = new Stats();
    stat.domElement.style.position = ‘absolute‘;
    stat.domElement.style.right = ‘0px‘;
    stat.domElement.style.top = ‘0px‘;
    document.body.appendChild(stat.domElement);

    renderer = new THREE.WebGLRenderer({
        canvas: document.getElementById(‘mainCanvas‘)
    });
    scene = new THREE.Scene();

    camera = new THREE.OrthographicCamera(-5, 5, 3.75, -3.75, 0.1, 100);
    camera.position.set(5, 10, 20);
    camera.lookAt(new THREE.Vector3(0, 3, 0));
    scene.add(camera);

    // ball
    ballMesh = new THREE.Mesh(new THREE.SphereGeometry(ballRadius, 16, 8),
        new THREE.MeshLambertMaterial({
            color: 0xffff00
    }));
    ballMesh.position.y = ballRadius;
    scene.add(ballMesh);

    // plane
    var texture = THREE.ImageUtils.loadTexture(‘../img/chess.png‘, {}, function() {
        renderer.render(scene, camera);
    });
    texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
    texture.repeat.set(4, 4);
    var plane = new THREE.Mesh(new THREE.PlaneGeometry(5, 5),
            new THREE.MeshLambertMaterial({map: texture}));
    plane.rotation.x = -Math.PI / 2;
    scene.add(plane);

    var light = new THREE.DirectionalLight(0xffffff);
    light.position.set(10, 10, 15);
    scene.add(light);

    id = requestAnimationFrame(draw);
}

function draw() {
    stat.begin();

    if (isMoving) {
        ballMesh.position.y += v;
        v += a;

        if (ballMesh.position.y <= ballRadius) {
            // hit plane
            v = -v * 0.9;
        }

        if (Math.abs(v) < 0.001) {
            // stop moving
            isMoving = false;
            ballMesh.position.y = ballRadius;
        }
    }

    renderer.render(scene, camera);

    id = requestAnimationFrame(draw);

    stat.end();
}

function stop() {
    if (id !== null) {
        cancelAnimationFrame(id);
        id = null;
    }
}

function drop() {
    isMoving = true;
    ballMesh.position.y = maxHeight;
    v = 0;
}

链接:http://runjs.cn/code/qqpikkwt

链接:http://runjs.cn/detail/ecll36ex

要好好复习一下物理和数学了

 
时间: 2024-11-04 20:14:23

【webGl】threejs实现一个简单的动画-弹跳的小球的相关文章

pyqt一个简单的动画

import sys from PyQt4.QtGui import QApplication , QGraphicsEllipseItem , QGraphicsItemAnimationfrom PyQt4.QtGui import QGraphicsScene , QGraphicsViewfrom PyQt4.QtCore import SIGNAL , QPointF , QTimeLine app = QApplication( sys.argv ) # create a graph

一个简单hover动画效果

HTML: <div id="demo1" class="demo">demo1</div> <div id="demo2" class="demo">demo2</div> CSS: .demo { width: 100px; height: 100px; text-align: center; line-height: 100px; border: 10px solid #c

代码:一个简单css3动画效果demo

四行文字会逐次掉落: <style type="text/css"> @-webkit-keyframes fadeInDown1 { 0% { -webkit-transform: translate3d(0, -20px, 0); transform: translate3d(0, -20px, 0); opacity: 0; } 100% { -webkit-transform: none; transform: none; opacity: 1; } } .div1

js实现简单的动画效果之移动

不准时更新的日常,这次我们使用javaScript实现一个简单的动画移动效果,其思路想法很简单,就是采用"CSS DOM",对元素的位置进行改变.然后使用setTimeout()函数,对改变位置的函数进行反复调用,让文字或图片进行移动,行成动画效果. 废话不多说,直接上code: 这是HTML: <body> <p id="message"> 逝者如斯夫,不舍昼夜. </p> <script src="js/ini

一个简单的loading动画,version 1.0

一个简单的loading动画:如图 点我查看

分享:计算机图形学期末作业!!利用WebGL的第三方库three.js写一个简单的网页版“我的世界小游戏”

这几天一直在忙着期末考试,所以一直没有更新我的博客,今天刚把我的期末作业完成了,心情澎湃,所以晚上不管怎么样,我也要写一篇博客纪念一下我上课都没有听,还是通过强大的度娘完成了我的作业的经历.(当然作业不是百度来的,我只是百度了一些示例代码的意思,怎么用!算了,越解释万一越黑呢!哈哈O(∩_∩)O哈哈~) ----------------------------------------------------------------分界线------------------------------

一个简单的循环往复的动画效果

1.概述:在我们编程时会用到一些简单的动画效果,下面介绍一个: 2.代码如下: <!DOCTYPE HTML> <HTML> <HEAD> <TITLE> By ShaZhou </TITLE> </HEAD> <BODY> <div class="dotA" style="position:absolute">a</div> <div class=&q

【Unity】UGUI系列教程——拼接一个简单界面

0.简介: 在目前的游戏市场上,手游依然是市场上的主力军,而只有快速上线,玩法系统完善的游戏才能在国内市场中占据份额.而在手游开发过程中,搭建UI系统是非常基本且重要的技能,极端的说如果对Unity的UI系统熟悉,就可以去游戏公司上班了 :)(笑~). 但是就像蛋炒饭,最简单的事要做好也是非常困难的.UI这块的变动也经常是整个游戏最频繁的一块,如果没有一个合理的设计思路,和管理方案,后期将会陷入无止境的调试优化之中. 万丈高楼平地起,现在让我们开始从Unity中的UGUI系统进行讲解. 1.创建

[stm32] 一个简单的stm32vet6驱动2.4寸240X320的8位并口tft屏DEMO

书接上文: 最近在研究用低速.低RAM的单片机来驱动小LCD或TFT彩屏实现动画效果 首先我用一个16MHz晶振的m0内核的8位单片机nRF51822尝试驱动一个1.77寸的4线SPI屏(128X160), 发现,刷一屏大约要0.8s左右的时间, 具体收录在<1.一个简单的nRF51822驱动的天马4线SPI-1.77寸LCD彩屏DEMO>中 觉得,如果用72MHz的STM32也许效果会好很多 于是在stm32上做了个类似的版本, 具体收录在<一个简单的stm32vet6驱动的天马4线S