WebGL-基础概念
着色器(Shader)
- 顶点着色器(Vertex shader): 顶点着色器是用来描述定点特性(位置/颜色)的程序, 顶点(vertex)是指二维或者三维空间中的一个点. 比如二维或者三维的端点或者交点
- 片元着色器(Fragment shader): 进行逐片元处理程序. 比如光照处理. 片元(fragment)是一个 webgl 术语, 可以理解为像素.
坐标系统
webgl 采用三维坐标系统(笛卡尔坐标系), x 轴水平(正向为右), y 轴垂直(正向为下), z 轴垂直于屏幕(正向为外), 这套坐标系又称为右手坐标系. 默认情况下 webgl 采用这台坐标系. 但是 webgl 实际的情况要复杂的多, 它既不是右手坐标系也不是左手坐标系. 现在我们还是可以简单的认为 webgl 就是右手坐标系.

此外, 我们还要知道它与 canvas 的坐标的对应关系:

<canvas>的中心点:(0.0, 0.0, 0.0)<canvas>的上边缘和下边缘:(-1.0,0.0,0.0)和(1.0,0.0,0.0);<canvas>的左边缘和右边缘:(0.0,-1.0,0.0)和(0.0,1.0,0.0);
精度限定词(precision qualifier)
highp: 高精度, 顶点着色器的最低精度.mediump: 中精度, 介于高精度和低精度之间, 片元着色器的最低精度.lowp: 低精度, 可以表示所有颜色.
在某些webgl环境中, 片元着色器可能会不支持highp精度, 所以可以在程序顶部设置:
precision <精度限定词> <类型名称>
比如:
precision mediump float; // 所有浮点数默认精度为中精度
缓冲区对象(buffer object)
WebGL 提供了一种很方便的机制, 即缓冲区对象, 它可以一次性的向着色器传入多个顶点的数据. 缓冲区对象时 WebGL 系统中的一块内存区域. 我们可以一次性向缓冲区对象中填充大量的顶点数据, 然后将这些数据保存在其中,供顶点着色器使用.
类型化数组
JS 中的数据是一种通用类型, WebGL 引入了类型化数组, Float32Array 就是其中之一.
WebGL 中的类型化数组如下所示:
| 数组类型 | 每个元素所占字节数 | 描述 |
|---|---|---|
| Int8Array | 1 | 8 位整数型(char) |
| UInt8Array | 1 | 8 位无符号整数(unsigned char) |
| Int16Array | 2 | 16 位整数型(short) |
| UInt16Array | 2 | 16 位无符号整数型(unsigned short) |
| Int32Array | 4 | 32 位整数型(signed int) |
| UInt32Array | 4 | 32 位无符号整数型(unsigned int) |
| Float32Array | 4 | 单精度 32 位浮点数(float) |
| Float64Array | 8 | 双精度 64 位浮点数(double) |
与 JavaScript 中的数组类似, 类型化数组也有一系列的方法和属性, 但是与普通的 Array 数组不同, 类型化数组不支持 push()和 pop()方法.
| 方法属性和常量 | 描述 |
|---|---|
| get(index) | 获取第 index 个元素值 |
| set(index,value) | 设置第 index 个元素的值为 value |
| set(array,offset) | 从第 offset 个元素开始将数组 array 中的值填充进去 |
| length | 数组的长度 |
| BYTES_PER_ELEMENT | 数组中每个元素所占的字节数 |
顶点到片元: 图形装配和光栅化

- 图形装配过程: 将孤立的顶点转配成几何图形, 几何图形的类别由
gl.drawArrays函数的第一个参数决定. - 光栅化过程: 将装配好的几何图形转化为片元.

实际上, gl_position实际上就是几何图形装配(geometric shape assembly) 阶段的输入数据, 几何图形装配过程又被称为图元装配过程(primitive assembly process), 因为被装配出的基本图形(点,线,面)又被称为图元(primitives).
纹理
三维图形中有一项跟重要的技术, 叫做 纹理映射(texture mapping) . 纹理映射实际上就是将一张图像映射到一个几何图形的表面上去. 将一张真实世界的图片贴到一个有两个三角形组成的矩阵上, 这张图片就成为 "纹理图像" 或 "纹理" .
纹理坐标
纹理坐标是纹理图像上的坐标, 通过纹理坐标可以在纹理图像上获取纹素颜色, WebGL 系统中的纹理坐标系统是二维的, 为了将纹理坐标与广泛使用的 xy 坐标区分开, WebGL 使用 st 命名纹理坐标系统.

