跳到主要内容

WebGL-基础概念

着色器(Shader)

  • 顶点着色器(Vertex shader): 顶点着色器是用来描述定点特性(位置/颜色)的程序, 顶点(vertex)是指二维或者三维空间中的一个点. 比如二维或者三维的端点或者交点
  • 片元着色器(Fragment shader): 进行逐片元处理程序. 比如光照处理. 片元(fragment)是一个 webgl 术语, 可以理解为像素.

坐标系统

webgl 采用三维坐标系统(笛卡尔坐标系), x 轴水平(正向为右), y 轴垂直(正向为下), z 轴垂直于屏幕(正向为外), 这套坐标系又称为右手坐标系. 默认情况下 webgl 采用这台坐标系. 但是 webgl 实际的情况要复杂的多, 它既不是右手坐标系也不是左手坐标系. 现在我们还是可以简单的认为 webgl 就是右手坐标系.

image

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

image

  • <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 中的类型化数组如下所示:

数组类型每个元素所占字节数描述
Int8Array18 位整数型(char)
UInt8Array18 位无符号整数(unsigned char)
Int16Array216 位整数型(short)
UInt16Array216 位无符号整数型(unsigned short)
Int32Array432 位整数型(signed int)
UInt32Array432 位无符号整数型(unsigned int)
Float32Array4单精度 32 位浮点数(float)
Float64Array8双精度 64 位浮点数(double)

与 JavaScript 中的数组类似, 类型化数组也有一系列的方法和属性, 但是与普通的 Array 数组不同, 类型化数组不支持 push()和 pop()方法.

方法属性和常量描述
get(index)获取第 index 个元素值
set(index,value)设置第 index 个元素的值为 value
set(array,offset)从第 offset 个元素开始将数组 array 中的值填充进去
length数组的长度
BYTES_PER_ELEMENT数组中每个元素所占的字节数

顶点到片元: 图形装配和光栅化

image

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

image

实际上, gl_position实际上就是几何图形装配(geometric shape assembly) 阶段的输入数据, 几何图形装配过程又被称为图元装配过程(primitive assembly process), 因为被装配出的基本图形(点,线,面)又被称为图元(primitives).

纹理

三维图形中有一项跟重要的技术, 叫做 纹理映射(texture mapping) . 纹理映射实际上就是将一张图像映射到一个几何图形的表面上去. 将一张真实世界的图片贴到一个有两个三角形组成的矩阵上, 这张图片就成为 "纹理图像" 或 "纹理" .

纹理坐标

纹理坐标是纹理图像上的坐标, 通过纹理坐标可以在纹理图像上获取纹素颜色, WebGL 系统中的纹理坐标系统是二维的, 为了将纹理坐标与广泛使用的 xy 坐标区分开, WebGL 使用 st 命名纹理坐标系统.

image

纹理单元(texture unit)

WebGL 通过一种称为纹理单元的的机制来同时使用多个纹理. 每个纹理单元有一个单元编号来管理一张纹理图像. 即使程序中只有一张纹理图像, 也要为其制定一个纹理单元.

系统支持的纹理单元个数取决于硬件和浏览器的 WebGL 实现, 但是在默认情况下, WebGL 至少支持 8 个纹理单元, 一些其他系统支持的个数更多. 内置的变量gl.TEXTURE0,gl.TEXTURE1,...,gl.TEXTURE7各表示一个纹理单元.

流明

流明(luminance)表示我们感知到的物体表面的亮度. 通常使用物体表面红, 绿, 蓝颜色分量值的加权平均来计算流明.

视点和视距

三维物体和二维图形最大的区别在于, 三维物体具有深度, 也就是 Z 轴. 因此, 当三维物体会知道二维屏幕上式, 就像是在绘制通过观察者看到的世界. 有两个要素是我们需要考虑的:

  1. 观察的方向
  2. 可视的距离

我们将观察者所处的位置称为视点, 将从视点触发沿着观察方向称作视线.

在 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)产生.

下面是一个可视空间的示意图:

盒状空间示意:

image

透视投影可视空间示意:

image

深度缓冲区

深度缓冲区(depth buffer)是 Webgl 中的一个中间对象,作用是帮助 WebGL 进行隐藏面消除. WebGL 在颜色缓冲区中绘制几何图形, 绘制完成后将颜色缓冲到要显示的<canvas>上, 如果要将隐藏面小数, 就必须要知道几个图形的深度信息, 深度缓冲区正是用来存储这个的. 一般深度方向默认时 z 方向, 所以深度缓冲区也称为 Z 缓冲区.

在绘制任意一帧之前, 都要清除深度缓冲区, 来消除绘制上一帧留下的痕迹.

深度冲突

隐藏面消除是 WebGL 中一项很复杂的特性, 在绝大多数情况下, 都可以很好的完成任务, 但是当两个几何物体的两个表面极为接近时, 就会出现一些错误, 使表面看起来斑斑驳驳, 这种现象即 深度冲突(Z fighting).

之所以会产生深度冲突, 是因为两个表面过于接近, 深度缓冲区有限的精度不能够区分那个在前哪个在后, 当场景中有多个物体在运动的时候, 这几乎是一个无法避免的问题.

一般可以通过多边形偏移来处理这个问题

反射类型

反射光的颜色和方向分别取决于物体表面的类型和入射光.

反射光线的方式有两种:

  1. 漫反射(diffuse reflection):

    其颜色可以有如下公式得到:

    <漫反射光颜色> = <入射光颜色> x <表面基底色> x cosθ
  2. 环境反射(environment/ambient reflection)

    其颜色可以有如下公式得到:

    <环境反射光颜色> = <入射光颜色> x <表面基底色>

上式中颜色的计算式逐分量进行的.

漫反射和环境光同时存在时, 将两者加起来就得到了物体被观察到的颜色:

<表面的反射颜色> = <漫反射光颜色> + <环境反射光颜色>

两种反射光并不一定总是存在, 也不一定需要按照这个公式来计算

光源类型

当物体被光照射的时候, 必然存在发出光线的光源. 真实世界中的光主要分为两种类型:平行光(directional light), 点光源(point light). 此外我们还需要一种环境光(ambient light)来模拟真实世界中的非直射光. 还有一些在本文范围之外的比如聚光灯光(spot light)等, 可以参考OPENGL ES2.0一书.

  • 平行光: 太阳光
  • 点光源: 灯/火焰等
  • 环境光: 漫反射的光

法线: 表面的朝向

物体表面的朝向, 即垂直于表面的方向, 称为法线或者法向量. 一个表面具有两个法向量. 因为每个表面都有"正面"和"背面".

在三维图形中, 表面的正面和背面取决于绘制表面时候的顶点顺序, 顺时针为正面, 逆时针为背面.