Skip to content

Instantly share code, notes, and snippets.

@EncodeTheCode
Created May 14, 2025 02:58
Show Gist options
  • Save EncodeTheCode/ef07ad0056d5fc95ff83ff5cd7323e10 to your computer and use it in GitHub Desktop.
Save EncodeTheCode/ef07ad0056d5fc95ff83ff5cd7323e10 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>3D Room FPS Style</title>
<style>
body { margin: 0; overflow: hidden; }
canvas { display: block; background: #000; }
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
const canvas = document.getElementById("canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const gl = canvas.getContext("webgl");
if (!gl) alert("WebGL not supported");
const vs = `
attribute vec4 position;
uniform mat4 model, view, projection;
void main() {
gl_Position = projection * view * model * position;
}
`;
const fs = `
precision mediump float;
uniform vec4 color;
void main() {
gl_FragColor = color;
}
`;
function compileShader(type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
return shader;
}
const program = gl.createProgram();
gl.attachShader(program, compileShader(gl.VERTEX_SHADER, vs));
gl.attachShader(program, compileShader(gl.FRAGMENT_SHADER, fs));
gl.linkProgram(program);
gl.useProgram(program);
const posAttr = gl.getAttribLocation(program, "position");
const modelUni = gl.getUniformLocation(program, "model");
const viewUni = gl.getUniformLocation(program, "view");
const projUni = gl.getUniformLocation(program, "projection");
const colorUni = gl.getUniformLocation(program, "color");
function mat4() {
return new Float32Array(16);
}
function perspective(out, fov, aspect, near, far) {
const f = 1.0 / Math.tan(fov / 2);
out[0] = f / aspect;
out[5] = f;
out[10] = (far + near) / (near - far);
out[11] = -1;
out[14] = (2 * far * near) / (near - far);
return out;
}
function lookAt(out, eye, center, up) {
const x0 = eye[0] - center[0];
const x1 = eye[1] - center[1];
const x2 = eye[2] - center[2];
const len = Math.hypot(x0, x1, x2);
const z = [x0 / len, x1 / len, x2 / len];
const x = [
up[1] * z[2] - up[2] * z[1],
up[2] * z[0] - up[0] * z[2],
up[0] * z[1] - up[1] * z[0]
];
const l = Math.hypot(...x);
x[0] /= l; x[1] /= l; x[2] /= l;
const y = [
z[1] * x[2] - z[2] * x[1],
z[2] * x[0] - z[0] * x[2],
z[0] * x[1] - z[1] * x[0]
];
out.set([
x[0], y[0], z[0], 0,
x[1], y[1], z[1], 0,
x[2], y[2], z[2], 0,
-(x[0] * eye[0] + x[1] * eye[1] + x[2] * eye[2]),
-(y[0] * eye[0] + y[1] * eye[1] + y[2] * eye[2]),
-(z[0] * eye[0] + z[1] * eye[1] + z[2] * eye[2]),
1
]);
return out;
}
function identity(out) {
out.set([1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1]);
return out;
}
function translate(out, x, y, z) {
identity(out);
out[12] = x;
out[13] = y;
out[14] = z;
return out;
}
const cube = new Float32Array([
-1,-1,-1, 1,-1,-1, 1,1,-1, -1,1,-1,
-1,-1,1, 1,-1,1, 1,1,1, -1,1,1
]);
const indices = new Uint16Array([
0,1,2, 2,3,0,
4,5,6, 6,7,4,
0,1,5, 5,4,0,
2,3,7, 7,6,2,
1,2,6, 6,5,1,
3,0,4, 4,7,3
]);
const vbo = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
gl.bufferData(gl.ARRAY_BUFFER, cube, gl.STATIC_DRAW);
gl.enableVertexAttribArray(posAttr);
gl.vertexAttribPointer(posAttr, 3, gl.FLOAT, false, 0, 0);
const ibo = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
const proj = mat4();
const view = mat4();
const model = mat4();
perspective(proj, Math.PI / 4, canvas.width / canvas.height, 0.1, 100);
gl.enable(gl.DEPTH_TEST);
let keys = {};
let mouseDown = false;
let camera = {
x: 0, y: 1.6, z: 5,
rotY: 0,
move(forward, strafe) {
this.x += Math.sin(this.rotY) * forward + Math.cos(this.rotY) * strafe;
this.z -= Math.cos(this.rotY) * forward - Math.sin(this.rotY) * strafe;
}
};
document.addEventListener("keydown", e => keys[e.key.toLowerCase()] = true);
document.addEventListener("keyup", e => keys[e.key.toLowerCase()] = false);
document.addEventListener("mousedown", e => { if (e.button === 0) mouseDown = true; });
document.addEventListener("mouseup", e => { if (e.button === 0) mouseDown = false; });
function render() {
if (keys['w'] && mouseDown) camera.move(0.2, 0);
if (keys['s']) camera.move(-0.2, 0);
if (keys['a']) camera.move(0, 0.2);
if (keys['d']) camera.move(0, -0.2);
if (keys['arrowleft']) camera.rotY -= 0.03;
if (keys['arrowright']) camera.rotY += 0.03;
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
let cx = Math.sin(camera.rotY);
let cz = Math.cos(camera.rotY);
lookAt(view, [camera.x, camera.y, camera.z], [camera.x + cx, camera.y, camera.z - cz], [0, 1, 0]);
gl.uniformMatrix4fv(projUni, false, proj);
gl.uniformMatrix4fv(viewUni, false, view);
// Walls (room)
let walls = [
[0, 0, -10], [0, 0, 10], [-10, 0, 0], [10, 0, 0], [0, 5, 0], [0, -5, 0]
];
walls.forEach(pos => {
translate(model, ...pos);
gl.uniformMatrix4fv(modelUni, false, model);
gl.uniform4f(colorUni, 0.3, 0.3, 0.3, 1);
gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);
});
// Boxes
for (let i = -4; i <= 4; i += 4) {
for (let j = -4; j <= 4; j += 4) {
translate(model, i, -4, j);
gl.uniformMatrix4fv(modelUni, false, model);
gl.uniform4f(colorUni, 0.6, 0.4, 0.3, 1);
gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);
}
}
requestAnimationFrame(render);
}
render();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment