Refactor OceanLOD to ProjectedOcean; update grid generation and shader integration for improved horizon projection and clipping
This commit is contained in:
127
index.html
127
index.html
@@ -374,15 +374,17 @@
|
||||
<script id="default-vs" type="x-shader/x-vertex">
|
||||
precision mediump float;
|
||||
|
||||
attribute vec3 positionAttr;
|
||||
attribute vec2 positionAttr; // Grid position in [0,1] range
|
||||
|
||||
uniform mat4 view;
|
||||
uniform mat4 model;
|
||||
uniform mat4 projection;
|
||||
uniform mat4 uProjectorMatrix; // Inverse projector view-proj
|
||||
uniform mat4 uRangeMatrix; // Range conversion matrix
|
||||
uniform float uTime;
|
||||
uniform float uWaveHeight;
|
||||
uniform float uWaveSpeed;
|
||||
uniform vec3 eyePos;
|
||||
uniform float uHorizonClipY; // Y position of horizon in clip space [-1,1]
|
||||
|
||||
varying vec3 v_fragPos;
|
||||
varying vec3 v_normal;
|
||||
@@ -416,9 +418,84 @@
|
||||
d.y * a * cos(f)
|
||||
);
|
||||
}
|
||||
|
||||
// Project grid point onto ocean plane using projector
|
||||
vec3 projectToOcean(vec2 gridPos, out float horizonBlend, out vec3 rayDirection) {
|
||||
// Transform grid position [0,1] through range matrix to projector space [-1,1]
|
||||
vec4 clipPos = uRangeMatrix * vec4(gridPos, 0.0, 1.0);
|
||||
|
||||
// Get two points along the projection ray (near and far planes)
|
||||
vec4 nearPoint = uProjectorMatrix * vec4(clipPos.xy, -1.0, 1.0);
|
||||
vec4 farPoint = uProjectorMatrix * vec4(clipPos.xy, 1.0, 1.0);
|
||||
|
||||
// Perspective divide to get world positions
|
||||
nearPoint /= nearPoint.w;
|
||||
farPoint /= farPoint.w;
|
||||
|
||||
vec3 rayOrigin = nearPoint.xyz;
|
||||
vec3 rayDir = normalize(farPoint.xyz - nearPoint.xyz);
|
||||
rayDirection = rayDir;
|
||||
|
||||
// The skybox horizon is where rayDir.z = 0 (looking horizontally)
|
||||
float angleToHorizon = -rayDir.z; // 0 at horizon, negative = looking up, positive = looking down
|
||||
|
||||
// If ray is pointing up or nearly horizontal, this vertex approaches horizon
|
||||
if (angleToHorizon <= 0.001) {
|
||||
horizonBlend = 1.0;
|
||||
// Project in horizontal direction at ocean level
|
||||
vec2 hDir = length(rayDir.xy) > 0.001 ? normalize(rayDir.xy) : vec2(1.0, 0.0);
|
||||
return vec3(rayOrigin.xy + hDir * 5000.0, 0.0);
|
||||
}
|
||||
|
||||
// Ray is pointing down - intersect with ocean plane (Z = 0)
|
||||
float t = -rayOrigin.z / rayDir.z;
|
||||
|
||||
if (t < 0.0) {
|
||||
horizonBlend = 1.0;
|
||||
vec2 hDir = length(rayDir.xy) > 0.001 ? normalize(rayDir.xy) : vec2(1.0, 0.0);
|
||||
return vec3(rayOrigin.xy + hDir * 5000.0, 0.0);
|
||||
}
|
||||
|
||||
// Camera height affects max render distance
|
||||
// Higher camera = need to limit distance more to avoid precision issues
|
||||
float cameraHeight = max(eyePos.z, 0.5);
|
||||
|
||||
// Base max distance scales with camera height, but with diminishing returns
|
||||
// At height 2: maxBase = ~200
|
||||
// At height 10: maxBase = ~450
|
||||
// At height 100: maxBase = ~1400
|
||||
// At height 500: maxBase = ~3100
|
||||
float maxBase = 100.0 * sqrt(cameraHeight);
|
||||
|
||||
// Also limit based on angle - shallow angles get much shorter max distance
|
||||
float angleScale = smoothstep(0.001, 0.3, angleToHorizon); // 0 at horizon, 1 at ~17 degrees down
|
||||
float maxT = maxBase * (0.1 + 0.9 * angleScale);
|
||||
maxT = max(maxT, 50.0); // Minimum distance
|
||||
|
||||
// Smooth horizon blend based on angle AND distance
|
||||
horizonBlend = 1.0 - smoothstep(0.001, 0.05, angleToHorizon);
|
||||
|
||||
// If t exceeds limit, increase horizon blend
|
||||
if (t > maxT * 0.8) {
|
||||
float distBlend = smoothstep(maxT * 0.8, maxT, t);
|
||||
horizonBlend = max(horizonBlend, distBlend);
|
||||
}
|
||||
|
||||
t = min(t, maxT);
|
||||
|
||||
// Compute world position
|
||||
vec3 worldPos = rayOrigin + rayDir * t;
|
||||
|
||||
return worldPos;
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
vec4 worldPos = model * vec4(positionAttr.xyz, 1.0);
|
||||
// Project grid point onto ocean plane
|
||||
float horizonBlend;
|
||||
vec3 rayDir;
|
||||
vec3 worldPos3 = projectToOcean(positionAttr, horizonBlend, rayDir);
|
||||
vec4 worldPos = vec4(worldPos3, 1.0);
|
||||
|
||||
// Grid is on XY plane, Z is up
|
||||
vec2 pos = worldPos.xy;
|
||||
float time = uTime * 0.0004 * uWaveSpeed;
|
||||
@@ -427,6 +504,8 @@
|
||||
float distToCamera = length(worldPos.xyz - eyePos);
|
||||
float waveFade = exp(-distToCamera * 0.015); // Gradual fade over distance
|
||||
waveFade = clamp(waveFade, 0.0, 1.0);
|
||||
// Fade out waves at horizon to prevent edge breakup
|
||||
waveFade *= (1.0 - horizonBlend);
|
||||
v_distanceFade = waveFade;
|
||||
|
||||
float heightMod = uWaveHeight * waveFade;
|
||||
@@ -504,42 +583,22 @@
|
||||
normal = mix(flatNormal, normal, waveFade);
|
||||
v_normal = vec3(normal.x, normal.z, normal.y);
|
||||
|
||||
// Horizon projection: calculate where the world horizon would be in clip space
|
||||
// The horizon is where z=0 plane meets the sky (at eye height)
|
||||
// Project a point at the horizon in the same XY direction as this vertex
|
||||
float horizonStretch = smoothstep(40.0, 100.0, distToCamera);
|
||||
// Project back to clip space
|
||||
gl_Position = projection * view * worldPos;
|
||||
|
||||
if (horizonStretch > 0.0) {
|
||||
// Get direction from camera to vertex (XY only, on ocean plane)
|
||||
vec2 toVertex = normalize(worldPos.xy - eyePos.xy);
|
||||
|
||||
// Create a horizon point far away in that direction at z=0
|
||||
vec3 horizonPoint = vec3(
|
||||
eyePos.xy + toVertex * 10000.0,
|
||||
0.0
|
||||
);
|
||||
|
||||
// Project horizon point to get true horizon clip position
|
||||
vec4 horizonClip = projection * view * vec4(horizonPoint, 1.0);
|
||||
|
||||
// Get actual clip position
|
||||
vec4 clipPos = projection * view * worldPos;
|
||||
|
||||
// Blend vertex toward the horizon point's clip position (normalized)
|
||||
// Overshoot slightly past horizon to ensure no gap
|
||||
float horizonY = horizonClip.y / horizonClip.w * clipPos.w;
|
||||
float overshoot = 1.0 + horizonStretch * 0.1; // Push slightly past horizon
|
||||
clipPos.y = mix(clipPos.y, horizonY * overshoot, horizonStretch);
|
||||
|
||||
gl_Position = clipPos;
|
||||
} else {
|
||||
gl_Position = projection * view * worldPos;
|
||||
// For vertices near the horizon, smoothly blend Y towards the horizon line
|
||||
// This ensures ocean meets skybox without gaps or discontinuities
|
||||
if (horizonBlend > 0.0) {
|
||||
float targetY = uHorizonClipY * gl_Position.w;
|
||||
// Use squared blend for smoother transition
|
||||
float smoothBlend = horizonBlend * horizonBlend;
|
||||
gl_Position.y = mix(gl_Position.y, targetY, smoothBlend);
|
||||
// Push depth towards far plane for horizon vertices
|
||||
gl_Position.z = mix(gl_Position.z, gl_Position.w * 0.9999, smoothBlend);
|
||||
}
|
||||
|
||||
v_fragPos = worldPos.xyz;
|
||||
}
|
||||
</script>
|
||||
}
|
||||
</script>
|
||||
<script id="sky-fs" type="x-shader/x-fragment">
|
||||
precision mediump float;
|
||||
|
||||
Reference in New Issue
Block a user