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">
@@ -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,16 +338,18 @@
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>
@@ -315,11 +359,13 @@
<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

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() {
@@ -169,6 +173,9 @@ function drawScene() {
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);
@@ -206,6 +213,29 @@ function drawScene() {
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);
let translationCentering = vec3.create(); let translationCentering = vec3.create();
@@ -325,6 +355,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.
if (gl.getError() != gl.NO_ERROR) { if (gl.getError() != gl.NO_ERROR) {