diff --git a/index.html b/index.html
index 6871cf9..b69309d 100644
--- a/index.html
+++ b/index.html
@@ -405,6 +405,7 @@
void main(void) {
vec4 worldPos = model * vec4(positionAttr.xyz, 1.0);
+ // Grid is on XY plane, Z is up
vec2 pos = worldPos.xy;
float time = uTime * 0.0004 * uWaveSpeed;
float heightMod = uWaveHeight;
@@ -470,10 +471,10 @@
// Foam appears where wave is high AND rising (leading edge / crest)
v_foamFactor = clamp((foamFromHeight * waveRising * 1.2 + foamFromSlope * 0.3), 0.0, 1.0);
- // Apply displacement
+ // Apply displacement - Z is up, XY is horizontal plane
worldPos.x += displacement.x;
worldPos.y += displacement.z;
- worldPos.z += displacement.y;
+ worldPos.z += displacement.y; // Height displacement
// Calculate normal from tangent and binormal
vec3 normal = normalize(cross(binormal, tangent));
@@ -488,10 +489,51 @@
diff --git a/src/Grid.ts b/src/Grid.ts
index b9a2b2a..6f389f8 100644
--- a/src/Grid.ts
+++ b/src/Grid.ts
@@ -25,6 +25,7 @@ export class Grid {
for (let j = 0; j <= this.size; ++j) {
for (let i = 0; i <= this.size; ++i) {
// Generate Vertices normalized to 0-1, then scale and offset
+ // Grid is on XY plane (horizontal), Z is up
const u = i / this.size;
const v = j / this.size;
const x = (u - 0.5) * this.scale + this.offsetX;
diff --git a/src/Skybox.ts b/src/Skybox.ts
new file mode 100644
index 0000000..4af4358
--- /dev/null
+++ b/src/Skybox.ts
@@ -0,0 +1,85 @@
+/** 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;
+
+ // Disable face culling for skybox (we're inside the cube)
+ gl.disable(gl.CULL_FACE);
+
+ gl.bindVertexArray(this.vao);
+ gl.drawElements(gl.TRIANGLES, this.indexCount, gl.UNSIGNED_SHORT, 0);
+ gl.bindVertexArray(null);
+
+ gl.enable(gl.CULL_FACE);
+ }
+}
diff --git a/src/main.ts b/src/main.ts
index 1343d57..7526cc6 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,6 +1,7 @@
import { vec3, mat4 } from 'gl-matrix';
import { Camera } from './Camera';
import { OceanLOD } from './OceanLOD';
+import { Skybox } from './Skybox';
import { createProgram } from './Shader';
import * as Config from './constants';
@@ -89,10 +90,12 @@ function initGeometry() {
var perlinNoiseProgram: WebGLProgram | null;
var defaultProgram: WebGLProgram | null;
var textureProgram: WebGLProgram | null;
+var skyProgram: WebGLProgram | null;
function initShaders() {
perlinNoiseProgram = createProgram(gl, "ndc-vs", "noise-fs", "Perlin Noise");
defaultProgram = createProgram(gl, "default-vs", "default-fs", "Default");
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 */
@@ -142,6 +145,7 @@ var keysPressed: Set = new Set();
/** Objects and states*/
var camera: Camera;
var oceanLOD: OceanLOD;
+var skybox: Skybox;
var curRotX = Config.CAMERA_DEFAULT_ROT_X;
var curRotY = Config.CAMERA_DEFAULT_ROT_Y;
var wireframeMode = false;
@@ -172,17 +176,18 @@ function drawScene() {
console.log(`LOD Stats - High:${stats[0]} Med:${stats[1]} Low:${stats[2]} VeryLow:${stats[3]}`);
}
lastTime = now.getTime();
- // Single render pass with Gerstner waves computed in vertex shader
+
+ // Sun direction (matches the one in ocean shader)
+ const sunDirection = vec3.fromValues(0.3, 0.5, 0.8);
+ vec3.normalize(sunDirection, sunDirection);
- //--- Render pass -> Ocean with Gerstner wave displacement ---
+ //--- Render pass -> Skybox first (no depth write) ---
{
- gl.bindFramebuffer(gl.FRAMEBUFFER, null); //Bind default framebuffer
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.viewport(0, 0, viewportWidth, viewportHeight);
-
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
var projection = mat4.create();
- mat4.identity(projection);
mat4.perspective(projection, Config.FOV, viewportWidth / viewportHeight, Config.NEAR_PLANE, Config.FAR_PLANE);
camera.setOffset(Config.CAMERA_DEFAULT_OFFSET + keyboardZoom);
@@ -190,6 +195,22 @@ function drawScene() {
camera.setRotationY((curRotY += mouseXVel * Config.MOUSE_SENSITIVITY + keyboardRotationY));
var view = camera.getViewMatrix();
+ // Draw skybox first with depth test disabled (always behind everything)
+ gl.depthMask(false);
+ gl.disable(gl.DEPTH_TEST);
+ gl.useProgram(skyProgram);
+
+ let sky_view_loc = gl.getUniformLocation(skyProgram, "view");
+ gl.uniformMatrix4fv(sky_view_loc, false, view);
+ let sky_projection_loc = gl.getUniformLocation(skyProgram, "projection");
+ gl.uniformMatrix4fv(sky_projection_loc, false, projection);
+ let sky_sun_loc = gl.getUniformLocation(skyProgram, "uSunDirection");
+ gl.uniform3fv(sky_sun_loc, sunDirection);
+
+ skybox.draw(gl);
+ gl.enable(gl.DEPTH_TEST);
+ gl.depthMask(true);
+
// Update LOD based on camera position and view direction
oceanLOD.updateLOD(gl, camera.pos, camera.target);
@@ -346,6 +367,10 @@ function main() {
oceanLOD = new OceanLOD();
oceanLOD.initVAO(gl);
console.log(`Ocean LOD initialized with ${oceanLOD.getGridCount()} patches`);
+
+ skybox = new Skybox();
+ skybox.initVAO(gl);
+ console.log('Skybox initialized');
camera = new Camera();
//Check if any errors apeared during init.