第一章:WebGL 基础


文档摘要

第一章:WebGL 基础 第一章:WebGL 基础 1.1 什么是 WebGL? WebGL (Web Graphics Library) 是一种在网页浏览器中渲染交互式 2D 和 3D 图形的技术标准。它允许 JavaScript 通过利用用户计算机的图形处理单元 (GPU) 来生成高性能的图形。简单来说,WebGL 让你的浏览器能够像游戏或者专业图形软件一样,流畅地展示复杂的 3D 场景和特效。 核心要点: 基于 OpenGL ES 3.0 或 2.0: WebGL 的底层是 OpenGL ES,这是一个针对嵌入式和移动设备的精简版 OpenGL 标准。这意味着 WebGL 继承了 OpenGL 强大的图形渲染能力,同时针对 Web 环境进行了优化。

第一章:WebGL 基础

第一章:WebGL 基础

1.1 什么是 WebGL?

WebGL (Web Graphics Library) 是一种在网页浏览器中渲染交互式 2D 和 3D 图形的技术标准。它允许 JavaScript 通过利用用户计算机的图形处理单元 (GPU) 来生成高性能的图形。简单来说,WebGL 让你的浏览器能够像游戏或者专业图形软件一样,流畅地展示复杂的 3D 场景和特效。

核心要点:

  • 基于 OpenGL ES 3.0 或 2.0: WebGL 的底层是 OpenGL ES,这是一个针对嵌入式和移动设备的精简版 OpenGL 标准。这意味着 WebGL 继承了 OpenGL 强大的图形渲染能力,同时针对 Web 环境进行了优化。

  • 浏览器原生支持: WebGL 不需要安装任何插件,现代浏览器(Chrome, Firefox, Safari, Edge 等)都内置了 WebGL 支持。

  • 硬件加速: WebGL 利用 GPU 进行图形计算,极大地提升了渲染性能,使得在网页上实现复杂的 3D 效果成为可能。

  • JavaScript API: WebGL 提供了 JavaScript API,开发者可以使用 JavaScript 代码来控制图形渲染流程。

1.2 为什么选择 WebGL?

在 Web 开发领域,我们有多种方式来呈现图形,例如 Canvas 2D、SVG、CSS 3D 等。那么,为什么我们需要 WebGL 呢?

  • 高性能 3D 渲染: WebGL 最主要的优势在于其强大的 3D 渲染能力和高性能。对于需要展示复杂 3D 模型、场景、动画以及进行数据可视化等应用,WebGL 是最佳选择。Canvas 2D 和 SVG 更适合 2D 图形和简单的交互。

  • 硬件加速优势: 相比于 Canvas 2D 和 SVG 主要依赖 CPU 进行渲染,WebGL 利用 GPU 的并行计算能力,可以大幅度提升渲染效率,特别是在处理大量图形元素时。

  • 跨平台兼容性: 只要用户的浏览器支持 WebGL,你的 WebGL 应用就能在不同的操作系统和设备上运行,实现了良好的跨平台兼容性。

  • 丰富的生态系统: WebGL 拥有庞大的社区和丰富的资源,包括各种成熟的库和框架(例如 Three.js, Babylon.js, PlayCanvas 等),可以极大地简化 WebGL 开发流程,提高开发效率。

  • 新兴 Web 应用的需求: 随着 Web 技术的发展,越来越多的应用场景需要强大的 3D 图形能力,例如虚拟现实 (VR)、增强现实 (AR)、游戏、在线 3D 设计工具、地理信息系统 (GIS) 等,WebGL 成为了这些新兴应用的关键技术。

mermaid 图表:Web 图形技术对比

图表解释: 该图表展示了 Web 图形技术的主要分类,并突出了 WebGL 在复杂 3D 图形和硬件加速方面的优势。Canvas 2D、SVG 和 CSS 3D 更适合处理简单的 2D 或伪 3D 效果,而 WebGL 则专注于高性能的 2D 和 3D 图形渲染。

1.3 WebGL 工作原理:渲染管线

