npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

twgl

v7.0.3

Published

A Tiny WebGL helper library

Downloads

5,154

Readme

[非官方,只是学习过程中添加中文注释]

=====================================================

构建状态

TWGL: 一个极简的 WebGL 辅助库。该库的唯一目的是让使用 WebGL API 更加简洁。

太长不看版 (TL;DR)

如果想快速完成任务,请使用 three.js。如果想用低级方式操作 WebGL,可以考虑使用 TWGL

最简单的例子

不包括着色器(这是一个简单的四边形着色器),下面是完整的代码:

<canvas id="c"></canvas>
<script src="../dist/7.x/twgl-full.min.js"></script>
<script>
  const gl = document.getElementById("c").getContext("webgl");
  const programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);

  const arrays = {
    position: [-1, -1, 0, 1, -1, 0, -1, 1, 0, -1, 1, 0, 1, -1, 0, 1, 1, 0],
  };
  const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);

  function render(time) {
    twgl.resizeCanvasToDisplaySize(gl.canvas);
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

    const uniforms = {
      time: time * 0.001,
      resolution: [gl.canvas.width, gl.canvas.height],
    };

    gl.useProgram(programInfo.program);
    twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
    twgl.setUniforms(programInfo, uniforms);
    twgl.drawBufferInfo(gl, bufferInfo);

    requestAnimationFrame(render);
  }
  requestAnimationFrame(render);
</script>

在线演示.

为什么?是什么?怎么做?

WebGL 是一个非常冗长的 API。设置着色器、缓冲区、属性和统一变量需要大量代码。一个简单的带光照的立方体在 WebGL 中可能会轻松调用超过 60 次 API。

其核心功能只有以下几个主要函数:

  • twgl.createProgramInfo 编译着色器并创建属性和统一变量的设置器
  • twgl.createBufferInfoFromArrays 创建缓冲区和属性设置
  • twgl.setBuffersAndAttributes 绑定缓冲区并设置属性
  • twgl.setUniforms 设置统一变量
  • twgl.createTextures 创建各种类型的纹理
  • twgl.createFramebufferInfo 创建帧缓冲区和附件

还有一些额外的辅助函数和较低级别的函数,但以上 6 个函数是 TWGL 的核心。

比较使用 TWGL 和原生 WebGL 实现带点光源的立方体的代码。

编译着色器并查找位置

TWGL

const programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);

WebGL

// 注意:这里假设你已经有 30 行用于编译 GLSL 的代码
const program = twgl.createProgramFromScripts(gl, ["vs", "fs"]);

const u_lightWorldPosLoc = gl.getUniformLocation(program, "u_lightWorldPos");
const u_lightColorLoc = gl.getUniformLocation(program, "u_lightColor");
const u_ambientLoc = gl.getUniformLocation(program, "u_ambient");
const u_specularLoc = gl.getUniformLocation(program, "u_specular");
const u_shininessLoc = gl.getUniformLocation(program, "u_shininess");
const u_specularFactorLoc = gl.getUniformLocation(program, "u_specularFactor");
const u_diffuseLoc = gl.getUniformLocation(program, "u_diffuse");
const u_worldLoc = gl.getUniformLocation(program, "u_world");
const u_worldInverseTransposeLoc = gl.getUniformLocation(program, "u_worldInverseTranspose");
const u_worldViewProjectionLoc = gl.getUniformLocation(program, "u_worldViewProjection");
const u_viewInverseLoc = gl.getUniformLocation(program, "u_viewInverse");

const positionLoc = gl.getAttribLocation(program, "a_position");
const normalLoc = gl.getAttribLocation(program, "a_normal");
const texcoordLoc = gl.getAttribLocation(program, "a_texcoord");

为立方体创建缓冲区

TWGL

const arrays = {
  position: [1,1,-1,1,1,1,1,-1,1,1,-1,-1,-1,1,1,-1,1,-1,-1,-1,-1,-1,-1,1,-1,1,1,1,1,1,1,1,-1,-1,1,-1,-1,-1,-1,1,-1,-1,1,-1,1,-1,-1,1,1,1,1,-1,1,1,-1,-1,1,1,-1,1,-1,1,-1,1,1,-1,1,-1,-1,-1,-1,-1],
  normal:   [1,0,0,1,0,0,1,0,0,1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,0,1,0,0,1,0,0,1,0,0,1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,0,1,0,0,1,0,0,1,0,0,1,0,0,-1,0,0,-1,0,0,-1,0,0,-1],
  texcoord: [1,0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1,0,0,0,0,1,1,1],
  indices:  [0,1,2,0,2,3,4,5,6,4,6,7,8,9,10,8,10,11,12,13,14,12,14,15,16,17,18,16,18,19,20,21,22,20,22,23],
};
const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);

