WebGL 的第一步

2025-06-08

WebGL 的第一步

原文:https ://aralroca.com/blog/first-steps-in-webgl

在本文中,我们将了解什么是 WebGL,以及如何通过与图形处理单元 (GPU) 交互来绘制三角形。虽然这个简单的例子可以用更好的方法解决,例如使用带有 2D 上下文的画布,甚至使用 CSS,但我们的目标是从 WebGL 入手,就像“Hello World”一样,了解它的工作原理。

三角形
摄影:Apurv Das (Unsplash)

我们将介绍以下内容:

什么是 WebGL?

WebGL 的字面定义是“Web 图形库”。然而,它并不是一个提供简单易用的 API 的 3D 库,无法让我们像在这儿放个光源,在那儿放个摄像头,在这儿画个角色等等。

它处于底层,将顶点转换为像素。我们可以将 WebGL 理解为一个光栅化引擎。它基于 OpenGL ES 3.0 图形 API(WebGL 2.0,不同于基于 ES 2.0 的旧版本)。

WebGL 架构

网络上现有的 3D 库(例如THREE.jsBabylon.js)在下面使用了 WebGL。它们需要一种方式与 GPU 通信,以指示要绘制的内容。

这个例子也可以直接用 THREE.js 来解决,使用。你可以在这里THREE.Triangle看到一个例子。然而,本教程的目的是理解其底层工作原理,即这些 3D 库如何通过 WebGL 与 GPU 通信。我们将在不使用任何 3D 库的情况下渲染一个三角形。

让我们探索如何在没有 3D 库的情况下制作三角形

创建 WebGL 画布

为了绘制三角形,我们需要定义通过 WebGL 渲染的区域。

我们将使用 HTML5 的元素画布,以 的形式检索上下文webgl2

import { useRef, useEffect } from 'preact/hooks'

export default function Triangle() {
  const canvas = useRef()

  useEffect(() => {
    const bgColor = [0.47, 0.7, 0.78, 1] // r,g,b,a as 0-1
    const gl = canvas.current.getContext('webgl2') // WebGL 2.0

    gl.clearColor(bgColor) // set canvas background color
    gl.clear(gl.DEPTH_BUFFER_BIT | gl.COLOR_BUFFER_BIT) // clear buffers
    // @todo: Render the triangle...
  }, [])

  return <canvas style={{ width: '100vw', height: '100vh' }} ref={canvas} />
}
Enter fullscreen mode Exit fullscreen mode

clearColor方法使用 RGBA(值从 0 到 1)设置画布的背景颜色。

此外,该clear方法会将缓冲区清空为预设值。使用的常量值将取决于您的 GPU 容量。

一旦我们创建了画布,我们就可以使用 WebGL 渲染内部三角形了...让我们看看如何操作。

顶点坐标

首先,我们需要知道所有这些向量的范围都是从 -1 到 1。

画布的角落:

坐标

  • (0, 0) -中心
  • (1, 1) - 右上角
  • (1,-1) - 右下角
  • (-1, 1) - 左上角
  • (-1,-1) - 左下角

我们要绘制的三角形有以下三个点:

坐标

(-1,-1)(0,1)(1,-1)。因此,我们将三角形坐标存储到一个数组中:

const coordinates = [-1, -1, 0, 1, 1, -1]
Enter fullscreen mode Exit fullscreen mode

GLSL 和着色器

着色器是计算机图形学中用于计算渲染效果的一种程序,具有高度的灵活性。这些着色器使用 OpenGL ES 着色语言 (GLSL ES)(一种类似于 C 或 C++ 的语言)编写,并在 GPU 上运行。

WebGL着色器

我们要运行的每个 WebGL 程序都由两个着色器函数组成:顶点着色器片段着色器

几乎所有的 WebGL API 都是以不同的方式运行这两个函数(顶点和片段着色器)。

顶点着色器

顶点着色器的作用是计算顶点的位置。GPU 根据计算结果 ( gl_Position ) 来定位视口上的点、线和三角形。

为了写入三角形,我们将创建这个顶点着色器:

const vertexShader = `#version 300 es
  precision mediump float;
  in vec2 position;

  void main () {
      gl_Position = vec4(position.x, position.y, 0.0, 1.0); // x,y,z,w
  }
`
Enter fullscreen mode Exit fullscreen mode

我们现在可以将其作为模板字符串保存在我们的 JavaScript 代码中。

