欢迎光临
我们一直在努力

WebGL drawArrays 实现点线面绘制

一、前言

gl.drawArrays 是WebGL中最核心的绘图方法之一。它就像一个“绘图指挥官”,告诉GPU如何解释顶点缓冲区中的数据,并将其绘制成点、线或三角形。理解它的每种绘图模式及其适用场景,是驾驭WebGL、实现从简单粒子到复杂3D模型的基础。本文将带您深入掌握这个命令,并通过完整的代码示例演示每种模式的实际用法。

二、方法签名与核心参数

gl.drawArrays(mode, first, count) 方法接受三个参数,决定了绘图的方式和范围。

参数类型描述
mode GLenum 指定绘制图元的类型。WebGL根据这个模式将顶点数据组装成不同的图形。
first GLint 指定从顶点数组的哪个索引开始读取数据(通常从0开始)。
count GLsizei 指定要渲染的顶点数量。
三、核心参数详解:绘制模式(mode)

mode 参数是 gl.drawArrays 的灵魂,它决定了图形的组装方式。

模式常量值描述图元组装方式典型使用场景
gl.POINTS 0x0000 绘制独立的点 每个顶点作为一个单独的点。 粒子系统:绘制星空、雪花、大量子弹或标记点。
gl.LINES 0x0001 绘制独立的线段 顶点对 (v0, v1),(v2, v3),… 构成线段。若顶点数为奇数,最后一个被忽略。 线框网格:绘制模型的边缘。网络拓扑图:连接不连续的点。
gl.LINE_STRIP 0x0003 绘制一条连续折线 线段由 (v0, v1),(v1, v2),(v2, v3),… 构成。 函数图像:绘制正弦波、股票走势图。路径动画:显示物体移动轨迹。
gl.LINE_LOOP 0x0002 绘制一条闭合折线 与 LINE_STRIP 类似,但会在最后一个顶点和第一个顶点之间自动添加一条线段。 绘制多边形轮廓:如空心矩形、多边形边框。
gl.TRIANGLES 0x0004 绘制独立的三角形 顶点三元组 (v0, v1, v2),(v3, v4, v5),… 构成三角形。 绝大多数3D模型:任何实体表面都可分解为三角形。
gl.TRIANGLE_STRIP 0x0005 绘制一个三角形带 前三个顶点构成第一个三角形,之后每新增一个顶点与上两个顶点构成新三角形。 高效构建连续表面:如地形、带状物体(道路、丝带)。优势:顶点利用率高。
gl.TRIANGLE_FAN 0x0006 绘制一个三角形扇 前三个顶点构成第一个三角形,之后每新增一个顶点与第一个顶点和上一个顶点构成新三角形。 绘制锥体或扇形:如金字塔侧面、圆形或饼图填充。

类型如下图

四、完整代码示例

以下示例均使用相同的顶点数据(六个顶点位置,外加颜色属性),以便直观对比不同模式下的绘制结果。每个示例都是完整可运行的HTML文件。

准备工作:基础HTML模板与通用着色器

所有示例共用一个基础模板,只需替换 <script> 标签内的核心绘制逻辑即可。

html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebGL示例</title>
<style>
#webgl {
border: 2px solid red;

}
</style>
</head>
<body>

<canvas id="canvas" width="400" height="400"></canvas>

<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.8.1/gl-matrix-min.js"></script>
<!– 使用普通方式引入,不要用 module –>
<script src="main.js"></script>

</body>
</html>
</body>
</html>

公共代码

// ================== 封装工具:创建着色器程序 ==================
function createShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}

function createProgram(gl, vsSource, fsSource) {
const vs = createShader(gl, gl.VERTEX_SHADER, vsSource);
const fs = createShader(gl, gl.FRAGMENT_SHADER, fsSource);
const program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error(gl.getProgramInfoLog(program));
return null;
}
return program;
}

// ===== 通用的顶点属性设置 (位置2分量 + 颜色3分量, 步长20字节) =====
function setupPositionAndColor(gl, program, bufferData) {
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(bufferData), gl.STATIC_DRAW);

const posLoc = gl.getAttribLocation(program, 'a_position');
const colorLoc = gl.getAttribLocation(program, 'a_color');

gl.enableVertexAttribArray(posLoc);
gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 20, 0);

if (colorLoc !== -1) {
gl.enableVertexAttribArray(colorLoc);
gl.vertexAttribPointer(colorLoc, 3, gl.FLOAT, false, 20, 8);
}
return { posLoc, colorLoc };
}