WebGL

const positions = [1,1,-1,1,1,1,1,-1,1,1,-1,-1,-1,1,1,-1,1,-1,-1,-1,-1,-1,-1,1,-1,1,1,1,1,1,1,1,-1,-1,1,-1,-1,-1,-1,1,-1,-1,1,-1,1,-1,-1,1,1,1,1,-1,1,1,-1,-1,1,1,-1,1,-1,1,-1,1,1,-1,1,-1,-1,-1,-1,-1];
const normals   = [1,0,0,1,0,0,1,0,0,1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,0,1,0,0,1,0,0,1,0,0,1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,0,1,0,0,1,0,0,1,0,0,1,0,0,-1,0,0,-1,0,0,-1,0,0,-1];
const texcoords = [1,0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1,0,0,0,0,1,1,1];
const indices   = [0,1,2,0,2,3,4,5,6,4,6,7,8,9,10,8,10,11,12,13,14,12,14,15,16,17,18,16,18,19,20,21,22,20,22,23];

const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
const normalBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normals), gl.STATIC_DRAW);
const texcoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texcoords), gl.STATIC_DRAW);
const indicesBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indicesBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);

设置立方体的属性和索引

TWGL

twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);

WebGL

gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.vertexAttribPointer(positionLoc, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(positionLoc);
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
gl.vertexAttribPointer(normalLoc, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(normalLoc);
gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
gl.vertexAttribPointer(texcoordLoc, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(texcoordLoc);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indicesBuffer);

设置带光照立方体的统一变量

TWGL

// 初始化时
const uniforms = {
  u_lightWorldPos: [1, 8, -10],
  u_lightColor: [1, 0.8, 0.8, 1],
  u_ambient: [0, 0, 0, 1],
  u_specular: [1, 1, 1, 1],
  u_shininess: 50,
  u_specularFactor: 1,
  u_diffuse: tex,
};

// 渲染时
uniforms.u_viewInverse = camera;
uniforms.u_world = world;
uniforms.u_worldInverseTranspose = m4.transpose(m4.inverse(world));
uniforms.u_worldViewProjection = m4.multiply(viewProjection, world);

twgl.setUniforms(programInfo, uniforms);

WebGL

// 初始化时
const u_lightWorldPos = [1, 8, -10];
const u_lightColor = [1, 0.8, 0.8, 1];
const u_ambient = [0, 0, 0, 1];
const u_specular = [1, 1, 1, 1];
const u_shininess = 50;
const u_specularFactor = 1;
const u_diffuse = 0;

// 渲染时
gl.uniform3fv(u_lightWorldPosLoc, u_lightWorldPos);
gl.uniform4fv(u_lightColorLoc, u_lightColor);
gl.uniform4fv(u_ambientLoc, u_ambient);
gl.uniform4fv(u_specularLoc, u_specular);
gl.uniform1f(u_shininessLoc, u_shininess);
gl.uniform1f(u_specularFactorLoc, u_specularFactor);
gl.uniform1i(u_diffuseLoc, u_diffuse);
gl.uniformMatrix4fv(u_viewInverseLoc, false, camera);
gl.uniformMatrix4fv(u_worldLoc, false, world);
gl.uniformMatrix4fv(u_worldInverseTransposeLoc, false, m4.transpose(m4.inverse(world)));
gl.uniformMatrix4fv(u_worldViewProjectionLoc, false, m4.multiply(viewProjection, world));

加载 / 设置纹理

TWGL

const textures = twgl.createTextures(gl, {
  // 2 的幂次图像
  hftIcon: { src: "images/hft-icon-16.png", mag: gl.NEAREST },
  // 非 2 的幂次图像
  clover: { src: "images/clover.jpg" },
  // 来自画布
  fromCanvas: { src: ctx.canvas },
  // 来自 6 张图像的立方体贴图
  yokohama: {
    target: gl.TEXTURE_CUBE_MAP,
    src: [
      'images/yokohama/posx.jpg',
      'images/yokohama/negx.jpg',
      'images/yokohama/posy.jpg',
      'images/yokohama/negy.jpg',
      'images/yokohama/posz.jpg',
      'images/yokohama/negz.jpg',
    ],
  },
  // 来自单张图像的立方体贴图(可以是 1x6、2x3、3x2、6x1)
  goldengate: {
    target: gl.TEXTURE_CUBE_MAP,
    src: 'images/goldengate.jpg',
  },
  // 来自 JavaScript 数组的 2x2 像素纹理
  checker: {
    mag: gl.NEAREST,
    min: gl.LINEAR,
    src: [
      255,255,255,255,
      192,192,192,255,
      192,192,192,255,
      255,255,255,255,
    ],
  },
  // 来自类型化数组的 1x8 像素纹理
  stripe: {
    mag: gl.NEAREST,
    min: gl.LINEAR,
    format: gl.LUMINANCE,
    src: new Uint8Array([
      255,
      128,
      255,
      128,
      255,
      128,
      255,
      128,
    ]),
    width: 1,
  },
});

WebGL

// 假设我已经加载了所有图片

// 2 的幂次图像
const hftIconTex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, hftIconImg);
gl.generateMipmaps(gl.TEXTURE_2D);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
// 非 2 的幂次图像
const cloverTex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, hftIconImg);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
// 来自画布
const cloverTex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, ctx.canvas);
gl.generateMipmaps(gl.TEXTURE_2D);
// 来自 6 张图像的立方体贴图
const yokohamaTex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_CUBE_MAP, tex);
gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, posXImg);
gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_X, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, negXImg);
gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_Y, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, posYImg);
gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, negYImg);
gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_Z, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, posZImg);
gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, negZImg);
gl.generateMipmaps(gl.TEXTURE_CUBE_MAP);
// 来自单张图像的立方体贴图(可以是 1x6、2x3、3x2、6x1)
const goldengateTex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_CUBE_MAP, tex);
const size = goldengate.width / 3;  // 假设是 3x2 纹理
const slices = [0, 0, 1, 0, 2, 0, 0, 1, 1, 1, 2, 1];
const tempCtx = document.createElement("canvas").getContext("2d");
tempCtx.canvas.width = size;
tempCtx.canvas.height = size;
for (let ii = 0; ii < 6; ++ii) {
  const xOffset = slices[ii * 2 + 0] * size;
  const yOffset = slices[ii * 2 + 1] * size;
  tempCtx.drawImage(element, xOffset, yOffset, size, size, 0, 0, size, size);
  gl.texImage2D(faces[ii], 0, format, format, type, tempCtx.canvas);
}
gl.generateMipmaps(gl.TEXTURE_CUBE_MAP);
// 来自 JavaScript 数组的 2x2 像素纹理
const checkerTex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2, 2, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([
    255,255,255,255,
    192,192,192,255,
    192,192,192,255,
    255,255,255,255,
  ]));
