WebGL - 快速指南
WebGL - 快速指南
Section titled “WebGL - 快速指南”本指南提供 WebGL 概念和基础知识的快速概述。
WebGL - 介绍
Section titled “WebGL - 介绍”历史上,在 Web 上渲染 3D 图形需要浏览器插件,如 Java applets(使用 JOGL)或 Adobe Flash(使用 Stage3D)。这些方法面临兼容性、安全性和对专有技术的依赖等挑战。
为了提供一种标准化的、无需插件的方式来直接从 Web 浏览器访问 GPU(图形处理器),WebGL 应运而生。
OpenGL 及相关技术
Section titled “OpenGL 及相关技术”OpenGL(Open Graphics Library)是一个历史悠久的跨平台 API,用于渲染 2D 和 3D 图形。WebGL 直接基于专为嵌入式系统设计的特定版本的 OpenGL。
| API/技术 | 描述 |
|---|---|
| OpenGL ES | OpenGL 的一个子集,专为移动电话、游戏机、车辆等嵌入式系统量身定制。WebGL 1.0 基于 OpenGL ES 2.0,WebGL 2.0 基于 OpenGL ES 3.0。由 Khronos Group (www.khronos.org) 维护。 |
| WebGL | 一个 JavaScript API,通过 HTML <canvas> 元素向 Web 开发人员公开 OpenGL ES 功能。允许在兼容的 Web 浏览器中直接进行硬件加速图形渲染。WebGL 1.0 和 WebGL 2.0 是当前标准,由 Khronos Group 维护。 |
| GLSL | OpenGL 着色语言(OpenGL Shading Language)。一种类似于 C 的语言,用于编写着色器(shaders)——直接在 GPU 上运行的小程序,用于控制顶点位置和像素颜色。WebGL 使用 GLSL ES(适用于嵌入式系统的 GLSL)。 |
什么是 WebGL?
Section titled “什么是 WebGL?”WebGL(Web Graphics Library)是一个 JavaScript API,用于在任何兼容的 Web 浏览器中渲染交互式 2D 和 3D 图形,无需使用插件。它通过 HTML <canvas> 元素提供对设备 GPU 的低级别访问来实现。
WebGL 与 HTML、CSS 和 JavaScript 等其他 Web 标准无缝集成,从而能够在 Web 应用程序中实现丰富的图形体验。
谁开发了 WebGL?
Section titled “谁开发了 WebGL?”最初的工作始于 2006 年 Mozilla 的 Vladimir Vukićević,他进行了一项将 OpenGL 移植到 Canvas 元素的实验。这项工作后来演变成 Khronos Group 下的一个合作项目,涉及苹果、谷歌、微软和 Mozilla 等主要浏览器厂商。WebGL 1.0 于 2011 年 3 月正式发布。
WebGL 在现代桌面和移动浏览器中享有广泛的支持。
桌面浏览器(截至 2023 年末/2024 年初)
Section titled “桌面浏览器(截至 2023 年末/2024 年初)”| 浏览器 | 支持状态 |
|---|---|
| Google Chrome | 是 (WebGL 1 & 2) |
| Mozilla Firefox | 是 (WebGL 1 & 2) |
| Apple Safari | 是 (WebGL 1 & 2) |
| Microsoft Edge | 是 (WebGL 1 & 2, 基于 Chromium) |
| Opera | 是 (WebGL 1 & 2, 基于 Chromium) |
移动浏览器(截至 2023 年末/2024 年初)
Section titled “移动浏览器(截至 2023 年末/2024 年初)”| 浏览器 | 支持状态 |
|---|---|
| Android 版 Chrome | 是 (WebGL 1 & 2) |
| iOS 版 Safari | 是 (WebGL 1 & 2) |
| Android 版 Firefox | 是 (WebGL 1 & 2) |
| Samsung Internet | 是 (WebGL 1 & 2) |
注意:具体功能支持和性能可能有所不同。请查看 caniuse.com (https://caniuse.com/?search=webgl) 等资源获取详细、最新的信息。
WebGL 的优势
Section titled “WebGL 的优势”- 无需插件:在浏览器中原生运行,无需外部插件。
- 硬件加速:利用 GPU 进行高性能图形渲染。
- 跨平台:在兼容浏览器支持的各种操作系统和设备上工作。
- JavaScript 集成:与标准 Web 技术(HTML、CSS、DOM、其他 JS 库)无缝集成。
- 开放标准:由 Khronos Group 开发和维护,确保互操作性。
- 不断发展的生态系统:由众多简化开发的库和框架(如 Three.js、Babylon.js)支持。
除了支持 WebGL 的现代 Web 浏览器和用于编写代码的文本编辑器外,无需特殊设置。浏览器开发者工具对于调试至关重要。
WebGL - 基本图形概念
Section titled “WebGL - 基本图形概念”理解核心计算机图形概念是使用 WebGL 的基础。
渲染 (Rendering)
Section titled “渲染 (Rendering)”渲染是指使用计算机程序从 2D 或 3D 模型描述生成 2D 图像的过程。此描述包含几何体(形状)、视点(相机)、纹理(表面外观)、光照和着色等信息。渲染管线接收此场景描述并生成最终用于显示的像素数据。
渲染类型:
- 软件渲染(Software Rendering):计算主要由 CPU 执行。对于复杂场景通常较慢。
- 硬件渲染(Hardware Rendering):计算被卸载到专用的图形处理器(GPU)。WebGL 利用硬件渲染来实现高性能。
WebGL 执行客户端硬件渲染。浏览器使用用户的 GPU 处理通过 JavaScript API 发送的图形指令。
GPU (Graphics Processing Unit)
Section titled “GPU (Graphics Processing Unit)”GPU 是一种专用的电子电路,设计用于快速操作和更改内存,以加速在帧缓冲区中创建图像,用于输出到显示设备。GPU 具有高度并行性,这意味着它们可以同时执行许多计算,这使其非常适合图形渲染中涉及的重复性任务(例如处理数千个顶点或数百万个像素)。
与 WebGL 相关的 GPU 关键特性:
- 并行处理:并发执行许多小程序(着色器)实例。
- 专用内存(VRAM):拥有自己的高速内存,用于存储图形数据(纹理、顶点数据)。数据必须从主系统 RAM 传输到 VRAM,GPU 才能高效访问。
- 专用管线:硬件针对特定的图形任务进行了优化,如变换、裁剪、光栅化和纹理映射。
GPU 加速计算
Section titled “GPU 加速计算”在 WebGL 中,GPU 加速意味着浏览器将计算密集型图形任务(由你的 JavaScript 代码和 GLSL 着色器定义)卸载到 GPU。CPU 处理主应用程序逻辑,准备数据,并通过 WebGL API 向 GPU 发送绘制命令。
高效的 WebGL 开发涉及最大程度地减少 CPU 和 GPU 之间的通信开销。这通常意味着在初始化期间一次性将数据(顶点、纹理)上传到 GPU 内存(缓冲区),然后可以在多个绘制命令中引用这些数据,而不是为每一帧发送新数据。
Html5 Canvas 概述
Section titled “Html5 Canvas 概述”HTML5 <canvas> 元素在 HTML 文档中提供了一个绘图表面。然后 JavaScript 可以访问此表面以动态绘制图形。WebGL 使用 <canvas> 元素作为其渲染目标。
HTML5 Canvas 元素
Section titled “HTML5 Canvas 元素”<canvas> 元素本身很简单,主要由其 width 和 height 属性定义,这些属性指定绘图缓冲区的尺寸(以像素为单位)。
<canvas id="myCanvas" width="640" height="480"> <!-- Fallback content for browsers that don't support canvas --> 您的浏览器不支持 HTML canvas 元素。</canvas>- id:用于通过 JavaScript 访问元素的唯一标识符(例如,使用
document.getElementById)。 - width:canvas 绘图表面的宽度,以 CSS 像素为单位。
- height:canvas 绘图表面的高度,以 CSS 像素为单位。
重要:通常建议直接在 <canvas> 标签上设置 width 和 height 属性,而不是使用 CSS。通过 CSS 设置尺寸会缩放 canvas 元素及其内容,可能导致模糊,而设置属性会定义绘图缓冲区的实际大小。
示例 - 创建 Canvas
Section titled “示例 - 创建 Canvas”此 HTML 创建一个 300x300 的 canvas,并带有红色边框以便查看。
<!DOCTYPE html><html><head> <title>Canvas Example</title> <style> #myRenderingCanvas { border: 1px solid red; } </style></head><body> <canvas id="myRenderingCanvas" width="300" height="300"></canvas></body></html>这将显示一个带有红色边框的空白 300x300 正方形框。
渲染上下文 (Rendering Contexts)
Section titled “渲染上下文 (Rendering Contexts)”<canvas> 元素只是一个容器。要在其上绘图,你需要使用 JavaScript 中的 getContext() 方法获取渲染上下文。
常见上下文类型:
- ‘2d’:提供 CanvasRenderingContext2D 接口,用于绘制 2D 形状、文本和图像。
- ‘webgl’:提供 WebGLRenderingContext 接口(基于 OpenGL ES 2.0),用于硬件加速的 2D 和 3D 图形。
- ‘webgl2’:提供 WebGL2RenderingContext 接口(基于 OpenGL ES 3.0),提供更高级的功能。
示例 - 获取 2D 上下文:
Section titled “示例 - 获取 2D 上下文:”const canvas = document.getElementById('myRenderingCanvas');const ctx = canvas.getContext('2d');
if (ctx) { ctx.fillStyle = 'green'; ctx.fillRect(10, 10, 150, 100); // Draw a green rectangle} else { console.error('2D context not supported');}WebGL 上下文
Section titled “WebGL 上下文”要使用 WebGL,你需要请求 'webgl' 或 'webgl2' 上下文。检查是否成功获取上下文是一个好习惯,因为 WebGL 可能不受支持或未启用。
const canvas = document.getElementById('myRenderingCanvas');// Try getting WebGL 2, fallback to WebGL 1const gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
if (!gl) { alert('Unable to initialize WebGL. Your browser or machine may not support it.'); // Handle the error appropriately} else { // WebGL context obtained successfully! Start setting up. gl.clearColor(0.9, 0.9, 0.8, 1.0); // Set clear color to light gray gl.clear(gl.COLOR_BUFFER_BIT); // Clear the canvas console.log('WebGL initialized successfully!');}获取 WebGL 上下文后(通常存储在名为 gl 的变量中),你可以使用其方法来配置渲染管线、创建缓冲区、编译着色器和绘制图形。
有关 2D Canvas API 的更多详细信息,请参阅 MDN Web Docs:https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API
WebGL - 基础
Section titled “WebGL - 基础”WebGL 提供了基于 OpenGL ES 的低级 API。理解其坐标系以及顶点、缓冲区和着色器等基本概念至关重要。
WebGL 坐标系(裁剪空间)
Section titled “WebGL 坐标系(裁剪空间)”顶点处理后,WebGL 要求坐标位于一个称为**裁剪空间(Clip Space)**的系统中。这是一个在所有三个轴(X、Y、Z)上范围从 -1 到 +1 的立方体。
- X 轴:+1 向右,-1 向左。
- Y 轴:+1 向上,-1 向下。
- Z 轴:+1 朝向观察者(更近),-1 远离观察者(更远)。如果你期望 Z 随着深度增加而增加,这可能看起来违反直觉,但这是 OpenGL/WebGL 裁剪空间中的标准约定。
在此 [-1, +1] 立方体之外的任何维度的任何内容都将被“裁剪”(丢弃)而不予渲染。你的顶点着色器主要职责是将模型的原始坐标(通常称为模型空间或世界空间)变换到此裁剪空间,通常通过将其与模型、视图和投影矩阵相乘。
轴的文本表示:
+Y | |-X ----+---- +X /| / | +Z -Y (Towards Viewer)WebGL 基础
Section titled “WebGL 基础”WebGL 编程中的关键术语和概念:
顶点 (Vertices)
Section titled “顶点 (Vertices)”顶点(单数:vertex,复数:vertices)是 2D 或 3D 空间中的一个点,通常由其坐标(X、Y、Z)定义。顶点是几何形状(如三角形、线条、点)的基本构建块。顶点还可以具有关联的数据,如颜色、纹理坐标或法线(指示表面方向的向量,用于光照)。
示例(三角形的顶点):
Section titled “示例(三角形的顶点):”// Data for three vertices (X, Y, Z)const triangleVertices = [ 0.0, 0.5, 0.0, // Top vertex -0.5, -0.5, 0.0, // Bottom-left vertex 0.5, -0.5, 0.0 // Bottom-right vertex];索引(索引缓冲区)(Indices (Index Buffer))
Section titled “索引(索引缓冲区)(Indices (Index Buffer))”通常,网格中的多个多边形共享相同的顶点。为避免重复顶点数据,我们可以使用索引缓冲区(index buffer)。我们在顶点缓冲区中定义每个唯一的顶点一次。然后,索引缓冲区包含一个整数列表(索引),指定从顶点缓冲区中绘制顶点的顺序,以形成基本图元(primitive)(如三角形)。这节省了内存并可以提高性能。
示例(使用 4 个顶点的四边形的索引):
Section titled “示例(使用 4 个顶点的四边形的索引):”const quadVertices = [ -0.5, 0.5, 0.0, // Vertex 0: Top-left -0.5, -0.5, 0.0, // Vertex 1: Bottom-left 0.5, -0.5, 0.0, // Vertex 2: Bottom-right 0.5, 0.5, 0.0 // Vertex 3: Top-right];
// Indices define two triangles (0,1,2) and (0,2,3) to form the quadconst quadIndices = [ 0, 1, 2, // First triangle 0, 2, 3 // Second triangle];类型化数组 (Typed Arrays)
Section titled “类型化数组 (Typed Arrays)”WebGL 要求几何数据(顶点、索引、颜色等)以类型化数组(Typed Arrays)(例如,Float32Array、Uint16Array)形式传递。这些是 JavaScript 对象,提供了访问原始二进制数据的机制,这对于高效传输到 GPU 至关重要。
const vertices = new Float32Array([ 0.0, 0.5, 0.0, -0.5, -0.5, 0.0, 0.5, -0.5, 0.0 ]);const indices = new Uint16Array([ 0, 1, 2 ]);缓冲区(VBO 和 IBO)(Buffers (VBOs and IBOs))
Section titled “缓冲区(VBO 和 IBO)(Buffers (VBOs and IBOs))”WebGL 缓冲区是 GPU 管理的内存块,用于存储大量图形数据。JavaScript 类型化数组中的数据被复制到这些缓冲区中。
- **顶点缓冲区对象(Vertex Buffer Object,VBO):**存储每个顶点的数据,如位置、颜色、纹理坐标、法线。数据通常绑定到
gl.ARRAY_BUFFER目标。 - **索引缓冲区对象(Index Buffer Object,IBO):**存储顶点索引,用于与
gl.drawElements一起使用。数据通常绑定到gl.ELEMENT_ARRAY_BUFFER目标。 - **帧缓冲区对象(Frame Buffer Object,FBO):**表示渲染目标(颜色缓冲区、深度缓冲区、模板缓冲区)的集合。默认情况下,WebGL 绘制到 canvas 的主帧缓冲区,但 FBO 允许进行离屏渲染,这对于后处理或反射等效果非常有用。
使用缓冲区对于性能至关重要,因为它最大限度地减少了 CPU (JavaScript) 和 GPU 之间的数据传输。通常在初始化期间上传数据一次。
创建和填充缓冲区的步骤:
Section titled “创建和填充缓冲区的步骤:”1. 创建一个空缓冲区:
Section titled “1. 创建一个空缓冲区:”使用 gl.createBuffer() 请求 WebGL 分配一个缓冲区对象。
const vertexBuffer = gl.createBuffer();const indexBuffer = gl.createBuffer();2. 绑定缓冲区:
Section titled “2. 绑定缓冲区:”使用 gl.bindBuffer(target, buffer) 将缓冲区在特定目标绑定点上设为“活动”。随后对该目标进行的缓冲区操作将影响绑定的缓冲区。
target:对于顶点数据,为gl.ARRAY_BUFFER;对于索引数据,为gl.ELEMENT_ARRAY_BUFFER。buffer:由gl.createBuffer()返回的缓冲区对象。
// Bind the VBOgl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
// Bind the IBOgl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);3. 将数据传输到缓冲区:
Section titled “3. 将数据传输到缓冲区:”使用 gl.bufferData(target, data, usage) 将数据从 JavaScript 类型化数组复制到当前绑定到 GPU 的缓冲区。
target:与bindBuffer中的相同(gl.ARRAY_BUFFER或gl.ELEMENT_ARRAY_BUFFER)。data:包含数据的类型化数组(例如,new Float32Array(...))。usage:一个提示,告诉 WebGL 你打算如何使用这些数据。这有助于驱动程序优化内存放置。常见值:-
gl.STATIC_DRAW:数据设置一次,多次使用(例如,静态模型几何体)。最常见。
-
gl.DYNAMIC_DRAW:数据频繁更新,多次使用(例如,动画角色顶点)。
-
gl.STREAM_DRAW:数据设置一次,仅使用几次。
// Assuming 'vertices' is a Float32Array and 'indices' is a Uint16Array
// Transfer vertex data to the bound ARRAY_BUFFER (vertexBuffer)gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// Transfer index data to the bound ELEMENT_ARRAY_BUFFER (indexBuffer)gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);4. 解绑缓冲区(可选但良好的实践):
Section titled “4. 解绑缓冲区(可选但良好的实践):”你可以通过将 null 绑定到目标来解绑缓冲区:gl.bindBuffer(gl.ARRAY_BUFFER, null);。这可以防止以后意外修改缓冲区。
gl.bindBuffer(gl.ARRAY_BUFFER, null);gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);数据进入缓冲区后,你需要使用属性(attributes)(下一节介绍)告诉着色器如何访问这些数据。
缓冲区相关 WebGL 方法总结:
Section titled “缓冲区相关 WebGL 方法总结:”| 序号 | 方法 | 描述 |
|---|---|---|
| 1 | createBuffer() | 创建一个新的、空的 WebGL 缓冲区对象。 |
| 2 | deleteBuffer(buffer) | 删除一个 WebGL 缓冲区对象,释放 GPU 内存。 |
| 3 | bindBuffer(target, buffer) | 将一个缓冲区对象绑定到一个目标(gl.ARRAY_BUFFER 或 gl.ELEMENT_ARRAY_BUFFER)。 |
| 4 | bufferData(target, data, usage) | 将数据从类型化数组(data)复制到当前绑定到 target 的缓冲区。指定 usage 提示。 |
| 5 | bufferSubData(target, offset, data) | 更新现有缓冲区的一部分数据,而无需重新分配整个缓冲区。 |
| 6 | isBuffer(buffer) | 如果提供的对象是有效的 WebGL 缓冲区,则返回 true。 |
| 7 | getBufferParameter(target, pname) | 检索关于绑定到 target 的缓冲区的信息(例如,pname = gl.BUFFER_SIZE)。 |
WebGL - 着色器
Section titled “WebGL - 着色器”着色器是 WebGL 中可编程图形的核心。它们是使用 GLSL ES 编写的在 GPU 上运行的小程序,用于定义顶点变换和像素颜色。
GLSL ES 基础
Section titled “GLSL ES 基础”GLSL ES(OpenGL Shading Language for Embedded Systems)是一种类似于 C 的语言,具有专为图形处理定制的功能。
基本数据类型:
Section titled “基本数据类型:”| 序号 | 类型 | 描述 |
|---|---|---|
| 1 | void | 表示无值(用于函数返回类型)。 |
| 2 | bool | 布尔值(true 或 false)。 |
| 3 | int | 有符号整数。 |
| 4 | float | 浮点数(需要指定精度)。 |
| 5 | vec2, vec3, vec4 | 具有 2、3 或 4 个分量的浮点向量(例如,用于位置、颜色、纹理坐标)。 |
| 6 | bvec2, bvec3, bvec4 | 布尔向量。 |
| 7 | ivec2, ivec3, ivec4 | 有符号整数向量。 |
| 8 | mat2, mat3, mat4 | 2x2、3x3、4x4 浮点矩阵(用于变换)。 |
| 9 | sampler2D | 表示一个 2D 纹理单元,用于访问纹理数据。 |
| 10 | samplerCube | 表示一个立方体贴图纹理单元。 |
存储限定符(WebGL 1 / GLSL ES 1.00):
Section titled “存储限定符(WebGL 1 / GLSL ES 1.00):”| 序号 | 限定符 | 描述 |
|---|---|---|
| 1 | attribute | 顶点着色器输入。链接来自 WebGL 缓冲区的每个顶点的数据。值随每个顶点调用而变化。 |
| 2 | uniform | 顶点/片段着色器输入。链接来自 JavaScript 的绘制调用中保持不变的数据(gl.uniform*)。在着色器中是只读的。示例:uniform mat4 uProjectionMatrix; |
| 3 | varying | 链接从顶点着色器输出传递到片段着色器输入的数据。值在图元表面上进行插值。示例:varying vec3 vColor; |
| 4 | const | 在着色器中声明一个编译时常量变量。 |
精度限定符:
Section titled “精度限定符:”指定 float 和 int 类型的计算精度:highp、mediump 和 lowp。片段着色器中强制要求默认浮点精度(precision mediump float;)。可以按变量指定精度。
// Fragment Shader Snippetprecision mediump float;
varying vec3 vColor; // Interpolated color from vertex shader
void main() { gl_FragColor = vec4(vColor, 1.0);}GLSL 提供特殊的内置变量,用于与关键管线交互:
- 顶点着色器输出:
-
gl_Position(highp vec4):必须写入。裁剪空间中的最终顶点位置。
-
gl_PointSize(mediump float):可选。设置以gl.POINTS绘制的图元的大小(以像素为单位)。
- 片段着色器输入/输出:
-
gl_FragCoord(mediump vec4):只读输入。包含片段的窗口相关坐标 (x, y, z, 1/w)。
-
gl_FrontFacing(bool):只读输入。如果片段属于正面图元,则为 True(对于双面光照有用)。
-
gl_PointCoord(mediump vec2):只读输入。包含点图元内的坐标(对于纹理映射点有用)。
-
gl_FragColor(mediump vec4):在 WebGL 1 中必须写入。片段的最终 RGBA 输出颜色。
-
gl_FragData[n](mediump vec4):输出数组,用于 WebGL 1 中的多重渲染目标(MRT)扩展 / WebGL 2 中的标准功能。
顶点着色器示例(简单位置)
Section titled “顶点着色器示例(简单位置)”// GLSL ES 1.00 Vertex Shaderattribute vec3 aVertexPosition; // Input: Per-vertex positionuniform mat4 uModelViewMatrix; // Input: Model-view transformuniform mat4 uProjectionMatrix; // Input: Projection transform
varying lowp vec4 vColor; // Output: Pass color to fragment shader
void main(void) { // Calculate final clip space position gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aVertexPosition, 1.0);
// Example: Set a fixed color to pass (could also be an attribute) vColor = vec4(0.5, 0.0, 0.5, 1.0); // Purple}片段着色器示例(简单颜色)
Section titled “片段着色器示例(简单颜色)”// GLSL ES 1.00 Fragment Shaderprecision mediump float; // Mandatory precision for floats
varying lowp vec4 vColor; // Input: Interpolated color from vertex shader
void main(void) { // Output the interpolated color gl_FragColor = vColor;}在 JavaScript 中编译和链接着色器
Section titled “在 JavaScript 中编译和链接着色器”你需要 JavaScript 代码来获取 GLSL 字符串,将其发送到 GPU 进行编译,然后将它们链接到一个可用的 WebGL 程序。
- **1. 创建着色器对象:**使用
gl.createShader(type),其中type是gl.VERTEX_SHADER或gl.FRAGMENT_SHADER。 - **2. 附加源代码:**使用
gl.shaderSource(shader, sourceString)提供 GLSL 代码。 - **3. 编译着色器:**使用
gl.compileShader(shader)。 - **4. 检查编译状态:**使用
gl.getShaderParameter(shader, gl.COMPILE_STATUS)和gl.getShaderInfoLog(shader)检查错误。这对于调试至关重要! - **5. 创建程序对象:**使用
gl.createProgram()。 - **6. 附加着色器:**使用
gl.attachShader(program, vertexShader)和gl.attachShader(program, fragmentShader)。 - **7. 链接程序:**使用
gl.linkProgram(program)。 - **8. 检查链接状态:**使用
gl.getProgramParameter(program, gl.LINK_STATUS)和gl.getProgramInfoLog(program)检查错误。 - **9. 使用程序:**在绘制前使用
gl.useProgram(program)激活此着色器程序。
示例(来自示例应用程序的辅助函数):
Section titled “示例(来自示例应用程序的辅助函数):”// Function to load and compile a single shaderfunction loadShader(gl, type, source) { const shader = gl.createShader(type); gl.shaderSource(shader, source); gl.compileShader(shader); if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { console.error(`Error compiling ${type === gl.VERTEX_SHADER ? 'vertex' : 'fragment'} shader:`, gl.getShaderInfoLog(shader)); gl.deleteShader(shader); return null; } return shader;}
// Function to initialize the shader programfunction initShaderProgram(gl, vsSource, fsSource) { const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource); const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource); if (!vertexShader || !fragmentShader) return null;
const shaderProgram = gl.createProgram(); gl.attachShader(shaderProgram, vertexShader); gl.attachShader(shaderProgram, fragmentShader); gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { console.error('Error linking shader program:', gl.getProgramInfoLog(shaderProgram)); gl.deleteProgram(shaderProgram); return null; } return shaderProgram;}请记住为着色器编译和程序链接实现健壮的错误检查,因为在开发过程中 GLSL 错误很常见。
关联属性与缓冲区对象 (Associating Attributes & Buffer Objects)
Section titled “关联属性与缓冲区对象 (Associating Attributes & Buffer Objects)”创建包含顶点数据的缓冲区并编译/链接着色器程序后,你需要告诉 WebGL 如何将缓冲区中的数据与顶点着色器中声明的 attribute 变量对应起来。
这涉及三个主要步骤:
1. 获取属性位置
Section titled “1. 获取属性位置”你的着色器程序中的每个 attribute 变量都有一个在程序链接期间由 WebGL 分配的特定位置(一个索引)。你需要使用 gl.getAttribLocation(program, name) 查询此位置。你将在后续步骤中使用此位置索引。
program:已链接的 WebGL 着色器程序对象。name:一个字符串,包含顶点着色器中 attribute 变量的精确名称(例如,'aVertexPosition'、'aVertexColor')。
它返回属性的整数位置,如果未找到或不活动(可能被编译器优化掉了,因为它未使用),则返回 -1。务必检查位置是否有效(>= 0)!
const shaderProgram = /* ... your linked program ... */;
const positionAttribLocation = gl.getAttribLocation(shaderProgram, 'aVertexPosition');const colorAttribLocation = gl.getAttribLocation(shaderProgram, 'aVertexColor');
if (positionAttribLocation < 0) { console.error("Attribute 'aVertexPosition' not found in shader!");}if (colorAttribLocation < 0) { console.error("Attribute 'aVertexColor' not found in shader!");}2. 将属性指向顶点缓冲区对象(VBO)
Section titled “2. 将属性指向顶点缓冲区对象(VBO)”这是核心步骤,你在此指定 WebGL 应如何解释当前绑定的 ARRAY_BUFFER 中的原始字节数据,以便将数据输入到特定的 attribute 中。这是通过 gl.vertexAttribPointer(location, size, type, normalized, stride, offset) 完成的。
参数:
location:attribute 的整数位置,从getAttribLocation获取。size:每个顶点 attribute 的分量数量(例如,vec3位置 [x, y, z] 为 3,vec4颜色 [r, g, b, a] 为 4)。type:缓冲区中每个分量的数据类型(例如,gl.FLOAT、gl.UNSIGNED_BYTE、gl.SHORT)。必须与bufferData中使用的数据类型匹配。normalized:布尔值。对于非浮点数据类型(如gl.BYTE、gl.UNSIGNED_BYTE),指定当被着色器访问时,是否将值归一化到 [-1, 1](对于有符号类型)或 [0, 1](对于无符号类型)的范围。对于浮点数通常为false。stride:连续顶点 attribute 之间的字节偏移量。如果你的属性是紧密打包的(如 [P1x, P1y, P1z, P2x, P2y, P2z,…]),步长为 0。如果数据是交错的(如 [P1x, P1y, C1r, C1g, P2x, P2y, C2r, C2g,…]),步长是从一个顶点数据开始到下一个顶点数据开始的字节数。offset:缓冲区中此 attribute 数据开始的字节偏移量。如果缓冲区仅包含此 attribute 的数据,通常为 0;如果数据是交错的,则为偏移量。
重要:在调用 gl.vertexAttribPointer 之前,你必须使用 gl.bindBuffer() 将相关的 VBO 绑定到 gl.ARRAY_BUFFER。
示例(简单位置缓冲区):
Section titled “示例(简单位置缓冲区):”const positionBuffer = /* ... buffer with vertex positions ... */;const positionAttribLocation = /* ... location of 'aVertexPosition' ... */;
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); // Bind the VBO
gl.vertexAttribPointer( positionAttribLocation, // Attribute location 3, // size: 3 components per vertex (X, Y, Z) gl.FLOAT, // type: the data is 32bit floats false, // normalized: don't normalize 0, // stride: 0 = tightly packed 0 // offset: 0 = start at the beginning);示例(交错的位置和颜色):
Section titled “示例(交错的位置和颜色):”// Buffer contains [PosX1, PosY1, PosZ1, ColR1, ColG1, ColB1, PosX2, PosY2, PosZ2, ColR2, ColG2, ColB2, ...]const interleavedBuffer = /* ... buffer with interleaved data ... */;const positionAttribLocation = /* ... location of 'aVertexPosition' ... */;const colorAttribLocation = /* ... location of 'aVertexColor' ... */;
const bytesPerFloat = 4;const stride = 6 * bytesPerFloat; // 6 floats per vertex (3 pos + 3 color)
gl.bindBuffer(gl.ARRAY_BUFFER, interleavedBuffer);
// Setup position attributegl.vertexAttribPointer(positionAttribLocation, 3, gl.FLOAT, false, stride, 0);
// Setup color attributeconst colorOffset = 3 * bytesPerFloat; // Color data starts after 3 position floatsgl.vertexAttribPointer(colorAttribLocation, 3, gl.FLOAT, false, stride, colorOffset);3. 启用属性
Section titled “3. 启用属性”最后,你必须使用 gl.enableVertexAttribArray(location) 启用顶点 attribute 数组。这告诉 WebGL 在调用 drawArrays 或 drawElements 时,实际根据 vertexAttribPointer 定义的设置从缓冲区拉取数据。
gl.enableVertexAttribArray(positionAttribLocation);// If using color attribute as well:// gl.enableVertexAttribArray(colorAttribLocation);如果需要,你可以稍后使用 gl.disableVertexAttribArray(location) 禁用 attribute。
顶点数组对象(VAOs)- WebGL 2 / 扩展
Section titled “顶点数组对象(VAOs)- WebGL 2 / 扩展”为每个对象使用 bindBuffer、vertexAttribPointer 和 enableVertexAttribArray 设置多个 attribute 可能很重复。顶点数组对象(Vertex Array Objects,VAOs)(在 WebGL 2 中可用,或通过 WebGL 1 中的 OES_vertex_array_object 扩展可用)允许你将所有这些 attribute 配置状态封装到一个对象中。你设置好 VAO 一次,然后在绘制对象之前只需绑定 VAO,这样可以显著减少每次绘制的 WebGL 调用次数。
虽然可能超出绝对基础知识的范畴,但 VAO 对于具有许多对象或复杂顶点设置的应用程序来说是提高性能的关键最佳实践。有关详细信息,请参阅 MDN:https://developer.mozilla.org/en-US/docs/Web/API/WebGLVertexArrayObject
WebGL - 绘制模型
Section titled “WebGL - 绘制模型”一旦你初始化了 WebGL,创建了缓冲区,编译了着色器,并配置了顶点属性,最后一步就是发出绘制命令来渲染你的几何体。
两个主要的绘制命令是 gl.drawArrays() 和 gl.drawElements()。
gl.drawArrays()
Section titled “gl.drawArrays()”当你的 ARRAY_BUFFER 中的顶点已经按照正确的顺序排列以形成所需的基本图元,并且你没有使用索引缓冲区(ELEMENT_ARRAY_BUFFER)时,使用 drawArrays。
void gl.drawArrays(mode, first, count);参数:
mode:指定要渲染的基本图元的类型。常见值:-
gl.POINTS:渲染单个点。
-
gl.LINES:渲染单个线段(顶点 0-1, 2-3 等)。
-
gl.LINE_STRIP:渲染连接的线段(0-1, 1-2, 2-3, …)。
-
gl.LINE_LOOP:类似于LINE_STRIP,但也会将最后一个顶点连接回第一个顶点。
-
gl.TRIANGLES:渲染单个三角形(顶点 0-1-2, 3-4-5 等)。
-
gl.TRIANGLE_STRIP:渲染连接的三角形(0-1-2, 1-2-3, 2-3-4, …)。对于网格很高效。
-
gl.TRIANGLE_FAN:渲染共享第一个顶点的连接三角形(0-1-2, 0-2-3, 0-3-4, …)。适用于圆形/圆锥体。
first:启用顶点属性数组中的起始索引。count:要渲染的顶点数量。
示例(绘制一个三角形):
Section titled “示例(绘制一个三角形):”// Assumes a VBO with 3 vertices is bound and attributes are configuredconst vertexCount = 3;gl.drawArrays(gl.TRIANGLES, 0, vertexCount);示例(绘制两个独立的三角形):
Section titled “示例(绘制两个独立的三角形):”// Assumes a VBO with 6 vertices (3 for first triangle, 3 for second)const vertexCount = 6;gl.drawArrays(gl.TRIANGLES, 0, vertexCount);gl.drawElements()
Section titled “gl.drawElements()”当你已经在 ARRAY_BUFFER (VBO) 中定义了唯一的顶点,并且正在使用包含索引的 ELEMENT_ARRAY_BUFFER (IBO) 来指定绘制这些顶点的顺序时,使用 drawElements。
这对于复杂的网格通常更高效,因为它避免了重复的顶点数据。
void gl.drawElements(mode, count, type, offset);参数:
mode:与drawArrays相同基本图元类型(例如,gl.TRIANGLES)。count:要从索引缓冲区渲染的索引数量。type:索引缓冲区中值的类型。必须是gl.UNSIGNED_BYTE、gl.UNSIGNED_SHORT,或者(带有扩展/WebGL 2)gl.UNSIGNED_INT。这必须与用于索引缓冲区的类型化数组类型(例如,Uint16Array对应gl.UNSIGNED_SHORT)匹配。offset:ELEMENT_ARRAY_BUFFER中索引开始的字节偏移量。通常为 0。
重要:在调用 gl.drawElements 之前,你必须使用 gl.bindBuffer() 将相关的 IBO 绑定到 gl.ELEMENT_ARRAY_BUFFER。
示例(使用 4 个顶点和 6 个索引绘制一个四边形):
Section titled “示例(使用 4 个顶点和 6 个索引绘制一个四边形):”// Assume VBO with 4 vertices is bound to ARRAY_BUFFER and attributes configured// Assume IBO with 6 indices [0,1,2, 0,2,3] is bound to ELEMENT_ARRAY_BUFFERconst indexCount = 6;const indexType = gl.UNSIGNED_SHORT; // Assuming Uint16Array was used for indicesconst offset = 0;
gl.drawElements(gl.TRIANGLES, indexCount, indexType, offset);绘制前的必要设置
Section titled “绘制前的必要设置”在每一帧(或每次绘制一个对象)调用 drawArrays 或 drawElements 之前,你通常需要执行这些设置操作:
1. 清空 Canvas:
Section titled “1. 清空 Canvas:”使用 gl.clearColor(r, g, b, a) 设置背景颜色(值范围 0.0 到 1.0)。然后使用 gl.clear(mask) 实际清空缓冲区。
mask 是以下各项的按位或组合:
gl.COLOR_BUFFER_BIT:清空颜色缓冲区。gl.DEPTH_BUFFER_BIT:清空深度缓冲区。gl.STENCIL_BUFFER_BIT:清空模板缓冲区。
gl.clearColor(0.1, 0.1, 0.2, 1.0); // Dark slate bluegl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // Clear color and depth2. 启用深度测试:
Section titled “2. 启用深度测试:”对于 3D 渲染,启用深度测试以使更靠近摄像头的对象能够正确遮挡更远的对象:gl.enable(gl.DEPTH_TEST);。你也可以使用 gl.depthFunc(gl.LEQUAL);(默认值)设置深度函数。
gl.enable(gl.DEPTH_TEST);gl.depthFunc(gl.LEQUAL); // Default, but good to be explicit3. 设置视口:
Section titled “3. 设置视口:”使用 gl.viewport(x, y, width, height) 定义 WebGL 应在 canvas 上绘制的矩形区域。这会将归一化的 [-1, +1] 裁剪空间坐标映射到 canvas 上的像素坐标。
// Typically set to the full canvas sizegl.viewport(0, 0, gl.canvas.width, gl.canvas.height);4. 使用着色器程序:
Section titled “4. 使用着色器程序:”激活所需的着色器程序:gl.useProgram(yourShaderProgram);
5. 绑定缓冲区并设置属性/统一变量:
Section titled “5. 绑定缓冲区并设置属性/统一变量:”确保绑定了正确的 VBO(gl.bindBuffer(gl.ARRAY_BUFFER, ...)),设置了属性指针(gl.vertexAttribPointer,gl.enableVertexAttribArray),如果使用 drawElements,则绑定了正确的 IBO(gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ...)),并且更新了当前对象/帧所需的任何统一变量(如变换矩阵、颜色、纹理单元)(gl.uniform*)。
完成这些设置步骤后,你最终可以调用 gl.drawArrays(...) 或 gl.drawElements(...)。