理解 WebGL 的工作原理,核心在于理解其渲染管线 (Rendering Pipeline)。渲染管线是一个将 3D 场景数据转化为屏幕上 2D 像素的流程。WebGL 的渲染管线主要包括以下几个关键阶段:

  1. 顶点着色器 (Vertex Shader):

    • 输入: 顶点数据 (例如顶点坐标、颜色、法线等)。

    • 处理: 对每个顶点进行处理,例如坐标变换 (模型视图投影变换)、光照计算等。

    • 输出: 变换后的顶点坐标、颜色、纹理坐标等,传递给下一阶段。

    • 功能: 决定了模型在空间中的位置、大小、旋转和形变。

  2. 图元装配 (Primitive Assembly):

    • 输入: 顶点着色器输出的顶点数据。

    • 处理: 将顶点按照指定的图元类型 (例如三角形、线段、点) 组合成几何图元。

    • 输出: 组装好的图元。

    • 功能: 确定了要绘制的基本图形形状。

  3. 光栅化 (Rasterization):

    • 输入: 图元装配阶段输出的图元。

    • 处理: 将几何图元转化为屏幕上的像素片段 (Fragments)。对于每个图元覆盖的像素,生成一个片段,并计算片段的屏幕坐标、深度值等信息。

    • 输出: 片段数据。

    • 功能: 将连续的几何图形转化为离散的像素点。

  4. 片段着色器 (Fragment Shader):

    • 输入: 光栅化阶段输出的片段数据。

    • 处理: 对每个片段进行处理,例如颜色计算、纹理采样、光照计算等。

    • 输出: 片段的最终颜色值。

    • 功能: 决定了每个像素最终显示的颜色。

  5. 测试与混合 (Tests and Blending):

    • 输入: 片段着色器输出的片段颜色值。

    • 处理: 进行各种测试 (例如深度测试、模板测试) 来决定片段是否可见。如果可见,则进行混合操作,将片段颜色与帧缓冲区中已有的颜色进行混合。

    • 输出: 最终像素颜色写入帧缓冲区。

    • 功能: 处理像素的可见性,实现遮挡关系和透明效果。

mermaid 图表:WebGL 渲染管线

图表解释: 该图表简洁地展示了 WebGL 渲染管线的核心阶段。顶点着色器和片段着色器是可编程的阶段,开发者需要编写 GLSL (OpenGL Shading Language) 代码来控制这两个阶段的处理逻辑。

1.4 WebGL 上下文 (Context)

要使用 WebGL,首先需要获取 WebGL 上下文。WebGL 上下文是浏览器提供的 JavaScript 对象,它代表了 WebGL 的运行环境,我们通过它来调用 WebGL 的各种 API,进行图形渲染操作。

