WebGL简介与入门(绘制一个点)

198 阅读7分钟

WebGL(WebGL)是Web Graphics Library的缩写,它是一种用于在浏览器中渲染3D图形和2D图形的JavaScript API。WebGL使开发者可以在无需安装插件的情况下,通过浏览器在网页上创建复杂的三维图形和动画,它使用了OpenGL ES 2.0作为底层技术。

1 WebGL 的概述

  • 跨平台性:WebGL 能够在任何支持 HTML5 的浏览器中运行,无论是桌面、移动设备,还是其他设备。
  • 硬件加速:WebGL 直接调用 GPU(图形处理器)来进行图形处理,这使得其性能大大优于仅依赖 CPU 的图形渲染方式。
  • 开放标准:WebGL 由Khronos Group维护,这是一个致力于制定开放标准的联盟,其他成员还包括 Apple、Google、Microsoft 等。
  • 主要用途:主要用于网页中的实时3D游戏、互动式3D模型展示、数据可视化、虚拟现实(VR)等。

2 WebGL 的历史

  • 2006-2007:WebGL的雏形可以追溯到 Mozilla 的一名工程师 Vladimir Vukićević 提出的一个概念:将 OpenGL ES 2.0 的功能引入浏览器,进而形成浏览器中的3D API。
  • 2009年:Khronos Group 成立了 WebGL 工作组,致力于开发一个标准化的 WebGL API。此时 WebGL 已经在 Firefox 和 Chrome 中有了早期的实现。
  • 2011年3月:WebGL 1.0 正式发布,这是一个基于 OpenGL ES 2.0 的3D渲染API。Firefox、Chrome、Safari 等浏览器逐渐开始支持 WebGL 1.0。
  • 2017年:WebGL 2.0 发布,这是 WebGL 的重大更新,基于 OpenGL ES 3.0,带来了更多功能,比如多重渲染目标、改进的纹理支持等。

WebGL 通过 JavaScript 直接调用底层的 OpenGL 功能,极大地扩展了网页的图形渲染能力。在 WebGL 的推动下,浏览器成为了一个强大的3D图形渲染平台,推动了 Web 游戏、虚拟现实以及其他互动式图形应用的发展。

图 1.1

3 绘制一个点

使用 gl.POINTS 绘制点

gl.POINTS 是 WebGL 提供的一种绘制方式,最常见的绘制点的方法就是通过该模式。

3.1 步骤:

  1. 创建 WebGL 上下文:获取 Canvas 上的 WebGL 上下文。
  2. 定义顶点着色器和片段着色器:着色器会处理如何将顶点绘制到屏幕上。
  3. 传递顶点数据:将表示点的顶点坐标传递到着色器中。
  4. 设置绘制点的大小:可以通过 gl_PointSize 设置点的大小。

3.2 代码示例:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <style>
    #canvas {
      width: 600px;
      height: 600px;
      position: absolute;
      top: calc(50% - 300px);
      left: calc(50% - 300px);
      background-color: black;
    }
  </style>
  <body>
    <canvas id="canvas"></canvas>
    <script>
      // 通过元素获取二维图形的绘图上下文
      const canvas = document.getElementById("canvas");
      const gl = canvas.getContext("webgl");

      // 定义顶点着色器
      const vertexShaderSource = `
    attribute vec4 a_Position;
    void main() {
        gl_Position = a_Position; // 设置点的坐标
        gl_PointSize = 10.0;      // 设置点的大小
    }
`;

      // 定义片段着色器
      const fragmentShaderSource = `
    void main() {
        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 设置点的颜色为红色
    }
`;

      // 编译着色器函数
      function createShader(gl, type, source) {
        const shader = gl.createShader(type);
        gl.shaderSource(shader, source);
        gl.compileShader(shader);
        return shader;
      }

      // 创建和链接着色器程序
      const vertexShader = createShader(
        gl,
        gl.VERTEX_SHADER,
        vertexShaderSource
      );
      const fragmentShader = createShader(
        gl,
        gl.FRAGMENT_SHADER,
        fragmentShaderSource
      );
      const program = gl.createProgram();
      gl.attachShader(program, vertexShader);
      gl.attachShader(program, fragmentShader);
      gl.linkProgram(program);
      gl.useProgram(program);

      // 设置顶点数据
      const vertices = new Float32Array([
        0.0,
        0.0,
        0.0, // 在画布中心绘制一个点
      ]);
      const buffer = gl.createBuffer();
      gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
      gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

      // 连接顶点着色器的a_Position变量
      // 获取a_Position变量存储位置
      const a_Position = gl.getAttribLocation(program, "a_Position");
      gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, 0, 0);
      gl.enableVertexAttribArray(a_Position);

      // 设置背景颜色并清空
      gl.clearColor(0.0, 0.0, 0.0, 1.0);
      gl.clear(gl.COLOR_BUFFER_BIT);

      // 绘制点
      gl.drawArrays(gl.POINTS, 0, 1);
    </script>
  </body>
