如何基于Canvas来模拟真实雨景Part1:预备知识和创建基本对象

techbrood 发表于 2016-05-20 20:15:55

标签: canvas, html5, 下雨, 窗户, 教程

- +

我们之前使用CSS3实现过逼真的雨滴,但这个方法只适合用来模拟一些静态场景或少量雨滴动画,如果用来实现大量雨滴的动画,会遭遇严重的性能问题。而使用Canvas能利用硬件加速,渲染性能要高得多,适合用来创建大量粒子的动画。本例我们讲解如何使用Canvas 2D来实现一个“雨打窗户”的动画场景,雨滴(粒子)的数量可以有成百上千乃至上万个。本例不涉及更为复杂的3D空间场景的模拟。

blob.png

预备知识

如果你不知道怎么使用Canvas 2D,请先阅读踏得网在线教程Canvas基础知识

基本上浏览器为我们准备了一个2D渲染上下文,我们在这里绘制图形图像对象,绘制好后再和页面合成。所以和CSS不同,Canvas内部对象的变化不会影响DOM文档,也因此不会导致DOM重排和重绘而影响性能。

除了Canvas接口外,我们还需要具备一些基本的物理学和数学知识。我们观察实际物理世界中的雨点,还有这么几个特征:

  1. 能反射外部环境景色(真实的反射、环境折射情况相当复杂)

  2. 小的雨点停留在窗户上,由于有阻力而静止不动,但会被移动的雨滴所吞没

  3. 大到一定程度的雨点会因为重力效应而滑落

  4. 雨滴在滑落的过程中形成轨迹,这个现象会导致雨滴变小

  5. 由于雨点是液体,足够接近的雨点会相互吸引并融合成单个更大的雨点(因为单个球面积要远小于2个单独的球面积,因此融合后液体将处于势能最小状态即最稳定状态。)

  6. 如果存在风向,那么雨滴将是斜着掉落/滑落的。

我们的代码不会去试图完美模拟这些特征,因为在一个快速变换的环境中人眼留意的是总体印象和感觉。

我们将去除一些不必要的复杂性,比如暂不考虑环境折射,另外我们把雨滴降低维度在2D空间上,简化为一个运动的小圆点(在三维空间是一个椭球体),它包含大小、位置、速度、加速度属性,这样在计算雨点距离时只需要考虑x、y轴分量,而求体积也退化为求面积。

创建雨景容器

我们首先创建一个场景视图对象来容纳背景(远景图片)、玻璃窗户(我们的观察点)和雨滴:

function RainyView(options, canvas) {
	this.img = options.image;//背景图片
	this.options = options;//参数
	this.drops = [];//一组雨滴

	// 创建canvas容器,用来绘制对象和动画
	this.canvas = canvas || this.prepareCanvas();
	this.prepareBackground();//创建背景
	this.prepareGlass();//创建窗户

	// 设置window.requestAnimFrame动画方法
	this.setRequestAnimFrame();
}

其中setRequestAnimFrame方法实现了requestAnimFrame接口,这个是比setTimeout更好的动画绘制接口,在Canvas按钮绕边动画一文中已有详细说明。

创建背景

RainyView.prototype.prepareBackground = function() {
	this.background = document.createElement('canvas');
	this.background.width = this.canvas.width;
	this.background.height = this.canvas.height;

	var context = this.background.getContext('2d');
	context.clearRect(0, 0, this.canvas.width, this.canvas.height);
	
        context.drawImage(this.img, this.options.crop[0], this.options.crop[1], this.options.crop[2], this.options.crop[3], 0, 0, this.canvas.width, this.canvas.height);
	
	//blur the background ...
};

上面的代码创建一个background的canvas,然后把图像绘制在上面,由于是远景图并且是透过玻璃看出去的,我们需要给图片添加模糊效果,这个有很多方法,比如CSS3的blur、scale方法,以及使用JS的一些基于像素处理的模糊算法,比如相邻像素的混合算法、高斯模糊、栈模糊算法。这不是本文的重点,不做进一步细述。

创建窗户

RainyView.prototype.prepareGlass = function() {
	this.glass = document.createElement('canvas');
	this.glass.width = this.canvas.width;
	this.glass.height = this.canvas.height;
	this.context = this.glass.getContext('2d');
};

这个比较简单,使用一个新的canvas上下文来绘制窗户。

创建雨滴

/**
 * 定义一个雨滴对象
 * @param RainyView 雨滴的父容器
 * @param centerX 雨滴中心的x坐标
 * @param centerY 雨滴中心的y坐标
 * @param min 最小尺寸
 * @param base 随机尺寸基准
 */

function Drop(RainyView, centerX, centerY, min, base) {
	this.x = Math.floor(centerX);
	this.y = Math.floor(centerY);
	this.r = (Math.random() * base) + min;
	this.RainyView = RainyView;
	this.context = RainyView.context;
	this.reflection = RainyView.reflected;
}

你可以看到雨滴的大小是随机生成的,介于min和min+base之间。

雨滴对象除了上述的属性外,还有两个核心的方法:绘制和移动(滑落)。

Drop.prototype.draw = function() {
	this.context.save();//保存画板绘图状态
	this.context.beginPath();
	
	this.context.arc(this.x, this.y, this.r, 0, Math.PI * 2, true);
	
	this.context.closePath();

	if (this.RainyView.reflection) {
		this.RainyView.reflection(this);
	}
	
	this.context.restore();//恢复画板绘图状态
};

