Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] changes to the ocean; trying to fix Gerstner waves #59

Draft
wants to merge 17 commits into
base: main
Choose a base branch
from
30 changes: 30 additions & 0 deletions public/shaders/std-gerstner.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
fn rotateDir(a: vec2<f32>, rad: f32) -> vec2<f32> {
let sinC = sin(rad);
let cosC = cos(rad);
return vec2(
a.x * cosC - a.y * sinC,
a.x * sinC + a.y * cosC
);
}

fn gerstner(uv: vec2<f32>, t: f32) -> mat2x3<f32> {
var displacement = vec3<f32>(0.0, 0.0, 0.0);
var normal = vec3<f32>(0.0, 0.0, 0.0);
for (var i = 0u; i < scene.numGerstnerWaves; i++) {
let wave = gerstnerWaves.ms[i];
// let D = rotateDir(wave.D, 0.7);
let D = wave.D;
let dot_w_d_uv_phi_t = wave.w * dot(D, uv) + wave.phi * t;
let _cos = cos(dot_w_d_uv_phi_t);
let _sin = sin(dot_w_d_uv_phi_t);
displacement.x += wave.Q * wave.A * D.x * _cos;
displacement.z += wave.Q * wave.A * D.y * _cos;
displacement.y += wave.A * _sin;
normal.x += -1.0 * D.x * wave.w * wave.A * _cos;
normal.z += -1.0 * D.y * wave.w * wave.A * _cos;
normal.y += wave.Q * wave.w * wave.A * _sin;
}
normal.y = 1.0 - normal.y;
normalize(normal);
return mat2x3(displacement, normal);
}
82 changes: 36 additions & 46 deletions public/shaders/std-ocean.wgsl
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
struct VertexOutput {
// TODO(@darzu): change
@location(0) normal : vec3<f32>,
@location(1) color : vec3<f32>,
// @location(0) @interpolate(flat) normal : vec3<f32>,
@location(1) @interpolate(flat) color : vec3<f32>,
@location(2) worldPos: vec4<f32>,
@location(3) uv: vec2<f32>,
@location(4) @interpolate(flat) surface: u32,
@@ -56,25 +57,6 @@ fn getShadowVis(shadowPos: vec3<f32>, normal: vec3<f32>, lightDir: vec3<f32>, in
return visibility;
}

fn gerstner(uv: vec2<f32>, t: f32) -> mat2x3<f32> {
var displacement = vec3<f32>(0.0, 0.0, 0.0);
var normal = vec3<f32>(0.0, 0.0, 0.0);
for (var i = 0u; i < scene.numGerstnerWaves; i++) {
let wave = gerstnerWaves.ms[i];
displacement = displacement +
vec3<f32>(wave.Q * wave.A + wave.D.x * cos(dot(wave.w * wave.D, uv) + wave.phi * t),
wave.A * sin(dot(wave.w * wave.D, uv) + wave.phi * t),
wave.Q * wave.A + wave.D.y * cos(dot(wave.w * wave.D, uv) + wave.phi * t));
normal = normal +
vec3<f32>(-1.0 * wave.D.x * wave.w * wave.A * cos(wave.w * dot(wave.D, uv) + wave.phi * t),
wave.Q * wave.w * wave.A * sin(wave.w * dot(wave.D, uv) + wave.phi * t),
-1.0 * wave.D.y * wave.w * wave.A * cos(wave.w * dot(wave.D, uv) + wave.phi * t));
}
normal.y = 1.0 - normal.y;
normalize(normal);
return mat2x3(displacement, normal);
}

@vertex
fn vert_main(input: VertexInput) -> VertexOutput {
let position = input.position;
@@ -88,10 +70,13 @@ fn vert_main(input: VertexInput) -> VertexOutput {
// TODO(@darzu): we're not totally sure about x,y,z vs normal,tangent,perp
let surfBasis = mat3x3<f32>(perp, normal, tangent);
let gerst = gerstner(input.uv * 1000, scene.time * .001);
let displacedPos = position + surfBasis * gerst[0];
//let displacedPos = flattenedPos + gerst[0];
let gerstNormal = surfBasis * gerst[1];
//let gerstNormal = gerst[1];

// let displacedPos = position + surfBasis * gerst[0];
// let gerstNormal = surfBasis * gerst[1];
// FLATTENED:
let displacedPos = flattenedPos + gerst[0];
let gerstNormal = gerst[1];

// let displacedPos = flattenedPos + wave1;
// let displacedPos = position + wave0;
// let displacedPos = position + wave1;
@@ -120,42 +105,47 @@ fn vert_main(input: VertexInput) -> VertexOutput {

struct FragOut {
@location(0) color: vec4<f32>,
@location(1) surface: vec2<u32>,
@location(1) normal: vec4<f32>,
@location(2) surface: vec2<u32>,
}

@fragment
fn frag_main(input: VertexOutput) -> FragOut {
let normal = normalize(input.normal);

var lightingColor: vec3<f32> = vec3<f32>(0.0, 0.0, 0.0);
let unlit = 0u;
for (var i: u32 = 0u; i < scene.numPointLights; i++) {
let light = pointLights.ms[i];
let toLight = light.position - input.worldPos.xyz;
let distance = length(toLight);
let attenuation = 1.0 / (light.constant + light.linear * distance +
light.quadratic * distance * distance);
let angle = clamp(dot(normalize(toLight), input.normal), 0.0, 1.0);
// XY is in (-1, 1) space, Z is in (0, 1) space
let posFromLight = (pointLights.ms[i].viewProj * input.worldPos).xyz;
// var lightingColor: vec3<f32> = vec3<f32>(0.0, 0.0, 0.0);
// let unlit = 1u;
// for (var i: u32 = 0u; i < scene.numPointLights; i++) {
// let light = pointLights.ms[i];
// let toLight = light.position - input.worldPos.xyz;
// let distance = length(toLight);
// let attenuation = 1.0 / (light.constant + light.linear * distance +
// light.quadratic * distance * distance);
// let angle = clamp(dot(normalize(toLight), input.normal), 0.0, 1.0);
// // XY is in (-1, 1) space, Z is in (0, 1) space
// let posFromLight = (pointLights.ms[i].viewProj * input.worldPos).xyz;

// Convert XY to (0, 1), Y is flipped because texture coords are Y-down.
let shadowPos = vec3<f32>(posFromLight.xy * vec2<f32>(0.5, -0.5) + vec2<f32>(0.5, 0.5),
posFromLight.z
);
let shadowVis = getShadowVis(shadowPos, input.normal, normalize(toLight), i);
//lightingColor = lightingColor + clamp(abs((light.ambient * attenuation) + (light.diffuse * angle * attenuation * shadowVis)), vec3(0.0), vec3(1.0));
//lightingColor += light.ambient;
lightingColor = lightingColor + f32(1u - unlit) * ((light.ambient * attenuation) + (light.diffuse * angle * attenuation * shadowVis));
}
let litColor = input.color * (lightingColor + vec3(f32(unlit)));
// // Convert XY to (0, 1), Y is flipped because texture coords are Y-down.
// let shadowPos = vec3<f32>(posFromLight.xy * vec2<f32>(0.5, -0.5) + vec2<f32>(0.5, 0.5),
// posFromLight.z
// );
// let shadowVis = getShadowVis(shadowPos, input.normal, normalize(toLight), i);
// //lightingColor = lightingColor + clamp(abs((light.ambient * attenuation) + (light.diffuse * angle * attenuation * shadowVis)), vec3(0.0), vec3(1.0));
// //lightingColor += light.ambient;
// lightingColor = lightingColor + f32(1u - unlit) * ((light.ambient * attenuation) + (light.diffuse * angle * attenuation * shadowVis));
// }
// let litColor = input.color * (lightingColor + vec3(f32(unlit)));

// let litColor = mix(vec3(0.1, 0.3, 0.8), vec3(0.015), input.uv.x);
let litColor = mix(vec3(0.1, 0.3, 0.8), vec3(0.1, 0.05, 0.1), input.uv.x);


var out: FragOut;
out.color = vec4<f32>(litColor, 1.0);

out.surface.r = input.surface;
out.surface.g = input.id;
out.normal = vec4(normal, 0.0);

return out;
}
2 changes: 1 addition & 1 deletion public/shaders/std-outline.wgsl
Original file line number Diff line number Diff line change
@@ -95,7 +95,7 @@ fn frag_main(@location(0) uv : vec2<f32>) -> @location(0) vec4<f32> {
color *= 1.0 + edgeLum;
// TODO(@darzu): DEBUG WIREFRAME
// color *= 2.0;
// color = vec3(0.8);
color = vec3(0.8);
}
// DEBUG WIREFRAME
// else {
180 changes: 140 additions & 40 deletions src/game/assets.ts
Original file line number Diff line number Diff line change
@@ -93,6 +93,7 @@ const MeshTransforms: Partial<{
mat4.fromYRotation(mat4.create(), -Math.PI * 0.5),
vec3.fromValues(-5, 0, 0)
),
// ocean: mat4.fromScaling(mat4.create(), [0.1, 0.1, 0.1]),
ocean: mat4.fromScaling(mat4.create(), [2, 2, 2]),
};

@@ -168,8 +169,28 @@ const MeshModify: Partial<{
// console.log("getMeshAsGrid failed!");
// console.error(e);
// }

const xLen = grid.length;
const yLen = grid[0].length;

// redo quad indices based on the grid (optional?)
for (let xi = 0; xi < xLen - 1; xi++) {
for (let yi = 0; yi < yLen - 1; yi++) {
const qi = gridXYtoQuad(xi, yi);
vec4.copy(m.quad[qi], [
grid[xi][yi],
grid[xi + 1][yi],
grid[xi + 1][yi + 1],
grid[xi][yi + 1],
]);
}
}
function gridXYtoQuad(xi: number, yi: number): number {
const qi = yi + xi * (yLen - 1);
assert(qi < m.quad.length, "quads and grid mismatch!");
return qi;
}

// console.log(`xLen:${xLen},yLen:${yLen}`);
const uvs = m.pos.map((_, vi) => vec2.create());
m.uvs = uvs;
@@ -182,45 +203,132 @@ const MeshModify: Partial<{
// instead of per quad, for vertex displacement (e.g. waves)
// purposes?

//set tangents
// set tangents and normals
m.tangents = m.pos.map(() => vec3.create());
m.normals = m.pos.map(() => vec3.create());
for (let xIndex = 0; xIndex < grid.length; xIndex++) {
for (let yIndex = 0; yIndex < grid[0].length; yIndex++) {
for (let xi = 0; xi < grid.length; xi++) {
for (let yi = 0; yi < grid[0].length; yi++) {
let normal: vec3;
let tangent: vec3;
if (xIndex + 1 < grid.length && yIndex + 1 < grid[0].length) {
const pos = m.pos[grid[xIndex][yIndex]];
const posNX = m.pos[grid[xIndex + 1][yIndex]];
const posNY = m.pos[grid[xIndex][yIndex + 1]];
if (xi + 1 < grid.length && yi + 1 < grid[0].length) {
const pos = m.pos[grid[xi][yi]];
const posNX = m.pos[grid[xi + 1][yi]];
const posNY = m.pos[grid[xi][yi + 1]];

normal = computeTriangleNormal(pos, posNX, posNY);

tangent = vec3.sub(m.tangents[grid[xIndex][yIndex]], posNX, pos);
tangent = vec3.sub(m.tangents[grid[xi][yi]], posNX, pos);
vec3.normalize(tangent, tangent);
} else if (xIndex + 1 >= grid.length) {
normal = m.normals[grid[xIndex - 1][yIndex]];
tangent = m.tangents[grid[xIndex - 1][yIndex]];
} else if (yIndex + 1 >= grid[0].length) {
normal = m.normals[grid[xIndex][yIndex - 1]];
tangent = m.tangents[grid[xIndex][yIndex - 1]];
} else if (xi + 1 >= grid.length) {
normal = m.normals[grid[xi - 1][yi]];
tangent = m.tangents[grid[xi - 1][yi]];
} else if (yi + 1 >= grid[0].length) {
normal = m.normals[grid[xi][yi - 1]];
tangent = m.tangents[grid[xi][yi - 1]];
} else {
assert(false);
}
vec3.copy(m.normals[grid[xIndex][yIndex]], normal);
vec3.copy(m.tangents[grid[xIndex][yIndex]], tangent);
vec3.copy(m.normals[grid[xi][yi]], normal);
vec3.copy(m.tangents[grid[xi][yi]], tangent);
}
}

// TODO(@darzu): testing subdivide
// for (let i = 0; i < 100; i++) {
// subdivideQuad(i);
// }
let startXi = Math.floor(grid.length * 0.85);
let endXi = Math.floor(grid.length * 1.0);
let startYi = Math.floor(grid[0].length * 0.0);
let endYi = Math.floor(grid[0].length * 0.2);
for (let xi = startXi; xi < endXi; xi++) {
for (let yi = startYi; yi < endYi; yi++) {
subdivideQuad(gridXYtoQuad(xi, yi), 2);
}
}

// console.dir(uvs);
// console.log(`
// X:
// ${max(uvs.map((uv) => uv[0]))}
// ${min(uvs.map((uv) => uv[0]))}
// Y:
// ${max(uvs.map((uv) => uv[1]))}
// ${min(uvs.map((uv) => uv[1]))}
// `);
// console.dir(m);

// console.dir({
// uvMin: [min(m.uvs.map((a) => a[0])), min(m.uvs.map((a) => a[1]))],
// uvMax: [max(m.uvs.map((a) => a[0])), max(m.uvs.map((a) => a[1]))],
// });

// console.dir(m.uvs);
// console.dir({ minX, maxX, minZ, maxZ });
return m;

function subdivideQuad(quadIdx: number, recurse = 0) {
// maintain: colors, normals, pos, quad, surfaceIds, tangents, uvs
let [tl, tr, br, bl] = m.quad[quadIdx];
let tm = m.pos.push(midPos(tl, tr)) - 1;
let rm = m.pos.push(midPos(tr, br)) - 1;
let bm = m.pos.push(midPos(br, bl)) - 1;
let lm = m.pos.push(midPos(bl, tl)) - 1;
let mm = m.pos.push(midPos(tm, bm)) - 1;

// all quads are: top-left, top-right, bottom-right, bottom-left
let q_tl = quadIdx; // re-use
m.quad[q_tl] = [tl, tm, mm, lm];
let q_tr = m.quad.push([tm, tr, rm, mm]) - 1;
let q_br = m.quad.push([mm, rm, br, bm]) - 1;
let q_bl = m.quad.push([lm, mm, bm, bl]) - 1;

// init per-vertex (set later)
for (let p of [tm, rm, bm, lm, mm]) {
m.uvs![p] = vec2.create();
m.tangents![p] = vec3.create();
m.normals![p] = vec3.create();
}

// update quad properties
for (let q of [q_tl, q_tr, q_br, q_bl]) {
// per quad:
m.colors[q] = vec3.clone(m.colors[quadIdx]);
m.surfaceIds![q] = q;
// per provoking vert:
let vs = m.quad[q];
m.normals![vs[0]] = computeTriangleNormal(
m.pos[vs[0]],
m.pos[vs[1]],
m.pos[vs[2]]
);
m.tangents![vs[0]] = vec3.sub(
vec3.create(),
m.pos[vs[1]],
m.pos[vs[0]]
);
vec3.normalize(m.tangents![vs[0]], m.tangents![vs[0]]);
}
// per vertex:
m.uvs![tm] = midUV(tl, tr);
m.uvs![rm] = midUV(tr, br);
m.uvs![bm] = midUV(br, bl);
m.uvs![lm] = midUV(bl, tl);
m.uvs![mm] = midUV(tm, bm);

// recurse?
if (recurse > 0) {
subdivideQuad(q_tl, recurse - 1);
subdivideQuad(q_tr, recurse - 1);
subdivideQuad(q_br, recurse - 1);
subdivideQuad(q_bl, recurse - 1);
}

function midPos(p0: number, p1: number): vec3 {
return vec3.fromValues(
(m.pos[p0][0] + m.pos[p1][0]) * 0.5,
(m.pos[p0][1] + m.pos[p1][1]) * 0.5,
(m.pos[p0][2] + m.pos[p1][2]) * 0.5
);
}
function midUV(p0: number, p1: number): vec2 {
return vec2.fromValues(
(m.uvs![p0][0] + m.uvs![p1][0]) * 0.5,
(m.uvs![p0][1] + m.uvs![p1][1]) * 0.5
);
}
}

function setUV(
x: number,
@@ -252,14 +360,6 @@ const MeshModify: Partial<{
];
setUV(nX, nY, dir, newDist, branch);
}
// console.dir({
// uvMin: [min(m.uvs.map((a) => a[0])), min(m.uvs.map((a) => a[1]))],
// uvMax: [max(m.uvs.map((a) => a[0])), max(m.uvs.map((a) => a[1]))],
// });

// console.dir(m.uvs);
// console.dir({ minX, maxX, minZ, maxZ });
return m;
},
};

@@ -714,13 +814,13 @@ onInit(async (em) => {

const assetsPromise = loadAssets(renderer.renderer);
assetLoader.promise = assetsPromise;
try {
const result = await assetsPromise;
em.addSingletonComponent(AssetsDef, result);
} catch (failureReason) {
// TODO(@darzu): fail more gracefully
throw `Failed to load assets: ${failureReason}`;
}
// try {
const result = await assetsPromise;
em.addSingletonComponent(AssetsDef, result);
// } catch (failureReason) {
// // TODO(@darzu): fail more gracefully
// throw `Failed to load assets: ${failureReason}`;
// }
});

async function loadTxtInternal(relPath: string): Promise<string> {
Loading