</html>

整个流程大致如下:

  1. 创建缓冲区对象
  2. 将缓冲区对象绑定到target对象上
  3. 将顶点数据写入缓存区
  4. 将缓冲区对象分配给对象的attribute变量
  5. 开启attribute变量
  6. 根据类型绘制点

注:

const vertices = new Float32Array([
  1.00.0,
  0.0,
  0.0, // 在画布中心绘制一个点
]);
gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, 0, 4);

如果这样设置也可以  就会从buffer数组的第五个字节开始读取,Float32四个字节

3.3 知识点

3.3.1 attribute vec4

attribute:存储限定符,表示接下来的变量是一个attribute变量,attribute变量只有顶点着色器才能使用它;

vec4:数据类型,表示四个浮点数组成的矢量。

这是GLSL ES数据类型

3.3.2 WebGL需要两种着色器:

顶点着色器(Vertex shader)

顶点着色器用来描述顶点特性(如位置、颜色)的程序。顶点是指二维或者三维空间中的一个点,比如二维或者三维图形的端点或者交点。

片元着色器(Fragment shader)

进行逐片元处理过程如光照的程序,片元是一个WegGL术语,可以理解为一个像素。

后面会单开一个章节详细介绍OpenGL ES的着色器

4 通过attribute变量

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <style>
    #canvas {
      width: 600px;
      height: 600px;
      position: absolute;
      top: calc(50% - 300px);
      left: calc(50% - 300px);
      background-color: black;
    }
  </style>
  <body>
    <canvas id="canvas"></canvas>
    <script>
      // 通过元素获取二维图形的绘图上下文
      const canvas = document.getElementById("canvas");
      const gl = canvas.getContext("webgl");

      // 定义顶点着色器
      const vertexShaderSource = `
    attribute vec4 a_Position;
    void main() {
        gl_Position = a_Position; // 设置点的坐标
        gl_PointSize = 10.0;      // 设置点的大小
    }
`;

      // 定义片段着色器
      const fragmentShaderSource = `
    void main() {
        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 设置点的颜色为红色
    }
`;

      // 编译着色器函数
      function createShader(gl, type, source) {
        const shader = gl.createShader(type);
        gl.shaderSource(shader, source);
        gl.compileShader(shader);
        return shader;
      }

      // 创建和链接着色器程序
      const vertexShader = createShader(
        gl,
        gl.VERTEX_SHADER,
        vertexShaderSource
      );
      const fragmentShader = createShader(
        gl,
        gl.FRAGMENT_SHADER,
        fragmentShaderSource
      );
      const program = gl.createProgram();
      gl.attachShader(program, vertexShader);
      gl.attachShader(program, fragmentShader);
      gl.linkProgram(program);
      gl.useProgram(program);

      // 连接顶点着色器的a_Position变量
      // 获取a_Position变量存储位置
      const a_Position = gl.getAttribLocation(program, "a_Position");
      gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);

      // 设置背景颜色并清空
      gl.clearColor(0.0, 0.0, 0.0, 1.0);
      gl.clear(gl.COLOR_BUFFER_BIT);

      // 绘制点
      gl.drawArrays(gl.POINTS, 0, 1);
    </script>
  </body>
</html>

问题核心在于如何给attribute变量 a_Position赋值,前面通过vertexAttribPointer指向缓冲区数据,这里采用

attribute同族函数赋值。

4.1 知识点、

WebGLRenderingContext.vertexAttrib[1234]fv

