diff --git a/deps.ts b/deps.ts index 2cc6564..ce6372f 100644 --- a/deps.ts +++ b/deps.ts @@ -2,3 +2,5 @@ export * as gmath from "https://deno.land/x/gmath@0.1.11/mod.ts"; export * as png from "https://deno.land/x/pngs@0.1.1/mod.ts"; export * as obj from "https://crux.land/obj@0.0.2"; export { Dds } from "https://crux.land/dds@0.0.1"; +export { slidingWindows } from "https://deno.land/std@0.123.0/collections/mod.ts"; +export { makeNoise2D } from "https://deno.land/x/open_simplex_noise@v2.5.0/mod.ts"; diff --git a/water/deno_trace/data1.bin b/water/deno_trace/data1.bin new file mode 100644 index 0000000..0d68b3c Binary files /dev/null and b/water/deno_trace/data1.bin differ diff --git a/water/deno_trace/data2.bin b/water/deno_trace/data2.bin new file mode 100644 index 0000000..f2a786a Binary files /dev/null and b/water/deno_trace/data2.bin differ diff --git a/water/deno_trace/data3.bin b/water/deno_trace/data3.bin new file mode 100644 index 0000000..cf3480c Binary files /dev/null and b/water/deno_trace/data3.bin differ diff --git a/water/deno_trace/data4.bin b/water/deno_trace/data4.bin new file mode 100644 index 0000000..ed07206 Binary files /dev/null and b/water/deno_trace/data4.bin differ diff --git a/water/deno_trace/data5.bin b/water/deno_trace/data5.bin new file mode 100644 index 0000000..b06a388 Binary files /dev/null and b/water/deno_trace/data5.bin differ diff --git a/water/deno_trace/data6.wgsl b/water/deno_trace/data6.wgsl new file mode 100644 index 0000000..9f7e256 --- /dev/null +++ b/water/deno_trace/data6.wgsl @@ -0,0 +1,48 @@ +struct Uniforms { + projection_view: mat4x4; + clipping_plane: vec4; +}; + +[[group(0), binding(0)]] +var uniforms: Uniforms; + +let light: vec3 = vec3(150.0, 70.0, 0.0); +let light_colour: vec3 = vec3(1.0, 0.98, 0.82); +let ambient: f32 = 0.2; + +struct VertexOutput { + [[builtin(position)]] position: vec4; + [[location(0)]] colour: vec4; + // Comment this out if using user-clipping planes: + [[location(1)]] clip_dist: f32; +}; + +[[stage(vertex)]] +fn vs_main( + [[location(0)]] position: vec3, + [[location(1)]] normal: vec3, + [[location(2)]] colour: vec4, +) -> VertexOutput { + var out: VertexOutput; + out.position = uniforms.projection_view * vec4(position, 1.0); + + // https://www.desmos.com/calculator/nqgyaf8uvo + let normalized_light_direction = normalize(position - light); + let brightness_diffuse = clamp(dot(normalized_light_direction, normal), 0.2, 1.0); + + out.colour = vec4(max((brightness_diffuse + ambient) * light_colour * colour.rgb, vec3(0.0, 0.0, 0.0)), colour.a); + out.clip_dist = dot(vec4(position, 1.0), uniforms.clipping_plane); + return out; +} + +[[stage(fragment), early_depth_test]] +fn fs_main( + in: VertexOutput, +) -> [[location(0)]] vec4 { + // Comment this out if using user-clipping planes: + if(in.clip_dist < 0.0) { + discard; + } + + return vec4(in.colour.xyz, 1.0); +} diff --git a/water/deno_trace/data7.wgsl b/water/deno_trace/data7.wgsl new file mode 100644 index 0000000..b7a4701 --- /dev/null +++ b/water/deno_trace/data7.wgsl @@ -0,0 +1,251 @@ +struct Uniforms { + view: mat4x4; + projection: mat4x4; + time_size_width: vec4; + viewport_height: f32; +}; +[[group(0), binding(0)]] var uniforms: Uniforms; + +let light_point: vec3 = vec3(150.0, 70.0, 0.0); +let light_colour: vec3 = vec3(1.0, 0.98, 0.82); +let one: vec4 = vec4(1.0, 1.0, 1.0, 1.0); + +let Y_SCL: f32 = 0.86602540378443864676372317075294; +let CURVE_BIAS: f32 = -0.1; +let INV_1_CURVE_BIAS: f32 = 1.11111111111; //1.0 / (1.0 + CURVE_BIAS); + +// +// The following code to calculate simplex 3D +// is from https://github.com/ashima/webgl-noise +// +// Simplex 3D Noise +// by Ian McEwan, Ashima Arts. +// +fn permute(x: vec4) -> vec4 { + var temp: vec4 = 289.0 * one; + return modf(((x*34.0) + one) * x, &temp); +} + +fn taylorInvSqrt(r: vec4) -> vec4 { + return 1.79284291400159 * one - 0.85373472095314 * r; +} + +fn snoise(v: vec3) -> f32 { + let C = vec2(1.0/6.0, 1.0/3.0); + let D = vec4(0.0, 0.5, 1.0, 2.0); + + // First corner + //TODO: use the splat operations when available + let vCy = dot(v, C.yyy); + var i: vec3 = floor(v + vec3(vCy, vCy, vCy)); + let iCx = dot(i, C.xxx); + let x0 = v - i + vec3(iCx, iCx, iCx); + + // Other corners + let g = step(x0.yzx, x0.xyz); + let l = (vec3(1.0, 1.0, 1.0) - g).zxy; + let i1 = min(g, l); + let i2 = max(g, l); + + // x0 = x0 - 0.0 + 0.0 * C.xxx; + // x1 = x0 - i1 + 1.0 * C.xxx; + // x2 = x0 - i2 + 2.0 * C.xxx; + // x3 = x0 - 1.0 + 3.0 * C.xxx; + let x1 = x0 - i1 + C.xxx; + let x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y + let x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y + + // Permutations + var temp: vec3 = 289.0 * one.xyz; + i = modf(i, &temp); + let p = permute( + permute( + permute(i.zzzz + vec4(0.0, i1.z, i2.z, 1.0)) + + i.yyyy + vec4(0.0, i1.y, i2.y, 1.0)) + + i.xxxx + vec4(0.0, i1.x, i2.x, 1.0)); + + // Gradients: 7x7 points over a square, mapped onto an octahedron. + // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294) + let n_ = 0.142857142857;// 1.0/7.0 + let ns = n_ * D.wyz - D.xzx; + + let j = p - 49.0 * floor(p * ns.z * ns.z);// mod(p,7*7) + + let x_ = floor(j * ns.z); + let y_ = floor(j - 7.0 * x_);// mod(j,N) + + var x: vec4 = x_ *ns.x + ns.yyyy; + var y: vec4 = y_ *ns.x + ns.yyyy; + let h = one - abs(x) - abs(y); + + let b0 = vec4(x.xy, y.xy); + let b1 = vec4(x.zw, y.zw); + + //vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - one; + //vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - one; + let s0 = floor(b0)*2.0 + one; + let s1 = floor(b1)*2.0 + one; + let sh = -step(h, 0.0 * one); + + let a0 = b0.xzyw + s0.xzyw*sh.xxyy; + let a1 = b1.xzyw + s1.xzyw*sh.zzww; + + var p0: vec3 = vec3(a0.xy, h.x); + var p1: vec3 = vec3(a0.zw, h.y); + var p2: vec3 = vec3(a1.xy, h.z); + var p3: vec3 = vec3(a1.zw, h.w); + + //Normalise gradients + let norm = taylorInvSqrt(vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3))); + p0 = p0 * norm.x; + p1 = p1 * norm.y; + p2 = p2 * norm.z; + p3 = p3 * norm.w; + + // Mix final noise value + var m: vec4 = max(0.6 * one - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0 * one); + m = m * m; + return 9.0 * dot(m*m, vec4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3))); +} + +// End of 3D simplex code. + +fn apply_distortion(pos: vec3) -> vec3 { + var perlin_pos: vec3 = pos; + + //Do noise transformation to permit for smooth, + //continuous movement. + + //TODO: we should be able to name them `sin` and `cos`. + let sn = uniforms.time_size_width.x; + let cs = uniforms.time_size_width.y; + let size = uniforms.time_size_width.z; + + // Rotate 90 Z, Move Left Size / 2 + perlin_pos = vec3(perlin_pos.y - perlin_pos.x - size, perlin_pos.x, perlin_pos.z); + + let xcos = perlin_pos.x * cs; + let xsin = perlin_pos.x * sn; + let ycos = perlin_pos.y * cs; + let ysin = perlin_pos.y * sn; + let zcos = perlin_pos.z * cs; + let zsin = perlin_pos.z * sn; + + // Rotate Time Y + let perlin_pos_y = vec3(xcos + zsin, perlin_pos.y, -xsin + xcos); + + // Rotate Time Z + let perlin_pos_z = vec3(xcos - ysin, xsin + ycos, perlin_pos.x); + + // Rotate 90 Y + perlin_pos = vec3(perlin_pos.z - perlin_pos.x, perlin_pos.y, perlin_pos.x); + + // Rotate Time X + let perlin_pos_x = vec3(perlin_pos.x, ycos - zsin, ysin + zcos); + + // Sample at different places for x/y/z to get random-looking water. + return vec3( + //TODO: use splats + pos.x + snoise(perlin_pos_x + 2.0*one.xxx) * 0.4, + pos.y + snoise(perlin_pos_y - 2.0*one.xxx) * 1.8, + pos.z + snoise(perlin_pos_z) * 0.4 + ); +} + +// Multiply the input by the scale values. +fn make_position(original: vec2) -> vec4 { + let interpreted = vec3(original.x * 0.5, 0.0, original.y * Y_SCL); + return vec4(apply_distortion(interpreted), 1.0); +} + +// Create the normal, and apply the curve. Change the Curve Bias above. +fn make_normal(a: vec3, b: vec3, c: vec3) -> vec3 { + let norm = normalize(cross(b - c, a - c)); + let center = (a + b + c) * (1.0 / 3.0); //TODO: use splat + return (normalize(a - center) * CURVE_BIAS + norm) * INV_1_CURVE_BIAS; +} + +// Calculate the fresnel effect. +fn calc_fresnel(view: vec3, normal: vec3) -> f32 { + var refractive: f32 = abs(dot(view, normal)); + refractive = pow(refractive, 1.33333333333); + return refractive; +} + +// Calculate the specular lighting. +fn calc_specular(eye: vec3, normal: vec3, light: vec3) -> f32 { + let light_reflected = reflect(light, normal); + var specular: f32 = max(dot(eye, light_reflected), 0.0); + specular = pow(specular, 10.0); + return specular; +} + +struct VertexOutput { + [[builtin(position)]] position: vec4; + [[location(0)]] f_WaterScreenPos: vec2; + [[location(1)]] f_Fresnel: f32; + [[location(2)]] f_Light: vec3; +}; + +[[stage(vertex)]] +fn vs_main( + [[location(0)]] position: vec2, + [[location(1)]] offsets: vec4, +) -> VertexOutput { + let p_pos = vec2(position); + let b_pos = make_position(p_pos + vec2(offsets.xy)); + let c_pos = make_position(p_pos + vec2(offsets.zw)); + let a_pos = make_position(p_pos); + let original_pos = vec4(p_pos.x * 0.5, 0.0, p_pos.y * Y_SCL, 1.0); + + let vm = uniforms.view; + let transformed_pos = vm * a_pos; + //TODO: use vector splats for division + let water_pos = transformed_pos.xyz * (1.0 / transformed_pos.w); + let normal = make_normal((vm * a_pos).xyz, (vm * b_pos).xyz, (vm * c_pos).xyz); + let eye = normalize(-water_pos); + let transformed_light = vm * vec4(light_point, 1.0); + + var out: VertexOutput; + out.f_Light = light_colour * calc_specular(eye, normal, normalize(water_pos.xyz - (transformed_light.xyz * (1.0 / transformed_light.w)))); + out.f_Fresnel = calc_fresnel(eye, normal); + + let gridpos = uniforms.projection * vm * original_pos; + out.f_WaterScreenPos = (0.5 * gridpos.xy * (1.0 / gridpos.w)) + vec2(0.5, 0.5); + + out.position = uniforms.projection * transformed_pos; + return out; +} + + +let water_colour: vec3 = vec3(0.0, 0.46, 0.95); +let zNear: f32 = 10.0; +let zFar: f32 = 400.0; + +[[group(0), binding(1)]] var reflection: texture_2d; +[[group(0), binding(2)]] var terrain_depth_tex: texture_2d; +[[group(0), binding(3)]] var colour_sampler: sampler; + +fn to_linear_depth(depth: f32) -> f32 { + let z_n: f32 = 2.0 * depth - 1.0; + let z_e: f32 = 2.0 * zNear * zFar / (zFar + zNear - z_n * (zFar - zNear)); + return z_e; +} + +[[stage(fragment)]] +fn fs_main(in: VertexOutput) -> [[location(0)]] vec4 { + let reflection_colour = textureSample(reflection, colour_sampler, in.f_WaterScreenPos.xy).xyz; + + let pixel_depth = to_linear_depth(in.position.z); + let normalized_coords = in.position.xy / vec2(uniforms.time_size_width.w, uniforms.viewport_height); + let terrain_depth = to_linear_depth(textureSample(terrain_depth_tex, colour_sampler, normalized_coords).r); + + let dist = terrain_depth - pixel_depth; + let clamped = pow(smoothStep(0.0, 1.5, dist), 4.8); + + let final_colour = in.f_Light + reflection_colour; + let t = smoothStep(1.0, 5.0, dist) * 0.2; //TODO: splat for mix()? + let depth_colour = mix(final_colour, water_colour, vec3(t, t, t)); + + return vec4(depth_colour, clamped * (1.0 - in.f_Fresnel)); +} diff --git a/water/deno_trace/data8.bin b/water/deno_trace/data8.bin new file mode 100644 index 0000000..3ce6484 Binary files /dev/null and b/water/deno_trace/data8.bin differ diff --git a/water/deno_trace/trace.ron b/water/deno_trace/trace.ron new file mode 100644 index 0000000..726efc5 --- /dev/null +++ b/water/deno_trace/trace.ron @@ -0,0 +1,786 @@ +[ +Init( + desc: ( + label: None, + features: 0, + limits: ( + maxTextureDimension1d: 8192, + maxTextureDimension2d: 8192, + maxTextureDimension3d: 2048, + maxTextureArrayLayers: 256, + maxBindGroups: 4, + maxDynamicUniformBuffersPerPipelineLayout: 8, + maxDynamicStorageBuffersPerPipelineLayout: 4, + maxSampledTexturesPerShaderStage: 16, + maxSamplersPerShaderStage: 16, + maxStorageBuffersPerShaderStage: 8, + maxStorageTexturesPerShaderStage: 8, + maxUniformBuffersPerShaderStage: 12, + maxUniformBufferBindingSize: 65536, + maxStorageBufferBindingSize: 134217728, + maxVertexBuffers: 8, + maxVertexAttributes: 16, + maxVertexBufferArrayStride: 2048, + maxPushConstantSize: 0, + minUniformBufferOffsetAlignment: 256, + minStorageBufferOffsetAlignment: 256, + maxInterStageShaderComponents: 60, + maxComputeWorkgroupStorageSize: 16352, + maxComputeInvocationsPerWorkgroup: 256, + maxComputeWorkgroupSizeX: 256, + maxComputeWorkgroupSizeY: 256, + maxComputeWorkgroupSizeZ: 64, + maxComputeWorkgroupsPerDimension: 65535, + ), + ), + backend: Metal, +), +CreateBuffer(Id(0, 1, Metal), ( + label: Some("Water vertices"), + size: 141556, + usage: 40, + mapped_at_creation: false, +)), +WriteBuffer( + id: Id(0, 1, Metal), + data: "data1.bin", + range: ( + start: 0, + end: 141556, + ), + queued: true, +), +CreateBuffer(Id(1, 1, Metal), ( + label: Some("Terrain vertices"), + size: 2972596, + usage: 40, + mapped_at_creation: false, +)), +WriteBuffer( + id: Id(1, 1, Metal), + data: "data2.bin", + range: ( + start: 0, + end: 2972596, + ), + queued: true, +), +CreateBindGroupLayout(Id(0, 1, Metal), ( + label: Some("Water Bind Group Layout"), + entries: [ + ( + binding: 0, + visibility: 3, + ty: Buffer( + ty: Uniform, + has_dynamic_offset: false, + min_binding_size: Some(160), + ), + count: None, + ), + ( + binding: 1, + visibility: 2, + ty: Texture( + sample_type: Float( + filterable: true, + ), + view_dimension: r#2d, + multisampled: false, + ), + count: None, + ), + ( + binding: 2, + visibility: 2, + ty: Texture( + sample_type: Float( + filterable: true, + ), + view_dimension: r#2d, + multisampled: false, + ), + count: None, + ), + ( + binding: 3, + visibility: 2, + ty: Sampler(filtering), + count: None, + ), + ], +)), +CreateBindGroupLayout(Id(1, 1, Metal), ( + label: Some("Terrain Bind Group Layout"), + entries: [ + ( + binding: 0, + visibility: 1, + ty: Buffer( + ty: Uniform, + has_dynamic_offset: false, + min_binding_size: Some(80), + ), + count: None, + ), + ], +)), +CreatePipelineLayout(Id(0, 1, Metal), ( + label: Some("water"), + bind_group_layouts: [ + Id(0, 1, Metal), + ], + push_constant_ranges: [], +)), +CreatePipelineLayout(Id(1, 1, Metal), ( + label: Some("terrain"), + bind_group_layouts: [ + Id(1, 1, Metal), + ], + push_constant_ranges: [], +)), +CreateBuffer(Id(2, 1, Metal), ( + label: Some("Water Uniforms"), + size: 160, + usage: 72, + mapped_at_creation: false, +)), +CreateBuffer(Id(3, 1, Metal), ( + label: Some("Normal Terrain Uniforms"), + size: 80, + usage: 72, + mapped_at_creation: false, +)), +CreateBuffer(Id(4, 1, Metal), ( + label: Some("Flipped Terrain Uniforms"), + size: 80, + usage: 72, + mapped_at_creation: false, +)), +WriteBuffer( + id: Id(3, 1, Metal), + data: "data3.bin", + range: ( + start: 0, + end: 80, + ), + queued: true, +), +WriteBuffer( + id: Id(4, 1, Metal), + data: "data4.bin", + range: ( + start: 0, + end: 80, + ), + queued: true, +), +WriteBuffer( + id: Id(2, 1, Metal), + data: "data5.bin", + range: ( + start: 0, + end: 160, + ), + queued: true, +), +CreateTexture(Id(0, 1, Metal), ( + label: Some("Reflection Render Texture"), + size: ( + width: 1600, + height: 1200, + depthOrArrayLayers: 1, + ), + mip_level_count: 1, + sample_count: 1, + dimension: r#2d, + format: r#rgba8unorm-srgb, + usage: 22, +)), +CreateTexture(Id(1, 1, Metal), ( + label: Some("Depth Buffer"), + size: ( + width: 1600, + height: 1200, + depthOrArrayLayers: 1, + ), + mip_level_count: 1, + sample_count: 1, + dimension: r#2d, + format: depth32float, + usage: 22, +)), +CreateSampler(Id(0, 1, Metal), ( + label: Some("Texture Sampler"), + address_modes: (r#clamp-to-edge, r#clamp-to-edge, r#clamp-to-edge), + mag_filter: nearest, + min_filter: linear, + mipmap_filter: nearest, + lod_min_clamp: 0, + lod_max_clamp: 340282350000000000000000000000000000000, + compare: None, + anisotropy_clamp: None, + border_color: None, +)), +CreateTextureView( + id: Id(0, 1, Metal), + parent_id: Id(1, 1, Metal), + desc: ( + label: None, + format: None, + dimension: None, + range: ( + aspect: all, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: None, + ), + ), +), +CreateTextureView( + id: Id(1, 1, Metal), + parent_id: Id(0, 1, Metal), + desc: ( + label: None, + format: None, + dimension: None, + range: ( + aspect: all, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: None, + ), + ), +), +CreateBindGroup(Id(0, 1, Metal), ( + label: Some("Water Bind Group"), + layout: Id(0, 1, Metal), + entries: [ + ( + binding: 0, + resource: Buffer(( + buffer_id: Id(2, 1, Metal), + offset: 0, + size: None, + )), + ), + ( + binding: 1, + resource: TextureView(Id(1, 1, Metal)), + ), + ( + binding: 2, + resource: TextureView(Id(0, 1, Metal)), + ), + ( + binding: 3, + resource: Sampler(Id(0, 1, Metal)), + ), + ], +)), +CreateTextureView( + id: Id(2, 1, Metal), + parent_id: Id(0, 1, Metal), + desc: ( + label: None, + format: None, + dimension: None, + range: ( + aspect: all, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: None, + ), + ), +), +CreateBindGroup(Id(1, 1, Metal), ( + label: Some("Terrain Normal Bind Group"), + layout: Id(1, 1, Metal), + entries: [ + ( + binding: 0, + resource: Buffer(( + buffer_id: Id(3, 1, Metal), + offset: 0, + size: None, + )), + ), + ], +)), +CreateBindGroup(Id(2, 1, Metal), ( + label: Some("Terrain Flipped Bind Group"), + layout: Id(1, 1, Metal), + entries: [ + ( + binding: 0, + resource: Buffer(( + buffer_id: Id(4, 1, Metal), + offset: 0, + size: None, + )), + ), + ], +)), +CreateShaderModule( + id: Id(0, 1, Metal), + desc: ( + label: Some("terrain"), + shader_bound_checks: ( + runtime_checks: true, + ), + ), + data: "data6.wgsl", +), +CreateShaderModule( + id: Id(1, 1, Metal), + desc: ( + label: Some("water"), + shader_bound_checks: ( + runtime_checks: true, + ), + ), + data: "data7.wgsl", +), +CreateRenderPipeline( + id: Id(0, 1, Metal), + desc: ( + label: Some("water"), + layout: Some(Id(0, 1, Metal)), + vertex: ( + stage: ( + module: Id(1, 1, Metal), + entry_point: "vs_main", + ), + buffers: [ + ( + arrayStride: 8, + stepMode: vertex, + attributes: [ + ( + format: sint16x2, + offset: 0, + shaderLocation: 0, + ), + ( + format: sint8x4, + offset: 4, + shaderLocation: 1, + ), + ], + ), + ], + ), + primitive: ( + topology: r#triangle-list, + stripIndexFormat: None, + frontFace: cw, + cullMode: None, + unclippedDepth: false, + polygonMode: fill, + conservative: false, + ), + depth_stencil: Some(( + format: depth32float, + depth_write_enabled: false, + depth_compare: less, + stencil: ( + front: ( + compare: always, + failOp: keep, + depthFailOp: keep, + passOp: keep, + ), + back: ( + compare: always, + failOp: keep, + depthFailOp: keep, + passOp: keep, + ), + read_mask: 0, + write_mask: 0, + ), + bias: ( + constant: 0, + slope_scale: 0, + clamp: 0, + ), + )), + multisample: ( + count: 1, + mask: 4294967295, + alphaToCoverageEnabled: false, + ), + fragment: Some(( + stage: ( + module: Id(1, 1, Metal), + entry_point: "fs_main", + ), + targets: [ + ( + format: r#rgba8unorm-srgb, + blend: Some(( + color: ( + srcFactor: r#src-alpha, + dstFactor: r#one-minus-src-alpha, + operation: add, + ), + alpha: ( + srcFactor: one, + dstFactor: one, + operation: max, + ), + )), + writeMask: 15, + ), + ], + )), + multiview: None, + ), + implicit_context: None, +), +CreateRenderPipeline( + id: Id(1, 1, Metal), + desc: ( + label: Some("terrain"), + layout: Some(Id(1, 1, Metal)), + vertex: ( + stage: ( + module: Id(0, 1, Metal), + entry_point: "vs_main", + ), + buffers: [ + ( + arrayStride: 28, + stepMode: vertex, + attributes: [ + ( + format: float32x3, + offset: 0, + shaderLocation: 0, + ), + ( + format: float32x3, + offset: 12, + shaderLocation: 1, + ), + ( + format: unorm8x4, + offset: 24, + shaderLocation: 2, + ), + ], + ), + ], + ), + primitive: ( + topology: r#triangle-list, + stripIndexFormat: None, + frontFace: ccw, + cullMode: Some(front), + unclippedDepth: false, + polygonMode: fill, + conservative: false, + ), + depth_stencil: Some(( + format: depth32float, + depth_write_enabled: true, + depth_compare: less, + stencil: ( + front: ( + compare: always, + failOp: keep, + depthFailOp: keep, + passOp: keep, + ), + back: ( + compare: always, + failOp: keep, + depthFailOp: keep, + passOp: keep, + ), + read_mask: 0, + write_mask: 0, + ), + bias: ( + constant: 0, + slope_scale: 0, + clamp: 0, + ), + )), + multisample: ( + count: 1, + mask: 4294967295, + alphaToCoverageEnabled: false, + ), + fragment: Some(( + stage: ( + module: Id(0, 1, Metal), + entry_point: "fs_main", + ), + targets: [ + ( + format: r#rgba8unorm-srgb, + blend: None, + writeMask: 15, + ), + ], + )), + multiview: None, + ), + implicit_context: None, +), +CreateBuffer(Id(5, 1, Metal), ( + label: Some("Capture"), + size: 7680000, + usage: 9, + mapped_at_creation: false, +)), +CreateTexture(Id(2, 1, Metal), ( + label: Some("Capture"), + size: ( + width: 1600, + height: 1200, + depthOrArrayLayers: 1, + ), + mip_level_count: 1, + sample_count: 1, + dimension: r#2d, + format: r#rgba8unorm-srgb, + usage: 17, +)), +CreateTextureView( + id: Id(3, 1, Metal), + parent_id: Id(2, 1, Metal), + desc: ( + label: None, + format: None, + dimension: None, + range: ( + aspect: all, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: None, + ), + ), +), +WriteBuffer( + id: Id(2, 1, Metal), + data: "data8.bin", + range: ( + start: 128, + end: 136, + ), + queued: true, +), +Submit(1, [ + RunRenderPass( + base: ( + label: None, + commands: [ + SetPipeline(Id(1, 1, Metal)), + SetBindGroup( + index: 0, + num_dynamic_offsets: 0, + bind_group_id: Id(2, 1, Metal), + ), + SetVertexBuffer( + slot: 0, + buffer_id: Id(1, 1, Metal), + offset: 0, + size: None, + ), + Draw( + vertex_count: 106164, + instance_count: 1, + first_vertex: 0, + first_instance: 0, + ), + ], + dynamic_offsets: [], + string_data: [], + push_constant_data: [], + ), + target_colors: [ + ( + view: Id(2, 1, Metal), + resolve_target: None, + channel: ( + load_op: clear, + store_op: store, + clear_value: ( + r: 0.6313725490196078, + g: 0.9647058823529412, + b: 1, + a: 1, + ), + read_only: false, + ), + ), + ], + target_depth_stencil: Some(( + view: Id(0, 1, Metal), + depth: ( + load_op: clear, + store_op: store, + clear_value: 1, + read_only: false, + ), + stencil: ( + load_op: load, + store_op: store, + clear_value: 0, + read_only: true, + ), + )), + ), + RunRenderPass( + base: ( + label: None, + commands: [ + SetPipeline(Id(1, 1, Metal)), + SetBindGroup( + index: 0, + num_dynamic_offsets: 0, + bind_group_id: Id(1, 1, Metal), + ), + SetVertexBuffer( + slot: 0, + buffer_id: Id(1, 1, Metal), + offset: 0, + size: None, + ), + Draw( + vertex_count: 106164, + instance_count: 1, + first_vertex: 0, + first_instance: 0, + ), + ], + dynamic_offsets: [], + string_data: [], + push_constant_data: [], + ), + target_colors: [ + ( + view: Id(3, 1, Metal), + resolve_target: None, + channel: ( + load_op: clear, + store_op: store, + clear_value: ( + r: 0.6313725490196078, + g: 0.9647058823529412, + b: 1, + a: 1, + ), + read_only: false, + ), + ), + ], + target_depth_stencil: Some(( + view: Id(0, 1, Metal), + depth: ( + load_op: clear, + store_op: store, + clear_value: 1, + read_only: false, + ), + stencil: ( + load_op: load, + store_op: store, + clear_value: 0, + read_only: true, + ), + )), + ), + RunRenderPass( + base: ( + label: None, + commands: [ + SetPipeline(Id(0, 1, Metal)), + SetBindGroup( + index: 0, + num_dynamic_offsets: 0, + bind_group_id: Id(0, 1, Metal), + ), + SetVertexBuffer( + slot: 0, + buffer_id: Id(0, 1, Metal), + offset: 0, + size: None, + ), + Draw( + vertex_count: 17694, + instance_count: 1, + first_vertex: 0, + first_instance: 0, + ), + ], + dynamic_offsets: [], + string_data: [], + push_constant_data: [], + ), + target_colors: [ + ( + view: Id(3, 1, Metal), + resolve_target: None, + channel: ( + load_op: load, + store_op: store, + clear_value: ( + r: 0, + g: 0, + b: 0, + a: 0, + ), + read_only: false, + ), + ), + ], + target_depth_stencil: Some(( + view: Id(0, 1, Metal), + depth: ( + load_op: load, + store_op: store, + clear_value: 0, + read_only: true, + ), + stencil: ( + load_op: load, + store_op: store, + clear_value: 0, + read_only: true, + ), + )), + ), + CopyTextureToBuffer( + src: ( + texture: Id(2, 1, Metal), + mip_level: 0, + origin: ( + x: 0, + y: 0, + z: 0, + ), + aspect: all, + ), + dst: ( + buffer: Id(5, 1, Metal), + layout: ( + offset: 0, + bytes_per_row: Some(6400), + rows_per_image: None, + ), + ), + size: ( + width: 1600, + height: 1200, + depthOrArrayLayers: 1, + ), + ), +]), +] \ No newline at end of file diff --git a/water/mod.ts b/water/mod.ts new file mode 100644 index 0000000..c9b8b14 --- /dev/null +++ b/water/mod.ts @@ -0,0 +1,558 @@ +import { gmath, makeNoise2D } from "../deps.ts"; +import { Framework } from "../framework.ts"; +import { + createBufferInit, + Dimensions, + OPENGL_TO_WGPU_MATRIX, +} from "../utils.ts"; +import { + HexTerrainMesh, + HexWaterMesh, + TERRAIN_VERTEX_ATTRIBUTES_SIZE, + WATER_VERTEX_ATTRIBUTES_SIZE, +} from "./point_gen.ts"; + +const SIZE = 29; +const CAMERA = new gmath.Vector3(-200.0, 70.0, 200.0); + +interface Matrices { + view: gmath.Matrix4; + flippedView: gmath.Matrix4; + projection: gmath.Matrix4; +} + +function generateMatrices(aspectRatio: number): Matrices { + const projection = new gmath.PerspectiveFov( + new gmath.Deg(45), + aspectRatio, + 10, + 400, + ).toMatrix4(); + const regView = gmath.Matrix4.lookAtRh( + CAMERA, + gmath.Vector3.zero(), + gmath.Vector3.up(), + ); + const scale = gmath.Matrix4.from( + 8, 0, 0, 0, + 0, 1.5, 0, 0, + 0, 0, 8, 0, + 0, 0, 0, 1, + ); + + const flippedView = gmath.Matrix4.lookAtRh( + new gmath.Vector3(CAMERA.x, -CAMERA.y, CAMERA.z), + gmath.Vector3.zero(), + gmath.Vector3.up(), + ); + + return { + view: regView.mul(scale), + flippedView, + projection: OPENGL_TO_WGPU_MATRIX.mul(projection), + }; +} + +interface Uniforms { + terrainNormal: Uint8Array; + terrainFlipped: Uint8Array; + water: Uint8Array; +} + +const TERRAIN_SIZE = 20 * 4; +const WATER_SIZE = 40 * 4; + +function generateUniforms(width: number, height: number): Uniforms { + const { view, flippedView, projection } = generateMatrices(width / height); + + const terrainNormal = new Float32Array(TERRAIN_SIZE / 4); + terrainNormal.set(projection.mul(view).toFloat32Array()); + terrainNormal.set([0, 0, 0, 0], 16); + + const terrainFlipped = new Float32Array(TERRAIN_SIZE / 4); + terrainFlipped.set(projection.mul(flippedView).toFloat32Array()); + terrainFlipped.set([0, 1, 0, 0], 16); + + const water = new Float32Array(WATER_SIZE / 4); + water.set(view.toFloat32Array()); + water.set(projection.toFloat32Array(), 16); + water.set([0.0, 1.0, SIZE * 2.0, width], 32); + water.set([height, 0.0, 0.0, 0.0], 36); + + return { + terrainNormal: new Uint8Array(terrainNormal.buffer), + terrainFlipped: new Uint8Array(terrainFlipped.buffer), + water: new Uint8Array(water.buffer), + }; +} + +function initializeResources( + dimensions: Dimensions, + device: GPUDevice, + waterUniforms: GPUBuffer, + terrainNormalUniforms: GPUBuffer, + terrainFlippedUniforms: GPUBuffer, + waterBindGroupLayout: GPUBindGroupLayout, +): [GPUTextureView, GPUTextureView, GPUBindGroup] { + const { + terrainNormal, + terrainFlipped, + water, + } = generateUniforms(dimensions.width, dimensions.height); + + device.queue.writeBuffer(terrainNormalUniforms, 0, terrainNormal); + device.queue.writeBuffer(terrainFlippedUniforms, 0, terrainFlipped); + device.queue.writeBuffer(waterUniforms, 0, water); + + const reflectionTexture = device.createTexture({ + label: "Reflection Render Texture", + size: dimensions, + format: "rgba8unorm-srgb", + usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | + GPUTextureUsage.RENDER_ATTACHMENT, + }); + + const drawDepthBuffer = device.createTexture({ + label: "Depth Buffer", + size: dimensions, + format: "depth32float", + usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | + GPUTextureUsage.RENDER_ATTACHMENT, + }); + + const sampler = device.createSampler({ + label: "Texture Sampler", + minFilter: "linear", + lodMaxClamp: 340282350000000000000000000000000000000, + maxAnisotropy: 0, + }); + + const depthView = drawDepthBuffer.createView(); + + const waterBindGroup = device.createBindGroup({ + label: "Water Bind Group", + layout: waterBindGroupLayout, + entries: [ + { + binding: 0, + resource: { + buffer: waterUniforms, + }, + }, + { + binding: 1, + resource: reflectionTexture.createView(), + }, + { + binding: 2, + resource: depthView, + }, + { + binding: 3, + resource: sampler, + }, + ], + }); + + return [reflectionTexture.createView(), depthView, waterBindGroup]; +} + +class Water extends Framework { + waterVertexBuf!: GPUBuffer; + waterVertexCount!: number; + waterBindGroupLayout!: GPUBindGroupLayout; + waterBindGroup!: GPUBindGroup; + waterUniformBuf!: GPUBuffer; + waterPipeline!: GPURenderPipeline; + + terrainVertexBuf!: GPUBuffer; + terrainVertexCount!: number; + terrainNormalBindGroup!: GPUBindGroup; + + terrainFlippedBindGroup!: GPUBindGroup; + terrainNormalUniformBuf!: GPUBuffer; + + terrainFlippedUniformBuf!: GPUBuffer; + terrainPipeline!: GPURenderPipeline; + + reflectView!: GPUTextureView; + depthBuffer!: GPUTextureView; + + async init(): Promise { + const waterVertices = new HexWaterMesh(SIZE).generatePoints(); + this.waterVertexCount = waterVertices.length; + const waterVerticesBuf = new Int8Array( + waterVertices.map((buf) => [...buf]).flat(), + ); + + const terrainNoise = makeNoise2D(0); + const terrain = new HexTerrainMesh(SIZE, (point) => { + const noise = terrainNoise(point[0] / 5, point[1] / 5) + 0.1; + const y = noise * 22; + function mulArr( + arr: [number, number, number, number], + by: number, + ): [number, number, number, number] { + arr[0] = Math.min(arr[0] * by, 255); + arr[1] = Math.min(arr[1] * by, 255); + arr[2] = Math.min(arr[2] * by, 255); + return arr; + } + + const DARK_SAND = [235, 175, 71, 255]; + const SAND = [217, 191, 76, 255]; + const GRASS = [122, 170, 19, 255]; + const SNOW = [175, 224, 237, 255]; + + const random = Math.random() * 0.2 + 0.9; + + let color: number[]; + if (y <= 0.0) { + color = DARK_SAND; + } else if (y <= 0.8) { + color = SAND; + } else if (y <= 10.0) { + color = GRASS; + } else { + color = SNOW; + } + + return { + position: new gmath.Vector3(point[0], y, point[1]), + color: mulArr(color as [number, number, number, number], random), + }; + }); + const terrainVertices = terrain.makeBufferData(); + this.terrainVertexCount = terrainVertices.length; + const terrainVerticesBuf = new Uint8Array( + terrainVertices.map((buf) => [...buf]).flat(), + ); + + this.waterVertexBuf = createBufferInit(this.device, { + label: "Water vertices", + contents: waterVerticesBuf.buffer, + usage: GPUBufferUsage.VERTEX, + }); + this.terrainVertexBuf = createBufferInit(this.device, { + label: "Terrain vertices", + contents: terrainVerticesBuf.buffer, + usage: GPUBufferUsage.VERTEX, + }); + + this.waterBindGroupLayout = this.device.createBindGroupLayout({ + label: "Water Bind Group Layout", + entries: [ + { + binding: 0, + visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, + buffer: { + minBindingSize: WATER_SIZE, + }, + }, + { + binding: 1, + visibility: GPUShaderStage.FRAGMENT, + texture: {}, + }, + { + binding: 2, + visibility: GPUShaderStage.FRAGMENT, + texture: {}, + }, + { + binding: 3, + visibility: GPUShaderStage.FRAGMENT, + sampler: {}, + }, + ], + }); + const terrainBindGroupLayout = this.device.createBindGroupLayout({ + label: "Terrain Bind Group Layout", + entries: [ + { + binding: 0, + visibility: GPUShaderStage.VERTEX, + buffer: { + minBindingSize: TERRAIN_SIZE, + }, + }, + ], + }); + + const waterPipelineLayout = this.device.createPipelineLayout({ + label: "water", + bindGroupLayouts: [this.waterBindGroupLayout], + }); + const terrainPipelineLayout = this.device.createPipelineLayout({ + label: "terrain", + bindGroupLayouts: [terrainBindGroupLayout], + }); + + this.waterUniformBuf = this.device.createBuffer({ + label: "Water Uniforms", + size: WATER_SIZE, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, + }); + this.terrainNormalUniformBuf = this.device.createBuffer({ + label: "Normal Terrain Uniforms", + size: TERRAIN_SIZE, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, + }); + this.terrainFlippedUniformBuf = this.device.createBuffer({ + label: "Flipped Terrain Uniforms", + size: TERRAIN_SIZE, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, + }); + + const [reflectView, depthBuffer, waterBindGroup] = initializeResources( + this.dimensions, + this.device, + this.waterUniformBuf, + this.terrainNormalUniformBuf, + this.terrainFlippedUniformBuf, + this.waterBindGroupLayout, + ); + this.reflectView = reflectView; + this.depthBuffer = depthBuffer; + this.waterBindGroup = waterBindGroup; + + this.terrainNormalBindGroup = this.device.createBindGroup({ + label: "Terrain Normal Bind Group", + layout: terrainBindGroupLayout, + entries: [ + { + binding: 0, + resource: { + buffer: this.terrainNormalUniformBuf, + }, + }, + ], + }); + this.terrainFlippedBindGroup = this.device.createBindGroup({ + label: "Terrain Flipped Bind Group", + layout: terrainBindGroupLayout, + entries: [ + { + binding: 0, + resource: { + buffer: this.terrainFlippedUniformBuf, + }, + }, + ], + }); + + const terrainModule = this.device.createShaderModule({ + label: "terrain", + code: Deno.readTextFileSync(new URL("./terrain.wgsl", import.meta.url)), + }); + const waterModule = this.device.createShaderModule({ + label: "water", + code: Deno.readTextFileSync(new URL("./water.wgsl", import.meta.url)), + }); + + this.waterPipeline = this.device.createRenderPipeline({ + label: "water", + layout: waterPipelineLayout, + vertex: { + module: waterModule, + entryPoint: "vs_main", + buffers: [ + { + arrayStride: WATER_VERTEX_ATTRIBUTES_SIZE, + attributes: [ + { + format: "sint16x2", + offset: 0, + shaderLocation: 0, + }, + { + format: "sint8x4", + offset: 4, + shaderLocation: 1, + }, + ], + }, + ], + }, + fragment: { + module: waterModule, + entryPoint: "fs_main", + targets: [ + { + format: "rgba8unorm-srgb", + blend: { + color: { + srcFactor: "src-alpha", + dstFactor: "one-minus-src-alpha", + }, + alpha: { + operation: "max", + dstFactor: "one", + }, + }, + }, + ], + }, + primitive: { + frontFace: "cw", + }, + depthStencil: { + format: "depth32float", + depthCompare: "less", + stencilReadMask: 0, + stencilWriteMask: 0, + }, + }); + this.terrainPipeline = this.device.createRenderPipeline({ + label: "terrain", + layout: terrainPipelineLayout, + vertex: { + module: terrainModule, + entryPoint: "vs_main", + buffers: [ + { + arrayStride: TERRAIN_VERTEX_ATTRIBUTES_SIZE, + attributes: [ + { + format: "float32x3", + offset: 0, + shaderLocation: 0, + }, + { + format: "float32x3", + offset: 12, + shaderLocation: 1, + }, + { + format: "unorm8x4", + offset: 24, + shaderLocation: 2, + }, + ], + }, + ], + }, + fragment: { + module: terrainModule, + entryPoint: "fs_main", + targets: [{ + format: "rgba8unorm-srgb", + }], + }, + primitive: { + cullMode: "front", + }, + depthStencil: { + format: "depth32float", + depthWriteEnabled: true, + depthCompare: "less", + stencilReadMask: 0, + stencilWriteMask: 0, + }, + }); + } + + render(encoder: GPUCommandEncoder, view: GPUTextureView) { + const backColor = { + r: 161.0 / 255.0, + g: 246.0 / 255.0, + b: 255.0 / 255.0, + a: 1.0, + }; + const waterSin = Math.sin(0); + const waterCos = Math.cos(0); + + this.device.queue.writeBuffer( + this.waterUniformBuf, + 128, + new Float32Array([waterSin, waterCos]), + ); + + { + const renderPass = encoder.beginRenderPass({ + colorAttachments: [ + { + view: this.reflectView, + loadValue: backColor, + storeOp: "store", + }, + ], + depthStencilAttachment: { + view: this.depthBuffer, + depthLoadValue: 1, + depthStoreOp: "store", + stencilLoadValue: "load", + stencilStoreOp: "store", + stencilReadOnly: true, + }, + }); + renderPass.setPipeline(this.terrainPipeline); + renderPass.setBindGroup(0, this.terrainFlippedBindGroup); + renderPass.setVertexBuffer(0, this.terrainVertexBuf); + renderPass.draw(this.terrainVertexCount); + renderPass.endPass(); + } + + { + const renderPass = encoder.beginRenderPass({ + colorAttachments: [ + { + view, + loadValue: backColor, + storeOp: "store", + }, + ], + depthStencilAttachment: { + view: this.depthBuffer, + depthLoadValue: 1, + depthStoreOp: "store", + stencilLoadValue: "load", + stencilStoreOp: "store", + stencilReadOnly: true, + }, + }); + renderPass.setPipeline(this.terrainPipeline); + renderPass.setBindGroup(0, this.terrainNormalBindGroup); + renderPass.setVertexBuffer(0, this.terrainVertexBuf); + renderPass.draw(this.terrainVertexCount); + renderPass.endPass(); + } + + { + const renderPass = encoder.beginRenderPass({ + colorAttachments: [ + { + view, + loadValue: "load", + storeOp: "store", + }, + ], + depthStencilAttachment: { + view: this.depthBuffer, + depthLoadValue: "load", + depthStoreOp: "store", + stencilLoadValue: "load", + stencilStoreOp: "store", + depthReadOnly: true, + stencilReadOnly: true, + }, + }); + renderPass.setPipeline(this.waterPipeline); + renderPass.setBindGroup(0, this.waterBindGroup); + renderPass.setVertexBuffer(0, this.waterVertexBuf); + renderPass.draw(this.waterVertexCount); + renderPass.endPass(); + } + } +} +const device = await Water.getDevice(); +device.pushErrorScope("validation"); +const water = new Water( + { + width: 1600, + height: 1200, + }, + device, +); +await water.renderPng(); +console.log(await device.popErrorScope()); diff --git a/water/output.png b/water/output.png new file mode 100644 index 0000000..23c8850 Binary files /dev/null and b/water/output.png differ diff --git a/water/point_gen.ts b/water/point_gen.ts new file mode 100644 index 0000000..48c6362 --- /dev/null +++ b/water/point_gen.ts @@ -0,0 +1,255 @@ +import { gmath, slidingWindows } from "../deps.ts"; + +const A = Math.SQRT1_2; +const SQRT_3 = Math.sqrt(3); +const B = SQRT_3 * A; +const S45 = Math.SQRT1_2; +const C45 = S45; + +function surroundingHexagonalPoints( + x: number, + y: number, +): [ + [number, number], + [number, number], + [number, number], + [number, number], + [number, number], + [number, number], +] { + return [ + [x, y - 1], + [x + 1, y - 1], + [x + 1, y], + [x, y + 1], + [x - 1, y + 1], + [x - 1, y], + ]; +} + +function surroundingPointValuesIter( + map: Map, + x: number, + y: number, + forEach: (val: [T, T]) => void, +) { + const points = surroundingHexagonalPoints(x, y); + const newPoints = [ + points[0], + points[1], + points[2], + points[3], + points[4], + points[5], + points[0], + ]; + + (slidingWindows(newPoints, 2) + .map((x) => [map.get(JSON.stringify(x[0])), map.get(JSON.stringify(x[1]))] as const) + .filter(([a, b]) => a !== undefined && b !== undefined) as [T, T][]) + .forEach(forEach); +} + +function calculateNormal( + a: gmath.Vector3, + b: gmath.Vector3, + c: gmath.Vector3, +): gmath.Vector3 { + return b.sub(a).normal().cross(c.sub(a).normal()).normal(); +} + +function qGivenR(radius: number): number { + return ((Math.floor(Math.floor(((4 * radius) / SQRT_3) + 1) / 2) * 2) + 1); +} + +export interface TerrainVertex { + position: gmath.Vector3; + color: [number, number, number, number]; +} + +export const TERRAIN_VERTEX_ATTRIBUTES_SIZE = 28; + +export class HexTerrainMesh { + vertices: Map; + halfSize: number; + + constructor( + radius: number, + genVertex: (val: [number, number]) => TerrainVertex, + ) { + const width = qGivenR(radius); + const halfWidth = Math.floor(width / 2); + const map = new Map(); + let max = Number.MIN_VALUE; + for (let i = -halfWidth; i <= halfWidth; i++) { + const xO = i; + for (let j = -halfWidth; j <= halfWidth; j++) { + const yO = j; + const x = A * (xO * C45 - yO * S45); + const z = B * (xO * S45 + yO * C45); + if (Math.hypot(x, z) < radius) { + const vertex = genVertex([x, z]); + if (vertex.position.y > max) { + max = vertex.position.y; + } + map.set(JSON.stringify([i, j]), vertex); + } + } + } + this.vertices = map; + this.halfSize = halfWidth; + } + + makeBufferData(): Uint8Array[] { + let vertices: Uint8Array[] = []; + function middle( + p1: TerrainVertex, + p2: TerrainVertex, + p3: TerrainVertex, + ): gmath.Vector3 { + return new gmath.Vector3( + (p1.position.x + p2.position.x + p3.position.x) / 3.0, + (p1.position.y + p2.position.y + p3.position.y) / 3.0, + (p1.position.z + p2.position.z + p3.position.z) / 3.0, + ); + } + function half(p1: TerrainVertex, p2: TerrainVertex): gmath.Vector3 { + return new gmath.Vector3( + (p1.position.x + p2.position.x) / 2.0, + (p1.position.y + p2.position.y) / 2.0, + (p1.position.z + p2.position.z) / 2.0, + ); + } + + function pushTriangle( + p1: TerrainVertex, + p2: TerrainVertex, + p3: TerrainVertex, + c: [number, number, number, number], + ) { + const m = middle(p1, p2, p3); + const ap = half(p1, p3); + const bp = half(p2, p3); + const p = p3.position; + const n1 = calculateNormal(ap, m, p); + const n2 = calculateNormal(m, bp, p); + + vertices = vertices.concat( + [[ap, n1], [m, n1], [p, n1], [m, n2], [bp, n2], [p, n2]].map(( + [pos, normal], + ) => { + const u8 = new Uint8Array(TERRAIN_VERTEX_ATTRIBUTES_SIZE); + const f32 = new Float32Array(u8.buffer); + f32.set(pos.toArray()); + f32.set(normal.toArray(), 3); + u8.set(c, 24); + + return u8; + }), + ); + } + + for (let i = -this.halfSize; i <= this.halfSize; i++) { + for (let j = -this.halfSize; j <= this.halfSize; j++) { + const p = this.vertices.get(JSON.stringify([i, j])); + if (p) { + surroundingPointValuesIter( + this.vertices, + i, + j, + ([a, b]) => pushTriangle(a, b, p, p.color), + ); + } + } + } + + return vertices; + } +} + +export const WATER_VERTEX_ATTRIBUTES_SIZE = 8; + +export class HexWaterMesh { + vertices: Map; + halfSize: number; + + constructor(radius: number) { + const width = qGivenR(radius); + const halfWidth = Math.floor(width / 2); + const map = new Map(); + + for (let i = -halfWidth; i <= halfWidth; i++) { + const xO = i; + for (let j = -halfWidth; j <= halfWidth; j++) { + const yO = j; + const x = A * (xO * C45 - yO * S45); + const z = B * (xO * S45 + yO * C45); + if (Math.hypot(x, z) < radius) { + const x2 = Math.round(x * 2.0); + const z2 = Math.round((z / B) * Math.sqrt(2)); + map.set(JSON.stringify([i, j]), [x2, z2]); + } + } + } + + this.vertices = map; + this.halfSize = halfWidth; + } + + generatePoints(): Int8Array[] { + let vertices: Int8Array[] = []; + + function calculateDifferences( + a: [number, number], + b: [number, number], + c: [number, number], + ): [number, number, number, number] { + return [ + b[0] - a[0], + b[1] - a[1], + c[0] - a[0], + c[1] - a[1], + ]; + } + + function pushTriangle( + a: [number, number], + b: [number, number], + c: [number, number], + ) { + const bc = calculateDifferences(a, b, c); + const ca = calculateDifferences(b, c, a); + const ab = calculateDifferences(c, a, b); + + vertices = vertices.concat( + [[a, bc], [b, ca], [c, ab]].map(([pos, offsets]) => { + const i8 = new Int8Array(WATER_VERTEX_ATTRIBUTES_SIZE); + const i16 = new Int16Array(i8.buffer); + i16.set(pos); + + i8.set(offsets, 4); + + return i8; + }), + ); + } + + for (let i = -this.halfSize; i <= this.halfSize; i++) { + for (let j = -this.halfSize; j <= this.halfSize; j++) { + if ((i - j) % 3 === 0) { + const p = this.vertices.get(JSON.stringify([i, j])); + if (p !== undefined) { + surroundingPointValuesIter( + this.vertices, + i, + j, + ([a, b]) => pushTriangle(a, b, p), + ); + } + } + } + } + + return vertices; + } +} diff --git a/water/rust_trace/data1.bin b/water/rust_trace/data1.bin new file mode 100644 index 0000000..def1569 Binary files /dev/null and b/water/rust_trace/data1.bin differ diff --git a/water/rust_trace/data10.bin b/water/rust_trace/data10.bin new file mode 100644 index 0000000..2182d56 --- /dev/null +++ b/water/rust_trace/data10.bin @@ -0,0 +1 @@ +ÝÖ£;.ÿ? \ No newline at end of file diff --git a/water/rust_trace/data11.bin b/water/rust_trace/data11.bin new file mode 100644 index 0000000..709c44e --- /dev/null +++ b/water/rust_trace/data11.bin @@ -0,0 +1 @@ +¤sÚ;‹þ? \ No newline at end of file diff --git a/water/rust_trace/data12.bin b/water/rust_trace/data12.bin new file mode 100644 index 0000000..709ef95 --- /dev/null +++ b/water/rust_trace/data12.bin @@ -0,0 +1 @@ +!ˆ<¹ý? \ No newline at end of file diff --git a/water/rust_trace/data13.bin b/water/rust_trace/data13.bin new file mode 100644 index 0000000..c675683 --- /dev/null +++ b/water/rust_trace/data13.bin @@ -0,0 +1 @@ +WÖ#<¹ü? \ No newline at end of file diff --git a/water/rust_trace/data14.bin b/water/rust_trace/data14.bin new file mode 100644 index 0000000..f69e086 --- /dev/null +++ b/water/rust_trace/data14.bin @@ -0,0 +1 @@ +p$?<Šû? \ No newline at end of file diff --git a/water/rust_trace/data15.bin b/water/rust_trace/data15.bin new file mode 100644 index 0000000..daeefae --- /dev/null +++ b/water/rust_trace/data15.bin @@ -0,0 +1 @@ +frZ<-ú? \ No newline at end of file diff --git a/water/rust_trace/data16.bin b/water/rust_trace/data16.bin new file mode 100644 index 0000000..61c2833 --- /dev/null +++ b/water/rust_trace/data16.bin @@ -0,0 +1 @@ +3Àu<¡ø? \ No newline at end of file diff --git a/water/rust_trace/data17.bin b/water/rust_trace/data17.bin new file mode 100644 index 0000000..1aa9ca9 --- /dev/null +++ b/water/rust_trace/data17.bin @@ -0,0 +1 @@ +놈<æö? \ No newline at end of file diff --git a/water/rust_trace/data18.bin b/water/rust_trace/data18.bin new file mode 100644 index 0000000..ced7e52 --- /dev/null +++ b/water/rust_trace/data18.bin @@ -0,0 +1 @@ +¢-–<ýô? \ No newline at end of file diff --git a/water/rust_trace/data19.bin b/water/rust_trace/data19.bin new file mode 100644 index 0000000..02ed551 --- /dev/null +++ b/water/rust_trace/data19.bin @@ -0,0 +1 @@ +>Ô£<åò? \ No newline at end of file diff --git a/water/rust_trace/data2.bin b/water/rust_trace/data2.bin new file mode 100644 index 0000000..cae9038 Binary files /dev/null and b/water/rust_trace/data2.bin differ diff --git a/water/rust_trace/data20.bin b/water/rust_trace/data20.bin new file mode 100644 index 0000000..c8ebe3b --- /dev/null +++ b/water/rust_trace/data20.bin @@ -0,0 +1 @@ +½z±<žð? \ No newline at end of file diff --git a/water/rust_trace/data21.bin b/water/rust_trace/data21.bin new file mode 100644 index 0000000..ce6f4c6 --- /dev/null +++ b/water/rust_trace/data21.bin @@ -0,0 +1 @@ +!¿<)î? \ No newline at end of file diff --git a/water/rust_trace/data22.bin b/water/rust_trace/data22.bin new file mode 100644 index 0000000..23b87bb --- /dev/null +++ b/water/rust_trace/data22.bin @@ -0,0 +1 @@ +WÇÌ<…ë? \ No newline at end of file diff --git a/water/rust_trace/data23.bin b/water/rust_trace/data23.bin new file mode 100644 index 0000000..2884359 --- /dev/null +++ b/water/rust_trace/data23.bin @@ -0,0 +1 @@ +mmÚ<³è? \ No newline at end of file diff --git a/water/rust_trace/data24.bin b/water/rust_trace/data24.bin new file mode 100644 index 0000000..fd5b418 --- /dev/null +++ b/water/rust_trace/data24.bin @@ -0,0 +1 @@ +\è<²å? \ No newline at end of file diff --git a/water/rust_trace/data25.bin b/water/rust_trace/data25.bin new file mode 100644 index 0000000..d1e0043 --- /dev/null +++ b/water/rust_trace/data25.bin @@ -0,0 +1 @@ +¹õ<ƒâ? \ No newline at end of file diff --git a/water/rust_trace/data26.bin b/water/rust_trace/data26.bin new file mode 100644 index 0000000..f603fdd --- /dev/null +++ b/water/rust_trace/data26.bin @@ -0,0 +1 @@ +[¯=%ß? \ No newline at end of file diff --git a/water/rust_trace/data3.bin b/water/rust_trace/data3.bin new file mode 100644 index 0000000..a51ee3f Binary files /dev/null and b/water/rust_trace/data3.bin differ diff --git a/water/rust_trace/data4.bin b/water/rust_trace/data4.bin new file mode 100644 index 0000000..5d65697 Binary files /dev/null and b/water/rust_trace/data4.bin differ diff --git a/water/rust_trace/data5.bin b/water/rust_trace/data5.bin new file mode 100644 index 0000000..1093f2f Binary files /dev/null and b/water/rust_trace/data5.bin differ diff --git a/water/rust_trace/data6.wgsl b/water/rust_trace/data6.wgsl new file mode 100644 index 0000000..9f7e256 --- /dev/null +++ b/water/rust_trace/data6.wgsl @@ -0,0 +1,48 @@ +struct Uniforms { + projection_view: mat4x4; + clipping_plane: vec4; +}; + +[[group(0), binding(0)]] +var uniforms: Uniforms; + +let light: vec3 = vec3(150.0, 70.0, 0.0); +let light_colour: vec3 = vec3(1.0, 0.98, 0.82); +let ambient: f32 = 0.2; + +struct VertexOutput { + [[builtin(position)]] position: vec4; + [[location(0)]] colour: vec4; + // Comment this out if using user-clipping planes: + [[location(1)]] clip_dist: f32; +}; + +[[stage(vertex)]] +fn vs_main( + [[location(0)]] position: vec3, + [[location(1)]] normal: vec3, + [[location(2)]] colour: vec4, +) -> VertexOutput { + var out: VertexOutput; + out.position = uniforms.projection_view * vec4(position, 1.0); + + // https://www.desmos.com/calculator/nqgyaf8uvo + let normalized_light_direction = normalize(position - light); + let brightness_diffuse = clamp(dot(normalized_light_direction, normal), 0.2, 1.0); + + out.colour = vec4(max((brightness_diffuse + ambient) * light_colour * colour.rgb, vec3(0.0, 0.0, 0.0)), colour.a); + out.clip_dist = dot(vec4(position, 1.0), uniforms.clipping_plane); + return out; +} + +[[stage(fragment), early_depth_test]] +fn fs_main( + in: VertexOutput, +) -> [[location(0)]] vec4 { + // Comment this out if using user-clipping planes: + if(in.clip_dist < 0.0) { + discard; + } + + return vec4(in.colour.xyz, 1.0); +} diff --git a/water/rust_trace/data7.wgsl b/water/rust_trace/data7.wgsl new file mode 100644 index 0000000..b7a4701 --- /dev/null +++ b/water/rust_trace/data7.wgsl @@ -0,0 +1,251 @@ +struct Uniforms { + view: mat4x4; + projection: mat4x4; + time_size_width: vec4; + viewport_height: f32; +}; +[[group(0), binding(0)]] var uniforms: Uniforms; + +let light_point: vec3 = vec3(150.0, 70.0, 0.0); +let light_colour: vec3 = vec3(1.0, 0.98, 0.82); +let one: vec4 = vec4(1.0, 1.0, 1.0, 1.0); + +let Y_SCL: f32 = 0.86602540378443864676372317075294; +let CURVE_BIAS: f32 = -0.1; +let INV_1_CURVE_BIAS: f32 = 1.11111111111; //1.0 / (1.0 + CURVE_BIAS); + +// +// The following code to calculate simplex 3D +// is from https://github.com/ashima/webgl-noise +// +// Simplex 3D Noise +// by Ian McEwan, Ashima Arts. +// +fn permute(x: vec4) -> vec4 { + var temp: vec4 = 289.0 * one; + return modf(((x*34.0) + one) * x, &temp); +} + +fn taylorInvSqrt(r: vec4) -> vec4 { + return 1.79284291400159 * one - 0.85373472095314 * r; +} + +fn snoise(v: vec3) -> f32 { + let C = vec2(1.0/6.0, 1.0/3.0); + let D = vec4(0.0, 0.5, 1.0, 2.0); + + // First corner + //TODO: use the splat operations when available + let vCy = dot(v, C.yyy); + var i: vec3 = floor(v + vec3(vCy, vCy, vCy)); + let iCx = dot(i, C.xxx); + let x0 = v - i + vec3(iCx, iCx, iCx); + + // Other corners + let g = step(x0.yzx, x0.xyz); + let l = (vec3(1.0, 1.0, 1.0) - g).zxy; + let i1 = min(g, l); + let i2 = max(g, l); + + // x0 = x0 - 0.0 + 0.0 * C.xxx; + // x1 = x0 - i1 + 1.0 * C.xxx; + // x2 = x0 - i2 + 2.0 * C.xxx; + // x3 = x0 - 1.0 + 3.0 * C.xxx; + let x1 = x0 - i1 + C.xxx; + let x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y + let x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y + + // Permutations + var temp: vec3 = 289.0 * one.xyz; + i = modf(i, &temp); + let p = permute( + permute( + permute(i.zzzz + vec4(0.0, i1.z, i2.z, 1.0)) + + i.yyyy + vec4(0.0, i1.y, i2.y, 1.0)) + + i.xxxx + vec4(0.0, i1.x, i2.x, 1.0)); + + // Gradients: 7x7 points over a square, mapped onto an octahedron. + // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294) + let n_ = 0.142857142857;// 1.0/7.0 + let ns = n_ * D.wyz - D.xzx; + + let j = p - 49.0 * floor(p * ns.z * ns.z);// mod(p,7*7) + + let x_ = floor(j * ns.z); + let y_ = floor(j - 7.0 * x_);// mod(j,N) + + var x: vec4 = x_ *ns.x + ns.yyyy; + var y: vec4 = y_ *ns.x + ns.yyyy; + let h = one - abs(x) - abs(y); + + let b0 = vec4(x.xy, y.xy); + let b1 = vec4(x.zw, y.zw); + + //vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - one; + //vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - one; + let s0 = floor(b0)*2.0 + one; + let s1 = floor(b1)*2.0 + one; + let sh = -step(h, 0.0 * one); + + let a0 = b0.xzyw + s0.xzyw*sh.xxyy; + let a1 = b1.xzyw + s1.xzyw*sh.zzww; + + var p0: vec3 = vec3(a0.xy, h.x); + var p1: vec3 = vec3(a0.zw, h.y); + var p2: vec3 = vec3(a1.xy, h.z); + var p3: vec3 = vec3(a1.zw, h.w); + + //Normalise gradients + let norm = taylorInvSqrt(vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3))); + p0 = p0 * norm.x; + p1 = p1 * norm.y; + p2 = p2 * norm.z; + p3 = p3 * norm.w; + + // Mix final noise value + var m: vec4 = max(0.6 * one - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0 * one); + m = m * m; + return 9.0 * dot(m*m, vec4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3))); +} + +// End of 3D simplex code. + +fn apply_distortion(pos: vec3) -> vec3 { + var perlin_pos: vec3 = pos; + + //Do noise transformation to permit for smooth, + //continuous movement. + + //TODO: we should be able to name them `sin` and `cos`. + let sn = uniforms.time_size_width.x; + let cs = uniforms.time_size_width.y; + let size = uniforms.time_size_width.z; + + // Rotate 90 Z, Move Left Size / 2 + perlin_pos = vec3(perlin_pos.y - perlin_pos.x - size, perlin_pos.x, perlin_pos.z); + + let xcos = perlin_pos.x * cs; + let xsin = perlin_pos.x * sn; + let ycos = perlin_pos.y * cs; + let ysin = perlin_pos.y * sn; + let zcos = perlin_pos.z * cs; + let zsin = perlin_pos.z * sn; + + // Rotate Time Y + let perlin_pos_y = vec3(xcos + zsin, perlin_pos.y, -xsin + xcos); + + // Rotate Time Z + let perlin_pos_z = vec3(xcos - ysin, xsin + ycos, perlin_pos.x); + + // Rotate 90 Y + perlin_pos = vec3(perlin_pos.z - perlin_pos.x, perlin_pos.y, perlin_pos.x); + + // Rotate Time X + let perlin_pos_x = vec3(perlin_pos.x, ycos - zsin, ysin + zcos); + + // Sample at different places for x/y/z to get random-looking water. + return vec3( + //TODO: use splats + pos.x + snoise(perlin_pos_x + 2.0*one.xxx) * 0.4, + pos.y + snoise(perlin_pos_y - 2.0*one.xxx) * 1.8, + pos.z + snoise(perlin_pos_z) * 0.4 + ); +} + +// Multiply the input by the scale values. +fn make_position(original: vec2) -> vec4 { + let interpreted = vec3(original.x * 0.5, 0.0, original.y * Y_SCL); + return vec4(apply_distortion(interpreted), 1.0); +} + +// Create the normal, and apply the curve. Change the Curve Bias above. +fn make_normal(a: vec3, b: vec3, c: vec3) -> vec3 { + let norm = normalize(cross(b - c, a - c)); + let center = (a + b + c) * (1.0 / 3.0); //TODO: use splat + return (normalize(a - center) * CURVE_BIAS + norm) * INV_1_CURVE_BIAS; +} + +// Calculate the fresnel effect. +fn calc_fresnel(view: vec3, normal: vec3) -> f32 { + var refractive: f32 = abs(dot(view, normal)); + refractive = pow(refractive, 1.33333333333); + return refractive; +} + +// Calculate the specular lighting. +fn calc_specular(eye: vec3, normal: vec3, light: vec3) -> f32 { + let light_reflected = reflect(light, normal); + var specular: f32 = max(dot(eye, light_reflected), 0.0); + specular = pow(specular, 10.0); + return specular; +} + +struct VertexOutput { + [[builtin(position)]] position: vec4; + [[location(0)]] f_WaterScreenPos: vec2; + [[location(1)]] f_Fresnel: f32; + [[location(2)]] f_Light: vec3; +}; + +[[stage(vertex)]] +fn vs_main( + [[location(0)]] position: vec2, + [[location(1)]] offsets: vec4, +) -> VertexOutput { + let p_pos = vec2(position); + let b_pos = make_position(p_pos + vec2(offsets.xy)); + let c_pos = make_position(p_pos + vec2(offsets.zw)); + let a_pos = make_position(p_pos); + let original_pos = vec4(p_pos.x * 0.5, 0.0, p_pos.y * Y_SCL, 1.0); + + let vm = uniforms.view; + let transformed_pos = vm * a_pos; + //TODO: use vector splats for division + let water_pos = transformed_pos.xyz * (1.0 / transformed_pos.w); + let normal = make_normal((vm * a_pos).xyz, (vm * b_pos).xyz, (vm * c_pos).xyz); + let eye = normalize(-water_pos); + let transformed_light = vm * vec4(light_point, 1.0); + + var out: VertexOutput; + out.f_Light = light_colour * calc_specular(eye, normal, normalize(water_pos.xyz - (transformed_light.xyz * (1.0 / transformed_light.w)))); + out.f_Fresnel = calc_fresnel(eye, normal); + + let gridpos = uniforms.projection * vm * original_pos; + out.f_WaterScreenPos = (0.5 * gridpos.xy * (1.0 / gridpos.w)) + vec2(0.5, 0.5); + + out.position = uniforms.projection * transformed_pos; + return out; +} + + +let water_colour: vec3 = vec3(0.0, 0.46, 0.95); +let zNear: f32 = 10.0; +let zFar: f32 = 400.0; + +[[group(0), binding(1)]] var reflection: texture_2d; +[[group(0), binding(2)]] var terrain_depth_tex: texture_2d; +[[group(0), binding(3)]] var colour_sampler: sampler; + +fn to_linear_depth(depth: f32) -> f32 { + let z_n: f32 = 2.0 * depth - 1.0; + let z_e: f32 = 2.0 * zNear * zFar / (zFar + zNear - z_n * (zFar - zNear)); + return z_e; +} + +[[stage(fragment)]] +fn fs_main(in: VertexOutput) -> [[location(0)]] vec4 { + let reflection_colour = textureSample(reflection, colour_sampler, in.f_WaterScreenPos.xy).xyz; + + let pixel_depth = to_linear_depth(in.position.z); + let normalized_coords = in.position.xy / vec2(uniforms.time_size_width.w, uniforms.viewport_height); + let terrain_depth = to_linear_depth(textureSample(terrain_depth_tex, colour_sampler, normalized_coords).r); + + let dist = terrain_depth - pixel_depth; + let clamped = pow(smoothStep(0.0, 1.5, dist), 4.8); + + let final_colour = in.f_Light + reflection_colour; + let t = smoothStep(1.0, 5.0, dist) * 0.2; //TODO: splat for mix()? + let depth_colour = mix(final_colour, water_colour, vec3(t, t, t)); + + return vec4(depth_colour, clamped * (1.0 - in.f_Fresnel)); +} diff --git a/water/rust_trace/data8.bin b/water/rust_trace/data8.bin new file mode 100644 index 0000000..ab68a26 --- /dev/null +++ b/water/rust_trace/data8.bin @@ -0,0 +1 @@ +tÚ:éÿ? \ No newline at end of file diff --git a/water/rust_trace/data9.bin b/water/rust_trace/data9.bin new file mode 100644 index 0000000..2f9d531 --- /dev/null +++ b/water/rust_trace/data9.bin @@ -0,0 +1 @@ +ósZ;£ÿ? \ No newline at end of file diff --git a/water/rust_trace/trace.ron b/water/rust_trace/trace.ron new file mode 100644 index 0000000..269531d --- /dev/null +++ b/water/rust_trace/trace.ron @@ -0,0 +1,771 @@ +[ +Init( + desc: ( + label: None, + features: 0, + limits: ( + maxTextureDimension1d: 16384, + maxTextureDimension2d: 16384, + maxTextureDimension3d: 2048, + maxTextureArrayLayers: 256, + maxBindGroups: 4, + maxDynamicUniformBuffersPerPipelineLayout: 8, + maxDynamicStorageBuffersPerPipelineLayout: 0, + maxSampledTexturesPerShaderStage: 16, + maxSamplersPerShaderStage: 16, + maxStorageBuffersPerShaderStage: 0, + maxStorageTexturesPerShaderStage: 0, + maxUniformBuffersPerShaderStage: 11, + maxUniformBufferBindingSize: 16384, + maxStorageBufferBindingSize: 0, + maxVertexBuffers: 8, + maxVertexAttributes: 16, + maxVertexBufferArrayStride: 255, + maxPushConstantSize: 0, + minUniformBufferOffsetAlignment: 256, + minStorageBufferOffsetAlignment: 256, + maxInterStageShaderComponents: 60, + maxComputeWorkgroupStorageSize: 0, + maxComputeInvocationsPerWorkgroup: 0, + maxComputeWorkgroupSizeX: 0, + maxComputeWorkgroupSizeY: 0, + maxComputeWorkgroupSizeZ: 0, + maxComputeWorkgroupsPerDimension: 0, + ), + ), + backend: Metal, +), +ConfigureSurface(Id(0, 1, Empty), ( + usage: 16, + format: r#bgra8unorm-srgb, + width: 1600, + height: 1200, + present_mode: Mailbox, +)), +CreateBuffer(Id(0, 1, Metal), ( + label: Some("Water vertices"), + size: 141840, + usage: 40, + mapped_at_creation: false, +)), +WriteBuffer( + id: Id(0, 1, Metal), + data: "data1.bin", + range: ( + start: 0, + end: 141840, + ), + queued: true, +), +CreateBuffer(Id(1, 1, Metal), ( + label: Some("Terrain vertices"), + size: 2978640, + usage: 40, + mapped_at_creation: false, +)), +WriteBuffer( + id: Id(1, 1, Metal), + data: "data2.bin", + range: ( + start: 0, + end: 2978640, + ), + queued: true, +), +CreateBindGroupLayout(Id(0, 1, Metal), ( + label: Some("Water Bind Group Layout"), + entries: [ + ( + binding: 0, + visibility: 3, + ty: Buffer( + ty: Uniform, + has_dynamic_offset: false, + min_binding_size: Some(160), + ), + count: None, + ), + ( + binding: 1, + visibility: 2, + ty: Texture( + sample_type: Float( + filterable: true, + ), + view_dimension: r#2d, + multisampled: false, + ), + count: None, + ), + ( + binding: 2, + visibility: 2, + ty: Texture( + sample_type: Float( + filterable: true, + ), + view_dimension: r#2d, + multisampled: false, + ), + count: None, + ), + ( + binding: 3, + visibility: 2, + ty: Sampler(filtering), + count: None, + ), + ], +)), +CreateBindGroupLayout(Id(1, 1, Metal), ( + label: Some("Terrain Bind Group Layout"), + entries: [ + ( + binding: 0, + visibility: 1, + ty: Buffer( + ty: Uniform, + has_dynamic_offset: false, + min_binding_size: Some(80), + ), + count: None, + ), + ], +)), +CreatePipelineLayout(Id(0, 1, Metal), ( + label: Some("water"), + bind_group_layouts: [ + Id(0, 1, Metal), + ], + push_constant_ranges: [], +)), +CreatePipelineLayout(Id(1, 1, Metal), ( + label: Some("terrain"), + bind_group_layouts: [ + Id(1, 1, Metal), + ], + push_constant_ranges: [], +)), +CreateBuffer(Id(2, 1, Metal), ( + label: Some("Water Uniforms"), + size: 160, + usage: 72, + mapped_at_creation: false, +)), +CreateBuffer(Id(3, 1, Metal), ( + label: Some("Normal Terrain Uniforms"), + size: 80, + usage: 72, + mapped_at_creation: false, +)), +CreateBuffer(Id(4, 1, Metal), ( + label: Some("Flipped Terrain Uniforms"), + size: 80, + usage: 72, + mapped_at_creation: false, +)), +WriteBuffer( + id: Id(3, 1, Metal), + data: "data3.bin", + range: ( + start: 0, + end: 80, + ), + queued: true, +), +WriteBuffer( + id: Id(4, 1, Metal), + data: "data4.bin", + range: ( + start: 0, + end: 80, + ), + queued: true, +), +WriteBuffer( + id: Id(2, 1, Metal), + data: "data5.bin", + range: ( + start: 0, + end: 160, + ), + queued: true, +), +CreateTexture(Id(0, 1, Metal), ( + label: Some("Reflection Render Texture"), + size: ( + width: 1600, + height: 1200, + depthOrArrayLayers: 1, + ), + mip_level_count: 1, + sample_count: 1, + dimension: r#2d, + format: r#bgra8unorm-srgb, + usage: 22, +)), +CreateTexture(Id(1, 1, Metal), ( + label: Some("Depth Buffer"), + size: ( + width: 1600, + height: 1200, + depthOrArrayLayers: 1, + ), + mip_level_count: 1, + sample_count: 1, + dimension: r#2d, + format: depth32float, + usage: 22, +)), +CreateSampler(Id(0, 1, Metal), ( + label: Some("Texture Sampler"), + address_modes: (r#clamp-to-edge, r#clamp-to-edge, r#clamp-to-edge), + mag_filter: nearest, + min_filter: linear, + mipmap_filter: nearest, + lod_min_clamp: 0, + lod_max_clamp: 340282350000000000000000000000000000000, + compare: None, + anisotropy_clamp: None, + border_color: None, +)), +CreateTextureView( + id: Id(0, 1, Metal), + parent_id: Id(1, 1, Metal), + desc: ( + label: None, + format: None, + dimension: None, + range: ( + aspect: all, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: None, + ), + ), +), +CreateTextureView( + id: Id(1, 1, Metal), + parent_id: Id(0, 1, Metal), + desc: ( + label: None, + format: None, + dimension: None, + range: ( + aspect: all, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: None, + ), + ), +), +CreateBindGroup(Id(0, 1, Metal), ( + label: Some("Water Bind Group"), + layout: Id(0, 1, Metal), + entries: [ + ( + binding: 0, + resource: Buffer(( + buffer_id: Id(2, 1, Metal), + offset: 0, + size: None, + )), + ), + ( + binding: 1, + resource: TextureView(Id(1, 1, Metal)), + ), + ( + binding: 2, + resource: TextureView(Id(0, 1, Metal)), + ), + ( + binding: 3, + resource: Sampler(Id(0, 1, Metal)), + ), + ], +)), +CreateTextureView( + id: Id(2, 1, Metal), + parent_id: Id(0, 1, Metal), + desc: ( + label: None, + format: None, + dimension: None, + range: ( + aspect: all, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: None, + ), + ), +), +CreateBindGroup(Id(1, 1, Metal), ( + label: Some("Terrain Normal Bind Group"), + layout: Id(1, 1, Metal), + entries: [ + ( + binding: 0, + resource: Buffer(( + buffer_id: Id(3, 1, Metal), + offset: 0, + size: None, + )), + ), + ], +)), +CreateBindGroup(Id(2, 1, Metal), ( + label: Some("Terrain Flipped Bind Group"), + layout: Id(1, 1, Metal), + entries: [ + ( + binding: 0, + resource: Buffer(( + buffer_id: Id(4, 1, Metal), + offset: 0, + size: None, + )), + ), + ], +)), +CreateShaderModule( + id: Id(0, 1, Metal), + desc: ( + label: Some("terrain"), + shader_bound_checks: ( + runtime_checks: true, + ), + ), + data: "data6.wgsl", +), +CreateShaderModule( + id: Id(1, 1, Metal), + desc: ( + label: Some("water"), + shader_bound_checks: ( + runtime_checks: true, + ), + ), + data: "data7.wgsl", +), +CreateRenderPipeline( + id: Id(0, 1, Metal), + desc: ( + label: Some("water"), + layout: Some(Id(0, 1, Metal)), + vertex: ( + stage: ( + module: Id(1, 1, Metal), + entry_point: "vs_main", + ), + buffers: [ + ( + arrayStride: 8, + stepMode: vertex, + attributes: [ + ( + format: sint16x2, + offset: 0, + shaderLocation: 0, + ), + ( + format: sint8x4, + offset: 4, + shaderLocation: 1, + ), + ], + ), + ], + ), + primitive: ( + topology: r#triangle-list, + stripIndexFormat: None, + frontFace: cw, + cullMode: None, + unclippedDepth: false, + polygonMode: fill, + conservative: false, + ), + depth_stencil: Some(( + format: depth32float, + depth_write_enabled: false, + depth_compare: less, + stencil: ( + front: ( + compare: always, + failOp: keep, + depthFailOp: keep, + passOp: keep, + ), + back: ( + compare: always, + failOp: keep, + depthFailOp: keep, + passOp: keep, + ), + read_mask: 0, + write_mask: 0, + ), + bias: ( + constant: 0, + slope_scale: 0, + clamp: 0, + ), + )), + multisample: ( + count: 1, + mask: 18446744073709551615, + alphaToCoverageEnabled: false, + ), + fragment: Some(( + stage: ( + module: Id(1, 1, Metal), + entry_point: "fs_main", + ), + targets: [ + ( + format: r#bgra8unorm-srgb, + blend: Some(( + color: ( + srcFactor: r#src-alpha, + dstFactor: r#one-minus-src-alpha, + operation: add, + ), + alpha: ( + srcFactor: one, + dstFactor: one, + operation: max, + ), + )), + writeMask: 15, + ), + ], + )), + multiview: None, + ), + implicit_context: None, +), +CreateRenderPipeline( + id: Id(1, 1, Metal), + desc: ( + label: Some("terrain"), + layout: Some(Id(1, 1, Metal)), + vertex: ( + stage: ( + module: Id(0, 1, Metal), + entry_point: "vs_main", + ), + buffers: [ + ( + arrayStride: 28, + stepMode: vertex, + attributes: [ + ( + format: float32x3, + offset: 0, + shaderLocation: 0, + ), + ( + format: float32x3, + offset: 12, + shaderLocation: 1, + ), + ( + format: unorm8x4, + offset: 24, + shaderLocation: 2, + ), + ], + ), + ], + ), + primitive: ( + topology: r#triangle-list, + stripIndexFormat: None, + frontFace: ccw, + cullMode: Some(front), + unclippedDepth: false, + polygonMode: fill, + conservative: false, + ), + depth_stencil: Some(( + format: depth32float, + depth_write_enabled: true, + depth_compare: less, + stencil: ( + front: ( + compare: always, + failOp: keep, + depthFailOp: keep, + passOp: keep, + ), + back: ( + compare: always, + failOp: keep, + depthFailOp: keep, + passOp: keep, + ), + read_mask: 0, + write_mask: 0, + ), + bias: ( + constant: 0, + slope_scale: 0, + clamp: 0, + ), + )), + multisample: ( + count: 1, + mask: 18446744073709551615, + alphaToCoverageEnabled: false, + ), + fragment: Some(( + stage: ( + module: Id(0, 1, Metal), + entry_point: "fs_main", + ), + targets: [ + ( + format: r#bgra8unorm-srgb, + blend: None, + writeMask: 15, + ), + ], + )), + multiview: None, + ), + implicit_context: None, +), +DestroyShaderModule(Id(1, 1, Metal)), +DestroyShaderModule(Id(0, 1, Metal)), +GetSurfaceTexture( + id: Id(2, 1, Metal), + parent_id: Id(0, 1, Empty), +), +CreateTextureView( + id: Id(3, 1, Metal), + parent_id: Id(2, 1, Metal), + desc: ( + label: None, + format: None, + dimension: None, + range: ( + aspect: all, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: None, + ), + ), +), +WriteBuffer( + id: Id(2, 1, Metal), + data: "data8.bin", + range: ( + start: 128, + end: 136, + ), + queued: true, +), +Submit(1, [ + RunRenderPass( + base: ( + label: None, + commands: [ + SetPipeline(Id(1, 1, Metal)), + SetBindGroup( + index: 0, + num_dynamic_offsets: 0, + bind_group_id: Id(2, 1, Metal), + ), + SetVertexBuffer( + slot: 0, + buffer_id: Id(1, 1, Metal), + offset: 0, + size: None, + ), + Draw( + vertex_count: 106380, + instance_count: 1, + first_vertex: 0, + first_instance: 0, + ), + ], + dynamic_offsets: [], + string_data: [], + push_constant_data: [], + ), + target_colors: [ + ( + view: Id(2, 1, Metal), + resolve_target: None, + channel: ( + load_op: clear, + store_op: store, + clear_value: ( + r: 0.6313725490196078, + g: 0.9647058823529412, + b: 1, + a: 1, + ), + read_only: false, + ), + ), + ], + target_depth_stencil: Some(( + view: Id(0, 1, Metal), + depth: ( + load_op: clear, + store_op: store, + clear_value: 1, + read_only: false, + ), + stencil: ( + load_op: load, + store_op: store, + clear_value: 0, + read_only: true, + ), + )), + ), + RunRenderPass( + base: ( + label: None, + commands: [ + SetPipeline(Id(1, 1, Metal)), + SetBindGroup( + index: 0, + num_dynamic_offsets: 0, + bind_group_id: Id(1, 1, Metal), + ), + SetVertexBuffer( + slot: 0, + buffer_id: Id(1, 1, Metal), + offset: 0, + size: None, + ), + Draw( + vertex_count: 106380, + instance_count: 1, + first_vertex: 0, + first_instance: 0, + ), + ], + dynamic_offsets: [], + string_data: [], + push_constant_data: [], + ), + target_colors: [ + ( + view: Id(3, 1, Metal), + resolve_target: None, + channel: ( + load_op: clear, + store_op: store, + clear_value: ( + r: 0.6313725490196078, + g: 0.9647058823529412, + b: 1, + a: 1, + ), + read_only: false, + ), + ), + ], + target_depth_stencil: Some(( + view: Id(0, 1, Metal), + depth: ( + load_op: clear, + store_op: store, + clear_value: 1, + read_only: false, + ), + stencil: ( + load_op: load, + store_op: store, + clear_value: 0, + read_only: true, + ), + )), + ), + RunRenderPass( + base: ( + label: None, + commands: [ + SetPipeline(Id(0, 1, Metal)), + SetBindGroup( + index: 0, + num_dynamic_offsets: 0, + bind_group_id: Id(0, 1, Metal), + ), + SetVertexBuffer( + slot: 0, + buffer_id: Id(0, 1, Metal), + offset: 0, + size: None, + ), + Draw( + vertex_count: 17730, + instance_count: 1, + first_vertex: 0, + first_instance: 0, + ), + ], + dynamic_offsets: [], + string_data: [], + push_constant_data: [], + ), + target_colors: [ + ( + view: Id(3, 1, Metal), + resolve_target: None, + channel: ( + load_op: load, + store_op: store, + clear_value: ( + r: 0, + g: 0, + b: 0, + a: 0, + ), + read_only: false, + ), + ), + ], + target_depth_stencil: Some(( + view: Id(0, 1, Metal), + depth: ( + load_op: load, + store_op: store, + clear_value: 0, + read_only: true, + ), + stencil: ( + load_op: load, + store_op: store, + clear_value: 0, + read_only: true, + ), + )), + ), +]), +Present(Id(0, 1, Empty)), +DestroyBindGroup(Id(2, 1, Metal)), +DestroyBindGroup(Id(1, 1, Metal)), +DestroyBindGroup(Id(0, 1, Metal)), +DestroyTextureView(Id(3, 1, Metal)), +DestroyTextureView(Id(2, 1, Metal)), +DestroyTextureView(Id(0, 1, Metal)), +DestroyTextureView(Id(1, 1, Metal)), +DestroyTexture(Id(0, 1, Metal)), +DestroyTexture(Id(1, 1, Metal)), +DestroySampler(Id(0, 1, Metal)), +DestroyBuffer(Id(0, 1, Metal)), +DestroyBuffer(Id(2, 1, Metal)), +DestroyBuffer(Id(1, 1, Metal)), +DestroyBuffer(Id(3, 1, Metal)), +DestroyBuffer(Id(4, 1, Metal)), +] \ No newline at end of file diff --git a/water/terrain.wgsl b/water/terrain.wgsl new file mode 100644 index 0000000..9f7e256 --- /dev/null +++ b/water/terrain.wgsl @@ -0,0 +1,48 @@ +struct Uniforms { + projection_view: mat4x4; + clipping_plane: vec4; +}; + +[[group(0), binding(0)]] +var uniforms: Uniforms; + +let light: vec3 = vec3(150.0, 70.0, 0.0); +let light_colour: vec3 = vec3(1.0, 0.98, 0.82); +let ambient: f32 = 0.2; + +struct VertexOutput { + [[builtin(position)]] position: vec4; + [[location(0)]] colour: vec4; + // Comment this out if using user-clipping planes: + [[location(1)]] clip_dist: f32; +}; + +[[stage(vertex)]] +fn vs_main( + [[location(0)]] position: vec3, + [[location(1)]] normal: vec3, + [[location(2)]] colour: vec4, +) -> VertexOutput { + var out: VertexOutput; + out.position = uniforms.projection_view * vec4(position, 1.0); + + // https://www.desmos.com/calculator/nqgyaf8uvo + let normalized_light_direction = normalize(position - light); + let brightness_diffuse = clamp(dot(normalized_light_direction, normal), 0.2, 1.0); + + out.colour = vec4(max((brightness_diffuse + ambient) * light_colour * colour.rgb, vec3(0.0, 0.0, 0.0)), colour.a); + out.clip_dist = dot(vec4(position, 1.0), uniforms.clipping_plane); + return out; +} + +[[stage(fragment), early_depth_test]] +fn fs_main( + in: VertexOutput, +) -> [[location(0)]] vec4 { + // Comment this out if using user-clipping planes: + if(in.clip_dist < 0.0) { + discard; + } + + return vec4(in.colour.xyz, 1.0); +} diff --git a/water/water.wgsl b/water/water.wgsl new file mode 100644 index 0000000..b7a4701 --- /dev/null +++ b/water/water.wgsl @@ -0,0 +1,251 @@ +struct Uniforms { + view: mat4x4; + projection: mat4x4; + time_size_width: vec4; + viewport_height: f32; +}; +[[group(0), binding(0)]] var uniforms: Uniforms; + +let light_point: vec3 = vec3(150.0, 70.0, 0.0); +let light_colour: vec3 = vec3(1.0, 0.98, 0.82); +let one: vec4 = vec4(1.0, 1.0, 1.0, 1.0); + +let Y_SCL: f32 = 0.86602540378443864676372317075294; +let CURVE_BIAS: f32 = -0.1; +let INV_1_CURVE_BIAS: f32 = 1.11111111111; //1.0 / (1.0 + CURVE_BIAS); + +// +// The following code to calculate simplex 3D +// is from https://github.com/ashima/webgl-noise +// +// Simplex 3D Noise +// by Ian McEwan, Ashima Arts. +// +fn permute(x: vec4) -> vec4 { + var temp: vec4 = 289.0 * one; + return modf(((x*34.0) + one) * x, &temp); +} + +fn taylorInvSqrt(r: vec4) -> vec4 { + return 1.79284291400159 * one - 0.85373472095314 * r; +} + +fn snoise(v: vec3) -> f32 { + let C = vec2(1.0/6.0, 1.0/3.0); + let D = vec4(0.0, 0.5, 1.0, 2.0); + + // First corner + //TODO: use the splat operations when available + let vCy = dot(v, C.yyy); + var i: vec3 = floor(v + vec3(vCy, vCy, vCy)); + let iCx = dot(i, C.xxx); + let x0 = v - i + vec3(iCx, iCx, iCx); + + // Other corners + let g = step(x0.yzx, x0.xyz); + let l = (vec3(1.0, 1.0, 1.0) - g).zxy; + let i1 = min(g, l); + let i2 = max(g, l); + + // x0 = x0 - 0.0 + 0.0 * C.xxx; + // x1 = x0 - i1 + 1.0 * C.xxx; + // x2 = x0 - i2 + 2.0 * C.xxx; + // x3 = x0 - 1.0 + 3.0 * C.xxx; + let x1 = x0 - i1 + C.xxx; + let x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y + let x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y + + // Permutations + var temp: vec3 = 289.0 * one.xyz; + i = modf(i, &temp); + let p = permute( + permute( + permute(i.zzzz + vec4(0.0, i1.z, i2.z, 1.0)) + + i.yyyy + vec4(0.0, i1.y, i2.y, 1.0)) + + i.xxxx + vec4(0.0, i1.x, i2.x, 1.0)); + + // Gradients: 7x7 points over a square, mapped onto an octahedron. + // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294) + let n_ = 0.142857142857;// 1.0/7.0 + let ns = n_ * D.wyz - D.xzx; + + let j = p - 49.0 * floor(p * ns.z * ns.z);// mod(p,7*7) + + let x_ = floor(j * ns.z); + let y_ = floor(j - 7.0 * x_);// mod(j,N) + + var x: vec4 = x_ *ns.x + ns.yyyy; + var y: vec4 = y_ *ns.x + ns.yyyy; + let h = one - abs(x) - abs(y); + + let b0 = vec4(x.xy, y.xy); + let b1 = vec4(x.zw, y.zw); + + //vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - one; + //vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - one; + let s0 = floor(b0)*2.0 + one; + let s1 = floor(b1)*2.0 + one; + let sh = -step(h, 0.0 * one); + + let a0 = b0.xzyw + s0.xzyw*sh.xxyy; + let a1 = b1.xzyw + s1.xzyw*sh.zzww; + + var p0: vec3 = vec3(a0.xy, h.x); + var p1: vec3 = vec3(a0.zw, h.y); + var p2: vec3 = vec3(a1.xy, h.z); + var p3: vec3 = vec3(a1.zw, h.w); + + //Normalise gradients + let norm = taylorInvSqrt(vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3))); + p0 = p0 * norm.x; + p1 = p1 * norm.y; + p2 = p2 * norm.z; + p3 = p3 * norm.w; + + // Mix final noise value + var m: vec4 = max(0.6 * one - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0 * one); + m = m * m; + return 9.0 * dot(m*m, vec4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3))); +} + +// End of 3D simplex code. + +fn apply_distortion(pos: vec3) -> vec3 { + var perlin_pos: vec3 = pos; + + //Do noise transformation to permit for smooth, + //continuous movement. + + //TODO: we should be able to name them `sin` and `cos`. + let sn = uniforms.time_size_width.x; + let cs = uniforms.time_size_width.y; + let size = uniforms.time_size_width.z; + + // Rotate 90 Z, Move Left Size / 2 + perlin_pos = vec3(perlin_pos.y - perlin_pos.x - size, perlin_pos.x, perlin_pos.z); + + let xcos = perlin_pos.x * cs; + let xsin = perlin_pos.x * sn; + let ycos = perlin_pos.y * cs; + let ysin = perlin_pos.y * sn; + let zcos = perlin_pos.z * cs; + let zsin = perlin_pos.z * sn; + + // Rotate Time Y + let perlin_pos_y = vec3(xcos + zsin, perlin_pos.y, -xsin + xcos); + + // Rotate Time Z + let perlin_pos_z = vec3(xcos - ysin, xsin + ycos, perlin_pos.x); + + // Rotate 90 Y + perlin_pos = vec3(perlin_pos.z - perlin_pos.x, perlin_pos.y, perlin_pos.x); + + // Rotate Time X + let perlin_pos_x = vec3(perlin_pos.x, ycos - zsin, ysin + zcos); + + // Sample at different places for x/y/z to get random-looking water. + return vec3( + //TODO: use splats + pos.x + snoise(perlin_pos_x + 2.0*one.xxx) * 0.4, + pos.y + snoise(perlin_pos_y - 2.0*one.xxx) * 1.8, + pos.z + snoise(perlin_pos_z) * 0.4 + ); +} + +// Multiply the input by the scale values. +fn make_position(original: vec2) -> vec4 { + let interpreted = vec3(original.x * 0.5, 0.0, original.y * Y_SCL); + return vec4(apply_distortion(interpreted), 1.0); +} + +// Create the normal, and apply the curve. Change the Curve Bias above. +fn make_normal(a: vec3, b: vec3, c: vec3) -> vec3 { + let norm = normalize(cross(b - c, a - c)); + let center = (a + b + c) * (1.0 / 3.0); //TODO: use splat + return (normalize(a - center) * CURVE_BIAS + norm) * INV_1_CURVE_BIAS; +} + +// Calculate the fresnel effect. +fn calc_fresnel(view: vec3, normal: vec3) -> f32 { + var refractive: f32 = abs(dot(view, normal)); + refractive = pow(refractive, 1.33333333333); + return refractive; +} + +// Calculate the specular lighting. +fn calc_specular(eye: vec3, normal: vec3, light: vec3) -> f32 { + let light_reflected = reflect(light, normal); + var specular: f32 = max(dot(eye, light_reflected), 0.0); + specular = pow(specular, 10.0); + return specular; +} + +struct VertexOutput { + [[builtin(position)]] position: vec4; + [[location(0)]] f_WaterScreenPos: vec2; + [[location(1)]] f_Fresnel: f32; + [[location(2)]] f_Light: vec3; +}; + +[[stage(vertex)]] +fn vs_main( + [[location(0)]] position: vec2, + [[location(1)]] offsets: vec4, +) -> VertexOutput { + let p_pos = vec2(position); + let b_pos = make_position(p_pos + vec2(offsets.xy)); + let c_pos = make_position(p_pos + vec2(offsets.zw)); + let a_pos = make_position(p_pos); + let original_pos = vec4(p_pos.x * 0.5, 0.0, p_pos.y * Y_SCL, 1.0); + + let vm = uniforms.view; + let transformed_pos = vm * a_pos; + //TODO: use vector splats for division + let water_pos = transformed_pos.xyz * (1.0 / transformed_pos.w); + let normal = make_normal((vm * a_pos).xyz, (vm * b_pos).xyz, (vm * c_pos).xyz); + let eye = normalize(-water_pos); + let transformed_light = vm * vec4(light_point, 1.0); + + var out: VertexOutput; + out.f_Light = light_colour * calc_specular(eye, normal, normalize(water_pos.xyz - (transformed_light.xyz * (1.0 / transformed_light.w)))); + out.f_Fresnel = calc_fresnel(eye, normal); + + let gridpos = uniforms.projection * vm * original_pos; + out.f_WaterScreenPos = (0.5 * gridpos.xy * (1.0 / gridpos.w)) + vec2(0.5, 0.5); + + out.position = uniforms.projection * transformed_pos; + return out; +} + + +let water_colour: vec3 = vec3(0.0, 0.46, 0.95); +let zNear: f32 = 10.0; +let zFar: f32 = 400.0; + +[[group(0), binding(1)]] var reflection: texture_2d; +[[group(0), binding(2)]] var terrain_depth_tex: texture_2d; +[[group(0), binding(3)]] var colour_sampler: sampler; + +fn to_linear_depth(depth: f32) -> f32 { + let z_n: f32 = 2.0 * depth - 1.0; + let z_e: f32 = 2.0 * zNear * zFar / (zFar + zNear - z_n * (zFar - zNear)); + return z_e; +} + +[[stage(fragment)]] +fn fs_main(in: VertexOutput) -> [[location(0)]] vec4 { + let reflection_colour = textureSample(reflection, colour_sampler, in.f_WaterScreenPos.xy).xyz; + + let pixel_depth = to_linear_depth(in.position.z); + let normalized_coords = in.position.xy / vec2(uniforms.time_size_width.w, uniforms.viewport_height); + let terrain_depth = to_linear_depth(textureSample(terrain_depth_tex, colour_sampler, normalized_coords).r); + + let dist = terrain_depth - pixel_depth; + let clamped = pow(smoothStep(0.0, 1.5, dist), 4.8); + + let final_colour = in.f_Light + reflection_colour; + let t = smoothStep(1.0, 5.0, dist) * 0.2; //TODO: splat for mix()? + let depth_colour = mix(final_colour, water_colour, vec3(t, t, t)); + + return vec4(depth_colour, clamped * (1.0 - in.f_Fresnel)); +}