Grid in WebGL

Drawing the grid using the WebGL.


使用 WebGL 绘制可交互的网格

在前端图形编程中,网格是一种非常基础且常见的视觉元素。无论是作为坐标参考系,还是作为可视化背景,网格都能帮助我们更好地理解空间关系。本文将通过一个完整的 WebGL 示例,展示如何绘制一个动态的、可交互的网格,并且支持直角坐标和极坐标两种模式。

你将看到一个可交互的网格。直角网格下,背景为简单的红蓝渐变;极坐标模式下,背景变为色环,网格线沿角度和半径方向分布。通过调节参数,你可以直观地看到线条粗细、密度以及边缘柔和度的变化。

程序概览

这个程序使用原生 WebGL 在画布上绘制网格,并通过 dat.GUI 提供了实时控制参数的面板。核心功能包括:

核心技术点

1. WebGL 上下文与画布适配

代码首先获取 canvas 元素,并设置其尺寸为父容器宽度的 16:9 比例。通过监听 resize 事件,画布可以在窗口大小改变时自适应调整。

function resizeCanvas() {
  const w = main.clientWidth;
  canvas.width = w;
  canvas.height = w / ratio;
  if (gl) gl.viewport(0, 0, canvas.width, canvas.height);
}

2. 着色器代码组织

为了保持代码清晰,作者将所有 GLSL 代码片段定义在一个 glsl 对象中,包括:

3. 顶点着色器

顶点着色器非常简单:接收一个二维顶点位置(范围 -1 到 1),将其乘以宽高比 u_ratio 后传递给片段着色器。这样做是为了保持网格在不同屏幕比例下显示为均匀的方格。

attribute vec2 position;
uniform float u_ratio;
varying vec2 v_position;

void main() {
    gl_Position = vec4(position, 0.0, 1.0);
    v_position = vec2(position.x * u_ratio, position.y);
}

4. 片段着色器与网格生成

片段着色器包含了两种网格模式的核心逻辑:

wireframe 函数解析

wireframe 函数是实现高质量网格的关键。它利用了导数的思想,根据屏幕空间的变化率自适应地调整线条宽度,从而避免锯齿。

核心思路:对输入的参数(如坐标)取小数部分,并计算到最近整数线的距离。通过 fwidth 获得该参数在屏幕上的变化率,结合线宽和羽化值,使用 smoothstep 生成平滑过渡的线条。

float wireframe (vec2 parameter, float width, float feather) {
    float w1 = width - feather * 0.5;
    vec2 d = fwidth(parameter);
    vec2 looped = 0.5 - abs(mod(parameter, 1.0) - 0.5);
    vec2 a2 = smoothstep(d * w1, d * (w1 + feather), looped);
    return min(a2.x, a2.y);
}

颜色混合

在片段着色器中,首先根据模式计算基础颜色(直角坐标下为简单的渐变,极坐标下使用 HSV 生成色环),然后将网格因子与背景混合,使网格线呈现白色半透明效果。

gridFactor = 1.0 - wireframe(...);
gl_FragColor = mix(vec4(rgb, 1.0), vec4(vec3(1.0), gridFactor), gridFactor*0.5);

5. 数据传递与 GUI 控制

通过 dat.GUI 创建控制面板,调整以下参数:

每次参数改变时,更新对应的 uniform 值。

6. 渲染循环

使用 requestAnimationFrame 驱动持续渲染,保证画布实时更新。

总结

这个示例展示了 WebGL 在绘制基本图形方面的灵活性。通过组合简单的顶点数据和强大的片段着色器,我们可以实现各种复杂的视觉效果。wireframe 函数的技巧尤其值得借鉴,它利用屏幕空间导数实现了设备无关的线条渲染,是抗锯齿线条的常用方法。

希望这篇文章能帮助你理解 WebGL 渲染管线以及如何利用着色器绘制高质量的网格。完整代码可以在文末找到,你也可以在此基础上扩展更多功能,比如动态动画、纹理映射等。