gl.generateMipmaps(gl.TEXTURE_2D);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
// 来自类型化数组的 1x8 像素纹理
const stripeTex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, 1, 8, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, new Uint8Array([
    255,
    128,
    255,
    128,
    255,
    128,
    255,
    128,
  ]));
gl.generateMipmaps(gl.TEXTURE_2D);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);

创建帧缓冲区和附件

TWGL

const attachments = [
  { format: RGBA, type: UNSIGNED_BYTE, min: LINEAR, wrap: CLAMP_TO_EDGE },
  { format: DEPTH_STENCIL, },
];
const fbi = twgl.createFramebufferInfo(gl, attachments);

WebGL

const fb = gl.createFramebuffer(gl.FRAMEBUFFER);
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.drawingBufferWidth, gl.drawingBufferHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
const rb = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, rb);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, gl.drawingBufferWidth, gl.drawingBufferHeight);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, rb);

设置统一变量和统一块结构及数组

给定如下 GLSL 结构数组:

struct Light {
  float intensity;
  float shininess;
  vec4 color;
}
uniform Light lights[2];

TWGL

const progInfo = twgl.createProgramInfo(gl, [vs, fs]);
...
twgl.setUniforms(progInfo, {
  lights: [
    { intensity: 5.0, shininess: 100, color: [1, 0, 0, 1] },
    { intensity: 2.0, shininess:  50, color: [0, 0, 1, 1] },
  ],
});

