1.3 第一个 WebGL 程序


文档摘要

1.3 第一个 WebGL 程序 1.3 第一个 WebGL 程序:点亮你的 WebGL 世界 WebGL,全称 Web Graphics Library,是一个在网页浏览器中渲染交互式 2D 和 3D 图形标准的 JavaScript API。它无需任何插件,利用计算机的图形处理单元(GPU)进行硬件加速渲染,从而实现高性能的图形效果。对于想要在网页上创建令人惊叹的视觉体验的开发者来说,WebGL 是一个强大的工具。 本节,我们将踏入 WebGL 的基础领域,从零开始构建你的第一个 WebGL 程序。这个程序虽然简单,但它将为你揭开 WebGL 工作原理的冰山一角,并为你后续深入学习和开发更复杂的 WebGL 应用奠定坚实的基础。 1.3.

1.3 第一个 WebGL 程序

1.3 第一个 WebGL 程序:点亮你的 WebGL 世界

WebGL,全称 Web Graphics Library,是一个在网页浏览器中渲染交互式 2D 和 3D 图形标准的 JavaScript API。它无需任何插件,利用计算机的图形处理单元(GPU)进行硬件加速渲染,从而实现高性能的图形效果。对于想要在网页上创建令人惊叹的视觉体验的开发者来说,WebGL 是一个强大的工具。

本节,我们将踏入 WebGL 的基础领域,从零开始构建你的第一个 WebGL 程序。这个程序虽然简单,但它将为你揭开 WebGL 工作原理的冰山一角,并为你后续深入学习和开发更复杂的 WebGL 应用奠定坚实的基础。

1.3.1 准备工作:HTML Canvas 与 WebGL 上下文

在开始编写 WebGL 代码之前,我们首先需要一个“画布”来承载我们的图形世界。这个画布就是 HTML5 的 <canvas> 元素。WebGL 实际上是在 <canvas> 上下文中运行的,我们需要先获取 WebGL 的上下文,才能开始绘制。

1. 创建 HTML 文件

首先,创建一个 HTML 文件(例如 index.html),并在 <body> 中添加一个 <canvas> 元素。为了方便 JavaScript 代码操作,我们给 <canvas> 元素设置一个唯一的 id,例如 webglCanvas

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>我的第一个 WebGL 程序</title> </head> <body> <canvas id="webglCanvas" width="500" height="500"></canvas> <script src="main.js"></script> </body> </html>

在这个 HTML 文件中,我们定义了一个 500x500 像素的画布,并通过 <script src="main.js"></script> 引入了我们将要编写 WebGL 代码的 JavaScript 文件 main.js

2. 获取 WebGL 上下文

接下来,在 main.js 文件中,我们需要获取 <canvas> 元素的 WebGL 上下文。这是连接 JavaScript 代码和 WebGL API 的桥梁。

const canvas = document.getElementById('webglCanvas'); // 尝试获取 WebGL2 上下文,如果不支持则回退到 WebGL1 const gl = canvas.getContext('webgl2') || canvas.getContext('webgl'); if (!gl) { alert('你的浏览器不支持 WebGL!'); } else { console.log('成功获取 WebGL 上下文!'); // WebGL 初始化和渲染代码将在这里编写 }

这段 JavaScript 代码首先通过 document.getElementById('webglCanvas') 获取了 <canvas> 元素。然后,使用 canvas.getContext('webgl2') 尝试获取 WebGL2 上下文。如果浏览器不支持 WebGL2,则会返回 null,此时我们再尝试使用 canvas.getContext('webgl') 获取 WebGL1 上下文。

getContext() 方法是获取渲染上下文的关键。如果成功获取到 WebGL 上下文,gl 变量将存储 WebGL 的 API 对象,我们可以通过 gl 对象调用 WebGL 的各种函数进行图形渲染。如果获取失败(例如浏览器不支持 WebGL),gl 将为 null,我们需要进行错误处理,例如弹出警告信息。

流程图(mermaid graph TD):

1.3.2 顶点着色器和片元着色器:WebGL 的“左右手”

