Skip to content

WebGL - 颜色

之前,我们通常在片段着色器(fragment shader)中为整个对象设置单一颜色(例如,gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); 表示红色)。一种更灵活的方法是定义**每顶点(per vertex)**的颜色。然后,WebGL 可以将这些颜色在图元(primitives)的面上进行插值(interpolate)(例如,三角形),从而创建平滑的渐变或允许模型的不同部分具有不同的颜色。

过程包括以下主要步骤:

  1. 定义颜色数据: 创建一个 JavaScript 类型化数组(TypedArray)(通常是 Float32Array),包含每个顶点的颜色数据。颜色通常指定为 RGB 或 RGBA 值,标准化到 [0.0, 1.0] 的范围。颜色的顺序必须与顶点的顺序相对应。
  2. 创建颜色缓冲区: 创建一个新的 WebGL 缓冲区对象(buffer object)(gl.createBuffer),专门用于存储颜色数据。
  3. 上传颜色数据: 绑定颜色缓冲区(gl.bindBuffer),并将颜色 TypedArray 数据上传到其中(gl.bufferData)。
  4. 更新顶点着色器(Vertex Shader): 添加一个 attribute(GLSL 1.00)或 in(GLSL 3.00)变量,用于接收每顶点的颜色。添加一个 varying(GLSL 1.00)或 out(GLSL 3.00)变量,将颜色(或其处理后的版本)传递给片段着色器。
  5. 更新片段着色器(Fragment Shader): 添加一个对应的 varying(GLSL 1.00)或 in(GLSL 3.00)变量,用于接收从顶点着色器插值而来的颜色。使用这个输入的颜色值来设置最终的 gl_FragColor(GLSL 1.00)或片段着色器的 out 颜色变量(GLSL 3.00)。
  6. 配置颜色属性: 在 JavaScript 中,获取颜色属性的位置(gl.getAttribLocation)。绑定颜色缓冲区。使用 gl.vertexAttribPointer 告诉 WebGL 如何从颜色缓冲区读取数据到属性中。启用颜色属性数组(gl.enableVertexAttribArray)。

让我们修改简单的三角形示例,绘制一个由两个三角形组成的四边形(quad),每个顶点分配不同的颜色。

步骤 1:准备 Canvas 和上下文

与之前相同:获取 canvas 元素和 WebGL 上下文。

const canvas = document.getElementById('my_Canvas');
const gl = canvas.getContext('webgl');
if (!gl) { /* ... error handling ... */ }

步骤 2:定义几何图形和颜色

我们定义了四边形的 4 个顶点以及对应的颜色(红、绿、蓝、白)。我们还定义了索引(indices)来绘制这两个三角形。

const vertices = new Float32Array([
-0.5, 0.5, // Vertex 0: Top-left
0.5, 0.5, // Vertex 1: Top-right
0.5, -0.5, // Vertex 2: Bottom-right
-0.5, -0.5 // Vertex 3: Bottom-left
]);
// Colors for each vertex (R, G, B), normalized [0.0, 1.0]
const colors = new Float32Array([
1.0, 0.0, 0.0, // Vertex 0: Red
0.0, 1.0, 0.0, // Vertex 1: Green
0.0, 0.0, 1.0, // Vertex 2: Blue
1.0, 1.0, 1.0 // Vertex 3: White
]);
// Indices to form two triangles: (0, 3, 1) and (1, 3, 2)
const indices = new Uint16Array([
0, 3, 1,
1, 3, 2
]);

步骤 3:创建和填充缓冲区

为顶点、颜色和索引分别创建缓冲区。

// Vertex Buffer
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// Color Buffer
const colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
// Index Buffer
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);

步骤 4:创建和编译着色器

着色器现在需要处理颜色属性。

const vsSource = `
attribute vec2 aVertexPosition;
attribute vec3 aVertexColor; // Input: Per-vertex color
varying lowp vec3 vColor; // Output: Color to fragment shader
void main(void) {
gl_Position = vec4(aVertexPosition, 0.0, 1.0);
vColor = aVertexColor; // Pass the color through
}
`;
const fsSource = `
varying lowp vec3 vColor; // Input: Interpolated color from vertex shader
void main(void) {
// Output the interpolated color
gl_FragColor = vec4(vColor, 1.0);
}
`;

