当前的一个项目任务是实现动态粒子拓扑连线3D效果图,采用angular2基于typescript实现,这方面的资料比较少,所以需要借鉴javascript的资源,调研了很多实现方案,最终选择了canvas来实现,查到的资料系统的汇总一下。
坐标与颜色
- 坐标
- 2D坐标系:
- 左上角为原点
- x: width,也就是从左往右
- y: height,从上往下
- 当然也有负的x/y坐标,默认是hidden的,但还是存在的,就是原点向向上向左。
- 3D坐标系:
+
- 2D坐标系:
- 颜色
- RGB模式:
- 红/绿/蓝(255,255,255)
- 一个255对应FF,十六进制表示就是:#FFFFFF
- RGB模式:
2D转3D原理
- 人眼看3D原理:
- 眼睛对焦距离:
- 人眼对焦和相机的光学变焦原理是一样的,只是人眼对焦非常精准迅速。观察一个物体时,根据眼睛的对焦距离就可以判断出物体距离眼睛的距离。
- 视差:
- 3D中最重要的概念:视差。
- 两只眼睛同时观看物体的差别:两只眼睛看到同一个立体实体的画面是不一样的。
- 在移动中观测物体的差别:同上。
- 没有视差就没有3D,人之所以能感受到立体,就是因为两只眼睛左右错开6cm多,所以两只眼睛看到的东西会有一点差异,这就是视差,只要能模拟出这种差异,人眼就能感受到立体感。
- 3D中最重要的概念:视差。
- 物体的几何形变:
- 一辆车在往前开,窗户外马路边的树飞速倒退,远一点的房子退的慢一点,更远的山几乎没有动。
- 光影、遮罩而产生的层次感:
- 遮挡:物体a把物体b挡住了一部分,我们会认为物体a在物体b前面,也就是a离我们的眼睛更近。
- 光影:比如同一个路灯下,ab两个物体,其中一个影子几乎很短,另外一个影子很长(这个长短是相对于ab物体自身而言,不是绝对长度),则可以认为a物体离路灯很近,而b物体离得远。
- 大脑的想象:
- 终极武器,这是人最难以用机器模拟的特点。人最大的特点就是拥有丰富的想象力,而且这个想象力是无处不在的。比如你看到一只猫卧在地上,你会更具生活经验想象出它应该有四条腿缩在身子下面;你看到一个人的左半边,你会不由自主的在脑海中把它的右半边被挡住的耳朵胳膊等补上去。
- 想象力大部分时候是准的,但是偶尔也会出错⋯⋯最典型的应用就是魔术,魔术不是让观众看到了假的,二是诱导力观众的想象力,使观众把看不到的或者看不清的或者没注意看的东西在大脑中靠想象力补出来。
- 眼睛对焦距离:
3D电影原理
- 2D电影:
- 2d电影里面因为物体的运动和摄像镜头的运动,使得它比静态的画面更能使人感受到”3d“的效果。
- 比如2d电影里面的一辆车在往前开,窗户外马路边的树飞速倒退,远一点的房子退的慢一点,更远的山几乎没有动,这个强烈的视觉效果可以促使大脑构想一个3d的场景:车在马路上飞驰,马路两旁有很多树,马路不远处有很多房子,更远处有绵延的群山。
- 3D电影:
- 3d电影之所以区别于2d电影,是因为它是唯一满足条件2的,由于左右眼看到了两个角度的不同画面,因此有”真实“的3d感。
- 制作原理-偏光原理:
- 3D立体电影的制作有多种形式,其中较为广泛采用的是偏光眼镜法。
- 制作:它以人眼观察景物的方法,利用两台并列安置的电影摄影机,分别代表人的左、右眼,同步拍摄出两条略带水平视差的电影画面。
- 放映:将两条电影影片分别装入左、右电影放映机,并在放映镜头前分别装置两个偏振轴互成90度的偏振镜。两台放映机需同步运转,同时将画面投放在金属银幕上,形成左像右像双影。
- 观看:当观众戴上特制的偏光眼镜时,由于左、右两片偏光镜的偏振轴互相垂直,并与放映镜头前的偏振轴相一致;致使观众的左眼只能看到左像、右眼只能看到右像,通过双眼汇聚功能将左、右像叠和在视网膜上,由大脑神经产生三维立体的视觉效果。展现出一幅幅连贯的立体画面,使观众感到景物扑面而来、或进入银幕深凹处,能产生强烈的“身临其境”感。
- 2D转3D电影:
- 这是一种伪3D电影:通过把2D电影的前景、中景、背景剪下来,重新贴到一个立体的动态高浮雕上,然后分别用表示左右眼的虚拟摄影机重新拍摄一次,在放映就是3D了。
- 逐帧绘制景深:也就是产生了人眼看3D效果需要的视差。
- 例子:《泰坦尼克号》目前2D拍摄转3D效果最好的一部电影。
- 其原理也是一样的,人工扣图,把后面被遮挡额物体全部三维重建。
- 区分真正的3D电影:
- 戴上眼镜看画面,左右眼轮流睁开
- 真3D:
- 看见两个画面不一样,能看到前景后面遮挡的背景
- 2D转3D:
- 看见的画面不一样,但是玻璃杯、头发这些物体的边缘看起来很奇怪,有一种空气烧热的扭动感
- 假3D:
- 看见两个一模一样的画面。
- 2D电影:
2D-3D算法:
动画实现requestAnimationFrame用法
- 背景:由于我要实现的功能,需要重复调用我的paint()函数,导致paint()函数内部的requestAnimationFrame对同样的callback执行多次,也就导致我的动画效果多次重叠渲染了,所以对requestAnimationFrame的使用研究了一番。
- 浏览器渲染原理:
- DNS 查询
- TCP 连接
- HTTP 请求即响应
- 服务器响应
- 客户端渲染:
- 处理 HTML 标记并构建 DOM 树。
- 处理 CSS 标记并构建 CSSOM 树。
- 将 DOM 与 CSSOM 合并成一个渲染树。
- 根据渲染树来布局,以计算每个节点的几何信息。
- 将各个节点绘制到屏幕上。
- 绘制频率:
- 页面上每一帧变化都是系统绘制出来的(GPU或者CPU)
- 但这种绘制又和PC游戏的绘制不同,它的最高绘制频率受限于显示器的刷新频率(而非显卡)
- 所以大多数情况下最高的绘制频率只能是每秒60帧(frame per second,以下用fps简称),对应于显示器的60Hz。
- 60fps是一个最理想的状态,在日常对页面性能的测试中,60fps也是一个重要的指标,the closer the better。
- 刷新频率:
- 图像在屏幕上更新的速度,也即屏幕上的图像每秒钟出现的次数,它的单位是赫兹(Hz)。
- 刷新频率越高,屏幕上图像闪烁感就越小,稳定性也就越高,换言之对视力的保护也越好。
- 一般人的眼睛、不容易察觉75Hz以上刷新频率带来的闪烁感,因此最好能将您显示卡刷新频率调到75Hz以上。
- 显示卡:并不是所有的显示卡都能够在最大分辨率下达到70Hz以上的刷新频率(这个性能取决于显示卡上RAMDAC的速度),而且显示器也可能因为带宽不够而不能达到要求。影响刷新率最主要的还是显示器的带宽。
- 显示器带宽:是显示器视频放大器通频带宽度的简称,指电子枪每秒钟在屏幕上扫过的最大总像素数,以MHz(兆赫兹)为单位。 带宽的值越大,显示器性能越好。
- 硬件加速:
- 硬件有三个处理器,CPU、GPU和APU(不是加速处理器是声音处理器)。他们通过PCI/AGP/PCIE总线交换数据。今天,GPU已经不再局限于3D图形处理了,GPU通用计算技术发展已经引起业界不少的关注,事实也证明在浮点运算、并行计算等部分计算方面,GPU可以提供数十倍乃至于上百倍于CPU的性能。
- 60Hz和60fps:
- 没有任何关系。fps代表GPU渲染画面的频率,Hz代表显示器刷新屏幕的频率。
- 一幅静态图片,你可以说这副图片的fps是0帧/秒,但绝对不能说此时屏幕的刷新率是0Hz,也就是说刷新率不随图像内容的变化而变化。
- 游戏也好浏览器也好,我们谈到掉帧,是指GPU渲染画面频率降低。比如跌落到30fps甚至20fps,但因为视觉暂留原理,我们看到的画面仍然是运动和连贯的。
- 浏览器中动画有两种实现形式:
- JavaScript:通过定时器(setTimeout 和 setIterval)来间隔来改变元素样式,或者使用requestAnimationFrame;
- setTimeout 和 setIterval:
- 优点:
- 易用,低效,兼容好
- 缺点:
- setInterval多个间隔可能会被跳过
- setInterval多个间隔可能比预期小
- 不够流畅,且会占用额外的资源
- 这种做法的主要问题是回调将会在帧中的某个时间点运行,这可能会刚好在末尾(会丢失帧导致发生卡顿)
- 优点:
- RAF:
- 优点:
- 在每次浏览器更新页面时,能获取通知并执行应用。 简单理解为,RAF能在每个16.7ms间执行一次咱们的函数,不多不少。
- 最小化的消耗资源,RAF在页面被切换或浏览器最小化时,会暂停执行,等页面再次关注时,继续执行动画。
- 不需要使用者指定循环间隔时间,浏览器会基于当前页面是否可见、CPU的负荷情况等来自行决定最佳的帧速率,从而更合理地使用CPU
- 相比 CSS 动画有更好的掌控,能合理降低CPU的使用。
- 缺点:
- 无法控制执行时间,执行时间由系统根据屏幕刷新时间决定
- 浏览器兼容性问题,IE10+及现代浏览器,低版本浏览器建议降级处理,使用setInterval或setTimeout
- 优点:
- setTimeout 和 setIterval:
- CSS3:transition 和 animation;
- HTML5:使用HTML5提供的绘图方式(canvas、svg、WebGL)
- canvas:
- 优点:
1) 画2D图形时,页面渲染性能比较高
2) 页面渲染性能受图形复杂度影响小
3) 渲染性能只受图形的分辨率的影响
4) 画出来的图形可以直接保存为 .png 或者 .jpg的图形
5) 最适合于画光栅图像(如游戏和不规则几何图形等),编辑图片还有其他基于像素的图形操作。 - 缺点:
1) 整个就是一张图,无论你往上画什么东西——没有DOM 结点可供操作
2) 没有实现动画的API,你必须依靠定时器和其他事件来更新Canvas
3) 对文本的渲染支持是比较差
4) 对要求有高可访问性(盲文、声音朗读等)页面,比较困难
5) 对交互要求高的(比如TIBCO的很多产品)的界面,不建议使用Canvas
- 优点:
- canvas:
- JavaScript:通过定时器(setTimeout 和 setIterval)来间隔来改变元素样式,或者使用requestAnimationFrame;
requestAnimationFrame:
- requestAnimationFrame是浏览器用于定时循环操作的一个接口,类似于setTimeout,主要用途是按帧对网页进行重绘。
- 设置这个API的目的是为了让各种网页动画效果(DOM动画、Canvas动画、SVG动画、WebGL动画)能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果。代码中使用这个API,就是告诉浏览器希望执行一个动画,让浏览器在下一个动画帧安排一次网页重绘。
API:
提供了两个接口:
1
2
3
4partial interface Window {
long requestAnimationFrame(FrameRequestCallback callback);
void cancelAnimationFrame(long handle);
};requestAnimationFrame
- requestAnimationFrame方法用于通知浏览器重采样动画
- 当requestAnimationFrame(callback)被调用时不会执行callback函数,而是会将元组< handle,callback>插入到动画帧请求回调函数列表末尾(其中元组的callback就是传入requestAnimationFrame的回调函数),并且返回handle值,该值为浏览器定义的、大于0的整数,唯一标识了该回调函数在列表中位置。
- 每个回调函数都有一个布尔标识cancelled,该标识初始值为false,并且对外不可见。浏览器在执行“采样所有动画”的任务时会遍历动画帧请求回调函数列表,判断每个元组的callback的cancelled,如果为false,则执行callback。
- cancelAnimationFrame
- cancelAnimationFrame 方法用于取消先前安排的一个动画帧更新的请求
- 当调用cancelAnimationFrame(handle)时,浏览器会设置该handle指向的回调函数的cancelled为true。无论该回调函数是否在动画帧请求回调函数列表中,它的cancelled都会被设置为true。
- 如果该handle没有指向任何回调函数,则调用cancelAnimationFrame 不会发生任何事情。
使用方式:
只能执行一次函数a:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 方式一:callback内部执行cancelAnimationFrame
function a() {
console.log('hh');
id = window.requestAnimationFrame(a);
window.cancelAnimationFrame(id);
}
a();
// 方式二:callback外部执行cancelAnimationFrame
function a() {
console.log('hh');
id = window.requestAnimationFrame(a);
}
a();
window.cancelAnimationFrame(id);- 因为window.requestAnimationFrame(a)不会执行a函数,只是将<id, a>插入到动画帧请求回调函数列表的末尾,window.cancelAnimationFrame(id)则把id指向的回掉函数a的cancelled置位true;当浏览器执行“采样所有动画的”的任务时,会遍历动画帧请求回调函数列表,先判断元祖的callback的cancelled,为true,所以后续不再不执行a函数。
循环执行多次函数a:
1
2
3
4
5function a() {
console.log('hh');
window.requestAnimationFrame(a);
}
a();
* 循环多次函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
flag = false;
function draw() {
function init() {
;
}
function a() {
console.log('hh');
idRAF = window.requestAnimationFrame(a);
}
if (flag == true) {
window.cancelAnimationFrame(idRAF);
}
}
draw(); // 正常循环
flag = true; // 需要修改init函数
draw(); //
flag = false; // 重新循环
- 持续保持一次循环,不会重复渲染多次:
+ 操作:循环多次draw函数,每次执行前,先修改flag,相当于仅执行内部init()函数,不会执行动画渲染的a()函数,处理完逻辑后恢复flag,保持循环。
+ 原理:第一次draw()后,已经有了一个不断循环进行动画帧请求的任务了,一直保持;下一次再次执行draw()的时候,会先执行我们想要的init()函数,然后进入a()的时候,由于idRAF已经被cancel了,所以此次draw()的渲染函数a()不会执行;