获取 WebGL 上下文的步骤:

  1. 获取 <canvas> 元素: WebGL 渲染的内容会显示在 HTML 的 <canvas> 元素上。首先需要获取到 <canvas> 元素的 JavaScript 对象。

    <canvas id="myCanvas" width="500" height="500"></canvas>
    const canvas = document.getElementById('myCanvas');
  2. 尝试获取 WebGL 上下文: 使用 canvas.getContext('webgl')canvas.getContext('webgl2') 方法来获取 WebGL 上下文。'webgl' 获取的是 WebGL 1.0 上下文, 'webgl2' 获取的是 WebGL 2.0 上下文。WebGL 2.0 是 WebGL 1.0 的升级版,提供了更多高级功能,但并非所有浏览器都完全支持 WebGL 2.0。

    const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); // 兼容旧版本浏览器 // 或者使用 WebGL 2.0 // const gl = canvas.getContext('webgl2');
  3. 检查上下文是否获取成功: getContext() 方法如果获取上下文失败 (例如浏览器不支持 WebGL),会返回 null。因此,需要检查返回值,确保上下文获取成功。

    if (!gl) { alert('您的浏览器不支持 WebGL!'); // 或者进行降级处理,例如使用 Canvas 2D 渲染 return; } console.log('WebGL 上下文获取成功:', gl);

代码实践:获取 WebGL 上下文

<!DOCTYPE html> <html> <head> <title>WebGL 基础 - 获取上下文</title> <style> body { margin: 0; } canvas { display: block; } </style> </head> <body> <canvas id="myCanvas"></canvas> <script> const canvas = document.getElementById('myCanvas'); const gl = canvas.getContext('webgl'); if (!gl) { alert('您的浏览器不支持 WebGL!'); } else { console.log('WebGL 上下文获取成功:', gl); // 设置 canvas 尺寸 (可选,可以在 HTML 中设置) canvas.width = 500; canvas.height = 500; // 设置 WebGL 视口大小 (通常与 canvas 尺寸一致) gl.viewport(0, 0, canvas.width, canvas.height); // 设置清屏颜色 (RGBA 格式,范围 0.0 - 1.0) gl.clearColor(0.0, 0.0, 0.0, 1.0); // 黑色 // 清除颜色缓冲区 (使用清屏颜色填充 canvas) gl.clear(gl.COLOR_BUFFER_BIT); } </script> </body> </html>

代码详解:

  • HTML 部分: 创建了一个 <canvas> 元素,并设置了 id="myCanvas",方便 JavaScript 获取。

  • JavaScript 部分:

    • 获取 <canvas> 元素。

    • 使用 canvas.getContext('webgl') 获取 WebGL 上下文并赋值给变量 gl

    • 检查 gl 是否为 null,如果为 null 则表示 WebGL 上下文获取失败,弹出警告。

    • 如果获取成功,则打印成功信息到控制台。

    • canvas.width = 500; canvas.height = 500; 设置 canvas 的实际渲染尺寸。

    • gl.viewport(0, 0, canvas.width, canvas.height); 设置 WebGL 视口,视口定义了 WebGL 渲染结果在 canvas 上显示的区域。通常视口大小与 canvas 尺寸一致。

    • gl.clearColor(0.0, 0.0, 0.0, 1.0); 设置清屏颜色为黑色 (RGBA: Red, Green, Blue, Alpha,取值范围 0.0 到 1.0)。

    • gl.clear(gl.COLOR_BUFFER_BIT); 执行清屏操作,用设置的清屏颜色填充颜色缓冲区,相当于清空 canvas 内容。

运行结果: 如果浏览器支持 WebGL,并且代码没有错误,你将在控制台中看到 "WebGL 上下文获取成功: [WebGLRenderingContext 对象]",并且 canvas 区域会变成黑色 (清屏颜色)。如果浏览器不支持 WebGL,则会弹出警告框。

1.5 Shader (着色器) 基础

Shader (着色器) 是 WebGL 渲染管线中至关重要的可编程阶段。WebGL 使用 GLSL (OpenGL Shading Language) 编写着色器代码。着色器程序运行在 GPU 上,负责处理顶点和片段数据。

WebGL 中主要有两种类型的着色器:

  • 顶点着色器 (Vertex Shader): 处理顶点数据。它接收顶点属性 (例如顶点坐标、颜色、法线等) 作为输入,并输出变换后的顶点位置等数据。每个顶点都会执行一次顶点着色器。

  • 片段着色器 (Fragment Shader): 处理光栅化后的片段数据。它接收来自顶点着色器的插值数据,并输出片段的颜色值。每个像素片段都会执行一次片段着色器。

GLSL 基础语法简介:

GLSL 语法类似于 C 语言,但针对图形编程进行了扩展。

  • 变量类型: float, vec2, vec3, vec4 (浮点数和浮点向量), int, ivec2, ivec3, ivec4 (整数和整数向量), bool, bvec2, bvec3, bvec4 (布尔值和布尔向量), mat2, mat3, mat4 (矩阵), sampler2D (2D 纹理采样器) 等。

  • 限定符:

    • attribute: 顶点着色器输入属性变量,从缓冲区接收顶点数据。

    • uniform: 全局 uniform 变量,在着色器程序执行期间保持不变,通常用于传递变换矩阵、颜色、光照参数等。

    • varying: 顶点着色器输出,片段着色器输入变量,用于在顶点着色器和片段着色器之间传递数据,并进行插值。

  • 内置函数: GLSL 提供了丰富的内置函数,用于向量和矩阵运算、数学运算、纹理采样、几何运算等。例如 vec4(r, g, b, a), normalize(), dot(), cross(), texture2D() 等。

  • main 函数: 每个着色器程序都必须有一个 main 函数,作为程序的入口点。

代码实践:简单的顶点着色器和片段着色器

顶点着色器 (vertexShader.glsl):

attribute vec4 a_position; // 顶点坐标属性 void main() { gl_Position = a_position; // 将顶点坐标赋值给 gl_Position (内置变量,表示顶点最终位置) }

片段着色器 (fragmentShader.glsl):

precision mediump float; // 设置浮点数精度为 mediump (中等精度) void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 设置片段颜色为红色 (RGBA: 1.0, 0.0, 0.0, 1.0) }

JavaScript 代码 (结合 HTML 代码):

<!DOCTYPE html> <html> <head> <title>WebGL 基础 - Shader</title> <style> body { margin: 0; } canvas { display: block; } </style> </head> <body> <canvas id="myCanvas"></canvas> <script> const canvas = document.getElementById('myCanvas'); const gl = canvas.getContext('webgl'); if (!gl) { alert('您的浏览器不支持 WebGL!'); return; } canvas.width = 500; canvas.height = 500; gl.viewport(0, 0, canvas.width, canvas.height); gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.clear(gl.COLOR_BUFFER_BIT); // **1. 创建 Shader 对象** const vertexShader = gl.createShader(gl.VERTEX_SHADER); const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); // **2. 设置 Shader 源码** const vertexShaderSource = ` attribute vec4 a_position; void main() { gl_Position = a_position; } `; const fragmentShaderSource = ` precision mediump float; void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); } `; gl.shaderSource(vertexShader, vertexShaderSource); gl.shaderSource(fragmentShader, fragmentShaderSource); // **3. 编译 Shader** gl.compileShader(vertexShader); gl.compileShader(fragmentShader); // **4. 检查 Shader 编译状态** if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) { console.error('顶点着色器编译错误:', gl.getShaderInfoLog(vertexShader)); gl.deleteShader(vertexShader); return; } if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) { console.error('片段着色器编译错误:', gl.getShaderInfoLog(fragmentShader)); gl.deleteShader(fragmentShader); return; } // **5. 创建 Program 对象** const program = gl.createProgram(); // **6. 关联 Shader 到 Program** gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); // **7. 链接 Program** gl.linkProgram(program); // **8. 检查 Program 链接状态** if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { console.error('Program 链接错误:', gl.getProgramInfoLog(program)); gl.deleteProgram(program); gl.deleteShader(vertexShader); gl.deleteShader(fragmentShader); return; } // **9. 使用 Program** gl.useProgram(program); // **10. 获取 attribute 变量 "a_position" 的位置** const positionAttributeLocation = gl.getAttribLocation(program, 'a_position'); // **11. 创建 Buffer 对象** const positionBuffer = gl.createBuffer(); // **12. 绑定 Buffer 到 ARRAY_BUFFER 目标** gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); // **13. 定义顶点数据 (一个三角形的三个顶点坐标)** const positions = [ 0, 0, // 第一个顶点 0, 0.5, // 第二个顶点 0.7, 0, // 第三个顶点 ]; // **14. 将顶点数据写入 Buffer** gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW); // **15. 启用 attribute 属性 "a_position"** gl.enableVertexAttribArray(positionAttributeLocation); // **16. 设置 attribute 属性 "a_position" 的数据读取方式** gl.vertexAttribPointer( positionAttributeLocation, // attribute 位置 2, // 每个顶点数据的大小 (2 个分量: x, y) gl.FLOAT, // 数据类型为浮点数 false, // 是否需要归一化 (不需要) 0, // 步长 (0 表示紧密排列) 0 // 偏移量 (从缓冲区起始位置开始读取) ); // **17. 绘制三角形** gl.drawArrays(gl.TRIANGLES, 0, 3); // 绘制三角形,从第 0 个顶点开始,绘制 3 个顶点 </script> </body> </html>

代码详解:

  • Shader 代码部分:

    • vertexShader.glsl 定义了一个简单的顶点着色器,它接收 a_position 属性 (顶点坐标),并将它直接赋值给 gl_Position,实现顶点坐标的直接传递,不做任何变换。

    • fragmentShader.glsl 定义了一个简单的片段着色器,它将所有片段的颜色设置为红色。

  • JavaScript 代码部分:

    • Shader 创建、编译、检查: 代码创建了顶点着色器和片段着色器对象,读取 Shader 源码,编译 Shader,并检查编译是否出错。如果出错,会在控制台输出错误信息并停止程序。

    • Program 创建、关联 Shader、链接、检查: 创建 Program 对象,将编译好的顶点着色器和片段着色器关联到 Program,链接 Program,并检查链接是否出错。Program 是 Shader 的容器,WebGL 程序需要使用 Program 来进行渲染。

    • 使用 Program: gl.useProgram(program); 激活 Program,告诉 WebGL 使用当前 Program 进行渲染。

    • 获取 attribute 位置: gl.getAttribLocation(program, 'a_position'); 获取顶点着色器中 a_position 属性变量的位置索引。

    • Buffer 创建、绑定、数据写入: 创建 Buffer 对象,绑定 Buffer 到 gl.ARRAY_BUFFER 目标,定义顶点坐标数据 positions,并将数据写入 Buffer。Buffer 用于存储顶点数据。

    • 启用 attribute 属性、设置数据读取方式: gl.enableVertexAttribArray(positionAttributeLocation); 启用 a_position 属性。gl.vertexAttribPointer(...) 设置 WebGL 如何从 Buffer 中读取数据并传递给顶点着色器的 a_position 属性。

    • 绘制三角形: gl.drawArrays(gl.TRIANGLES, 0, 3); 使用 gl.TRIANGLES 图元类型绘制三角形,从第 0 个顶点开始,绘制 3 个顶点。

运行结果: 如果代码运行成功,你将在 canvas 上看到一个红色的三角形。

mermaid 图表:Shader Program 流程

图表解释: 该图表展示了创建和使用 Shader Program 的详细流程,包括 Shader 对象的创建、源码设置、编译、编译状态检查、Program 对象的创建、Shader 关联、Program 链接和链接状态检查等步骤。

1.6 Buffer 和 Attribute

在 WebGL 中,我们需要将顶点数据 (例如顶点坐标、颜色、法线等) 存储在 Buffer (缓冲区) 对象中,然后通过 Attribute (属性) 变量将 Buffer 中的数据传递给顶点着色器。

  • Buffer (缓冲区): WebGL Buffer 是 GPU 内存中的一块区域,用于存储顶点数据、索引数据等。Buffer 可以提高数据传输和访问效率,因为数据直接存储在 GPU 内存中,减少了 CPU 和 GPU 之间的数据传输开销。

  • Attribute (属性): Attribute 是顶点着色器中的输入变量,用于接收来自 Buffer 的顶点数据。每个 attribute 变量都关联到一个 Buffer,WebGL 会从 Buffer 中读取数据,并将数据传递给顶点着色器中对应的 attribute 变量。

Buffer 的创建、绑定和数据写入:

  1. 创建 Buffer 对象: 使用 gl.createBuffer() 创建一个 Buffer 对象。

  2. 绑定 Buffer: 使用 gl.bindBuffer(target, buffer) 将 Buffer 对象绑定到指定的 目标 (target)。常用的目标有:

    • gl.ARRAY_BUFFER: 用于存储顶点属性数据 (例如顶点坐标、颜色、法线等)。

    • gl.ELEMENT_ARRAY_BUFFER: 用于存储索引数据 (用于索引绘制)。

  3. 写入数据到 Buffer: 使用 gl.bufferData(target, data, usage) 将数据写入当前绑定的 Buffer。

    • target: Buffer 目标 (与 bindBuffer 中的 target 保持一致)。

    • data: 要写入的数据,通常是 Float32Array (浮点数数组) 或 Uint16Array (无符号 16 位整数数组) 等类型化数组。

    • usage: 指定数据的使用方式,影响 WebGL 如何优化 Buffer 存储和访问。常用的 usage 值:

      • gl.STATIC_DRAW: 数据将被写入一次,多次绘制使用 (静态数据,例如模型顶点数据)。

      • gl.DYNAMIC_DRAW: 数据会被频繁修改和绘制 (动态数据,例如粒子系统顶点数据)。

      • gl.STREAM_DRAW: 数据每次绘制都会更新 (流式数据)。

Attribute 的启用和数据读取方式设置:

  1. 获取 attribute 位置: 使用 gl.getAttribLocation(program, attributeName) 获取顶点着色器中 attribute 变量的位置索引。

  2. 启用 attribute 属性: 使用 gl.enableVertexAttribArray(location) 启用指定位置的 attribute 属性。

  3. 设置 attribute 数据读取方式: 使用 gl.vertexAttribPointer(location, size, type, normalized, stride, offset) 设置 WebGL 如何从绑定的 gl.ARRAY_BUFFER 中读取数据并传递给 attribute 变量。

    • location: attribute 位置索引 (从 getAttribLocation 获取)。

    • size: 每个顶点 attribute 数据的分量个数 (例如顶点坐标是 vec2 或 vec3,则 size 为 2 或 3)。

    • type: 数据类型,例如 gl.FLOAT, gl.BYTE, gl.UNSIGNED_BYTE, gl.SHORT, gl.UNSIGNED_SHORT 等。

    • normalized: 是否需要将数据归一化到 [0, 1] 或 [-1, 1] 范围 (通常为 false)。

    • stride: 步长,即每个顶点 attribute 数据在 Buffer 中的字节数间隔。如果数据紧密排列,可以设置为 0。

    • offset: 偏移量,即第一个顶点 attribute 数据在 Buffer 中的起始字节偏移量。

代码实践:使用 Buffer 和 Attribute 绘制三角形 (与 Shader 代码示例结合)

在之前的 Shader 代码示例中,JavaScript 代码已经包含了 Buffer 和 Attribute 的使用,主要代码段如下:

// ... (Shader 创建、编译、Program 创建、链接、使用等代码) ... // **10. 获取 attribute 变量 "a_position" 的位置** const positionAttributeLocation = gl.getAttribLocation(program, 'a_position'); // **11. 创建 Buffer 对象** const positionBuffer = gl.createBuffer(); // **12. 绑定 Buffer 到 ARRAY_BUFFER 目标** gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); // **13. 定义顶点数据 (一个三角形的三个顶点坐标)** const positions = [ 0, 0, // 第一个顶点 0, 0.5, // 第二个顶点 0.7, 0, // 第三个顶点 ]; // **14. 将顶点数据写入 Buffer** gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW); // **15. 启用 attribute 属性 "a_position"** gl.enableVertexAttribArray(positionAttributeLocation); // **16. 设置 attribute 属性 "a_position" 的数据读取方式** gl.vertexAttribPointer( positionAttributeLocation, // attribute 位置 2, // 每个顶点数据的大小 (2 个分量: x, y) gl.FLOAT, // 数据类型为浮点数 false, // 是否需要归一化 (不需要) 0, // 步长 (0 表示紧密排列) 0 // 偏移量 (从缓冲区起始位置开始读取) ); // **17. 绘制三角形** gl.drawArrays(gl.TRIANGLES, 0, 3); // 绘制三角形,从第 0 个顶点开始,绘制 3 个顶点

代码详解:

  • 代码首先获取顶点着色器中 a_position 属性的位置。

  • 创建一个 Buffer 对象 positionBuffer

  • positionBuffer 绑定到 gl.ARRAY_BUFFER 目标。

  • 定义顶点坐标数据 positions (一个三角形的三个顶点,每个顶点包含 x, y 两个坐标分量)。

  • 使用 gl.bufferDatapositions 数据写入 positionBuffer

  • 启用 a_position 属性。

  • 使用 gl.vertexAttribPointer 设置 a_position 属性的数据读取方式:每个顶点包含 2 个浮点数分量,数据类型为 gl.FLOAT,紧密排列,从 Buffer 起始位置开始读取。


发布者: 作者: 转发
评论区 (0)
U