Compare commits

...

19 Commits
u02 ... main

11 changed files with 84668 additions and 128 deletions

View File

@ -4,42 +4,69 @@
<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="wavefront.js"></script>
<script src="script.js"></script>
<style>
body {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 100vh;
:root {
font-size: 20px;
--padding: 10px;
}
html, body {
margin: 0;
height: 100%;
}
body {
padding: 1vw;
box-sizing: border-box;
background-color: #1e1e2e;
color: #cdd6f4;
font-family: monospace;
font-size: 20px;
gap: 20px;
overflow: hidden;
}
#display {
position: relative;
overflow: hidden;
box-sizing: border-box;
border-radius: 20px;
height: 100%;
width: 100%;
border: solid 2px #11111b;
}
#cv {
border: solid 2px #11111b;
border-radius: 20px;
position: relative;
max-width: 100%;
max-height: 100%;
width: 100%;
height: 100%;
display: block;
background-color: #181825;
}
#stats {
width: 100%;
max-width: 800px;
box-sizing: border-box;
padding: 20px;
position: absolute;
top: var(--padding);
padding: var(--padding);
display: block;
border: solid 2px #11111b;
background-color: #313244;
border-radius: 20px;
background-color: #181825;
}
#stats {
right: var(--padding);
}
</style>
</head>
<body>
<canvas id="cv" width="800" height="600"></canvas>
<div id="stats">
<div>FPS: <span id="fps"></span></div>
<div>Frame time: <span id="ms"></span></div>
<div id="display">
<canvas id="cv"></canvas>
<div id="stats">
FPS: <span id="fps"></span> - <span id="ms"></span>
</div>
</div>
</body>
</html>

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

