GLSL's types are built for geometry and color: a vec3 holds a position or an RGB color, a mat4 a transform.
| Kind | GLSL ES 3.00 | Missing from GLSL ES 1.00 |
|---|---|---|
| Scalars | bool, int, uint, float | uint |
| Vectors | vec2-4, ivec2-4, uvec2-4, bvec2-4 | uvec |
| Matrices | mat2, mat3, mat4, mat2x3 to mat4x3 | non-square matrices |
| Samplers | sampler2D, sampler3D, samplerCube, sampler2DArray, shadow and integer samplers | all but sampler2D, samplerCube |
Constructors convert and assemble values: extra components are dropped, one scalar fills every component, and a matrix built from one scalar puts it on the diagonal. Matrices are column-major: the first values form column 0, and m[1] is column 1. A show() function prints what the GPU computed, or the first compiler error:
const show = (body, declarations) => {
try {
const values = GLH.evaluate(gl, body, declarations);
console.log(values.map((x) => +x.toFixed(6)).join(', '));
} catch (e) {
console.log(e.message.split('\n')[1]); // the first compiler error
}
};
show('result = vec4(vec2(vec3(1.0, 2.0, 3.0)), vec4(5.0));'); // truncate, then fill
show('mat3 m = mat3(1., 2., 3., 4., 5., 6., 7., 8., 9.); result = vec4(m[1], m[2][0]);');
show('mat4 m = mat4(2.0); result = m[0] + m[3];'); // diagonal matrix
show('result = vec4(int(2.9), int(-2.9), float(true), 7u / 2u);');
show('float f = 5; result = vec4(f);'); // no implicit int -> floatOutput
1, 2, 5, 5 4, 5, 6, 7 2, 0, 0, 2 2, -2, 1, 3 ERROR: 0:3: '=' : cannot convert from 'const int' to 'highp float'
GLSL never converts implicitly, so 5 is an int and cannot initialize a float: write 5.0 or float(5).
<!doctype html>
<style>
body { margin: 0; font: 12px system-ui, sans-serif; background: #f7f4ee; color: #333; }
canvas { display: block; width: 100%; max-width: 600px; }
.names { display: flex; max-width: 600px; text-align: center; font-family: monospace; }
.names div { flex: 1; padding: 3px 0; }
pre { max-width: 600px; margin: 6px 0 0; padding: 6px 8px; background: #fff; border: 1px solid #ddd; font-size: 11px; }
</style>
<canvas id="view" width="600" height="150"></canvas>
<div class="names"><div>float</div><div>vec3</div><div>mat2 * vec2</div><div>mat3(1.6) * vec3</div></div>
<pre id="out"></pre>
<script>
const gl = document.getElementById('view').getContext('webgl2');
function compile(vsSource, fsSource) {
const p = gl.createProgram();
for (const [type, src] of [[gl.VERTEX_SHADER, vsSource], [gl.FRAGMENT_SHADER, fsSource]]) {
const s = gl.createShader(type);
gl.shaderSource(s, src.trim()); gl.compileShader(s);
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(s));
gl.attachShader(p, s);
}
gl.linkProgram(p);
return p;
}
const fullScreen = `#version 300 es
void main() { // one triangle that covers the viewport
gl_Position = vec4(gl_VertexID == 1 ? 3.0 : -1.0, gl_VertexID == 2 ? 3.0 : -1.0, 0.0, 1.0);
}`;
// Each tile uses a different GLSL type to colour a book-cover stripe pattern.
const tiles = compile(fullScreen, `#version 300 es
precision highp float;
uniform int uTile; uniform float uTime; uniform vec2 uOrigin;
out vec4 outColor;
float stripes(vec2 p) { return step(0.5, fract(p.x * 4.0)); }
void main() {
vec2 p = (gl_FragCoord.xy - uOrigin) / 150.0 - 0.5; // -0.5..0.5 inside the tile
vec3 blue = vec3(0.12, 0.37, 0.55); // The Quiet Harbor's cover colour
if (uTile == 0) {
float gray = 0.3 + 0.5 * stripes(p); // a scalar...
outColor = vec4(vec3(gray), 1.0); // ...fills every component
} else if (uTile == 1) {
outColor = vec4(mix(blue, vec3(0.88, 0.6, 0.06), p.x + 0.5) * (0.7 + 0.3 * stripes(p)), 1.0);
} else if (uTile == 2) {
float c = cos(uTime), s = sin(uTime);
mat2 rotate = mat2(c, s, -s, c); // column-major: columns (c, s) and (-s, c)
outColor = vec4(blue * (0.6 + 0.4 * stripes(rotate * p)), 1.0);
} else {
mat3 scale = mat3(1.6); // one scalar -> the diagonal
vec3 q = scale * vec3(p, 1.0);
outColor = vec4(vec3(0.71, 0.27, 0.18) * (0.6 + 0.4 * stripes(q.xy)), 1.0);
}
}`);
// GPU evaluator: run one expression in a fragment shader, read the vec4 back as floats.
gl.getExtension('EXT_color_buffer_float');
const target = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, target);
gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA32F, 1, 1);
const fbo = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, target, 0);
function evaluate(body) {
try {
const p = compile(fullScreen, `#version 300 es
precision highp float; out vec4 result;
void main() { ${body} }`);
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); gl.viewport(0, 0, 1, 1);
gl.useProgram(p); gl.drawArrays(gl.TRIANGLES, 0, 3);
const values = new Float32Array(4);
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.FLOAT, values);
return [...values].map(v => +v.toFixed(3)).join(', ');
} catch (e) {
return 'compile error: ' + e.message.split('\n')[0];
}
}
const lines = [
'result = vec4(vec2(vec3(1.0, 2.0, 3.0)), vec4(5.0));',
'mat3 m = mat3(1., 2., 3., 4., 5., 6., 7., 8., 9.); result = vec4(m[1], m[2][0]);',
'mat4 m = mat4(2.0); result = m[0] + m[3];',
'result = vec4(int(2.9), int(-2.9), float(true), 7u / 2u);',
'float f = 5; result = vec4(f);',
];
document.getElementById('out').textContent = lines.map(l => l + '\n -> ' + evaluate(l)).join('\n');
gl.bindVertexArray(gl.createVertexArray());
gl.useProgram(tiles);
gl.enable(gl.SCISSOR_TEST);
function frame(ms) {
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.uniform1f(gl.getUniformLocation(tiles, 'uTime'), ms * 0.001);
for (let i = 0; i < 4; i++) {
const x = i * 150 + 5;
gl.viewport(x, 0, 140, 150); gl.scissor(x, 0, 140, 150);
gl.uniform1i(gl.getUniformLocation(tiles, 'uTile'), i);
gl.uniform2f(gl.getUniformLocation(tiles, 'uOrigin'), x - 5, 0);
gl.drawArrays(gl.TRIANGLES, 0, 3);
}
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
</script>