2.5 属性 (Attributes) 和 Uniforms WebGL 核心概念:属性 (Attributes) 和 Uniforms 详解 在 WebGL 的世界中,渲染管线是构建精美 3D 图形的核心。而要驱动这条管线,我们需要向它提供数据。这些数据主要分为两种关键类型:属性 (Attributes) 和 Uniforms。它们是连接 JavaScript 代码和 GLSL 着色器代码的桥梁,理解它们对于掌握 WebGL 编程至关重要。 2.5.1 属性 (Attributes):逐顶点的数据 概念解析 属性 (Attributes) 可以被认为是逐顶点的数据输入。它们是顶点着色器 (Vertex Shader) 的输入变量,每个顶点都拥有自己独立的属性值。
WebGL 核心概念:属性 (Attributes) 和 Uniforms 详解
在 WebGL 的世界中,渲染管线是构建精美 3D 图形的核心。而要驱动这条管线,我们需要向它提供数据。这些数据主要分为两种关键类型:属性 (Attributes) 和 Uniforms。它们是连接 JavaScript 代码和 GLSL 着色器代码的桥梁,理解它们对于掌握 WebGL 编程至关重要。
2.5.1 属性 (Attributes):逐顶点的数据
概念解析
属性 (Attributes) 可以被认为是逐顶点的数据输入。它们是顶点着色器 (Vertex Shader) 的输入变量,每个顶点都拥有自己独立的属性值。想象一下,您要绘制一个三角形,每个顶点都有不同的颜色、位置或法线。这些顶点各自不同的信息,就需要通过属性来传递给顶点着色器。
更具体地说:
顶点相关性: 属性数据与顶点一一对应。对于绘制的每个顶点,顶点着色器都会接收到一组新的属性值。
输入变量: 属性是顶点着色器 GLSL 代码中声明的 attribute 类型的变量。
数据来源: 属性数据来源于 JavaScript 代码,您需要将顶点数据存储在缓冲区对象 (Buffer Object) 中,并告知 WebGL 如何将缓冲区中的数据与顶点着色器中的属性变量关联起来。
数据类型
属性可以承载多种数据类型,常见的包括:
float: 单精度浮点数
vec2: 包含两个浮点数的向量
vec3: 包含三个浮点数的向量 (常用于表示 3D 坐标、颜色、法线等)
vec4: 包含四个浮点数的向量 (常用于表示齐次坐标、颜色等)
mat2, mat3, mat4: 矩阵 (虽然作为属性使用较少见,但也是合法的)
代码实践与详解
为了更好地理解属性的用法,我们来看一个简单的例子,绘制一个彩色三角形。
1. 顶点着色器 (Vertex Shader)
attribute vec4 a_position; // 顶点位置属性 attribute vec4 a_color; // 顶点颜色属性 varying vec4 v_color; // 传递给片元着色器的颜色 void main() { gl_Position = a_position; // 将顶点位置赋值给 gl_Position v_color = a_color; // 将顶点颜色传递给片元着色器 }
attribute vec4 a_position; 和 attribute vec4 a_color;:声明了两个属性变量,a_position 用于接收顶点位置,a_color 用于接收顶点颜色。vec4 表示它们都是四维向量。
varying vec4 v_color;:声明了一个 varying 变量 v_color。varying 变量用于从顶点着色器向片元着色器传递数据。在这里,我们将顶点颜色 a_color 传递给片元着色器,以便在片元着色器中使用。
gl_Position = a_position;:这是顶点着色器必须设置的内置变量,它决定了顶点在裁剪空间中的位置。我们直接将输入的顶点位置属性 a_position 赋值给 gl_Position,表示我们不进行任何变换 (在这个简单例子中)。
v_color = a_color;:将顶点颜色属性 a_color 赋值给 varying 变量 v_color,以便传递给片元着色器。
2. 片元着色器 (Fragment Shader)
precision mediump float; // 设置浮点精度为 mediump varying vec4 v_color; // 接收来自顶点着色器的颜色 void main() { gl_FragColor = v_color; // 将接收到的颜色赋值给 gl_FragColor }
precision mediump float;:设置浮点精度为 mediump (中等精度)。在片元着色器中,通常需要指定浮点数的精度。
varying vec4 v_color;:声明了一个 varying 变量 v_color,与顶点着色器中声明的 v_color 类型和名称一致,用于接收从顶点着色器传递过来的颜色数据。
gl_FragColor = v_color;:这是片元着色器必须设置的内置变量,它决定了当前片元的颜色。我们将从顶点着色器接收到的颜色 v_color 赋值给 gl_FragColor,从而实现为三角形填充顶点颜色插值后的颜色。
3. JavaScript 代码
// 获取 canvas 和 WebGL 上下文 (省略,假设已完成) const canvas = document.getElementById('myCanvas'); const gl = canvas.getContext('webgl'); // ... (着色器程序创建和链接代码省略,假设已完成) ... const program = createProgram(gl, vertexShaderSource, fragmentShaderSource); // 假设 createProgram 函数已定义 gl.useProgram(program); // 1. 获取属性位置 (Attribute Location) const positionAttributeLocation = gl.getAttribLocation(program, "a_position"); const colorAttributeLocation = gl.getAttribLocation(program, "a_color"); // 2. 创建缓冲区 (Buffer Object) const positionBuffer = gl.createBuffer(); const colorBuffer = gl.createBuffer(); // 3. 绑定缓冲区并写入数据 (Vertex Data) // 顶点位置数据 const positions = new Float32Array([ 0.0, 0.5, // 顶点 1 -0.5, -0.5, // 顶点 2 0.5, -0.5 // 顶点 3 ]); gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW); // 顶点颜色数据 (RGB 格式) const colors = new Float32Array([ 1.0, 0.0, 0.0, 1.0, // 顶点 1: 红色 0.0, 1.0, 0.0, 1.0, // 顶点 2: 绿色 0.0, 0.0, 1.0, 1.0 // 顶点 3: 蓝色 ]); gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW); // 4. 启用属性并指定数据格式 (Enable and Configure Attribute) // 顶点位置属性 gl.enableVertexAttribArray(positionAttributeLocation); // 启用属性 gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); // 绑定位置缓冲区 gl.vertexAttribPointer( // 指定属性数据格式 positionAttributeLocation, // 属性位置 2, // 尺寸 (每个顶点使用 2 个值,x, y) gl.FLOAT, // 数据类型 (浮点数) false, // 是否归一化 (不需要) 0, // 步长 (0 表示紧密排列) 0 // 偏移量 (从缓冲区起始位置开始读取) ); // 顶点颜色属性 gl.enableVertexAttribArray(colorAttributeLocation); // 启用属性 gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); // 绑定颜色缓冲区 gl.vertexAttribPointer( // 指定属性数据格式 colorAttributeLocation, // 属性位置 4, // 尺寸 (每个顶点使用 4 个值,R, G, B, A) gl.FLOAT, // 数据类型 (浮点数) false, // 是否归一化 (不需要) 0, // 步长 (0 表示紧密排列) 0 // 偏移量 (从缓冲区起始位置开始读取) ); // 5. 绘制 (Draw) gl.drawArrays(gl.TRIANGLES, 0, 3); // 绘制三角形 (3 个顶点)
代码详解
获取属性位置 (Attribute Location):
gl.getAttribLocation(program, "a_position") 和 gl.getAttribLocation(program, "a_color"):这两个函数用于获取顶点着色器中属性变量 a_position 和 a_color 的位置 (索引)。WebGL 需要知道这些属性在着色器程序中的位置,才能正确地将数据传递给它们。创建缓冲区 (Buffer Object):
gl.createBuffer():创建两个缓冲区对象,positionBuffer 用于存储顶点位置数据,colorBuffer 用于存储顶点颜色数据。缓冲区对象是 WebGL 中用于存储大量顶点数据的容器,可以高效地将数据传递给 GPU。绑定缓冲区并写入数据 (Vertex Data):
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);:将 positionBuffer 绑定到 gl.ARRAY_BUFFER 目标。gl.ARRAY_BUFFER 是用于存储顶点属性数据的缓冲区目标。
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);:将顶点位置数据 positions 写入当前绑定的 gl.ARRAY_BUFFER (即 positionBuffer)。
gl.STATIC_DRAW 是一个提示 WebGL 如何使用这些数据的标志。gl.STATIC_DRAW 表示数据将被写入一次,然后多次绘制,适合静态几何体。颜色数据的处理方式与位置数据类似,只是数据内容和绑定的缓冲区不同。
启用属性并指定数据格式 (Enable and Configure Attribute):
gl.enableVertexAttribArray(positionAttributeLocation); 和 gl.enableVertexAttribArray(colorAttributeLocation);:启用指定位置的属性。默认情况下,属性是禁用的,需要显式启用才能使用。
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); 和 gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);:再次绑定对应的缓冲区,确保 vertexAttribPointer 函数操作的是正确的缓冲区。
gl.vertexAttribPointer(...):这个函数至关重要,它告诉 WebGL 如何从当前绑定的缓冲区中读取数据,并将其解释为顶点着色器中属性变量的数据。
第一个参数是属性位置 (positionAttributeLocation 或 colorAttributeLocation)。
第二个参数是尺寸 (size),即每个顶点使用多少个数值来表示该属性。位置属性使用 2 个值 (x, y),颜色属性使用 4 个值 (R, G, B, A)。
第三个参数是数据类型 (gl.FLOAT),表示缓冲区中的数据是浮点数。
第四个参数是 normalized,是否需要将数据归一化到 [0, 1] 或 [-1, 1] 范围。这里设置为 false。
第五个参数是 stride (步长),表示顶点属性数据在缓冲区中是如何排列的。0 表示数据是紧密排列的,没有间隔。
第六个参数是 offset (偏移量),表示从缓冲区起始位置开始读取数据的偏移量。0 表示从起始位置开始读取。
绘制 (Draw):
gl.drawArrays(gl.TRIANGLES, 0, 3);:使用三角形图元绘制,从第 0 个顶点开始,绘制 3 个顶点。这将使用我们设置的顶点位置和颜色属性来渲染一个彩色三角形。Graph TD 图示属性数据流
图示解释:
Vertex Data (JavaScript Array): 顶点数据首先在 JavaScript 中以数组的形式创建,例如 positions 和 colors 数组。
Buffer Object: JavaScript 数组数据被上传到 WebGL 的缓冲区对象 (Buffer Object) 中,存储在 GPU 内存中,以便高效访问。
gl.ARRAY_BUFFER: 缓冲区对象被绑定到 gl.ARRAY_BUFFER 目标,表示它将用于存储顶点属性数据。
Vertex Shader Attributes: 顶点着色器中的 attribute 变量 (a_position, a_color 等) 与绑定到 gl.ARRAY_BUFFER 的缓冲区关联起来,并通过 vertexAttribPointer 指定了数据的格式和读取方式。
Vertex Shader: 顶点着色器从缓冲区中读取每个顶点的属性数据,并进行处理 (例如,坐标变换、颜色计算等)。
Rasterization: 光栅化阶段将顶点着色器处理后的顶点信息转换为屏幕上的片元 (像素)。
Fragment Shader: 片元着色器接收来自顶点着色器的插值后的数据 (例如,varying 变量 v_color),并计算每个片元的最终颜色。
Canvas: 最终的像素颜色被写入 Canvas,显示在屏幕上。
总结属性 (Attributes)
属性是逐顶点的数据输入,每个顶点拥有独立的属性值。
属性用于向顶点着色器传递顶点相关的信息,例如位置、颜色、法线、纹理坐标等。
属性数据来源于 JavaScript 代码,需要存储在缓冲区对象中,并通过 vertexAttribPointer 与顶点着色器中的属性变量关联。
属性是实现各种顶点级别效果 (例如,顶点颜色、顶点动画等) 的基础。
2.5.2 Uniforms:全局共享的数据
概念解析
与属性不同,Uniforms 是全局共享的数据输入。它们是着色器程序 (顶点着色器和片元着色器) 的输入变量,但对于每一次绘制调用 (draw call),所有顶点和片元都共享相同的 Uniforms 值。
想象一下,您要绘制多个三角形,它们都使用相同的光照方向、模型视图矩阵或投影矩阵。这些在整个绘制过程中保持不变的全局参数,就适合使用 Uniforms 来传递。
更具体地说:
全局性: Uniforms 的值在一次绘制调用中对于所有顶点和片元都是相同的。
输入变量: Uniforms 是 GLSL 着色器代码中声明的 uniform 类型的变量。
数据来源: Uniforms 数据也来源于 JavaScript 代码,您需要获取 Uniforms 变量在着色器程序中的位置 (Uniform Location),并通过 WebGL API 设置 Uniforms 的值。
数据类型
Uniforms 可以承载的数据类型非常丰富,包括:
float, vec2, vec3, vec4: 浮点数和向量
int, ivec2, ivec3, ivec4: 整数和向量
bool, bvec2, bvec3, bvec4: 布尔值和向量
mat2, mat3, mat4: 矩阵 (常用于变换矩阵,例如模型视图矩阵、投影矩阵)
sampler2D, samplerCube: 纹理采样器 (用于在着色器中访问纹理)
代码实践与详解
我们继续使用之前的彩色三角形例子,这次我们添加一个 Uniform 变量来控制三角形的整体颜色。
1. 修改后的顶点着色器 (Vertex Shader)
attribute vec4 a_position; uniform vec4 u_globalColor; // 全局颜色 Uniform varying vec4 v_color; void main() { gl_Position = a_position; v_color = u_globalColor; // 使用 Uniform 颜色 }
uniform vec4 u_globalColor;:声明了一个 uniform 变量 u_globalColor,用于接收全局颜色。2. 修改后的片元着色器 (Fragment Shader)
precision mediump float; varying vec4 v_color; void main() { gl_FragColor = v_color; // 片元颜色仍然来自 varying 变量 }
varying 变量 v_color 作为片元颜色。3. 修改后的 JavaScript 代码
// ... (之前的 JavaScript 代码,包括获取上下文、创建程序、获取属性位置等) ... // 1. 获取 Uniform 位置 (Uniform Location) const globalColorUniformLocation = gl.getUniformLocation(program, "u_globalColor"); // ... (缓冲区创建、绑定、写入数据、启用属性等代码保持不变) ... // 2. 设置 Uniform 值 (Set Uniform Value) gl.uniform4f(globalColorUniformLocation, 0.5, 0.5, 0.5, 1.0); // 设置全局颜色为灰色 (RGBA: 0.5, 0.5, 0.5, 1.0) // 3. 绘制 (Draw) gl.drawArrays(gl.TRIANGLES, 0, 3);
代码详解
获取 Uniform 位置 (Uniform Location):
gl.getUniformLocation(program, "u_globalColor"):获取顶点着色器中 Uniform 变量 u_globalColor 的位置 (索引)。与获取属性位置类似,WebGL 需要知道 Uniform 变量在着色器程序中的位置,才能设置它的值。设置 Uniform 值 (Set Uniform Value):
gl.uniform4f(globalColorUniformLocation, 0.5, 0.5, 0.5, 1.0);:设置 Uniform 变量 u_globalColor 的值。
gl.uniform4f 是 WebGL 提供的用于设置 vec4 类型 Uniform 变量值的函数。4f 表示设置 4 个浮点数 (float)。
第一个参数是 Uniform 位置 (globalColorUniformLocation)。
后面的四个参数是要设置的 vec4 值 (RGBA: 0.5, 0.5, 0.5, 1.0),这里设置为灰色。
WebGL 提供了多种 gl.uniformXXX 函数,用于设置不同类型的 Uniform 变量值,例如:
gl.uniform1f, gl.uniform2f, gl.uniform3f, gl.uniform4f: 设置 float, vec2, vec3, vec4 类型 Uniforms。
gl.uniform1i, gl.uniform2i, gl.uniform3i, gl.uniform4i: 设置 int, ivec2, ivec3, ivec4 类型 Uniforms。
gl.uniform1fv, gl.uniform2fv, gl.uniform3fv, gl.uniform4fv: 设置 float, vec2, vec3, vec4 类型的 Uniform 数组。
gl.uniformMatrix4fv: 设置 mat4 类型的 Uniform 矩阵。 (类似的还有 gl.uniformMatrix3fv, gl.uniformMatrix2fv)
gl.uniform1i: 设置 sampler2D 或 samplerCube 类型的 Uniform 纹理采样器 (用于指定纹理单元)。
Graph TD 图示 Uniform 数据流
图示解释:
JavaScript Code (CPU): Uniform 数据在 JavaScript 代码中设置,例如使用 gl.uniform4f 函数。Uniform 值在 CPU 端准备好。
gl.uniformXXX: 通过 gl.uniformXXX 函数,将 Uniform 值从 JavaScript 代码传递到 WebGL 着色器程序。
Uniform Variable (Vertex Shader) & Uniform Variable (Fragment Shader): 在顶点着色器和片元着色器中声明的 uniform 变量 (u_globalColor 等) 接收来自 JavaScript 代码设置的 Uniform 值。注意,同一个 Uniform 变量可以在顶点着色器和片元着色器中同时使用,它们共享相同的值。
Vertex Shader & Fragment Shader: 顶点着色器和片元着色器可以使用 Uniform 变量的值进行计算和渲染。
Rasterization, Canvas: 后续的渲染管线阶段与属性的流程类似,最终将图像显示在 Canvas 上。
总结 Uniforms
Uniforms 是全局共享的数据输入,在一次绘制调用中对于所有顶点和片元都相同。
Uniforms 用于向着色器程序传递全局参数,例如变换矩阵、光照参数、材质属性、纹理等。
Uniforms 数据来源于 JavaScript 代码,需要通过 gl.uniformXXX 函数设置 Uniform 变量的值。
Uniforms 是实现各种全局效果 (例如,模型变换、光照、材质、纹理贴图等) 的重要工具。
2.5.3 属性 (Attributes) vs. Uniforms:对比与总结
| 特性 | 属性 (Attributes) | Uniforms |
|---|---|---|
| 数据类型 | 逐顶点 (Per-vertex) | 全局共享 (Global, Per-draw-call) |
| 作用域 | 顶点着色器输入变量 | 顶点着色器和片元着色器输入变量 |
| 数据来源 | 顶点缓冲区对象 (Buffer Object) | JavaScript 代码 |
| 设置方式 | 通过缓冲区对象和 vertexAttribPointer 配置 |
通过 gl.uniformXXX 函数设置值 |
| 用途 | 传递顶点相关信息 (位置、颜色、法线等) | 传递全局参数 (变换矩阵、光照参数、材质、纹理等) |
| 变化频率 | 每个顶点都可能不同 | 在一次绘制调用中保持不变,不同绘制调用可以不同 |
总结
属性 (Attributes) 和 Uniforms 是 WebGL 中两种最基本也是最重要的数据输入方式。理解它们的区别和用途,是掌握 WebGL 编程的关键一步。
属性 (Attributes) 用于传递逐顶点的数据,让我们可以为每个顶点定义不同的特性,实现精细的几何形状和顶点级别的效果。
Uniforms 用于传递全局共享的数据,让我们可以控制场景的整体外观和效果,例如场景变换、光照、材质等。
在实际 WebGL 应用中,属性和 Uniforms 常常结合使用,共同驱动渲染管线,创造出丰富多彩的 3D 图形世界。例如,我们可以使用属性传递顶点位置和法线,使用 Uniforms 传递模型视图矩阵、投影矩阵、光照方向和材质颜色,从而实现光照和阴影效果。