WebGL 的渲染管线是可编程的,这意味着我们需要编写程序来控制渲染过程。这些程序被称为着色器(Shader)。WebGL 主要使用两种着色器:顶点着色器(Vertex Shader)片元着色器(Fragment Shader)

  • 顶点着色器:负责处理顶点数据。它接收顶点的位置、颜色、法线等属性,并进行变换、光照计算等操作,最终输出顶点的裁剪空间坐标。简单来说,顶点着色器决定了图形的形状和位置。

  • 片元着色器:负责处理光栅化后的每个片元(可以理解为像素)。它接收来自顶点着色器的插值数据,并计算每个片元的颜色值。简单来说,片元着色器决定了图形的颜色和纹理等视觉效果。

着色器使用 GLSL ES (OpenGL ES Shading Language) 语言编写,这是一种类似于 C 语言的专门用于图形编程的语言。

1. 编写顶点着色器

我们的第一个 WebGL 程序的目标是在画布上绘制一个点。为了实现这个目标,我们需要一个非常简单的顶点着色器。

// 顶点着色器代码 (vertexShader.glsl) attribute vec4 a_Position; // 顶点位置属性 void main() { gl_Position = a_Position; // 将顶点位置赋值给 gl_Position gl_PointSize = 10.0; // 设置点的大小 }

这段 GLSL 代码定义了一个顶点着色器。

  • attribute vec4 a_Position;:声明了一个 attribute 类型的变量 a_Position,它是一个四维向量 (vec4)。attribute 变量用于接收来自 JavaScript 代码传递的顶点属性数据,例如顶点的位置。在这里,a_Position 将接收每个顶点的坐标。

  • void main() { ... }main 函数是顶点着色器的入口点,每个顶点着色器都必须包含一个 main 函数。

  • gl_Position = a_Position;gl_Position 是顶点着色器内置的输出变量,类型为 vec4。顶点着色器必须将计算后的顶点裁剪空间坐标赋值给 gl_Position。在这里,我们直接将输入的顶点位置 a_Position 赋值给 gl_Position,表示我们不进行任何顶点变换。

  • gl_PointSize = 10.0;gl_PointSize 是顶点着色器内置的输出变量,用于设置绘制点的大小。这里我们将其设置为 10.0 像素。

2. 编写片元着色器

接下来,我们需要编写片元着色器来为点着色。

// 片元着色器代码 (fragmentShader.glsl) precision mediump float; // 设置浮点数精度 void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 设置片元颜色为红色 (RGBA: 1, 0, 0, 1) }

这段 GLSL 代码定义了一个片元着色器。

  • precision mediump float;:指定浮点数精度为 mediump (中等精度)。片元着色器需要指定浮点数精度,可以选择 highp (高精度)、mediump (中等精度) 或 lowp (低精度)。选择合适的精度可以在性能和精度之间取得平衡。

  • void main() { ... }main 函数是片元着色器的入口点。

  • gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);gl_FragColor 是片元着色器内置的输出变量,类型为 vec4。片元着色器必须将计算后的片元颜色值赋值给 gl_FragColor。在这里,我们将其设置为 vec4(1.0, 0.0, 0.0, 1.0),表示红色 (RGBA 格式,R=1, G=0, B=0, A=1)。这意味着我们绘制的点将是红色的。

着色器流程图(mermaid graph TD):

1.3.3 加载、编译和链接着色器程序

我们已经编写了顶点着色器和片元着色器的 GLSL 代码,接下来需要在 JavaScript 代码中加载这些着色器代码,并将其编译和链接成一个 着色器程序 (Shader Program)。着色器程序是 WebGL 实际执行的程序。

1. 加载着色器代码

通常,我们会将着色器代码保存在单独的文件中(例如 vertexShader.glslfragmentShader.glsl),然后在 JavaScript 代码中通过网络请求加载这些文件。为了简化示例,我们也可以直接将着色器代码作为字符串写在 JavaScript 代码中。

// 顶点着色器源码 const vertexShaderSource = ` attribute vec4 a_Position; void main() { gl_Position = a_Position; gl_PointSize = 10.0; } `; // 片元着色器源码 const fragmentShaderSource = ` precision mediump float; void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); } `;

