如何使用WebGL创建一个逼真的下雨动画
之前写过文章来分别讲解如何使用CSS3和Canvas2D实现过雨滴和下雨动画。
通过背景处理看起来也有视觉上的3D效果,但并非真正的3D场景,
如果要加入用户交互,进行360°全景浏览就很难实现,并且粒子放大后会失真。
今天我们使用WebGL来实现一个真正3D建模的下雨动画,所使用的技术可用于很多场景。
对WebGL没有概念的,请阅读踏得网之前写的WebGL基础知识相关文章。
要使用WebGL绘图,总体上有4个步骤,初始化webgl绘图环境、建立数据模型并绑定缓存、建立着色器程序、关联着色器属性和缓存完成绘制或动画。
要实现一个下雨动画,我们首先要实现一个3D雨滴的绘制。
3D雨滴具有如下特点:
雨滴是一个不规则椭球体,因为重力效应,下面胖上面瘦,Z轴长度比X/Y轴要长
雨滴是一个球透镜。球透镜是凸透镜的一种。凸透镜的成像原理:当物距(u)大于二倍焦距(f)时,所成像为倒立缩小的实像。
下图右为平行但不经过主光轴的光线入射球透镜时的光路图;球透镜的焦距(f)即由球心(O)到焦点(F)的距离。
经计算可以得出,球透镜的焦距为:
其中,N 为透镜材料的折射率,我们知道水的折射率约为1.33(玻璃的折射率为1.5~1.9),
不难算出雨滴的焦距(约为) f ≈ 2R。
在摄影/观察雨景过程中,物距一般远大于2倍焦距的,故透过雨滴观察到的,是远景的倒立缩小实像。
雨滴和水晶球一样,会产生“桶形畸变”,如下图
桶形畸变(Barrel Distortion)又称桶形失真,是一种成像缺陷。使用广角镜头时最容易发生桶形畸变,原本是方形的物体影像,会变成四角向内收缩、边线中段则向外凸出,好象木桶一样。
由于存在重力加速度,雨滴将呈现加速下落的运动
雨滴的大小是随机的,而小雨滴容易受到风阻影响,所以原则上大雨滴的下落速度要快,也就是雨滴的速度是各自不同的。
我们完成一个雨滴对象的绘制后,在尺寸、形状和位置这个方面引入随机量,从而生成大量雨滴。
最后在Y方向上添加恒定加速度和随机阻力,形成下落的动画。
生成不规则椭球体
所谓创建一个球体,在WebGL中,我们是创建一个多面体,而多面体由一组顶点所定义。
我们模仿地球,使用经纬线来生成椭球体的各个顶点(vertex),坐标设定使用椭球体坐标公式:
x=asinθcosφ
y=bsinθsinφ
z=ccosθ (0≤θ≤π, 0≤φ<2π)
其中a,b,c是椭球体的3个半径,θ(theta)是z轴夹角,φ(phi)是投影到x/y平面上从x开始的夹角。
代码类似如下:(注意WebGL中的x/y/z坐标轴的方向和上述立体几何学中不完全相同)
var latitudeBands = 60; var longitudeBands = 60; var radius = 0.4; var aRadius = radius * 1.2; var bRadius = radius * 1.3; var cRadius = radius * 1.0; var vertexPositionData = []; var normalData = []; var textureCoordData = []; for (var latNumber = 0; latNumber <= latitudeBands; latNumber++) { var theta = latNumber * Math.PI / latitudeBands; var sinTheta = Math.sin(theta); var cosTheta = Math.cos(theta); for (var longNumber = 0; longNumber <= longitudeBands; longNumber++) { var phi = longNumber * 2 * Math.PI / longitudeBands; var sinPhi = Math.sin(phi); var cosPhi = Math.cos(phi); var x = aRadius * cosPhi * sinTheta; var y = bRadius * cosTheta; var z = cRadius * sinPhi * sinTheta; vertexPositionData.push(radius * x); vertexPositionData.push(radius * y); vertexPositionData.push(radius * z); } }
创建透镜效果
透镜效果最主要的就是要实现一个倒影图像,我们使用纹理贴图(Texture)来实现。
贴图到球面的投影,参考地图圆柱体投影方法(原理见下图)来实现:
不同的是,只需要投影正面区域,因为我们一般只观察正前方区域,我们可以制作一个半面的贴图。
我们可以直接把贴图旋转180°,或者通过投影时给y坐标一个负数值来实现倒影效果。
//纹理图片加载完成时执行的回调函数 function handleLoadedTexture(texture) { gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);//把几何坐标转换成屏幕坐标,即Y轴翻转 gl.bindTexture(gl.TEXTURE_2D, texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.image);//使用TEXTURE0来绑定贴图 //设置纹理缩放过滤器参数 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST); gl.generateMipmap(gl.TEXTURE_2D); gl.bindTexture(gl.TEXTURE_2D, null); } var dropTexture; function initTexture() { dropTexture = gl.createTexture(); dropTexture.image = new Image(); dropTexture.image.onload = function() { handleLoadedTexture(dropTexture) } dropTexture.image.src = "//wow.techbrood.com/assets/love_half_r.png"; } //按照外切圆柱投影法生成顶点纹理贴图的坐标 for (var latNumber = 0; latNumber <= latitudeBands; latNumber++) { aRadius -= latNumber * radius / 1000; var theta = latNumber * Math.PI / latitudeBands; var sinTheta = Math.sin(theta); var cosTheta = Math.cos(theta); for (var longNumber = 0; longNumber <= longitudeBands; longNumber++) { var u = 1 - (longNumber / longitudeBands); var v = 1 - (latNumber / latitudeBands); textureCoordData.push(u); textureCoordData.push(v); } }
我们通过上面的方法处理球面贴图时,会同时生成相应的桶形变形效果。
随机雨滴的生成和动画
接下来我们给雨滴的形状、大小、位置以及速度添加随机分量,来模拟下雨的动画。
function drawScene() { //...... for (var i = 0; i < SPHERE_NUM; i++) { g_mMatrix[i][13] -= 0.1 + i * Math.random() * 0.002;//随机速度 if (g_mMatrix[i][13] < -6.5) {//随机位置 g_mMatrix[i][12] = Math.random() * 30.0 - 15.0; g_mMatrix[i][13] = 6.5; g_mMatrix[i][14] = Math.random() * 4.0 - 20.0; }; //使用纹理TEXTURE0 gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, dropTexture); gl.uniform1i(shaderProgram.samplerUniform, 0); var blending = true; if (blending) {//使用混合模式 gl.blendFunc(gl.SRC_ALPHA, gl.ONE); gl.enable(gl.BLEND); gl.disable(gl.DEPTH_TEST); gl.uniform1f(shaderProgram.alphaUniform, 1.0); } else { gl.disable(gl.BLEND); gl.enable(gl.DEPTH_TEST); } } //...... } function initBuffer() { //...... var latitudeBands = 60; var longitudeBands = 60; var radius = 0.5; //创建随机形状和大小的椭球体,保持总体形状为一个蒜头形 var aRadius = radius * (1.2 + Math.random()); var bRadius = radius * (1.3 + Math.random()); var cRadius = radius * 1.0; var vertexPositionData = []; var normalData = []; var textureCoordData = []; for (var latNumber = 0; latNumber <= latitudeBands; latNumber++) { aRadius -= latNumber * radius / 1000; var theta = latNumber * Math.PI / latitudeBands; var sinTheta = Math.sin(theta); var cosTheta = Math.cos(theta); for (var longNumber = 0; longNumber <= longitudeBands; longNumber++) { var phi = longNumber * 2 * Math.PI / longitudeBands; var sinPhi = Math.sin(phi); var cosPhi = Math.cos(phi); var x = aRadius * cosPhi * sinTheta; var y = -(bRadius * cosTheta); var z = cRadius * sinPhi * sinTheta; normalData.push(x); normalData.push(y); normalData.push(z); vertexPositionData.push(radius * x); vertexPositionData.push(radius * y); vertexPositionData.push(radius * z); } } //...... }
最后我们编写动画程序的主流程,动画我们使用本站这篇文章中介绍过的requestAnimationFrame接口:
function main() { window.scrollTo(0, 0); var canvas = document.getElementById("container"); initGL(canvas); initShaders(); initBuffers(); initTexture(); //gl.clearColor(0.0, 0.0, 0.0, 1.0); //clear background gl.enable(gl.DEPTH_TEST); requestAnimationFrame(drawScene()); } window.addEventListener('load', main);
最新评论
- 相关文章
CentOS6 Apache2.2多站点HTTPS配置
可以使用letsencrypt(certbot)免费证书服务。支持多系统、多站点和多目录,支持wildcard(通配符域名),90天生效,可用定时任务自动更新。需要注意一点的是apache2.4以下版本需要在默认的ssl配置中添加如下的指令:NameVirtualHost
A-Frame WebVR(网页虚拟现实)快速开发入门教程
WebVR和WebGL应用程序接口使得我们已经可以在浏览器上创建虚拟现实(VR)体验,但从工程化的角度而言,开发社区还需要更多方便强大的开发库来简化编程,Mozilla的
WebGL、Asm.js和WebAssembly概念简介
随着HTML技术的发展,网页要解决的问题已经远不止是简单的文本信息,而包括了更多的高性能图像处理和3D渲染方面。这正是要引入WebGL、Asm.js和WebAssembly这些技...
三维向量的简单运算和实用意义
在WebGL的实际应用中我们广泛使用向量的几何运算来计算角度、距离,判断点线、点面之间的关系,比如物体之间的碰撞检测。本文简要介绍三维计算机图形学中常用的...
深度贴图(depth map)概念简介和生成流程
Depth map 深度图是一张2D图片,每个像素都记录了从视点(viewpoint)到遮挡物表面(遮挡物就是阴影生成物体)的距离,这些像素对应的顶点对于观察者而言是“可...
Blender2.7 快捷键一览表
通用操作
停止当前操作:ESC
快捷搜索:SPACE撤销:ctrl+z重做:ctrl+shift+z渲染:F12
单选:鼠标右键(RMB)全选:A
框选:B
刷选:...Three.js入门教程6 - 创建全景图和纹理
全景图非常酷。使用Three.js做一个属于自己的全景图并不是那么困难。要做一个全景图,你需要一个软件用来做一张全景图片。我使用了iPhone上的Microsoft Photosyn...
Three.js入门教程2 - 着色器(下)
这是WebGL着色器教程的后半部分,如果你没看过前一篇,阅读这一篇教程可能会使你感到困惑,建议你翻阅前面的教程。
S3TC(S3 Texture Compression)纹理压缩格式详解
使用S3TC格式存储的压缩纹理是以4X4的纹理单元块(texel blocks)为基本单位存储的,每纹理单元块(texel blocks)有64bit或者128bit的纹理单元数据(texel data)。这...
WebGL入门教程3 - Canvas、Context、API和绘制一个矩形
使用top/left/margin和CSS3 translate两种方法实现标题居中的性能差异详解
要实现标题全屏居中(同时在垂直和水平方向居中),有若干种方法,包括使用弹性布局、表格单元、绝对定位、自动外边距和CSS3平移变换等。你可能已经使用了这些方...
HTML网页布局:静态、自适应、流式、响应式
静态布局(Static Layout)即传统Web设计,对于PC设计一个Layout,在屏幕宽高有调整时,使用横向和竖向的滚动条来查阅被遮掩部分;对于移动设备,单独设计一个布...
Three.js 开发基础知识 - 绘制3D对象
Three.js是一个用来简化WebGL开发的JavaScript库,比如绘制一个三维立方体,使用WebGL需要100多行,那Three.js只要10几行就能够完成。本文通过创建一个立方体来...
更多...