WebGL(WebGL)是Web Graphics Library的缩写,它是一种用于在浏览器中渲染3D图形和2D图形的JavaScript API。WebGL使开发者可以在无需安装插件的情况下,通过浏览器在网页上创建复杂的三维图形和动画,它使用了OpenGL ES 2.0作为底层技术。
1 WebGL 的概述
- 跨平台性:WebGL 能够在任何支持 HTML5 的浏览器中运行,无论是桌面、移动设备,还是其他设备。
- 硬件加速:WebGL 直接调用 GPU(图形处理器)来进行图形处理,这使得其性能大大优于仅依赖 CPU 的图形渲染方式。
- 开放标准:WebGL 由Khronos Group维护,这是一个致力于制定开放标准的联盟,其他成员还包括 Apple、Google、Microsoft 等。
- 主要用途:主要用于网页中的实时3D游戏、互动式3D模型展示、数据可视化、虚拟现实(VR)等。
2 WebGL 的历史
- 2006-2007:WebGL的雏形可以追溯到 Mozilla 的一名工程师 Vladimir Vukićević 提出的一个概念:将 OpenGL ES 2.0 的功能引入浏览器,进而形成浏览器中的3D API。
- 2009年:Khronos Group 成立了 WebGL 工作组,致力于开发一个标准化的 WebGL API。此时 WebGL 已经在 Firefox 和 Chrome 中有了早期的实现。
- 2011年3月:WebGL 1.0 正式发布,这是一个基于 OpenGL ES 2.0 的3D渲染API。Firefox、Chrome、Safari 等浏览器逐渐开始支持 WebGL 1.0。
- 2017年:WebGL 2.0 发布,这是 WebGL 的重大更新,基于 OpenGL ES 3.0,带来了更多功能,比如多重渲染目标、改进的纹理支持等。
WebGL 通过 JavaScript 直接调用底层的 OpenGL 功能,极大地扩展了网页的图形渲染能力。在 WebGL 的推动下,浏览器成为了一个强大的3D图形渲染平台,推动了 Web 游戏、虚拟现实以及其他互动式图形应用的发展。
图 1.1
3 绘制一个点
使用 gl.POINTS 绘制点
gl.POINTS 是 WebGL 提供的一种绘制方式,最常见的绘制点的方法就是通过该模式。
3.1 步骤:
- 创建 WebGL 上下文:获取 Canvas 上的 WebGL 上下文。
- 定义顶点着色器和片段着色器:着色器会处理如何将顶点绘制到屏幕上。
- 传递顶点数据:将表示点的顶点坐标传递到着色器中。
- 设置绘制点的大小:可以通过
gl_PointSize设置点的大小。
3.2 代码示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<style>
#canvas {
width: 600px;
height: 600px;
position: absolute;
top: calc(50% - 300px);
left: calc(50% - 300px);
background-color: black;
}
</style>
<body>
<canvas id="canvas"></canvas>
<script>
// 通过元素获取二维图形的绘图上下文
const canvas = document.getElementById("canvas");
const gl = canvas.getContext("webgl");
// 定义顶点着色器
const vertexShaderSource = `
attribute vec4 a_Position;
void main() {
gl_Position = a_Position; // 设置点的坐标
gl_PointSize = 10.0; // 设置点的大小
}
`;
// 定义片段着色器
const fragmentShaderSource = `
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 设置点的颜色为红色
}
`;
// 编译着色器函数
function createShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
return shader;
}
// 创建和链接着色器程序
const vertexShader = createShader(
gl,
gl.VERTEX_SHADER,
vertexShaderSource
);
const fragmentShader = createShader(
gl,
gl.FRAGMENT_SHADER,
fragmentShaderSource
);
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
// 设置顶点数据
const vertices = new Float32Array([
0.0,
0.0,
0.0, // 在画布中心绘制一个点
]);
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// 连接顶点着色器的a_Position变量
// 获取a_Position变量存储位置
const a_Position = gl.getAttribLocation(program, "a_Position");
gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_Position);
// 设置背景颜色并清空
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
// 绘制点
gl.drawArrays(gl.POINTS, 0, 1);
</script>
</body>
</html>
整个流程大致如下:
- 创建缓冲区对象
- 将缓冲区对象绑定到target对象上
- 将顶点数据写入缓存区
- 将缓冲区对象分配给对象的attribute变量
- 开启attribute变量
- 根据类型绘制点
注:
const vertices = new Float32Array([
1.0,
0.0,
0.0,
0.0, // 在画布中心绘制一个点
]);
gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, 0, 4);
如果这样设置也可以 就会从buffer数组的第五个字节开始读取,Float32四个字节
3.3 知识点
3.3.1 attribute vec4
attribute:存储限定符,表示接下来的变量是一个attribute变量,attribute变量只有顶点着色器才能使用它;
vec4:数据类型,表示四个浮点数组成的矢量。
这是GLSL ES数据类型
3.3.2 WebGL需要两种着色器:
顶点着色器(Vertex shader)
顶点着色器用来描述顶点特性(如位置、颜色)的程序。顶点是指二维或者三维空间中的一个点,比如二维或者三维图形的端点或者交点。
片元着色器(Fragment shader)
进行逐片元处理过程如光照的程序,片元是一个WegGL术语,可以理解为一个像素。
后面会单开一个章节详细介绍OpenGL ES的着色器
4 通过attribute变量
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<style>
#canvas {
width: 600px;
height: 600px;
position: absolute;
top: calc(50% - 300px);
left: calc(50% - 300px);
background-color: black;
}
</style>
<body>
<canvas id="canvas"></canvas>
<script>
// 通过元素获取二维图形的绘图上下文
const canvas = document.getElementById("canvas");
const gl = canvas.getContext("webgl");
// 定义顶点着色器
const vertexShaderSource = `
attribute vec4 a_Position;
void main() {
gl_Position = a_Position; // 设置点的坐标
gl_PointSize = 10.0; // 设置点的大小
}
`;
// 定义片段着色器
const fragmentShaderSource = `
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 设置点的颜色为红色
}
`;
// 编译着色器函数
function createShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
return shader;
}
// 创建和链接着色器程序
const vertexShader = createShader(
gl,
gl.VERTEX_SHADER,
vertexShaderSource
);
const fragmentShader = createShader(
gl,
gl.FRAGMENT_SHADER,
fragmentShaderSource
);
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
// 连接顶点着色器的a_Position变量
// 获取a_Position变量存储位置
const a_Position = gl.getAttribLocation(program, "a_Position");
gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);
// 设置背景颜色并清空
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
// 绘制点
gl.drawArrays(gl.POINTS, 0, 1);
</script>
</body>
</html>
问题核心在于如何给attribute变量 a_Position赋值,前面通过vertexAttribPointer指向缓冲区数据,这里采用
attribute同族函数赋值。
4.1 知识点、
WebGLRenderingContext.vertexAttrib[1234]fv
void gl.vertexAttrib1f(location, v0);
void gl.vertexAttrib2f(location, v0, v1);
void gl.vertexAttrib3f(location, v0, v1, v2);
void gl.vertexAttrib4f(location, v0, v1, v2, v3);
将数据传输给location参数指定的attribute变量中。gl.vertexAttrib1f仅仅传输一个值,这个值将填充到location的第一个分量中,第2、3个分量被设置为0.0,第四个分量被设置为1.0。类似地gl.vertexAttrib2f将填充两个分量,第三个分量为0.0,第四个分量为1.0。
WebGL相关函数命名规范
你可能想知道 gl.vertexAttrib3f中的3f是什么意思。WebGL中的函数命名遵循OpenGL ES 2.0中的函数名。Open GL函数名由三部分组成:<基础函数名><参数个数><参数类型>。WebGL的函数命名使用同样的结构。
5 通过鼠标点击绘点
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<style>
#canvas {
width: 600px;
height: 600px;
position: absolute;
top: calc(50% - 300px);
left: calc(50% - 300px);
background-color: black;
}
</style>
<body>
<canvas id="canvas"></canvas>
<script>
// 通过元素获取二维图形的绘图上下文
const canvas = document.getElementById("canvas");
const gl = canvas.getContext("webgl");
// 定义顶点着色器
const vertexShaderSource = `
attribute vec4 a_Position;
void main() {
gl_Position = a_Position; // 设置点的坐标
gl_PointSize = 10.0; // 设置点的大小
}
`;
// 定义片段着色器
const fragmentShaderSource = `
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 设置点的颜色为红色
}
`;
// 编译着色器函数
function createShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
return shader;
}
// 创建和链接着色器程序
const vertexShader = createShader(
gl,
gl.VERTEX_SHADER,
vertexShaderSource
);
const fragmentShader = createShader(
gl,
gl.FRAGMENT_SHADER,
fragmentShaderSource
);
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
const points = [];
canvas.onmousedown = function (e) {
let x = e.clientX;
let y = e.clientY;
// 获取元素大小及相对于视口位置
const rect = e.target.getBoundingClientRect();
const width = rect.width;
const height = rect.height;
x = (x - rect.left - width / 2) / (width / 2);
y = (height / 2 - (y - rect.top)) / (height / 2);
points.push(x, y);
// 设置背景颜色并清空
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
// 连接顶点着色器的a_Position变量
const a_Position = gl.getAttribLocation(program, "a_Position");
for (let index = 0; index < points.length; index += 2) {
gl.vertexAttrib3f(a_Position, points[index], points[index + 1], 0.0);
// 绘制点
gl.drawArrays(gl.POINTS, 0, 1);
}
};
</script>
</body>
</html>
5.1 知识点
因为鼠标点击位置是相对于视口的,所以要进行坐标转换,转换到Cancas坐标系下;Canvas坐标系与WebGL 坐标系不一样,所以还需要转换到WebGL坐标系下
x = (x - rect.left - width / 2) / (width / 2);
y = (height / 2 - (y - rect.top)) / (height / 2);
Canvas与WebGL坐标系区别
| 特性 | Canvas | WebGL |
|---|---|---|
| 坐标维度 | 2D 坐标系统 (X, Y) | 3D 坐标系统 (X, Y, Z) |
| 坐标原点 | 左上角 (0, 0) | 屏幕中心 (0, 0) |
| 坐标范围 | X 和 Y 坐标取决于画布的像素大小 | X 和 Y 坐标在 [-1, 1] 范围内 |
| Y 轴方向 | 向下为正 | 向上为正 |
| Z 轴 | 无 | Z 轴控制深度,范围为 [-1, 1] |
| 使用场景 | 主要用于 2D 图形和简单的动画绘制 | 用于 3D 图形渲染,支持复杂的图形变换 |