Drop.prototype.move = function() {
	if (this.terminate) {
		return false;
	}
	var stopped = this.RainyView.gravity(this);
	if (!stopped && this.RainyView.trail) {
		this.RainyView.trail(this);
	}
	if (this.RainyView.options.enableCollisions) {
		var collisions = this.RainyView.matrix.update(this, stopped);
		if (collisions) {
			this.RainyView.collision(this, collisions);
		}
	}
	return !stopped || this.terminate;
};

上述draw方法就是绘制一个半径为r的圆,为了简单起见,暂未考虑当两个Drop对象由于接近而融合时所产生的变形。move方法完成两个任务,一个是重力掉落(gravity函数),另外一个功能是碰撞检测(当发生碰撞时,触发融合)。由于这两个方法和环境有关,所以这里实现为其容器RainyView的方法。


到此,我们就完成了雨景布局和基本对象构建,接下去就是要为对象添加实际的动画。

我们在第2部分进行详细描述。


possitive(8) views15662 comments1

发送私信

最新评论

iefreer 2016-05-22 15:38:35

雨滴对象应该绘制为非完美的圆形。稍后单独发文描述。


请先 登录 再评论.
相关文章
  • 常用光照类型基本概念工作原理及其计算公式

    在三维场景中,原理上物体的渲染效果取决于光照与物体表面的相互作用,对于渲染程序而言,可以通过把一些数学公式应用于像素着色来实现,从而模拟出真实生活中的...

  • html5跨平台实战-第一周-水平测验-新闻列表页面

    这是一个DIV+CSS布局页面的一个实例,主要介绍POSITION定位、导航UL LI的制作、利用浮动原理对页面分栏、分列的页面布局。新闻页面的效果图

  • Blender2.7给平面模型添加纹理贴图

    在blender中给模型添加纹理,需要有2个步骤:首先在对象属性栏中给该对象添加材料和纹理建立纹理映射添加材料和纹理这是常见操作,略过步骤。但是仅仅这样操作,...

  • WebGL、Asm.js和WebAssembly概念简介

    随着HTML技术的发展,网页要解决的问题已经远不止是简单的文本信息,而包括了更多的高性能图像处理和3D渲染方面。这正是要引入WebGL、Asm.js和WebAssembly这些技...

  • JavaScript语言多编程范式简介

    和C++等语言类似,JS支持多范式(paradigms)编程。我们常常混合这些范式来完成一些大型Web项目。JS支持3种编程范式:命令式、面向对象和函数式。命令式(Imperative JavaScript)命令式就是简单的从上而下完成任务,流水账过程式编码风格:function

  • JavaScript事件模型图解

    在JavaScript中用户交互的核心部分就是事件处理。本文为对事件模型和处理机制的总体性描述。Event是什么?
    event是用户操作网页时发生的交互动作,比如clic...

  • Web界面编程状态变化和JS开发框架(React/Angular/Ember)

    UI编程中的一个关键课题就是界面组件化(可复用)以及组件状态管理。稍早一些的windows程序员可能接触过MFC,其界面编程中有一个DDX(DoDataExchange)的机制,...

  • 使用SVG和CSS3创建圆形进度条动画

    圆形进度条是一个经典的控制面板元素,常用于显示任务进度,比如用户档案的完整程度,或者升级状态。有很多方法来实现圆形进度条,比如用JS, CSS3, Canvas, SVG...

  • 学习使用CSS制作进度条

    进度条是基础的界面控件,可用于多种场合,比如任务完成进度,手机充电状态等。本文介绍一个简单实用的进度条制作方法。预期效果如下图所示:直观上,我们可以把该进度条控件分为2个部分,外部的边界用来表示固定的目标范围,里面的条形部分用来表示当前进度。外部目标范围元素的CSS代码编写如下:.pb-scope

  • 如何使用Three.js加载obj和mtl文件

    OBJ和MTL是3D模型的几何模型文件和材料文件。在最新的three.js版本(r78)中,以前的OBJMTLLoader类已废弃。现在要加载OBJ和MTL文件,需要结合OBJLoader和MTLLoade...

  • WebGL入门教程4 - 使用纹理贴图(Texture Map)

    3D建模和纹理贴图的关系就好比人体和皮肤(或着装)的关系,3D建模用来处理空间属性,而贴图适合用来处理细腻的表面属性。如果不使用贴图,而想在表面达到足够的...

  • 如何使用CSS3实现一个平滑的3D文本标题

    要实现3D文本,基本上有3种方法:1. 使用CSS3的投影滤镜(filter: drop-shadow)2. 使用3d建模和CSS3 3d变换来实现(最真实)3. 使用CSS3 text-shadow属性来实现...

  • 如何使用CSS3实现一个3D商品标签

    使用3D缎带形状的标签是常见的一个设计模式,用在商品折扣、文章标题或网站推荐信息上,来突出显示重点内容,吸引用户视觉焦点。实现的方法有2种,一种是使用背...

  • 如何使用CSS3/SCSS实现逼真的车窗雨滴效果

    在天气预报类的Web移动应用中,常常需要实现屏幕的雨滴效果,表示阴雨天气。感觉上比较神奇,其实想通了,这个效果的实现只需要一点物理知识和CSS3的简单变换。实现一个小雨滴首先雨滴是一个个小的椭圆形元素:.raindrop

  • 更多...