Add Skybox class for rendering sky and update shaders for sky rendering
This commit is contained in:
61
index.html
61
index.html
@@ -405,6 +405,7 @@
|
|||||||
|
|
||||||
void main(void) {
|
void main(void) {
|
||||||
vec4 worldPos = model * vec4(positionAttr.xyz, 1.0);
|
vec4 worldPos = model * vec4(positionAttr.xyz, 1.0);
|
||||||
|
// Grid is on XY plane, Z is up
|
||||||
vec2 pos = worldPos.xy;
|
vec2 pos = worldPos.xy;
|
||||||
float time = uTime * 0.0004 * uWaveSpeed;
|
float time = uTime * 0.0004 * uWaveSpeed;
|
||||||
float heightMod = uWaveHeight;
|
float heightMod = uWaveHeight;
|
||||||
@@ -470,10 +471,10 @@
|
|||||||
// Foam appears where wave is high AND rising (leading edge / crest)
|
// 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);
|
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.x += displacement.x;
|
||||||
worldPos.y += displacement.z;
|
worldPos.y += displacement.z;
|
||||||
worldPos.z += displacement.y;
|
worldPos.z += displacement.y; // Height displacement
|
||||||
|
|
||||||
// Calculate normal from tangent and binormal
|
// Calculate normal from tangent and binormal
|
||||||
vec3 normal = normalize(cross(binormal, tangent));
|
vec3 normal = normalize(cross(binormal, tangent));
|
||||||
@@ -488,10 +489,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">
|
||||||
@@ -499,14 +541,15 @@
|
|||||||
|
|
||||||
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>
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export class Grid {
|
|||||||
for (let j = 0; j <= this.size; ++j) {
|
for (let j = 0; j <= this.size; ++j) {
|
||||||
for (let i = 0; i <= this.size; ++i) {
|
for (let i = 0; i <= this.size; ++i) {
|
||||||
// Generate Vertices normalized to 0-1, then scale and offset
|
// 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 u = i / this.size;
|
||||||
const v = j / this.size;
|
const v = j / this.size;
|
||||||
const x = (u - 0.5) * this.scale + this.offsetX;
|
const x = (u - 0.5) * this.scale + this.offsetX;
|
||||||
|
|||||||
85
src/Skybox.ts
Normal file
85
src/Skybox.ts
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
35
src/main.ts
35
src/main.ts
@@ -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 { OceanLOD } from './OceanLOD';
|
import { OceanLOD } from './OceanLOD';
|
||||||
|
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 */
|
||||||
@@ -142,6 +145,7 @@ var keysPressed: Set<string> = new Set();
|
|||||||
/** Objects and states*/
|
/** Objects and states*/
|
||||||
var camera: Camera;
|
var camera: Camera;
|
||||||
var oceanLOD: OceanLOD;
|
var oceanLOD: OceanLOD;
|
||||||
|
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;
|
||||||
var wireframeMode = false;
|
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]}`);
|
console.log(`LOD Stats - High:${stats[0]} Med:${stats[1]} Low:${stats[2]} VeryLow:${stats[3]}`);
|
||||||
}
|
}
|
||||||
lastTime = now.getTime();
|
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.viewport(0, 0, viewportWidth, viewportHeight);
|
||||||
|
|
||||||
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
||||||
|
|
||||||
var projection = mat4.create();
|
var projection = mat4.create();
|
||||||
mat4.identity(projection);
|
|
||||||
mat4.perspective(projection, Config.FOV, viewportWidth / viewportHeight, Config.NEAR_PLANE, Config.FAR_PLANE);
|
mat4.perspective(projection, Config.FOV, viewportWidth / viewportHeight, Config.NEAR_PLANE, Config.FAR_PLANE);
|
||||||
|
|
||||||
camera.setOffset(Config.CAMERA_DEFAULT_OFFSET + keyboardZoom);
|
camera.setOffset(Config.CAMERA_DEFAULT_OFFSET + keyboardZoom);
|
||||||
@@ -190,6 +195,22 @@ 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();
|
||||||
|
|
||||||
|
// 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(<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);
|
||||||
|
|
||||||
// Update LOD based on camera position and view direction
|
// Update LOD based on camera position and view direction
|
||||||
oceanLOD.updateLOD(gl, camera.pos, camera.target);
|
oceanLOD.updateLOD(gl, camera.pos, camera.target);
|
||||||
|
|
||||||
@@ -346,6 +367,10 @@ function main() {
|
|||||||
oceanLOD = new OceanLOD();
|
oceanLOD = new OceanLOD();
|
||||||
oceanLOD.initVAO(gl);
|
oceanLOD.initVAO(gl);
|
||||||
console.log(`Ocean LOD initialized with ${oceanLOD.getGridCount()} patches`);
|
console.log(`Ocean LOD initialized with ${oceanLOD.getGridCount()} patches`);
|
||||||
|
|
||||||
|
skybox = new Skybox();
|
||||||
|
skybox.initVAO(gl);
|
||||||
|
console.log('Skybox initialized');
|
||||||
|
|
||||||
camera = new Camera();
|
camera = new Camera();
|
||||||
//Check if any errors apeared during init.
|
//Check if any errors apeared during init.
|
||||||
|
|||||||
Reference in New Issue
Block a user