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] },
});对比
示例
WebGL 2 示例
OffscreenCanvas 示例
ES6 模块支持
AMD 支持
CommonJS / Browserify 支持
其他特性
包含一些可选的 3D 数学函数(完整版)
欢迎使用任何数学库,只要它以扁平的 Float32Array 或 JavaScript 数组存储矩阵即可。
包含一些可选的基本几何生成器(完整版)
平面、立方体、球体等。只是为了让入门更容易。
使用方法
查看示例。否则有几种不同的版本:
- twgl-full.module.js es6 模块版本
- twgl-full.min.js 完整版的压缩版本
- twgl-full.js 完整版的连接版本
- twgl.min.js 最小版本(无 3D 数学,无基本几何体)
- twgl.js 连接后的最小版本(无 3D 数学,无基本几何体)
下载
从 github
从 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 混合使用。
