From 49a741a0c6224461a0f7f03f86e9d7fab025df74 Mon Sep 17 00:00:00 2001 From: Dzmitry Malyshau Date: Sat, 13 Feb 2021 14:00:49 -0500 Subject: [PATCH] [rs] Update to gfx-19, port the water example shaders to WGSL --- wgpu/Cargo.toml | 10 +- wgpu/examples/README.md | 2 +- wgpu/examples/boids/compute.wgsl | 1 - wgpu/examples/shadow/shader.wgsl | 1 - wgpu/examples/water/main.rs | 45 ++-- wgpu/examples/water/terrain.wgsl | 49 ++++ wgpu/examples/water/terrain_shader.frag | 19 -- wgpu/examples/water/terrain_shader.frag.spv | Bin 668 -> 0 bytes wgpu/examples/water/terrain_shader.vert | 38 --- wgpu/examples/water/terrain_shader.vert.spv | Bin 2404 -> 0 bytes wgpu/examples/water/water.wgsl | 252 ++++++++++++++++++++ wgpu/examples/water/water_shader.frag | 46 ---- wgpu/examples/water/water_shader.frag.spv | Bin 3464 -> 0 bytes wgpu/examples/water/water_shader.vert | 211 ---------------- wgpu/examples/water/water_shader.vert.spv | Bin 17624 -> 0 bytes 15 files changed, 334 insertions(+), 340 deletions(-) create mode 100644 wgpu/examples/water/terrain.wgsl delete mode 100644 wgpu/examples/water/terrain_shader.frag delete mode 100644 wgpu/examples/water/terrain_shader.frag.spv delete mode 100644 wgpu/examples/water/terrain_shader.vert delete mode 100644 wgpu/examples/water/terrain_shader.vert.spv create mode 100644 wgpu/examples/water/water.wgsl delete mode 100644 wgpu/examples/water/water_shader.frag delete mode 100644 wgpu/examples/water/water_shader.frag.spv delete mode 100644 wgpu/examples/water/water_shader.vert delete mode 100644 wgpu/examples/water/water_shader.vert.spv diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 4ea00da43f..919d94470a 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -28,20 +28,20 @@ cross = ["wgc/cross"] [target.'cfg(not(target_arch = "wasm32"))'.dependencies.wgc] package = "wgpu-core" git = "https://github.com/gfx-rs/wgpu" -rev = "b7ba481a9170e5b9c6364b2eb16080e527462039" +rev = "41f106d7fc8e2ca49b21aed0919fa6cc67317f7f" features = ["raw-window-handle"] [target.'cfg(target_arch = "wasm32")'.dependencies.wgc] package = "wgpu-core" git = "https://github.com/gfx-rs/wgpu" -rev = "b7ba481a9170e5b9c6364b2eb16080e527462039" +rev = "41f106d7fc8e2ca49b21aed0919fa6cc67317f7f" features = ["raw-window-handle"] optional = true [dependencies.wgt] package = "wgpu-types" git = "https://github.com/gfx-rs/wgpu" -rev = "b7ba481a9170e5b9c6364b2eb16080e527462039" +rev = "41f106d7fc8e2ca49b21aed0919fa6cc67317f7f" [dependencies] arrayvec = "0.5" @@ -70,13 +70,13 @@ env_logger = "0.8" # used to test all the example shaders [dev-dependencies.naga] git = "https://github.com/gfx-rs/naga" -tag = "gfx-18" +tag = "gfx-19" features = ["wgsl-in"] # used to generate SPIR-V for the Web target [target.'cfg(target_arch = "wasm32")'.dependencies.naga] git = "https://github.com/gfx-rs/naga" -tag = "gfx-18" +tag = "gfx-19" features = ["wgsl-in", "spv-out"] [[example]] diff --git a/wgpu/examples/README.md b/wgpu/examples/README.md index f2e178edbd..f893d173bf 100644 --- a/wgpu/examples/README.md +++ b/wgpu/examples/README.md @@ -12,7 +12,7 @@ All framework-based examples render to the window. ## Feature matrix | Feature | boids | cube | mipmap | msaa-line | shadow | skybox | texture-arrays | water | conservative-raster | | ---------------------------- | ------ | ------ | ------ | --------- | ------ | ------ | -------------- | ------ | ------------------- | -| WGSL shaders | :star: | :star: | :star: | :star: | :star: | :star: | | | :star: | +| WGSL shaders | :star: | :star: | :star: | :star: | :star: | :star: | | :star: | :star: | | vertex attributes | :star: | :star: | | :star: | :star: | :star: | :star: | :star: | | | instancing | :star: | | | | | | | | | | lines and points | | | | :star: | | | | | :star: | diff --git a/wgpu/examples/boids/compute.wgsl b/wgpu/examples/boids/compute.wgsl index 43c354d266..4a5bc6bb09 100644 --- a/wgpu/examples/boids/compute.wgsl +++ b/wgpu/examples/boids/compute.wgsl @@ -1,7 +1,6 @@ // This should match `NUM_PARTICLES` on the Rust side. const NUM_PARTICLES: u32 = 1500u; -[[block]] struct Particle { pos : vec2; vel : vec2; diff --git a/wgpu/examples/shadow/shader.wgsl b/wgpu/examples/shadow/shader.wgsl index fb0305542e..f23bba68ab 100644 --- a/wgpu/examples/shadow/shader.wgsl +++ b/wgpu/examples/shadow/shader.wgsl @@ -43,7 +43,6 @@ fn vs_main( // fragment shader -[[block]] struct Light { proj: mat4x4; pos: vec4; diff --git a/wgpu/examples/water/main.rs b/wgpu/examples/water/main.rs index dbc931cd1a..f2ccbe325d 100644 --- a/wgpu/examples/water/main.rs +++ b/wgpu/examples/water/main.rs @@ -5,7 +5,7 @@ mod point_gen; use bytemuck::{Pod, Zeroable}; use cgmath::Point3; -use std::{iter, mem}; +use std::{borrow::Cow, iter, mem}; use wgpu::util::DeviceExt; /// @@ -263,7 +263,7 @@ impl Example { impl framework::Example for Example { fn init( sc_desc: &wgpu::SwapChainDescriptor, - _adapter: &wgpu::Adapter, + adapter: &wgpu::Adapter, device: &wgpu::Device, queue: &wgpu::Queue, ) -> Self { @@ -486,14 +486,23 @@ impl framework::Example for Example { }); // Upload/compile them to GPU code. - let water_vs_module = - device.create_shader_module(&wgpu::include_spirv!("water_shader.vert.spv")); - let water_fs_module = - device.create_shader_module(&wgpu::include_spirv!("water_shader.frag.spv")); - let terrain_vs_module = - device.create_shader_module(&wgpu::include_spirv!("terrain_shader.vert.spv")); - let terrain_fs_module = - device.create_shader_module(&wgpu::include_spirv!("terrain_shader.frag.spv")); + let mut flags = wgpu::ShaderFlags::VALIDATION; + match adapter.get_info().backend { + wgpu::Backend::Metal | wgpu::Backend::Vulkan => { + flags |= wgpu::ShaderFlags::EXPERIMENTAL_TRANSLATION + } + _ => (), //TODO + } + let terrain_module = device.create_shader_module(&wgpu::ShaderModuleDescriptor { + label: Some("terrain"), + source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("terrain.wgsl"))), + flags, + }); + let water_module = device.create_shader_module(&wgpu::ShaderModuleDescriptor { + label: Some("water"), + source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("water.wgsl"))), + flags, + }); // Create the render pipelines. These describe how the data will flow through the GPU, and what // constraints and modifiers it will have. @@ -503,8 +512,8 @@ impl framework::Example for Example { layout: Some(&water_pipeline_layout), // Vertex shader and input buffers vertex: wgpu::VertexState { - module: &water_vs_module, - entry_point: "main", + module: &water_module, + entry_point: "vs_main", // Layout of our vertices. This should match the structs // which are uploaded to the GPU. This should also be // ensured by tagging on either a `#[repr(C)]` onto a @@ -518,8 +527,8 @@ impl framework::Example for Example { }, // Fragment shader and output targets fragment: Some(wgpu::FragmentState { - module: &water_fs_module, - entry_point: "main", + module: &water_module, + entry_point: "fs_main", // Describes how the colour will be interpolated // and assigned to the output attachment. targets: &[wgpu::ColorTargetState { @@ -572,8 +581,8 @@ impl framework::Example for Example { label: Some("terrain"), layout: Some(&terrain_pipeline_layout), vertex: wgpu::VertexState { - module: &terrain_vs_module, - entry_point: "main", + module: &terrain_module, + entry_point: "vs_main", buffers: &[wgpu::VertexBufferLayout { array_stride: terrain_vertex_size as wgpu::BufferAddress, step_mode: wgpu::InputStepMode::Vertex, @@ -581,8 +590,8 @@ impl framework::Example for Example { }], }, fragment: Some(wgpu::FragmentState { - module: &terrain_fs_module, - entry_point: "main", + module: &terrain_module, + entry_point: "fs_main", targets: &[sc_desc.format.into()], }), primitive: wgpu::PrimitiveState { diff --git a/wgpu/examples/water/terrain.wgsl b/wgpu/examples/water/terrain.wgsl new file mode 100644 index 0000000000..50f923610c --- /dev/null +++ b/wgpu/examples/water/terrain.wgsl @@ -0,0 +1,49 @@ +[[block]] +struct Uniforms { + projection_view: mat4x4; + clipping_plane: vec4; +}; + +[[group(0), binding(0)]] +var uniforms: Uniforms; + +const light: vec3 = vec3(150.0, 70.0, 0.0); +const light_colour: vec3 = vec3(1.0, 0.98, 0.82); +const 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 + const normalized_light_direction = normalize(position - light); + const 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/wgpu/examples/water/terrain_shader.frag b/wgpu/examples/water/terrain_shader.frag deleted file mode 100644 index 895b179023..0000000000 --- a/wgpu/examples/water/terrain_shader.frag +++ /dev/null @@ -1,19 +0,0 @@ -#version 450 - -layout(early_fragment_tests) in; - -layout(location = 0) in vec4 v_Colour; -// Comment this out if using user-clipping planes: -layout(location = 1) in float v_ClipDist; - -layout(location = 0) out vec4 outColour; - -void main() { - // Comment this out if using user-clipping planes: - if(v_ClipDist < 0.0) { - discard; - } - - outColour = v_Colour; - outColour.a = 1.0; -} diff --git a/wgpu/examples/water/terrain_shader.frag.spv b/wgpu/examples/water/terrain_shader.frag.spv deleted file mode 100644 index aa54ce6d05ee1d40b2f3b3d4e8600786499d679b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 668 zcmYk3%}PRH5QZo9lx3ObUq28=%RpLG1W^$NZrpVN5wsC3Beivx)w-K1qM+wFdBTx# zX5N{3zjrjB+h2-Uh(Z)&C;qG&Igp4_ET*y7zv*|zll#um@u3st$ftwOtg}}j>JN8= zM;k?a1Fz#1X6^CFf4)qf=aVD8Ly|Btzn_4}@p9W84xX+D?=8L|c0et@f@^gc zO2ig;g=o*)+I&}u{&M2-SgTFUevK$+*0)yc{5wtRh}X*o^DVNrC8iGRO+4eD^Idri|trfcS(^Q_s_)}$JoJL2kGquX%tX>&C~oIB>~qkCy(ePy^*-X0zu z8^NN*)kcQcw91N8C)(Q#LippN)1vF5QPDloBT=pXYIJ{-V?rL)J_+R_(rzx3&K6%} zi{9$mf^l!JUIv>~#mh6oSB@_KNX#m;o(l5Qav6c&S0c{AL~R&;cLZaJ3ITOD1Kcu z>)1ce!Q2lr3;A4rzbIDzb`HFQZesqXoHd;z|q6#A~cC#YlVn^I1#bvhrB96&@u^7qBySGYa zFOi9fJ+rFTv#-b&pE;R*yHM~x)j+TCo#N_sCq-R~NA1YWphLfnx|1T#pqHD(lfOsu zQQg>s^@?L=76ToWlwGrKoA0ET*;g>~v_y7sT=F2dia6bpnJ00nt5?6w&@kekluSS1 z#7AbfVCYYI9v}Mb(oNmuN8hcRp1|=(X7*szgY8+7bMI!b?-PeFH6f=wkH6Vp6t4*{ zn0zUcKh2wbIg*_lQgr;Ti15J<9dU~&T*GP`JX@xEyh z@vvXh&0CRgT;CErZ_UByw-CNPYDwuH5Ls=^i2pChbF$*CJ4C!cJ2WL9-n~-c)k;(W<@5}oCtq#{ANX#cV0Z-$EyBgQOAM^3?KH59iuOHj()Ml zhIeDm4@A_xD6$zWc^n(|{GkXxY>0u34ffRcSX8}tD<0ET*|8@cqYvA=rygT2ws))I o@y8xL??fGZPtQbr1AJ%J|GLNR{hv!F1~|3yje`Bt1SdrQ0TfE4QUCw| diff --git a/wgpu/examples/water/water.wgsl b/wgpu/examples/water/water.wgsl new file mode 100644 index 0000000000..1e32908f95 --- /dev/null +++ b/wgpu/examples/water/water.wgsl @@ -0,0 +1,252 @@ +[[block]] +struct Uniforms { + view: mat4x4; + projection: mat4x4; + time_size_width: vec4; + viewport_height: f32; +}; +[[group(0), binding(0)]] var uniforms: Uniforms; + +const light_point: vec3 = vec3(150.0, 70.0, 0.0); +const light_colour: vec3 = vec3(1.0, 0.98, 0.82); +const one: vec4 = vec4(1.0, 1.0, 1.0, 1.0); + +const Y_SCL: f32 = 0.86602540378443864676372317075294; +const CURVE_BIAS: f32 = -0.1; +const 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 { + const C = vec2(1.0/6.0, 1.0/3.0); + const D = vec4(0.0, 0.5, 1.0, 2.0); + + // First corner + //TODO: use the splat operations when available + const vCy = dot(v, C.yyy); + var i: vec3 = floor(v + vec3(vCy, vCy, vCy)); + const iCx = dot(i, C.xxx); + const x0 = v - i + vec3(iCx, iCx, iCx); + + // Other corners + const g = step(x0.yzx, x0.xyz); + const l = (vec3(1.0, 1.0, 1.0) - g).zxy; + const i1 = min(g, l); + const 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; + const x1 = x0 - i1 + C.xxx; + const x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y + const 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); + const 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) + const n_ = 0.142857142857;// 1.0/7.0 + const ns = n_ * D.wyz - D.xzx; + + const j = p - 49.0 * floor(p * ns.z * ns.z);// mod(p,7*7) + + const x_ = floor(j * ns.z); + const 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; + const h = one - abs(x) - abs(y); + + const b0 = vec4(x.xy, y.xy); + const 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; + const s0 = floor(b0)*2.0 + one; + const s1 = floor(b1)*2.0 + one; + const sh = -step(h, 0.0 * one); + + const a0 = b0.xzyw + s0.xzyw*sh.xxyy; + const 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 + const 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`. + const sn = uniforms.time_size_width.x; + const cs = uniforms.time_size_width.y; + const 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); + + const xcos = perlin_pos.x * cs; + const xsin = perlin_pos.x * sn; + const ycos = perlin_pos.y * cs; + const ysin = perlin_pos.y * sn; + const zcos = perlin_pos.z * cs; + const zsin = perlin_pos.z * sn; + + // Rotate Time Y + const perlin_pos_y = vec3(xcos + zsin, perlin_pos.y, -xsin + xcos); + + // Rotate Time Z + const 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 + const 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 { + const 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 { + const norm = normalize(cross(b - c, a - c)); + const 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 { + const 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 { + const p_pos = vec2(position); + const b_pos = make_position(p_pos + vec2(offsets.xy)); + const c_pos = make_position(p_pos + vec2(offsets.zw)); + const a_pos = make_position(p_pos); + const original_pos = vec4(p_pos.x * 0.5, 0.0, p_pos.y * Y_SCL, 1.0); + + const vm = uniforms.view; + const transformed_pos = vm * a_pos; + //TODO: use vector splats for division + const water_pos = transformed_pos.xyz * (1.0 / transformed_pos.w); + const normal = make_normal((vm * a_pos).xyz, (vm * b_pos).xyz, (vm * c_pos).xyz); + const eye = normalize(-water_pos); + const 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); + + const 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; +} + + +const water_colour: vec3 = vec3(0.0, 0.46, 0.95); +const zNear: f32 = 10.0; +const 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 { + const z_n: f32 = 2.0 * depth - 1.0; + const 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 { + const reflection_colour = textureSample(reflection, colour_sampler, in.f_WaterScreenPos.xy).xyz; + + const pixel_depth = to_linear_depth(in.position.z); + const normalized_coords = in.position.xy / vec2(uniforms.time_size_width.w, uniforms.viewport_height); + const terrain_depth = to_linear_depth(textureSample(terrain_depth_tex, colour_sampler, normalized_coords).r); + + const dist = terrain_depth - pixel_depth; + const clamped = pow(smoothStep(0.0, 1.5, dist), 4.8); + + const final_colour = in.f_Light + reflection_colour; + const t = smoothStep(1.0, 5.0, dist) * 0.2; //TODO: splat for mix()? + const depth_colour = mix(final_colour, water_colour, vec3(t, t, t)); + + return vec4(depth_colour, clamped * (1.0 - in.f_Fresnel)); +} diff --git a/wgpu/examples/water/water_shader.frag b/wgpu/examples/water/water_shader.frag deleted file mode 100644 index 0d704e33be..0000000000 --- a/wgpu/examples/water/water_shader.frag +++ /dev/null @@ -1,46 +0,0 @@ -#version 450 - -const vec3 water_colour = vec3(0.0, 117.0 / 255.0, 242.0 / 255.0); -const float zNear = 10.0; -const float zFar = 400.0; - -layout(set = 0, binding = 0) uniform Uniforms { - mat4x4 _view; - mat4x4 _projection; - vec4 time_size_width; - float viewport_height; -}; - -layout(set = 0, binding = 1) uniform texture2D reflection; -layout(set = 0, binding = 2) uniform texture2D terrain_depth_tex; -layout(set = 0, binding = 3) uniform sampler colour_sampler; - -layout(location = 0) in vec2 f_WaterScreenPos; -layout(location = 1) in float f_Fresnel; -layout(location = 2) in vec3 f_Light; - -layout(location = 0) out vec4 outColor; - -float to_linear_depth(float depth) { - float z_n = 2.0 * depth - 1.0; - float z_e = 2.0 * zNear * zFar / (zFar + zNear - z_n * (zFar - zNear)); - return z_e; -} - -void main() { - vec3 reflection_colour = texture(sampler2D(reflection, colour_sampler), f_WaterScreenPos.xy).xyz; - - float pixel_depth = to_linear_depth(gl_FragCoord.z); - float terrain_depth = to_linear_depth(texture(sampler2D(terrain_depth_tex, colour_sampler), gl_FragCoord.xy / vec2(time_size_width.w, viewport_height)).r); - - float dist = terrain_depth - pixel_depth; - float clamped = pow(smoothstep(0.0, 1.5, dist), 4.8); - - outColor.a = clamped * (1.0 - f_Fresnel); - - vec3 final_colour = f_Light + reflection_colour; - - vec3 depth_colour = mix(final_colour, water_colour, smoothstep(1.0, 5.0, dist) * 0.2); - - outColor.xyz = depth_colour; -} diff --git a/wgpu/examples/water/water_shader.frag.spv b/wgpu/examples/water/water_shader.frag.spv deleted file mode 100644 index 8eb94cfa6e4f400fd4c442269b621bcb15e3915c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3464 zcmZ9NTXR%J6os24Ga(lU5C|7boFs@S7(uy;gv1CzjV2Hw-Vc+^ndIPPW;}BO7;m90 zt9Xh+grm5!I4fYYv?O1lV zdrEd1S8MT7mNe?oQlr*bOZ}LF8~LA)d5Z3^w02O`Dz7$cv4WY0y;P%xau%oaOKBX} z=Nc_Z@)g_(_08mNTx;j(=V9N=wditMUY=?+QnoL>%bg^)oR(KTFS_&UHD{`lh0*Qx zUU^)PviPnaQ*cx2uh)}mBVF}56g4v%ExMh=chnd4hsN1N&9rf=T>z=wUZ?I^vKmLN zWIc}VB$aICk&jblnr0)-qLnyVUdgmVr@JJb#y+m8-`q!+yCGglTABIZm$6h+1>=h1 z3;qr?*0L$ppYnT+qEuC)xK)q28`S?6T2e39Hjherm%lsJXeQ)RbWO$i^UuXiD~>Y- zG~>gvsRNwz&HLf_*)x95`Jh09bB=w&K;WE9emtD<9l{mG+c^|G-#nL5aoQE_#$xye zc6hBljvVvj;EbCPY#iF=xb|GEP27fHah*Z?Uj4Lo$C|{kC(dz1pc!WmH29+Wd<{4! zIBVnQtl-QysQ>iCT7xyQhh!Dimpr*VVt@+w{g+w+7rs)i0qO;UeBp#U24SGqw2Q^=!}JP|ImmZQ%`)CK>Um9@u3qB=YFE? z5kIBAB+#e}{Ip=}osmr}bK0J`zqEade^otmv)(~Pac9s>XB>IFrk*+RGY_0Qg+?Bi z)t3a;A`keqfZy`EBHPaA_26TEi=PR8i=P*#!HN68vrYb?=RZBVFOm2U_wP@TZ&@~L zS>51^K_h?aQW40P_?6&e9r!1~$NeE5&No23<-?=)`@@Ih##`cl{*hihrfeCV0y=(bh-Q1bAe(Op&Ys4F{GKj)Ut1@JJxmDXFstgY$4Ox#K%LKjQmgY_ zydm&yO$&T8mh+pk@v$%B-V%u8yRn$JgFdGi)|nL;hyGpA=47)zKCAIn+2oDyYN*3I z-qt@g>cHJVvpQXu-7BEu=PsdHE(@~JjK3?JT7DDee@`|s=#1yCp;`P#veEi==A6sN z0_V&ed{=z`#PqAC_-_jM4+z9Y0`qcbZwfZ=l58~Nv25M~%d0AThk*WV$aPsZcHh1%$#^Y}Y#Go_&R?sZ|Q*kuQ^S11KJ?@BGJ;;}L zg&52EGug!473NeS)?63R4b&F>bAh?W1lG+2V#%wi9>3-Fg={o9@m~tmVOGDh@UMbK z9B)-Y;Cwm-<|CGvf3)IRfp-l*cZs%DIHS03^*sV_T%US-)B0uOGrlWutJ8LI##tQ( zWN#DD?TmNIMyGD%#GGh0#~#^eHpgDsXhVX{u}}7H0UzHMd)O}w3iz0p+|X>^1G3Tn J)t<1~|9{qV63+kt diff --git a/wgpu/examples/water/water_shader.vert b/wgpu/examples/water/water_shader.vert deleted file mode 100644 index b35251ef16..0000000000 --- a/wgpu/examples/water/water_shader.vert +++ /dev/null @@ -1,211 +0,0 @@ -#version 450 - -layout(set = 0, binding = 0) uniform Uniforms { - mat4x4 view; - mat4x4 projection; - vec4 time_size_width; - float _viewport_height; -}; - -const vec3 light_point = vec3(150.0, 70.0, 0.0); -const vec3 light_colour = vec3(1.0, 250.0 / 255.0, 209.0 / 255.0); - -const float Y_SCL = 0.86602540378443864676372317075294; -const float CURVE_BIAS = -0.1; -const float INV_1_CURVE_BIAS = 1.0 / (1.0 + CURVE_BIAS); - -layout(location = 0) in ivec2 position; -layout(location = 1) in ivec4 offsets; - -layout(location = 0) out vec2 f_WaterScreenPos; -layout(location = 1) out float f_Fresnel; -layout(location = 2) out vec3 f_Light; - -// -// The following code to calculate simplex 3D -// is from https://github.com/ashima/webgl-noise -// -// Simplex 3D Noise -// by Ian McEwan, Ashima Arts. -// -vec4 permute(vec4 x) { - return mod(((x*34.0)+1.0)*x, 289.0); -} - -vec4 taylorInvSqrt(vec4 r){ - return 1.79284291400159 - 0.85373472095314 * r; -} - -float snoise(vec3 v){ - const vec2 C = vec2(1.0/6.0, 1.0/3.0); - const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); - - // First corner - vec3 i = floor(v + dot(v, C.yyy)); - vec3 x0 = v - i + dot(i, C.xxx); - - // Other corners - vec3 g = step(x0.yzx, x0.xyz); - vec3 l = 1.0 - g; - vec3 i1 = min(g.xyz, l.zxy); - vec3 i2 = max(g.xyz, l.zxy); - - // 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; - vec3 x1 = x0 - i1 + C.xxx; - vec3 x2 = x0 - i2 + C.yyy;// 2.0*C.x = 1/3 = C.y - vec3 x3 = x0 - D.yyy;// -1.0+3.0*C.x = -0.5 = -D.y - - // Permutations - i = mod(i, 289.0); - vec4 p = permute(permute(permute( - i.z + vec4(0.0, i1.z, i2.z, 1.0)) - + i.y + vec4(0.0, i1.y, i2.y, 1.0)) - + i.x + 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) - float n_ = 0.142857142857;// 1.0/7.0 - vec3 ns = n_ * D.wyz - D.xzx; - - vec4 j = p - 49.0 * floor(p * ns.z * ns.z);// mod(p,7*7) - - vec4 x_ = floor(j * ns.z); - vec4 y_ = floor(j - 7.0 * x_);// mod(j,N) - - vec4 x = x_ *ns.x + ns.yyyy; - vec4 y = y_ *ns.x + ns.yyyy; - vec4 h = 1.0 - abs(x) - abs(y); - - vec4 b0 = vec4(x.xy, y.xy); - vec4 b1 = vec4(x.zw, y.zw); - - //vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0; - //vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0; - vec4 s0 = floor(b0)*2.0 + 1.0; - vec4 s1 = floor(b1)*2.0 + 1.0; - vec4 sh = -step(h, vec4(0.0)); - - vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy; - vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww; - - vec3 p0 = vec3(a0.xy, h.x); - vec3 p1 = vec3(a0.zw, h.y); - vec3 p2 = vec3(a1.xy, h.z); - vec3 p3 = vec3(a1.zw, h.w); - - //Normalise gradients - vec4 norm = taylorInvSqrt(vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3))); - p0 *= norm.x; - p1 *= norm.y; - p2 *= norm.z; - p3 *= norm.w; - - // Mix final noise value - vec4 m = max(0.6 - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0); - 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. - -vec3 apply_distortion(vec3 pos) { - vec3 perlin_pos = pos; - - //Do noise transformation to permit for smooth, - //continuous movement. - - float sin = time_size_width.x; - float cos = time_size_width.y; - float size = time_size_width.z; - - // Rotate 90 Z - perlin_pos.xy = perlin_pos.yx; - perlin_pos.x = -perlin_pos.x; - // Move Left Size / 2 - perlin_pos.x -= size; - - float xcos = perlin_pos.x * cos; - float xsin = perlin_pos.x * sin; - float ycos = perlin_pos.y * cos; - float ysin = perlin_pos.y * sin; - float zcos = perlin_pos.z * cos; - float zsin = perlin_pos.z * sin; - - // Rotate Time Y - vec3 perlin_pos_y = vec3(xcos + zsin, perlin_pos.y, -xsin + xcos); - - // Rotate Time Z - vec3 perlin_pos_z = vec3(xcos - ysin, xsin + ycos, perlin_pos.x); - - // Rotate 90 Y - perlin_pos.xz = perlin_pos.zx; - perlin_pos.x = -perlin_pos.x; - - // Rotate Time X - vec3 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(pos.x + snoise(perlin_pos_x + 2.0) * 0.4, pos.y + snoise(perlin_pos_y - 2.0) * 1.8, pos.z + snoise(perlin_pos_z) * 0.4); -} - -// Multiply the input by the scale values. -vec3 make_position(vec2 original) { - vec3 interpreted = vec3(original.x * 0.5, 0.0, original.y * Y_SCL); - return apply_distortion(interpreted); -} - -// Create the normal, and apply the curve. Change the Curve Bias above. -vec3 make_normal(vec3 a, vec3 b, vec3 c) { - vec3 norm = normalize(cross(b - c, a - c)); - vec3 center = (a + b + c) / 3.0; - return (normalize(a - center) * CURVE_BIAS + norm) * INV_1_CURVE_BIAS; -} - -// Calculate the fresnel effect. -float calc_fresnel(vec3 view, vec3 normal) { - float refractive = abs(dot(view, normal)); - refractive = pow(refractive, 1.33333333333); - return refractive; -} - -// Calculate the specular lighting. -float calc_specular(vec3 eye, vec3 normal, vec3 light) { - vec3 light_reflected = reflect(light, normal); - float specular = max(dot(eye, light_reflected), 0.0); - specular = pow(specular, 10.0); - return specular; -} - -void main() { - vec2 p_pos = position; - vec3 b_pos = make_position(p_pos + offsets.xy); - vec3 c_pos = make_position(p_pos + offsets.zw); - vec4 a_pos = vec4(make_position(p_pos), 1.0); - vec4 original_pos = vec4(p_pos.x * 0.5, 0.0, p_pos.y * Y_SCL, 1.0); - - vec4 water_pos = a_pos; - - mat4x4 vm = view; - - vec4 transformed_pos = vm * water_pos; - water_pos.xyz = transformed_pos.xyz / transformed_pos.w; - - vec3 normal = make_normal((vm * a_pos).xyz, (vm * vec4(b_pos, 1.0)).xyz, (vm * vec4(c_pos, 1.0)).xyz); - vec3 eye = normalize(-water_pos.xyz); - - vec4 transformed_light = vm * vec4(light_point, 1.0); - - f_Light = light_colour * calc_specular(eye, normal, normalize(water_pos.xyz - (transformed_light.xyz / transformed_light.w))); - f_Fresnel = calc_fresnel(eye, normal); - - vec4 projected_pos = projection * transformed_pos; - - gl_Position = projected_pos; - - vec4 gridpos = projection * vm * original_pos; - f_WaterScreenPos.xy = (0.5 * gridpos.xy / gridpos.w) + 0.5; -} diff --git a/wgpu/examples/water/water_shader.vert.spv b/wgpu/examples/water/water_shader.vert.spv deleted file mode 100644 index 688792a7c909a4f95b8096105fe80e7fed617504..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17624 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 J>ne6#{}0qmo7w;X