Cg Lab 2023 Project Reineggersilber
A university computer graphics lab project implementing real-time animated 3D scenes directly against raw WebGL (no engine, no three.js) using hand-rolled GLSL shaders. A person wanders a furnished room, pulls out a portal gun, and steps through onto a heightmap-generated alien planet.



A university computer graphics lab project rendering a connected sequence of animated scenes directly against raw WebGL2 — no three.js, no engine, just a small hand-written framework.js wrapper and gl-matrix for the math. A person sits in a furnished room (lit by a spotlight from the TV), stands up, pulls out a Rick-and-Morty-style portal gun, and steps through a rendered portal onto an alien planet with terrain procedurally generated from a heightmap, before stepping back through a second portal. Each scene exercises a different rendering technique by hand against the WebGL pipeline: per-fragment lighting with a moving point light on the portal gun, GLSL shaders for surface shading, and heightmap-driven terrain generation for the alien planet — all keyframe-animated, including a camera move that follows the character between rooms. The heightmap terrain isn't sampled from an image: generateHeightmap() builds an n×m vertex grid where each vertex's height comes from a seeded pseudo-random generator (seedrandom("heightmap")), so the same terrain regenerates deterministically on every load. A strip along one edge is forced flat (flatHeight) so the character has level ground to walk on before the terrain ramps up. Per-vertex normals are computed afterward from the cross product of each triangle's edge vectors and accumulated/normalized across shared vertices, so lighting on the terrain looks continuous instead of faceted. The textures wrapped onto the room's surfaces and the alien terrain were generated with Stable Diffusion rather than hand-painted or photographed; the room's furniture and the portal gun model were modeled in Blender and exported as .obj for loading into the WebGL scene graph.
Architecture
graph TD Resources["loadResources()
images, .obj models, JSON"] --> Scene["sceneManager.js"] Scene --> GL["WebGL2 context
(createContext)"] GL --> Shaders["createShader()
vertex + fragment"] Shaders --> Loop["requestAnimationFrame loop"] Loop -->|update transforms| Scene Loop -->|draw calls| GL GL --> Canvas["canvas element on screen"]
Under the hood
Heightmap terrain isn't sampled from an image — each vertex height comes from a seeded pseudo-random generator, so the same seed always regenerates identical terrain. A flat strip along one edge gives the character level ground to walk on before the terrain ramps up.
function generateHeightmap(n, m, height, flatSideLength, flatHeight, scale) {
var generator = new Math.seedrandom("heightmap");
for (var i = 0; i <= n; i++) {
for (var j = 0; j <= m; j++) {
var x = (i / n - 0.5) * 2 * width;
var z = (j / m - 0.5) * 2 * depth;
var y = generator() * height;
if (i <= flatSideLength) y = flatHeight; // flat side to walk on
position.push(x, y, z);
texture.push(i / n, j / m);
if (i < n && j < m) {
var startIndex = i * (m + 1) + j;
index.push(startIndex, startIndex + 1, startIndex + m + 1);
index.push(startIndex + m + 1, startIndex + 1, startIndex + m + 2);
}
}
}
// normals: cross product of each triangle's edge vectors,
// accumulated per shared vertex and normalized afterward
}