@ -0,0 +1,530 @@
"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, inm);
}
/**
* 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;
}
function mat4vec4Multiply(out, inMat, inVec) {
let result = [0, 0, 0, 0];
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
result[i] += inMat[i + j * 4] * inVec[j];
}
}
out[0] = result[0];
out[1] = result[1];
out[2] = result[2];
out[3] = result[3];
}
/**
* 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);
}
function mat3MakeNormal(out, inm) {
let result = [];
mat3From4(result, inm);
mat3Inverse(result, result);
mat3Transpose(result, result);
mat3Copy(result, out);
}

34
src/obj/cube.obj Normal file
View File

@ -0,0 +1,34 @@
# Blender 4.4.3
# www.blender.org
o Cube
v 1.000000 1.000000 -1.000000
v 1.000000 -1.000000 -1.000000
v 1.000000 1.000000 1.000000
v 1.000000 -1.000000 1.000000
v -1.000000 1.000000 -1.000000
v -1.000000 -1.000000 -1.000000
v -1.000000 1.000000 1.000000
v -1.000000 -1.000000 1.000000
vn -0.0000 1.0000 -0.0000
vn -0.0000 -0.0000 1.0000
vn -1.0000 -0.0000 -0.0000
vn -0.0000 -1.0000 -0.0000
vn 1.0000 -0.0000 -0.0000
vn -0.0000 -0.0000 -1.0000
vt 1.000000 0.000000
vt 0.000000 1.000000
vt 0.000000 0.000000
vt 1.000000 1.000000
s 0
f 5/1/1 3/2/1 1/3/1
f 3/1/2 8/2/2 4/3/2
f 7/1/3 6/2/3 8/3/3
f 2/1/4 8/2/4 6/3/4
f 1/1/5 4/2/5 2/3/5
f 5/1/6 2/2/6 6/3/6
f 5/1/1 7/4/1 3/2/1
f 3/1/2 7/4/2 8/2/2
f 7/1/3 5/4/3 6/2/3
f 2/1/4 4/4/4 8/2/4
f 1/1/5 3/4/5 4/2/5
f 5/1/6 1/4/6 2/2/6

33819
src/obj/monkey.obj Normal file

File diff suppressed because it is too large Load Diff

33905
src/obj/smooth_monkey.obj Normal file

File diff suppressed because it is too large Load Diff

16109
src/obj/teapot.obj Normal file

File diff suppressed because it is too large Load Diff

View File

@ -4,122 +4,120 @@ let cv, gl;
let program;
let vertices;
let vertexBuffer;
class Object {
vertexBuffer;
numVertices;
}
let o = new Object();
let lastFrame = Date.now();
let cameraPitch = 0.565;
let cameraYaw = 0.375;
let cameraDistance = 4;
let smoothCameraDistance = 4;
let mouseDragging = false;
let frameTimes = [];
function resizeCanvas() {
const dpr = window.devicePixelRatio;
cv.width = cv.parentElement.clientWidth * dpr;
cv.height = cv.parentElement.clientHeight * dpr;
gl.viewport(0, 0, cv.width, cv.height);
}
async function init() {
cv = document.getElementById("cv");
gl = cv.getContext("webgl");
if (!gl) {
window.alert("WebGL not supported");
}
const resizeObserver = new ResizeObserver(resizeCanvas);
resizeObserver.observe(cv.parentElement);
resizeCanvas();
// input handling
cv.addEventListener("mousedown", (e) => {
if (e.button == 0) {
mouseDragging = true;
}
});
document.addEventListener("mouseup", (e) => {
if (e.button == 0) {
mouseDragging = false;
}
});
document.addEventListener("mouseleave", (e) => {
mouseDragging = false;
});
document.addEventListener("mousemove", (e) => {
if (mouseDragging) {
cameraYaw += e.movementX / 100;
cameraPitch += e.movementY / 100;
if (cameraPitch < -Math.PI / 2) cameraPitch = -Math.PI / 2;
if (cameraPitch > Math.PI / 2) cameraPitch = Math.PI / 2;
}
});
cv.addEventListener("wheel", (e) => {
cameraDistance += e.deltaY / 100;
if (cameraDistance < 1) cameraDistance = 1;
if (cameraDistance > 15) cameraDistance = 15;
e.preventDefault();
});
// end input handling
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);
// get location of vertPosition attribute
let positionAttribLocation = gl.getAttribLocation(program, "vertPosition");
let f = await readObjFile("obj/smooth_monkey.obj");
o = new Object();
gl.vertexAttribPointer(
positionAttribLocation, // which attribute is read
2, // 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
0 // offset where to start reading
);
// enable attribute
gl.enableVertexAttribArray(positionAttribLocation);
// unbind buffer
o.vertexBuffer = gl.createBuffer();
o.numVertices = f.length / 8;
gl.bindBuffer(gl.ARRAY_BUFFER, o.vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(f), gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
// set clear colour
gl.clearColor(0.1, 0.1, 0.1, 1.0);
gl.clearColor(...hexToRgb("181825"), 1.0);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.BLEND);
gl.enable(gl.CULL_FACE);
gl.frontFace(gl.CCW);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
// start drawing
requestAnimationFrame(draw);
}
function setAttribPointers() {
let positionAttribLocation = gl.getAttribLocation(program, "vertPosition");
let normalAttribLocation = gl.getAttribLocation(program, "vertNormal");
let textureAttribLocation = gl.getAttribLocation(program, "vertTexture");
gl.vertexAttribPointer(positionAttribLocation, 3, gl.FLOAT, gl.FALSE, 8 * Float32Array.BYTES_PER_ELEMENT, 0);
gl.vertexAttribPointer(textureAttribLocation, 2, gl.FLOAT, gl.FALSE, 8 * Float32Array.BYTES_PER_ELEMENT, 3 * Float32Array.BYTES_PER_ELEMENT);
gl.vertexAttribPointer(normalAttribLocation, 3, gl.FLOAT, gl.FALSE, 8 * Float32Array.BYTES_PER_ELEMENT, 5 * Float32Array.BYTES_PER_ELEMENT);
gl.enableVertexAttribArray(positionAttribLocation);
gl.enableVertexAttribArray(textureAttribLocation);
gl.enableVertexAttribArray(normalAttribLocation);
}
let frameCount = 0;
let lastStatUpdate = Date.now();
@ -137,8 +135,11 @@ function updateStats(deltaTime) {
fpsDisplay.innerText = Math.round(frameCount / timeSinceLastUpdate)
let msDisplay = document.getElementById("ms");
msDisplay.innerText = deltaTime * 1000 + "ms";
if (frameTimes.length > 0) {
msDisplay.innerText = (Math.round(frameTimes.reduce((prev, curr) => prev + curr) / frameTimes.length * 100) / 100) + "ms";
}
frameTimes = [];
frameCount = 0;
}
@ -147,31 +148,60 @@ let hue = 0;
async function draw() {
let now = Date.now();
let deltaTime = (now - lastFrame) / 1000;
let frameStart = performance.now();
hue += deltaTime / 5;
if (hue > 1) hue -= 1;
if (hue > 1) hue = 0;
smoothCameraDistance += (cameraDistance - smoothCameraDistance) / 8;
updateStats(deltaTime);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
let identity = [];
mat4Identity(identity);
// Real Matrices
let projectionMatrix = [];
mat4BuildPerspective(projectionMatrix, 90.0 / 180.0 * Math.PI, cv.width / cv.height, 0.1, 30);
let viewMatrix = [];
let cameraPosition = [Math.cos(cameraYaw) * smoothCameraDistance * Math.cos(cameraPitch), Math.sin(cameraPitch) * smoothCameraDistance, Math.sin(cameraYaw) * smoothCameraDistance * Math.cos(cameraPitch)];
let cameraLookAt = [0, 0, 0];
let cameraUp = [0, 1, 0];
mat4BuildLookAt(viewMatrix, cameraPosition, cameraLookAt, cameraUp);
let normalMatrix = [];
mat3MakeNormal(normalMatrix, viewMatrix);
gl.useProgram(program);
gl.uniformMatrix4fv(gl.getUniformLocation(program, "projectionMatrix"), gl.FALSE, new Float32Array(projectionMatrix));
gl.uniformMatrix4fv(gl.getUniformLocation(program, "modelViewMatrix"), gl.FALSE, new Float32Array(viewMatrix));
gl.uniformMatrix3fv(gl.getUniformLocation(program, "normalMatrix"), gl.FALSE, new Float32Array(normalMatrix));
let colorLocation = gl.getUniformLocation(program, "color");
gl.uniform3f(colorLocation, ...hslToRgb(hue, 1, 0.5));
let lightPosition = [Math.cos(hue * Math.PI * 2) * 3, 2.0, Math.sin(hue * Math.PI * 2) * 3, 1.0];
mat4vec4Multiply(lightPosition, viewMatrix, lightPosition);
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);
gl.uniform3f(gl.getUniformLocation(program, "Light.position"), ...lightPosition.slice(0, 3));
gl.uniform3f(gl.getUniformLocation(program, "Light.color"), 1, 1, 1);
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.uniform3f(gl.getUniformLocation(program, "Material.ambientColor"), 0.1, 0.1, 0.1);
gl.uniform3f(gl.getUniformLocation(program, "Material.emitColor"), 0.05, 0.05, 0.0);
gl.uniform3f(gl.getUniformLocation(program, "Material.diffColor"), 0.7, 0.8, 0.1);
gl.uniform3f(gl.getUniformLocation(program, "Material.specColor"), 1, 1, 0.8);
gl.uniform1f(gl.getUniformLocation(program, "Material.shininess"), 10);
gl.drawArrays(gl.TRIANGLES, 0, vertices.length / 2);
gl.bindBuffer(gl.ARRAY_BUFFER, o.vertexBuffer);
setAttribPointers();
gl.drawArrays(gl.TRIANGLES, 0, o.numVertices);
frameTimes.push(performance.now() - frameStart);
lastFrame = now;
requestAnimationFrame(draw);
}
function hexColorToFloatArray(hex) {
function hexToRgb(hex) {
let r = parseInt(hex.substring(0, 2), 16) / 255;
let g = parseInt(hex.substring(2, 4), 16) / 255;
let b = parseInt(hex.substring(4, 6), 16) / 255;

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,6 +1,33 @@
precision mediump float;
varying vec3 fragColor;
uniform struct {
vec3 position;
vec3 color;
} Light;
uniform struct {
vec3 ambientColor;
vec3 emitColor;
vec3 diffColor;
vec3 specColor;
float shininess;
} Material;
varying vec3 vNormal;
varying vec2 vTexture;
varying vec3 vPosition;
void main() {
gl_FragColor = vec4(fragColor, 1.0);
vec3 pleaseDontDeleteMe = vec3(vTexture, 0.0);
vec3 eyeDir = normalize(-vPosition);
vec3 lightDir = normalize(Light.position - vPosition);
vec3 r = reflect(-lightDir, vNormal);
vec3 ambient = Material.ambientColor;
vec3 emit = Material.emitColor;
vec3 diff = max(dot(lightDir, vNormal), 0.0) * Material.diffColor * Light.color;
vec3 spec = pow(max(dot(r, eyeDir), 0.0), Material.shininess) * Material.specColor * Light.color;
gl_FragColor = vec4(ambient + emit + diff + spec, 1);
}

View File

@ -1,11 +1,21 @@
precision mediump float;
attribute vec2 vertPosition;
uniform vec3 color;
uniform vec2 position;
varying vec3 fragColor;
attribute vec3 vertPosition;
attribute vec3 vertNormal;
attribute vec2 vertTexture;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform mat3 normalMatrix;
varying vec3 vNormal;
varying vec2 vTexture;
varying vec3 vPosition;
void main() {
fragColor = color;
gl_Position = vec4(position + vertPosition, 0.0, 1.0);
vNormal = normalize(normalMatrix * vertNormal);
vTexture = vertTexture;
vPosition = (modelViewMatrix * vec4(vertPosition, 1.0)).xyz;
gl_Position = projectionMatrix * modelViewMatrix * vec4(vertPosition, 1.0);
}

43
src/wavefront.js Normal file
View File

@ -0,0 +1,43 @@
async function readObjFile(path) {
const res = await fetch(path);
const text = await res.text();
let vertices = [];
let textures = [];
let normals = [];
let result = [];
const lines = text.replace("\r","").split("\n");
for (let line of lines) {
const parts = line.split(/\s+/);
switch (parts[0]) {
case "v":
vertices.push(parts.slice(1, 4).map(parseFloat));
break;
case "vt":
textures.push(parts.slice(1, 3).map(parseFloat));
break;
case "vn":
normals.push(parts.slice(1, 4).map(parseFloat));
break;
case "f":
for (const part of parts.slice(1, 4)) {
const [v, vt, vn] = part.split("/").map((i) => parseInt(i) - 1);
result.push(...vertices[v], ...textures[vt], ...normals[vn]);
}
// support for quads:
if (parts.length > 4) {
for (let i of [1, 3, 4]) {
const part = parts[i];
const [v, vt, vn] = part.split("/").map((i) => parseInt(i) - 1);
result.push(...vertices[v], ...textures[vt], ...normals[vn]);
}
}
}
}
return result;
}