1. gl.POINTS:独立点
  • 核心代码替换:

    javascript

    const vs = `
    attribute vec2 a_position;
    attribute vec3 a_color;
    varying vec3 v_color;
    void main() {
    gl_Position = vec4(a_position, 0.0, 1.0);
    gl_PointSize = 22.0;
    v_color = a_color;
    }`;

    const fs = `
    precision mediump float;
    varying vec3 v_color;
    void main() {
    vec2 coord = gl_PointCoord – vec2(0.5);
    float d = length(coord);
    if (d > 0.5) discard;
    float alpha = 1.0 – smoothstep(0.2, 0.5, d);
    gl_FragColor = vec4(v_color, alpha);
    }
    `;
    const program = createProgram(gl, vs, fs);
    gl.useProgram(program);

    const vertices = [
    // 位置 (x,y) , 颜色 (r,g,b)
    0.0, 0.65, 1.0, 0.3, 0.3, // 上红
    0.55, 0.1, 0.3, 1.0, 0.3, // 右绿
    0.3, -0.55, 0.3, 0.3, 1.0, // 右下蓝
    -0.3, -0.55, 1.0, 1.0, 0.2, // 下黄
    -0.55, 0.1, 1.0, 0.4, 1.0 // 左紫
    ];
    setupPositionAndColor(gl, program, vertices);
    gl.drawArrays(gl.POINTS, 0, 5);

  • 应用场景:非常适合粒子系统,如在游戏中绘制星星、火花或标记点。

  • 运行效果: 独立圆点。

2. gl.LINES:独立线段
  • 核心代码替换:

    javascript

    const vs = `
    attribute vec2 a_position;
    attribute vec3 a_color;
    varying vec3 v_color;
    void main() {
    gl_Position = vec4(a_position,0.0,1.0); v_color = a_color;
    } `;

    const fs = `
    precision mediump float;
    varying vec3 v_color;
    void main() {
    gl_FragColor = vec4(v_color,1.0);
    }`;

    const program = createProgram(gl, vs, fs);
    gl.useProgram(program);
    const vertices = [
    // 横线红
    -0.7, 0.0, 1.0, 0.2, 0.2,
    0.7, 0.0, 1.0, 0.2, 0.2,
    // 竖线绿
    0.0, -0.7, 0.2, 1.0, 0.2,
    0.0, 0.7, 0.2, 1.0, 0.2,
    // 斜线蓝 (\\)
    -0.6, -0.6, 0.2, 0.4, 1.0,
    0.6, 0.6, 0.2, 0.4, 1.0,
    // 斜线黄 (/)
    -0.6, 0.6, 1.0, 1.0, 0.2,
    0.6, -0.6, 1.0, 1.0, 0.2
    ];
    setupPositionAndColor(gl, program, vertices);
    gl.drawArrays(gl.LINES, 0, 8);

  • 应用场景:绘制网格线、DNA双螺旋结构的横档、或任何不需要连接的线条。

3. gl.LINE_STRIP:连续折线
  • 核心代码替换:

    javascript

    const vs = `
    attribute vec2 a_position;
    void main() { gl_Position = vec4(a_position,0.0,1.0);
    }`;
    const fs = `
    precision mediump float;
    void main() {
    gl_FragColor = vec4(0.3,0.9,0.4,1.0);
    }`;

    const program = createProgram(gl, vs, fs);
    gl.useProgram(program);

    const points = [];
    for (let i = 0; i <= 20; i++) {
    const t = i / 20;
    const x = -0.8 + t * 1.6;
    const y = Math.sin(x * 6.5) * 0.45;
    points.push(x, y);
    }

    const buffer = gl.createBuffer();

    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(points), gl.STATIC_DRAW);

    const posLoc = gl.getAttribLocation(program, 'a_position');
    gl.enableVertexAttribArray(posLoc);

    gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);

    gl.drawArrays(gl.LINE_STRIP, 0, 21);

  • 应用场景:完美适配数据可视化中的折线图、绘制心电波形或路径轨迹。

4. gl.LINE_LOOP:闭合折线
  • 核心代码替换:

    javascript

    const vs = `
    attribute vec2 a_position;
    attribute float a_bright;
    varying float v_bright;
    void main() {
    gl_Position = vec4(a_position,0.0,1.0); v_bright = a_bright;
    }
    `;
    const fs = `
    precision mediump float;
    varying float v_bright;
    void main() {
    gl_FragColor = vec4(0.4, 0.7, 1.0, 1.0) * (0.6 + 0.5 * v_bright);
    }
    `;
    const program = createProgram(gl, vs, fs);
    gl.useProgram(program);

    const data = [];
    for (let i = 0; i < 6; i++) {
    const angle = (i / 6) * Math.PI * 2;
    const x = Math.cos(angle) * 0.7;
    const y = Math.sin(angle) * 0.7;
    data.push(x, y, i / 6); // 亮度因子
    }
    const buffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);

    const posLoc = gl.getAttribLocation(program, 'a_position');
    const brightLoc = gl.getAttribLocation(program, 'a_bright');
    gl.enableVertexAttribArray(posLoc);
    gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 12, 0);
    gl.enableVertexAttribArray(brightLoc);
    gl.vertexAttribPointer(brightLoc, 1, gl.FLOAT, false, 12, 8);

    gl.drawArrays(gl.LINE_LOOP, 0, 6);

  • 应用场景:绘制多边形边框、区域轮廓或循环路径示意图。

