- Added WebGPUContext class to manage WebGPU initialization and resource creation. - Created main_webgl.ts for WebGL rendering setup and scene management. - Introduced WGSL shaders for Perlin noise generation and ocean rendering. - Implemented vertex and fragment shaders for ocean surface displacement and lighting effects. - Enhanced camera controls and rendering logic for improved user experience.
318 lines
11 KiB
TypeScript
318 lines
11 KiB
TypeScript
// WGSL Shaders for WebGPU
|
|
|
|
// Vertex shader for noise generation (fullscreen quad)
|
|
export const noiseVertexShader = `
|
|
@vertex
|
|
fn main(@builtin(vertex_index) vertexIndex: u32) -> @builtin(position) vec4<f32> {
|
|
var pos = array<vec2<f32>, 6>(
|
|
vec2<f32>(-1.0, -1.0),
|
|
vec2<f32>(1.0, -1.0),
|
|
vec2<f32>(-1.0, 1.0),
|
|
vec2<f32>(1.0, -1.0),
|
|
vec2<f32>(1.0, 1.0),
|
|
vec2<f32>(-1.0, 1.0)
|
|
);
|
|
return vec4<f32>(pos[vertexIndex], 0.0, 1.0);
|
|
}
|
|
`;
|
|
|
|
// Fragment shader for Perlin noise
|
|
export const noiseFragmentShader = `
|
|
@group(0) @binding(0) var<uniform> uTime: f32;
|
|
|
|
fn permute(x: vec4<f32>) -> vec4<f32> {
|
|
return ((x * 34.0 + 1.0) * x) % vec4<f32>(289.0);
|
|
}
|
|
|
|
fn taylorInvSqrt(r: vec4<f32>) -> vec4<f32> {
|
|
return 1.79284291400159 - 0.85373472095314 * r;
|
|
}
|
|
|
|
fn fade(t: vec3<f32>) -> vec3<f32> {
|
|
return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);
|
|
}
|
|
|
|
fn cnoise(P: vec3<f32>) -> f32 {
|
|
var Pi0: vec3<f32> = floor(P);
|
|
var Pi1: vec3<f32> = Pi0 + vec3<f32>(1.0);
|
|
Pi0 = Pi0 % vec3<f32>(289.0);
|
|
Pi1 = Pi1 % vec3<f32>(289.0);
|
|
let Pf0 = fract(P);
|
|
let Pf1 = Pf0 - vec3<f32>(1.0);
|
|
let ix = vec4<f32>(Pi0.x, Pi1.x, Pi0.x, Pi1.x);
|
|
let iy = vec4<f32>(Pi0.yy, Pi1.yy);
|
|
let iz0 = Pi0.zzzz;
|
|
let iz1 = Pi1.zzzz;
|
|
|
|
let ixy = permute(permute(ix) + iy);
|
|
let ixy0 = permute(ixy + iz0);
|
|
let ixy1 = permute(ixy + iz1);
|
|
|
|
var gx0: vec4<f32> = ixy0 / 7.0;
|
|
var gy0: vec4<f32> = fract(floor(gx0) / 7.0) - 0.5;
|
|
gx0 = fract(gx0);
|
|
let gz0 = vec4<f32>(0.5) - abs(gx0) - abs(gy0);
|
|
let sz0 = step(gz0, vec4<f32>(0.0));
|
|
gx0 = gx0 - sz0 * (step(vec4<f32>(0.0), gx0) - 0.5);
|
|
gy0 = gy0 - sz0 * (step(vec4<f32>(0.0), gy0) - 0.5);
|
|
|
|
var gx1: vec4<f32> = ixy1 / 7.0;
|
|
var gy1: vec4<f32> = fract(floor(gx1) / 7.0) - 0.5;
|
|
gx1 = fract(gx1);
|
|
let gz1 = vec4<f32>(0.5) - abs(gx1) - abs(gy1);
|
|
let sz1 = step(gz1, vec4<f32>(0.0));
|
|
gx1 = gx1 - sz1 * (step(vec4<f32>(0.0), gx1) - 0.5);
|
|
gy1 = gy1 - sz1 * (step(vec4<f32>(0.0), gy1) - 0.5);
|
|
|
|
var g000: vec3<f32> = vec3<f32>(gx0.x, gy0.x, gz0.x);
|
|
var g100: vec3<f32> = vec3<f32>(gx0.y, gy0.y, gz0.y);
|
|
var g010: vec3<f32> = vec3<f32>(gx0.z, gy0.z, gz0.z);
|
|
var g110: vec3<f32> = vec3<f32>(gx0.w, gy0.w, gz0.w);
|
|
var g001: vec3<f32> = vec3<f32>(gx1.x, gy1.x, gz1.x);
|
|
var g101: vec3<f32> = vec3<f32>(gx1.y, gy1.y, gz1.y);
|
|
var g011: vec3<f32> = vec3<f32>(gx1.z, gy1.z, gz1.z);
|
|
var g111: vec3<f32> = vec3<f32>(gx1.w, gy1.w, gz1.w);
|
|
|
|
let norm0 = taylorInvSqrt(vec4<f32>(dot(g000, g000), dot(g100, g100), dot(g010, g010), dot(g110, g110)));
|
|
g000 = g000 * norm0.x;
|
|
g100 = g100 * norm0.y;
|
|
g010 = g010 * norm0.z;
|
|
g110 = g110 * norm0.w;
|
|
let norm1 = taylorInvSqrt(vec4<f32>(dot(g001, g001), dot(g101, g101), dot(g011, g011), dot(g111, g111)));
|
|
g001 = g001 * norm1.x;
|
|
g101 = g101 * norm1.y;
|
|
g011 = g011 * norm1.z;
|
|
g111 = g111 * norm1.w;
|
|
|
|
let n000 = dot(g000, Pf0);
|
|
let n100 = dot(g100, vec3<f32>(Pf1.x, Pf0.yz));
|
|
let n010 = dot(g010, vec3<f32>(Pf0.x, Pf1.y, Pf0.z));
|
|
let n110 = dot(g110, vec3<f32>(Pf1.xy, Pf0.z));
|
|
let n001 = dot(g001, vec3<f32>(Pf0.xy, Pf1.z));
|
|
let n101 = dot(g101, vec3<f32>(Pf1.x, Pf0.y, Pf1.z));
|
|
let n011 = dot(g011, vec3<f32>(Pf0.x, Pf1.yz));
|
|
let n111 = dot(g111, Pf1);
|
|
|
|
let fade_xyz = fade(Pf0);
|
|
let n_z = mix(vec4<f32>(n000, n100, n010, n110), vec4<f32>(n001, n101, n011, n111), fade_xyz.z);
|
|
let n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y);
|
|
let n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x);
|
|
return 2.2 * n_xyz;
|
|
}
|
|
|
|
@fragment
|
|
fn main(@builtin(position) fragCoord: vec4<f32>) -> @location(0) vec4<f32> {
|
|
let resolution = vec2<f32>(256.0, 256.0);
|
|
let uv = fragCoord.xy / resolution;
|
|
var sum = 0.0;
|
|
var scale = 1.0;
|
|
var amplitude = 0.5;
|
|
|
|
for (var i = 0; i < 5; i = i + 1) {
|
|
// Make noise tileable by wrapping coordinates
|
|
let wrapped_uv = fract(uv * scale);
|
|
let p = vec3<f32>(wrapped_uv * 10.0, uTime * 0.2);
|
|
sum += cnoise(p) * amplitude;
|
|
scale *= 2.0;
|
|
amplitude *= 0.5;
|
|
}
|
|
|
|
return vec4<f32>(sum, 0.0, 0.0, 1.0);
|
|
}
|
|
`;
|
|
|
|
// Ocean vertex shader
|
|
export const oceanVertexShader = `
|
|
struct Uniforms {
|
|
view: mat4x4<f32>,
|
|
model: mat4x4<f32>,
|
|
projection: mat4x4<f32>,
|
|
eyePos: vec3<f32>,
|
|
};
|
|
|
|
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
|
@group(0) @binding(1) var displacementTexture: texture_2d<f32>;
|
|
@group(0) @binding(2) var displacementSampler: sampler;
|
|
|
|
struct VertexOutput {
|
|
@builtin(position) position: vec4<f32>,
|
|
@location(0) fragPos: vec3<f32>,
|
|
@location(1) uv: vec2<f32>,
|
|
};
|
|
|
|
@vertex
|
|
fn main(
|
|
@location(0) position: vec3<f32>,
|
|
@location(1) uv: vec2<f32>
|
|
) -> VertexOutput {
|
|
var output: VertexOutput;
|
|
var worldPos = uniforms.model * vec4<f32>(position, 1.0);
|
|
|
|
output.uv = uv;
|
|
|
|
// Sample displacement using textureSampleLevel (works in vertex shader)
|
|
let displace = textureSampleLevel(displacementTexture, displacementSampler, uv, 0.0);
|
|
worldPos.z = worldPos.z + displace.r * 0.15;
|
|
|
|
output.position = uniforms.projection * uniforms.view * worldPos;
|
|
output.fragPos = worldPos.xyz;
|
|
|
|
return output;
|
|
}
|
|
`;
|
|
|
|
// Ocean fragment shader
|
|
export const oceanFragmentShader = `
|
|
struct Uniforms {
|
|
view: mat4x4<f32>,
|
|
model: mat4x4<f32>,
|
|
projection: mat4x4<f32>,
|
|
eyePos: vec3<f32>,
|
|
};
|
|
|
|
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
|
@group(0) @binding(1) var displacementTexture: texture_2d<f32>;
|
|
@group(0) @binding(2) var displacementSampler: sampler;
|
|
|
|
@fragment
|
|
fn main(
|
|
@location(0) fragPos: vec3<f32>,
|
|
@location(1) uv: vec2<f32>
|
|
) -> @location(0) vec4<f32> {
|
|
// Sample displacement for normal calculation only
|
|
let gridPointDelta = 1.0 / 256.0;
|
|
let displacementScale = 0.15;
|
|
|
|
let displace = textureSample(displacementTexture, displacementSampler, uv).r * displacementScale;
|
|
let right = textureSample(displacementTexture, displacementSampler, vec2<f32>(uv.x + gridPointDelta, uv.y)).r * displacementScale;
|
|
let left = textureSample(displacementTexture, displacementSampler, vec2<f32>(uv.x - gridPointDelta, uv.y)).r * displacementScale;
|
|
let up = textureSample(displacementTexture, displacementSampler, vec2<f32>(uv.x, uv.y + gridPointDelta)).r * displacementScale;
|
|
let down = textureSample(displacementTexture, displacementSampler, vec2<f32>(uv.x, uv.y - gridPointDelta)).r * displacementScale;
|
|
|
|
// Calculate surface normal
|
|
let dX = vec3<f32>(gridPointDelta * 2.0, 0.0, right - left);
|
|
let dY = vec3<f32>(0.0, gridPointDelta * 2.0, up - down);
|
|
var norm = normalize(cross(dX, dY));
|
|
|
|
// Lighting
|
|
let lightDir = normalize(vec3<f32>(0.3, 0.5, 0.8));
|
|
let diff = max(dot(norm, lightDir), 0.0);
|
|
let diffuse = diff * vec3<f32>(0.8, 0.9, 1.0);
|
|
|
|
// Fresnel
|
|
let toCameraVector = normalize(fragPos - uniforms.eyePos);
|
|
let reflec = normalize(reflect(toCameraVector, norm));
|
|
let n1 = 1.0;
|
|
let n2 = 1.33333;
|
|
let R0 = pow((n1 - n2) / (n1 + n2), 2.0);
|
|
let fresnel = R0 + (1.0 - R0) * pow((1.0 - dot(norm, reflec)), 5.0);
|
|
|
|
let oceanColor = vec3<f32>(0.0, 0.25, 0.35);
|
|
let skyColor = vec3<f32>(0.4, 0.6, 0.8);
|
|
|
|
// Subsurface scattering
|
|
let sssSun = vec3<f32>(0.0, -5.0, -7.0);
|
|
let tosssSunVec = normalize(sssSun - fragPos);
|
|
let tosssSun = normalize(vec3<f32>(0.0, -100.0, 1.0));
|
|
let ssDistortion = 0.1;
|
|
let sssIntensity = 1.0;
|
|
let halfWay = normalize(tosssSun + norm * ssDistortion);
|
|
let ssScateringCoef = pow(clamp(dot(toCameraVector, -halfWay), 0.0, 1.0), 5.0) * sssIntensity;
|
|
|
|
// Sun glittering
|
|
var glitterFactor = max(0.0, dot(tosssSunVec, reflect(-toCameraVector, norm)));
|
|
if (glitterFactor <= 0.98) {
|
|
glitterFactor = 0.0;
|
|
}
|
|
|
|
let lightColor = vec3<f32>(1.0, 1.0, 1.0);
|
|
let ambientColor = vec3<f32>(0.1, 0.15, 0.2);
|
|
let finalColor = ambientColor +
|
|
diffuse * 0.4 +
|
|
mix(oceanColor * (1.0 + ssScateringCoef), skyColor * 0.5, fresnel * 0.7) +
|
|
lightColor * glitterFactor * 0.5;
|
|
|
|
return vec4<f32>(clamp(finalColor, vec3<f32>(0.0), vec3<f32>(1.0)), 1.0);
|
|
}
|
|
`;
|
|
|
|
// Skybox vertex shader
|
|
export const skyboxVertexShader = `
|
|
struct Uniforms {
|
|
view: mat4x4<f32>,
|
|
projection: mat4x4<f32>,
|
|
sunDirection: vec3<f32>,
|
|
};
|
|
|
|
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
|
|
|
struct VertexOutput {
|
|
@builtin(position) position: vec4<f32>,
|
|
@location(0) rayDir: vec3<f32>,
|
|
};
|
|
|
|
@vertex
|
|
fn main(@location(0) position: vec3<f32>) -> VertexOutput {
|
|
var output: VertexOutput;
|
|
output.rayDir = position;
|
|
|
|
// Remove translation from view matrix
|
|
var rotView = uniforms.view;
|
|
rotView[3] = vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
|
|
|
let pos = uniforms.projection * rotView * vec4<f32>(position, 1.0);
|
|
output.position = pos;
|
|
|
|
return output;
|
|
}
|
|
`;
|
|
|
|
// Skybox fragment shader
|
|
export const skyboxFragmentShader = `
|
|
struct Uniforms {
|
|
view: mat4x4<f32>,
|
|
projection: mat4x4<f32>,
|
|
sunDirection: vec3<f32>,
|
|
};
|
|
|
|
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
|
|
|
@fragment
|
|
fn main(@location(0) rayDir: vec3<f32>) -> @location(0) vec4<f32> {
|
|
let ray = normalize(rayDir);
|
|
let upAmount = ray.z;
|
|
|
|
// Sky gradient
|
|
let horizonBlend = pow(1.0 - max(upAmount, 0.0), 2.0);
|
|
let zenithColor = vec3<f32>(0.15, 0.35, 0.75);
|
|
let horizonColor = vec3<f32>(0.55, 0.7, 0.9);
|
|
var skyColor = mix(zenithColor, horizonColor, horizonBlend);
|
|
|
|
// Horizon glow
|
|
let horizonGlow = pow(max(1.0 - abs(upAmount), 0.0), 6.0);
|
|
skyColor = skyColor + vec3<f32>(0.4, 0.25, 0.1) * horizonGlow * 0.4;
|
|
|
|
// Sun
|
|
let sunDir = normalize(uniforms.sunDirection);
|
|
let sunAngle = max(dot(ray, sunDir), 0.0);
|
|
|
|
let sunDisk = smoothstep(0.9993, 0.9998, sunAngle);
|
|
let sunColor = vec3<f32>(1.0, 0.95, 0.85);
|
|
let sunGlow = pow(sunAngle, 48.0) * 0.6;
|
|
let sunHalo = pow(sunAngle, 6.0) * 0.25;
|
|
|
|
skyColor = skyColor + sunColor * sunDisk * 3.0;
|
|
skyColor = skyColor + vec3<f32>(1.0, 0.85, 0.5) * sunGlow;
|
|
skyColor = skyColor + vec3<f32>(1.0, 0.9, 0.7) * sunHalo;
|
|
|
|
// Below horizon
|
|
if (upAmount < 0.0) {
|
|
let depth = -upAmount;
|
|
let deepColor = vec3<f32>(0.02, 0.08, 0.15);
|
|
skyColor = mix(horizonColor * 0.7, deepColor, smoothstep(0.0, 0.5, depth));
|
|
}
|
|
|
|
return vec4<f32>(skyColor, 1.0);
|
|
}
|
|
`;
|