Add skybox

This commit is contained in:
2026-02-04 22:34:59 +01:00
parent dedcec547d
commit 86d6da33d2
3 changed files with 184 additions and 25 deletions

View File

@@ -1,5 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -10,20 +11,20 @@
padding: 0; padding: 0;
box-sizing: border-box; box-sizing: border-box;
} }
body { body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
overflow: hidden; overflow: hidden;
background: #000; background: #000;
} }
#window { #window {
display: block; display: block;
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
cursor: move; cursor: move;
} }
#controls { #controls {
position: absolute; position: absolute;
top: 20px; top: 20px;
@@ -37,23 +38,23 @@
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
transition: opacity 0.3s; transition: opacity 0.3s;
} }
#controls.hidden { #controls.hidden {
opacity: 0; opacity: 0;
pointer-events: none; pointer-events: none;
} }
#controls h3 { #controls h3 {
margin: 0 0 10px 0; margin: 0 0 10px 0;
font-size: 16px; font-size: 16px;
font-weight: 600; font-weight: 600;
} }
#controls .control-group { #controls .control-group {
margin-bottom: 8px; margin-bottom: 8px;
line-height: 1.6; line-height: 1.6;
} }
#controls .key { #controls .key {
display: inline-block; display: inline-block;
background: rgba(255, 255, 255, 0.2); background: rgba(255, 255, 255, 0.2);
@@ -63,7 +64,7 @@
font-size: 12px; font-size: 12px;
margin: 0 2px; margin: 0 2px;
} }
#toggle-controls { #toggle-controls {
position: absolute; position: absolute;
top: 20px; top: 20px;
@@ -78,11 +79,11 @@
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
transition: background 0.3s; transition: background 0.3s;
} }
#toggle-controls:hover { #toggle-controls:hover {
background: rgba(0, 0, 0, 0.85); background: rgba(0, 0, 0, 0.85);
} }
#fps-counter { #fps-counter {
position: absolute; position: absolute;
bottom: 20px; bottom: 20px;
@@ -285,10 +286,51 @@
<script id="sky-fs" type="x-shader/x-fragment"> <script id="sky-fs" type="x-shader/x-fragment">
precision mediump float; precision mediump float;
varying vec3 fragPos; varying vec3 v_rayDir;
uniform vec3 uSunDirection;
void main(void) { void main(void) {
gl_FragColor = vec4(fragPos,1.0); vec3 rayDir = normalize(v_rayDir);
// Use Z as up (matches world space where ocean is on XY plane)
float upAmount = rayDir.z;
// Sky gradient - from horizon to zenith
float horizonBlend = pow(1.0 - max(upAmount, 0.0), 2.0);
vec3 zenithColor = vec3(0.15, 0.35, 0.75); // Deep blue at top
vec3 horizonColor = vec3(0.55, 0.7, 0.9); // Light blue at horizon
vec3 skyColor = mix(zenithColor, horizonColor, horizonBlend);
// Add warm glow near horizon
float horizonGlow = pow(max(1.0 - abs(upAmount), 0.0), 6.0);
skyColor += vec3(0.4, 0.25, 0.1) * horizonGlow * 0.4;
// Sun direction already in correct coordinate system
vec3 sunDir = normalize(uSunDirection);
float sunAngle = max(dot(rayDir, sunDir), 0.0);
// Sun disk
float sunDisk = smoothstep(0.9993, 0.9998, sunAngle);
vec3 sunColor = vec3(1.0, 0.95, 0.85);
// Sun glow
float sunGlow = pow(sunAngle, 48.0) * 0.6;
float sunHalo = pow(sunAngle, 6.0) * 0.25;
// Combine sun effects
skyColor += sunColor * sunDisk * 3.0;
skyColor += vec3(1.0, 0.85, 0.5) * sunGlow;
skyColor += vec3(1.0, 0.9, 0.7) * sunHalo;
// Below horizon - fade to darker color
if (upAmount < 0.0) {
float depth = -upAmount;
vec3 deepColor = vec3(0.02, 0.08, 0.15);
skyColor = mix(horizonColor * 0.7, deepColor, smoothstep(0.0, 0.5, depth));
}
gl_FragColor = vec4(skyColor, 1.0);
} }
</script> </script>
<script id="sky-vs" type="x-shader/x-vertex"> <script id="sky-vs" type="x-shader/x-vertex">
@@ -296,30 +338,34 @@
uniform mat4 projection; uniform mat4 projection;
uniform mat4 view; uniform mat4 view;
uniform mat4 testModel;
varying vec3 fragPos; varying vec3 v_rayDir;
void main(void) { void main(void) {
gl_PointSize = 10.; v_rayDir = positionAttr;
gl_Position = projection * mat4(mat3(view)) * vec4(positionAttr, 1.0); // Remove translation from view matrix for skybox
fragPos = (view * vec4(positionAttr,1.0)).xyz; //This is wrong probably mat4 rotView = mat4(mat3(view));
vec4 pos = projection * rotView * vec4(positionAttr, 1.0);
gl_Position = pos;
} }
</script> </script>
</head> </head>
<body> <body>
<canvas id="window"></canvas> <canvas id="window"></canvas>
<div id="controls"> <div id="controls">
<h3>🌊 Ocean Controls</h3> <h3>🌊 Ocean Controls</h3>
<div class="control-group"> <div class="control-group">
<strong>Camera Rotation:</strong><br> <strong>Camera Rotation:</strong><br>
<span class="key">W</span><span class="key">A</span><span class="key">S</span><span class="key">D</span> or Arrow Keys <span class="key">W</span><span class="key">A</span><span class="key">S</span><span class="key">D</span> or
Arrow Keys
</div> </div>
<div class="control-group"> <div class="control-group">
<strong>Zoom:</strong><br> <strong>Zoom:</strong><br>
<span class="key">Q</span> / <span class="key">E</span> or <span class="key">+</span> / <span class="key">-</span> <span class="key">Q</span> / <span class="key">E</span> or <span class="key">+</span> / <span
class="key">-</span>
</div> </div>
<div class="control-group"> <div class="control-group">
<strong>Mouse:</strong> Click and drag to rotate <strong>Mouse:</strong> Click and drag to rotate
@@ -331,21 +377,21 @@
<strong>Toggle Help:</strong> <span class="key">H</span> <strong>Toggle Help:</strong> <span class="key">H</span>
</div> </div>
</div> </div>
<button id="toggle-controls">Toggle Controls (H)</button> <button id="toggle-controls">Toggle Controls (H)</button>
<div id="fps-counter">FPS: 0</div> <div id="fps-counter">FPS: 0</div>
<script type="module" src="/src/main.ts"></script> <script type="module" src="/src/main.ts"></script>
<script> <script>
// Toggle controls visibility // Toggle controls visibility
const controls = document.getElementById('controls'); const controls = document.getElementById('controls');
const toggleBtn = document.getElementById('toggle-controls'); const toggleBtn = document.getElementById('toggle-controls');
toggleBtn.addEventListener('click', () => { toggleBtn.addEventListener('click', () => {
controls.classList.toggle('hidden'); controls.classList.toggle('hidden');
}); });
window.addEventListener('keydown', (evt) => { window.addEventListener('keydown', (evt) => {
if (evt.key === 'h' || evt.key === 'H') { if (evt.key === 'h' || evt.key === 'H') {
controls.classList.toggle('hidden'); controls.classList.toggle('hidden');

80
src/Skybox.ts Normal file
View File

@@ -0,0 +1,80 @@
/** Skybox cube for rendering the sky */
export class Skybox {
private vao: WebGLVertexArrayObject | null = null;
private vbo: WebGLBuffer | null = null;
private indexCount: number = 0;
constructor() {}
initVAO(gl: WebGL2RenderingContext): void {
// Cube vertices - positions only
const vertices = new Float32Array([
// Front face
-1, -1, 1,
1, -1, 1,
1, 1, 1,
-1, 1, 1,
// Back face
-1, -1, -1,
-1, 1, -1,
1, 1, -1,
1, -1, -1,
// Top face
-1, 1, -1,
-1, 1, 1,
1, 1, 1,
1, 1, -1,
// Bottom face
-1, -1, -1,
1, -1, -1,
1, -1, 1,
-1, -1, 1,
// Right face
1, -1, -1,
1, 1, -1,
1, 1, 1,
1, -1, 1,
// Left face
-1, -1, -1,
-1, -1, 1,
-1, 1, 1,
-1, 1, -1,
]);
const indices = new Uint16Array([
0, 2, 1, 0, 3, 2, // front
4, 6, 5, 4, 7, 6, // back
8, 10, 9, 8, 11, 10, // top
12, 14, 13, 12, 15, 14, // bottom
16, 18, 17, 16, 19, 18, // right
20, 22, 21, 20, 23, 22, // left
]);
this.indexCount = indices.length;
this.vao = gl.createVertexArray();
gl.bindVertexArray(this.vao);
this.vbo = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
const ibo = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
// Position attribute
gl.enableVertexAttribArray(0);
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
gl.bindVertexArray(null);
}
draw(gl: WebGL2RenderingContext): void {
if (!this.vao) return;
gl.bindVertexArray(this.vao);
gl.drawElements(gl.TRIANGLES, this.indexCount, gl.UNSIGNED_SHORT, 0);
gl.bindVertexArray(null);
}
}

View File

@@ -1,6 +1,7 @@
import { vec3, mat4 } from 'gl-matrix'; import { vec3, mat4 } from 'gl-matrix';
import { Camera } from './Camera'; import { Camera } from './Camera';
import { Grid } from './Grid'; import { Grid } from './Grid';
import { Skybox } from './Skybox';
import { createProgram } from './Shader'; import { createProgram } from './Shader';
import * as Config from './constants'; import * as Config from './constants';
@@ -89,10 +90,12 @@ function initGeometry() {
var perlinNoiseProgram: WebGLProgram | null; var perlinNoiseProgram: WebGLProgram | null;
var defaultProgram: WebGLProgram | null; var defaultProgram: WebGLProgram | null;
var textureProgram: WebGLProgram | null; var textureProgram: WebGLProgram | null;
var skyProgram: WebGLProgram | null;
function initShaders() { function initShaders() {
perlinNoiseProgram = createProgram(gl, "ndc-vs", "noise-fs", "Perlin Noise"); perlinNoiseProgram = createProgram(gl, "ndc-vs", "noise-fs", "Perlin Noise");
defaultProgram = createProgram(gl, "default-vs", "default-fs", "Default"); defaultProgram = createProgram(gl, "default-vs", "default-fs", "Default");
textureProgram = createProgram(gl, "texture-vs", "texture-fs", "Texture"); textureProgram = createProgram(gl, "texture-vs", "texture-fs", "Texture");
skyProgram = createProgram(gl, "sky-vs", "sky-fs", "Sky");
} }
/** Init an FBO used for the first render pass / perlin noise */ /** Init an FBO used for the first render pass / perlin noise */
@@ -141,6 +144,7 @@ var keysPressed: Set<string> = new Set();
/** Objects and states*/ /** Objects and states*/
var camera: Camera; var camera: Camera;
var oceanGrid: Grid; var oceanGrid: Grid;
var skybox: Skybox;
var curRotX = Config.CAMERA_DEFAULT_ROT_X; var curRotX = Config.CAMERA_DEFAULT_ROT_X;
var curRotY = Config.CAMERA_DEFAULT_ROT_Y; var curRotY = Config.CAMERA_DEFAULT_ROT_Y;
function drawScene() { function drawScene() {
@@ -168,6 +172,9 @@ function drawScene() {
//Clear buffer content //Clear buffer content
gl.clearColor(1.0, 1.0, 1.0, 1); gl.clearColor(1.0, 1.0, 1.0, 1);
gl.clear(gl.COLOR_BUFFER_BIT); //No depth buffer gl.clear(gl.COLOR_BUFFER_BIT); //No depth buffer
// Disable face culling for fullscreen quad
gl.disable(gl.CULL_FACE);
//draw a fullscreen quad //draw a fullscreen quad
gl.bindBuffer(gl.ARRAY_BUFFER, VBO); gl.bindBuffer(gl.ARRAY_BUFFER, VBO);
@@ -205,6 +212,29 @@ function drawScene() {
camera.setRotationX((curRotX += mouseYVel * Config.MOUSE_SENSITIVITY + keyboardRotationX)); camera.setRotationX((curRotX += mouseYVel * Config.MOUSE_SENSITIVITY + keyboardRotationX));
camera.setRotationY((curRotY += mouseXVel * Config.MOUSE_SENSITIVITY + keyboardRotationY)); camera.setRotationY((curRotY += mouseXVel * Config.MOUSE_SENSITIVITY + keyboardRotationY));
var view = camera.getViewMatrix(); var view = camera.getViewMatrix();
// Sun direction (matches the one in ocean shader)
const sunDirection = vec3.fromValues(0.3, 0.5, 0.8);
vec3.normalize(sunDirection, sunDirection);
// Draw skybox first with depth test disabled (always behind everything)
gl.depthMask(false);
gl.disable(gl.DEPTH_TEST);
gl.disable(gl.CULL_FACE); // Disable face culling for skybox (we're inside)
gl.useProgram(skyProgram);
let sky_view_loc = gl.getUniformLocation(<WebGLProgram>skyProgram, "view");
gl.uniformMatrix4fv(sky_view_loc, false, view);
let sky_projection_loc = gl.getUniformLocation(<WebGLProgram>skyProgram, "projection");
gl.uniformMatrix4fv(sky_projection_loc, false, projection);
let sky_sun_loc = gl.getUniformLocation(<WebGLProgram>skyProgram, "uSunDirection");
gl.uniform3fv(sky_sun_loc, sunDirection);
skybox.draw(gl);
gl.enable(gl.DEPTH_TEST);
gl.depthMask(true);
gl.enable(gl.CULL_FACE); // Re-enable face culling for ocean
gl.cullFace(gl.BACK); // Cull back faces for ocean
var model = mat4.create(); var model = mat4.create();
mat4.identity(model); mat4.identity(model);
@@ -324,6 +354,9 @@ function main() {
oceanGrid = new Grid(Config.GRID_SIZE); oceanGrid = new Grid(Config.GRID_SIZE);
oceanGrid.initVAO(gl); oceanGrid.initVAO(gl);
skybox = new Skybox();
skybox.initVAO(gl);
camera = new Camera(); camera = new Camera();
//Check if any errors apeared during init. //Check if any errors apeared during init.