Skip to content

WebGL - 绘制点

本章演示了在 WebGL 中绘制简单点的基本过程。我们将沿用之前建立的核心步骤,并根据 gl.POINTS 原始类型进行调整。

绘制点与绘制三角形或其他原始类型遵循相同的通用流程:

步骤 1: 设置 Canvas 和 WebGL 上下文

获取 canvas 元素和 WebGL 渲染上下文 (gl)。包含错误处理。

步骤 2: 定义几何体(顶点)和缓冲区

定义要绘制的每个点的 3D 坐标。将这些坐标存储在 Float32Array 中,并使用 gl.createBuffer、gl.bindBuffer 和 gl.bufferData 将它们传输到 WebGL 顶点缓冲区对象 (VBO)。

// 定义三个点的坐标 (X, Y, Z)
const pointVertices = new Float32Array([
-0.5, 0.5, 0.0, // 点 1
0.0, 0.5, 0.0, // 点 2
-0.25, 0.0, 0.0 // 点 3
]);
// ... (创建 VBO 和 bufferData 代码) ...

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

编写、编译和链接顶点着色器和片段着色器。

  • 顶点着色器: 接收顶点位置属性 (a_position)。它必须将最终位置输出到 gl_Position。关键的是,对于点,你可以通过写入内置变量 gl_PointSize 来控制其像素大小。
  • 片段着色器: 决定每个点的颜色。设置 gl_FragColor。
// 绘制点的顶点着色器
attribute vec3 a_position;
uniform float u_pointSize; // 从 JS 传递大小
void main() {
gl_Position = vec4(a_position, 1.0);
gl_PointSize = u_pointSize; // 设置点的大小
}
// 绘制点的片段着色器
precision mediump float;
void main() {
gl_FragColor = vec4(1.0, 0.8, 0.0, 1.0); // 橙色
}

步骤 4: 链接着色器和缓冲区

获取 a_position 属性和任何 Uniform(如 u_pointSize)的位置。启用属性数组 (gl.enableVertexAttribArray) 并使用 gl.vertexAttribPointer 配置其数据源,使其指向 VBO。

步骤 5: 设置 Uniform 并绘制

使用 gl.uniform1f 为 u_pointSize Uniform 设置值。清除 canvas,设置视口 (viewport),然后使用 gl.drawArrays(gl.POINTS, first, count) 渲染点。count 应该是顶点缓冲区中定义的点数量。

gl.drawArrays(gl.POINTS, 0, 3); // 从索引 0 开始绘制 3 个点

这个完整的例子绘制了三个不同的橙色点。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebGL Drawing Points</title>
<style>
body { margin: 0; overflow: hidden; background-color: #222; }
canvas { display: block; border: 1px solid lightgray; }
</style>
</head>
<body>
<canvas id="webgl-canvas" width="500" height="500"></canvas>
<script>
// --- 着色器源码 ---
const vsSource = `
attribute vec3 a_position;
uniform float u_pointSize;
void main() {
gl_Position = vec4(a_position, 1.0);
gl_PointSize = u_pointSize;
}
`;
const fsSource = `
precision mediump float;
void main() {
gl_FragColor = vec4(1.0, 0.8, 0.0, 1.0); // 橙色
}
`;
// --- 工具函数:创建着色器(包含错误检查)---
function createShader(gl, type, source) { /* ... (same as previous example) ... */
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error(`着色器编译错误 (${type === gl.VERTEX_SHADER ? '顶点' : '片段'}):`, gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
// --- 工具函数:创建程序(包含错误检查)---
function createProgram(gl, vsSource, fsSource) { /* ... (same as previous example) ... */
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vsSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fsSource);
if (!vertexShader || !fragmentShader) return null;
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error('程序链接错误:', gl.getProgramInfoLog(program));
gl.deleteProgram(program);
return null;
}
return program;
}
// --- 主 WebGL 初始化 ---
const canvas = document.getElementById('webgl-canvas');
const gl = canvas.getContext('webgl');
if (!gl) {
alert('WebGL 不受支持。');
throw new Error('WebGL 不受支持');
}
// --- 着色器程序 ---
const shaderProgram = createProgram(gl, vsSource, fsSource);
if (!shaderProgram) throw new Error('创建着色器程序失败。');
// --- 几何体和缓冲区 ---
const pointVertices = new Float32Array([
-0.5, 0.5, 0.0, // 点 1(左上)
0.0, 0.5, 0.0, // 点 2(中上)
-0.25, 0.0, 0.0 // 点 3(左中)
]);
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, pointVertices, gl.STATIC_DRAW);
// --- 属性和 Uniform 位置 ---
const positionAttribLocation = gl.getAttribLocation(shaderProgram, 'a_position');
const pointSizeUniformLocation = gl.getUniformLocation(shaderProgram, 'u_pointSize');
// --- 位置错误检查 ---
if (positionAttribLocation < 0) console.error("未找到属性 'a_position'");
if (!pointSizeUniformLocation) console.error("未找到 Uniform 'u_pointSize'");
// --- 配置属性指针 ---
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); // 设置指针前先绑定
gl.vertexAttribPointer(
positionAttribLocation, // 位置
3, // 大小 (x, y, z)
gl.FLOAT, // 类型
false, // 是否标准化?
0, // 步长
0 // 偏移量
);
gl.enableVertexAttribArray(positionAttribLocation);
// --- 设置 Uniform 并绘制 ---
gl.useProgram(shaderProgram); // 设置 Uniform 前先激活程序
const desiredPointSize = 10.0;
gl.uniform1f(pointSizeUniformLocation, desiredPointSize);
// --- 绘制设置 ---
gl.clearColor(0.15, 0.15, 0.15, 1.0); // 深灰色背景
gl.enable(gl.DEPTH_TEST);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
// --- 绘制调用 ---
gl.drawArrays(
gl.POINTS, // 原始类型
0, // 起始索引
3 // 要绘制的点数量
);
</script>
</body>
</html>

预期输出:

这段代码将在深灰色背景上显示三个不同的橙色方形点(大小为 10x10 像素)。根据提供的坐标,一个点将靠近 canvas 左上角,一个靠近 canvas 中上部,一个靠近 canvas 左中部。

  • 改变 desiredPointSize 的值。
  • 在 pointVertices 数组中添加更多点,并更新 gl.drawArrays 中的数量。
  • 修改片段着色器以改变点的颜色。
  • 将点的大小设置为属性 (attribute float a_pointSize;),以便为每个点指定不同的大小(需要将大小数据添加到缓冲区并配置另一个属性指针)。