void gl.vertexAttrib1f(location, v0);
void gl.vertexAttrib2f(location, v0, v1);
void gl.vertexAttrib3f(location, v0, v1, v2);
void gl.vertexAttrib4f(location, v0, v1, v2, v3);

将数据传输给location参数指定的attribute变量中。gl.vertexAttrib1f仅仅传输一个值,这个值将填充到location的第一个分量中,第2、3个分量被设置为0.0,第四个分量被设置为1.0。类似地gl.vertexAttrib2f将填充两个分量,第三个分量为0.0,第四个分量为1.0。

WebGL相关函数命名规范

你可能想知道 gl.vertexAttrib3f中的3f是什么意思。WebGL中的函数命名遵循OpenGL ES 2.0中的函数名。Open GL函数名由三部分组成:<基础函数名><参数个数><参数类型>。WebGL的函数命名使用同样的结构。

5 通过鼠标点击绘点

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <style>
    #canvas {
      width: 600px;
      height: 600px;
      position: absolute;
      top: calc(50% - 300px);
      left: calc(50% - 300px);
      background-color: black;
    }
  </style>
  <body>
    <canvas id="canvas"></canvas>
    <script>
      // 通过元素获取二维图形的绘图上下文
      const canvas = document.getElementById("canvas");
      const gl = canvas.getContext("webgl");

      // 定义顶点着色器
      const vertexShaderSource = `
          attribute vec4 a_Position;
          void main() {
              gl_Position = a_Position; // 设置点的坐标
              gl_PointSize = 10.0;      // 设置点的大小
          }
      `;

      // 定义片段着色器
      const fragmentShaderSource = `
          void main() {
              gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 设置点的颜色为红色
          }
      `;

      // 编译着色器函数
      function createShader(gl, type, source) {
        const shader = gl.createShader(type);
        gl.shaderSource(shader, source);
        gl.compileShader(shader);
        return shader;
      }

      // 创建和链接着色器程序
      const vertexShader = createShader(
        gl,
        gl.VERTEX_SHADER,
        vertexShaderSource
      );
      const fragmentShader = createShader(
        gl,
        gl.FRAGMENT_SHADER,
        fragmentShaderSource
      );
      const program = gl.createProgram();
      gl.attachShader(program, vertexShader);
      gl.attachShader(program, fragmentShader);
      gl.linkProgram(program);
      gl.useProgram(program);

      const points = [];
      canvas.onmousedown = function (e) {
        let x = e.clientX;
        let y = e.clientY;
        // 获取元素大小及相对于视口位置
        const rect = e.target.getBoundingClientRect();
        const width = rect.width;
        const height = rect.height;
        x = (x - rect.left - width / 2) / (width / 2);
        y = (height / 2 - (y - rect.top)) / (height / 2);
        points.push(x, y);

        // 设置背景颜色并清空
        gl.clearColor(0.0, 0.0, 0.0, 1.0);
        gl.clear(gl.COLOR_BUFFER_BIT);

        // 连接顶点着色器的a_Position变量
        const a_Position = gl.getAttribLocation(program, "a_Position");
        for (let index = 0; index < points.length; index += 2) {
          gl.vertexAttrib3f(a_Position, points[index], points[index + 1], 0.0);

          // 绘制点
          gl.drawArrays(gl.POINTS, 0, 1);
        }
      };
    </script>
  </body>
</html>

5.1 知识点

因为鼠标点击位置是相对于视口的,所以要进行坐标转换,转换到Cancas坐标系下;Canvas坐标系与WebGL 坐标系不一样,所以还需要转换到WebGL坐标系下

x = (x - rect.left - width / 2) / (width / 2);
y = (height / 2 - (y - rect.top)) / (height / 2);

Canvas与WebGL坐标系区别

特性CanvasWebGL
坐标维度2D 坐标系统 (X, Y)3D 坐标系统 (X, Y, Z)
坐标原点左上角 (0, 0)屏幕中心 (0, 0)
坐标范围X 和 Y 坐标取决于画布的像素大小X 和 Y 坐标在 [-1, 1] 范围内
Y 轴方向向下为正向上为正
Z 轴Z 轴控制深度,范围为 [-1, 1]
使用场景主要用于 2D 图形和简单的动画绘制用于 3D 图形渲染,支持复杂的图形变换