<!DOCTYPE html>
<html>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<body style="background-color: black;">
<script>
function matrix_mul(m1, m2) {
let out = Array(16).fill(0);
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
for (let k = 0; k < 4; k++) {
out[4 * i + k] += m1[4 * j + k] * m2[4 * i + j];
}
}
}
return out;
}
let mesh_data = Float32Array.from([
+0.45, +0.55, +0.45, 1, 0, 0, -1, 0,
+0.45, +0.45, +0.45, 1, 0, 0, -1, 0,
-0.45, +0.45, +0.45, 1, 0, 0, -1, 0,
]);
let instance_data = function () {
let matrices = Array();
[-1, 1].forEach(j => [-1, 1].forEach(k => {
for (let x = -2; x <= 2; x++) {
for (let y = -2; y <= 2; y++) {
for (let z = -2; z <= 2; z++) {
[j * k, 0, 0, 0, 0, j, 0, 0, 0, 0, k, 0, x, y, z, 1].forEach(val => { matrices.push(val); });
[j * k, 0, 0, 0, 0, 0, j, 0, 0, k, 0, 0, x, y, z, 1].forEach(val => { matrices.push(val); });
[0, j * k, 0, 0, 0, 0, j, 0, k, 0, 0, 0, x, y, z, 1].forEach(val => { matrices.push(val); });
[0, 0, j * k, 0, 0, j, 0, 0, k, 0, 0, 0, x, y, z, 1].forEach(val => { matrices.push(val); });
[0, 0, j * k, 0, j, 0, 0, 0, 0, k, 0, 0, x, y, z, 1].forEach(val => { matrices.push(val); });
[0, j * k, 0, 0, j, 0, 0, 0, 0, 0, k, 0, x, y, z, 1].forEach(val => { matrices.push(val); });
}
}
}
}));
return Float32Array.from(matrices);
}();
let canvas_size = 800;
let canvas = window.document.createElement("canvas");
canvas.setAttribute("width", canvas_size);
canvas.setAttribute("height", canvas_size);
canvas.setAttribute("style", "background-color: #555; cursor: pointer;");
window.document.body.appendChild(canvas);
canvas.addEventListener("click", function (e) { canvas.requestPointerLock(); });
let perspective = [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 0, -1,
0, 0, -0.01, 0
];
let transform = [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
];
let gl = canvas.getContext("webgl2");
gl.clearColor(0.0, 0.0, 0.0, 0.0);
gl.viewport(0, 0, canvas_size, canvas_size);
gl.enable(gl.DEPTH_TEST);
let vshader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vshader,
'#version 300 es\n' +
'in vec4 pos;\n' +
'in vec4 norm;\n' +
'in mat4 instance_transform;\n' +
'out vec4 vpos;\n' +
'out vec4 vnorm;\n' +
'out vec4 lightpos[3];\n' +
'uniform mat4 transform;\n' +
'uniform mat4 perspective;\n' +
'uniform vec4 lightPositions[3];\n' +
'void main() {\n' +
' vpos = transform * instance_transform * pos;\n' +
' vnorm = transform * instance_transform * norm;\n' +
' lightpos[0] = transform * lightPositions[0];\n' +
' lightpos[1] = transform * lightPositions[1];\n' +
' lightpos[2] = transform * lightPositions[2];\n' +
' gl_Position = perspective * vpos;\n' +
'}\n'
);
gl.compileShader(vshader);
let fshader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fshader,
'#version 300 es\n' +
'precision mediump float;\n' +
'in vec4 vpos;\n' +
'in vec4 vnorm;\n' +
'in vec4 lightpos[3];\n' +
'out vec4 color;\n' +
'vec4 directionTo(vec4 u, vec4 v) {\n' +
' return u - v;\n' +
'}\n' +
'float lighting(vec4 u, vec4 v) {\n' +
' return 1. / dot(u-v, u-v);\n' +
'}\n' +
'void main() {\n' +
' color = vec4(0.3, 0.3, 0.3, 1.0);\n' +
' float cos_angle;\n' +
' cos_angle = dot(\n' +
' normalize(directionTo(lightpos[0], vpos)),\n' +
' normalize(vnorm)\n' +
' );\n' +
' color.x += 0.6 * max(0., cos_angle) * lighting(lightpos[0], vpos);\n' +
' cos_angle = dot(\n' +
' normalize(directionTo(lightpos[1], vpos)),\n' +
' normalize(vnorm)\n' +
' );\n' +
' color.y += 0.6 * max(0., cos_angle) * lighting(lightpos[1], vpos);\n' +
' cos_angle = dot(\n' +
' normalize(directionTo(lightpos[2], vpos)),\n' +
' normalize(vnorm)\n' +
' );\n' +
' color.z += 0.6 * max(0., cos_angle) * lighting(lightpos[2], vpos);\n' +
'}\n'
);
gl.compileShader(fshader);
let program = gl.createProgram();
gl.attachShader(program, vshader);
gl.attachShader(program, fshader);
gl.linkProgram(program);
gl.useProgram(program);
gl.deleteShader(vshader);
gl.deleteShader(fshader);
let vao = gl.createVertexArray();
gl.bindVertexArray(vao);
let vertex_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
gl.bufferData(gl.ARRAY_BUFFER, mesh_data, gl.STATIC_DRAW);
let instance_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, instance_buffer);
gl.bufferData(gl.ARRAY_BUFFER,
instance_data,
gl.STATIC_DRAW,
);
let attrib_pos = gl.getAttribLocation(program, "pos");
let attrib_norm = gl.getAttribLocation(program, "norm");
gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
gl.enableVertexAttribArray(attrib_pos);
gl.enableVertexAttribArray(attrib_norm);
gl.vertexAttribPointer(attrib_pos, 4, gl.FLOAT, false, 8 * 4, 0);
gl.vertexAttribPointer(attrib_norm, 4, gl.FLOAT, false, 8 * 4, 4 * 4);
let attrib_instance_transform = gl.getAttribLocation(program, "instance_transform");
gl.bindBuffer(gl.ARRAY_BUFFER, instance_buffer);
for (let i = 0; i < 4; i++) {
gl.enableVertexAttribArray(attrib_instance_transform + i);
gl.vertexAttribPointer(attrib_instance_transform + i, 4, gl.FLOAT, false, 16 * 4, 16 * i);
gl.vertexAttribDivisor(attrib_instance_transform + i, 1);
}
let running = null;
let key_pressed = {};
window.addEventListener("keydown", function (e) { key_pressed[e.code] = true });
window.addEventListener("keyup", function (e) { key_pressed[e.code] = false });
let time;
canvas.addEventListener("mousemove", function (e) {
transform = matrix_mul([
Math.cos(e.movementX / canvas_size), 0, -Math.sin(e.movementX / canvas_size), 0,
0, 1, 0, 0,
Math.sin(e.movementX / canvas_size), 0, Math.cos(e.movementX / canvas_size), 0,
0, 0, 0, 1,
], transform);
transform = matrix_mul([
1, 0, 0, 0,
0, Math.cos(e.movementY / canvas_size), Math.sin(e.movementY / canvas_size), 0,
0, -Math.sin(e.movementY / canvas_size), Math.cos(e.movementY / canvas_size), 0,
0, 0, 0, 1,
], transform);
});
function animation_frame_callback(t) {
running = null;
if (!time) { time = t; }
let dt = (t - time) / 1000;
time = t;
if (key_pressed["KeyW"]) {
transform = matrix_mul([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, dt, 1
], transform);
}
if (key_pressed["KeyS"]) {
transform = matrix_mul([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, -dt, 1
], transform);
}
if (key_pressed["ShiftLeft"]) {
transform = matrix_mul([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, dt, 0, 1
], transform);
}
if (key_pressed["Space"]) {
transform = matrix_mul([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, -dt, 0, 1
], transform);
}
if (key_pressed["KeyD"]) {
transform = matrix_mul([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
-dt, 0, 0, 1
], transform);
}
if (key_pressed["KeyA"]) {
transform = matrix_mul([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
dt, 0, 0, 1
], transform);
}
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.uniformMatrix4fv(gl.getUniformLocation(program, "transform"), false, transform);
gl.uniformMatrix4fv(gl.getUniformLocation(program, "perspective"), false, perspective);
gl.uniform4fv(gl.getUniformLocation(program, "lightPositions"), Float32Array.from([1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1]));
gl.drawArraysInstanced(gl.TRIANGLES, 0, mesh_data.length / 8, instance_data.length / 16);
running = window.requestAnimationFrame(animation_frame_callback);
};
document.addEventListener('pointerlockchange', function (e) {
if (running) {
window.cancelAnimationFrame(running)
}
if (document.pointerLockElement === canvas) {
key_pressed = {};
running = window.requestAnimationFrame(animation_frame_callback);
}
}, false);
</script>
</body>
</html>