纹理单元(texture unit)
WebGL 通过一种称为纹理单元的的机制来同时使用多个纹理. 每个纹理单元有一个单元编号来管理一张纹理图像. 即使程序中只有一张纹理图像, 也要为其制定一个纹理单元.
系统支持的纹理单元个数取决于硬件和浏览器的 WebGL 实现, 但是在默认情况下, WebGL 至少支持 8 个纹理单元, 一些其他系统支持的个数更多. 内置的变量gl.TEXTURE0,gl.TEXTURE1,...,gl.TEXTURE7各表示一个纹理单元.
流明
流明(luminance)表示我们感知到的物体表面的亮度. 通常使用物体表面红, 绿, 蓝颜色分量值的加权平均来计算流明.
视点和视距
三维物体和二维图形最大的区别在于, 三维物体具有深度, 也就是 Z 轴. 因此, 当三维物体会知道二维屏幕上式, 就像是在绘制通过观察者看到的世界. 有两个要素是我们需要考虑的:
- 观察的方向
- 可视的距离
我们将观察者所处的位置称为视点, 将从视点触发沿着观察方向称作视线.
在 WebGL 系统中, 默认情况下, 视点处于原点(0,0,0), 视线为 z 轴负半轴.
观察点, 目标点和上方向
观察点即上面所描述的视点, 我们将视点坐标用(eyeX, eyeY, eyeZ)来表示观察点坐标.
目标点: 被观察目标所在的点, 视线从视点触发, 穿过观察目标点并继续延伸. 可以用(atX,atY,atZ)来表示
上方向: 最终绘制在屏幕上的影像中的向上的防线. 可以用(upX,upY,upZ)来表示.
在 WebGL 中, 可以用上述三个矢量创建一个视图矩阵(view matrix), 然后将该矩阵传给顶点着色器. 视图矩阵可以表示观察者的状态, 含有观察者的视点, 目标点, 上方向等信息. 最终决定了显示在屏幕上的视图, 也就是观察者观察到的场景.
在 webgl 中的默认状态是:
- 视点位于坐标系统原点(0,0,0)
- 视线为 z 轴负方向
- 观察点为(0,0,-1)
- 上方向为 y 轴负方向.即(0,1,0)
可视范围
在三维世界中, 还需要考虑可视范围的问题. 因为我们是通过浏览器去观察三维世界的, 就像我们通过眼睛去看真实的世界一样, 都有一个可视的范围区域. 在 webgl 中, 只有当三维物体在我们的可视范围内, webgl 才会去绘制它.
除了上下左右的范围, 还有远近, 这六个面组成一个几何体, 组成 webgl 中的绘制区域, 也就是可视区域.
一般来说, 有两类可视空间:
- 长方体可视空间, 也称盒状空间, 由正射投影(orthographic projection)产生.
- 金字塔可视空间, 有透视投影(perspective projection)产生.
下面是一个可视空间的示意图:
盒状空间示意:

透视投影可视空间示意:

深度缓冲区
深度缓冲区(depth buffer)是 Webgl 中的一个中间对象,作用是帮助 WebGL 进行隐藏面消除. WebGL 在颜色缓冲区中绘制几何图形, 绘制完成后将颜色缓冲到要显示的<canvas>上, 如果要将隐藏面小数, 就必须要知道几个图形的深度信息, 深度缓冲区正是用来存储这个的. 一般深度方向默认时 z 方向, 所以深度缓冲区也称为 Z 缓冲区.
在绘制任意一帧之前, 都要清除深度缓冲区, 来消除绘制上一帧留下的痕迹.
深度冲突
隐藏面消除是 WebGL 中一项很复杂的特性, 在绝大多数情况下, 都可以很好的完成任务, 但是当两个几何物体的两个表面极为接近时, 就会出现一些错误, 使表面看起来斑斑驳驳, 这种现象即 深度冲突(Z fighting).
之所以会产生深度冲突, 是因为两个表面过于接近, 深度缓冲区有限的精度不能够区分那个在前哪个在后, 当场景中有多个物体在运动的时候, 这几乎是一个无法避免的问题.
一般可以通过多边形偏移来处理这个问题
反射类型
反射光的颜色和方向分别取决于物体表面的类型和入射光.
反射光线的方式有两种:
漫反射(diffuse reflection):
其颜色可以有如下公式得到:
<漫反射光颜色> = <入射光颜色> x <表面基底色> x cosθ环境反射(environment/ambient reflection)
其颜色可以有如下公式得到:
<环境反射光颜色> = <入射光颜色> x <表面基底色>
上式中颜色的计算式逐分量进行的.
漫反射和环境光同时存在时, 将两者加起来就得到了物体被观察到的颜色:
<表面的反射颜色> = <漫反射光颜色> + <环境反射光颜色>
两种反射光并不一定总是存在, 也不一定需要按照这个公式来计算
光源类型
当物体被光照射的时候, 必然存在发出光线的光源. 真实世界中的光主要分为两种类型:平行光(directional light), 点光源(point light). 此外我们还需要一种环境光(ambient light)来模拟真实世界中的非直射光. 还有一些在本文范围之外的比如聚光灯光(spot light)等, 可以参考OPENGL ES2.0一书.
- 平行光: 太阳光
- 点光源: 灯/火焰等
- 环境光: 漫反射的光
法线: 表面的朝向
物体表面的朝向, 即垂直于表面的方向, 称为法线或者法向量. 一个表面具有两个法向量. 因为每个表面都有"正面"和"背面".
在三维图形中, 表面的正面和背面取决于绘制表面时候的顶点顺序, 顺时针为正面, 逆时针为背面.