WebGL

// 假设我们已经编译并链接了程序
const light0IntensityLoc = gl.getUniformLocation('lights[0].intensity');
const light0ShininessLoc = gl.getUniformLocation('lights[0].shininess');
const light0ColorLoc = gl.getUniformLocation('lights[0].color');
const light1IntensityLoc = gl.getUniformLocation('lights[1].intensity');
const light1ShininessLoc = gl.getUniformLocation('lights[1].shininess');
const light1ColorLoc = gl.getUniformLocation('lights[1].color');
...
gl.uniform1f(light0IntensityLoc, 5.0);
gl.uniform1f(light0ShininessLoc, 100);
gl.uniform4fv(light0ColorLoc, [1, 0, 0, 1]);
gl.uniform1f(light1IntensityLoc, 2.0);
gl.uniform1f(light1ShininessLoc, 50);
gl.uniform4fv(light1ColorLoc, [0, 0, 1, 1]);

如果你只想在 TWGL 中设置第二个光源,可以这样做:

const progInfo = twgl.createProgramInfo(gl, [vs, fs]);
...
twgl.setUniforms(progInfo, {
  'lights[1]': { intensity: 5.0, shininess: 100, color: [1, 0, 0, 1] },
});

对比

TWGL 示例 vs WebGL 示例

示例

WebGL 2 示例

OffscreenCanvas 示例

ES6 模块支持

AMD 支持

CommonJS / Browserify 支持

其他特性

  • 包含一些可选的 3D 数学函数(完整版)

    欢迎使用任何数学库,只要它以扁平的 Float32Array 或 JavaScript 数组存储矩阵即可。

  • 包含一些可选的基本几何生成器(完整版)

    平面、立方体、球体等。只是为了让入门更容易。

使用方法

查看示例。否则有几种不同的版本:

下载

  • 从 github

    http://github.com/greggman/twgl.js

  • 从 bower

    bower install twgl.js
  • 从 npm

    npm install twgl.js

    或者

    npm install twgl-base.js
  • 从 git

    git clone https://github.com/greggman/twgl.js.git

理由及其他闲聊

TWGL 的目标是通过提供一些小型辅助函数来使 WebGL 更加简洁,并减少冗余和消除乏味的操作。TWGL 不是为了帮助处理着色器的复杂性或编写 GLSL。也不是像 three.js 那样的 3D 库。它只是试图让 WebGL 不那么冗长。

TWGL 可以被视为 TDL 的精神继承者。TDL 创建了几个 来包装 WebGL,而 TWGL 尽量不进行包装。实际上你可以手动创建几乎所有 TWGL 数据结构。

例如,函数 setAttributes 接收一个属性对象。 在 WebGL 中,你可能会这样写代码:

gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.vertexAttribPointer(positionLoc, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(positionLoc);
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
gl.vertexAttribPointer(normalLoc, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(normalLoc);
gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
gl.vertexAttribPointer(texcoordLoc, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(texcoordLoc);
gl.bindBuffer(gl.ARRAY_BUFFER, colorsBuffer);
gl.vertexAttribPointer(colorLoc, 4, gl.UNSIGNED_BYTE, true, 0, 0);
gl.enableVertexAttribArray(colorLoc);

setAttributes 只是最简单的代码来为你做这些。

// 手动为 TWGL 创建属性
const attribs = {
  a_position: { buffer: positionBuffer, size: 3, },
  a_normal:   { buffer: normalBuffer,   size: 3, },
  a_texcoord: { buffer: texcoordBuffer, size: 2, },
  a_color:    { buffer: colorBuffer,    size: 4, type: gl.UNSIGNED_BYTE, normalize: true, },
};
twgl.setAttributes(attribSetters, attribs);

上面的例子的重点是,TWGL 是一个轻量级的封装。它所做的只是试图让常见的 WebGL 操作更加简单和不那么冗长。欢迎将其与原生 WebGL 混合使用。

API 文档

API 文档在此.

想学习 WebGL?

试试 webglfundamentals.org