2. 创建、编译着色器

WebGL 提供了 gl.createShader() 方法来创建着色器对象,gl.shaderSource() 方法来设置着色器源码,gl.compileShader() 方法来编译着色器。

// 创建顶点着色器 const vertexShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vertexShader, vertexShaderSource); gl.compileShader(vertexShader); // 检查顶点着色器编译是否成功 if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) { console.error('顶点着色器编译失败:', gl.getShaderInfoLog(vertexShader)); gl.deleteShader(vertexShader); return null; // 或抛出异常 } // 创建片元着色器 const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fragmentShader, fragmentShaderSource); gl.compileShader(fragmentShader); // 检查片元着色器编译是否成功 if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) { console.error('片元着色器编译失败:', gl.getShaderInfoLog(fragmentShader)); gl.deleteShader(fragmentShader); return null; // 或抛出异常 }

这段代码分别创建了顶点着色器和片元着色器对象,并加载了对应的源码,然后进行了编译。gl.getShaderParameter(shader, gl.COMPILE_STATUS) 方法用于检查着色器编译是否成功,如果不成功,可以通过 gl.getShaderInfoLog(shader) 获取编译错误信息。

3. 创建、链接着色器程序

WebGL 提供了 gl.createProgram() 方法来创建着色器程序对象,gl.attachShader() 方法将着色器附加到程序,gl.linkProgram() 方法链接程序。

// 创建着色器程序 const program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); // 检查着色器程序链接是否成功 if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { console.error('着色器程序链接失败:', gl.getProgramInfoLog(program)); gl.deleteProgram(program); gl.deleteShader(vertexShader); gl.deleteShader(fragmentShader); return null; // 或抛出异常 } // 告知 WebGL 使用该着色器程序 gl.useProgram(program);

这段代码创建了一个着色器程序对象,并将编译好的顶点着色器和片元着色器附加到程序中,然后进行了链接。gl.getProgramParameter(program, gl.LINK_STATUS) 方法用于检查程序链接是否成功,如果不成功,可以通过 gl.getProgramInfoLog(program) 获取链接错误信息。最后,gl.useProgram(program) 方法告知 WebGL 使用这个着色器程序进行后续的渲染操作。

着色器编译链接流程图(mermaid graph TD):

1.3.4 传递顶点数据:属性变量与缓冲区

着色器程序已经准备好了,接下来我们需要将顶点数据传递给顶点着色器。在我们的顶点着色器中,我们声明了一个 attribute vec4 a_Position 变量用于接收顶点位置数据。我们需要将实际的顶点数据传递给这个 a_Position 属性变量。

1. 获取属性变量的存储位置

WebGL 使用 gl.getAttribLocation() 方法来获取 attribute 变量在着色器程序中的存储位置 (location)。

const a_PositionLocation = gl.getAttribLocation(program, 'a_Position'); if (a_PositionLocation === -1) { console.error('获取属性变量 a_Position 失败'); return; }

gl.getAttribLocation() 方法接收两个参数:着色器程序对象 program 和属性变量名字符串 'a_Position'。如果成功获取到位置,则返回一个非负整数作为位置索引;如果获取失败(例如着色器程序中没有声明该属性变量),则返回 -1

2. 创建缓冲区对象

WebGL 使用缓冲区对象 (Buffer Object) 来管理顶点数据。我们需要创建一个缓冲区对象,并将顶点数据存储到缓冲区中。

// 创建缓冲区对象 const positionBuffer = gl.createBuffer(); if (!positionBuffer) { console.error('创建缓冲区对象失败'); return; }

gl.createBuffer() 方法用于创建一个缓冲区对象,返回一个表示缓冲区对象的 WebGLBuffer 对象。

3. 绑定缓冲区对象

在操作缓冲区之前,我们需要先将其绑定到 WebGL 的目标 (target) 上。对于顶点属性数据,我们需要绑定到 gl.ARRAY_BUFFER 目标。

