WebGL - 颜色
WebGL - 为几何图形应用颜色
Section titled “WebGL - 为几何图形应用颜色”之前,我们通常在片段着色器(fragment shader)中为整个对象设置单一颜色(例如,gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); 表示红色)。一种更灵活的方法是定义**每顶点(per vertex)**的颜色。然后,WebGL 可以将这些颜色在图元(primitives)的面上进行插值(interpolate)(例如,三角形),从而创建平滑的渐变或允许模型的不同部分具有不同的颜色。
应用每顶点颜色
Section titled “应用每顶点颜色”过程包括以下主要步骤:
- 定义颜色数据: 创建一个 JavaScript 类型化数组(TypedArray)(通常是
Float32Array),包含每个顶点的颜色数据。颜色通常指定为 RGB 或 RGBA 值,标准化到 [0.0, 1.0] 的范围。颜色的顺序必须与顶点的顺序相对应。 - 创建颜色缓冲区: 创建一个新的 WebGL 缓冲区对象(buffer object)(
gl.createBuffer),专门用于存储颜色数据。 - 上传颜色数据: 绑定颜色缓冲区(
gl.bindBuffer),并将颜色 TypedArray 数据上传到其中(gl.bufferData)。 - 更新顶点着色器(Vertex Shader): 添加一个
attribute(GLSL 1.00)或in(GLSL 3.00)变量,用于接收每顶点的颜色。添加一个varying(GLSL 1.00)或out(GLSL 3.00)变量,将颜色(或其处理后的版本)传递给片段着色器。 - 更新片段着色器(Fragment Shader): 添加一个对应的
varying(GLSL 1.00)或in(GLSL 3.00)变量,用于接收从顶点着色器插值而来的颜色。使用这个输入的颜色值来设置最终的gl_FragColor(GLSL 1.00)或片段着色器的out颜色变量(GLSL 3.00)。 - 配置颜色属性: 在 JavaScript 中,获取颜色属性的位置(
gl.getAttribLocation)。绑定颜色缓冲区。使用gl.vertexAttribPointer告诉 WebGL 如何从颜色缓冲区读取数据到属性中。启用颜色属性数组(gl.enableVertexAttribArray)。
示例:绘制多色四边形
Section titled “示例:绘制多色四边形”让我们修改简单的三角形示例,绘制一个由两个三角形组成的四边形(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 Bufferconst vertexBuffer = gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// Color Bufferconst colorBuffer = gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
// Index Bufferconst indexBuffer = gl.createBuffer();gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);步骤 4:创建和编译着色器
着色器现在需要处理颜色属性。
顶点着色器 (GLSL ES 1.00):
Section titled “顶点着色器 (GLSL ES 1.00):”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 }`;片段着色器 (GLSL ES 1.00):
Section titled “片段着色器 (GLSL ES 1.00):”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 locationsconst positionAttributeLocation = gl.getAttribLocation(shaderProgram, 'aVertexPosition');const colorAttributeLocation = gl.getAttribLocation(shaderProgram, 'aVertexColor');
// Configure Position Attributegl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);gl.enableVertexAttribArray(positionAttributeLocation);
// Configure Color Attributegl.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 indicesconst primitiveType = gl.TRIANGLES;const count = indices.length; // Number of indices to drawconst indexType = gl.UNSIGNED_SHORT; // Data type of indicesconst drawOffset = 0; // Start at the beginning of the index buffergl.drawElements(primitiveType, count, indexType, drawOffset);完整示例代码片段:
Section titled “完整示例代码片段:”<!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>预期结果:
这将在灰色背景上显示一个四边形。颜色将在角点之间平滑混合:红色(左上)、绿色(右上)、蓝色(右下)和白色(左下)。