This commit is contained in:
Luca Conte 2025-04-24 17:00:50 +02:00
parent 65d7ee8e82
commit 64bf69ec71
5 changed files with 625 additions and 90 deletions

View File

@ -4,6 +4,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>CG1 MDI</title>
<script src="matrix-math.js"></script>
<script src="shader.js"></script>
<script src="script.js"></script>
<style>

510
src/matrix-math.js Normal file
View File

@ -0,0 +1,510 @@
"use strict";
/**
* overwrites a 4x4 matrix with the identity matrix
*/
function mat4Identity(mat) {
for (let i = 0; i < 16; i++) {
mat[i] = (i % 5 == 0) ? 1 : 0;
}
}
/**
* copies a mat4 from src to dst
*/
function mat4Copy(src, dst) {
for (let i = 0; i < 16; i++) {
dst[i] = src[i];
}
}
/**
* sets all the values in a mat4 to zero
*/
function mat4Empty(mat) {
for (let i = 0; i < 16; i++) {
mat[i] = 0;
}
}
/**
* mutliplies A with B and stores the result in result
*/
function mat4Multiply(result, A, B) {
// if result is one of the arguments, modify copy instead of result directly
if (result == A || result == B) {
let tempResult = [];
mat4Multiply(tempResult, A, B);
mat4Copy(tempResult, result);
return;
}
// loops over cells of the result matrix
for (let i = 0; i < 16; i++) {
let col = (i / 4) | 0;
let row = i % 4;
// initialise current cell with 0
result[i] = 0;
// loop over the row of A and column of B
// continuously adding the multiplication of the two values to the result cell
for (let j = 0; j < 4; j++) {
result[i] += A[row + j * 4] * B[j + col * 4];
}
}
}
/**
* prints a mat4 to the screen
*/
function mat4Print(m) {
let str = "";
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
str += m[j * 4 + i] + " ";
}
str += "\n";
}
console.log(str);
}
/**
* translates by the vector v
*/
function mat4Translate(out, inm, v) {
let t = [];
mat4Identity(t);
t[12] = v[0];
t[13] = v[1];
t[14] = v[2];
mat4Multiply(out, t, inv);
}
/**
* scales by the vector v
*/
function mat4Scale(out, inm, v) {
let t = [];
mat4Identity(t);
t[0] = v[0];
t[5] = v[1];
t[10] = v[2];
mat4Multiply(out, t, inm);
}
/**
* rotates around the X axis by the angle a (in radians)
*/
function mat4RotateX(out, inm, a) {
let t = [];
mat4Identity(t);
t[5] = Math.cos(a);
t[6] = Math.sin(a);
t[9] = -Math.sin(a);
t[10] = Math.cos(a);
mat4Multiply(out, t, inm);
}
/**
* rotates around the Y axis by the angle a (in radians)
*/
function mat4RotateY(out, inm, a) {
let t = [];
mat4Identity(t);
t[0] = Math.cos(a);
t[2] = -Math.sin(a);
t[8] = Math.sin(a);
t[10] = Math.cos(a);
mat4Multiply(out, t, inm);
}
/**
* rotates around the Z axis by the angle a (in radians)
*/
function mat4RotateZ(out, inm, a) {
let t = [];
mat4Identity(t);
t[0] = Math.cos(a);
t[1] = Math.sin(a);
t[4] = -Math.sin(a);
t[5] = Math.cos(a);
mat4Multiply(out, t, inm);
}
/**
* builds a look-at matrix, overwriting out
* eye is the position of the camera
* look is the position of the target to be looked at
* up is the up vector
*/
function mat4BuildLookAt(out, eye, look, up) {
let n = [];
let u = [];
let v = [];
let t = [];
vec3Subtract(n, eye, look);
vec3CrossProduct(u, up, n);
vec3CrossProduct(v, n, u);
vec3Normalise(n, n);
vec3Normalise(u, u);
vec3Normalise(v, v);
t[0] = - vec3DotProduct(u, eye);
t[1] = - vec3DotProduct(v, eye);
t[2] = - vec3DotProduct(n, eye);
out[0] = u[0]; out[4] = u[1]; out[8] = u[2]; out[12] = t[0];
out[1] = v[0]; out[5] = v[1]; out[9] = v[2]; out[13] = t[1];
out[2] = n[0]; out[6] = n[1]; out[10] = n[2]; out[14] = t[2];
out[3] = 0; out[7] = 0; out[11] = 0; out[15] = 1;
}
/**
* builds a projection matrix, overwriting out
* r, l, t, b are the right, left, top and bottom of the frustum
* n and f are the distance of the near and far planes from the camera
*/
function mat4BuildProjection(out, r, l, t, b, n, f) {
mat4Identity(out);
out[0] = 2.0 / (r - l);
out[5] = 2.0 / (t - b);
out[8] = 1.0 / n * (r + l) / (r - l);
out[9] = 1.0 / n * (t + b) / (t - b);
out[10] = -1.0 / n * (f + n) / (f - n);
out[11] = -1.0 / n;
out[14] = - 2.0 * f / (f - n);
out[15] = 0.0;
}
/**
* builds a perspective projection matrix, overwriting out
* fovy is the field of view in the y direction
* aspect is the aspect ratio
* n and f are the distance of the near and far planes from the camera
*/
function mat4BuildPerspective(out, fovy, aspect, n, f) {
let t = n * Math.tan(0.5 * fovy);
let b = -t;
let r = aspect * t;
let l = -r;
mat4BuildProjection(out, r, l, t, b, n, f);
}
/**
* linearly interpolates between a and b, storing the result in out
*/
function mat4Interpolate(out, a, b, f) {
for (let i = 0; i < 16; i++) {
out[i] = (1 - f) * a[i] + f * b[i];
}
}
function mat4From3(out, inm) {
mat4Identity(out);
out[0] = inm[0];
out[1] = inm[1];
out[2] = inm[2];
out[4] = inm[3];
out[5] = inm[4];
out[6] = inm[5];
out[8] = inm[6];
out[9] = inm[7];
out[10] = inm[8];
}
/**
* https://stackoverflow.com/questions/1148309/inverting-a-4x4-matrix
*/
function mat4Inverse(out, m) {
let inv = [];
let det;
let i;
inv[0] = m[5] * m[10] * m[15] -
m[5] * m[11] * m[14] -
m[9] * m[6] * m[15] +
m[9] * m[7] * m[14] +
m[13] * m[6] * m[11] -
m[13] * m[7] * m[10];
inv[4] = -m[4] * m[10] * m[15] +
m[4] * m[11] * m[14] +
m[8] * m[6] * m[15] -
m[8] * m[7] * m[14] -
m[12] * m[6] * m[11] +
m[12] * m[7] * m[10];
inv[8] = m[4] * m[9] * m[15] -
m[4] * m[11] * m[13] -
m[8] * m[5] * m[15] +
m[8] * m[7] * m[13] +
m[12] * m[5] * m[11] -
m[12] * m[7] * m[9];
inv[12] = -m[4] * m[9] * m[14] +
m[4] * m[10] * m[13] +
m[8] * m[5] * m[14] -
m[8] * m[6] * m[13] -
m[12] * m[5] * m[10] +
m[12] * m[6] * m[9];
inv[1] = -m[1] * m[10] * m[15] +
m[1] * m[11] * m[14] +
m[9] * m[2] * m[15] -
m[9] * m[3] * m[14] -
m[13] * m[2] * m[11] +
m[13] * m[3] * m[10];
inv[5] = m[0] * m[10] * m[15] -
m[0] * m[11] * m[14] -
m[8] * m[2] * m[15] +
m[8] * m[3] * m[14] +
m[12] * m[2] * m[11] -
m[12] * m[3] * m[10];
inv[9] = -m[0] * m[9] * m[15] +
m[0] * m[11] * m[13] +
m[8] * m[1] * m[15] -
m[8] * m[3] * m[13] -
m[12] * m[1] * m[11] +
m[12] * m[3] * m[9];
inv[13] = m[0] * m[9] * m[14] -
m[0] * m[10] * m[13] -
m[8] * m[1] * m[14] +
m[8] * m[2] * m[13] +
m[12] * m[1] * m[10] -
m[12] * m[2] * m[9];
inv[2] = m[1] * m[6] * m[15] -
m[1] * m[7] * m[14] -
m[5] * m[2] * m[15] +
m[5] * m[3] * m[14] +
m[13] * m[2] * m[7] -
m[13] * m[3] * m[6];
inv[6] = -m[0] * m[6] * m[15] +
m[0] * m[7] * m[14] +
m[4] * m[2] * m[15] -
m[4] * m[3] * m[14] -
m[12] * m[2] * m[7] +
m[12] * m[3] * m[6];
inv[10] = m[0] * m[5] * m[15] -
m[0] * m[7] * m[13] -
m[4] * m[1] * m[15] +
m[4] * m[3] * m[13] +
m[12] * m[1] * m[7] -
m[12] * m[3] * m[5];
inv[14] = -m[0] * m[5] * m[14] +
m[0] * m[6] * m[13] +
m[4] * m[1] * m[14] -
m[4] * m[2] * m[13] -
m[12] * m[1] * m[6] +
m[12] * m[2] * m[5];
inv[3] = -m[1] * m[6] * m[11] +
m[1] * m[7] * m[10] +
m[5] * m[2] * m[11] -
m[5] * m[3] * m[10] -
m[9] * m[2] * m[7] +
m[9] * m[3] * m[6];
inv[7] = m[0] * m[6] * m[11] -
m[0] * m[7] * m[10] -
m[4] * m[2] * m[11] +
m[4] * m[3] * m[10] +
m[8] * m[2] * m[7] -
m[8] * m[3] * m[6];
inv[11] = -m[0] * m[5] * m[11] +
m[0] * m[7] * m[9] +
m[4] * m[1] * m[11] -
m[4] * m[3] * m[9] -
m[8] * m[1] * m[7] +
m[8] * m[3] * m[5];
inv[15] = m[0] * m[5] * m[10] -
m[0] * m[6] * m[9] -
m[4] * m[1] * m[10] +
m[4] * m[2] * m[9] +
m[8] * m[1] * m[6] -
m[8] * m[2] * m[5];
det = m[0] * inv[0] + m[1] * inv[4] + m[2] * inv[8] + m[3] * inv[12];
if (det == 0) {
mat4Copy(m, out); // singular matrix, can't invert
return;
}
det = 1.0 / det;
for (i = 0; i < 16; i++)
out[i] = inv[i] * det;
return;
}
/**
* subtracts b from a, storing the result in out
*/
function vec3Subtract(out, a, b) {
out[0] = a[0] - b[0];
out[1] = a[1] - b[1];
out[2] = a[2] - b[2];
}
/**
* calculates the cross product of a and b, storing the result in out
*/
function vec3CrossProduct(out, a, b) {
let result = [];
result[0] = a[1] * b[2] - a[2] * b[1];
result[1] = a[2] * b[0] - a[0] * b[2];
result[2] = a[0] * b[1] - a[1] * b[0];
out[0] = result[0];
out[1] = result[1];
out[2] = result[2];
}
/**
* normalizes in storing the result in out
*/
function vec3Normalise(out, inv) {
let length = vec3Length(inv);
out[0] = inv[0] / length;
out[1] = inv[1] / length;
out[2] = inv[2] / length;
}
/**
* returns the length of the vector in
*/
function vec3Length(inv) {
return Math.sqrt(inv[0] * inv[0] + inv[1] * inv[1] + inv[2] * inv[2]);
}
/**
* returns the dot product of the vectors a and b
*/
function vec3DotProduct(a, b) {
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
}
/**
* sets the values of out to x, y and z
*/
function vec3Set(out, x, y, z) {
out[0] = x;
out[1] = y;
out[2] = z;
}
function mat3Copy(src, dst) {
for (let i = 0; i < 9; i++) {
dst[i] = src[i];
}
}
function mat3From4(out, inm) {
out[0] = inm[0];
out[1] = inm[1];
out[2] = inm[2];
out[3] = inm[4];
out[4] = inm[5];
out[5] = inm[6];
out[6] = inm[8];
out[7] = inm[9];
out[8] = inm[10];
}
function mat3Transpose(out, inm) {
let result = [];
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
result[i * 3 + j] = inm[j * 3 + i];
}
}
mat3Copy(result, out);
}
function mat3Minor(out, inm) {
let result = [];
// TODO: check if this is correct
result[0] = inm[4] * inm[8] - inm[5] * inm[7];
result[1] = inm[3] * inm[8] - inm[5] * inm[6];
result[2] = inm[3] * inm[7] - inm[4] * inm[6];
result[3] = inm[1] * inm[8] - inm[2] * inm[7];
result[4] = inm[0] * inm[8] - inm[2] * inm[6];
result[5] = inm[0] * inm[7] - inm[1] * inm[6];
result[6] = inm[1] * inm[5] - inm[2] * inm[4];
result[7] = inm[0] * inm[5] - inm[2] * inm[3];
result[8] = inm[0] * inm[4] - inm[1] * inm[3];
mat3Copy(result, out);
}
function mat3Cofactor(out, inm) {
mat3Minor(out, inm);
out[1] *= -1;
out[3] *= -1;
out[5] *= -1;
out[7] *= -1;
}
function mat3Adjoint(out, inm) {
mat3Cofactor(out, inm);
mat3Transpose(out, inm);
}
function mat3MultiplyScalar(out, inm, x) {
for (let i = 0; i < 9; i++) {
out[i] = inm[i] * x;
}
}
function mat3Determinant(m) {
return + m[0] * m[4] * m[8] + m[3] * m[7] * m[2] + m[6] * m[1] * m[5] - m[2] * m[4] * m[6] - m[5] * m[7] * m[0] - m[8] * m[1] * m[3];
}
function mat3Inverse(out, inm) {
let result = [];
mat3Adjoint(result, inm);
mat3MultiplyScalar(result, result, 1 / mat3Determinant(inm));
mat3Copy(result, out);
}