// 绑定缓冲区对象到 ARRAY_BUFFER 目标 gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

gl.bindBuffer() 方法接收两个参数:目标 target 和缓冲区对象 buffergl.ARRAY_BUFFER 表示缓冲区对象包含顶点属性数据。

4. 将顶点数据写入缓冲区

现在我们可以将实际的顶点数据写入缓冲区。对于绘制一个点,我们只需要一个顶点的位置坐标。例如,我们将点的位置设置为画布中心 (0, 0, 0)。

// 顶点数据 (一个点的坐标,x, y, z) const positions = new Float32Array([0.0, 0.0, 0.0]); // 使用 Float32Array 创建浮点数数组 // 将顶点数据写入缓冲区 gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);

gl.bufferData() 方法用于将数据写入当前绑定的缓冲区对象。它接收三个参数:目标 target (必须与绑定时的目标一致,这里是 gl.ARRAY_BUFFER)、数据 data (类型化数组,例如 Float32Array) 和使用方式 usagegl.STATIC_DRAW 表示数据将被写入缓冲区一次,并多次用于绘制。

5. 将缓冲区数据分配给属性变量

最后,我们需要将缓冲区中的顶点数据分配给顶点着色器中的 a_Position 属性变量。

// 启用顶点属性数组 gl.enableVertexAttribArray(a_PositionLocation); // 指定属性变量如何从绑定的缓冲区中读取数据 gl.vertexAttribPointer( a_PositionLocation, // 属性变量的位置 3, // 每个顶点属性的分量数 (这里是 x, y, z 三个分量) gl.FLOAT, // 数据类型 (浮点数) false, // 是否归一化 (不需要) 0, // 相邻顶点属性之间的字节偏移量 (紧密排列,为 0) 0 // 属性数据在缓冲区中的起始字节偏移量 (从头开始,为 0) );
  • gl.enableVertexAttribArray(a_PositionLocation):启用顶点属性数组,使得 WebGL 可以从缓冲区中读取数据并传递给属性变量。

  • gl.vertexAttribPointer():指定属性变量如何从绑定的缓冲区中读取数据。它接收多个参数,详细解释如下:

    • a_PositionLocation:属性变量的位置索引。

    • 3:每个顶点属性的分量数。由于我们传递的是三维坐标 (x, y, z),所以分量数为 3。即使 a_Positionvec4 类型,我们只需要提供三个分量,第四个分量默认会被设置为 0 或 1 (取决于 vec4 的具体用法,对于位置坐标通常会补全为 w=1)。

    • gl.FLOAT:数据类型为浮点数。

    • false:是否需要将数据归一化到 [-1, 1][0, 1] 范围。我们不需要归一化,所以设置为 false

    • 0:步长 (stride),即相邻顶点属性之间的字节偏移量。当顶点属性数据紧密排列在缓冲区中时,步长为 0。

    • 0:偏移量 (offset),即属性数据在缓冲区中的起始字节偏移量。我们的属性数据从缓冲区开头开始,所以偏移量为 0。

顶点数据传递流程图(mermaid graph TD):

1.3.5 清空画布和绘制

最后一步,我们需要清空画布颜色,并使用 gl.drawArrays() 方法来绘制点。

1. 设置清空颜色和清空画布

在绘制之前,通常需要先清空画布,避免上次绘制的内容影响本次绘制。

// 设置清空画布颜色 (RGBA: 黑色) gl.clearColor(0.0, 0.0, 0.0, 1.0); // 清空画布 gl.clear(gl.COLOR_BUFFER_BIT);
  • gl.clearColor(r, g, b, a):设置清空画布的颜色,参数为 RGBA 颜色值,范围为 [0, 1]。这里我们设置为黑色 (0, 0, 0, 1)

  • gl.clear(mask):清空画布。gl.COLOR_BUFFER_BIT 表示清空颜色缓冲区。

2. 绘制

WebGL 提供了多种绘制方法,对于绘制点、线、三角形等基本图元,可以使用 gl.drawArrays() 方法。

// 绘制点 gl.drawArrays(gl.POINTS, 0, 1);

