5

JavaScript WebGL 绘制一个面

 2 years ago
source link: https://segmentfault.com/a/1190000041144928
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

JavaScript WebGL 基础疑惑点之后进行了一些优化,然后尝试绘制常见二维的面。

WebGL 中几何体最终都是由三角形构成,由三角形切入比较合适。

绘制三角形

这是示例,基于绘制一条直线主要的变化有:

三角形有三个顶点,在基础疑惑点中知道坐标系是右手坐标系,个人习惯描述顶点的顺序以图形中心为原点,从第一象限到第四象限。

  let vertices = [
    0.5, 0.5, 0.0, // 第一象限
    -0.5, 0.5, 0.0, // 第二象限
    -0.5, -0.5, 0.0, // 第三象限
  ]; // 三角形

这次绘制的是一个面,drawArrays 中绘制模式变为 gl.TRIANGLES 。顺便看看图元的几种模式。

  • gl.POINTS : 绘制一系列点。
  • gl.LINES :绘制一系列单独线段,每两个点作为端点,线段之间不连接。例如有顶点 A、B、C、D、E、F,就会得到了三条线段。

94-lines

  • gl.LINE_STRIP : 绘制一系列线段,上一点连接下一点。

94-line-strip

  • gl.LINE_LOOP : 绘制一系列线段,上一点连接下一点,并且最后一点与第一个点相连。

94-line-loop

  • gl.TRIANGLES : 绘制一系列三角形,每三个点作为顶点。例如有 6 个顶点 A、B、C、D、E、F,就会绘制 2 个三角形: ABC 和 DEF。

94-triangles

  • gl.TRIANGLE_STRIP : 用来绘制有共享边的三角形。从第二个三角形开始,每次读取一个顶点,并利用前面的末尾两个顶点构成一个三角形,以此类推。例如有 6 个顶点 A、B、C、D、E、F,就会绘制 4 个三角形: ABC 和 BCD 和 CDE 和 DEF。

94-triangle-strip

  • gl.TRIANGLE_FAN : 绘制有共享边的三角形。从第二个三角形开始,每次读取一个顶点,并利用首个顶点和之前最后一个顶点来构成一个三角形,以此类推。例如有 6 个顶点 A、B、C、D、E、F,就会绘制 4 个三角形: ABC 和 ACD 和 ADE 和 AEF。

94-triangle-fan

这里有一个绘制三角形执行过程可视化,结合看看有助于加深理解。

上面的示例,在高清屏幕中会出现明显的模糊和锯齿,但跟处理 2d 上下文的模糊又有些不一样。最主要的一个区别是 WebGL 中需要用 viewport 方法指定从标准设备到窗口坐标的映射变换。详细可以见这篇文章里面的解释。

这是高清示例

  function WebGLHD(w = 300, h = 150) {
    const ratio = window.devicePixelRatio || 1;
    const canvas = document.createElement("canvas");
    const context = canvas.getContext("webgl");
    // 高清屏幕模糊问题处理
    canvas.width = w * ratio; // 实际渲染像素
    canvas.height = h * ratio; // 实际渲染像素
    canvas.style.width = `${w}px`; // 控制显示大小
    canvas.style.height = `${h}px`; // 控制显示大小
    context.viewport(0, 0, context.canvas.width, context.canvas.height);
  }

前面有说 WebGL 中几何体最终都是由三角形构成,在绘制多边形的时候需要分解为多个三角形。

这是示例,一个矩形可以分为两个三角形:

  let vertices = [
    0.5, 0.5, 0.0,
    -0.5, 0.5, 0.0,
    -0.5, -0.5, 0.0, // 第一个三角形
    -0.5, -0.5, 0.0,
    0.5, -0.5, 0.0,
    0.5, 0.5, 0.0, // 第二个三角形
  ]; // 矩形

可以发现有一条边是公共,这个时候可以索引缓冲区对象减少冗余的数据。

索引缓冲对象

索引缓冲对象全称是 Index Buffer Object(IBO),通过索引的方式复用已有的数据。

基于上面正方形的示例,主要的变化有以下几方面:

顶点位置数据只需要 4 个就足够了,公共数据使用索引代替。

  const vertices = [
    0.5, 0.5, 0.0, // 第 1 个顶点
    -0.5, 0.5, 0.0, // 第 2 个顶点
    -0.5, -0.5, 0.0, // 第 3 个顶点
    0.5, -0.5, 0.0, // 第 4 个顶点
  ]; // 矩形

索引数据跟上面提示到的图元绘制模式有关。

绘制模式为 gl.TRIANGLES 时,两个三角形是独立的,索引数据如下:

const indexData = [
  0, 1, 2, // 对应顶点位置数据中 1、2、3 顶点的索引
  0, 2, 3, // 对应顶点位置数据中 1、3、4 顶点的索引
]

绘制模式为 gl.TRIANGLE_STRIP 时,利用前一个三角形末尾的两个顶点构建三角形:

const indexData = [
  1, 0, 2, 3 // 绘制时,先取索引 1、0、2 的位置数据绘制第一个三角形,然后再取索引 0、2、3 的位置数据 绘制第二个三角形
]

绘制模式为 gl.TRIANGLE_FAN 时,利用第一个顶点,和前一个三角形末尾的一个顶点,加上新读取的顶点构建三角形:

const indexData = [
  0, 1, 2, 3 // 绘制时,先取索引 0、1、2 的位置数据绘制第一个三角形,然后再取索引 0、2、3 的位置数据 绘制第二个三角形
]

索引的数据需要缓冲到对应的变量才能使用。

/**
 * 缓冲索引数据
 * @param {*} gl WebGL 上下文
 * @param {*} data 索引数据
 */
function setIndexBuffers(gl, data) {
  // 创建空白的缓冲对象
  const buffer = gl.createBuffer();
  // 绑定目标
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer);
  // WebGL 不支持直接使用 JavaScript 原始数组类型,需要转换
  const dataFormat = new Uint16Array(data);
  // 初始化数据存储
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, dataFormat, gl.STATIC_DRAW);
}

使用了索引缓冲对象,需要用 drawElements 方法来替代 drawArrays。该方法多了一个 type 参数,指的是索引缓冲数据的类型,有下面的值可取:

  • gl.UNSIGNED_BYTE
  • gl.UNSIGNED_SHORT

前面缓冲索引数据类型转换为了 Uint16Array ,这里应该使用 gl.UNSIGNED_SHORT

三种方式示例如下:


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK