计算机图形学(Computer Graphics)主要是研究物体在计算机中的图形显示与交互,各个领域都能利用计算机图形显示获取相关的好处,作为一个前端工程师,了解相关图形学的基础有利于往更高的台阶前行。本文章借鉴了《现代计算机图形学基础》一书中的部分概念,希望整理出对前端工程师更为贴近的内容
计算机图形在前端中最常见的应用就是数据统计了吧,echart,G2等等插件提供了多样的数据图,这些插件通过将大量的数据转化成可视形式,其数据趋势与线性关系得以更容易的展示出来,使得数据在前端的显示更为直观与优雅
另外,新兴起的VR(virtual-reality)技术,也在不少的前端页面中出现,VR交互中,用户可与三维场景中的对象进行交互。此类展现方式可以让用户更好的与设计者沟通,使用户体验得到增强

CG是以建模,绘制,交互,动画,影像为核心内容并服务于各个领域的一门科学。
对于前端工程师来说,建模就是指使用前端库,通过数据来表达物体和场景并构建对应的数据化三维模型的过程。
绘制则是指将这些三维模型通过一些计算和变换,最终在屏幕上渲染出来的过程,也是从几何模型到像素的转化。
所以,工程师使用计算机语言输入的是三维模型,而通过计算机模拟人类的视觉功能,对三维模型进行加工处理,最终在屏幕上输出的是二维图像。虽然输出的是二维图像,但是通过计算机的处理,图像中的物体之间的关系符合人类对现实世界的认知,从而使大脑形成三维空间的感知。
图形是由点,线,面等几何元素组成而成的,这些元素统称为图元。简单的图元可以组成一些立体图元,从而又组合成简单的几何模型,复杂的模型又通过这些简单的模型组合而成,通过模型变换整合坐标系最终形成复杂场景。在threejs中的场景图就是这样一层一层的构建的。
图形只得是由数据组成的几何信息,而图像指的是由像素组成的二维栅格,所以图形不会失真,图像会失真
正如上述,创建的三维模型最终要在显示器中显示,而显示器是基于像素的二维图像显示,所以需要将图形经过一系列的几何变换映射成屏幕坐标中的二维图像,确定图元对应的图像坐标位置,其中涉及各种物理坐标的转换,最后还要经过光栅化处理(确定指定分辨率下哪些像素被覆盖,以何种颜色进行色彩显示)。最后才显示在显示器中。
这一系列的过程也称做GPU的图形流水线(也称渲染流水线或渲染管线),实际的应用中,图形流水线是十分复杂的,以OpenGL为例,按照流水线操作的对象大体可分为两类操作:
| 处理图像元素的像素操作 | 处理图元元素的几何操作 |
|---|---|
| 区域填充 | 模型变换 |
| 片元组装 | 顶点着色 |
| 纹理映射 | 纹理变换 |
| 颜色合成 | 视点变换 |
| 深度测试 | 视域剔除 |
| 遮罩测试 | 投影变换 |
| 透明融合 | 背面剔除 |
| 雾化处理 | 窗口变换 |
几何变换包括了:
几种变换的原理都是矩阵变换,几何操作中使用矩阵变换与canvas的矩阵变换类似,因为使用齐次坐标系,所以使用四阶矩阵表示线性关系
1const point = vec(x, y, z, 1)光栅化处理包括了:

几何模型是从数学形状的角度描述物体的外观。通常建模使用多边形网格表示。
由顶点、边和多边形面组成的集合来表示的几何形状称为网格(Mesh)。其中由三角形面片组成的三角形网格更加的稳定与灵活,成为前端建模的首选。
在WebGL中,与大多数实时3D图形一样,三角形是绘制模型的基本元素。因此,在WebGL中绘制的过程涉及使用JavaScript生成信息,该信息指定了这些三角形的创建位置和方式,以及它们的外观(颜色,阴影,纹理等)。然后,此信息将馈送到 GPU 进行处理,并返回场景视图。
通过对网格的编辑,操纵和修改基础几何模型,可以实现对复杂模型的快速建模
显卡的功能是将计算机需要显示的数据信息转换为显示器信息,并驱动其显示的计算机硬件。在GPU出现之前,图形流水线的操作大多由CPU来完成,显卡上只能处理一些简单的算法。而随着图形显示功能的需求发展,CPU的串联工作模式已经不能满足数以亿计的像素计算,(一台1920*1080的显示器,一帧就有约200万的像素需要计算,60Hz的刷新率,一秒就有上亿的计算量)。所以以并联工作方式为基础的GPU得以迅速发展,GPU是由数以千计的更小更高效的核心组成的大规模并行计算构建,专门为同时处理多重任务而设计的。
支持可编程GPU(Graphic Processing Unit)图形处理器的出现,取代了部分原本由CPU执行的工作(主要是几何变换和光栅化),GPU更适合处理大量并行而相单一的逻辑,这样就减轻了CPU的负担,并提升了绘制效率。
GPU的并行处理包括基于任务的并行处理与基于数据的并行处理
图形流水线也称渲染流水线或渲染管线,GPU的并行处理机制使得渲染流水线更加高效的进行。由固定函数进行处理流水线上功能的也称固定管线
随着开发者需求的增长,上述固定渲染流水线已经满足不了开发者的需求,所以一些用来代替固定渲染管线的可高度编程程序算法出现了,这就是着色器。通过着色器,开发者可以自己编写显卡渲染的相关算法,可以实现各种各样的图像效果而不用受显卡的固定渲染管线限制。

