1. 什么是WebGL
WebGL全称叫做Web Graphics Library,它是JavaScrint API、用于在任何兼容的Web浏览器中演染交互式的3D图形,并且无需使用插件。它基于 OpenGL ES 2.0(一个用于嵌入式系统的图形库),并与其他 Web 标准完全集成,使开发者能够利用 GPU 加速图形处理直接在网页上实现复杂的图形效果。
WebGL可以在网页上开发高性能的3D游戏,许多现代网页游戏都利用了WebGL实现了丰富的图形效果和流畅的用户体验。
在网页上实现这些丰富的图形效果,就需要在网页上有一块专门用来绘制图形的区域,这块区域叫做画布。因此在web网页上实现WebGL效果,需要使用到 canvas 画布元素。
学习WebGL,你需要掌握基本的HTML知识和JavaScript知识。
1.1 WebGL坐标
在WebGL中,坐标系使用的是右手坐标系,手掌朝向自己大拇指与食指垂直,食指与中指垂直形成一个空间。大拇指方向是X轴,食指方向是Y轴,中指方向是Z轴。
- X、Y、Z轴正方向最大值都唯1,负方向最大值都为-1。
1.2 Canvas画布上的坐标
Canvas画布可以支持2D,也可以支持3D。但是2D和3D坐标方向是完全不同的。
- 2D,坐标原点是canvas画布的左顶点,水平方向是向右的X轴,竖直方向向下是Y轴
- 3D,坐标原点是canvas画布的中心点,水平方向向右是X轴,竖直方向向上是Y轴,屏幕朝外方向是Z轴
1.3 WebGL渲染管线
渲染管线是指将数据从3D场景转换成2D图像,最终在屏幕上显示出来的总过程。它包含多个步骤,每个步骤处理不同的图形处理任务。
- 顶点处理:顶点着色器处理每个顶点的数据,将顶点坐标从对象空间转换到裁剪空间。
- 图元装配:顶点被组合成图元(例如三角形、线段、点)。WebGL 支持的基本图元类型包括点、线段和三角形。
- 光栅化:将图元转换为片段(fragments),每个片段对应一个屏幕像素。光栅化过程确定哪些像素被图元覆盖,并生成这些片段的插值属性(如颜色、深度、纹理坐标)。
- 片段处理:片段着色器对每个片段执行操作,确定其最终颜色。片段着色器可以执行纹理采样、光照计算和颜色混合等操作。
- 测试阶段:深度测试和模板测试。
- a.深度测试:确定片段是否在当前帧缓冲区的深度范围内,以决定是否绘制该片段。
- b.模板测试:使用模板缓冲区进行进一步的片段筛选。
6.融合阶段:将片段颜色与帧缓冲区中已有的颜色混合,以实现透明效果等。
7.帧缓冲操作:最终的片段颜色被写入帧缓冲区,准备显示在屏幕上。帧缓冲区是一个存储颜 色、深度和模板信息的内存区域。
2.canvas画布知识
<canvas> 元素是 HTML5 提供的一个用于绘制图形的区域,它可以通过 JavaScript 动态地渲染图形内容。<canvas> 可用于绘制图形、动画、图表、游戏以及实时数据可视化等。
可以在canvas画布上绘制2D或者3D图形,这取决于渲染上下文对象是2D还是3D。
2.1 获取渲染上下文
HTMLCanvasElement.getContext()方法用于返回绘图上下文对象,绘图上下文对象是2D上下文还是3D上下文取决于传入的参数。
• getContext('2d'):创建一个CanvasRenderingContext2D二维的渲染上下文对象
• getContext('webgl'):创建一个WebGLRenderingContext三维的渲染上下文对象
2.2 画布宽高设置
Canvas画布默认会在页面中占据一块位置,这块位置的大小默认是300*150。虽然canvas是一个元素,可以通过CSS样式属性来设置这个元素的宽和高,但是通常不推荐使用CSS样式属性来设置画布的宽高。
因为使用CSS属性只是影响画布元素在网页上显示的尺寸,并不会改变画布的分辨率。即使使用CSS样式改变了画布的大小,但是画布的分辨率还是默认的300*150,在显示上的缩放会导致绘制的图形失真或者模糊。
Canvas有两个属性:width和height,这两个属性定义了画布的实际尺寸,也就是会更改画布绘图的分辨率。
使用HTML属性来设置宽高可以:
- 避免图像失真:使用 HTML 属性来设置画布的宽度和高度确保了绘图操作是在正确的分辨率下进行的。如果使用 CSS 设置尺寸,画布的分辨率不会改变,绘制的图像可能会被拉伸或压缩,从而导致失真。
- 更好地控制分辨率:在高分辨率设备(如 Retina 显示屏)上,通过 HTML 属性设置宽度和高度可以确保绘制的内容具有足够的像素密度,避免模糊。
- 一致的绘图结果:HTML 属性设置的宽度和高度确保了绘制操作的坐标系统是一致的。这对于复杂的绘图操作或需要精确控制图像位置和尺寸的场景尤其重要。
3. 类型化数组
JavaScript的类型化数组是一种用于处理和操作二进制数据的对象,类型化数组在处理WebGL等需要高性能和大数据量操作的应用中非常有用。
类型化数组和普通的JavaScript数组不同,类型化数组的每一个元素都是同一类型的二进制数据,这种类型在创建数组时就已经确定了。
作用:常用在处理图像数据、音频数据、视频数据这些方向。对应的API有:WebGL、Canvas API、WebRTC、File API。
3.1 和普通数组的区别
内存管理方面
- 类型化数组:类型化数组直接在一个连续的内存块上操作。这意味着所有的数据都紧密地存储在一个固定大小的缓冲区中,数据访问和修改非常高效,因为它们不需要像普通数组那样进行额外的内存引用或分配。
- 普通数组:普通数组是动态的,可以容纳不同类型的数据。这使得它们非常灵活,但由于需要动态调整大小和存储多种类型的数据,它们的内存管理和访问速度比类型化数组要慢。
数据类型方面
- 类型化数组:类型化数组是强类型的,数组中的每个元素都必须是特定类型的值(如 Int8、Uint8、Float32 等)。这种类型的严格性确保了数据的一致性和高效的内存使用。
- 普通数组:普通数组是弱类型的,可以包含任意类型的值(如数字、字符串、对象等)。这种灵活性虽然方便,但会导致在进行大量数据处理时效率较低。
操作方法方面
- 类型化数组:类型化数组支持一组专门的方法,用于在二进制数据块上进行高效操作。它们不继承自 Array.prototype,因此不具备一些普通数组的方法(如 push、pop、splice 等)。
- 普通数组:普通数组继承自 Array.prototype,拥有丰富的数组操作方法,可以灵活地添加、删除、修改和遍历数组元素。
3.2 类型化数组有哪些
- Int8Array:8位有符号整数,每个元素占1字节
- Uint8Array:8 位无符号整数,每个元素占 1 字节。
- Uint8ClampedArray:8 位无符号整数(固定值,溢出时值被截断),每个元素占 1 字节。
- Int16Array:16 位有符号整数,每个元素占 2 字节。
- Uint16Array:16 位无符号整数,每个元素占 2 字节。
- Int32Array:32 位有符号整数,每个元素占 4 字节。
- Uint32Array:32 位无符号整数,每个元素占 4 字节。
- Float32Array:32 位 IEEE 754 浮点数,每个元素占 4 字节。
- Float64Array:64 位 IEEE 754 浮点数,每个元素占 8 字节。
3.3 创建和使用类型化数组
类型化数组每一种都有对应的构造函数,要创建类型化数组实例就需要使用new操作符实例化构造函数。
// 创建一个包含10个32位整数的类型化数组
let intArray = new Int32Array(10);
// 向类型化数组中填充数据
for (let i = 0; i < intArray.length; i++) {
intArray[i] = i * 2;
}
// 计算所有元素的总和
let sum = 0;
for (let i = 0; i < intArray.length; i++) {
sum += intArray[i];
}
console.log("类型化数组中的元素:", intArray); // 输出: 类型化数组中的元素: Int32Array(10) [ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18 ]
console.log("总和:", sum); // 输出: 总和: 90
这些类型化数组在WebGL得到了广泛的作用,使用也是非常频繁的。比如在定义几何图形的顶点,可以使用类型化数组定义,如下
const vertices = new Float32Array([
0.0, 1.0, // 第一个顶点的坐标 (x=0.0, y=1.0)
-1.0, -1.0, // 第二个顶点的坐标 (x=-1.0, y=-1.0)
1.0, -1.0 // 第三个顶点的坐标 (x=1.0, y=-1.0)
]);
内部项两两一组作为一个点的x坐标和y坐标,上面代码是定义了一个三角形的三个顶点的坐标。
4. 着色器的基础知识
GLSL(OpenGL Shading Language)是一种用于编写图形着色器的编程语言。着色器是用于在图形处理单元(GPU)上执行特定图形处理任务的程序。通俗讲,着色器是画点的工具,一个图形是由无数个点组成的,每个点都有其自己的颜色。
4.1 着色器的类型
一个着色器就是一个绘制东西到屏幕上的函数,着色器有顶点着色器和片段着色器
- 顶点着色器:处理每个顶点的属性,如位置、法线、纹理坐标等。它的主要任务是将顶点从对象坐标系转换到屏幕坐标系,并传递其他顶点属性给片段着色器。
- 片段着色器:处理每个像素的颜色。它接收来自顶点着色器插值后的数据,并最终决定每个像素的颜色。
4.2 着色器的语法
在HTML中,如果你想把着色器代码直接写在<script>标签中,通常会使用自定义的MIME类型,以避免浏览器将其作为常规JavaScript代码执行。常见的做法是将type属性设置为一个非标准类型,例如x-shader/x-vertex和x-shader/x-fragment,分别用于顶点着色器和片段着色器。这样做的好处是可以通过JavaScript获取和解析着色器代码。
<!--顶点着色器-->
<script id="vertex-shader" type="x-shader/x-vertex"></script>
<!--片段着色器-->
<script id="fragment-shader" type="x-shader/x-fragment"></script>
数据类型:
- 基本数据类型:float, int, bool
- 向量类型:vec2, vec3, vec4(浮点型);ivec2, ivec3, ivec4(整数型);bvec2, bvec3, bvec4(布尔型)
- 矩阵类型:mat2, mat3, mat4
- 采样器类型:sampler2D, samplerCube(用于纹理采样)
变量修饰符:
用于指定变量的作用域、生命周期和用途。不同的修饰符在顶点着色器和片段着色器中起到不同的作用。
- attribute:用于顶点着色器,定义从顶点缓冲区传入的变量(仅在顶点着色器中使用)。
- uniform:定义在整个渲染过程中保持不变的变量,常用于传递变换矩阵、光照参数等。
- varying:用于在顶点着色器和片段着色器之间传递插值数据。
内置变量:
顶点着色器内置变量:
- gl_Position顶点的变换后位置
- gl_PointSize大小
片段着色器内置变量:
- gl_FragColor:片段的最终颜色。
函数:
- 常用数学函数:sin, cos, tan, pow, exp, log, sqrt, abs, min, max,radians
- 向量函数:dot, cross, normalize, length, distance
- 纹理采样函数:texture2D, textureCube
定义精度:precision 关键字用于声明变量的默认精度。
GLSL 支持三种不同的精度修饰符:
- highp(高精度):通常用32位表示,适用于需要高精度的计算,如位置计算、复杂的物理效果等。
- mediump(中精度):通常用16位表示,适用于需要中等精度但不需要高精度的计算,如纹理坐标。
- lowp(低精度):通常用10位表示,适用于对精度要求最低的计算,如颜色值。
precision lowp float;
主函数:着色器必须要有一个主函数void main(),它是入口
void main(void) {
gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
vColor = vec4(1.0, 1.0, 1.0, 1.0);
}