Top Related Projects
JavaScript 3D Library.
👑 Functional WebGL
Minimal WebGL Library
The HTML5 Creation Engine: Create beautiful digital content with the fastest, most flexible 2D WebGL renderer.
The Official Khronos WebGL Repository
GPU Accelerated JavaScript
Quick Overview
TWGL.js (Tiny WebGL) is a small, lightweight library for working with WebGL. It simplifies many common WebGL operations, reducing boilerplate code and making it easier to create WebGL applications. TWGL.js aims to make WebGL programming more accessible and efficient.
Pros
- Lightweight and minimal, with a small footprint
- Simplifies common WebGL tasks, reducing boilerplate code
- Provides both low-level and high-level APIs for flexibility
- Compatible with other WebGL libraries and frameworks
Cons
- Less feature-rich compared to larger WebGL frameworks
- May require more manual work for complex 3D scenes
- Limited documentation compared to more popular libraries
- Smaller community and ecosystem compared to alternatives
Code Examples
Creating a simple WebGL program:
const gl = document.querySelector('canvas').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);
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.drawBufferInfo(gl, bufferInfo);
Loading a texture:
const textures = twgl.createTextures(gl, {
myTexture: { src: 'path/to/image.jpg' },
});
// Use the texture in your shader
twgl.setUniforms(programInfo, {
u_texture: textures.myTexture,
});
Creating a 3D object:
const arrays = twgl.primitives.createCubeVertices(1);
const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
const vao = twgl.createVAOFromBufferInfo(gl, programInfo, bufferInfo);
// Render the cube
gl.bindVertexArray(vao);
twgl.drawBufferInfo(gl, bufferInfo);
Getting Started
-
Include TWGL.js in your project:
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
-
Create a canvas element in your HTML:
<canvas id="c"></canvas>
-
Initialize WebGL and create a simple program:
const gl = document.getElementById('c').getContext('webgl'); const programInfo = twgl.createProgramInfo(gl, ['vs', 'fs']); // Your WebGL code here
-
Start creating WebGL objects and rendering your scene using TWGL.js functions.
Competitor Comparisons
JavaScript 3D Library.
Pros of Three.js
- Extensive feature set with high-level abstractions for 3D graphics
- Large community and ecosystem with numerous examples and extensions
- Comprehensive documentation and learning resources
Cons of Three.js
- Larger file size and potentially higher performance overhead
- Steeper learning curve for beginners due to its extensive API
- May be overkill for simple WebGL projects
Code Comparison
Three.js:
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
TWGL.js:
const gl = document.createElement("canvas").getContext("webgl");
const programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);
const bufferInfo = twgl.primitives.createCubeBufferInfo(gl);
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
TWGL.js is a lightweight WebGL library that provides a thin wrapper around WebGL, offering a more low-level approach compared to Three.js. It's designed for developers who want more direct control over WebGL operations while reducing boilerplate code. TWGL.js is smaller in size and potentially faster for simple projects, but lacks the high-level features and extensive ecosystem of Three.js.
👑 Functional WebGL
Pros of regl
- Functional approach with stateless rendering, promoting cleaner and more predictable code
- More advanced features like instancing and batch rendering built-in
- Extensive documentation and examples
Cons of regl
- Steeper learning curve, especially for developers familiar with imperative WebGL
- Less flexible for low-level WebGL operations
- Potentially higher overhead for simple rendering tasks
Code Comparison
twgl.js:
const programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);
const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.drawBufferInfo(gl, bufferInfo);
regl:
const draw = regl({
frag: `...`,
vert: `...`,
attributes: { position: [/*...*/] },
count: 3
})
draw()
Summary
regl offers a more functional and declarative approach to WebGL rendering, with built-in advanced features. It's well-suited for complex rendering tasks but may have a steeper learning curve. twgl.js provides a more traditional, lower-level API that closely mirrors WebGL, offering flexibility and ease of use for simpler tasks. The choice between them depends on the project's complexity and the developer's preferred programming paradigm.
Minimal WebGL Library
Pros of OGL
- More comprehensive 3D engine with built-in math utilities and scene graph
- Modern ES6+ codebase with modular architecture
- Extensive examples and documentation
Cons of OGL
- Larger file size and potentially higher learning curve
- Less focus on raw WebGL abstraction compared to TWGL.js
- May be overkill for simpler projects or those requiring fine-grained control
Code Comparison
TWGL.js (simple triangle):
const programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);
const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.drawBufferInfo(gl, bufferInfo);
OGL (simple triangle):
const geometry = new Geometry(gl, {
position: {size: 3, data: new Float32Array([-0.5, -0.5, 0, 0.5, -0.5, 0, 0, 0.5, 0])},
});
const program = new Program(gl, {vertex, fragment});
const mesh = new Mesh(gl, {geometry, program});
renderer.render({scene: mesh});
Both libraries simplify WebGL usage, but OGL provides a higher-level abstraction with more built-in features for 3D graphics, while TWGL.js focuses on streamlining raw WebGL operations.
The HTML5 Creation Engine: Create beautiful digital content with the fastest, most flexible 2D WebGL renderer.
Pros of PixiJS
- More comprehensive and feature-rich, offering a complete 2D rendering engine
- Extensive documentation and large community support
- Built-in support for sprites, textures, and complex animations
Cons of PixiJS
- Larger file size and potentially higher performance overhead
- Steeper learning curve due to its extensive API
- May be overkill for simple WebGL projects
Code Comparison
TWGL.js (basic triangle rendering):
const programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);
const arrays = {
position: [-1, -1, 0, 1, -1, 0, 0, 1, 0],
};
const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
twgl.drawBufferInfo(gl, bufferInfo);
PixiJS (basic sprite rendering):
const app = new PIXI.Application();
document.body.appendChild(app.view);
const sprite = PIXI.Sprite.from("image.png");
app.stage.addChild(sprite);
Summary
TWGL.js is a lightweight WebGL wrapper focused on simplifying WebGL programming, while PixiJS is a full-featured 2D rendering engine. TWGL.js is better suited for developers who want low-level WebGL control with less boilerplate, whereas PixiJS is ideal for creating complex 2D graphics and games with a higher-level API.
The Official Khronos WebGL Repository
Pros of WebGL
- Official specification and reference implementation
- Comprehensive documentation and examples
- Broad industry support and standardization
Cons of WebGL
- Lower-level API, requiring more boilerplate code
- Steeper learning curve for beginners
- Less abstraction for common tasks
Code Comparison
WebGL:
const gl = canvas.getContext('webgl');
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
twgl.js:
const programInfo = twgl.createProgramInfo(gl, [vertexShaderSource, fragmentShaderSource]);
Key Differences
- WebGL provides a low-level API for direct GPU access
- twgl.js offers a higher-level abstraction layer on top of WebGL
- WebGL requires more manual setup and management of WebGL state
- twgl.js simplifies common tasks like buffer creation and shader compilation
- WebGL gives more fine-grained control over rendering pipeline
- twgl.js reduces boilerplate code and improves development speed
Use Cases
- WebGL: When full control over the rendering pipeline is needed
- twgl.js: For rapid prototyping and simpler WebGL projects
- WebGL: In performance-critical applications requiring optimizations
- twgl.js: When focusing on higher-level graphics concepts without diving into WebGL intricacies
GPU Accelerated JavaScript
Pros of gpu.js
- Provides high-level abstractions for GPU computing
- Supports both browser and Node.js environments
- Offers automatic kernel compilation for different GPU architectures
Cons of gpu.js
- Steeper learning curve due to its focus on GPU computing
- May have performance overhead for simpler graphics tasks
- Less focused on WebGL-specific optimizations
Code Comparison
twgl.js (WebGL-focused):
const programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);
const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.drawBufferInfo(gl, bufferInfo);
gpu.js (GPU computing):
const gpu = new GPU();
const kernel = gpu.createKernel(function(a, b) {
return a[this.thread.y][this.thread.x] + b[this.thread.y][this.thread.x];
}).setOutput([width, height]);
const result = kernel(matrixA, matrixB);
twgl.js focuses on simplifying WebGL operations, while gpu.js provides a higher-level abstraction for GPU computing. twgl.js is more suitable for direct WebGL programming, whereas gpu.js excels in general-purpose GPU computations across different environments.
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual CopilotREADME
TWGL: A Tiny WebGL helper Library[rhymes with wiggle]
This library's sole purpose is to make using the WebGL API less verbose.
TL;DR
If you want to get stuff done use three.js. If you want to do stuff low-level with WebGL consider using TWGL.
The tiniest example
Not including the shaders (which is a simple quad shader) here's the entire code
<canvas id="c"></canvas>
<script src="../dist/6.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>
Why? What? How?
WebGL is a very verbose API. Setting up shaders, buffers, attributes and uniforms takes a lot of code. A simple lit cube in WebGL might easily take over 60 calls into WebGL.
At its core there's really only a few main functions
twgl.createProgramInfo
compiles a shader and creates setters for attribs and uniformstwgl.createBufferInfoFromArrays
creates buffers and attribute settingstwgl.setBuffersAndAttributes
binds buffers and sets attributestwgl.setUniforms
sets the uniformstwgl.createTextures
creates textures of various sortstwgl.createFramebufferInfo
creates a framebuffer and attachments.
There's a few extra helpers and lower-level functions if you need them but those 6 functions are the core of TWGL.
Compare the TWGL vs WebGL code for a point lit cube.
Compiling a Shader and looking up locations
TWGL
const programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);
WebGL
// Note: I'm conceding that you'll likely already have the 30 lines of
// code for compiling 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");
Creating Buffers for a Cube
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);
Setting Attributes and Indices for a Cube
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);
Setting Uniforms for a Lit Cube
TWGL
// At Init time
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,
};
// At render time
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
// At Init time
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;
// At render time
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));
Loading / Setting up textures
TWGL
const textures = twgl.createTextures(gl, {
// a power of 2 image
hftIcon: { src: "images/hft-icon-16.png", mag: gl.NEAREST },
// a non-power of 2 image
clover: { src: "images/clover.jpg" },
// From a canvas
fromCanvas: { src: ctx.canvas },
// A cubemap from 6 images
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',
],
},
// A cubemap from 1 image (can be 1x6, 2x3, 3x2, 6x1)
goldengate: {
target: gl.TEXTURE_CUBE_MAP,
src: 'images/goldengate.jpg',
},
// A 2x2 pixel texture from a JavaScript array
checker: {
mag: gl.NEAREST,
min: gl.LINEAR,
src: [
255,255,255,255,
192,192,192,255,
192,192,192,255,
255,255,255,255,
],
},
// a 1x8 pixel texture from a typed array.
stripe: {
mag: gl.NEAREST,
min: gl.LINEAR,
format: gl.LUMINANCE,
src: new Uint8Array([
255,
128,
255,
128,
255,
128,
255,
128,
]),
width: 1,
},
});
WebGL
// Let's assume I already loaded all the images
// a power of 2 image
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);
// a non-power of 2 image
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);
// From a canvas
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);
// A cubemap from 6 images
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);
// A cubemap from 1 image (can be 1x6, 2x3, 3x2, 6x1)
const goldengateTex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_CUBE_MAP, tex);
const size = goldengate.width / 3; // assume it's a 3x2 texture
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);
// A 2x2 pixel texture from a JavaScript array
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);
// a 1x8 pixel texture from a typed array.
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);
Creating Framebuffers and attachments
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);
Setting uniform and uniformblock structures and arrays
Given an array of GLSL structures like this
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
// assuming we already compiled and linked the program
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]);
If you just want to set the 2nd light in TWGL you can do this
const progInfo = twgl.createProgramInfo(gl, [vs, fs]);
...
twgl.setUniforms(progInfo, {
'lights[1]': { intensity: 5.0, shininess: 100, color: [1, 0, 0, 1] },
});
Compare
Examples
- tiny
- twgl cube
- textures
- primitives
- 2d-lines
- dynamic-buffers
- zoom-around
- text
- kaleidoscope
- tunnel
- GPGPU particles
- item list
- no box skybox
- cross origin
- vertex array objects
- instancing
WebGL 2 Examples
- uniform buffer objects
- 3d textures tone mapping
- samplers
- webgl2 textures
- 3d texture volume
- 3d texture volume no buffers
- 2d array texture
- transform feedback
- transform feedback particles
- transform feedback particles vertex arrays
OffscreenCanvas Example
ES6 module support
AMD support
CommonJS / Browserify support
Other Features
-
Includes some optional 3d math functions (full version)
You are welcome to use any math library as long as it stores matrices as flat Float32Array or JavaScript arrays.
-
Includes some optional primitive generators (full version)
planes, cubes, spheres, ... Just to help get started
Usage
See the examples. Otherwise there's a few different versions
twgl-full.module.js
the es6 module versiontwgl-full.min.js
the minified full versiontwgl-full.js
the concatenated full versiontwgl.min.js
the minimum version (no 3d math, no primitives)twgl.js
the concatenated minimum version (no 3d math, no primitives)
Download
-
from github
-
from bower
bower install twgl.js
-
from npm
npm install twgl.js
or
npm install twgl-base.js
-
from git
git clone https://github.com/greggman/twgl.js.git
Rationale and other chit-chat
TWGL's is an attempt to make WebGL simpler by providing a few tiny helper functions that make it much less verbose and remove the tedium. TWGL is NOT trying to help with the complexity of managing shaders and writing GLSL. Nor is it a 3D library like three.js. It's just trying to make WebGL less verbose.
TWGL can be considered a spiritual successor to TDL. Where as TDL created several classes that wrapped WebGL, TWGL tries not to wrap anything. In fact you can manually create nearly all TWGL data structures.
For example the function setAttributes
takes an object of attributes.
In WebGL you might write code like this
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
is just the simplest code to do that for you.
// make attributes for TWGL manually
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);
The point of the example above is TWGL is a thin wrapper. All it's doing is trying to make common WebGL operations easier and less verbose. Feel free to mix it with raw WebGL.
API Docs
Want to learn WebGL?
Top Related Projects
JavaScript 3D Library.
👑 Functional WebGL
Minimal WebGL Library
The HTML5 Creation Engine: Create beautiful digital content with the fastest, most flexible 2D WebGL renderer.
The Official Khronos WebGL Repository
GPU Accelerated JavaScript
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual Copilot