gl.drawArrays() 方法接收三个参数:

  • mode:绘制模式,指定绘制的图元类型。gl.POINTS 表示绘制点。

  • first:从哪个顶点开始绘制。0 表示从第一个顶点开始。

  • count:绘制的顶点数量。1 表示绘制一个点。

绘制流程图(mermaid graph TD):

1.3.6 完整代码示例

将以上所有代码片段组合起来,我们就得到了第一个完整的 WebGL 程序,可以在画布中心绘制一个红色的点。

index.html:

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>我的第一个 WebGL 程序</title> </head> <body> <canvas id="webglCanvas" width="500" height="500"></canvas> <script src="main.js"></script> </body> </html>

main.js:

const canvas = document.getElementById('webglCanvas'); const gl = canvas.getContext('webgl2') || canvas.getContext('webgl'); if (!gl) { alert('你的浏览器不支持 WebGL!'); } else { console.log('成功获取 WebGL 上下文!'); // 顶点着色器源码 const vertexShaderSource = ` attribute vec4 a_Position; void main() { gl_Position = a_Position; gl_PointSize = 10.0; } `; // 片元着色器源码 const fragmentShaderSource = ` precision mediump float; void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); } `; // 创建、编译顶点着色器 const vertexShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vertexShader, vertexShaderSource); gl.compileShader(vertexShader); if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) { console.error('顶点着色器编译失败:', gl.getShaderInfoLog(vertexShader)); gl.deleteShader(vertexShader); return; } // 创建、编译片元着色器 const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fragmentShader, fragmentShaderSource); gl.compileShader(fragmentShader); if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) { console.error('片元着色器编译失败:', gl.getShaderInfoLog(fragmentShader)); gl.deleteShader(fragmentShader); return; } // 创建、链接着色器程序 const program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { console.error('着色器程序链接失败:', gl.getProgramInfoLog(program)); gl.deleteProgram(program); gl.deleteShader(vertexShader); gl.deleteShader(fragmentShader); return; } gl.useProgram(program); // 获取属性变量位置 const a_PositionLocation = gl.getAttribLocation(program, 'a_Position'); if (a_PositionLocation === -1) { console.error('获取属性变量 a_Position 失败'); return; } // 创建缓冲区对象 const positionBuffer = gl.createBuffer(); if (!positionBuffer) { console.error('创建缓冲区对象失败'); return; } // 绑定缓冲区对象并写入顶点数据 gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); const positions = new Float32Array([0.0, 0.0, 0.0]); gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW); // 将缓冲区数据分配给属性变量 gl.enableVertexAttribArray(a_PositionLocation); gl.vertexAttribPointer(a_PositionLocation, 3, gl.FLOAT, false, 0, 0); // 设置清空颜色和清空画布 gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.clear(gl.COLOR_BUFFER_BIT); // 绘制点 gl.drawArrays(gl.POINTS, 0, 1); }

将这两个文件保存在同一个目录下,用浏览器打开 index.html,如果一切顺利,你将在黑色的画布中心看到一个红色的点。恭喜你,你已经成功编写了你的第一个 WebGL 程序!

1.3.7 总结与下一步

通过这个简单的例子,我们学习了 WebGL 程序的基本结构和流程:

  1. 获取 WebGL 上下文:连接 JavaScript 和 WebGL API。

  2. 编写顶点着色器和片元着色器:控制渲染管线的关键程序。

  3. 加载、编译和链接着色器程序:准备 WebGL 可执行的程序。

  4. 传递顶点数据:通过缓冲区和属性变量将数据传递给着色器。

  5. 清空画布和绘制:执行渲染操作,显示图形。

虽然这只是一个非常简单的程序,但它涵盖了 WebGL 编程的核心概念。在后续的学习中,你将逐步学习更复杂的着色器编程、几何图形绘制、纹理贴图、光照模型、动画效果等,从而构建更丰富、更强大的 WebGL 应用。

下一步的探索方向:

  • 绘制三角形:尝试修改顶点数据和 gl.drawArrays() 的绘制模式,绘制一个三角形。

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U