创建顶点数组。这些数组包含顶点属性,以及有关顶点的纹理、颜色或光照(垂直顶点)如何影响顶点的信息。
然后,通过将顶点数组中的数据发送到顶点缓冲区,以便GPU快速读取。我们还提供指向顶点数组元素的附加索引数组。它们控制顶点稍后如何组合成三角形。
顶点缓冲区在显存中开辟一块区域,这样绘制时直接读取显存中的数据就可以了,明显提高渲染速度。
GPU 首先从顶点缓冲区中读取每个选定的顶点,并通过顶点着色器运行它。顶点着色器将一组顶点属性作为输入并输出一组新的属性。
然后,GPU 连接投影的顶点以形成三角形。它通过按 indexs 数组指定的顺序获取顶点。
光栅器获取每个三角形,对其进行裁剪,丢弃屏幕外部的部分,并将剩余的可见部分分解为像素大小的碎片。顶点着色器其他顶点属性的输出也会在每个三角形的栅格化表面上进行插值,从而为每个片元分配一个平滑的值渐变。
然后,生成的像素大小的片元通过片元着色器。片元着色器输出每个像素的颜色和深度值,然后将其绘制到帧缓冲区中。
帧缓冲器是渲染作业输出的最终目标。帧缓存包括颜色、scissor、alpha、stencil、depth这些缓存,所以帧缓存不是一片缓存,而是所有这些缓存的组合,帧缓冲器还可以具有深度缓冲区和/或模具缓冲区,这两者都可以选择在片元绘制到帧缓冲器之前对其进行过滤。
深度测试会丢弃位于已绘制对象后面的对象中的片元,而模具测试使用绘制到模具缓冲区中的形状来约束帧缓冲器的可绘制部分,从而“模具化”渲染作业。在这两个滤镜中幸存下来的片元的颜色值 alpha 与它们覆盖的颜色值混合在一起。最终的颜色、深度和模具值将绘制到相应的缓冲区中。缓冲区的输出还可以用作其他渲染作业的纹理输入。
专门用来为着色器编程的编程语言就是着色器语言。区别于大多数运行在CPU中的语言,着色器语言运行在GPU中,与OpenGL相配合的着色器语言是GLSL,应用在客户端,而与WebGL配合的着色器语言是GLSL ES,应用在浏览器平台。
WebGL着色器代码分为顶点着色器代码和片元着色器代码两部分,通过WebGL编译处理后,最终在GPU的顶点着色器单元与片元着色器单元上执行。
顶点着色器定义了顶点的渲染位置和点的渲染像素大小,对应几何操作。顶点着色器至少会计算顶点在屏幕空间中的投影位置。但它也可以生成其他属性,例如每个顶点的颜色或纹理坐标。您可以对自己的顶点着色器进行编码,也可以使用 WebGL 库提供的顶点着色器。
片元着色器定义了点的渲染结果像素的颜色值,对应像素操作。常见的片元着色器操作包括纹理贴图和光照。由于片元着色器为绘制的每个像素独立运行,因此它可以执行最复杂的特殊效果;但是,它也是图形管道中对性能最敏感的部分。与顶点着色器一样,您可以编写自己的片元着色器,也可以使用 WebGL 库提供的着色器。如果使用Three.js开发项目,其已经封装好大部分功能,但若是需要自定义着色器或者使用原生WebGL的时候,便需要学习着色器语言。
首先,与OpenGL配合的GLSL使用C语言作为基础高阶着色语言,避免了使用汇编语言或硬件规格语言的复杂性。GLSL ES语言是则是在GLSL着色器语言的基础上,删除和简化一部分功能后形成的,其语法与C语言的较为类似。
GLSL ES大小写敏感,而且表达式后不能省略';'
着色器语言GLSL的基本数据类型和C语言一样具有常见的整型数int、浮点数float和布尔值bool类型数据。
| 关键字 | 数据类型 | 值 |
|---|---|---|
| bool | 布尔值 | 布尔变量值为true或false |
| int | 整型数 | 值为整数,比如0,1,2,3… |
| float | 单精度浮点数 | 浮点数用小数点表示,比如0.6,3.14,2.8 |
浮点数不能省略小数点
基本数据类型可以使用内置函数转换:int()、float()、bool()
着色器语言ES GLSL和C语言一样属于强类型语言,声明一个变量需要定义变量的数据类型
1// 着色器语言定义一个整形常量变量名不能以数字开头
不能以gl_、webgl_或webgl开头,这些已经被OpenGL ES保留了
| 关键字 | 数据类型 |
|---|---|
| vec2 | 二维向量,具有xy两个分量,分量是浮点数 |
| vec3 | 三维向量 ,具有xyz三个分量,分量是浮点数 |
| vec4 | 四维向量 ,具有xyzw四个分量,分量是浮点数 |
1vec3 dirction = vec3(1.0, 0.0, 0.0)ivec 向量的分量是整型数
ivec 向量的分量是布尔值
向量与数字的运算和向量之间的运算都可以化简为向量分量之间的运算
| 关键字 | 数据类型 |
|---|---|
| mat2 | 2×2矩阵,4个元素 |
| mat3 | 3×3矩阵,9个元素 |
| mat4 | 4×4矩阵,16个元素 |
矩阵与数字运算可以化简为矩阵中的元素与数字的运算
矩阵与向量之间的运算和矩阵之间的运算则遵循线性代数的算法
和javascript语言、C语言一样 可以声明数组类型变量,不过WebGL着色器的数据仅仅支持一维数组,不支持多维数组。
浮点数数组使用Float32Array声明
关于if语句、for语句的使用,和javascript语言逻辑规则一致
函数有返回值时,函数计算后需要返回的值通过关键字return返回,注意声明函数时候,函数名称前需要声明return返回值的数据类型。
1int add(int x,int y){自定义函数无返回值的时候,和主函数main一样,使用关键字void声明函数。
自定义声明一个函数,传入一个参数,默认情况下,参数被修改了,但是并不影响传入的参数对应变量的值,如果使用out关键字声明参数变量,函数内部改变参数,函数外参数对应的变量会改变
结构体主要功能就是利用WebGL着色器已经提供的常见数据类型,自定义一个新的数据类型。
1struct newVar {内置变量就是不用声明可以直接赋值,主要是为了实现特定的功能。
| 内置变量 | 含义 | 值数据类型 |
|---|---|---|
| gl_PointSize | 点渲染模式,方形点区域渲染像素大小 | float |
| gl_Position | 顶点位置坐标 | vec4(x,y,z,1.0) |
| gl_FragColor | 片元颜色值 | vec4(r,g,b,a) |
| gl_FragCoord | 片元坐标,单位像素 | vec2 |
| gl_PointCoord | 点渲染模式对应点像素坐标 | vec2 |
| 关键字(变量类型) | 数据传递 | 声明变量 |
|---|---|---|
| attribute | javascript——>顶点着色器 | 声明顶点数据变量 |
| uniform | javascript——>顶点、片元着色器 | 声明非顶点数据变量 |
| varying | 顶点着色器——>片元着色器 | 声明需要插值计算的顶点变量 |
顶点着色器和片元着色器代码都有一个唯一的主函数main(),attribute、varying和uniform类型的变量需要在main函数之外声明,在main函数中通常编写,逐片元或逐顶点处理的代码。
着色器中通过attribute和uniform关键字声明的变量,需要通过javascript代码传递相关的数据。varying类型变量主要是为了完成顶点着色器和片元着色器之间的数据传递和插值计算。
舍弃片元,可用于光栅化中的剔除操作
WebGL是一个JavaScript API,它允许我们直接在浏览器中实现交互式3D图形。WebGL API多数与GPU硬件相关,GPU硬件(渲染管线)=>显卡驱动=>操作系统=>浏览器=>WebGL API为上一层提供接口,开发者调用WebGL API控制图形处理单元
Canvas对象方法.getContext('2d')可以在二维画布上绘制图像。执行canvas.getContext('webgl');返回对象具有一系列绘制渲染三维场景的方法,也就是WebGL API。
几乎整个WebGL API都是关于如何设置着色器的状态值以及运行它们。
对于想要绘制的每一个对象,都需要先设置一系列状态值,然后通过调用 gl.drawArrays 或 gl.drawElements 运行一个着色方法对(一个着色程序),使得你的着色器对能够在GPU上运行。
这边不详述WebGL的各个API,只通过下述例子简单实践管线渲染的流程,从而更好的理解前文内容,更多的请自行查阅官方文档
实际上学习WebGL是为了学习Web3D知识,而通过原生WebGL直接编写程序,会比较麻烦,实际开发中一般会使用Three.js这样或那样的库
