前言
学生阶段学习前端开发,工作后主要从事 H5 游戏开发,逐渐对图形学这个领域感兴趣,因此结合目前的技能,对 web 图形学这块做一些笔记和积累,也分享出来供大家参考学习。本文作为入门的第一篇文章,主要是对 Web 开发中用到的相关的图形渲染做一个介绍。
浏览器中渲染图形
在 Web 上,图形通常是通过浏览器绘制的。现代浏览器是一个复杂的系统,其中负责绘制图形的部分是渲染引擎。渲染引擎绘制图形的方式,主要为以下4 种方式。
HTML + CSS
Web 开发主要以 HTML 来描述结构,以 CSS 来描述表现,以 JavaScript 来描述行为。
图形渲染上 HTML 与 CSS 使用相对较少,而且使用方式也不太一样。于是,有些人认为,图形渲染只能使用 SVG、Canvas 这些方式,不能使用 HTML 与 CSS。
这个想法不对,其实现代浏览器的 HTML、CSS 表现能力很强大,完全可以实现常规的图表展现,比如,我们常见的柱状图、饼图和折线图,当然也可以去做游戏。
使用 HTML + CSS 绘制的圆形饼状图
笔者学生时代使用 HTML + CSS 做的 2048 小游戏
使用 HTML + CSS 渲染图形会有以下的一些优点:
-
以dom的形式表示,事件绑定由浏览器直接分发到对应节点上
-
理解 CSS 的绘图思想对图形开发有帮助,例如,CSS 的很多理论就和视觉相关的。
-
一些简单的图标,用CSS来实现很有好处,既能简化开发,有不需要引入额外的库,节省资源,提高网页打开速度 。
当然 HTML + CSS 不作为主要的渲染图形技术,主要是有以下缺点:
-
绘制方式并不简洁,从CSS代码里,很难看出数据与图形的对应关系,很多换算需要开发来做。
-
当图形发生变化时,我们很可能要重新执行全部的工作,这样的性能开销非常巨大。
Web 开发着重于处理普通的文本和多媒体信息,渲染普通的、易于阅读的文本和多媒体内容,而图形开发则着重于处理结构化数据,渲染各种相对复杂的图表、图形元素、动画等。
SVG
SVG(Scalable Vector Graphics,可缩放矢量图),SVG 是一种基于 XML 语法的图像格式,可以用图片(img 元素)的 src 属性加载。
W3C上是这样描述的:SVG是一种基于XML语法的图像格式,通过XML文档描述绘图。什么意思?通俗点讲就是SVG是通过标签元素DOM来绘制图形,并且像操作普通的 HTML 元素一样,利用 DOM API 操作 SVG 元素。甚至,CSS 也可以作用于内嵌的 SVG 元素。
相比Canvas,SVG是位老前辈了,2003年这位前辈就已经是W3C 标准,但是由于它长相复杂而且有点不求上进,十几年来发展非常缓慢。
使用 SVG 绘制圆形
网上找到的 svg 动画,没想到 svg 也能做出这么酷炫的动画
SVG 绘制图表与 HTML 和 CSS 绘制的方式差别不大,只不过是将 HTML 标签替换成 SVG 标签,运用了一些 SVG 支持的特殊属性,SVG 就是 HTML 的增强版。
SVG是一种无损格式。这意味着它在压缩时不会丢失任何数据,呈现不同的颜色。最常用于网络上的图形和LOGO以及将在视网膜或其他高分辨率屏幕上查看的项目。
因此 SVG 有以下一些优点:
-
支持事件处理器。
-
不依赖分辨率,矢量格式可呈现任何大小而不降低其质量。
-
SVG是一种无损格式。这意味着它在压缩时不会丢失任何数据,呈现不同的颜色。最常用于网络上的图形和LOGO以及将在视网膜或其他高分辨率屏幕上查看的项目。
同样 SVG 跟 HTML 有类似的缺点:
-
SVG 元素与 HTML 元素一样,在输出图形前都需要经过解析、布局计算和渲染树生成。
-
复杂度高会减慢渲染速度(任何过度使用 DOM 的应用都不快)。
大量的 SVG 元素不仅会占用很多内存空间,还会增加引擎、布局计算和渲染树生成的开销,降低性能,减慢渲染速度。这也就注定了 SVG 只适合应用于元素较少的简单可视化场景。
SVG功能强大并且很灵活,并且有一些效果还是简单实现的(比如贝赛尔曲线上紧贴文字的效果)。但是每个SVG都是一个DOM元素,数量达到一定限度之后不可避免的会有明显卡顿,因为交互会使元素发生改变(位置移动、样式改变、增删改查等等)从而触发reflow,reflow会触发repaint,一个节点的reflow很可能导致子节点甚至是父节点或者同级节点的reflow,而reflow的性能成本很高。
SVG在处理千级的数量时就已经显得很吃力了。
Canvas2D
浏览器提供的 Canvas API 中的其中一种上下文,使用它可以非常方便地绘制出基础的2d几何图形。
Canvas 是HTML5提供的一种新标签, ie9才开始支持的,Canvas是一个矩形区域的画布,可以用JS控制每一个像素在上面绘画。Canvas 标签使用 JavaScript 在网页上绘制图像,本身不具备绘图功能,分别提供 Canvas 2D 和 WebGL 两种上下文来绘制图形。
有哪些值得推荐的开源 JavaScript Canvas 库,分别有哪些优势和劣势?
WebGL
浏览器提供的 Canvas API 中的另一种上下文,它是 OpenGL ES(OpenGL是最常用的跨平台图形库) 规范在 Web 端的实现,API 相对更底层。
WebGL可以为 HTML5 Canvas 提供硬件3D加速渲染,利用了 GPU 并行处理的特性,这让它在处理大量数据展现的时候,性能大大优于前 3 种绘图方式。
一般情况下,Canvas2D 绘制图形的性能已经足够高了,但是在三种情况下我们有必要直接操作更强大的 GPU 来实现绘图
-
绘制的图形数量非常多,比如有多达数万个几何图形需要绘制,而且它们的位置和方向都在不停地变化
-
对较大图像的细节做像素处理,比如,实现物体的光影、流体效果和一些复杂的像素滤镜
-
绘制 3D 物体。因为 WebGL 内置了对 3D 物体的投影、深度检测等特性,所以用它来渲染 3D 物体就不需要我们自己对坐标做底层的处理了。
如果要绘制的图形太多,或者处理大量的像素计算时,Canvas2D 依然会遇到性能瓶颈。
Canvas2D 和 WebGL 都是 Canvas 标签提供的上下文,所以他们存相同的优缺点的:
-
页面渲染性能受图形复杂度影响小。
-
Canvas提供的功能更原始,适合像素处理,动态渲染和大数据量绘制。
-
Canvas就没有reflow的概念了,只有repaint的概念,因此在性能上要比SVG 和 HTML/CSS 好很多。
相对以上两种渲染,有以下的缺点:
-
弱的文本渲染能力。
-
依赖分辨率,无法高效保真,画布较大时候性能较低。
-
没有实现动画的API,你必须依靠定时器和其他事件来更新Canvas
-
不支持事件处理器,事件分发由 canvas 处理,绘制的内容的事件需要自己做处理。
指令式编程 VS 声明式编程
对于以上4中绘图渲染方式的使用,概括地说,我们可以有两种编写代码的方式:声明式编程(Declarative)和指令式编程(Imperative)。
还有一种函数式编程(Functional)不在这次分享范围内,有兴趣的可自行了解。【译】web 开发中的声明式与命令式编程
其实不难理解,在前端开发的过程中,在使用 html、css、js 就能体会到不同编程范式的差异。
| 编程范式 | 定义 | 例子 | 绘图方式 |
|---|---|---|---|
| 声明式编程(Declarative) | 告诉机器您想得到什么,让机器自己计算该如何做 | SQL、声明式UI | HTML + CSS 、SVG |
| 指令式编程(Imperative) | 告诉机器该如何做,并得到自己想要的结果 | 常规的编程、C++ | Canvas2D、WebGL |
使用 WEB 图形系统绘图
经过上面编程方式范式的了解,我们还是实际来体验一下不同编程范式和图形系统绘图。
使用上述 4 种绘图方式绘制一个三角形
HTML + CSS
<div class="box">
<div id="htmlDemo"></div>
</div>
#htmlDemo {
width: 0;
height: 0;
border-bottom: 200px solid yellow;
border-top: 0px solid transparent;
border-left: 100px solid transparent;
border-right: 100px solid transparent;
}
SVG
<div class="box">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<!-- 多边形 -->
<polygon points="0,200 100,0 200,200" style="fill: blue" />
</svg>
</div>
Canvas2D
<div class="box">
<canvas id="canvas" width="200" height="200"></canvas>
</div>
// 第一步: 获取 canvas 上下文
const canvas = document.querySelector('#canvas') as HTMLCanvasElement;
const context = canvas.getContext('2d');
// 第二步:用 Canvas 上下文绘制图形。
context.save();
context.fillStyle = 'green';
context.beginPath();
context.moveTo(canvas.width / 2, 0);
context.lineTo(0, canvas.height);
context.lineTo(canvas.width, canvas.height);
context.fill();
context.restore();
WebGL
// 第一步:创建 webgl 上下文
const canvas = document.querySelector('#webgl') as HTMLCanvasElement;
const gl = canvas.getContext('webgl');
//第二部:创建 webgl 程序
// 顶点着色器
const vertex = `
attribute vec2 position;
void main() {
gl_PointSize = 1.0;
gl_Position = vec4(position, 1.0, 1.0);
}`;
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertex);
gl.compileShader(vertexShader);
// 片元着色器
const fragment = `
precision mediump float;
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
} `;
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragment);
gl.compileShader(fragmentShader);
// 创建 webgl 程序
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
// 第三步:将数据存入缓冲区
const points = new Float32Array([-1, -1, 0, 1, 1, -1]);
const bufferId = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, bufferId);
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);
// 第四步:将缓冲区的数据读到GPU
const vPosition = gl.getAttribLocation(program, 'position'); // 获取顶点着色器中的position变量的地址;
gl.vertexAttribPointer(vPosition, 2, gl.FLOAT, false, 0, 0); // 给变量设置长度和类型;
gl.enableVertexAttribArray(vPosition); // 激活这个变量;
// 步骤五:执行着色器程序完成绘制
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, points.length / 2);
可以发现实现同一个三角形,HTML + CSS 和 SVG 实现起来,只需要几行代码即可,而 Canvas2D 和 WebGL,特别是 WebGL,需要调用对应的指令来告诉计算机来如何渲染一个三角形。
总结
-
HTML+CSS 的优点是方便,不需要第三方依赖,甚至不需要 JavaScript 代码。如果我们要绘制少量常见的图表,可以直接采用 HTML 和 CSS。它的缺点是 CSS 属性不能直观体现数据,绘制起来也相对麻烦,图形复杂会导致 HTML 元素多,而消耗性能。
-
SVG 是对 HTML/CSS 的增强,弥补了 HTML 绘制不规则图形的能力。它通过属性设置图形,可以直观地体现数据,使用起来非常方便。但是 SVG 也有和 HTML/CSS 同样的问题,图形复杂时需要的 SVG 元素太多,也非常消耗性能。
-
Canvas2D 是浏览器提供的简便快捷的指令式图形系统,它通过一些简单的指令就能快速绘制出复杂的图形。由于它直接操作绘图上下文,因此没有 HTML/CSS 和 SVG 绘图因为元素多导致消耗性能的问题,性能要比前两者快得多。但是如果要绘制的图形太多,或者处理大量的像素计算时,Canvas2D 依然会遇到性能瓶颈。
-
WebGL 是浏览器提供的功能强大的绘图系统,它使用比较复杂,但是功能强大,能够充分利用 GPU 并行计算的能力,来快速、精准地操作图像的像素,在同一时间完成数十万或数百万次计算。另外,它还内置了对 3D 物体的投影、深度检测等处理,这让它更适合绘制 3D 场景。