regl
regl
is a fast functional reactive abstraction for WebGL.
Example
In regl
, there are two fundamental abstractions, resources and commands:
- A resource is a handle to a GPU resident object, like a texture, FBO or buffer.
- A command is a complete representation of the WebGL state required to perform some draw call.
To define a command you specify a mixture of static and dynamic data for the object. Once this is done, regl
takes this description and then compiles it into optimized JavaScript code. For example, here is a simple regl
program to draw a colored triangle:
const regl = require('regl')()
const drawTriangle = regl({
frag: `
precision mediump float;
uniform vec4 color;
void main() {
gl_FragColor = color;
}`,
vert: `
precision mediump float;
attribute vec2 position;
void main() {
gl_Position = vec4(position, 0, 1);
}`,
attributes: {
position: regl.buffer([
[-2, -2],
[4, -2],
[4, 4]
])
},
uniforms: {
color: regl.prop('color')
},
count: 3
})
regl.frame(({time}) => {
regl.clear({
color: [0, 0, 0, 0],
depth: 1
})
drawTriangle({
color: [
Math.cos(time * 0.001),
Math.sin(time * 0.0008),
Math.cos(time * 0.003),
1
]
})
})
See this example live
More examples
Check out the gallery.
Setup
regl has no dependencies, so setting it up is pretty easy
Live editing
To try out regl right away, you can use the live editor in the gallery.
npm
The easiest way to use regl
in a project is via npm. Once you have node set up, you can install and use regl
in your project using the following command:
npm i -S regl
For more info on how to use npm, check out the official docs.
Standalone script tag
You can also use regl
as a standalone script if you are really stubborn. The most recent versions can be found under the releases tab. To do this, add the following script tag to your HTML:
<script src="[some cdn tbd]/regl.min.js"></script>
Why use regl
?
regl
is basically all of WebGL without all of the shared state. You can do anything you could in regular WebGL with little overhead and way less debugging.
Comparisons
In this section, we show how you can implement a spinning textured cube in regl
, and compare it with other WebGL frameworks.
const regl = require('../regl')()
const mat4 = require('gl-mat4')
var cubePosition = [
[-0.5, +0.5, +0.5], [+0.5, +0.5, +0.5], [+0.5, -0.5, +0.5], [-0.5, -0.5, +0.5],
[+0.5, +0.5, +0.5], [+0.5, +0.5, -0.5], [+0.5, -0.5, -0.5], [+0.5, -0.5, +0.5],
[+0.5, +0.5, -0.5], [-0.5, +0.5, -0.5], [-0.5, -0.5, -0.5], [+0.5, -0.5, -0.5],
[-0.5, +0.5, -0.5], [-0.5, +0.5, +0.5], [-0.5, -0.5, +0.5], [-0.5, -0.5, -0.5],
[-0.5, +0.5, -0.5], [+0.5, +0.5, -0.5], [+0.5, +0.5, +0.5], [-0.5, +0.5, +0.5],
[-0.5, -0.5, -0.5], [+0.5, -0.5, -0.5], [+0.5, -0.5, +0.5], [-0.5, -0.5, +0.5]
]
var cubeUv = [
[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0],
[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0],
[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0],
[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0],
[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0],
[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]
]
const cubeElements = [
[2, 1, 0], [2, 0, 3],
[6, 5, 4], [6, 4, 7],
[10, 9, 8], [10, 8, 11],
[14, 13, 12], [14, 12, 15],
[18, 17, 16], [18, 16, 19],
[20, 21, 22], [23, 20, 22]
]
const drawCube = regl({
frag: `
precision mediump float;
varying vec2 vUv;
uniform sampler2D tex;
void main () {
gl_FragColor = texture2D(tex,vUv);
}`,
vert: `
precision mediump float;
attribute vec3 position;
attribute vec2 uv;
varying vec2 vUv;
uniform mat4 projection, view;
void main() {
vUv = uv;
gl_Position = projection * view * vec4(position, 1);
}`,
attributes: {
position: cubePosition,
uv: cubeUv
},
elements: cubeElements,
uniforms: {
view: ({tick}) => {
const t = 0.01 * tick
return mat4.lookAt([],
[5 * Math.cos(t), 2.5 * Math.sin(t), 5 * Math.sin(t)],
[0, 0.0, 0],
[0, 1, 0])
},
projection: ({viewportWidth, viewportHeight}) =>
mat4.perspective([],
Math.PI / 4,
viewportWidth / viewportHeight,
0.01,
10),
tex: regl.prop('texture')
}
})
require('resl')({
manifest: {
texture: {
type: 'image',
src: 'assets/lena.png',
parser: (data) => regl.texture({
data: data,
mag: 'linear',
min: 'linear'
})
}
},
onDone: ({texture}) => {
regl.frame(() => {
regl.clear({
color: [0, 0, 0, 255],
depth: 1
})
drawCube({texture})
})
}
})
<!doctype html>
<html>
<head>
<title>WebGL Textured Cube Demo</title>
<script type="text/javascript">
var canvas
var gl
var cubePositionBuffer
var cubeUvBuffer
var cubeElementsBuffers
var cubeTexture
var shaderProgram
var cubePositionAttribute
var cubeUvAttribute
var projectionUniformLocation
var viewUniformLocation
var tick
function start () {
canvas = document.getElementById('glcanvas')
tick = 0
initWebGL(canvas)
if (gl) {
gl.clearColor(0.0, 0.0, 0.0, 1.0)
gl.enable(gl.DEPTH_TEST)
gl.enable(gl.CULL_FACE)
initShaders()
initBuffers()
initTextures()
requestAnimationFrame(drawScene)
}
}
function initWebGL () {
try {
gl = canvas.getContext('experimental-webgl')
} catch (e) {
}
if (!gl) {
alert('Unable to initialize WebGL. Your browser may not support it.')
}
}
function initBuffers () {
cubePositionBuffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, cubePositionBuffer)
var cubePosition = [
-0.5, +0.5, +0.5,
+0.5, +0.5, +0.5,
+0.5, -0.5, +0.5,
-0.5, -0.5, +0.5,
+0.5, +0.5, +0.5,
+0.5, +0.5, -0.5,
+0.5, -0.5, -0.5,
+0.5, -0.5, +0.5,
+0.5, +0.5, -0.5,
-0.5, +0.5, -0.5,
-0.5, -0.5, -0.5,
+0.5, -0.5, -0.5,
-0.5, +0.5, -0.5,
-0.5, +0.5, +0.5,
-0.5, -0.5, +0.5,
-0.5, -0.5, -0.5,
-0.5, +0.5, -0.5,
+0.5, +0.5, -0.5,
+0.5, +0.5, +0.5,
-0.5, +0.5, +0.5,
-0.5, -0.5, -0.5,
+0.5, -0.5, -0.5,
+0.5, -0.5, +0.5,
-0.5, -0.5, +0.5
]
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(cubePosition), gl.STATIC_DRAW)
cubeUvBuffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, cubeUvBuffer)
var cubeUv = [
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0
]
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(cubeUv), gl.STATIC_DRAW)
cubeElementsBuffers = gl.createBuffer()
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeElementsBuffers)
var cubeElements = [
2, 1, 0,
2, 0, 3,
6, 5, 4,
6, 4, 7,
10, 9, 8,
10, 8, 11,
14, 13, 12,
14, 12, 15,
18, 17, 16,
18, 16, 19,
20, 21, 22,
23, 20, 22
]
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(cubeElements), gl.STATIC_DRAW)
}
function initTextures () {
cubeTexture = gl.createTexture()
var cubeImage = new Image()
cubeImage.onload = function () {
gl.bindTexture(gl.TEXTURE_2D, cubeTexture)
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, cubeImage)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
}
cubeImage.src = '../example/assets/lena.png'
}
function drawScene () {
tick++
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
gl.bindBuffer(gl.ARRAY_BUFFER, cubePositionBuffer)
gl.vertexAttribPointer(cubePositionAttribute, 3, gl.FLOAT, false, 0, 0)
gl.bindBuffer(gl.ARRAY_BUFFER, cubeUvBuffer)
gl.vertexAttribPointer(cubeUvAttribute, 2, gl.FLOAT, false, 0, 0)
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeElementsBuffers)
gl.activeTexture(gl.TEXTURE0)
gl.bindTexture(gl.TEXTURE_2D, cubeTexture)
gl.uniform1i(gl.getUniformLocation(shaderProgram, 'tex'), 0)
const t = 0.01 * tick
var perspectiveMatrix = perspective(45, 640.0 / 480.0, 0.1, 100.0)
gl.uniformMatrix4fv(projectionUniformLocation, false, new Float32Array(perspectiveMatrix))
gl.uniformMatrix4fv(viewUniformLocation, false, new Float32Array(
lookAt(
[5 * Math.cos(t), 2.5 * Math.sin(t), 5 * Math.sin(t)],
[0, 0.0, 0],
[0, 1, 0])))
gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_SHORT, 0)
requestAnimationFrame(drawScene)
}
function initShaders () {
var fragmentShader = getShader(gl, 'shader-fs')
var vertexShader = getShader(gl, 'shader-vs')
shaderProgram = gl.createProgram()
gl.attachShader(shaderProgram, vertexShader)
gl.attachShader(shaderProgram, fragmentShader)
gl.linkProgram(shaderProgram)
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
alert('Unable to initialize the shader program: ' + gl.getProgramInfoLog(shaderProgram))
}
gl.useProgram(shaderProgram)
cubePositionAttribute = gl.getAttribLocation(shaderProgram, 'position')
gl.enableVertexAttribArray(cubePositionAttribute)
cubeUvAttribute = gl.getAttribLocation(shaderProgram, 'uv')
gl.enableVertexAttribArray(cubeUvAttribute)
projectionUniformLocation = gl.getUniformLocation(shaderProgram, 'projection')
viewUniformLocation = gl.getUniformLocation(shaderProgram, 'view')
}
function getShader (gl, id) {
var e = document.getElementById(id)
if (!e) {
return null
}
var sourceCode = e.firstChild.textContent
var shader
if (e.type === 'x-shader/x-fragment') {
shader = gl.createShader(gl.FRAGMENT_SHADER)
} else if (e.type === 'x-shader/x-vertex') {
shader = gl.createShader(gl.VERTEX_SHADER)
} else {
return null
}
gl.shaderSource(shader, sourceCode)
gl.compileShader(shader)
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader))
return null
}
return shader
}
function lookAt (eye, center, up) {
var x0, x1, x2, y0, y1, y2, z0, z1, z2, len
var eyex = eye[0]
var eyey = eye[1]
var eyez = eye[2]
var upx = up[0]
var upy = up[1]
var upz = up[2]
var centerx = center[0]
var centery = center[1]
var centerz = center[2]
z0 = eyex - centerx
z1 = eyey - centery
z2 = eyez - centerz
len = 1 / Math.sqrt(z0 * z0 + z1 * z1 + z2 * z2)
z0 *= len
z1 *= len
z2 *= len
x0 = upy * z2 - upz * z1
x1 = upz * z0 - upx * z2
x2 = upx * z1 - upy * z0
len = Math.sqrt(x0 * x0 + x1 * x1 + x2 * x2)
if (!len) {
x0 = 0
x1 = 0
x2 = 0
} else {
len = 1 / len
x0 *= len
x1 *= len
x2 *= len
}
y0 = z1 * x2 - z2 * x1
y1 = z2 * x0 - z0 * x2
y2 = z0 * x1 - z1 * x0
len = Math.sqrt(y0 * y0 + y1 * y1 + y2 * y2)
if (!len) {
y0 = 0
y1 = 0
y2 = 0
} else {
len = 1 / len
y0 *= len
y1 *= len
y2 *= len
}
return [
x0, y0, z0, 0,
x1, y1, z1, 0,
x2, y2, z2, 0,
-(x0 * eyex + x1 * eyey + x2 * eyez),
-(y0 * eyex + y1 * eyey + y2 * eyez),
-(z0 * eyex + z1 * eyey + z2 * eyez),
1
]
}
function perspective (fovy, aspect, near, far) {
var f = 1.0 / Math.tan(fovy / 2)
var nf = 1 / (near - far)
return [
f / aspect, 0.0, 0.0, 0.0,
0.0, f, 0.0, 0.0,
0.0, 0.0, (far + near) * nf, -1.0,
0.0, 0.0, (2 * far * near) * nf, 0.0
]
}
</script>
<script id="shader-fs" type="x-shader/x-fragment">
precision mediump float;
varying vec2 vUv;
uniform sampler2D tex;
void main () {
gl_FragColor = texture2D(tex,vUv);
}
</script>
<script id="shader-vs" type="x-shader/x-vertex">
precision mediump float;
attribute vec3 position;
attribute vec2 uv;
varying vec2 vUv;
uniform mat4 projection, view;
void main() {
vUv = uv;
gl_Position = projection * view * vec4(position, 1);
}
</script>
</head>
<body onload="start()">
<canvas id="glcanvas" width="640" height="480">
Your browser doesn't appear to support the <code><canvas></code> element.
</canvas>
</body>
</html>
TODO
TODO
TODO
TODO
Benchmarks
You can run benchmarks locally using npm run bench
or check them out here:
Development
The latest changes in regl
can be found in the CHANGELOG.
For info on how to build and test headless, see the contributing guide here
License
All code (c) 2016 MIT License
Development supported by the Freeman Lab and the Howard Hughes Medical Institute (@freeman-lab on GitHub)
Asset licenses
Many examples use creative commons or public domain artwork for illustrative purposes. These assets are not included in any of the redistributable packages of regl.
- Test video (doggie-chromakey.ogv) by L0ckergn0me, used under creative commons license
- Cube maps (posx.jpeg, negx.jpeg, posy.jpeg, negy.jpeg, posz.jpeg, negz.jpeg) by Humus, used under creative commons 3 license
- Environment map of Oregon (ogd-oregon-360.jpg) due to Max Ogden (@maxogd on GitHub)
- DDS test images (alpine_cliff_a, alpine_cliff_a_norm, alpine_cliff_a_spec) taken from the CC0 license 0-AD texture pack by Wildfire games
- Tile set for tile mapping demo (tiles.png) from CC0 licensed cobblestone paths pack
- Audio track for
audio.js
example is "Bamboo Cactus" by 8bitpeoples. CC BY-ND-NC 1.0 license