Refactor OceanLOD to ProjectedOcean; update grid generation and shader integration for improved horizon projection and clipping

This commit is contained in:
2026-01-31 23:53:07 +01:00
parent 503435bdf7
commit 9ffee04185
3 changed files with 309 additions and 241 deletions

View File

@@ -1,6 +1,6 @@
import { vec3, mat4 } from 'gl-matrix';
import { vec3, vec4, mat4 } from 'gl-matrix';
import { Camera } from './Camera';
import { OceanLOD } from './OceanLOD';
import { ProjectedOcean } from './OceanLOD';
import { Skybox } from './Skybox';
import { createProgram } from './Shader';
import * as Config from './constants';
@@ -134,14 +134,13 @@ var lastTime = new Date().getTime();
var counter = 0.0;
var fps = 0;
var fpsDisplay: HTMLElement | null = null;
var lodStatsTimer = 0;
/** Input states*/
var mouseXVel = 0;
var mouseYVel = 0;
var keysPressed: Set<string> = new Set();
/** Objects and states*/
var camera: Camera;
var oceanLOD: OceanLOD;
var projectedOcean: ProjectedOcean;
var skybox: Skybox;
var wireframeMode = false;
/** Camera movement speed */
@@ -157,7 +156,6 @@ function drawScene() {
let now = new Date();
let delta = now.getTime() - lastTime;
timeSpent += delta;
lodStatsTimer += delta;
if ((counter += delta) >= Config.FPS_UPDATE_INTERVAL) {
counter = 0;
@@ -166,13 +164,6 @@ function drawScene() {
}
fps = 0;
}
// Log LOD stats every 5 seconds
if (lodStatsTimer >= 5000) {
lodStatsTimer = 0;
const stats = oceanLOD.getLODStats();
console.log(`LOD Stats - High:${stats[0]} Med:${stats[1]} Low:${stats[2]} VeryLow:${stats[3]}`);
}
lastTime = now.getTime();
// Sun direction (matches the one in ocean shader)
@@ -216,20 +207,18 @@ function drawScene() {
gl.enable(gl.DEPTH_TEST);
gl.depthMask(true);
// Update LOD based on camera position and view direction
oceanLOD.updateLOD(gl, camera.pos, camera.target);
var model = mat4.create();
mat4.identity(model);
// No centering needed - grids are already positioned correctly in world space
// Update projected ocean's projector matrices
projectedOcean.updateProjector(camera.pos, camera.forward, view, projection);
gl.useProgram(defaultProgram);
let view_loc = gl.getUniformLocation(<WebGLProgram>defaultProgram, "view");
gl.uniformMatrix4fv(view_loc, false, view);
let model_loc = gl.getUniformLocation(<WebGLProgram>defaultProgram, "model");
gl.uniformMatrix4fv(model_loc, false, model);
let projection_loc = gl.getUniformLocation(<WebGLProgram>defaultProgram, "projection");
gl.uniformMatrix4fv(projection_loc, false, projection);
let projectorMatrix_loc = gl.getUniformLocation(<WebGLProgram>defaultProgram, "uProjectorMatrix");
gl.uniformMatrix4fv(projectorMatrix_loc, false, projectedOcean.projectorMatrix);
let rangeMatrix_loc = gl.getUniformLocation(<WebGLProgram>defaultProgram, "uRangeMatrix");
gl.uniformMatrix4fv(rangeMatrix_loc, false, projectedOcean.rangeMatrix);
let eye_loc = gl.getUniformLocation(<WebGLProgram>defaultProgram, "eyePos");
gl.uniform3fv(eye_loc, camera.pos);
let uTime_loc = gl.getUniformLocation(<WebGLProgram>defaultProgram, "uTime");
@@ -245,7 +234,60 @@ function drawScene() {
let uGlitterIntensity_loc = gl.getUniformLocation(<WebGLProgram>defaultProgram, "uGlitterIntensity");
gl.uniform1f(uGlitterIntensity_loc, glitterIntensity);
oceanLOD.draw(gl, wireframeMode);
// Calculate horizon Y in clip space
// The skybox horizon is where rayDir.z = 0 (horizontal ray from camera)
// This is a point at infinity in a horizontal direction from the camera
// We need to find where this projects to in clip space
// Get a horizontal direction (camera forward projected onto XY plane)
const horizonDir = vec3.fromValues(camera.forward[0], camera.forward[1], 0);
if (vec3.length(horizonDir) > 0.001) {
vec3.normalize(horizonDir, horizonDir);
} else {
vec3.set(horizonDir, 1, 0, 0);
}
// Transform a direction vector (not a point) to clip space
// For a point at infinity in direction D, its clip space position is:
// lim(t->inf) ViewProj * (eye + t*D) / w
// Which equals ViewProj * D (as a vec4 with w=0), then we look at x/w, y/w
// But since w would be 0 for a direction, we use the view matrix only
// The horizon is where view-space Y = 0 for an infinite point
// In our Z-up system, the horizon is where the ray is horizontal (z=0 in world)
// Transform a horizontal direction through view matrix
const horizonDirView = vec4.fromValues(horizonDir[0], horizonDir[1], 0, 0);
vec4.transformMat4(horizonDirView, horizonDirView, view);
// The Y in clip space where this direction points is based on the view-space direction
// projected through the projection matrix
// For perspective: clipY/clipW = viewY/(-viewZ) * projectionScaleY
// For a horizontal ray at infinity, we can compute where it ends up
// Simpler approach: transform a point very far away in horizon direction
const farDist = 1000000.0;
const horizonPoint = vec4.fromValues(
camera.pos[0] + horizonDir[0] * farDist,
camera.pos[1] + horizonDir[1] * farDist,
camera.pos[2], // Same height as camera - this is the horizon!
1
);
const viewProj = mat4.create();
mat4.multiply(viewProj, projection, view);
vec4.transformMat4(horizonPoint, horizonPoint, viewProj);
const horizonClipY = horizonPoint[3] !== 0 ? horizonPoint[1] / horizonPoint[3] : 0;
let uHorizonClipY_loc = gl.getUniformLocation(<WebGLProgram>defaultProgram, "uHorizonClipY");
gl.uniform1f(uHorizonClipY_loc, horizonClipY);
// Enable backface culling so ocean isn't visible from below
gl.enable(gl.CULL_FACE);
gl.cullFace(gl.BACK);
gl.frontFace(gl.CCW);
projectedOcean.draw(gl, wireframeMode);
gl.disable(gl.CULL_FACE);
}
requestAnimationFrame(drawScene);
}
@@ -380,9 +422,9 @@ function main() {
initGeometry();
initFBO();
oceanLOD = new OceanLOD();
oceanLOD.initVAO(gl);
console.log(`Ocean LOD initialized with ${oceanLOD.getGridCount()} patches`);
projectedOcean = new ProjectedOcean();
projectedOcean.initVAO(gl);
console.log(`Projected ocean initialized with ${projectedOcean.getIndexCount()} indices`);
skybox = new Skybox();
skybox.initVAO(gl);