5. gl.TRIANGLES:独立三角形
  • 核心代码替换:

    javascript

    const vs = `
    attribute vec2 a_position;
    attribute vec3 a_color;
    varying vec3 v_color;
    void main() {
    gl_Position = vec4(a_position,0.0,1.0);
    v_color = a_color;
    }`;

    const fs = `
    precision mediump float;
    varying vec3 v_color;
    void main() {
    gl_FragColor = vec4(v_color,1.0);
    }`;

    const program = createProgram(gl, vs, fs);
    gl.useProgram(program);

    const vertices = [
    // tri1 (红,绿,蓝)
    -0.5, -0.5, 1.0, 0.2, 0.2,
    0.5, -0.5, 0.2, 1.0, 0.2,
    -0.5, 0.5, 0.2, 0.2, 1.0,
    // tri2 (青,紫,黄)
    0.5, -0.5, 0.2, 1.0, 1.0,
    0.5, 0.5, 1.0, 0.3, 1.0,
    -0.5, 0.5, 1.0, 1.0, 0.2
    ];
    setupPositionAndColor(gl, program, vertices);
    gl.drawArrays(gl.TRIANGLES, 0, 6);

  • 应用场景:最基础的3D建模方式,用于构建任何复杂模型的表面。

6. gl.TRIANGLE_STRIP:矩形
  • 核心代码替换:

    javascript

    const vs = `
    attribute vec2 a_position;
    attribute vec3 a_color;
    varying vec3 v_color;
    void main() { gl_Position = vec4(a_position,0.0,1.0); v_color = a_color; }
    `;
    const fs = `
    precision mediump float;
    varying vec3 v_color;
    void main() {
    gl_FragColor = vec4(v_color,1.0);
    }`;

    const program = createProgram(gl, vs, fs);
    gl.useProgram(program);

    const vertices = [
    -0.6, -0.6, 1.0, 0.3, 0.3, // 左下红
    -0.6, 0.6, 0.3, 1.0, 0.3, // 左上绿
    0.6, -0.6, 0.3, 0.3, 1.0, // 右下蓝
    0.6, 0.6, 1.0, 1.0, 0.3 // 右上黄
    ];
    setupPositionAndColor(gl, program, vertices);
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

  • 应用场景:高效构建地形、路面、管道等连续表面,能显著减少顶点数据传输。

7. gl.TRIANGLE_FAN:三角形扇
  • 核心代码替换:

    javascript

    const vs = `
    attribute vec2 a_position;
    attribute float a_factor;
    varying float v_factor;
    void main() {
    gl_Position = vec4(a_position,0.0,1.0);
    v_factor = a_factor; }
    `;
    const fs = `
    precision mediump float;
    varying float v_factor;
    void main() {
    float r = 0.6 + 0.5 * sin(v_factor * 6.28);
    float g = 0.5 + 0.5 * cos(v_factor * 5.0);
    float b = 0.7 + 0.3 * sin(v_factor * 7.0 + 2.0);
    gl_FragColor = vec4(r, g, b, 1.0);
    } `;
    const program = createProgram(gl, vs, fs);
    gl.useProgram(program);

    const data = [0, 0, 0]; // 中心
    for (let i = 0; i <= 8; i++) {
    const angle = (i / 8) * Math.PI * 2;
    const x = Math.cos(angle) * 0.7;
    const y = Math.sin(angle) * 0.7;
    data.push(x, y, i / 8);
    }
    const buffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);

    const posLoc = gl.getAttribLocation(program, 'a_position');
    const factorLoc = gl.getAttribLocation(program, 'a_factor');
    gl.enableVertexAttribArray(posLoc);
    gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 12, 0);
    gl.enableVertexAttribArray(factorLoc);
    gl.vertexAttribPointer(factorLoc, 1, gl.FLOAT, false, 12, 8);

    gl.drawArrays(gl.TRIANGLE_FAN, 0, 10);

  • 应用场景:绘制锥体侧面、圆形填充、或任何从一个中心点向外辐射的图形,如饼图。

五、性能考量与最佳实践
  • 顶点数量 (count) 的选择:count 并非越大越好。它受GPU处理能力和显存带宽限制。对于粒子系统(POINTS),轻松支持数万点;对于复杂模型,需要结合视锥剔除等技术优化。

  • first 参数的妙用:可以将多个对象的顶点数据存储在同一个缓冲区中,通过不同的 first 和 count 值分批绘制,减少缓冲区切换的开销。

  • 与 gl.drawElements 的选择:drawArrays 简单直接,适合顶点不重复的几何体(如粒子)。对于顶点复用率高的模型(如立方体,8个顶点但要绘制12个三角形),使用索引绘制 (gl.drawElements) 更节省内存。

  • 六、总结

    gl.drawArrays 的七种绘制模式,为图形表达提供了极大的灵活性。从最简单的点,到复杂的三角形扇,选择合适的模式不仅能正确呈现图形,更是优化性能的关键。希望通过本文的参数详解和完整的可运行示例,能帮助您在未来的WebGL项目中,更自信、更高效地使用这个核心命令。

    感谢阅读! 喜欢本文请不要吝啬你的 一键三连:点赞、收藏、留言,让更多小伙伴看到这份干货!

    赞(0)
    未经允许不得转载:171主机测评 » WebGL drawArrays 实现点线面绘制
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址