View File

@ -4,8 +4,8 @@ let cv, gl;
let program;
let vertices;
let vertexBuffer;
let cubeVertices;
let cubeVertexBuffer;
let lastFrame = Date.now();
@ -18,104 +18,100 @@ async function init() {
}
console.log("compiling shaders...");
console.log("compiling vertex shader...");
let vertexShader = await fetchAndCompileShader(gl, gl.VERTEX_SHADER, "shaders/vertex.glsl");
console.log("compiling fragment shader...");
let fragmentShader = await fetchAndCompileShader(gl, gl.FRAGMENT_SHADER, "shaders/fragment.glsl");
console.log("linking shader program...");
program = linkShaderProgram(gl, vertexShader, fragmentShader);
/**
* -0.35 0.35
* | -0.2 0.2 |
* | | | |
*
* +----+ +----+ --- 0.6
* | | | |
* | | | |
* | +--------+ | --- 0.1
* | |
* | +--------+ | --- -0.1
* | | | |
* | | | |
* +----+ +----+ --- -0.6
*
* +------------------+ --- -0.7
* | |
* +------------------+ --- -0.9
*
*/
vertices = [
// X // Y
// left vertical bar
-0.35, -0.6,
-0.35, 0.6,
-0.2, 0.6,
-0.35, -0.6,
-0.2, 0.6,
-0.2, -0.6,
// right vertical bar
0.35, -0.6,
0.35, 0.6,
0.2, 0.6,
0.35, -0.6,
0.2, 0.6,
0.2, -0.6,
// middle bar
-0.2, -0.1,
-0.2, 0.1,
0.2, 0.1,
-0.2, -0.1,
0.2, 0.1,
0.2, -0.1,
// bottom bar
-0.35, -0.7,
-0.35, -0.9,
0.35, -0.9,
-0.35, -0.7,
0.35, -0.9,
0.35, -0.7
];
program = await buildShaderProgram(gl, "shaders/vertex.glsl", "shaders/fragment.glsl");
console.log("creating vertex buffer");
vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
cubeVertices = [
// Back face (red)
-1, -1, -1, 1, 0.5, 0.5,
-1, 1, -1, 1, 0.5, 0.5,
1, 1, -1, 1, 0.5, 0.5,
-1, -1, -1, 1, 0.5, 0.5,
1, 1, -1, 1, 0.5, 0.5,
1, -1, -1, 1, 0.5, 0.5,
// Front face (green)
-1, -1, 1, 0.5, 1, 0.5,
1, -1, 1, 0.5, 1, 0.5,
1, 1, 1, 0.5, 1, 0.5,
-1, -1, 1, 0.5, 1, 0.5,
1, 1, 1, 0.5, 1, 0.5,
-1, 1, 1, 0.5, 1, 0.5,
// Left face (blue)
-1, 1, -1, 0.5, 0.5, 1,
-1, -1, -1, 0.5, 0.5, 1,
-1, 1, 1, 0.5, 0.5, 1,
-1, 1, 1, 0.5, 0.5, 1,
-1, -1, -1, 0.5, 0.5, 1,
-1, -1, 1, 0.5, 0.5, 1,
// Right face (yellow)
1, -1, -1, 1, 1, 0.5,
1, 1, -1, 1, 1, 0.5,
1, 1, 1, 1, 1, 0.5,
1, -1, -1, 1, 1, 0.5,
1, 1, 1, 1, 1, 0.5,
1, -1, 1, 1, 1, 0.5,
// Top face (magenta)
-1, 1, -1, 1, 0.5, 1,
-1, 1, 1, 1, 0.5, 1,
1, 1, 1, 1, 0.5, 1,
-1, 1, -1, 1, 0.5, 1,
1, 1, 1, 1, 0.5, 1,
1, 1, -1, 1, 0.5, 1,
// Bottom face (cyan)
-1, -1, 1, 0.5, 1, 1,
-1, -1, -1, 0.5, 1, 1,
1, -1, 1, 0.5, 1, 1,
1, -1, 1, 0.5, 1, 1,
-1, -1, -1, 0.5, 1, 1,
1, -1, -1, 0.5, 1, 1
];
cubeVertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(cubeVertices), gl.STATIC_DRAW);
// get location of vertPosition attribute
let positionAttribLocation = gl.getAttribLocation(program, "vertPosition");
let colorAttribLocation = gl.getAttribLocation(program, "vertColor");
gl.vertexAttribPointer(
positionAttribLocation, // which attribute is read
2, // number of values to read
3, // number of values to read
gl.FLOAT, // type of values
gl.FALSE, // whether to normalize
2 * Float32Array.BYTES_PER_ELEMENT, // size of individual vertex / distance between values to read
6 * Float32Array.BYTES_PER_ELEMENT, // size of individual vertex / distance between values to read
0 // offset where to start reading
);
// enable attribute
gl.enableVertexAttribArray(positionAttribLocation);
gl.vertexAttribPointer(
colorAttribLocation, // which attribute is read
3, // number of values to read
gl.FLOAT, // type of values
gl.FALSE, // whether to normalize
6 * Float32Array.BYTES_PER_ELEMENT, // size of individual vertex / distance between values to read
3 * Float32Array.BYTES_PER_ELEMENT // offset where to start reading
);
gl.enableVertexAttribArray(colorAttribLocation);
// unbind buffer
gl.bindBuffer(gl.ARRAY_BUFFER, null);
// set clear colour
gl.clearColor(...hexToRgb("181825"), 1.0);
gl.enable(gl.CULL_FACE);
// start drawing
requestAnimationFrame(draw);
}
@ -153,19 +149,38 @@ async function draw() {
updateStats(deltaTime);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.useProgram(program);
let colorLocation = gl.getUniformLocation(program, "color");
gl.uniform3f(colorLocation, ...hslToRgb(hue, 1, 0.5));
let modelViewLocation = gl.getUniformLocation(program, "modelViewMatrix");
let projectionLocation = gl.getUniformLocation(program, "projectionMatrix");
let positionLocation = gl.getUniformLocation(program, "position");
gl.uniform2f(positionLocation, Math.cos(hue * 2 * Math.PI) * 0.2, Math.sin(hue * 2 * Math.PI) * 0.2);
let modelMatrix = [];
mat4Identity(modelMatrix);
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
mat4RotateX(modelMatrix, modelMatrix, hue * 2 * Math.PI);
mat4RotateY(modelMatrix, modelMatrix, hue * 2 * Math.PI);
gl.drawArrays(gl.TRIANGLES, 0, vertices.length / 2);
let viewMatrix = [];
let cameraPosition = [2, 2, 2];
let cameraLookAt = [0, 0, -1];
let cameraUp = [0, 1, 0];
mat4BuildLookAt(viewMatrix, cameraPosition, cameraLookAt, cameraUp);
let modelViewMatrix = [];
mat4Multiply(modelViewMatrix, viewMatrix, modelMatrix);
gl.uniformMatrix4fv(modelViewLocation, gl.FALSE, new Float32Array(modelViewMatrix));
let projectionMatrix = [];
mat4BuildPerspective(projectionMatrix, 90.0 / 180.0 * Math.PI, cv.width / cv.height, 0.1, 20);
gl.uniformMatrix4fv(projectionLocation, gl.FALSE, new Float32Array(projectionMatrix));
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexBuffer);
gl.drawArrays(gl.TRIANGLES, 0, cubeVertices.length / 6);
lastFrame = now;
requestAnimationFrame(draw);

View File

@ -45,4 +45,10 @@ function linkShaderProgram(gl, vertexShader, fragmentShader) {
}
return program;
}
async function buildShaderProgram(gl, vertexPath, fragmentPath) {
let vertexShader = await fetchAndCompileShader(gl, gl.VERTEX_SHADER, vertexPath);
let fragmentShader = await fetchAndCompileShader(gl, gl.FRAGMENT_SHADER, fragmentPath);
return linkShaderProgram(gl, vertexShader, fragmentShader);
}

View File

@ -1,11 +1,14 @@
precision mediump float;
attribute vec2 vertPosition;
uniform vec3 color;
uniform vec2 position;
attribute vec3 vertPosition;
attribute vec3 vertColor;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
varying vec3 fragColor;
void main() {
fragColor = color;
gl_Position = vec4(position + vertPosition, 0.0, 1.0);
fragColor = vertColor;
gl_Position = projectionMatrix * modelViewMatrix * vec4(vertPosition, 1.0);
}