(按照之前的示例所示,编译并将这些着色器链接到 shaderProgram 中,包括错误检查)。

步骤 5:关联着色器和缓冲区

获取属性位置并配置位置属性和颜色属性。

gl.useProgram(shaderProgram);
// Get attribute locations
const positionAttributeLocation = gl.getAttribLocation(shaderProgram, 'aVertexPosition');
const colorAttributeLocation = gl.getAttribLocation(shaderProgram, 'aVertexColor');
// Configure Position Attribute
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(positionAttributeLocation);
// Configure Color Attribute
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); // *** Bind the color buffer ***
gl.vertexAttribPointer(colorAttributeLocation, 3, gl.FLOAT, false, 0, 0); // 3 components (RGB)
gl.enableVertexAttribArray(colorAttributeLocation);
// Bind the index buffer (will be used by drawElements)
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);

步骤 6:绘制对象

清除 canvas 并使用 gl.drawElements,因为我们使用了索引缓冲区。

gl.clearColor(0.8, 0.8, 0.8, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
// Draw using indices
const primitiveType = gl.TRIANGLES;
const count = indices.length; // Number of indices to draw
const indexType = gl.UNSIGNED_SHORT; // Data type of indices
const drawOffset = 0; // Start at the beginning of the index buffer
gl.drawElements(primitiveType, count, indexType, drawOffset);
<!doctype html>
<html lang="en">
<head><meta charset="utf-8"><title>WebGL Colored Quad</title></head>
<body>
<canvas id="my_Canvas" width="300" height="300" style="border:1px solid black;"></canvas>
<script>
// --- GET CONTEXT ---
const canvas = document.getElementById('my_Canvas');
const gl = canvas.getContext('webgl');
if (!gl) { alert('WebGL not supported'); throw 'WebGL not supported'; }
// --- GEOMETRY & COLORS ---
const vertices = new Float32Array([-0.5,0.5, 0.5,0.5, 0.5,-0.5, -0.5,-0.5]);
const colors = new Float32Array([1,0,0, 0,1,0, 0,0,1, 1,1,1]); // R, G, B, W
const indices = new Uint16Array([0, 3, 1, 1, 3, 2]);
// --- BUFFERS ---
const vertexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
const colorBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
const indexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
// --- SHADERS ---
const vsSource = 'attribute vec2 pos; attribute vec3 col; varying lowp vec3 vCol; void main() { gl_Position = vec4(pos, 0.0, 1.0); vCol = col; }';
const fsSource = 'varying lowp vec3 vCol; void main() { gl_FragColor = vec4(vCol, 1.0); }';
function compileShader(gl, type, src) { /* ... (error checking compile function) ... */
const s = gl.createShader(type); gl.shaderSource(s, src); gl.compileShader(s);
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) { console.error(gl.getShaderInfoLog(s)); gl.deleteShader(s); return null;}
return s;
}
const vertexShader = compileShader(gl, gl.VERTEX_SHADER, vsSource);
const fragmentShader = compileShader(gl, gl.FRAGMENT_SHADER, fsSource);
const prog = gl.createProgram(); gl.attachShader(prog, vertexShader); gl.attachShader(prog, fragmentShader); gl.linkProgram(prog);
if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) { console.error(gl.getProgramInfoLog(prog)); throw 'Link failed'; }
gl.useProgram(prog);
// --- ATTRIBUTES ---
const posLoc = gl.getAttribLocation(prog, 'pos');
const colLoc = gl.getAttribLocation(prog, 'col');
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(posLoc);
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); gl.vertexAttribPointer(colLoc, 3, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(colLoc);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
// --- DRAW ---
gl.clearColor(0.8, 0.8, 0.8, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);
</script>
</body>
</html>

预期结果:

这将在灰色背景上显示一个四边形。颜色将在角点之间平滑混合:红色(左上)、绿色(右上)、蓝色(右下)和白色(左下)。