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.