Skip to content

learosema/glea

Repository files navigation

GLea - GL experience artistry

GLea is a low-level WebGL library with a minimal footprint. It provides helper functions for creating a WebGL program, compiling shaders and passing data from JavaScript to the shader language.

Introduction

There are several options to embed GLea into your project. You can load GLea directly via script tag:

<script src="https://unpkg.com/glea@latest/dist/glea.umd.min.js"></script>

Inside a JavaScript ES module:

import GLea from 'https://cdn.skypack.dev/glea';

Or via NPM, you can install GLea via npm i glea and import it like this:

import GLea from 'glea';

Initialization

By default, GLea looks for a canvas element in your HTML and uses that. If there is no canvas element existing, GLea creates one for you.

If your HTML document doesn't include any CSS (neither a style nor a link tag, a minimal stylesheet is provided that sizes the canvas to the browser's viewport size.

The GLea instance expects a shaders property, containing your fragment and vertex shader. Also, a buffers property, which contains the data that is passed as attributes to the vertex shader.

If no buffers are provided, GLea provides a default position attribute with a buffer containing 4 vec2 values for a triangle strip, defining a plane filling the screen.

Setting uniforms

GLea provides several helper functions to set uniforms to pass data from JavaScript to GLSL. These are:

// set uniform float
glea.uni('pi', Math.PI);

// set uniform int
glea.uniI('width', innerWidth);

// set uniform float vector (supported types are vec2, vec3, vec4)
glea.uniV('vector', [Math.sqrt(2), Math.sqrt(3)]);

// set uniform int vector
glea.uniIV('resolution', [innerWidth, innerHeight]);

// set uniform matrix
// HEADS UP: it is the other way round as you would write it down on paper
// prettier-ignore
glea.uniM('translateMatrix', [
  1, 0, 0, 0, // column 1
  0, 1, 0, 0, // column 2
  0, 0, 1, 0, // column 3
  x, y, z, 1, // column 4
]);

Draw

GLea provides a wrapper to drawArrays from the underlying WebGLRenderingContext. It works exactly like the original drawArrays function, but if you don't provide any vertex count, it is determined automatically from the buffers.

const { gl } = glea;

glea.drawArrays(gl.TRIANGLE_STRIP);

// The same as:
const numVertices = 4;
glea.gl.drawArrays(gl.TRIANGLE_STRIP, 0, numVertices);

Multiple programs and switching

GLea supports multiple programs.

const prg1 = new GLea({
  shaders: [GLea.vertexShader(vert), GLea.fragmentShader(frag)],
}).create();

const prg2 = prg1.add({
  shaders: [GLea.vertexShader(vert2), GLea.fragmentShader(frag2)],
  buffers: {
    position: GLea.buffer(3, Ella.Geometry.sphere(0.25, 32, 16).toTriangles()),
  },
});

The the main instance prg1 and its child prg2 use the same underlying WebGLRenderingContext. In the example prg1 renders a plane geometry (GLea provides a position attribute with a plane geometry by default), and prg2 provides a sphere geometry. The sphere geometry is provided by ella-math.

In the draw loop, the switching between programs is done via enableAttribs and disableAttribs:

// Shader 1 does the background animation
prg1.gl.disable(gl.DEPTH_TEST);
prg1.enableAttribs();
prg1.uniV('resolution', [width, height]);
prg1.uni('time', time * 1e-3);
prg1.drawArrays(gl.TRIANGLE_STRIP);
prg1.disableAttribs();

// Shader 2 renders a sphere
gl.enable(gl.DEPTH_TEST);
prg2.enableAttribs();
prg2.uniV('resolution', [width, height]);
prg2.uni('time', time * 1e-3);
prg2.uniM('uPM', this.projectionMat.toArray());
prg2.uniM('uVM', this.viewMat.toArray());
prg2.drawArrays(gl.TRIANGLES);
prg2.disableAttribs();

Full example

Loading textures

I'm using a loadImage helper function that wraps img.onload into a Promise:

function loadImage(url) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.crossOrigin = 'Anonymous';
    img.src = url;
    img.onload = () => {
      resolve(img);
    };
    img.onerror = () => {
      reject(img);
    };
  });
}

async function setup() {
  const img = await loadImage('https://placekitten.com/256/256/');
  const textureIndex = 0;
  glea.createTexture(textureIndex);
  glea.gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
  glea.uniI('texture0', textureIndex);
}

setup();

In GLSL, you can access the texture like this:

uniform sampler2D texture0;

void main() {
  vec2 coord = 1.0 - gl_FragCoord.xy / vec2(width, height);
  gl_FragColor = texture2D(texture1, coord);
}

Example

import GLea from 'https://cdn.skypack.dev/glea';

const vert = `
precision highp float;
attribute vec2 position;

void main() {
  gl_Position = vec4(position, 0, 1.0);
}
`;

const frag = `
precision highp float;
uniform float time;
uniform vec2 resolution;

void main() {
  float vmin = min(resolution.y, resolution.x);
  vec2 p = (gl_FragCoord.xy - .5 * resolution) / vmin;
  float r = .5 + .5 * sin(5. * log(length(p)) - time * 1.2);
  float g = .5 + .5 * sin(5. * log(length(p)) + sin(time + 2. * p.x));  
  float b = .5 + .5 * sin(.2 + 5. * log(length(p)) + sin(time * .4 + 4. * p.y));
  gl_FragColor = vec4(r, g, b, 1.);
}
`;

const glea = new GLea({
  shaders: [GLea.fragmentShader(frag), GLea.vertexShader(vert)],
  buffers: {
    // create a position attribute bound
    // to a buffer with 4 2D coordinates
    // this is what GLea provides by default if you omit buffers in the constructor
    position: GLea.buffer(2, [1, 1, -1, 1, 1, -1, -1, -1]),
  },
}).create();

function loop(time) {
  const { gl, width, height } = glea;
  glea.clear();
  glea.uniV('resolution', [width, height]);
  glea.uni('time', time * 1e-3);
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
  requestAnimationFrame(loop);
}

function setup() {
  const { gl } = glea;
  window.addEventListener('resize', () => {
    glea.resize();
  });
  loop(0);
}

setup();

Exampes

More examples

Additional WebGL resources

About

Minimal footprint WebGL library

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published