第一行(#version 300 es)告诉我们正在使用的 GLSL 版本。

第二行(precision mediump float;)决定 GPU 计算浮点数时使用的精度。可用选项包括highpmediumplowp,但某些系统不支持highp

在第三行(in vec2 position;)中,我们为GPU定义了一个二维的输入变量(X,Y)。三角形的每个向量都是二维的。

main函数在程序初始化后(类似 C/C++)启动时调用。GPU 将gl_Position = vec4(position.x, position.y, 0.0, 1.0);通过将gl_Position当前顶点的位置保存到 来运行其内容( )。第一个和第二个参数是 和xy距离我们的vec2位置。第三个参数是z轴,在本例中是 ,0.0因为我们是在二维空间中创建几何体,而不是三维空间。最后一个参数是w,默认情况下应设置为1.0

GLSL 内部识别并使用的值gl_Position

一旦我们创建了着色器,我们就应该编译它:

const vs = gl.createShader(gl.VERTEX_SHADER)

gl.shaderSource(vs, vertexShader)
gl.compileShader(vs)

// Catch some possible errors on vertex shader
if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS)) {
  console.error(gl.getShaderInfoLog(vs))
}
Enter fullscreen mode Exit fullscreen mode

片段着色器

在“顶点着色器”之后,执行“片段着色器”。该着色器的作用是计算每个位置对应的每个像素的颜色。

对于三角形,让我们用相同的颜色填充:

const fragmentShader = `#version 300 es
  precision mediump float;
  out vec4 color;

  void main () {
      color = vec4(0.7, 0.89, 0.98, 1.0); // r,g,b,a
  }
`
const fs = gl.createShader(gl.FRAGMENT_SHADER)

gl.shaderSource(fs, fragmentShader)
gl.compileShader(fs)

// Catch some possible errors on fragment shader
if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)) {
  console.error(gl.getShaderInfoLog(fs))
}
Enter fullscreen mode Exit fullscreen mode

语法与上一个非常相似,尽管vect4我们这里返回的 指的是每个像素的颜色。由于我们想用 填充三角形rgba(179, 229, 252, 1),因此我们将通过将每个 RGB 数字除以 255 来平移它。

从着色器创建程序

一旦我们编译了着色器,我们就需要创建运行 GPU 的程序,并添加两个着色器。

const program = gl.createProgram()
gl.attachShader(program, vs) // Attatch vertex shader
gl.attachShader(program, fs) // Attatch fragment shader
gl.linkProgram(program) // Link both shaders together
gl.useProgram(program) // Use the created program

// Catch some possible errors on program
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
  console.error(gl.getProgramInfoLog(program))
}
Enter fullscreen mode Exit fullscreen mode

创建缓冲区

我们将使用缓冲区为 GPU 分配内存,并将该内存绑定到用于 CPU-GPU 通信的通道。我们将使用此通道将三角形坐标发送到 GPU。

// allowcate memory to gpu
const buffer = gl.createBuffer()

// bind this memory to a channel
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)

// use this channel to send data to the GPU (our triangle coordinates)
gl.bufferData(
  gl.ARRAY_BUFFER,
  new Float32Array(coordinates),
  // In our case is a static triangle, so it's better to tell
  // how are we going to use the data so the WebGL can optimize
  // certain things.
  gl.STATIC_DRAW
)

// desallocate memory after send data to avoid memory leak issues
gl.bindBuffer(gl.ARRAY_BUFFER, null)
Enter fullscreen mode Exit fullscreen mode

缓冲

将数据从 CPU 链接到 GPU

在顶点着色器,我们定义了一个名为的输入变量position。但是,我们尚未指定此变量应采用通过缓冲区传递的值。我们必须按以下方式指定它:

const position = gl.getAttribLocation(program, 'position')
gl.enableVertexAttribArray(position)
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
gl.vertexAttribPointer(
  position, // Location of the vertex attribute
  2, // Dimension - 2D
  gl.FLOAT, // Type of data we are going to send to GPU
  gl.FALSE, // If data should be normalized
  0, // Stride
  0 // Offset
)
Enter fullscreen mode Exit fullscreen mode

绘制三角形

一旦我们为三角形创建了带有着色器的程序,并创建了链接缓冲区以将数据从 CPU 发送到 GPU,我们最终就可以告诉 GPU 渲染三角形!

三角肌练习

gl.drawArrays(
  gl.TRIANGLES, // Type of primitive
  0, // Start index in the array of vector points
  3 // Number of indices to be rendered
)
Enter fullscreen mode Exit fullscreen mode

此方法根据数组数据渲染图元。图元可以是点、线或三角形。我们来指定gl.TRIANGLES

所有代码放在一起

我已将文章代码上传至 CodeSandbox,以便您探索。

结论

WebGL 只能绘制三角形、直线或点,因为它只能进行光栅化,所以你只能做矢量能做的事情。这意味着 WebGL 的概念很简单,但处理过程却相当复杂……而且根据你想要开发的内容,它会变得越来越复杂。光栅化一个二维三角形和处理一个包含纹理、变量、变换等元素的 3D 电子游戏是不一样的……

希望本文能帮助您理解 WebGL 的工作原理。建议您阅读以下参考资料。

参考

鏂囩珷鏉ユ簮锛�https://dev.to/aralroca/first-steps-in-webgl-385c
PREV
使用 document.designMode 在 Google Chrome 中实时编辑您的网站
NEXT
Podman:Docker 的替代品?