├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── build.rs ├── default.nix ├── rust-toolchain.toml ├── shaders ├── Cargo.toml └── src │ ├── a_lot_of_spheres.rs │ ├── a_question_of_time.rs │ ├── apollonian.rs │ ├── atmosphere_system_test.rs │ ├── bubble_buckey_balls.rs │ ├── clouds.rs │ ├── filtering_procedurals.rs │ ├── flappy_bird.rs │ ├── galaxy_of_universes.rs │ ├── geodesic_tiling.rs │ ├── heart.rs │ ├── lib.rs │ ├── luminescence.rs │ ├── mandelbrot_smooth.rs │ ├── miracle_snowflakes.rs │ ├── morphing.rs │ ├── moving_square.rs │ ├── on_off_spikes.rs │ ├── phantom_star.rs │ ├── playing_marble.rs │ ├── protean_clouds.rs │ ├── raymarching_primitives.rs │ ├── seascape.rs │ ├── skyline.rs │ ├── soft_shadow_variation.rs │ ├── tileable_water_caustic.rs │ ├── tokyo.rs │ ├── two_tweets.rs │ └── voxel_pac_man.rs ├── shadertoys.jpg ├── shared ├── Cargo.toml └── src │ └── lib.rs └── src └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shadertoys-wgpu" 3 | version = "0.0.0" 4 | publish = false 5 | authors = [] 6 | edition = "2021" 7 | 8 | [features] 9 | default = ["use-compiled-tools"] 10 | use-installed-tools = ["spirv-builder/use-installed-tools"] 11 | use-compiled-tools = ["spirv-builder/use-compiled-tools"] 12 | 13 | [dependencies] 14 | shared = { path = "shared" } 15 | futures = { version = "0.3", default-features = false, features = [ 16 | "std", 17 | "executor" 18 | ] } 19 | wgpu = { version = "25.0.0", features = [ 20 | "spirv", 21 | "vulkan-portability" 22 | ] } 23 | winit = { git = "https://github.com/rust-windowing/winit.git", rev = "cdbdd974fbf79b82b3fb1a4bc84ed717312a3bd2" } 24 | bytemuck = "1.20.0" 25 | env_logger = "0.11.6" 26 | ouroboros = "0.18.5" 27 | 28 | [build-dependencies] 29 | spirv-builder.workspace = true 30 | 31 | [workspace] 32 | members = ["shaders", "shared"] 33 | 34 | [workspace.dependencies] 35 | spirv-builder = { git = "https://github.com/Rust-GPU/rust-gpu", rev = "0b37696e9f5edde8fa0c1363a88e6c8cb8e6ff68", default-features = false } 36 | spirv-std = { git = "https://github.com/Rust-GPU/rust-gpu", rev = "0b37696e9f5edde8fa0c1363a88e6c8cb8e6ff68" } 37 | 38 | # Compile build-dependencies in release mode with 39 | # the same settings as regular dependencies. 40 | [profile.release.build-override] 41 | opt-level = 3 42 | codegen-units = 16 43 | 44 | # HACK(eddyb) also compile debug mode's build-dependencies with optimizations, 45 | # because otherwise `rustc_codegen_spirv` (esspecially its linker) is too slow. 46 | # Also `spirv-opt` *alone* takes (just) over an hour to run, though this only 47 | # brings it down only to 10 minutes, so I've disabled it below, for now. 48 | [profile.dev.build-override] 49 | opt-level = 3 50 | 51 | # HACK(eddyb) don't optimize the shader crate, to avoid `spirv-opt` taking 52 | # a long time (10 minutes if itself was optimized, over an hour otherwise). 53 | [profile.release.package."shadertoys-shaders"] 54 | opt-level = 0 55 | 56 | [workspace.lints.rust] 57 | unexpected_cfgs = { level = "allow", check-cfg = [ 58 | 'cfg(target_arch, values("spirv"))' 59 | ] } 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [shadertoy.com] shaders ported to [Rust-GPU] 2 | 3 | ![shadertoys image](shadertoys.jpg) 4 | 5 | ## License 6 | 7 | [Rust-GPU] is dual-licensed under [Apache-2.0/MIT](https://github.com/EmbarkStudios/rust-gpu/#license) 8 | 9 | Individual shaders are licensed according to the original [shadertoy.com] entry 10 | (see each file's documentation comment for more details). 11 | 12 | [shadertoy.com]: https://shadertoy.com 13 | [Rust-GPU]: https://github.com/rust-gpu/rust-gpu 14 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use spirv_builder::{MetadataPrintout, SpirvBuilder}; 2 | use std::error::Error; 3 | 4 | fn build_shader(path_to_crate: &str) -> Result<(), Box> { 5 | let builder = SpirvBuilder::new(path_to_crate, "spirv-unknown-vulkan1.2") 6 | .print_metadata(MetadataPrintout::Full); 7 | 8 | let _result = builder.build()?; 9 | Ok(()) 10 | } 11 | 12 | fn main() -> Result<(), Box> { 13 | build_shader("shaders")?; 14 | Ok(()) 15 | } 16 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | let 2 | pkgs = import {}; 3 | in with pkgs; stdenv.mkDerivation rec { 4 | name = "rust-gpu"; 5 | 6 | # Workaround for https://github.com/NixOS/nixpkgs/issues/60919. 7 | hardeningDisable = [ "fortify" ]; 8 | 9 | # Allow cargo to download crates. 10 | SSL_CERT_FILE = "${cacert}/etc/ssl/certs/ca-bundle.crt"; 11 | 12 | buildInputs = [ 13 | pkgconfig rustup x11 libxkbcommon 14 | ]; 15 | 16 | # Runtime dependencies. 17 | LD_LIBRARY_PATH = with xlibs; stdenv.lib.makeLibraryPath [ 18 | libX11 libXcursor libXi libXrandr vulkan-loader 19 | ]; 20 | 21 | # swiftshader_env = 22 | # let swiftshader = pkgs.swiftshader.overrideAttrs (old: { 23 | # version = "2020-11-20"; 24 | # src = fetchgit { 25 | # url = "https://swiftshader.googlesource.com/SwiftShader"; 26 | # rev = "6d612051c083238db89541be4fbb2d624a9baef4"; 27 | # sha256 = "0xq317mgmbs2pzq84vvfn0nqjd38djh5jzrkyixfxkxpaaz5j6s8"; 28 | # }; 29 | # patches = [ 30 | # (writeText "no-validation.patch" '' 31 | # --- a/src/Vulkan/VkPipeline.cpp 32 | # +++ b/src/Vulkan/VkPipeline.cpp 33 | # @@ -78,1 +78,1 @@ 34 | # - opt.Run(code.data(), code.size(), &optimized); 35 | # + opt.Run(code.data(), code.size(), &optimized, spvtools::ValidatorOptions(), true); 36 | # '') 37 | # ]; 38 | # buildInputs = old.buildInputs ++ [zlib]; 39 | # hardeningDisable = [ "all" ]; 40 | # cmakeFlags = [ 41 | # "-DCMAKE_BUILD_TYPE=Debug" 42 | # "-DSWIFTSHADER_WARNINGS_AS_ERRORS=0" 43 | # "-DCMAKE_CXX_FLAGS=-DNDEBUG -O0" 44 | # # "-DSWIFTSHADER_LOGGING_LEVEL=Verbose" 45 | # ]; 46 | # dontStrip = true; 47 | # }); 48 | # in "VK_ICD_FILENAMES=${swiftshader}/share/vulkan/icd.d/vk_swiftshader_icd.json"; 49 | } 50 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly-2024-11-22" 3 | components = ["rust-src", "rustc-dev", "llvm-tools"] 4 | -------------------------------------------------------------------------------- /shaders/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shadertoys-shaders" 3 | version = "0.0.0" 4 | authors = [] 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["dylib"] 9 | 10 | [dependencies] 11 | spirv-std.workspace = true 12 | shared = { path = "../shared" } 13 | 14 | [lints] 15 | workspace = true 16 | -------------------------------------------------------------------------------- /shaders/src/a_lot_of_spheres.rs: -------------------------------------------------------------------------------- 1 | //! Ported to Rust from 2 | //! 3 | //! Original comment: 4 | //! ```glsl 5 | //! // A lot of spheres. Created by Reinder Nijhoff 2013 6 | //! // Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 7 | //! // @reindernijhoff 8 | //! // 9 | //! // https://www.shadertoy.com/view/lsX3WH 10 | //! // 11 | //! */ 12 | //! ``` 13 | 14 | use shared::*; 15 | use spirv_std::glam::{mat2, vec2, vec3, Mat2, Vec2, Vec3, Vec3Swizzles, Vec4}; 16 | 17 | // Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but 18 | // we tie #[no_std] above to the same condition, so it's fine. 19 | #[cfg(target_arch = "spirv")] 20 | use spirv_std::num_traits::Float; 21 | 22 | pub struct Inputs { 23 | pub resolution: Vec3, 24 | pub time: f32, 25 | } 26 | 27 | const SHADOW: bool = true; 28 | const REFLECTION: bool = true; 29 | 30 | const RAYCASTSTEPS: usize = 40; 31 | 32 | const EXPOSURE: f32 = 0.9; 33 | const EPSILON: f32 = 0.0001; 34 | const MAXDISTANCE: f32 = 400.0; 35 | const GRIDSIZE: f32 = 8.0; 36 | const GRIDSIZESMALL: f32 = 5.0; 37 | const MAXHEIGHT: f32 = 30.0; 38 | const SPEED: f32 = 0.5; 39 | 40 | // 41 | // math functions 42 | // 43 | 44 | const _MR: Mat2 = mat2(vec2(0.84147, 0.54030), vec2(0.54030, -0.84147)); 45 | fn hash(n: f32) -> f32 { 46 | (n.sin() * 43758.5453).fract_gl() 47 | } 48 | fn _hash2(n: f32) -> Vec2 { 49 | (vec2(n, n + 1.0).sin() * vec2(2.1459123, 3.3490423)).fract_gl() 50 | } 51 | fn hash2_vec(n: Vec2) -> Vec2 { 52 | (vec2(n.x * n.y, n.x + n.y).sin() * vec2(2.1459123, 3.3490423)).fract_gl() 53 | } 54 | fn _hash3(n: f32) -> Vec3 { 55 | (vec3(n, n + 1.0, n + 2.0).sin() * vec3(3.5453123, 4.1459123, 1.3490423)).fract_gl() 56 | } 57 | fn hash3_vec(n: Vec2) -> Vec3 { 58 | (vec3(n.x, n.y, n.x + 2.0).sin() * vec3(3.5453123, 4.1459123, 1.3490423)).fract_gl() 59 | } 60 | 61 | // 62 | // intersection functions 63 | // 64 | 65 | fn intersect_plane(ro: Vec3, rd: Vec3, height: f32, dist: &mut f32) -> bool { 66 | if rd.y == 0.0 { 67 | return false; 68 | } 69 | 70 | let mut d: f32 = -(ro.y - height) / rd.y; 71 | d = d.min(100000.0); 72 | if d > 0.0 { 73 | *dist = d; 74 | return true; 75 | } 76 | false 77 | } 78 | 79 | fn intersect_unit_sphere(ro: Vec3, rd: Vec3, sph: Vec3, dist: &mut f32, normal: &mut Vec3) -> bool { 80 | let ds: Vec3 = ro - sph; 81 | let bs: f32 = rd.dot(ds); 82 | let cs: f32 = ds.dot(ds) - 1.0; 83 | let mut ts: f32 = bs * bs - cs; 84 | 85 | if ts > 0.0 { 86 | ts = -bs - ts.sqrt(); 87 | if ts > 0.0 { 88 | *normal = ((ro + ts * rd) - sph).normalize(); 89 | *dist = ts; 90 | return true; 91 | } 92 | } 93 | false 94 | } 95 | 96 | // 97 | // Scene 98 | // 99 | 100 | fn get_sphere_offset(grid: Vec2, center: &mut Vec2) { 101 | *center = (hash2_vec(grid + vec2(43.12, 1.23)) - Vec2::splat(0.5)) * (GRIDSIZESMALL); 102 | } 103 | 104 | impl Inputs { 105 | fn get_moving_sphere_position(&self, grid: Vec2, sphere_offset: Vec2, center: &mut Vec3) { 106 | // falling? 107 | let s: f32 = 0.1 + hash(grid.x * 1.23114 + 5.342 + 74.324231 * grid.y); 108 | let t: f32 = (14. * s + self.time / s * 0.3).fract_gl(); 109 | 110 | let y: f32 = s * MAXHEIGHT * (4.0 * t * (1. - t)).abs(); 111 | let offset: Vec2 = grid + sphere_offset; 112 | 113 | *center = vec3(offset.x, y, offset.y) + 0.5 * vec3(GRIDSIZE, 2.0, GRIDSIZE); 114 | } 115 | } 116 | fn get_sphere_position(grid: Vec2, sphere_offset: Vec2, center: &mut Vec3) { 117 | let offset: Vec2 = grid + sphere_offset; 118 | *center = vec3(offset.x, 0.0, offset.y) + 0.5 * vec3(GRIDSIZE, 2.0, GRIDSIZE); 119 | } 120 | fn get_sphere_color(grid: Vec2) -> Vec3 { 121 | hash3_vec(grid + vec2(43.12 * grid.y, 12.23 * grid.x)).normalize() 122 | } 123 | 124 | impl Inputs { 125 | fn trace( 126 | &self, 127 | ro: Vec3, 128 | rd: Vec3, 129 | intersection: &mut Vec3, 130 | normal: &mut Vec3, 131 | dist: &mut f32, 132 | material: &mut i32, 133 | ) -> Vec3 { 134 | *material = 0; // sky 135 | *dist = MAXDISTANCE; 136 | let mut distcheck: f32 = 0.0; 137 | 138 | let mut sphere_center: Vec3 = Vec3::ZERO; 139 | let mut col: Vec3; 140 | let mut normalcheck: Vec3 = Vec3::ZERO; 141 | if intersect_plane(ro, rd, 0.0, &mut distcheck) && distcheck < MAXDISTANCE { 142 | *dist = distcheck; 143 | *material = 1; 144 | *normal = vec3(0.0, 1.0, 0.0); 145 | col = Vec3::ONE; 146 | } else { 147 | col = Vec3::ZERO; 148 | } 149 | 150 | // trace grid 151 | let mut pos: Vec3 = (ro / GRIDSIZE).floor() * GRIDSIZE; 152 | let ri: Vec3 = 1.0 / rd; 153 | let rs: Vec3 = rd.sign_gl() * GRIDSIZE; 154 | let mut dis: Vec3 = (pos - ro + 0.5 * Vec3::splat(GRIDSIZE) + rs * 0.5) * ri; 155 | let mut mm: Vec3; 156 | 157 | for _ in 0..RAYCASTSTEPS { 158 | if *material > 1 || ro.xz().distance(pos.xz()) > *dist + GRIDSIZE { 159 | break; 160 | } 161 | let mut offset: Vec2 = Vec2::ZERO; 162 | get_sphere_offset(pos.xz(), &mut offset); 163 | 164 | self.get_moving_sphere_position(pos.xz(), -offset, &mut sphere_center); 165 | 166 | if intersect_unit_sphere(ro, rd, sphere_center, &mut distcheck, &mut normalcheck) 167 | && distcheck < *dist 168 | { 169 | *dist = distcheck; 170 | *normal = normalcheck; 171 | *material = 2; 172 | } 173 | 174 | get_sphere_position(pos.xz(), offset, &mut sphere_center); 175 | if intersect_unit_sphere(ro, rd, sphere_center, &mut distcheck, &mut normalcheck) 176 | && distcheck < *dist 177 | { 178 | *dist = distcheck; 179 | *normal = normalcheck; 180 | col = Vec3::splat(2.0); 181 | *material = 3; 182 | } 183 | mm = dis.step(dis.zyx()); 184 | dis += mm * rs * ri; 185 | pos += mm * rs; 186 | } 187 | 188 | let mut color: Vec3 = Vec3::ZERO; 189 | if *material > 0 { 190 | *intersection = ro + rd * *dist; 191 | let map: Vec2 = (intersection.xz() / GRIDSIZE).floor() * GRIDSIZE; 192 | 193 | if *material == 1 || *material == 3 { 194 | // lightning 195 | let c: Vec3 = vec3(-GRIDSIZE, 0.0, GRIDSIZE); 196 | for x in 0..3 { 197 | for y in 0..3 { 198 | let mapoffset: Vec2 = map + vec2([c.x, c.y, c.z][x], [c.x, c.y, c.z][y]); 199 | let mut offset: Vec2 = Vec2::ZERO; 200 | get_sphere_offset(mapoffset, &mut offset); 201 | let lcolor: Vec3 = get_sphere_color(mapoffset); 202 | let mut lpos: Vec3 = Vec3::ZERO; 203 | self.get_moving_sphere_position(mapoffset, -offset, &mut lpos); 204 | 205 | let mut shadow: f32 = 1.0; 206 | 207 | if SHADOW && *material == 1 { 208 | for _ in 0..3 { 209 | for _ in 0..3 { 210 | if shadow < 1.0 { 211 | continue; 212 | } 213 | 214 | let smapoffset: Vec2 = 215 | map + vec2([c.x, c.y, c.z][x], [c.x, c.y, c.z][y]); 216 | let mut soffset: Vec2 = Vec2::ZERO; 217 | get_sphere_offset(smapoffset, &mut soffset); 218 | let mut slpos: Vec3 = Vec3::ZERO; 219 | let mut sn: Vec3 = Vec3::ZERO; 220 | get_sphere_position(smapoffset, soffset, &mut slpos); 221 | let mut sd: f32 = 0.0; 222 | if intersect_unit_sphere( 223 | *intersection, 224 | (lpos - *intersection).normalize(), 225 | slpos, 226 | &mut sd, 227 | &mut sn, 228 | ) { 229 | shadow = 0.0; 230 | } 231 | } 232 | } 233 | } 234 | color += col 235 | * lcolor 236 | * (shadow 237 | * ((lpos - *intersection).normalize().dot(*normal)).max(0.0) 238 | * (1. - (lpos.distance(*intersection) / GRIDSIZE).clamp(0.0, 1.))); 239 | } 240 | } 241 | } else { 242 | // emitter 243 | color = (1.5 + normal.dot(vec3(0.5, 0.5, -0.5))) * get_sphere_color(map); 244 | } 245 | } 246 | color 247 | } 248 | 249 | pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { 250 | let q: Vec2 = frag_coord / self.resolution.xy(); 251 | let mut p: Vec2 = Vec2::splat(-1.0) + 2.0 * q; 252 | p.x *= self.resolution.x / self.resolution.y; 253 | 254 | // camera 255 | let ce: Vec3 = vec3( 256 | (0.232 * self.time).cos() * 10.0, 257 | 6. + 3.0 * (0.3 * self.time).cos(), 258 | GRIDSIZE * (self.time / SPEED), 259 | ); 260 | let ro: Vec3 = ce; 261 | let ta: Vec3 = ro 262 | + vec3( 263 | -(0.232 * self.time).sin() * 10., 264 | -2.0 + (0.23 * self.time).cos(), 265 | 10.0, 266 | ); 267 | 268 | let roll: f32 = -0.15 * (0.5 * self.time).sin(); 269 | // camera tx 270 | let cw: Vec3 = (ta - ro).normalize(); 271 | let cp: Vec3 = vec3(roll.sin(), roll.cos(), 0.0); 272 | let cu: Vec3 = (cw.cross(cp)).normalize(); 273 | let cv: Vec3 = (cu.cross(cw)).normalize(); 274 | let mut rd: Vec3 = (p.x * cu + p.y * cv + 1.5 * cw).normalize(); 275 | // raytrace 276 | let mut material: i32 = 0; 277 | let mut normal: Vec3 = Vec3::ZERO; 278 | let mut intersection: Vec3 = Vec3::ZERO; 279 | let mut dist: f32 = 0.0; 280 | 281 | let mut col: Vec3 = self.trace( 282 | ro, 283 | rd, 284 | &mut intersection, 285 | &mut normal, 286 | &mut dist, 287 | &mut material, 288 | ); 289 | if material > 0 && REFLECTION { 290 | let ro: Vec3 = intersection + EPSILON * normal; 291 | rd = rd.reflect(normal); 292 | col += 0.05 293 | * self.trace( 294 | ro, 295 | rd, 296 | &mut intersection, 297 | &mut normal, 298 | &mut dist, 299 | &mut material, 300 | ); 301 | } 302 | 303 | col = col.powf_vec(vec3(EXPOSURE, EXPOSURE, EXPOSURE)); 304 | col = col.clamp(Vec3::ZERO, Vec3::ONE); 305 | // vigneting 306 | col *= 0.25 + 0.75 * (16.0 * q.x * q.y * (1.0 - q.x) * (1.0 - q.y)).powf(0.15); 307 | 308 | *frag_color = col.extend(1.0); 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /shaders/src/a_question_of_time.rs: -------------------------------------------------------------------------------- 1 | //! Ported to Rust from 2 | //! 3 | //! Original comment: 4 | //! ```glsl 5 | //! // Author: Rigel rui@gil.com 6 | //! // licence: https://creativecommons.org/licenses/by/4.0/ 7 | //! // link: https://www.shadertoy.com/view/lljfRD 8 | //! 9 | //! 10 | //! /* 11 | //! This was a study on circles, inspired by this artwork 12 | //! http://www.dailymail.co.uk/news/article-1236380/Worlds-largest-artwork-etched-desert-sand.html 13 | //! 14 | //! and implemented with the help of this article 15 | //! http://www.ams.org/samplings/feature-column/fcarc-kissing 16 | //! 17 | //! The structure is called an apollonian packing (or gasket) 18 | //! https://en.m.wikipedia.org/wiki/Apollonian_gasket 19 | //! 20 | //! There is a lot of apollonians in shadertoy, but not many quite like the image above. 21 | //! This one by klems is really cool. He uses a technique called a soddy circle. 22 | //! https://www.shadertoy.com/view/4s2czK 23 | //! 24 | //! This shader uses another technique called a Descartes Configuration. 25 | //! The only thing that makes this technique interesting is that it can be generalized to higher dimensions. 26 | //! */ 27 | //! ``` 28 | 29 | use shared::*; 30 | use spirv_std::glam::{ 31 | vec2, vec3, Mat2, Vec2, Vec2Swizzles, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles, 32 | }; 33 | 34 | // Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but 35 | // we tie #[no_std] above to the same condition, so it's fine. 36 | #[cfg(target_arch = "spirv")] 37 | use spirv_std::num_traits::Float; 38 | 39 | pub struct Inputs { 40 | pub resolution: Vec3, 41 | pub time: f32, 42 | pub mouse: Vec4, 43 | } 44 | 45 | // a few utility functions 46 | // a signed distance function for a rectangle 47 | fn sdf_rect(uv: Vec2, s: Vec2) -> f32 { 48 | let auv: Vec2 = uv.abs(); 49 | (auv.x - s.x).max(auv.y - s.y) 50 | } 51 | // a signed distance function for a circle 52 | fn sdf_circle(uv: Vec2, c: Vec2, r: f32) -> f32 { 53 | (uv - c).length() - r 54 | } 55 | // fills an sdf in 2d 56 | fn fill(d: f32, s: f32, i: f32) -> f32 { 57 | (smoothstep(0.0, s, d) - i).abs() 58 | } 59 | // makes a stroke of an sdf at the zero boundary 60 | fn stroke(d: f32, w: f32, s: f32, i: f32) -> f32 { 61 | (smoothstep(0.0, s, d.abs() - (w * 0.5)) - i).abs() 62 | } 63 | // a simple palette 64 | fn pal(d: f32) -> Vec3 { 65 | 0.5 * ((6.283 * d * vec3(2.0, 2.0, 1.0) + vec3(0.0, 1.4, 0.0)).cos() + Vec3::ONE) 66 | } 67 | // 2d rotation matrix 68 | fn uvr_rotate(a: f32) -> Mat2 { 69 | Mat2::from_cols_array(&[a.cos(), a.sin(), -a.sin(), a.cos()]) 70 | } 71 | // circle inversion 72 | fn inversion(uv: Vec2, r: f32) -> Vec2 { 73 | (r * r * uv) / Vec2::splat(uv.dot(uv)) 74 | } 75 | // seeded random number 76 | fn hash(s: Vec2) -> f32 { 77 | ((s.dot(vec2(12.9898, 78.2333))).sin() * 43758.5453123).fract_gl() 78 | } 79 | 80 | // this is an algorithm to construct an apollonian packing with a descartes configuration 81 | // remaps the plane to a circle at the origin and a specific radius. vec3(x,y,radius) 82 | fn apollonian(uv: Vec2) -> Vec3 { 83 | // the algorithm is recursive and must start with a initial descartes configuration 84 | // each vec3 represents a circle with the form vec3(centerx, centery, 1./radius) 85 | // the signed inverse radius is also called the bend (refer to the article above) 86 | let mut dec: [Vec3; 4] = [Vec3::ZERO, Vec3::ZERO, Vec3::ZERO, Vec3::ZERO]; 87 | // a DEC is a configuration of 4 circles tangent to each other 88 | // the easiest way to build the initial one it to construct a symetric Steiner Chain. 89 | // http://mathworld.wolfram.com/SteinerChain.html 90 | let a: f32 = 6.283 / 3.; 91 | let ra: f32 = 1.0 + (a * 0.5).sin(); 92 | let rb: f32 = 1.0 - (a * 0.5).sin(); 93 | dec[0] = vec3(0.0, 0.0, -1.0 / ra); 94 | let radius: f32 = 0.5 * (ra - rb); 95 | let bend: f32 = 1.0 / radius; 96 | for i in 1..4 { 97 | dec[i] = vec3((i as f32 * a).cos(), (i as f32 * a).sin(), bend); 98 | // if the point is in one of the starting circles we have already found our solution 99 | if (uv - dec[i].xy()).length() < radius { 100 | return (uv - dec[i].xy()).extend(radius); 101 | } 102 | } 103 | 104 | // Now that we have a starting DEC we are going to try to 105 | // find the solution for the current point 106 | for _ in 0..7 { 107 | // find the circle that is further away from the point uv, using euclidean distance 108 | let mut fi: usize = 0; 109 | let mut d: f32 = uv.distance(dec[0].xy()) - (1.0 / dec[0].z).abs(); 110 | // for some reason, the euclidean distance doesn't work for the circle with negative bend 111 | // can anyone with proper math skills, explain me why? 112 | d *= if dec[0].z < 0.0 { -0.5 } else { 1.0 }; // just scale it to make it work... 113 | for j in 1..4 { 114 | let mut fd: f32 = uv.distance(dec[j].xy()) - (1. / dec[j].z).abs(); 115 | fd *= if dec[j].z < 0.0 { -0.5 } else { 1.0 }; 116 | if fd > d { 117 | fi = j; 118 | d = fd; 119 | } 120 | } 121 | // put the cicle found in the last slot, to generate a solution 122 | // in the "direction" of the point 123 | let c: Vec3 = dec[3]; 124 | dec[3] = dec[fi]; 125 | dec[fi] = c; 126 | // generate a new solution 127 | let bend: f32 = (2.0 * (dec[0].z + dec[1].z + dec[2].z)) - dec[3].z; 128 | let center: Vec2 = (2.0 129 | * (dec[0].z * dec[0].xy() + dec[1].z * dec[1].xy() + dec[2].z * dec[2].xy()) 130 | - dec[3].z * dec[3].xy()) 131 | / bend; 132 | 133 | let solution: Vec3 = center.extend(bend); 134 | // is the solution radius is to small, quit 135 | if (1. / bend).abs() < 0.01 { 136 | break; 137 | } 138 | // if the solution contains the point return the circle 139 | if (uv - solution.xy()).length() < 1. / bend { 140 | return (uv - solution.xy()).extend(1. / bend); 141 | } 142 | // else update the descartes configuration, 143 | dec[3] = solution; 144 | // and repeat... 145 | } 146 | // if nothing is found we return by default the inner circle of the Steiner chain 147 | uv.extend(rb) 148 | } 149 | 150 | impl Inputs { 151 | fn scene(&self, mut uv: Vec2, ms: Vec4) -> Vec3 { 152 | let mut ci: Vec2 = Vec2::ZERO; 153 | 154 | // drag your mouse to apply circle inversion 155 | if ms.y != -2.0 && ms.w > -2.0 { 156 | uv = inversion(uv, 60.0.to_radians().cos()); 157 | ci = ms.xy(); 158 | } 159 | 160 | // remap uv to appolonian packing 161 | let uv_apo: Vec3 = apollonian(uv - ci); 162 | 163 | let d: f32 = 6.2830 / 360.0; 164 | let a: f32 = uv_apo.y.atan2(uv_apo.x); 165 | let r: f32 = uv_apo.xy().length(); 166 | 167 | let circle: f32 = sdf_circle(uv, uv - uv_apo.xy(), uv_apo.z); 168 | 169 | // background 170 | let mut c: Vec3 = uv.length() * pal(0.7) * 0.2; 171 | 172 | // drawing the clocks 173 | if uv_apo.z > 0.3 { 174 | c = mix(c, pal(0.75 - r * 0.1) * 0.8, fill(circle + 0.02, 0.01, 1.0)); // clock 175 | c = mix( 176 | c, 177 | pal(0.4 + r * 0.1), 178 | stroke(circle + (uv_apo.z * 0.03), uv_apo.z * 0.01, 0.005, 1.), 179 | ); // dial 180 | let h: f32 = stroke( 181 | (a + d * 15.0).rem_euclid(d * 30.) - d * 15.0, 182 | 0.02, 183 | 0.01, 184 | 1.0, 185 | ); 186 | c = mix( 187 | c, 188 | pal(0.4 + r * 0.1), 189 | h * stroke(circle + (uv_apo.z * 0.16), uv_apo.z * 0.25, 0.005, 1.0), 190 | ); // hours 191 | 192 | let m: f32 = stroke( 193 | (a + d * 15.0).rem_euclid(d * 6.0) - d * 3.0, 194 | 0.005, 195 | 0.01, 196 | 1.0, 197 | ); 198 | c = mix( 199 | c, 200 | pal(0.45 + r * 0.1), 201 | (1.0 - h) * m * stroke(circle + (uv_apo.z * 0.15), uv_apo.z * 0.1, 0.005, 1.0), 202 | ); // minutes, 203 | 204 | // needles rotation 205 | let uvrh: Vec2 = uvr_rotate( 206 | (hash(Vec2::splat(uv_apo.z)) * d * 180.0).cos().sign_gl() 207 | * d 208 | * self.time 209 | * (1.0 / uv_apo.z * 10.0) 210 | - d * 90.0, 211 | ) 212 | .transpose() 213 | * uv_apo.xy(); 214 | let uvrm: Vec2 = uvr_rotate( 215 | (hash(Vec2::splat(uv_apo.z) * 4.0) * d * 180.0) 216 | .cos() 217 | .sign_gl() 218 | * d 219 | * self.time 220 | * (1.0 / uv_apo.z * 120.0) 221 | - d * 90.0, 222 | ) 223 | .transpose() 224 | * uv_apo.xy(); 225 | // draw needles 226 | c = mix( 227 | c, 228 | pal(0.85), 229 | stroke( 230 | sdf_rect( 231 | uvrh + vec2(uv_apo.z - (uv_apo.z * 0.8), 0.0), 232 | uv_apo.z * vec2(0.4, 0.03), 233 | ), 234 | uv_apo.z * 0.01, 235 | 0.005, 236 | 1.0, 237 | ), 238 | ); 239 | c = mix( 240 | c, 241 | pal(0.9), 242 | fill( 243 | sdf_rect( 244 | uvrm + vec2(uv_apo.z - (uv_apo.z * 0.65), 0.0), 245 | uv_apo.z * vec2(0.5, 0.002), 246 | ), 247 | 0.005, 248 | 1.0, 249 | ), 250 | ); 251 | c = mix( 252 | c, 253 | pal(0.5 + r * 10.0), 254 | fill(circle + uv_apo.z - 0.02, 0.005, 1.0), 255 | ); // center 256 | // drawing the gears 257 | } else if uv_apo.z > 0.05 { 258 | let uvrg: Vec2 = uvr_rotate( 259 | (hash(Vec2::splat(uv_apo.z + 2.0)) * d * 180.0) 260 | .cos() 261 | .sign_gl() 262 | * d 263 | * self.time 264 | * (1.0 / uv_apo.z * 20.0), 265 | ) 266 | .transpose() 267 | * uv_apo.xy(); 268 | let g: f32 = stroke( 269 | (uvrg.y.atan2(uvrg.x) + d * 22.5).rem_euclid(d * 45.) - d * 22.5, 270 | 0.3, 271 | 0.05, 272 | 1.0, 273 | ); 274 | let size: Vec2 = uv_apo.z * vec2(0.45, 0.08); 275 | c = mix( 276 | c, 277 | pal(0.55 - r * 0.6), 278 | fill(circle + g * (uv_apo.z * 0.2) + 0.01, 0.001, 1.) 279 | * fill(circle + (uv_apo.z * 0.6), 0.005, 0.0), 280 | ); 281 | c = mix( 282 | c, 283 | pal(0.55 - r * 0.6), 284 | fill( 285 | sdf_rect(uvrg, size).min(sdf_rect(uvrg, size.yx())), 286 | 0.005, 287 | 1., 288 | ), 289 | ); 290 | // drawing the screws 291 | } else { 292 | let size: Vec2 = uv_apo.z * vec2(0.5, 0.1); 293 | c = mix( 294 | c, 295 | pal(0.85 - (uv_apo.z * 2.0)), 296 | fill(circle + 0.01, 0.007, 1.0), 297 | ); 298 | c = mix( 299 | c, 300 | pal(0.8 - (uv_apo.z * 3.)), 301 | fill( 302 | sdf_rect(uv_apo.xy(), size).min(sdf_rect(uv_apo.xy(), size.yx())), 303 | 0.002, 304 | 1.0, 305 | ), 306 | ); 307 | } 308 | c 309 | } 310 | 311 | pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { 312 | let uv: Vec2 = (frag_coord - self.resolution.xy() * 0.5) / self.resolution.y; 313 | let ms: Vec4 = (self.mouse - self.resolution.xyxy() * 0.5) / self.resolution.y; 314 | *frag_color = self.scene(uv * 4., ms * 4.).extend(1.0); 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /shaders/src/apollonian.rs: -------------------------------------------------------------------------------- 1 | //! Ported to Rust from 2 | //! 3 | //! Original comment: 4 | //! ```glsl 5 | //! // Created by inigo quilez - iq/2013 6 | //! // License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. 7 | //! // 8 | //! // I can't recall where I learnt about this fractal. 9 | //! // 10 | //! // Coloring and fake occlusions are done by orbit trapping, as usual. 11 | //! ``` 12 | 13 | use shared::*; 14 | use spirv_std::glam::{vec2, vec3, Vec2, Vec2Swizzles, Vec3, Vec3Swizzles, Vec4}; 15 | 16 | // Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but 17 | // we tie #[no_std] above to the same condition, so it's fine. 18 | #[cfg(target_arch = "spirv")] 19 | use spirv_std::num_traits::Float; 20 | 21 | pub struct Inputs { 22 | pub resolution: Vec3, 23 | pub time: f32, 24 | pub mouse: Vec4, 25 | } 26 | 27 | pub struct State { 28 | inputs: Inputs, 29 | orb: Vec4, 30 | } 31 | 32 | impl State { 33 | pub fn new(inputs: Inputs) -> Self { 34 | State { 35 | inputs, 36 | orb: Vec4::ZERO, 37 | } 38 | } 39 | } 40 | 41 | const HW_PERFORMANCE: usize = 1; 42 | 43 | // Antialiasing level 44 | const AA: usize = if HW_PERFORMANCE == 0 { 45 | 1 46 | } else { 47 | 2 // Make it 3 if you have a fast machine 48 | }; 49 | 50 | impl State { 51 | fn map(&mut self, mut p: Vec3, s: f32) -> f32 { 52 | let mut scale: f32 = 1.0; 53 | self.orb = Vec4::splat(1000.0); 54 | for _ in 0..8 { 55 | p = Vec3::splat(-1.0) + 2.0 * (0.5 * p + Vec3::splat(0.5)).fract_gl(); 56 | 57 | let r2: f32 = p.dot(p); 58 | 59 | self.orb = self.orb.min(p.abs().extend(r2)); 60 | 61 | let k: f32 = s / r2; 62 | p *= k; 63 | scale *= k; 64 | } 65 | 0.25 * p.y.abs() / scale 66 | } 67 | 68 | fn trace(&mut self, ro: Vec3, rd: Vec3, s: f32) -> f32 { 69 | let maxd = 30.0; 70 | let mut t: f32 = 0.01; 71 | 72 | for _ in 0..512 { 73 | let precis = 0.001 * t; 74 | 75 | let h: f32 = self.map(ro + rd * t, s); 76 | if h < precis || t > maxd { 77 | break; 78 | } 79 | t += h; 80 | } 81 | if t > maxd { 82 | t = -1.0; 83 | } 84 | t 85 | } 86 | 87 | fn calc_normal(&mut self, pos: Vec3, t: f32, s: f32) -> Vec3 { 88 | let precis: f32 = 0.001 * t; 89 | 90 | let e: Vec2 = vec2(1.0, -1.0) * precis; 91 | (e.xyy() * self.map(pos + e.xyy(), s) 92 | + e.yyx() * self.map(pos + e.yyx(), s) 93 | + e.yxy() * self.map(pos + e.yxy(), s) 94 | + e.xxx() * self.map(pos + e.xxx(), s)) 95 | .normalize() 96 | } 97 | 98 | fn render(&mut self, ro: Vec3, rd: Vec3, anim: f32) -> Vec3 { 99 | // trace 100 | let mut col: Vec3 = Vec3::ZERO; 101 | let t: f32 = self.trace(ro, rd, anim); 102 | if t > 0.0 { 103 | let tra: Vec4 = self.orb; 104 | let pos: Vec3 = ro + t * rd; 105 | let nor: Vec3 = self.calc_normal(pos, t, anim); 106 | 107 | // lighting 108 | let light1: Vec3 = vec3(0.577, 0.577, -0.577); 109 | let light2: Vec3 = vec3(-0.707, 0.000, 0.707); 110 | let key: f32 = light1.dot(nor).clamp(0.0, 1.0); 111 | let bac: f32 = (0.2 + 0.8 * light2.dot(nor)).clamp(0.0, 1.0); 112 | let amb: f32 = 0.7 + 0.3 * nor.y; 113 | let ao: f32 = (tra.w * 2.0).clamp(0.0, 1.0).powf(1.2); 114 | 115 | let mut brdf: Vec3 = 1.0 * vec3(0.40, 0.40, 0.40) * amb * ao; 116 | brdf += 1.0 * vec3(1.00, 1.00, 1.00) * key * ao; 117 | brdf += 1.0 * vec3(0.40, 0.40, 0.40) * bac * ao; 118 | 119 | // material 120 | let mut rgb: Vec3 = Vec3::ONE; 121 | rgb = mix(rgb, vec3(1.0, 0.80, 0.2), (6.0 * tra.y).clamp(0.0, 1.0)); 122 | rgb = mix( 123 | rgb, 124 | vec3(1.0, 0.55, 0.0), 125 | (1.0 - 2.0 * tra.z).clamp(0.0, 1.0).powf(8.0), 126 | ); 127 | 128 | // color 129 | col = rgb * brdf * (-0.2 * t).exp(); 130 | } 131 | 132 | col.sqrt() 133 | } 134 | 135 | pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { 136 | let time: f32 = self.inputs.time * 0.25 + 0.01 * self.inputs.mouse.x; 137 | let anim: f32 = 1.1 + 0.5 * smoothstep(-0.3, 0.3, (0.1 * self.inputs.time).cos()); 138 | let mut tot: Vec3 = Vec3::ZERO; 139 | 140 | for jj in 0..AA { 141 | for ii in 0..AA { 142 | let q: Vec2 = frag_coord + vec2(ii as f32, jj as f32) / AA as f32; 143 | let p: Vec2 = (2.0 * q - self.inputs.resolution.xy()) / self.inputs.resolution.y; 144 | 145 | // camera 146 | let ro: Vec3 = vec3( 147 | 2.8 * (0.1 + 0.33 * time).cos(), 148 | 0.4 + 0.30 * (0.37 * time).cos(), 149 | 2.8 * (0.5 + 0.35 * time).cos(), 150 | ); 151 | let ta: Vec3 = vec3( 152 | 1.9 * (1.2 + 0.41 * time).cos(), 153 | 0.4 + 0.10 * (0.27 * time).cos(), 154 | 1.9 * (2.0 + 0.38 * time).cos(), 155 | ); 156 | let roll: f32 = 0.2 * (0.1 * time).cos(); 157 | let cw: Vec3 = (ta - ro).normalize(); 158 | let cp: Vec3 = vec3(roll.sin(), roll.cos(), 0.0); 159 | let cu: Vec3 = cw.cross(cp).normalize(); 160 | let cv: Vec3 = cu.cross(cw).normalize(); 161 | let rd: Vec3 = (p.x * cu + p.y * cv + 2.0 * cw).normalize(); 162 | 163 | tot += self.render(ro, rd, anim); 164 | } 165 | } 166 | 167 | tot = tot / (AA * AA) as f32; 168 | 169 | *frag_color = tot.extend(1.0); 170 | } 171 | 172 | pub fn main_vr( 173 | &mut self, 174 | frag_color: &mut Vec4, 175 | _frag_coord: Vec2, 176 | frag_ray_ori: Vec3, 177 | freag_ray_dir: Vec3, 178 | ) { 179 | let _time: f32 = self.inputs.time * 0.25 + 0.01 * self.inputs.mouse.x; 180 | let anim: f32 = 1.1 + 0.5 * smoothstep(-0.3, 0.3, (0.1 * self.inputs.time).cos()); 181 | 182 | let col: Vec3 = self.render(frag_ray_ori + vec3(0.82, 1.2, -0.3), freag_ray_dir, anim); 183 | *frag_color = col.extend(1.0); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /shaders/src/atmosphere_system_test.rs: -------------------------------------------------------------------------------- 1 | //! Ported to Rust from 2 | //! 3 | //! Original comment: 4 | //! ```glsl 5 | //! // ---------------------------------------------------------------------------- 6 | //! // Rayleigh and Mie scattering atmosphere system 7 | //! // 8 | //! // implementation of the techniques described here: 9 | //! // http://www.scratchapixel.com/old/lessons/3d-advanced-lessons/simulating-the-colors-of-the-sky/atmospheric-scattering/ 10 | //! // ---------------------------------------------------------------------------- 11 | //! ``` 12 | 13 | use spirv_std::glam::{vec2, vec3, Mat3, Vec2, Vec3, Vec3Swizzles, Vec4}; 14 | 15 | // Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but 16 | // we tie #[no_std] above to the same condition, so it's fine. 17 | #[cfg(target_arch = "spirv")] 18 | use spirv_std::num_traits::Float; 19 | 20 | pub struct Inputs { 21 | pub resolution: Vec3, 22 | pub time: f32, 23 | pub mouse: Vec4, 24 | } 25 | 26 | pub struct State { 27 | inputs: Inputs, 28 | sun_dir: Vec3, 29 | } 30 | 31 | impl State { 32 | pub fn new(inputs: Inputs) -> Self { 33 | State { 34 | inputs, 35 | sun_dir: vec3(0.0, 1.0, 0.0), 36 | } 37 | } 38 | } 39 | 40 | use core::f32::consts::PI; 41 | 42 | #[derive(Copy, Clone)] 43 | struct Ray { 44 | origin: Vec3, 45 | direction: Vec3, 46 | } 47 | const _BIAS: f32 = 1e-4; // small offset to avoid self-intersections 48 | 49 | struct Sphere { 50 | origin: Vec3, 51 | radius: f32, 52 | _material: i32, 53 | } 54 | 55 | struct _Plane { 56 | direction: Vec3, 57 | distance: f32, 58 | material: i32, 59 | } 60 | 61 | fn rotate_around_x(angle_degrees: f32) -> Mat3 { 62 | let angle: f32 = angle_degrees.to_radians(); 63 | let _sin: f32 = angle.sin(); 64 | let _cos: f32 = angle.cos(); 65 | Mat3::from_cols_array(&[1.0, 0.0, 0.0, 0.0, _cos, -_sin, 0.0, _sin, _cos]) 66 | } 67 | 68 | fn get_primary_ray(cam_local_point: Vec3, cam_origin: &mut Vec3, cam_look_at: &mut Vec3) -> Ray { 69 | let fwd: Vec3 = (*cam_look_at - *cam_origin).normalize(); 70 | let mut up: Vec3 = vec3(0.0, 1.0, 0.0); 71 | let right: Vec3 = up.cross(fwd); 72 | up = fwd.cross(right); 73 | 74 | Ray { 75 | origin: *cam_origin, 76 | direction: (fwd + up * cam_local_point.y + right * cam_local_point.x).normalize(), 77 | } 78 | } 79 | 80 | fn isect_sphere(ray: Ray, sphere: Sphere, t0: &mut f32, t1: &mut f32) -> bool { 81 | let rc: Vec3 = sphere.origin - ray.origin; 82 | let radius2: f32 = sphere.radius * sphere.radius; 83 | let tca: f32 = rc.dot(ray.direction); 84 | let d2: f32 = rc.dot(rc) - tca * tca; 85 | if d2 > radius2 { 86 | return false; 87 | } 88 | let thc: f32 = (radius2 - d2).sqrt(); 89 | *t0 = tca - thc; 90 | *t1 = tca + thc; 91 | true 92 | } 93 | 94 | // scattering coefficients at sea level (m) 95 | const BETA_R: Vec3 = vec3(5.5e-6, 13.0e-6, 22.4e-6); // Rayleigh 96 | const BETA_M: Vec3 = vec3(21e-6, 21e-6, 21e-6); // Mie 97 | 98 | // scale height (m) 99 | // thickness of the atmosphere if its density were uniform 100 | const H_R: f32 = 7994.0; // Rayleigh 101 | const H_M: f32 = 1200.0; // Mie 102 | 103 | fn rayleigh_phase_func(mu: f32) -> f32 { 104 | 3.0 * (1.0 + mu*mu) 105 | / //------------------------ 106 | (16.0 * PI) 107 | } 108 | 109 | // Henyey-Greenstein phase function factor [-1, 1] 110 | // represents the average cosine of the scattered directions 111 | // 0 is isotropic scattering 112 | // > 1 is forward scattering, < 1 is backwards 113 | const G: f32 = 0.76; 114 | fn henyey_greenstein_phase_func(mu: f32) -> f32 { 115 | (1. - G*G) 116 | / //--------------------------------------------- 117 | ((4. * PI) * (1. + G*G - 2.*G*mu).powf(1.5)) 118 | } 119 | 120 | // Schlick Phase Function factor 121 | // Pharr and Humphreys [2004] equivalence to g above 122 | const K: f32 = 1.55 * G - 0.55 * (G * G * G); 123 | fn schlick_phase_func(mu: f32) -> f32 { 124 | (1. - K*K) 125 | / //------------------------------------------- 126 | (4. * PI * (1. + K*mu) * (1. + K*mu)) 127 | } 128 | 129 | const EARTH_RADIUS: f32 = 6360e3; // (m) 130 | const ATMOSPHERE_RADIUS: f32 = 6420e3; // (m) 131 | 132 | const SUN_POWER: f32 = 20.0; 133 | 134 | const ATMOSPHERE: Sphere = Sphere { 135 | origin: Vec3::ZERO, 136 | radius: ATMOSPHERE_RADIUS, 137 | _material: 0, 138 | }; 139 | 140 | const NUM_SAMPLES: i32 = 16; 141 | const NUM_SAMPLES_LIGHT: i32 = 8; 142 | 143 | fn get_sun_light(ray: Ray, optical_depth_r: &mut f32, optical_depth_m: &mut f32) -> bool { 144 | let mut t0: f32 = 0.0; 145 | let mut t1: f32 = 0.0; 146 | isect_sphere(ray, ATMOSPHERE, &mut t0, &mut t1); 147 | 148 | let mut march_pos: f32 = 0.0; 149 | let march_step: f32 = t1 / NUM_SAMPLES_LIGHT as f32; 150 | 151 | for _ in 0..NUM_SAMPLES_LIGHT { 152 | let s: Vec3 = ray.origin + ray.direction * (march_pos + 0.5 * march_step); 153 | let height: f32 = s.length() - EARTH_RADIUS; 154 | if height < 0.0 { 155 | return false; 156 | } 157 | 158 | *optical_depth_r += (-height / H_R).exp() * march_step; 159 | *optical_depth_m += (-height / H_M).exp() * march_step; 160 | 161 | march_pos += march_step; 162 | } 163 | true 164 | } 165 | 166 | impl State { 167 | fn get_incident_light(&self, ray: Ray) -> Vec3 { 168 | // "pierce" the atmosphere with the viewing ray 169 | let mut t0: f32 = 0.0; 170 | let mut t1: f32 = 0.0; 171 | if !isect_sphere(ray, ATMOSPHERE, &mut t0, &mut t1) { 172 | return Vec3::ZERO; 173 | } 174 | 175 | let march_step: f32 = t1 / NUM_SAMPLES as f32; 176 | 177 | // cosine of angle between view and light directions 178 | let mu: f32 = ray.direction.dot(self.sun_dir); 179 | 180 | // Rayleigh and Mie phase functions 181 | // A black box indicating how light is interacting with the material 182 | // Similar to BRDF except 183 | // * it usually considers a single angle 184 | // (the phase angle between 2 directions) 185 | // * integrates to 1 over the entire sphere of directions 186 | let phase_r: f32 = rayleigh_phase_func(mu); 187 | let phase_m: f32 = if true { 188 | henyey_greenstein_phase_func(mu) 189 | } else { 190 | schlick_phase_func(mu) 191 | }; 192 | 193 | // optical depth (or "average density") 194 | // represents the accumulated extinction coefficients 195 | // along the path, multiplied by the length of that path 196 | let mut optical_depth_r: f32 = 0.0; 197 | let mut optical_depth_m: f32 = 0.0; 198 | 199 | let mut sum_r: Vec3 = Vec3::ZERO; 200 | let mut sum_m: Vec3 = Vec3::ZERO; 201 | let mut march_pos: f32 = 0.0; 202 | 203 | for _ in 0..NUM_SAMPLES { 204 | let s: Vec3 = ray.origin + ray.direction * (march_pos + 0.5 * march_step); 205 | let height: f32 = s.length() - EARTH_RADIUS; 206 | 207 | // integrate the height scale 208 | let hr: f32 = (-height / H_R).exp() * march_step; 209 | let hm: f32 = (-height / H_M).exp() * march_step; 210 | optical_depth_r += hr; 211 | optical_depth_m += hm; 212 | 213 | // gather the sunlight 214 | let light_ray: Ray = Ray { 215 | origin: s, 216 | direction: self.sun_dir, 217 | }; 218 | let mut optical_depth_light_r: f32 = 0.0; 219 | let mut optical_depth_light_m: f32 = 0.0; 220 | let overground: bool = get_sun_light( 221 | light_ray, 222 | &mut optical_depth_light_r, 223 | &mut optical_depth_light_m, 224 | ); 225 | 226 | if overground { 227 | let tau: Vec3 = BETA_R * (optical_depth_r + optical_depth_light_r) 228 | + BETA_M * 1.1 * (optical_depth_m + optical_depth_light_m); 229 | let attenuation: Vec3 = Vec3::exp(-tau); 230 | 231 | sum_r += hr * attenuation; 232 | sum_m += hm * attenuation; 233 | } 234 | 235 | march_pos += march_step; 236 | } 237 | 238 | SUN_POWER * (sum_r * phase_r * BETA_R + sum_m * phase_m * BETA_M) 239 | } 240 | 241 | pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { 242 | let aspect_ratio: Vec2 = vec2(self.inputs.resolution.x / self.inputs.resolution.y, 1.0); 243 | let fov: f32 = 45.0.to_radians().tan(); 244 | let point_ndc: Vec2 = frag_coord / self.inputs.resolution.xy(); 245 | let point_cam: Vec3 = ((2.0 * point_ndc - Vec2::ONE) * aspect_ratio * fov).extend(-1.0); 246 | 247 | let col: Vec3; 248 | 249 | // sun 250 | let rot: Mat3 = rotate_around_x(-(self.inputs.time / 2.0).sin().abs() * 90.0); 251 | self.sun_dir = rot.transpose() * self.sun_dir; 252 | 253 | if self.inputs.mouse.z < 0.1 { 254 | // sky dome angles 255 | let p: Vec3 = point_cam; 256 | let z2: f32 = p.x * p.x + p.y * p.y; 257 | let phi: f32 = p.y.atan2(p.x); 258 | let theta: f32 = (1.0 - z2).acos(); 259 | let dir: Vec3 = vec3( 260 | theta.sin() * phi.cos(), 261 | theta.cos(), 262 | theta.sin() * phi.sin(), 263 | ); 264 | 265 | let ray: Ray = Ray { 266 | origin: vec3(0.0, EARTH_RADIUS + 1.0, 0.0), 267 | direction: dir, 268 | }; 269 | 270 | col = self.get_incident_light(ray); 271 | } else { 272 | let mut eye: Vec3 = vec3(0.0, EARTH_RADIUS + 1.0, 0.0); 273 | let mut look_at: Vec3 = vec3(0.0, EARTH_RADIUS + 1.5, -1.0); 274 | 275 | let ray: Ray = get_primary_ray(point_cam, &mut eye, &mut look_at); 276 | 277 | if ray.direction.dot(vec3(0.0, 1.0, 0.0)) > 0.0 { 278 | col = self.get_incident_light(ray); 279 | } else { 280 | col = Vec3::splat(0.333); 281 | } 282 | } 283 | 284 | *frag_color = col.extend(1.0); 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /shaders/src/clouds.rs: -------------------------------------------------------------------------------- 1 | //! Ported to Rust from 2 | 3 | use shared::*; 4 | use spirv_std::glam::{mat2, vec2, vec3, Mat2, Vec2, Vec3, Vec3Swizzles, Vec4}; 5 | 6 | pub struct Inputs { 7 | pub resolution: Vec3, 8 | pub time: f32, 9 | } 10 | 11 | const CLOUD_SCALE: f32 = 1.1; 12 | const SPEED: f32 = 0.03; 13 | const CLOUD_DARK: f32 = 0.5; 14 | const CLOUD_LIGHT: f32 = 0.3; 15 | const CLOUD_COVER: f32 = 0.2; 16 | const CLOUD_ALPHA: f32 = 8.0; 17 | const SKY_TINT: f32 = 0.5; 18 | const SKY_COLOUR1: Vec3 = vec3(0.2, 0.4, 0.6); 19 | const SKY_COLOUR2: Vec3 = vec3(0.4, 0.7, 1.0); 20 | 21 | const M: Mat2 = mat2(vec2(1.6, 1.2), vec2(-1.2, 1.6)); 22 | 23 | fn hash(mut p: Vec2) -> Vec2 { 24 | p = vec2(p.dot(vec2(127.1, 311.7)), p.dot(vec2(269.5, 183.3))); 25 | Vec2::splat(-1.0) + 2.0 * (p.sin() * 43758.5453123).fract_gl() 26 | } 27 | 28 | fn noise(p: Vec2) -> f32 { 29 | const K1: f32 = 0.366025404; // (sqrt(3)-1)/2; 30 | const K2: f32 = 0.211324865; // (3-sqrt(3))/6; 31 | let i: Vec2 = (p + Vec2::splat((p.x + p.y) * K1)).floor(); 32 | let a: Vec2 = p - i + Vec2::splat((i.x + i.y) * K2); 33 | let o: Vec2 = if a.x > a.y { 34 | vec2(1.0, 0.0) 35 | } else { 36 | vec2(0.0, 1.0) 37 | }; //vec2 of = 0.5 + 0.5*vec2(sign(a.x-a.y), sign(a.y-a.x)); 38 | let b: Vec2 = a - o + Vec2::splat(K2); 39 | let c: Vec2 = a - Vec2::splat(1.0 - 2.0 * K2); 40 | let h: Vec3 = (Vec3::splat(0.5) - vec3(a.dot(a), b.dot(b), c.dot(c))).max(Vec3::ZERO); 41 | let n: Vec3 = (h * h * h * h) 42 | * vec3( 43 | a.dot(hash(i + Vec2::ZERO)), 44 | b.dot(hash(i + o)), 45 | c.dot(hash(i + Vec2::splat(1.0))), 46 | ); 47 | n.dot(Vec3::splat(70.0)) 48 | } 49 | 50 | fn fbm(mut n: Vec2) -> f32 { 51 | let mut total: f32 = 0.0; 52 | let mut amplitude: f32 = 0.1; 53 | for _ in 0..7 { 54 | total += noise(n) * amplitude; 55 | let m = M; 56 | n = m.transpose() * n; 57 | amplitude *= 0.4; 58 | } 59 | total 60 | } 61 | 62 | // ----------------------------------------------- 63 | 64 | impl Inputs { 65 | pub fn main_image(&self, frag_color: &mut Vec4, frag_coord: Vec2) { 66 | let p: Vec2 = frag_coord / self.resolution.xy(); 67 | let mut uv: Vec2 = p * vec2(self.resolution.x / self.resolution.y, 1.0); 68 | let mut time: f32 = self.time * SPEED; 69 | let q: f32 = fbm(uv * CLOUD_SCALE * 0.5); 70 | 71 | //ridged noise shape 72 | let mut r: f32 = 0.0; 73 | uv *= CLOUD_SCALE; 74 | uv -= Vec2::splat(q - time); 75 | let mut weight: f32 = 0.8; 76 | for _ in 0..8 { 77 | r += (weight * noise(uv)).abs(); 78 | let m = M; 79 | uv = m.transpose() * uv + Vec2::splat(time); 80 | weight *= 0.7; 81 | } 82 | 83 | //noise shape 84 | let mut f: f32 = 0.0; 85 | uv = p * vec2(self.resolution.x / self.resolution.y, 1.0); 86 | uv *= CLOUD_SCALE; 87 | uv -= Vec2::splat(q - time); 88 | weight = 0.7; 89 | for _ in 0..8 { 90 | f += weight * noise(uv); 91 | let m = M; 92 | uv = m.transpose() * uv + Vec2::splat(time); 93 | weight *= 0.6; 94 | } 95 | 96 | f *= r + f; 97 | 98 | //noise colour 99 | let mut c: f32 = 0.0; 100 | time = self.time * SPEED * 2.0; 101 | uv = p * vec2(self.resolution.x / self.resolution.y, 1.0); 102 | uv *= CLOUD_SCALE * 2.0; 103 | uv -= Vec2::splat(q - time); 104 | weight = 0.4; 105 | for _ in 0..7 { 106 | c += weight * noise(uv); 107 | let m = M; 108 | uv = m.transpose() * uv + Vec2::splat(time); 109 | weight *= 0.6; 110 | } 111 | 112 | //noise ridge colour 113 | let mut c1: f32 = 0.0; 114 | time = self.time * SPEED * 3.0; 115 | uv = p * vec2(self.resolution.x / self.resolution.y, 1.0); 116 | uv *= CLOUD_SCALE * 3.0; 117 | uv -= Vec2::splat(q - time); 118 | weight = 0.4; 119 | for _ in 0..7 { 120 | c1 += (weight * noise(uv)).abs(); 121 | let m = M; 122 | uv = m.transpose() * uv + Vec2::splat(time); 123 | weight *= 0.6; 124 | } 125 | 126 | c += c1; 127 | 128 | let skycolour: Vec3 = mix(SKY_COLOUR2, SKY_COLOUR1, p.y); 129 | let cloudcolour: Vec3 = 130 | vec3(1.1, 1.1, 0.9) * (CLOUD_DARK + CLOUD_LIGHT * c).clamp(0.0, 1.0); 131 | 132 | f = CLOUD_COVER + CLOUD_ALPHA * f * r; 133 | 134 | let result: Vec3 = mix( 135 | skycolour, 136 | (SKY_TINT * skycolour + cloudcolour).clamp(Vec3::ZERO, Vec3::splat(1.0)), 137 | (f + c).clamp(0.0, 1.0), 138 | ); 139 | 140 | *frag_color = result.extend(1.0); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /shaders/src/filtering_procedurals.rs: -------------------------------------------------------------------------------- 1 | //! Ported to Rust from 2 | //! 3 | //! Original comment: 4 | //! ```glsl 5 | //! // The MIT License 6 | //! // Copyright © 2013 Inigo Quilez 7 | //! // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | //! 9 | //! // A test on using ray differentials (only primary rays for now) to choose texture filtering 10 | //! // footprint, and adaptively supersample/filter the procedural texture/patter (up to a rate 11 | //! // of 10x10). 12 | //! 13 | //! // This solves texture aliasing without resorting to full-screen 10x10 supersampling, which would 14 | //! // involve doing raytracing and lighting 10x10 times (not realtime at all). 15 | //! 16 | //! // The tecnique should be used to filter every texture independently. The ratio of the supersampling 17 | //! // could be inveresely proportional to the screen/lighing supersampling rate such that the cost 18 | //! // of texturing would be constant no matter the final image quality settings. 19 | //! */ 20 | //! ``` 21 | 22 | use shared::*; 23 | use spirv_std::arch::Derivative; 24 | use spirv_std::glam::{vec2, vec3, vec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles}; 25 | 26 | // Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but 27 | // we tie #[no_std] above to the same condition, so it's fine. 28 | #[cfg(target_arch = "spirv")] 29 | use spirv_std::num_traits::Float; 30 | 31 | pub struct Inputs { 32 | pub resolution: Vec3, 33 | pub time: f32, 34 | pub mouse: Vec4, 35 | } 36 | 37 | //=============================================================================================== 38 | //=============================================================================================== 39 | 40 | const MAX_SAMPLES: i32 = 10; // 10*10 41 | 42 | //=============================================================================================== 43 | //=============================================================================================== 44 | // noise implementation 45 | //=============================================================================================== 46 | //=============================================================================================== 47 | 48 | fn hash3(mut p: Vec3) -> Vec3 { 49 | p = vec3( 50 | p.dot(vec3(127.1, 311.7, 74.7)), 51 | p.dot(vec3(269.5, 183.3, 246.1)), 52 | p.dot(vec3(113.5, 271.9, 124.6)), 53 | ); 54 | 55 | -Vec3::ONE + 2.0 * (p.sin() * 13.5453123).fract_gl() 56 | } 57 | 58 | fn noise(p: Vec3) -> f32 { 59 | let i: Vec3 = p.floor(); 60 | let f: Vec3 = p.fract_gl(); 61 | 62 | let u: Vec3 = f * f * (Vec3::splat(3.0) - 2.0 * f); 63 | 64 | mix( 65 | mix( 66 | mix( 67 | hash3(i + vec3(0.0, 0.0, 0.0)).dot(f - vec3(0.0, 0.0, 0.0)), 68 | hash3(i + vec3(1.0, 0.0, 0.0)).dot(f - vec3(1.0, 0.0, 0.0)), 69 | u.x, 70 | ), 71 | mix( 72 | hash3(i + vec3(0.0, 1.0, 0.0)).dot(f - vec3(0.0, 1.0, 0.0)), 73 | hash3(i + vec3(1.0, 1.0, 0.0)).dot(f - vec3(1.0, 1.0, 0.0)), 74 | u.x, 75 | ), 76 | u.y, 77 | ), 78 | mix( 79 | mix( 80 | hash3(i + vec3(0.0, 0.0, 1.0)).dot(f - vec3(0.0, 0.0, 1.0)), 81 | hash3(i + vec3(1.0, 0.0, 1.0)).dot(f - vec3(1.0, 0.0, 1.0)), 82 | u.x, 83 | ), 84 | mix( 85 | hash3(i + vec3(0.0, 1.0, 1.0)).dot(f - vec3(0.0, 1.0, 1.0)), 86 | hash3(i + vec3(1.0, 1.0, 1.0)).dot(f - vec3(1.0, 1.0, 1.0)), 87 | u.x, 88 | ), 89 | u.y, 90 | ), 91 | u.z, 92 | ) 93 | } 94 | 95 | //=============================================================================================== 96 | //=============================================================================================== 97 | // sphere implementation 98 | //=============================================================================================== 99 | //=============================================================================================== 100 | 101 | fn soft_shadow_sphere(ro: Vec3, rd: Vec3, sph: Vec4) -> f32 { 102 | let oc: Vec3 = sph.xyz() - ro; 103 | let b: f32 = oc.dot(rd); 104 | 105 | let mut res: f32 = 1.0; 106 | if b > 0.0 { 107 | let h: f32 = oc.dot(oc) - b * b - sph.w * sph.w; 108 | res = smoothstep(0.0, 1.0, 2.0 * h / b); 109 | } 110 | res 111 | } 112 | 113 | fn occ_sphere(sph: Vec4, pos: Vec3, nor: Vec3) -> f32 { 114 | let di: Vec3 = sph.xyz() - pos; 115 | let l: f32 = di.length(); 116 | 1.0 - nor.dot(di / l) * sph.w * sph.w / (l * l) 117 | } 118 | 119 | fn i_sphere(ro: Vec3, rd: Vec3, sph: Vec4) -> f32 { 120 | let mut t: f32 = -1.0; 121 | let ce: Vec3 = ro - sph.xyz(); 122 | let b: f32 = rd.dot(ce); 123 | let c: f32 = ce.dot(ce) - sph.w * sph.w; 124 | let h: f32 = b * b - c; 125 | if h > 0.0 { 126 | t = -b - h.sqrt(); 127 | } 128 | 129 | t 130 | } 131 | 132 | //=============================================================================================== 133 | //=============================================================================================== 134 | // scene 135 | //=============================================================================================== 136 | //=============================================================================================== 137 | 138 | // spheres 139 | const SC0: Vec4 = vec4(0.0, 1.0, 0.0, 1.0); 140 | const SC1: Vec4 = vec4(0.0, 1.0, 14.0, 4.0); 141 | const SC2: Vec4 = vec4(-11.0, 1.0, 12.0, 4.0); 142 | const SC3: Vec4 = vec4(13.0, 1.0, -10.0, 4.0); 143 | 144 | fn intersect( 145 | ro: Vec3, 146 | rd: Vec3, 147 | pos: &mut Vec3, 148 | nor: &mut Vec3, 149 | occ: &mut f32, 150 | matid: &mut f32, 151 | ) -> f32 { 152 | // raytrace 153 | let mut tmin: f32 = 10000.0; 154 | *nor = Vec3::ZERO; 155 | *occ = 1.0; 156 | *pos = Vec3::ZERO; 157 | 158 | // raytrace-plane 159 | let mut h: f32 = (0.0 - ro.y) / rd.y; 160 | if h > 0.0 { 161 | tmin = h; 162 | *nor = vec3(0.0, 1.0, 0.0); 163 | *pos = ro + h * rd; 164 | *matid = 0.0; 165 | *occ = occ_sphere(SC0, *pos, *nor) 166 | * occ_sphere(SC1, *pos, *nor) 167 | * occ_sphere(SC2, *pos, *nor) 168 | * occ_sphere(SC3, *pos, *nor); 169 | } 170 | 171 | // raytrace-sphere 172 | h = i_sphere(ro, rd, SC0); 173 | if h > 0.0 && h < tmin { 174 | tmin = h; 175 | *pos = ro + h * rd; 176 | *nor = (*pos - SC0.xyz()).normalize(); 177 | *matid = 1.0; 178 | *occ = 0.5 + 0.5 * nor.y; 179 | } 180 | 181 | h = i_sphere(ro, rd, SC1); 182 | if h > 0.0 && h < tmin { 183 | tmin = h; 184 | *pos = ro + tmin * rd; 185 | *nor = (ro + h * rd - SC1.xyz()).normalize(); 186 | *matid = 1.0; 187 | *occ = 0.5 + 0.5 * nor.y; 188 | } 189 | 190 | h = i_sphere(ro, rd, SC2); 191 | if h > 0.0 && h < tmin { 192 | tmin = h; 193 | *pos = ro + tmin * rd; 194 | *nor = (ro + h * rd - SC2.xyz()).normalize(); 195 | *matid = 1.0; 196 | *occ = 0.5 + 0.5 * nor.y; 197 | } 198 | 199 | h = i_sphere(ro, rd, SC3); 200 | if h > 0.0 && h < tmin { 201 | tmin = h; 202 | *pos = ro + tmin * rd; 203 | *nor = (ro + h * rd - SC3.xyz()).normalize(); 204 | *matid = 1.0; 205 | *occ = 0.5 + 0.5 * nor.y; 206 | } 207 | 208 | tmin 209 | } 210 | 211 | fn tex_coords(p: Vec3) -> Vec3 { 212 | 64.0 * p 213 | } 214 | 215 | fn mytexture(mut p: Vec3, _n: Vec3, matid: f32) -> Vec3 { 216 | p += Vec3::splat(0.1); 217 | let ip: Vec3 = (p / 20.0).floor(); 218 | let fp: Vec3 = (Vec3::splat(0.5) + p / 20.0).fract_gl(); 219 | 220 | let mut id: f32 = ((ip.dot(vec3(127.1, 311.7, 74.7))).sin() * 58.5453123).fract_gl(); 221 | id = mix(id, 0.3, matid); 222 | 223 | let f: f32 = (ip.x + (ip.y + ip.z.rem_euclid(2.0)).rem_euclid(2.0)).rem_euclid(2.0); 224 | 225 | let mut g: f32 = 226 | 0.5 + 1.0 * noise(p * mix(vec3(0.2 + 0.8 * f, 1.0, 1.0 - 0.8 * f), Vec3::ONE, matid)); 227 | 228 | g *= mix( 229 | smoothstep(0.03, 0.04, (fp.x - 0.5).abs() / 0.5) 230 | * smoothstep(0.03, 0.04, (fp.z - 0.5).abs() / 0.5), 231 | 1.0, 232 | matid, 233 | ); 234 | 235 | let col: Vec3 = 236 | Vec3::splat(0.5) + 0.5 * (Vec3::splat(1.0 + 2.0 * id) + vec3(0.0, 1.0, 2.0)).sin(); 237 | 238 | col * g 239 | } 240 | 241 | impl Inputs { 242 | fn calc_camera(&self, ro: &mut Vec3, ta: &mut Vec3) { 243 | let an: f32 = 0.1 * self.time; 244 | *ro = vec3(5.5 * an.cos(), 1.0, 5.5 * an.sin()); 245 | *ta = vec3(0.0, 1.0, 0.0); 246 | } 247 | } 248 | 249 | fn do_lighting(pos: Vec3, nor: Vec3, occ: f32, rd: Vec3) -> Vec3 { 250 | let sh: f32 = soft_shadow_sphere(pos, Vec3::splat(0.57703), SC0) 251 | .min(soft_shadow_sphere(pos, Vec3::splat(0.57703), SC1)) 252 | .min(soft_shadow_sphere(pos, Vec3::splat(0.57703), SC2)) 253 | .min(soft_shadow_sphere(pos, Vec3::splat(0.57703), SC3)); 254 | let dif: f32 = nor.dot(Vec3::splat(0.57703)).clamp(0.0, 1.0); 255 | let bac: f32 = nor.dot(vec3(-0.707, 0.0, -0.707)).clamp(0.0, 1.0); 256 | let mut lin: Vec3 = dif * vec3(1.50, 1.40, 1.30) * sh; 257 | lin += occ * vec3(0.15, 0.20, 0.30); 258 | lin += bac * vec3(0.20, 0.20, 0.20); 259 | lin += Vec3::splat( 260 | sh * 0.8 261 | * rd.reflect(nor) 262 | .dot(Vec3::splat(0.57703)) 263 | .clamp(0.0, 1.0) 264 | .powf(12.0), 265 | ); 266 | 267 | lin 268 | } 269 | //=============================================================================================== 270 | //=============================================================================================== 271 | // render 272 | //=============================================================================================== 273 | //=============================================================================================== 274 | impl Inputs { 275 | fn calc_ray_for_pixel(&self, pix: Vec2, res_ro: &mut Vec3, res_rd: &mut Vec3) { 276 | let p: Vec2 = (-self.resolution.xy() + 2.0 * pix) / self.resolution.y; 277 | // camera movement 278 | let mut ro: Vec3 = Vec3::ZERO; 279 | let mut ta: Vec3 = Vec3::ZERO; 280 | self.calc_camera(&mut ro, &mut ta); 281 | // camera matrix 282 | let ww: Vec3 = (ta - ro).normalize(); 283 | let uu: Vec3 = ww.cross(vec3(0.0, 1.0, 0.0)).normalize(); 284 | let vv: Vec3 = uu.cross(ww).normalize(); 285 | // create view ray 286 | let rd: Vec3 = (p.x * uu + p.y * vv + 1.5 * ww).normalize(); 287 | 288 | *res_ro = ro; 289 | *res_rd = rd; 290 | } 291 | } 292 | 293 | // sample a procedural texture with filtering 294 | fn sample_texture_with_filter( 295 | uvw: Vec3, 296 | ddx_uvw: Vec3, 297 | ddy_uvw: Vec3, 298 | nor: Vec3, 299 | mid: f32, 300 | ) -> Vec3 { 301 | let sx: i32 = 1 + (4.0 * (ddx_uvw - uvw).length()).clamp(0.0, (MAX_SAMPLES - 1) as f32) as i32; 302 | let sy: i32 = 1 + (4.0 * (ddy_uvw - uvw).length()).clamp(0.0, (MAX_SAMPLES - 1) as f32) as i32; 303 | 304 | let mut no: Vec3 = Vec3::ZERO; 305 | 306 | if true { 307 | for j in 0..MAX_SAMPLES { 308 | for i in 0..MAX_SAMPLES { 309 | if j < sy && i < sx { 310 | let st: Vec2 = vec2(i as f32, j as f32) / vec2(sx as f32, sy as f32); 311 | no += mytexture( 312 | uvw + st.x * (ddx_uvw - uvw) + st.y * (ddy_uvw - uvw), 313 | nor, 314 | mid, 315 | ); 316 | } 317 | } 318 | } 319 | } else { 320 | for j in 0..sy { 321 | for i in 0..sx { 322 | let st: Vec2 = vec2(i as f32, j as f32) / vec2(sx as f32, sy as f32); 323 | no += mytexture( 324 | uvw + st.x * (ddx_uvw - uvw) + st.y * (ddy_uvw - uvw), 325 | nor, 326 | mid, 327 | ); 328 | } 329 | } 330 | } 331 | 332 | no / (sx * sy) as f32 333 | } 334 | 335 | fn sample_texture(uvw: Vec3, nor: Vec3, mid: f32) -> Vec3 { 336 | mytexture(uvw, nor, mid) 337 | } 338 | 339 | impl Inputs { 340 | pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { 341 | let p: Vec2 = (-self.resolution.xy() + 2.0 * frag_coord) / self.resolution.y; 342 | let mut th: f32 = (-self.resolution.x + 2.0 * self.mouse.x) / self.resolution.y; 343 | 344 | if self.mouse.z < 0.01 { 345 | th = 0.5 / self.resolution.y; 346 | } 347 | 348 | let mut ro: Vec3 = Vec3::ZERO; 349 | let mut rd: Vec3 = Vec3::ZERO; 350 | let mut ddx_ro: Vec3 = Vec3::ZERO; 351 | let mut ddx_rd: Vec3 = Vec3::ZERO; 352 | let mut ddy_ro: Vec3 = Vec3::ZERO; 353 | let mut ddy_rd: Vec3 = Vec3::ZERO; 354 | self.calc_ray_for_pixel(frag_coord + vec2(0.0, 0.0), &mut ro, &mut rd); 355 | self.calc_ray_for_pixel(frag_coord + vec2(1.0, 0.0), &mut ddx_ro, &mut ddx_rd); 356 | self.calc_ray_for_pixel(frag_coord + vec2(0.0, 1.0), &mut ddy_ro, &mut ddy_rd); 357 | 358 | // trace 359 | let mut pos: Vec3 = Vec3::ZERO; 360 | let mut nor: Vec3 = Vec3::ZERO; 361 | let mut occ: f32 = 0.0; 362 | let mut mid: f32 = 0.0; 363 | let t: f32 = intersect(ro, rd, &mut pos, &mut nor, &mut occ, &mut mid); 364 | 365 | let mut col: Vec3 = Vec3::splat(0.9); 366 | 367 | let uvw: Vec3; 368 | let ddx_uvw: Vec3; 369 | let ddy_uvw: Vec3; 370 | 371 | if t < 100.0 { 372 | if true { 373 | // ----------------------------------------------------------------------- 374 | // compute ray differentials by intersecting the tangent plane to the 375 | // surface. 376 | // ----------------------------------------------------------------------- 377 | 378 | // computer ray differentials 379 | let ddx_pos: Vec3 = ddx_ro - ddx_rd * (ddx_ro - pos).dot(nor) / ddx_rd.dot(nor); 380 | let ddy_pos: Vec3 = ddy_ro - ddy_rd * (ddy_ro - pos).dot(nor) / ddy_rd.dot(nor); 381 | 382 | // calc texture sampling footprint 383 | uvw = tex_coords(pos); 384 | ddx_uvw = tex_coords(ddx_pos); 385 | ddy_uvw = tex_coords(ddy_pos); 386 | } else { 387 | // ----------------------------------------------------------------------- 388 | // Because we are in the GPU, we do have access to differentials directly 389 | // This wouldn't be the case in a regular raytrace. 390 | // It wouldn't work as well in shaders doing interleaved calculations in 391 | // pixels (such as some of the 3D/stereo shaders here in Shadertoy) 392 | // ----------------------------------------------------------------------- 393 | uvw = tex_coords(pos); 394 | 395 | // calc texture sampling footprint 396 | ddx_uvw = uvw + uvw.dfdx(); 397 | ddy_uvw = uvw + uvw.dfdy(); 398 | } 399 | // shading 400 | let mate: Vec3; 401 | 402 | if p.x > th { 403 | mate = sample_texture(uvw, nor, mid); 404 | } else { 405 | mate = sample_texture_with_filter(uvw, ddx_uvw, ddy_uvw, nor, mid); 406 | } 407 | 408 | // lighting 409 | let lin: Vec3 = do_lighting(pos, nor, occ, rd); 410 | 411 | // combine lighting with material 412 | col = mate * lin; 413 | 414 | // fog 415 | col = mix(col, Vec3::splat(0.9), 1.0 - (-0.0002 * t * t).exp()); 416 | } 417 | 418 | // gamma correction 419 | col = col.powf(0.4545); 420 | 421 | col *= smoothstep(0.006, 0.008, (p.x - th).abs()); 422 | 423 | *frag_color = col.extend(1.0); 424 | } 425 | } 426 | -------------------------------------------------------------------------------- /shaders/src/galaxy_of_universes.rs: -------------------------------------------------------------------------------- 1 | //! Ported to Rust from 2 | //! 3 | //! Original comment: 4 | //! ```glsl 5 | //! // https://www.shadertoy.com/view/MdXSzS 6 | //! // The Big Bang - just a small explosion somewhere in a massive Galaxy of Universes. 7 | //! // Outside of this there's a massive galaxy of 'Galaxy of Universes'... etc etc. :D 8 | //! 9 | //! // To fake a perspective it takes advantage of the screen being wider than it is tall. 10 | //! ``` 11 | 12 | use shared::*; 13 | use spirv_std::glam::{vec3, Mat2, Vec2, Vec3, Vec3Swizzles, Vec4}; 14 | 15 | // Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but 16 | // we tie #[no_std] above to the same condition, so it's fine. 17 | #[cfg(target_arch = "spirv")] 18 | use spirv_std::num_traits::Float; 19 | 20 | pub struct Inputs { 21 | pub resolution: Vec3, 22 | pub time: f32, 23 | } 24 | 25 | impl Inputs { 26 | pub fn main_image(&self, frag_color: &mut Vec4, frag_coord: Vec2) { 27 | let uv: Vec2 = (frag_coord / self.resolution.xy()) - Vec2::splat(0.5); 28 | let t: f32 = self.time * 0.1 29 | + ((0.25 + 0.05 * (self.time * 0.1).sin()) / (uv.length() + 0.07)) * 2.2; 30 | let si: f32 = t.sin(); 31 | let co: f32 = t.cos(); 32 | let ma: Mat2 = Mat2::from_cols_array(&[co, si, -si, co]); 33 | 34 | let mut v1: f32 = 0.0; 35 | let mut v2: f32 = 0.0; 36 | let mut v3: f32 = 0.0; 37 | 38 | let mut s: f32 = 0.0; 39 | 40 | for _ in 0..90 { 41 | let mut p: Vec3 = s * uv.extend(0.0); 42 | p = (ma.transpose() * p.xy()).extend(p.z); 43 | p += vec3(0.22, 0.3, s - 1.5 - (self.time * 0.13).sin() * 0.1); 44 | for _ in 0..8 { 45 | p = p.abs() / p.dot(p) - Vec3::splat(0.659); 46 | } 47 | v1 += p.dot(p) * 0.0015 * (1.8 + ((uv * 13.0).length() + 0.5 - self.time * 0.2).sin()); 48 | v2 += p.dot(p) * 0.0013 * (1.5 + ((uv * 14.5).length() + 1.2 - self.time * 0.3).sin()); 49 | v3 += (p.xy() * 10.0).length() * 0.0003; 50 | s += 0.035; 51 | } 52 | 53 | let len: f32 = uv.length(); 54 | v1 *= smoothstep(0.7, 0.0, len); 55 | v2 *= smoothstep(0.5, 0.0, len); 56 | v3 *= smoothstep(0.9, 0.0, len); 57 | 58 | let col: Vec3 = vec3( 59 | v3 * (1.5 + (self.time * 0.2).sin() * 0.4), 60 | (v1 + v3) * 0.3, 61 | v2, 62 | ) + Vec3::splat(smoothstep(0.2, 0.0, len) * 0.85) 63 | + Vec3::splat(smoothstep(0.0, 0.6, v3) * 0.3); 64 | 65 | *frag_color = col 66 | .abs() 67 | .powf_vec(Vec3::splat(1.2)) 68 | .min(Vec3::splat(1.0)) 69 | .extend(1.0); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /shaders/src/heart.rs: -------------------------------------------------------------------------------- 1 | //! Ported to Rust from 2 | //! 3 | //! Original comment: 4 | //! ```glsl 5 | //! // Created by inigo quilez - iq/2013 6 | //! // License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. 7 | //! ``` 8 | 9 | use shared::*; 10 | use spirv_std::glam::{vec2, vec3, Vec2, Vec3, Vec3Swizzles, Vec4}; 11 | 12 | // Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but 13 | // we tie #[no_std] above to the same condition, so it's fine. 14 | #[cfg(target_arch = "spirv")] 15 | use spirv_std::num_traits::Float; 16 | 17 | pub struct Inputs { 18 | pub resolution: Vec3, 19 | pub time: f32, 20 | } 21 | 22 | impl Inputs { 23 | pub fn main_image(&self, frag_color: &mut Vec4, frag_coord: Vec2) { 24 | let mut p: Vec2 = 25 | (2.0 * frag_coord - self.resolution.xy()) / (self.resolution.y.min(self.resolution.x)); 26 | 27 | // background color 28 | let bcol: Vec3 = vec3(1.0, 0.8, 0.7 - 0.07 * p.y) * (1.0 - 0.25 * p.length()); 29 | 30 | // animate 31 | let tt: f32 = self.time.rem_euclid(1.5) / 1.5; 32 | let mut ss: f32 = tt.powf(0.2) * 0.5 + 0.5; 33 | ss = 1.0 + ss * 0.5 * (tt * 6.2831 * 3.0 + p.y * 0.5).sin() * (-tt * 4.0).exp(); 34 | p *= vec2(0.5, 1.5) + ss * vec2(0.5, -0.5); 35 | 36 | // shape 37 | let r: f32; 38 | let d: f32; 39 | 40 | if false { 41 | p *= 0.8; 42 | p.y = -0.1 - p.y * 1.2 + p.x.abs() * (1.0 - p.x.abs()); 43 | r = p.length(); 44 | d = 0.5; 45 | } else { 46 | p.y -= 0.25; 47 | let a: f32 = p.x.atan2(p.y) / 3.141593; 48 | r = p.length(); 49 | let h: f32 = a.abs(); 50 | d = (13.0 * h - 22.0 * h * h + 10.0 * h * h * h) / (6.0 - 5.0 * h); 51 | } 52 | 53 | // color 54 | let mut s: f32 = 0.75 + 0.75 * p.x; 55 | s *= 1.0 - 0.4 * r; 56 | s = 0.3 + 0.7 * s; 57 | s *= 0.5 + 0.5 * (1.0 - (r / d).clamp(0.0, 1.0)).powf(0.1); 58 | let hcol: Vec3 = vec3(1.0, 0.5 * r, 0.3) * s; 59 | 60 | let col: Vec3 = mix(bcol, hcol, smoothstep(-0.01, 0.01, d - r)); 61 | 62 | *frag_color = col.extend(1.0); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /shaders/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(target_arch = "spirv", no_std)] 2 | 3 | use shared::*; 4 | use spirv_std::glam::{vec2, vec3, vec4, Vec2, Vec3, Vec4}; 5 | use spirv_std::spirv; 6 | 7 | pub mod a_lot_of_spheres; 8 | pub mod a_question_of_time; 9 | pub mod apollonian; 10 | pub mod atmosphere_system_test; 11 | pub mod bubble_buckey_balls; 12 | pub mod clouds; 13 | pub mod filtering_procedurals; 14 | pub mod flappy_bird; 15 | pub mod galaxy_of_universes; 16 | pub mod geodesic_tiling; 17 | pub mod heart; 18 | pub mod luminescence; 19 | pub mod mandelbrot_smooth; 20 | pub mod miracle_snowflakes; 21 | pub mod morphing; 22 | pub mod moving_square; 23 | pub mod on_off_spikes; 24 | pub mod phantom_star; 25 | pub mod playing_marble; 26 | pub mod protean_clouds; 27 | pub mod raymarching_primitives; 28 | pub mod seascape; 29 | pub mod skyline; 30 | pub mod soft_shadow_variation; 31 | pub mod tileable_water_caustic; 32 | pub mod tokyo; 33 | pub mod two_tweets; 34 | pub mod voxel_pac_man; 35 | 36 | pub trait SampleCube: Copy { 37 | fn sample_cube(self, p: Vec3) -> Vec4; 38 | } 39 | 40 | #[derive(Copy, Clone)] 41 | struct ConstantColor { 42 | color: Vec4, 43 | } 44 | 45 | impl SampleCube for ConstantColor { 46 | fn sample_cube(self, _: Vec3) -> Vec4 { 47 | self.color 48 | } 49 | } 50 | 51 | #[derive(Copy, Clone)] 52 | struct RgbCube { 53 | alpha: f32, 54 | intensity: f32, 55 | } 56 | 57 | impl SampleCube for RgbCube { 58 | fn sample_cube(self, p: Vec3) -> Vec4 { 59 | (p.abs() * self.intensity).extend(self.alpha) 60 | } 61 | } 62 | 63 | pub fn fs(constants: &ShaderConstants, mut frag_coord: Vec2) -> Vec4 { 64 | const COLS: usize = 6; 65 | const ROWS: usize = 5; 66 | 67 | let resolution = vec3( 68 | constants.width as f32 / COLS as f32, 69 | constants.height as f32 / ROWS as f32, 70 | 0.0, 71 | ); 72 | let time = constants.time; 73 | let mut mouse = vec4( 74 | constants.drag_end_x / COLS as f32, 75 | constants.drag_end_y / ROWS as f32, 76 | constants.drag_start_x / COLS as f32, 77 | constants.drag_start_y / ROWS as f32, 78 | ); 79 | if mouse != Vec4::ZERO { 80 | mouse.y = resolution.y - mouse.y; 81 | mouse.w = resolution.y - mouse.w; 82 | } 83 | if !(constants.mouse_left_pressed == 1) { 84 | mouse.z *= -1.0; 85 | } 86 | if !(constants.mouse_left_clicked == 1) { 87 | mouse.w *= -1.0; 88 | } 89 | 90 | let col = (frag_coord.x / resolution.x) as usize; 91 | let row = (frag_coord.y / resolution.y) as usize; 92 | let i = row * COLS + col; 93 | 94 | frag_coord.x %= resolution.x; 95 | frag_coord.y = resolution.y - frag_coord.y % resolution.y; 96 | 97 | let mut color = Vec4::ZERO; 98 | match i { 99 | 0 => two_tweets::Inputs { resolution, time }.main_image(&mut color, frag_coord), 100 | 1 => heart::Inputs { resolution, time }.main_image(&mut color, frag_coord), 101 | 2 => clouds::Inputs { resolution, time }.main_image(&mut color, frag_coord), 102 | 3 => mandelbrot_smooth::Inputs { resolution, time }.main_image(&mut color, frag_coord), 103 | 4 => protean_clouds::State::new(protean_clouds::Inputs { 104 | resolution, 105 | time, 106 | mouse, 107 | }) 108 | .main_image(&mut color, frag_coord), 109 | 5 => tileable_water_caustic::Inputs { resolution, time }.main_image(&mut color, frag_coord), 110 | 6 => apollonian::State::new(apollonian::Inputs { 111 | resolution, 112 | time, 113 | mouse, 114 | }) 115 | .main_image(&mut color, frag_coord), 116 | 7 => phantom_star::Inputs { resolution, time }.main_image(&mut color, frag_coord), 117 | 8 => seascape::Inputs { 118 | resolution, 119 | time, 120 | mouse, 121 | } 122 | .main_image(&mut color, frag_coord), 123 | 9 => playing_marble::Inputs { 124 | resolution, 125 | time, 126 | mouse, 127 | channel0: RgbCube { 128 | alpha: 1.0, 129 | intensity: 1.0, 130 | }, 131 | } 132 | .main_image(&mut color, frag_coord), 133 | 10 => a_lot_of_spheres::Inputs { resolution, time }.main_image(&mut color, frag_coord), 134 | 11 => a_question_of_time::Inputs { 135 | resolution, 136 | time, 137 | mouse, 138 | } 139 | .main_image(&mut color, frag_coord), 140 | 12 => galaxy_of_universes::Inputs { resolution, time }.main_image(&mut color, frag_coord), 141 | 13 => atmosphere_system_test::State::new(atmosphere_system_test::Inputs { 142 | resolution, 143 | time, 144 | mouse, 145 | }) 146 | .main_image(&mut color, frag_coord), 147 | 14 => soft_shadow_variation::Inputs { resolution, time }.main_image(&mut color, frag_coord), 148 | 15 => miracle_snowflakes::State::new(miracle_snowflakes::Inputs { 149 | resolution, 150 | time, 151 | mouse, 152 | }) 153 | .main_image(&mut color, frag_coord), 154 | 16 => morphing::State::new(morphing::Inputs { 155 | resolution, 156 | time, 157 | mouse, 158 | }) 159 | .main_image(&mut color, frag_coord), 160 | 17 => bubble_buckey_balls::State::new(bubble_buckey_balls::Inputs { 161 | resolution, 162 | time, 163 | mouse, 164 | channel0: RgbCube { 165 | alpha: 1.0, 166 | intensity: 0.5, 167 | }, 168 | channel1: ConstantColor { color: Vec4::ONE }, 169 | }) 170 | .main_image(&mut color, frag_coord), 171 | 18 => raymarching_primitives::Inputs { 172 | resolution, 173 | frame: (time * 60.0) as i32, 174 | time, 175 | mouse, 176 | } 177 | .main_image(&mut color, frag_coord), 178 | 19 => moving_square::Inputs { resolution, time }.main_image(&mut color, frag_coord), 179 | 20 => skyline::State::new(skyline::Inputs { 180 | resolution, 181 | time, 182 | mouse, 183 | channel0: RgbCube { 184 | alpha: 1.0, 185 | intensity: 1.0, 186 | }, 187 | }) 188 | .main_image(&mut color, frag_coord), 189 | 21 => filtering_procedurals::Inputs { 190 | resolution, 191 | time, 192 | mouse, 193 | } 194 | .main_image(&mut color, frag_coord), 195 | 22 => geodesic_tiling::State::new(geodesic_tiling::Inputs { 196 | resolution, 197 | time, 198 | mouse, 199 | }) 200 | .main_image(&mut color, frag_coord), 201 | 23 => flappy_bird::State::new(flappy_bird::Inputs { resolution, time }) 202 | .main_image(&mut color, frag_coord), 203 | 24 => { 204 | tokyo::State::new(tokyo::Inputs { resolution, time }).main_image(&mut color, frag_coord) 205 | } 206 | 25 => on_off_spikes::State::new(on_off_spikes::Inputs { 207 | resolution, 208 | time, 209 | mouse, 210 | }) 211 | .main_image(&mut color, frag_coord), 212 | 26 => luminescence::State::new(luminescence::Inputs { 213 | resolution, 214 | time, 215 | mouse, 216 | }) 217 | .main_image(&mut color, frag_coord), 218 | 27 => voxel_pac_man::State::new(voxel_pac_man::Inputs { 219 | resolution, 220 | time, 221 | mouse, 222 | }) 223 | .main_image(&mut color, frag_coord), 224 | _ => {} 225 | } 226 | Vec3::powf(color.truncate(), 2.2).extend(color.w) 227 | } 228 | 229 | #[allow(unused_attributes)] 230 | #[spirv(fragment)] 231 | pub fn main_fs( 232 | #[spirv(frag_coord)] in_frag_coord: Vec4, 233 | #[spirv(push_constant)] constants: &ShaderConstants, 234 | output: &mut Vec4, 235 | ) { 236 | let frag_coord = vec2(in_frag_coord.x, in_frag_coord.y); 237 | let color = fs(constants, frag_coord); 238 | *output = color; 239 | } 240 | 241 | #[allow(unused_attributes)] 242 | #[spirv(vertex)] 243 | pub fn main_vs(#[spirv(vertex_index)] vert_idx: i32, #[spirv(position)] builtin_pos: &mut Vec4) { 244 | // Create a "full screen triangle" by mapping the vertex index. 245 | // ported from https://www.saschawillems.de/blog/2016/08/13/vulkan-tutorial-on-rendering-a-fullscreen-quad-without-buffers/ 246 | let uv = vec2(((vert_idx << 1) & 2) as f32, (vert_idx & 2) as f32); 247 | let pos = 2.0 * uv - Vec2::ONE; 248 | 249 | *builtin_pos = pos.extend(0.0).extend(1.0); 250 | } 251 | -------------------------------------------------------------------------------- /shaders/src/mandelbrot_smooth.rs: -------------------------------------------------------------------------------- 1 | //! Ported to Rust from 2 | //! 3 | //! Original comment: 4 | //! ```glsl 5 | //! // Created by inigo quilez - iq/2013 6 | //! // License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. 7 | //! 8 | //! // See here for more information on smooth iteration count: 9 | //! // 10 | //! // http://iquilezles.org/www/articles/mset_smooth/mset_smooth.htm 11 | //! ``` 12 | 13 | use shared::*; 14 | use spirv_std::glam::{vec2, vec3, Vec2, Vec3, Vec3Swizzles, Vec4}; 15 | 16 | // Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but 17 | // we tie #[no_std] above to the same condition, so it's fine. 18 | #[cfg(target_arch = "spirv")] 19 | use spirv_std::num_traits::Float; 20 | 21 | pub struct Inputs { 22 | pub resolution: Vec3, 23 | pub time: f32, 24 | } 25 | 26 | // increase this if you have a very fast GPU 27 | const AA: usize = 2; 28 | 29 | impl Inputs { 30 | fn mandelbrot(&self, c: Vec2) -> f32 { 31 | if true { 32 | let c2: f32 = c.dot(c); 33 | // skip computation inside M1 - http://iquilezles.org/www/articles/mset_1bulb/mset1bulb.htm 34 | if 256.0 * c2 * c2 - 96.0 * c2 + 32.0 * c.x - 3.0 < 0.0 { 35 | return 0.0; 36 | } 37 | // skip computation inside M2 - http://iquilezles.org/www/articles/mset_2bulb/mset2bulb.htm 38 | if (16.0 * (c2 + 2.0 * c.x + 1.0) - 1.0) < 0.0 { 39 | return 0.0; 40 | } 41 | } 42 | const B: f32 = 256.0; 43 | let mut l: f32 = 0.0; 44 | let mut z: Vec2 = Vec2::ZERO; 45 | for _ in 0..512 { 46 | z = vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y) + c; 47 | if z.dot(z) > (B * B) { 48 | break; 49 | } 50 | l += 1.0; 51 | } 52 | 53 | if l > 511.0 { 54 | return 0.0; 55 | } 56 | // ------------------------------------------------------ 57 | // smooth interation count 58 | //float sl = l - log(log(length(z))/log(B))/log(2.0); 59 | // 60 | // equivalent optimized smooth interation count 61 | let sl: f32 = l - z.dot(z).log2().log2() + 4.0; 62 | 63 | let al: f32 = smoothstep(-0.1, 0.0, (self.time * 0.5 * 6.2831).sin()); 64 | mix(l, sl, al) 65 | } 66 | 67 | pub fn main_image(&self, frag_color: &mut Vec4, frag_coord: Vec2) { 68 | let mut col: Vec3 = Vec3::ZERO; 69 | 70 | for m in 0..AA { 71 | for n in 0..AA { 72 | let p: Vec2 = (-self.resolution.xy() 73 | + Vec2::splat(2.0) * (frag_coord + vec2(m as f32, n as f32) / AA as f32)) 74 | / self.resolution.y; 75 | let w: f32 = (AA * m + n) as f32; 76 | let time: f32 = self.time + 0.5 * (1.0 / 24.0) * w / (AA * AA) as f32; 77 | 78 | let mut zoo: f32 = 0.62 + 0.38 * (0.07 * time).cos(); 79 | let coa: f32 = (0.15 * (1.0 - zoo) * time).cos(); 80 | let sia = (0.15 * (1.0 - zoo) * time).sin(); 81 | zoo = zoo.powf(8.0); 82 | let xy: Vec2 = vec2(p.x * coa - p.y * sia, p.x * sia + p.y * coa); 83 | let c: Vec2 = vec2(-0.745, 0.186) + xy * zoo; 84 | 85 | let l: f32 = self.mandelbrot(c); 86 | col += Vec3::splat(0.5) 87 | + Vec3::splat(0.5) * (Vec3::splat(3.0 + l * 0.15) + vec3(0.0, 0.6, 1.0)).cos(); 88 | } 89 | } 90 | col /= (AA * AA) as f32; 91 | *frag_color = col.extend(1.0); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /shaders/src/miracle_snowflakes.rs: -------------------------------------------------------------------------------- 1 | //! Ported to Rust from 2 | //! 3 | //! Original comment: 4 | //! ```glsl 5 | //! /* 6 | //! // 7 | //! /* Panteleymonov Aleksandr Konstantinovich 2015 8 | //! // 9 | //! // if i write this string my code will be 0 chars, :) */ 10 | //! */ 11 | //! ``` 12 | 13 | use shared::*; 14 | use spirv_std::glam::{ 15 | vec2, vec3, vec4, Mat3, Vec2, Vec2Swizzles, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles, 16 | }; 17 | 18 | // Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but 19 | // we tie #[no_std] above to the same condition, so it's fine. 20 | #[cfg(target_arch = "spirv")] 21 | use spirv_std::num_traits::Float; 22 | 23 | pub struct Inputs { 24 | pub resolution: Vec3, 25 | pub time: f32, 26 | pub mouse: Vec4, 27 | } 28 | 29 | const ITERATIONS: u32 = 15; 30 | const DEPTH: f32 = 0.0125; 31 | const LAYERS: f32 = 8.0; 32 | const LAYERSBLOB: i32 = 20; 33 | const STEP: f32 = 1.0; 34 | const FAR: f32 = 10000.0; 35 | 36 | pub struct State { 37 | inputs: Inputs, 38 | radius: f32, 39 | zoom: f32, 40 | 41 | light: Vec3, 42 | seed: Vec2, 43 | iteratorc: f32, 44 | powr: f32, 45 | res: f32, 46 | 47 | nray: Vec3, 48 | nray1: Vec3, 49 | nray2: Vec3, 50 | mxc: f32, 51 | } 52 | 53 | impl State { 54 | pub fn new(inputs: Inputs) -> Self { 55 | State { 56 | inputs, 57 | radius: 0.25, // radius of Snowflakes. maximum for this demo 0.25. 58 | zoom: 4.0, // use this to change details. optimal 0.1 - 4.0. 59 | light: vec3(0.0, 0.0, 1.0), 60 | seed: vec2(0.0, 0.0), 61 | iteratorc: ITERATIONS as f32, 62 | powr: 0.0, 63 | res: 0.0, 64 | 65 | nray: Vec3::ZERO, 66 | nray1: Vec3::ZERO, 67 | nray2: Vec3::ZERO, 68 | mxc: 1.0, 69 | } 70 | } 71 | } 72 | 73 | const NC0: Vec4 = vec4(0.0, 157.0, 113.0, 270.0); 74 | const NC1: Vec4 = vec4(1.0, 158.0, 114.0, 271.0); 75 | 76 | fn hash4(n: Vec4) -> Vec4 { 77 | (n.sin() * 1399763.5453123).fract_gl() 78 | } 79 | fn noise2(x: Vec2) -> f32 { 80 | let p: Vec2 = x.floor(); 81 | let mut f: Vec2 = x.fract_gl(); 82 | f = f * f * (Vec2::splat(3.0) - 2.0 * f); 83 | let n: f32 = p.x + p.y * 157.0; 84 | let nc0 = NC0; 85 | let nc1 = NC1; 86 | let h: Vec4 = hash4(Vec4::splat(n) + vec4(nc0.x, nc0.y, nc1.x, nc1.y)); 87 | let s1: Vec2 = mix(h.xy(), h.zw(), f.xx()); 88 | mix(s1.x, s1.y, f.y) 89 | } 90 | 91 | fn noise222(x: Vec2, y: Vec2, z: Vec2) -> f32 { 92 | let lx: Vec4 = vec4(x.x * y.x, x.y * y.x, x.x * y.y, x.y * y.y); 93 | let p: Vec4 = lx.floor(); 94 | let mut f: Vec4 = lx.fract_gl(); 95 | f = f * f * (Vec4::splat(3.0) - 2.0 * f); 96 | let n: Vec2 = p.xz() + p.yw() * 157.0; 97 | let h: Vec4 = mix( 98 | hash4(n.xxyy() + NC0.xyxy()), 99 | hash4(n.xxyy() + NC1.xyxy()), 100 | f.xxzz(), 101 | ); 102 | mix(h.xz(), h.yw(), f.yw()).dot(z) 103 | } 104 | 105 | fn noise3(x: Vec3) -> f32 { 106 | let p: Vec3 = x.floor(); 107 | let mut f: Vec3 = x.fract_gl(); 108 | f = f * f * (Vec3::splat(3.0) - 2.0 * f); 109 | let n: f32 = p.x + p.yz().dot(vec2(157.0, 113.0)); 110 | let s1: Vec4 = mix( 111 | hash4(Vec4::splat(n) + NC0), 112 | hash4(Vec4::splat(n) + NC1), 113 | f.xxxx(), 114 | ); 115 | return mix(mix(s1.x, s1.y, f.y), mix(s1.z, s1.w, f.y), f.z); 116 | } 117 | fn noise3_2(x: Vec3) -> Vec2 { 118 | vec2(noise3(x), noise3(x + Vec3::splat(100.0))) 119 | } 120 | 121 | impl State { 122 | fn map(&self, rad: Vec2) -> f32 { 123 | let a: f32; 124 | if self.res < 0.0015 { 125 | //a = noise2(rad.xy*20.6)*0.9+noise2(rad.xy*100.6)*0.1; 126 | a = noise222(rad, vec2(20.6, 100.6), vec2(0.9, 0.1)); 127 | } else if self.res < 0.005 { 128 | //let a1: f32 = mix(noise2(rad.xy()*10.6),1.0,l); 129 | //a = texture(iChannel0,rad*0.3).x; 130 | a = noise2(rad * 20.6); 131 | //if a1 Vec3 { 140 | let rq: f32 = r * r; 141 | let mut dist: Vec3 = ray * FAR; 142 | 143 | let norm: Vec3 = vec3(0.0, 0.0, 1.0); 144 | let invn: f32 = 1.0 / norm.dot(ray); 145 | let mut depthi: f32 = DEPTH; 146 | if invn < 0.0 { 147 | depthi = -depthi; 148 | } 149 | let mut ds: f32 = 2.0 * depthi * invn; 150 | let mut r1: Vec3 = ray * (norm.dot(pos) - depthi) * invn - pos; 151 | let op1: Vec3 = r1 + norm * depthi; 152 | let len1: f32 = op1.dot(op1); 153 | let mut r2: Vec3 = r1 + ray * ds; 154 | let op2: Vec3 = r2 - norm * depthi; 155 | let len2: f32 = op2.dot(op2); 156 | let n: Vec3 = ray.cross(norm).normalize(); 157 | let mind: f32 = pos.dot(n); 158 | let n2: Vec3 = ray.cross(n); 159 | let d: f32 = n2.dot(pos) / n2.dot(norm); 160 | let invd: f32 = 0.2 / DEPTH; 161 | 162 | if (len1 < rq || len2 < rq) || (mind.abs() < r && d <= DEPTH && d >= -DEPTH) { 163 | let _r3: Vec3 = r2; 164 | let len: f32 = len1; 165 | if len >= rq { 166 | let n3: Vec3 = norm.cross(n); 167 | let a: f32 = 1.0 / (rq - mind * mind).sqrt() * ray.dot(n3).abs(); 168 | let dt: Vec3 = ray / a; 169 | r1 = -d * norm - mind * n - dt; 170 | if len2 >= rq { 171 | r2 = -d * norm - mind * n + dt; 172 | } 173 | ds = (r2 - r1).dot(ray); 174 | } 175 | ds = (ds.abs() + 0.1) / (ITERATIONS as f32); 176 | ds = mix(DEPTH, ds, 0.2); 177 | if ds > 0.01 { 178 | ds = 0.01; 179 | } 180 | let ir: f32 = 0.35 / r; 181 | r *= self.zoom; 182 | ray = ray * ds * 5.0; 183 | for m in 0..ITERATIONS { 184 | if m as f32 >= self.iteratorc { 185 | break; 186 | } 187 | let mut l: f32 = r1.xy().length(); //r1.xy().dot(r1.xy()).sqrt(); 188 | let mut c3: Vec2 = (r1.xy() / l).abs(); 189 | if c3.x > 0.5 { 190 | c3 = (c3 * 0.5 + vec2(-c3.y, c3.x) * 0.86602540).abs(); 191 | } 192 | let g: f32 = l + c3.x * c3.x; //*1.047197551; 193 | l *= self.zoom; 194 | let mut h: f32 = l - r - 0.1; 195 | l = l.powf(self.powr) + 0.1; 196 | h = h.max(mix(self.map(c3 * l + seed), 1.0, (r1.z * invd).abs())) + g * ir - 0.245; //0.7*0.35=0.245 //*0.911890636 197 | if (h < self.res * 20.0) || r1.z.abs() > DEPTH + 0.01 { 198 | break; 199 | } 200 | r1 += ray * h; 201 | ray *= 0.99; 202 | } 203 | if r1.z.abs() < DEPTH + 0.01 { 204 | dist = r1 + pos; 205 | } 206 | } 207 | dist 208 | } 209 | 210 | fn filter_flake( 211 | &mut self, 212 | mut color: Vec4, 213 | pos: Vec3, 214 | ray: Vec3, 215 | ray1: Vec3, 216 | ray2: Vec3, 217 | ) -> Vec4 { 218 | let d: Vec3 = self.dist_obj(pos, ray, self.radius, self.seed); 219 | let n1: Vec3 = self.dist_obj(pos, ray1, self.radius, self.seed); 220 | let n2: Vec3 = self.dist_obj(pos, ray2, self.radius, self.seed); 221 | 222 | let lq: Vec3 = vec3(d.dot(d), n1.dot(n1), n2.dot(n2)); 223 | if lq.x < FAR || lq.y < FAR || lq.z < FAR { 224 | let n: Vec3 = (n1 - d).cross(n2 - d).normalize(); 225 | if lq.x < FAR && lq.y < FAR && lq.z < FAR { 226 | self.nray = n; //(self.nray+n).normalize(); 227 | //self.nray1 = (ray1+n).normalize(); 228 | //self.nray2 = (ray2+n).normalize(); 229 | } 230 | let da: f32 = n.dot(self.light).abs().powf(3.0); 231 | let mut cf: Vec3 = mix(vec3(0.0, 0.4, 1.0), color.xyz() * 10.0, n.dot(ray).abs()); 232 | cf = mix(cf, Vec3::splat(2.0), da); 233 | color = (mix( 234 | color.xyz(), 235 | cf, 236 | self.mxc * self.mxc * (0.5 + n.dot(ray).abs() * 0.5), 237 | )) 238 | .extend(color.w); 239 | } 240 | 241 | color 242 | } 243 | 244 | pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { 245 | let time: f32 = self.inputs.time * 0.2; //*0.1; 246 | self.res = 1.0 / self.inputs.resolution.y; 247 | let p: Vec2 = (-self.inputs.resolution.xy() + 2.0 * frag_coord) * self.res; 248 | 249 | let mut rotate: Vec3; 250 | let mut mr: Mat3; 251 | let mut ray: Vec3; 252 | let mut ray1: Vec3; 253 | let mut ray2: Vec3; 254 | let mut pos: Vec3 = vec3(0.0, 0.0, 1.0); 255 | 256 | *frag_color = vec4(0.0, 0.0, 0.0, 0.0); 257 | self.nray = Vec3::ZERO; 258 | self.nray1 = Vec3::ZERO; 259 | self.nray2 = Vec3::ZERO; 260 | 261 | let mut refcolor: Vec4 = Vec4::ZERO; 262 | self.iteratorc = ITERATIONS as f32 - LAYERS; 263 | 264 | let mut addrot: Vec2 = Vec2::ZERO; 265 | if self.inputs.mouse.z > 0.0 { 266 | addrot = (self.inputs.mouse.xy() - self.inputs.resolution.xy() * 0.5) * self.res; 267 | } 268 | 269 | let mut mxcl: f32 = 1.0; 270 | let mut addpos: Vec3 = Vec3::ZERO; 271 | pos.z = 1.0; 272 | self.mxc = 1.0; 273 | self.radius = 0.25; 274 | let mzd: f32 = (self.zoom - 0.1) / LAYERS; 275 | for i in 0..LAYERSBLOB { 276 | let p2: Vec2 = p - Vec2::splat(0.25) + Vec2::splat(0.1 * i as f32); 277 | ray = p2.extend(2.0) - self.nray * 2.0; 278 | //ray = self.nray;//*0.6; 279 | ray1 = (ray + vec3(0.0, self.res * 2.0, 0.0)).normalize(); 280 | ray2 = (ray + vec3(self.res * 2.0, 0.0, 0.0)).normalize(); 281 | ray = ray.normalize(); 282 | let mut sb: Vec2 = ray.xy() * pos.length() / pos.normalize().dot(ray) + vec2(0.0, time); 283 | self.seed = (sb + vec2(0.0, pos.z)).floor() + Vec2::splat(pos.z); 284 | let mut seedn: Vec3 = self.seed.extend(pos.z); 285 | sb = sb.floor(); 286 | if noise3(seedn) > 0.2 && i < LAYERS as i32 { 287 | self.powr = noise3(seedn * 10.0) * 1.9 + 0.1; 288 | rotate = (((Vec2::splat(0.5) - noise3_2(seedn)) * time * 5.0).sin() * 0.3 + addrot) 289 | .extend(0.0); 290 | rotate.z = (0.5 - noise3(seedn + vec3(10.0, 3.0, 1.0))) * time * 5.0; 291 | seedn.z += time * 0.5; 292 | addpos = (sb + vec2(0.25, 0.25 - time) + noise3_2(seedn) * 0.5).extend(addpos.z); 293 | let sins: Vec3 = rotate.sin(); 294 | let coss: Vec3 = rotate.cos(); 295 | mr = Mat3::from_cols( 296 | vec3(coss.x, 0.0, sins.x), 297 | vec3(0.0, 1.0, 0.0), 298 | vec3(-sins.x, 0.0, coss.x), 299 | ); 300 | mr = Mat3::from_cols( 301 | vec3(1.0, 0.0, 0.0), 302 | vec3(0.0, coss.y, sins.y), 303 | vec3(0.0, -sins.y, coss.y), 304 | ) * mr; 305 | mr = Mat3::from_cols( 306 | vec3(coss.z, sins.z, 0.0), 307 | vec3(-sins.z, coss.z, 0.0), 308 | vec3(0.0, 0.0, 1.0), 309 | ) * mr; 310 | 311 | self.light = mr.transpose() * vec3(1.0, 0.0, 1.0).normalize(); 312 | // let cc: Vec4 = self.filter_flake( 313 | // *frag_color, 314 | // mr.transpose() * (pos + addpos), 315 | // (mr.transpose() * ray + self.nray * 0.1).normalize(), 316 | // (mr.transpose() * ray1 + self.nray * 0.1).normalize(), 317 | // (mr.transpose() * ray2 + self.nray * 0.1).normalize(), 318 | // ); 319 | let mut cc: Vec4 = self.filter_flake( 320 | *frag_color, 321 | mr.transpose() * (pos + addpos), 322 | mr.transpose() * ray, 323 | mr.transpose() * ray1, 324 | mr.transpose() * ray2, 325 | ); 326 | if false { 327 | if i > 0 328 | && self.nray.dot(self.nray) != 0.0 329 | && self.nray1.dot(self.nray1) != 0.0 330 | && self.nray2.dot(self.nray2) != 0.0 331 | { 332 | refcolor = self.filter_flake( 333 | refcolor, 334 | mr.transpose() * (pos + addpos), 335 | self.nray, 336 | self.nray1, 337 | self.nray2, 338 | ); 339 | } 340 | cc += refcolor * 0.5; 341 | } 342 | *frag_color = mix(cc, *frag_color, frag_color.w.min(1.0)); 343 | } 344 | seedn = sb.extend(pos.z) + vec3(0.5, 1000.0, 300.0); 345 | if noise3(seedn * 10.0) > 0.4 { 346 | let raf: f32 = 0.3 + noise3(seedn * 100.0); 347 | addpos = 348 | (sb + vec2(0.2, 0.2 - time) + noise3_2(seedn * 100.0) * 0.6).extend(addpos.z); 349 | let mut l: f32 = (ray * ray.dot(pos + addpos) - pos - addpos).length(); 350 | l = (1.0 - l * 10.0 * raf).max(0.0); 351 | *frag_color += 352 | vec4(1.0, 1.2, 3.0, 1.0) * l.powf(5.0) * ((0.6 + raf).powf(2.0) - 0.6) * mxcl; 353 | } 354 | self.mxc -= 1.1 / LAYERS; 355 | pos.z += STEP; 356 | self.iteratorc += 2.0; 357 | mxcl -= 1.1 / LAYERSBLOB as f32; 358 | self.zoom -= mzd; 359 | } 360 | 361 | let cr: Vec3 = mix(Vec3::ZERO, vec3(0.0, 0.0, 0.4), (-0.55 + p.y) * 2.0); 362 | *frag_color = (frag_color.xyz() 363 | + mix( 364 | (cr - frag_color.xyz()) * 0.1, 365 | vec3(0.2, 0.5, 1.0), 366 | ((-p.y + 1.0) * 0.5).clamp(0.0, 1.0), 367 | )) 368 | .extend(frag_color.z); 369 | 370 | *frag_color = Vec4::ONE.min(*frag_color); 371 | } 372 | } 373 | -------------------------------------------------------------------------------- /shaders/src/morphing.rs: -------------------------------------------------------------------------------- 1 | //! Ported to Rust from 2 | //! 3 | //! Original comment: 4 | //! ```glsl 5 | //! // Created by Sebastien Durand - 2014 6 | //! // License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. 7 | //! ``` 8 | 9 | use shared::*; 10 | use spirv_std::glam::{ 11 | vec2, vec3, Mat2, Vec2, Vec2Swizzles, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles, 12 | }; 13 | 14 | // Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but 15 | // we tie #[no_std] above to the same condition, so it's fine. 16 | #[cfg(target_arch = "spirv")] 17 | use spirv_std::num_traits::Float; 18 | 19 | pub struct Inputs { 20 | pub resolution: Vec3, 21 | pub time: f32, 22 | pub mouse: Vec4, 23 | } 24 | 25 | pub struct State { 26 | inputs: Inputs, 27 | 28 | a: [Vec2; 15], 29 | t1: [Vec2; 5], 30 | t2: [Vec2; 5], 31 | 32 | l: Vec3, 33 | 34 | t_morph: f32, 35 | mat2_rot: Mat2, 36 | } 37 | 38 | impl State { 39 | pub fn new(inputs: Inputs) -> Self { 40 | State { 41 | inputs, 42 | 43 | a: [ 44 | Vec2::ZERO, 45 | Vec2::ZERO, 46 | Vec2::ZERO, 47 | Vec2::ZERO, 48 | Vec2::ZERO, 49 | Vec2::ZERO, 50 | Vec2::ZERO, 51 | Vec2::ZERO, 52 | Vec2::ZERO, 53 | Vec2::ZERO, 54 | Vec2::ZERO, 55 | Vec2::ZERO, 56 | Vec2::ZERO, 57 | Vec2::ZERO, 58 | Vec2::ZERO, 59 | ], 60 | t1: [Vec2::ZERO, Vec2::ZERO, Vec2::ZERO, Vec2::ZERO, Vec2::ZERO], 61 | t2: [Vec2::ZERO, Vec2::ZERO, Vec2::ZERO, Vec2::ZERO, Vec2::ZERO], 62 | 63 | l: vec3(1.0, 0.72, 1.0).normalize(), 64 | 65 | t_morph: 0.0, 66 | mat2_rot: Mat2::ZERO, 67 | } 68 | } 69 | } 70 | 71 | fn u(a: Vec2, b: Vec2) -> f32 { 72 | a.x * b.y - b.x * a.y 73 | } 74 | 75 | const Y: Vec3 = vec3(0.0, 1.0, 0.0); 76 | // const E: Vec3 = Y * 0.01; 77 | const _E: Vec3 = vec3(0.0, 0.01, 0.0); 78 | 79 | // Distance to Bezier 80 | // inspired by [iq:https://www.shadertoy.com/view/ldj3Wh] 81 | // calculate distance to 2D bezier curve on xy but without forgeting the z component of p 82 | // total distance is corrected using pytagore just before return 83 | fn bezier(mut m: Vec2, mut n: Vec2, mut o: Vec2, p: Vec3) -> Vec2 { 84 | let q: Vec2 = p.xy(); 85 | m -= q; 86 | n -= q; 87 | o -= q; 88 | let x: f32 = u(m, o); 89 | let y: f32 = 2.0 * u(n, m); 90 | let z: f32 = 2.0 * u(o, n); 91 | let i: Vec2 = o - m; 92 | let j: Vec2 = o - n; 93 | let k: Vec2 = n - m; 94 | let s: Vec2 = 2. * (x * i + y * j + z * k); 95 | let mut r: Vec2 = m + (y * z - x * x) * vec2(s.y, -s.x) / s.dot(s); 96 | let t: f32 = ((u(r, i) + 2.0 * u(k, r)) / (x + x + y + z)).clamp(0.0, 1.0); // parametric position on curve 97 | r = m + t * (k + k + t * (j - k)); // distance on 2D xy space 98 | vec2((r.dot(r) + p.z * p.z).sqrt(), t) // distance on 3D space 99 | } 100 | 101 | fn smin(a: f32, b: f32, k: f32) -> f32 { 102 | let h: f32 = (0.5 + 0.5 * (b - a) / k).clamp(0.0, 1.0); 103 | mix(b, a, h) - k * h * (1. - h) 104 | } 105 | 106 | impl State { 107 | // Distance to scene 108 | fn m(&self, mut p: Vec3) -> f32 { 109 | // Distance to Teapot --------------------------------------------------- 110 | // precalcul first part of teapot spout 111 | let h: Vec2 = bezier(self.t1[2], self.t1[3], self.t1[4], p); 112 | let mut a: f32 = 99.0; 113 | // distance to teapot handle (-.06 => make the thickness) 114 | let b: f32 = (bezier(self.t2[0], self.t2[1], self.t2[2], p) 115 | .x 116 | .min(bezier(self.t2[2], self.t2[3], self.t2[4], p).x) 117 | - 0.06) 118 | // max p.y-.9 => cut the end of the spout 119 | .min( 120 | (p.y - 0.9).max( 121 | // distance to second part of teapot spout (abs(dist,r1)-dr) => enable to make the spout hole 122 | ((bezier(self.t1[0], self.t1[1], self.t1[2], p).x - 0.07).abs() - 0.01) 123 | // distance to first part of teapot spout (tickness incrase with pos on curve) 124 | .min(h.x * (1. - 0.75 * h.y) - 0.08), 125 | ), 126 | ); 127 | 128 | // distance to teapot body => use rotation symetry to simplify calculation to a distance to 2D bezier curve 129 | let qq: Vec3 = vec3((p.dot(p) - p.y * p.y).sqrt(), p.y, 0.0); 130 | // the substraction of .015 enable to generate a small thickness arround bezier to help convergance 131 | // the .8 factor help convergance 132 | let mut i = 0; 133 | while i < 13 { 134 | a = a.min((bezier(self.a[i], self.a[i + 1], self.a[i + 2], qq).x - 0.015) * 0.7); 135 | i += 2; 136 | } 137 | // smooth minimum to improve quality at junction of handle and spout to the body 138 | let d_teapot: f32 = smin(a, b, 0.02); 139 | 140 | // Distance to other shapes --------------------------------------------- 141 | let mut d_shape: f32; 142 | let id_morph: i32 = ((0.5 + (self.inputs.time) / (2.0 * 3.141592658)).floor() % 3.0) as i32; 143 | 144 | if id_morph == 1 { 145 | p = (self.mat2_rot.transpose() * p.xz()).extend(p.y).xzy(); 146 | let d: Vec3 = (p - vec3(0.0, 0.5, 0.0)).abs() - vec3(0.8, 0.7, 0.8); 147 | d_shape = d.x.max(d.y.max(d.z)).min(0.0) + d.max(Vec3::ZERO).length(); 148 | } else if id_morph == 2 { 149 | p -= vec3(0.0, 0.55, 0.0); 150 | let d1: Vec3 = p.abs() - vec3(0.67, 0.67, 0.67 * 1.618); 151 | let d3: Vec3 = p.abs() - vec3(0.67 * 1.618, 0.67, 0.67); 152 | d_shape = d1.x.max(d1.y.max(d1.z)).min(0.0) + d1.max(Vec3::ZERO).length(); 153 | d_shape = d_shape.min(d3.x.max(d3.y.max(d3.z)).min(0.0) + d3.max(Vec3::ZERO).length()); 154 | } else { 155 | d_shape = (p - vec3(0.0, 0.45, 0.0)).length() - 1.1; 156 | } 157 | 158 | // !!! The morphing is here !!! 159 | mix(d_teapot, d_shape, self.t_morph.abs()) 160 | } 161 | } 162 | 163 | // HSV to RGB conversion 164 | // [iq: https://www.shadertoy.com/view/MsS3Wc] 165 | fn hsv2rgb_smooth(x: f32, y: f32, z: f32) -> Vec3 { 166 | let mut rgb: Vec3 = (((x * Vec3::splat(6.0) + vec3(0.0, 4.0, 2.0)) 167 | .rem_euclid(Vec3::splat(6.0)) 168 | - Vec3::splat(3.0)) 169 | .abs() 170 | - Vec3::ONE) 171 | .clamp(Vec3::ZERO, Vec3::ONE); 172 | rgb = rgb * rgb * (Vec3::splat(3.0) - 2.0 * rgb); // cubic smoothing 173 | z * mix(Vec3::ONE, rgb, y) 174 | } 175 | 176 | impl State { 177 | fn normal(&self, p: Vec3, ray: Vec3, t: f32) -> Vec3 { 178 | let pitch: f32 = 0.4 * t / self.inputs.resolution.x; 179 | let d: Vec2 = vec2(-1.0, 1.0) * pitch; 180 | // tetrahedral offsets 181 | let p0: Vec3 = p + d.xxx(); 182 | let p1: Vec3 = p + d.xyy(); 183 | let p2: Vec3 = p + d.yxy(); 184 | let p3: Vec3 = p + d.yyx(); 185 | let f0: f32 = self.m(p0); 186 | let f1: f32 = self.m(p1); 187 | let f2: f32 = self.m(p2); 188 | let f3: f32 = self.m(p3); 189 | let grad: Vec3 = p0 * f0 + p1 * f1 + p2 * f2 + p3 * f3 - p * (f0 + f1 + f2 + f3); 190 | // prevent normals pointing away from camera (caused by precision errors) 191 | (grad - (grad.dot(ray).max(0.0)) * ray).normalize() 192 | } 193 | 194 | pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { 195 | let aa: f32 = 3.14159 / 4.0; 196 | self.mat2_rot = Mat2::from_cols_array(&[aa.cos(), aa.sin(), -aa.sin(), aa.cos()]); 197 | 198 | // Morphing step 199 | self.t_morph = (self.inputs.time * 0.5).cos(); 200 | self.t_morph *= self.t_morph * self.t_morph * self.t_morph * self.t_morph; 201 | 202 | // Teapot body profil (8 quadratic curves) 203 | self.a[0] = vec2(0.0, 0.0); 204 | self.a[1] = vec2(0.64, 0.0); 205 | self.a[2] = vec2(0.64, 0.03); 206 | self.a[3] = vec2(0.8, 0.12); 207 | self.a[4] = vec2(0.8, 0.3); 208 | self.a[5] = vec2(0.8, 0.48); 209 | self.a[6] = vec2(0.64, 0.9); 210 | self.a[7] = vec2(0.6, 0.93); 211 | self.a[8] = vec2(0.56, 0.9); 212 | self.a[9] = vec2(0.56, 0.96); 213 | self.a[10] = vec2(0.12, 1.02); 214 | self.a[11] = vec2(0.0, 1.05); 215 | self.a[12] = vec2(0.16, 1.14); 216 | self.a[13] = vec2(0.2, 1.2); 217 | self.a[14] = vec2(0.0, 1.2); 218 | // Teapot spout (2 quadratic curves) 219 | self.t1[0] = vec2(1.16, 0.96); 220 | self.t1[1] = vec2(1.04, 0.9); 221 | self.t1[2] = vec2(1.0, 0.72); 222 | self.t1[3] = vec2(0.92, 0.48); 223 | self.t1[4] = vec2(0.72, 0.42); 224 | // Teapot handle (2 quadratic curves) 225 | self.t2[0] = vec2(-0.6, 0.78); 226 | self.t2[1] = vec2(-1.16, 0.84); 227 | self.t2[2] = vec2(-1.16, 0.63); 228 | self.t2[3] = vec2(-1.2, 0.42); 229 | self.t2[4] = vec2(-0.72, 0.24); 230 | 231 | // Configure camera 232 | let r: Vec2 = self.inputs.resolution.xy(); 233 | let m: Vec2 = self.inputs.mouse.xy() / r; 234 | let q: Vec2 = frag_coord / r; 235 | let mut p: Vec2 = q + q - Vec2::ONE; 236 | p.x *= r.x / r.y; 237 | let mut j: f32 = 0.0; 238 | let mut s: f32 = 1.0; 239 | let mut h: f32 = 0.1; 240 | let mut t: f32 = 5.0 + 0.2 * self.inputs.time + 4.0 * m.x; 241 | let o: Vec3 = 2.9 * vec3(t.cos(), 0.7 - m.y, t.sin()); 242 | let w: Vec3 = (Y * 0.4 - o).normalize(); 243 | let u: Vec3 = w.cross(Y).normalize(); 244 | let v: Vec3 = u.cross(w); 245 | let d: Vec3 = (p.x * u + p.y * v + w + w).normalize(); 246 | let n: Vec3; 247 | let x: Vec3; 248 | 249 | // Ray marching 250 | t = 0.0; 251 | for _ in 0..48 { 252 | if h < 0.0001 || t > 4.7 { 253 | break; 254 | } 255 | h = self.m(o + d * t); 256 | t += h; 257 | } 258 | 259 | // Background colour change as teapot complementaries colours (using HSV) 260 | let mut c: Vec3 = mix( 261 | hsv2rgb_smooth(0.5 + self.inputs.time * 0.02, 0.35, 0.4), 262 | hsv2rgb_smooth(-0.5 + self.inputs.time * 0.02, 0.35, 0.7), 263 | q.y, 264 | ); 265 | 266 | // Calculate color on point 267 | if h < 0.001 { 268 | x = o + t * d; 269 | n = self.normal(x, d, t); //normalize(vec3(M(x+E.yxx)-M(x-E.yxx),M(x+E)-M(x-E),M(x+E.xxy)-M(x-E.xxy))); 270 | 271 | // Calculate Shadows 272 | for _ in 0..20 { 273 | j += 0.02; 274 | s = s.min(self.m(x + self.l * j) / j); 275 | } 276 | // Teapot color rotation in HSV color space 277 | let c1: Vec3 = hsv2rgb_smooth(0.9 + self.inputs.time * 0.02, 1.0, 1.0); 278 | // Shading 279 | c = mix( 280 | c, 281 | mix( 282 | (((3.0 * s).clamp(0.0, 1.0) + 0.3) * c1).sqrt(), 283 | Vec3::splat(self.l.reflect(n).dot(d).max(0.0).powf(99.0)), 284 | 0.4, 285 | ), 286 | 2.0 * n.dot(-d), 287 | ); 288 | } 289 | 290 | c *= (16.0 * q.x * q.y * (1.0 - q.x) * (1.0 - q.y)).powf(0.16); // Vigneting 291 | *frag_color = c.extend(1.0); 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /shaders/src/moving_square.rs: -------------------------------------------------------------------------------- 1 | //! Ported to Rust from 2 | 3 | use spirv_std::glam::{vec3, Mat2, Vec2, Vec3, Vec3Swizzles, Vec4}; 4 | 5 | // Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but 6 | // we tie #[no_std] above to the same condition, so it's fine. 7 | #[cfg(target_arch = "spirv")] 8 | use spirv_std::num_traits::Float; 9 | 10 | pub struct Inputs { 11 | pub resolution: Vec3, 12 | pub time: f32, 13 | } 14 | 15 | fn rect(uv: Vec2, pos: Vec2, r: f32) -> Vec4 { 16 | let re_c: Vec2 = (uv - pos).abs(); 17 | let dif1: Vec2 = re_c - Vec2::splat(r / 2.); 18 | let dif2: Vec2 = (re_c - Vec2::splat(r / 2.)).clamp(Vec2::ZERO, Vec2::ONE); 19 | let d1: f32 = (dif1.x + dif1.y).clamp(0.0, 1.0); 20 | let _d2: f32 = (dif2.x + dif2.y).clamp(0.0, 1.0); 21 | 22 | Vec4::splat(d1) 23 | } 24 | 25 | impl Inputs { 26 | pub fn main_image(&self, frag_color: &mut Vec4, frag_coord: Vec2) { 27 | let mut uv: Vec2 = frag_coord; 28 | let t: f32 = self.time.sin(); 29 | 30 | let c: Vec2 = self.resolution.xy() * 0.5; // + sin(iTime) * 50.; 31 | 32 | uv = Mat2::from_cols_array(&[t.cos(), -t.sin(), t.sin(), t.cos()]) * (uv - c) + c; 33 | 34 | *frag_color = rect(uv, c, (self.time * 10.).sin() * 50. + 50.); 35 | *frag_color *= vec3(0.5, 0.2, 1.).extend(1.); 36 | *frag_color += rect(uv, c, self.time.sin() * 50. + 50.); 37 | *frag_color *= vec3(0.5, 0.8, 1.).extend(1.); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /shaders/src/on_off_spikes.rs: -------------------------------------------------------------------------------- 1 | //! Ported to Rust from 2 | //! 3 | //! Original comment: 4 | //! ```glsl 5 | //! // On/Off Spikes, fragment shader by movAX13h, oct 2014 6 | //! ``` 7 | 8 | use shared::*; 9 | use spirv_std::glam::{ 10 | vec2, vec3, Mat2, Mat3, Vec2, Vec2Swizzles, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles, 11 | }; 12 | 13 | // Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but 14 | // we tie #[no_std] above to the same condition, so it's fine. 15 | #[cfg(target_arch = "spirv")] 16 | use spirv_std::num_traits::Float; 17 | 18 | pub struct Inputs { 19 | pub resolution: Vec3, 20 | pub time: f32, 21 | pub mouse: Vec4, 22 | } 23 | 24 | pub struct State { 25 | inputs: Inputs, 26 | 27 | // globals 28 | glow: f32, 29 | bite: f32, 30 | sphere_col: Vec3, 31 | sun: Vec3, 32 | focus: f32, 33 | far: f32, 34 | } 35 | 36 | impl State { 37 | pub fn new(inputs: Inputs) -> State { 38 | State { 39 | inputs, 40 | 41 | glow: 0.0, 42 | bite: 0.0, 43 | sphere_col: Vec3::ZERO, 44 | sun: SUN_POS.normalize(), 45 | focus: 5.0, 46 | far: 23.0, 47 | } 48 | } 49 | } 50 | 51 | const HARD_SHADOW: bool = true; 52 | const GLOW: bool = true; 53 | const EDGES: bool = true; 54 | const NUM_TENTACLES: i32 = 6; 55 | const BUMPS: bool = true; 56 | const NUM_BUMPS: i32 = 8; 57 | const BACKGROUND: bool = true; 58 | const SUN_POS: Vec3 = vec3(15.0, 15.0, -15.0); 59 | const SUN_SPHERE: bool = false; 60 | 61 | const SPHERE_COL: Vec3 = vec3(0.6, 0.3, 0.1); 62 | const MOUTH_COL: Vec3 = vec3(0.9, 0.6, 0.1); 63 | const TENTACLE_COL: Vec3 = vec3(0.06, 0.06, 0.06); 64 | 65 | const GAMMA: f32 = 2.2; 66 | 67 | //--- 68 | const PI2: f32 = 6.283185307179586476925286766559; 69 | const PIH: f32 = 1.5707963267949; 70 | 71 | // Using the nebula function of the "Star map shader" by morgan3d 72 | // as environment map and light sphere texture (https://www.shadertoy.com/view/4sBXzG) 73 | const _PI: f32 = 3.1415927; 74 | const NUM_OCTAVES: i32 = 4; 75 | fn hash(n: f32) -> f32 { 76 | (n.sin() * 1e4).fract_gl() 77 | } 78 | fn hash_vec2(p: Vec2) -> f32 { 79 | (1e4 * (17.0 * p.x + p.y * 0.1).sin() * (0.1 + (p.y * 13.0 + p.x).sin().abs())).fract_gl() 80 | } 81 | fn noise(x: f32) -> f32 { 82 | let i: f32 = x.floor(); 83 | let f: f32 = x.fract_gl(); 84 | let u: f32 = f * f * (3.0 - 2.0 * f); 85 | mix(hash(i), hash(i + 1.0), u) 86 | } 87 | fn noise_vec2(x: Vec2) -> f32 { 88 | let i: Vec2 = x.floor(); 89 | let f: Vec2 = x.fract_gl(); 90 | let a: f32 = hash_vec2(i); 91 | let b: f32 = hash_vec2(i + vec2(1.0, 0.0)); 92 | let c: f32 = hash_vec2(i + vec2(0.0, 1.0)); 93 | let d: f32 = hash_vec2(i + vec2(1.0, 1.0)); 94 | let u: Vec2 = f * f * (Vec2::splat(3.0) - 2.0 * f); 95 | mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y 96 | } 97 | fn noise2(mut x: Vec2) -> f32 { 98 | let mut v: f32 = 0.0; 99 | let mut a: f32 = 0.5; 100 | let shift: Vec2 = Vec2::splat(100.0); 101 | let rot: Mat2 = 102 | Mat2::from_cols_array(&[0.5_f32.cos(), 0.5_f32.sin(), -0.5_f32.sin(), 0.50_f32.cos()]); 103 | 104 | for _ in 0..NUM_OCTAVES { 105 | v += a * noise_vec2(x); 106 | x = rot * x * 2.0 + shift; 107 | a *= 0.5; 108 | } 109 | v 110 | } 111 | fn square(x: f32) -> f32 { 112 | x * x 113 | } 114 | fn rotation(yaw: f32, pitch: f32) -> Mat3 { 115 | Mat3::from_cols_array(&[ 116 | yaw.cos(), 117 | 0.0, 118 | -yaw.sin(), 119 | 0.0, 120 | 1.0, 121 | 0.0, 122 | yaw.sin(), 123 | 0.0, 124 | yaw.cos(), 125 | ]) * Mat3::from_cols_array(&[ 126 | 1.0, 127 | 0.0, 128 | 0.0, 129 | 0.0, 130 | pitch.cos(), 131 | pitch.sin(), 132 | 0.0, 133 | -pitch.sin(), 134 | pitch.cos(), 135 | ]) 136 | } 137 | fn nebula(dir: Vec3) -> Vec3 { 138 | let purple: f32 = dir.x.abs(); 139 | let yellow: f32 = noise(dir.y); 140 | let streaky_hue: Vec3 = vec3(purple + yellow, yellow * 0.7, purple); 141 | let puffy_hue: Vec3 = vec3(0.8, 0.1, 1.0); 142 | let streaky: f32 = 1.0_f32.min( 143 | 8.0 * (noise2( 144 | dir.yz() * square(dir.x) * 13.0 + dir.xy() * square(dir.z) * 7.0 + vec2(150.0, 2.0), 145 | )) 146 | .powf(10.0), 147 | ); 148 | let puffy: f32 = square(noise2(dir.xz() * 4.0 + vec2(30.0, 10.0)) * dir.y); 149 | 150 | (puffy_hue * puffy * (1.0 - streaky) + streaky * streaky_hue) 151 | .clamp(Vec3::ZERO, Vec3::ONE) 152 | .powf(1.0 / 2.2) 153 | } 154 | // --- 155 | 156 | fn sd_box(p: Vec3, b: Vec3) -> f32 { 157 | let d: Vec3 = p.abs() - b; 158 | d.x.max(d.y.max(d.z)).min(0.0) + d.max(Vec3::ZERO).length() 159 | } 160 | 161 | fn sd_sphere(p: Vec3, r: f32) -> f32 { 162 | p.length() - r 163 | } 164 | 165 | fn sd_capped_cylinder(p: Vec3, h: Vec2) -> f32 { 166 | let d: Vec2 = vec2(p.xy().length(), p.z).abs() - h; 167 | d.x.max(d.y).min(0.0) + d.max(Vec2::ZERO).length() 168 | } 169 | 170 | fn rotate(p: Vec2, a: f32) -> Vec2 { 171 | let mut r: Vec2 = Vec2::ZERO; 172 | r.x = p.x * a.cos() - p.y * a.sin(); 173 | r.y = p.x * a.sin() + p.y * a.cos(); 174 | r 175 | } 176 | 177 | // polynomial smooth min (k = 0.1); by iq 178 | fn smin(a: f32, b: f32, k: f32) -> f32 { 179 | let h: f32 = (0.5 + 0.5 * (b - a) / k).clamp(0.0, 1.0); 180 | mix(b, a, h) - k * h * (1.0 - h) 181 | } 182 | 183 | #[derive(Clone, Copy, Default)] 184 | struct Hit { 185 | d: f32, 186 | color: Vec3, 187 | edge: f32, 188 | } 189 | 190 | impl State { 191 | fn scene(&self, p: Vec3) -> Hit { 192 | let mut d: f32; 193 | let mut d1: f32; 194 | let mut d2: f32; 195 | let d3: f32; 196 | let f: f32; 197 | let mut e: f32 = 0.15; 198 | 199 | let mut q: Vec3 = p; 200 | q = rotate(q.xy(), 1.5).extend(q.z); 201 | 202 | // center sphere 203 | d1 = sd_sphere(q, 0.3); 204 | // d = d1; 205 | let mut col: Vec3 = self.sphere_col; 206 | 207 | // tentacles 208 | let r: f32 = q.length(); 209 | let mut a: f32 = q.z.atan2(q.x); 210 | a += 0.4 * (r - self.inputs.time).sin(); 211 | 212 | q = vec3(a * NUM_TENTACLES as f32 / PI2, q.y, q.xz().length()); // circular domain 213 | q = vec3(q.x.rem_euclid(1.0) - 0.5 * 1.0, q.y, q.z); // repetition 214 | 215 | d3 = sd_capped_cylinder( 216 | q - vec3(0.0, 0.0, 0.9 + self.bite), 217 | vec2(0.1 - (r - self.bite) / 18.0, 0.8), 218 | ); 219 | d2 = d3.min(sd_box( 220 | q - vec3(0.0, 0.0, 0.1 + self.bite), 221 | vec3(0.2, 0.2, 0.2), 222 | )); // close box 223 | d2 = smin( 224 | d2, 225 | sd_box(q - vec3(0.0, 0.0, 0.4 + self.bite), vec3(0.2, 0.05, 0.4)), 226 | 0.1, 227 | ); // wide box 228 | 229 | f = smoothstep(0.11, 0.28, d2 - d1); 230 | col = mix(MOUTH_COL, col, f); 231 | e = mix(e, 0.0, f); 232 | d = smin(d1, d2, 0.24); 233 | 234 | col = mix(TENTACLE_COL, col, smoothstep(0.0, 0.48, d3 - d)); 235 | 236 | if SUN_SPHERE { 237 | d = d.min(sd_sphere(p - self.sun, 0.1)); 238 | } 239 | 240 | if BUMPS { 241 | for i in 0..NUM_BUMPS { 242 | d2 = i as f32; 243 | d1 = sd_sphere( 244 | p - 0.18 245 | * smoothstep(0.1, 1.0, self.glow) 246 | * vec3( 247 | (4.0 * self.inputs.time + d2 * 0.6).sin(), 248 | (5.3 * self.inputs.time + d2 * 1.4).sin(), 249 | (5.8 * self.inputs.time + d2 * 0.6).cos(), 250 | ), 251 | 0.03, 252 | ); 253 | 254 | d = smin(d1, d, 0.2); 255 | //d = min(d1, d); 256 | } 257 | } 258 | 259 | if BACKGROUND { 260 | q = p; 261 | q = q.yz().rem_euclid(Vec2::ONE).extend(q.x).zxy(); 262 | q -= vec3(-0.6, 0.5, 0.5); 263 | d1 = sd_box(q, vec3(0.1, 0.48, 0.48)); 264 | if d1 < d { 265 | d = d1; 266 | col = Vec3::splat(0.1); 267 | } 268 | } 269 | 270 | Hit { 271 | d, 272 | color: col, 273 | edge: e, 274 | } 275 | } 276 | 277 | fn normal(&self, p: Vec3) -> Vec3 { 278 | let c: f32 = self.scene(p).d; 279 | let h: Vec2 = vec2(0.01, 0.0); 280 | vec3( 281 | self.scene(p + h.xyy()).d - c, 282 | self.scene(p + h.yxy()).d - c, 283 | self.scene(p + h.yyx()).d - c, 284 | ) 285 | .normalize() 286 | } 287 | 288 | // by srtuss 289 | fn edges(&self, p: Vec3) -> f32 { 290 | let mut acc: f32 = 0.0; 291 | let h: f32 = 0.01; 292 | acc += self.scene(p + vec3(-h, -h, -h)).d; 293 | acc += self.scene(p + vec3(-h, -h, h)).d; 294 | acc += self.scene(p + vec3(-h, h, -h)).d; 295 | acc += self.scene(p + vec3(-h, h, h)).d; 296 | acc += self.scene(p + vec3(h, -h, -h)).d; 297 | acc += self.scene(p + vec3(h, -h, h)).d; 298 | acc += self.scene(p + vec3(h, h, -h)).d; 299 | acc += self.scene(p + vec3(h, h, h)).d; 300 | acc / h 301 | } 302 | 303 | fn colorize(&self, hit: Hit, n: Vec3, dir: Vec3, light_pos: Vec3) -> Vec3 { 304 | let diffuse: f32 = 0.3 * n.dot(light_pos).max(0.0); 305 | 306 | let ref_: Vec3 = dir.reflect(n).normalize(); 307 | let specular: f32 = 0.4 * ref_.dot(light_pos).max(0.0).powf(6.5); 308 | 309 | hit.color + diffuse * Vec3::splat(0.9) + specular * Vec3::splat(1.0) 310 | } 311 | 312 | pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { 313 | //self.time = self.inputs.time; 314 | self.glow = (2.0 * (self.inputs.time * 0.7 - 5.0).sin()) 315 | .min(1.0) 316 | .max(0.0); 317 | self.bite = smoothstep(0.0, 1.0, 1.6 * (self.inputs.time * 0.7).sin()); 318 | self.sphere_col = SPHERE_COL * self.glow; 319 | 320 | let pos: Vec2 = (frag_coord * 2.0 - self.inputs.resolution.xy()) / self.inputs.resolution.y; 321 | 322 | let d: f32 = (1.5 * (0.3 * self.inputs.time).sin()).clamp(0.5, 1.0); 323 | let mut cp: Vec3 = vec3( 324 | 10.0 * d, 325 | -2.3 * d, 326 | -6.2 * d + 4.0 * (2.0 * (self.inputs.time * 0.5).sin().clamp(0.0, 1.0)), 327 | ); // anim curious spectator 328 | 329 | if self.inputs.mouse.z > 0.5 { 330 | let mrel: Vec2 = 331 | self.inputs.mouse.xy() / self.inputs.resolution.xy() - Vec2::splat(0.5); 332 | let mdis: f32 = 8.0 + 6.0 * mrel.y; 333 | cp = vec3( 334 | mdis * (-mrel.x * PIH).cos(), 335 | 4.0 * mrel.y, 336 | mdis * (-mrel.x * PIH).sin(), 337 | ); 338 | } 339 | 340 | let ct: Vec3 = vec3(0.0, 0.0, 0.0); 341 | let cd: Vec3 = (ct - cp).normalize(); 342 | let cu: Vec3 = vec3(0.0, 1.0, 0.0); 343 | let cs: Vec3 = cd.cross(cu); 344 | let mut dir: Vec3 = (cs * pos.x + cu * pos.y + cd * self.focus).normalize(); 345 | 346 | let mut h: Hit = Hit::default(); 347 | let mut col: Vec3; 348 | let mut ray: Vec3 = cp; 349 | let mut dist: f32 = 0.0; 350 | 351 | // raymarch scene 352 | for _ in 0..60 { 353 | h = self.scene(ray); 354 | 355 | if h.d < 0.0001 { 356 | break; 357 | } 358 | 359 | dist += h.d; 360 | ray += dir * h.d * 0.9; 361 | 362 | if dist > self.far { 363 | dist = self.far; 364 | break; 365 | } 366 | } 367 | 368 | let m: f32 = 1.0 - dist / self.far; 369 | let n: Vec3 = self.normal(ray); 370 | col = self.colorize(h, n, dir, self.sun) * m; 371 | 372 | if EDGES { 373 | let edge: f32 = self.edges(ray); 374 | col = mix( 375 | col, 376 | Vec3::ZERO, 377 | h.edge * edge * smoothstep(0.3, 0.35, ray.length()), 378 | ); 379 | } 380 | 381 | let neb: Vec3 = nebula(n); 382 | col += self.glow.min(0.1) * neb.zxy(); 383 | 384 | // HARD SHADOW with low number of rm iterations (from obj to sun) 385 | if HARD_SHADOW { 386 | let mut ray1: Vec3 = ray; 387 | dir = (SUN_POS - ray1).normalize(); 388 | ray1 += n * 0.002; 389 | 390 | let sun_dist: f32 = (SUN_POS - ray1).length(); 391 | dist = 0.0; 392 | 393 | for _ in 0..35 { 394 | h = self.scene(ray1 + dir * dist); 395 | dist += h.d; 396 | if h.d.abs() < 0.001 { 397 | break; 398 | } 399 | } 400 | 401 | col -= Vec3::splat( 402 | 0.24 * smoothstep(0.5, -0.3, dist.min(sun_dist) / sun_dist.max(0.0001)), 403 | ); 404 | } 405 | 406 | // ILLUMINATION & free shadow with low number of rm iterations (from obj to sphere) 407 | if GLOW { 408 | dir = (-ray).normalize(); 409 | ray += n * 0.002; 410 | 411 | let sphere_dist: f32 = (ray.length() - 0.3).max(0.0001); 412 | dist = 0.0; 413 | 414 | for _ in 0..35 { 415 | h = self.scene(ray + dir * dist); 416 | dist += h.d; 417 | if h.d.abs() < 0.001 { 418 | break; 419 | } 420 | } 421 | 422 | let neb1: Vec3 = nebula(rotation(0.0, self.inputs.time * 0.4).transpose() * dir).zxy(); 423 | 424 | col += (0.7 * self.sphere_col + self.glow * neb1) 425 | * (0.6 * (smoothstep(3.0, 0.0, sphere_dist)) * dist.min(sphere_dist) / sphere_dist 426 | + 0.6 * smoothstep(0.1, 0.0, sphere_dist)); 427 | } 428 | 429 | col -= Vec3::splat(0.2 * smoothstep(0.6, 3.7, pos.length())); 430 | col = col.clamp(Vec3::ZERO, Vec3::ONE); 431 | col = col.powf_vec(vec3(2.2, 2.4, 2.5)) * 3.9; 432 | col = col.powf_vec(Vec3::splat(1.0 / GAMMA)); 433 | 434 | *frag_color = col.extend(1.0); 435 | } 436 | } 437 | -------------------------------------------------------------------------------- /shaders/src/phantom_star.rs: -------------------------------------------------------------------------------- 1 | //! Ported to Rust from 2 | 3 | use spirv_std::glam::{vec3, Mat2, Vec2, Vec3, Vec3Swizzles, Vec4}; 4 | 5 | use core::f32::consts::PI; 6 | 7 | // Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but 8 | // we tie #[no_std] above to the same condition, so it's fine. 9 | #[cfg(target_arch = "spirv")] 10 | use {shared::FloatExt, spirv_std::num_traits::Float}; 11 | 12 | pub struct Inputs { 13 | pub resolution: Vec3, 14 | pub time: f32, 15 | } 16 | 17 | fn rot(a: f32) -> Mat2 { 18 | let c: f32 = a.cos(); 19 | let s: f32 = a.sin(); 20 | Mat2::from_cols_array(&[c, s, -s, c]) 21 | } 22 | 23 | //const pi: f32 = (-1.0).acos(); 24 | const PI_: f32 = PI; 25 | const PI2: f32 = PI_ * 2.0; 26 | 27 | fn pmod(p: Vec2, r: f32) -> Vec2 { 28 | let mut a: f32 = p.x.atan2(p.y) + PI_ / r; 29 | let n: f32 = PI2 / r; 30 | a = (a / n).floor() * n; 31 | rot(-a).transpose() * p 32 | } 33 | 34 | fn box_(p: Vec3, b: Vec3) -> f32 { 35 | let d: Vec3 = p.abs() - b; 36 | d.x.max(d.y.max(d.z)).min(0.0) + d.max(Vec3::ZERO).length() 37 | } 38 | 39 | impl Inputs { 40 | fn ifs_box(&self, mut p: Vec3) -> f32 { 41 | for _ in 0..5 { 42 | p = p.abs() - Vec3::splat(1.0); 43 | p = (rot(self.time * 0.3).transpose() * p.xy()).extend(p.z); 44 | p = (rot(self.time * 0.1).transpose() * p.xz()) 45 | .extend(p.y) 46 | .xzy(); 47 | } 48 | p = (rot(self.time).transpose() * p.xz()).extend(p.y).xzy(); 49 | box_(p, vec3(0.4, 0.8, 0.3)) 50 | } 51 | 52 | fn map(&self, p: Vec3, _c_pos: Vec3) -> f32 { 53 | let mut p1: Vec3 = p; 54 | p1.x = (p1.x - 5.0).rem_euclid(10.0) - 5.0; 55 | p1.y = (p1.y - 5.0).rem_euclid(10.0) - 5.0; 56 | p1.z = p1.z.rem_euclid(16.0) - 8.0; 57 | p1 = pmod(p1.xy(), 5.0).extend(p1.z); 58 | self.ifs_box(p1) 59 | } 60 | 61 | pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { 62 | let p: Vec2 = 63 | (frag_coord * 2.0 - self.resolution.xy()) / self.resolution.x.min(self.resolution.y); 64 | 65 | let c_pos: Vec3 = vec3(0.0, 0.0, -3.0 * self.time); 66 | // let c_pos: Vec3 = vec3(0.3 * (self.time * 0.8).sin(), 0.4 * (self.time * 0.3).cos(), -6.0 * self.time,); 67 | let c_dir: Vec3 = vec3(0.0, 0.0, -1.0).normalize(); 68 | let c_up: Vec3 = vec3(self.time.sin(), 1.0, 0.0); 69 | let c_side: Vec3 = c_dir.cross(c_up); 70 | 71 | let ray: Vec3 = (c_side * p.x + c_up * p.y + c_dir).normalize(); 72 | 73 | // Phantom Mode https://www.shadertoy.com/view/MtScWW by aiekick 74 | let mut acc: f32 = 0.0; 75 | let mut acc2: f32 = 0.0; 76 | let mut t: f32 = 0.0; 77 | 78 | for _ in 0..99 { 79 | let pos: Vec3 = c_pos + ray * t; 80 | let mut dist: f32 = self.map(pos, c_pos); 81 | dist = dist.abs().max(0.02); 82 | let mut a: f32 = (-dist * 3.0).exp(); 83 | if (pos.length() + 24.0 * self.time).rem_euclid(30.0) < 3.0 { 84 | a *= 2.0; 85 | acc2 += a; 86 | } 87 | acc += a; 88 | t += dist * 0.5; 89 | } 90 | 91 | let col: Vec3 = vec3( 92 | acc * 0.01, 93 | acc * 0.011 + acc2 * 0.002, 94 | acc * 0.012 + acc2 * 0.005, 95 | ); 96 | *frag_color = col.extend(1.0 - t * 0.03); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /shaders/src/playing_marble.rs: -------------------------------------------------------------------------------- 1 | //! Ported to Rust from 2 | //! 3 | //! Original comment: 4 | //! ```glsl 5 | //! // License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. 6 | //! // Created by S. Guillitte 2015 7 | //! ``` 8 | 9 | use crate::SampleCube; 10 | use shared::*; 11 | use spirv_std::glam::{vec2, vec3, vec4, Mat2, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles}; 12 | 13 | // Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but 14 | // we tie #[no_std] above to the same condition, so it's fine. 15 | #[cfg(target_arch = "spirv")] 16 | use spirv_std::num_traits::Float; 17 | 18 | pub struct Inputs { 19 | pub resolution: Vec3, 20 | pub time: f32, 21 | pub mouse: Vec4, 22 | pub channel0: C0, 23 | } 24 | 25 | const ZOOM: f32 = 1.0; 26 | 27 | fn _cmul(a: Vec2, b: Vec2) -> Vec2 { 28 | vec2(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x) 29 | } 30 | fn csqr(a: Vec2) -> Vec2 { 31 | vec2(a.x * a.x - a.y * a.y, 2. * a.x * a.y) 32 | } 33 | 34 | fn rot(a: f32) -> Mat2 { 35 | Mat2::from_cols_array(&[a.cos(), a.sin(), -a.sin(), a.cos()]) 36 | } 37 | 38 | //from iq 39 | fn i_sphere(ro: Vec3, rd: Vec3, sph: Vec4) -> Vec2 { 40 | let oc: Vec3 = ro - sph.xyz(); 41 | let b: f32 = oc.dot(rd); 42 | let c: f32 = oc.dot(oc) - sph.w * sph.w; 43 | let mut h: f32 = b * b - c; 44 | if h < 0.0 { 45 | return Vec2::splat(-1.0); 46 | } 47 | h = h.sqrt(); 48 | vec2(-b - h, -b + h) 49 | } 50 | 51 | fn map(mut p: Vec3) -> f32 { 52 | let mut res: f32 = 0.0; 53 | let c: Vec3 = p; 54 | for _ in 0..10 { 55 | p = 0.7 * p.abs() / p.dot(p) - Vec3::splat(0.7); 56 | p = csqr(p.yz()).extend(p.x).zxy(); 57 | p = p.zxy(); 58 | res += (-19.0 * p.dot(c).abs()).exp(); 59 | } 60 | res / 2.0 61 | } 62 | 63 | impl Inputs { 64 | fn raymarch(&self, ro: Vec3, rd: Vec3, tminmax: Vec2) -> Vec3 { 65 | let mut t: f32 = tminmax.x; 66 | let dt: f32 = 0.02; 67 | //let dt: f32 = 0.2 - 0.195 * (self.time * 0.05).cos(); //animated 68 | let mut col: Vec3 = Vec3::ZERO; 69 | let mut c: f32 = 0.0; 70 | for _ in 0..64 { 71 | t += dt * (-2.0 * c).exp(); 72 | if t > tminmax.y { 73 | break; 74 | } 75 | let _pos: Vec3 = ro + t * rd; 76 | 77 | c = map(ro + t * rd); 78 | 79 | col = 0.99 * col + 0.08 * vec3(c * c, c, c * c * c); //green 80 | 81 | // col = 0.99 * col + 0.08 * vec3(c * c * c, c * c, c); //blue 82 | } 83 | col 84 | } 85 | pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { 86 | let time: f32 = self.time; 87 | let q: Vec2 = frag_coord / self.resolution.xy(); 88 | let mut p: Vec2 = Vec2::splat(-1.0) + 2.0 * q; 89 | p.x *= self.resolution.x / self.resolution.y; 90 | let mut m: Vec2 = Vec2::ZERO; 91 | if self.mouse.z > 0.0 { 92 | m = self.mouse.xy() / self.resolution.xy() * 3.14; 93 | } 94 | m = m - Vec2::splat(0.5); 95 | 96 | // camera 97 | 98 | let mut ro: Vec3 = ZOOM * Vec3::splat(4.0); 99 | ro = (rot(m.y).transpose() * ro.yz()).extend(ro.x).zxy(); 100 | ro = (rot(m.x + 0.1 * time).transpose() * ro.xz()) 101 | .extend(ro.y) 102 | .xzy(); 103 | let ta: Vec3 = Vec3::ZERO; 104 | let ww: Vec3 = (ta - ro).normalize(); 105 | let uu: Vec3 = (ww.cross(vec3(0.0, 1.0, 0.0))).normalize(); 106 | let vv: Vec3 = (uu.cross(ww)).normalize(); 107 | let rd: Vec3 = (p.x * uu + p.y * vv + 4.0 * ww).normalize(); 108 | 109 | let tmm: Vec2 = i_sphere(ro, rd, vec4(0.0, 0.0, 0.0, 2.0)); 110 | // raymarch 111 | let mut col: Vec3 = self.raymarch(ro, rd, tmm); 112 | if tmm.x < 0.0 { 113 | col = self.channel0.sample_cube(rd).xyz(); 114 | } else { 115 | let mut nor: Vec3 = (ro + tmm.x * rd) / 2.; 116 | nor = rd.reflect(nor); 117 | let fre: f32 = (0.5 + nor.dot(rd).clamp(0.0, 1.0)).powf(3.0) * 1.3; 118 | col += self.channel0.sample_cube(nor).xyz() * fre; 119 | } 120 | 121 | //shade 122 | 123 | col = 0.5 * (Vec3::ONE + col).ln(); 124 | col = col.clamp(Vec3::ZERO, Vec3::ONE); 125 | 126 | *frag_color = col.extend(1.0); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /shaders/src/protean_clouds.rs: -------------------------------------------------------------------------------- 1 | //! Ported to Rust from 2 | //! 3 | //! Original comment: 4 | //! ```glsl 5 | //! // Protean clouds by nimitz (twitter: @stormoid) 6 | //! // https://www.shadertoy.com/view/3l23Rh 7 | //! // License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License 8 | //! // Contact the author for other licensing options 9 | //! 10 | //! /* 11 | //! Technical details: 12 | //! 13 | //! The main volume noise is generated from a deformed periodic grid, which can produce 14 | //! a large range of noise-like patterns at very cheap evalutation cost. Allowing for multiple 15 | //! fetches of volume gradient computation for improved lighting. 16 | //! 17 | //! To further accelerate marching, since the volume is smooth, more than half the the density 18 | //! information isn't used to rendering or shading but only as an underlying volume distance to 19 | //! determine dynamic step size, by carefully selecting an equation (polynomial for speed) to 20 | //! step as a function of overall density (not necessarialy rendered) the visual results can be 21 | //! the same as a naive implementation with ~40% increase in rendering performance. 22 | //! 23 | //! Since the dynamic marching step size is even less uniform due to steps not being rendered at all 24 | //! the fog is evaluated as the difference of the fog integral at each rendered step. 25 | //! 26 | //! */ 27 | //! ``` 28 | 29 | use shared::*; 30 | use spirv_std::glam::{ 31 | mat3, vec2, vec3, vec4, Mat2, Mat3, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles, 32 | }; 33 | 34 | // Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but 35 | // we tie #[no_std] above to the same condition, so it's fine. 36 | #[cfg(target_arch = "spirv")] 37 | use spirv_std::num_traits::Float; 38 | 39 | pub struct Inputs { 40 | pub resolution: Vec3, 41 | pub time: f32, 42 | pub mouse: Vec4, 43 | } 44 | 45 | pub struct State { 46 | inputs: Inputs, 47 | prm1: f32, 48 | bs_mo: Vec2, 49 | } 50 | 51 | impl State { 52 | pub fn new(inputs: Inputs) -> Self { 53 | State { 54 | inputs, 55 | prm1: 0.0, 56 | bs_mo: Vec2::ZERO, 57 | } 58 | } 59 | } 60 | 61 | fn rot(a: f32) -> Mat2 { 62 | let c: f32 = a.cos(); 63 | let s: f32 = a.sin(); 64 | Mat2::from_cols_array(&[c, s, -s, c]) 65 | } 66 | 67 | // const m3: Mat3 = const_mat3!([ 68 | // 0.33338, 0.56034, -0.71817, -0.87887, 0.32651, -0.15323, 0.15162, 0.69596, 0.61339 69 | // ]) * 1.93; 70 | 71 | const M3: Mat3 = mat3( 72 | Vec3::new(0.33338 * 1.93, -0.87887 * 1.93, 0.15162 * 1.93), 73 | Vec3::new(0.56034 * 1.93, 0.32651 * 1.93, 0.69596 * 1.93), 74 | Vec3::new(-0.71817 * 1.93, -0.15323 * 1.93, 0.61339 * 1.93), 75 | ); 76 | 77 | fn mag2(p: Vec2) -> f32 { 78 | p.dot(p) 79 | } 80 | fn linstep(mn: f32, mx: f32, x: f32) -> f32 { 81 | ((x - mn) / (mx - mn)).clamp(0.0, 1.0) 82 | } 83 | 84 | fn disp(t: f32) -> Vec2 { 85 | vec2((t * 0.22).sin() * 1.0, (t * 0.175).cos() * 1.0) * 2.0 86 | } 87 | 88 | impl State { 89 | fn map(&self, mut p: Vec3) -> Vec2 { 90 | let mut p2: Vec3 = p; 91 | p2 = (p2.xy() - disp(p.z)).extend(p2.z); 92 | p = (rot( 93 | (p.z + self.inputs.time).sin() * (0.1 + self.prm1 * 0.05) + self.inputs.time * 0.09 94 | ) 95 | .transpose() 96 | * p.xy()) 97 | .extend(p.z); 98 | let cl: f32 = mag2(p2.xy()); 99 | let mut d: f32 = 0.0; 100 | p *= 0.61; 101 | let mut z: f32 = 1.0; 102 | let mut trk: f32 = 1.0; 103 | let dsp_amp: f32 = 0.1 + self.prm1 * 0.2; 104 | for _ in 0..5 { 105 | p += (p.zxy() * 0.75 * trk + Vec3::splat(self.inputs.time) * trk * 0.8).sin() * dsp_amp; 106 | d -= (p.cos().dot(p.yzx().sin()) * z).abs(); 107 | z *= 0.57; 108 | trk *= 1.4; 109 | let m3 = M3; 110 | p = m3.transpose() * p; 111 | } 112 | d = (d + self.prm1 * 3.0).abs() + self.prm1 * 0.3 - 2.5 + self.bs_mo.y; 113 | vec2(d + cl * 0.2 + 0.25, cl) 114 | } 115 | 116 | fn render(&self, ro: Vec3, rd: Vec3, time: f32) -> Vec4 { 117 | let mut rez: Vec4 = Vec4::ZERO; 118 | const LDST: f32 = 8.0; 119 | let _lpos: Vec3 = (disp(time + LDST) * 0.5).extend(time + LDST); 120 | let mut t: f32 = 1.5; 121 | let mut fog_t: f32 = 0.0; 122 | 123 | for _ in 0..130 { 124 | if rez.w > 0.99 { 125 | break; 126 | } 127 | 128 | let pos: Vec3 = ro + t * rd; 129 | let mpv: Vec2 = self.map(pos); 130 | let den: f32 = (mpv.x - 0.3).clamp(0.0, 1.0) * 1.12; 131 | let dn: f32 = (mpv.x + 2.0).clamp(0.0, 3.0); 132 | 133 | let mut col: Vec4 = Vec4::ZERO; 134 | if mpv.x > 0.6 { 135 | col = ((vec3(5.0, 0.4, 0.2) 136 | + Vec3::splat(mpv.y * 0.1 + (pos.z * 0.4).sin() * 0.5 + 1.8)) 137 | .sin() 138 | * 0.5 139 | + Vec3::splat(0.5)) 140 | .extend(0.08); 141 | col *= den * den * den; 142 | col = (col.xyz() * linstep(4.0, -2.5, mpv.x) * 2.3).extend(col.w); 143 | let mut dif: f32 = 144 | ((den - self.map(pos + Vec3::splat(0.8)).x) / 9.0).clamp(0.001, 1.0); 145 | dif += ((den - self.map(pos + Vec3::splat(0.35)).x) / 2.5).clamp(0.001, 1.0); 146 | col = (col.xyz() 147 | * den 148 | * (vec3(0.005, 0.045, 0.075) + 1.5 * vec3(0.033, 0.07, 0.03) * dif)) 149 | .extend(col.w); 150 | } 151 | 152 | let fog_c = (t * 0.2 - 2.2).exp(); 153 | col += vec4(0.06, 0.11, 0.11, 0.1) * (fog_c - fog_t).clamp(0.0, 1.0); 154 | fog_t = fog_c; 155 | rez = rez + col * (1.0 - rez.w); 156 | t += (0.5 - dn * dn * 0.05).clamp(0.09, 0.3); 157 | } 158 | 159 | rez.clamp(Vec4::ZERO, Vec4::ONE) 160 | } 161 | } 162 | 163 | fn getsat(c: Vec3) -> f32 { 164 | let mi: f32 = c.x.min(c.y).min(c.z); 165 | let ma: f32 = c.x.max(c.y).max(c.z); 166 | (ma - mi) / (ma + 1e-7) 167 | } 168 | 169 | //from my "Will it blend" shader (https://www.shadertoy.com/view/lsdGzN) 170 | fn i_lerp(a: Vec3, b: Vec3, x: f32) -> Vec3 { 171 | let mut ic: Vec3 = mix(a, b, x) + vec3(1e-6, 0.0, 0.0); 172 | let sd: f32 = (getsat(ic) - mix(getsat(a), getsat(b), x)).abs(); 173 | let dir: Vec3 = vec3( 174 | 2.0 * ic.x - ic.y - ic.z, 175 | 2.0 * ic.y - ic.x - ic.z, 176 | 2.0 * ic.z - ic.y - ic.x, 177 | ) 178 | .normalize(); 179 | let lgt: f32 = Vec3::splat(1.0).dot(ic); 180 | let ff: f32 = dir.dot(ic.normalize()); 181 | ic += 1.5 * dir * sd * ff * lgt; 182 | ic.clamp(Vec3::ZERO, Vec3::ONE) 183 | } 184 | 185 | impl State { 186 | pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { 187 | let q: Vec2 = frag_coord / self.inputs.resolution.xy(); 188 | let p: Vec2 = (frag_coord - 0.5 * self.inputs.resolution.xy()) / self.inputs.resolution.y; 189 | self.bs_mo = 190 | (self.inputs.mouse.xy() - 0.5 * self.inputs.resolution.xy()) / self.inputs.resolution.y; 191 | 192 | let time: f32 = self.inputs.time * 3.; 193 | let mut ro: Vec3 = vec3(0.0, 0.0, time); 194 | 195 | ro += vec3( 196 | self.inputs.time.sin() * 0.5, 197 | (self.inputs.time.sin() * 1.0) * 0.0, 198 | 0.0, 199 | ); 200 | 201 | let dsp_amp: f32 = 0.85; 202 | ro = (ro.xy() + disp(ro.z) * dsp_amp).extend(ro.z); 203 | let tgt_dst: f32 = 3.5; 204 | 205 | let target: Vec3 = 206 | (ro - (disp(time + tgt_dst) * dsp_amp).extend(time + tgt_dst)).normalize(); 207 | ro.x -= self.bs_mo.x * 2.; 208 | let mut rightdir: Vec3 = target.cross(vec3(0.0, 1.0, 0.0)).normalize(); 209 | let updir: Vec3 = rightdir.cross(target).normalize(); 210 | rightdir = updir.cross(target).normalize(); 211 | let mut rd: Vec3 = ((p.x * rightdir + p.y * updir) * 1.0 - target).normalize(); 212 | rd = (rot(-disp(time + 3.5).x * 0.2 + self.bs_mo.x).transpose() * rd.xy()).extend(rd.z); 213 | self.prm1 = smoothstep(-0.4, 0.4, (self.inputs.time * 0.3).sin()); 214 | let scn: Vec4 = self.render(ro, rd, time); 215 | 216 | let mut col: Vec3 = scn.xyz(); 217 | col = i_lerp(col.zyx(), col, (1.0 - self.prm1).clamp(0.05, 1.0)); 218 | 219 | col = col.powf_vec(vec3(0.55, 0.65, 0.6)) * vec3(1.0, 0.97, 0.9); 220 | 221 | col *= (16.0 * q.x * q.y * (1.0 - q.x) * (1.0 - q.y)).powf(0.12) * 0.7 + 0.3; //Vign 222 | 223 | *frag_color = col.extend(1.0); 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /shaders/src/seascape.rs: -------------------------------------------------------------------------------- 1 | //! Ported to Rust from 2 | //! 3 | //! Original comment: 4 | //! ```glsl 5 | //! /* 6 | //! * "Seascape" by Alexander Alekseev aka TDM - 2014 7 | //! * License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. 8 | //! * Contact: tdmaav@gmail.com 9 | //! */ 10 | //! ``` 11 | 12 | use shared::*; 13 | use spirv_std::glam::{mat2, vec2, vec3, Mat2, Mat3, Vec2, Vec3, Vec3Swizzles, Vec4}; 14 | 15 | // Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but 16 | // we tie #[no_std] above to the same condition, so it's fine. 17 | #[cfg(target_arch = "spirv")] 18 | use spirv_std::num_traits::Float; 19 | 20 | pub struct Inputs { 21 | pub resolution: Vec3, 22 | pub time: f32, 23 | pub mouse: Vec4, 24 | } 25 | 26 | const NUM_STEPS: usize = 8; 27 | const PI: f32 = 3.141592; 28 | const _EPSILON: f32 = 1e-3; 29 | impl Inputs { 30 | fn epsilon_nrm(&self) -> f32 { 31 | 0.1 / self.resolution.x 32 | } 33 | } 34 | const AA: bool = true; 35 | 36 | // sea 37 | const ITER_GEOMETRY: usize = 3; 38 | const ITER_FRAGMENT: usize = 5; 39 | const SEA_HEIGHT: f32 = 0.6; 40 | const SEA_CHOPPY: f32 = 4.0; 41 | const SEA_SPEED: f32 = 0.8; 42 | const SEA_FREQ: f32 = 0.16; 43 | const SEA_BASE: Vec3 = vec3(0.0, 0.09, 0.18); 44 | // const SEA_WATER_COLOR: Vec3 = const_vec3!([0.8, 0.9, 0.6]) * 0.6; 45 | const SEA_WATER_COLOR: Vec3 = vec3(0.8 * 0.6, 0.9 * 0.6, 0.6 * 0.6); 46 | impl Inputs { 47 | fn sea_time(&self) -> f32 { 48 | 1.0 + self.time * SEA_SPEED 49 | } 50 | } 51 | const OCTAVE_M: Mat2 = mat2(vec2(1.6, 1.2), vec2(-1.2, 1.6)); 52 | 53 | // math 54 | fn from_euler(ang: Vec3) -> Mat3 { 55 | let a1: Vec2 = vec2(ang.x.sin(), ang.x.cos()); 56 | let a2: Vec2 = vec2(ang.y.sin(), ang.y.cos()); 57 | let a3: Vec2 = vec2(ang.z.sin(), ang.z.cos()); 58 | Mat3::from_cols( 59 | vec3( 60 | a1.y * a3.y + a1.x * a2.x * a3.x, 61 | a1.y * a2.x * a3.x + a3.y * a1.x, 62 | -a2.y * a3.x, 63 | ), 64 | vec3(-a2.y * a1.x, a1.y * a2.y, a2.x), 65 | vec3( 66 | a3.y * a1.x * a2.x + a1.y * a3.x, 67 | a1.x * a3.x - a1.y * a3.y * a2.x, 68 | a2.y * a3.y, 69 | ), 70 | ) 71 | } 72 | fn hash(p: Vec2) -> f32 { 73 | let h: f32 = p.dot(vec2(127.1, 311.7)); 74 | (h.sin() * 43758.5453123).fract_gl() 75 | } 76 | fn noise(p: Vec2) -> f32 { 77 | let i: Vec2 = p.floor(); 78 | let f: Vec2 = p.fract_gl(); 79 | let u: Vec2 = f * f * (Vec2::splat(3.0) - 2.0 * f); 80 | -1.0 + 2.0 81 | * mix( 82 | mix(hash(i + vec2(0.0, 0.0)), hash(i + vec2(1.0, 0.0)), u.x), 83 | mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), u.x), 84 | u.y, 85 | ) 86 | } 87 | 88 | // lighting 89 | fn diffuse(n: Vec3, l: Vec3, p: f32) -> f32 { 90 | (n.dot(l) * 0.4 + 0.6).powf(p) 91 | } 92 | fn specular(n: Vec3, l: Vec3, e: Vec3, s: f32) -> f32 { 93 | let nrm: f32 = (s + 8.0) / (PI * 8.0); 94 | (e.reflect(n).dot(l).max(0.0)).powf(s) * nrm 95 | } 96 | 97 | // sky 98 | fn get_sky_color(mut e: Vec3) -> Vec3 { 99 | e.y = (e.y.max(0.0) * 0.8 + 0.2) * 0.8; 100 | vec3((1.0 - e.y).powf(2.0), 1.0 - e.y, 0.6 + (1.0 - e.y) * 0.4) * 1.1 101 | } 102 | 103 | // sea 104 | fn sea_octave(mut uv: Vec2, choppy: f32) -> f32 { 105 | uv += Vec2::splat(noise(uv)); 106 | let mut wv: Vec2 = Vec2::ONE - uv.sin().abs(); 107 | let swv: Vec2 = uv.cos().abs(); 108 | wv = mix(wv, swv, wv); 109 | (1.0 - (wv.x * wv.y).powf(0.65)).powf(choppy) 110 | } 111 | 112 | impl Inputs { 113 | fn map(&self, p: Vec3) -> f32 { 114 | let mut freq: f32 = SEA_FREQ; 115 | let mut amp: f32 = SEA_HEIGHT; 116 | let mut choppy: f32 = SEA_CHOPPY; 117 | let mut uv: Vec2 = p.xz(); 118 | uv.x *= 0.75; 119 | 120 | let mut d: f32; 121 | let mut h: f32 = 0.0; 122 | 123 | for _ in 0..ITER_GEOMETRY { 124 | d = sea_octave((uv + Vec2::splat(self.sea_time())) * freq, choppy); 125 | d += sea_octave((uv - Vec2::splat(self.sea_time())) * freq, choppy); 126 | h += d * amp; 127 | let octave_m = OCTAVE_M; 128 | uv = octave_m.transpose() * uv; 129 | freq *= 1.9; 130 | amp *= 0.22; 131 | choppy = mix(choppy, 1.0, 0.2); 132 | } 133 | p.y - h 134 | } 135 | 136 | fn map_detailed(&self, p: Vec3) -> f32 { 137 | let mut freq: f32 = SEA_FREQ; 138 | let mut amp: f32 = SEA_HEIGHT; 139 | let mut choppy: f32 = SEA_CHOPPY; 140 | let mut uv: Vec2 = p.xz(); 141 | uv.x *= 0.75; 142 | let mut d: f32; 143 | let mut h: f32 = 0.0; 144 | for _ in 0..ITER_FRAGMENT { 145 | d = sea_octave((uv + Vec2::splat(self.sea_time())) * freq, choppy); 146 | d += sea_octave((uv - Vec2::splat(self.sea_time())) * freq, choppy); 147 | h += d * amp; 148 | let octave_m = OCTAVE_M; 149 | uv = octave_m.transpose() * uv; 150 | freq *= 1.9; 151 | amp *= 0.22; 152 | choppy = mix(choppy, 1.0, 0.2); 153 | } 154 | p.y - h 155 | } 156 | } 157 | 158 | fn get_sea_color(p: Vec3, n: Vec3, l: Vec3, eye: Vec3, dist: Vec3) -> Vec3 { 159 | let mut fresnel: f32 = (1.0 - n.dot(-eye)).clamp(0.0, 1.0); 160 | fresnel = fresnel.powf(3.0) * 0.5; 161 | 162 | let reflected: Vec3 = get_sky_color(eye.reflect(n)); 163 | let refracted: Vec3 = SEA_BASE + diffuse(n, l, 80.0) * SEA_WATER_COLOR * 0.12; 164 | 165 | let mut color: Vec3 = mix(refracted, reflected, fresnel); 166 | let atten: f32 = (1.0 - dist.dot(dist) * 0.001).max(0.0); 167 | color += SEA_WATER_COLOR * (p.y - SEA_HEIGHT) * 0.18 * atten; 168 | 169 | color += Vec3::splat(specular(n, l, eye, 60.0)); 170 | color 171 | } 172 | 173 | impl Inputs { 174 | // tracing 175 | fn get_normal(&self, p: Vec3, eps: f32) -> Vec3 { 176 | let mut n: Vec3 = Vec3::ZERO; 177 | n.y = self.map_detailed(p); 178 | n.x = self.map_detailed(vec3(p.x + eps, p.y, p.z)) - n.y; 179 | n.z = self.map_detailed(vec3(p.x, p.y, p.z + eps)) - n.y; 180 | n.y = eps; 181 | n.normalize() 182 | } 183 | 184 | fn height_map_tracing(&self, ori: Vec3, dir: Vec3, p: &mut Vec3) -> f32 { 185 | let mut tm: f32 = 0.0; 186 | let mut tx: f32 = 1000.0; 187 | let mut hx: f32 = self.map(ori + dir * tx); 188 | if hx > 0.0 { 189 | return tx; 190 | } 191 | let mut hm: f32 = self.map(ori + dir * tm); 192 | let mut tmid: f32 = 0.0; 193 | for _ in 0..NUM_STEPS { 194 | tmid = mix(tm, tx, hm / (hm - hx)); 195 | *p = ori + dir * tmid; 196 | let hmid: f32 = self.map(*p); 197 | if hmid < 0.0 { 198 | tx = tmid; 199 | hx = hmid; 200 | } else { 201 | tm = tmid; 202 | hm = hmid; 203 | } 204 | } 205 | tmid 206 | } 207 | 208 | fn get_pixel(&self, coord: Vec2, time: f32) -> Vec3 { 209 | let mut uv: Vec2 = coord / self.resolution.xy(); 210 | uv = uv * 2.0 - Vec2::ONE; 211 | uv.x *= self.resolution.x / self.resolution.y; 212 | // ray 213 | let ang: Vec3 = vec3((time * 3.0).sin() * 0.1, time.sin() * 0.2 + 0.3, time); 214 | let ori: Vec3 = vec3(0.0, 3.5, time * 5.0); 215 | let mut dir: Vec3 = uv.extend(-2.0).normalize(); 216 | dir.z += uv.length() * 0.14; 217 | dir = from_euler(ang).transpose() * dir.normalize(); 218 | // tracing 219 | let mut p: Vec3 = Vec3::ZERO; 220 | self.height_map_tracing(ori, dir, &mut p); 221 | let dist: Vec3 = p - ori; 222 | let n: Vec3 = self.get_normal(p, dist.dot(dist) * self.epsilon_nrm()); 223 | let light: Vec3 = vec3(0.0, 1.0, 0.8).normalize(); 224 | // color 225 | mix( 226 | get_sky_color(dir), 227 | get_sea_color(p, n, light, dir, dist), 228 | smoothstep(0.0, -0.02, dir.y).powf(0.2), 229 | ) 230 | } 231 | 232 | // main 233 | pub fn main_image(&self, frag_color: &mut Vec4, frag_coord: Vec2) { 234 | let time: f32 = self.time * 0.3 + self.mouse.x * 0.01; 235 | let mut color: Vec3; 236 | if AA { 237 | color = Vec3::ZERO; 238 | for i in -1..=1 { 239 | for j in -1..=1 { 240 | let uv: Vec2 = frag_coord + vec2(i as f32, j as f32) / 3.0; 241 | color += self.get_pixel(uv, time); 242 | } 243 | } 244 | color /= 9.0; 245 | } else { 246 | color = self.get_pixel(frag_coord, time); 247 | } 248 | // post 249 | *frag_color = color.powf(0.65).extend(1.0); 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /shaders/src/soft_shadow_variation.rs: -------------------------------------------------------------------------------- 1 | //! Ported to Rust from 2 | //! 3 | //! Original comment: 4 | //! ```glsl 5 | //! // 6 | //! // Testing Sebastian Aaltonen's soft shadow improvement 7 | //! // 8 | //! // The technique is based on estimating a better closest point in ray 9 | //! // at each step by triangulating from the previous march step. 10 | //! // 11 | //! // More info about the technique at slide 39 of this presentation: 12 | //! // https://www.dropbox.com/s/s9tzmyj0wqkymmz/Claybook_Simulation_Raytracing_GDC18.pptx?dl=0 13 | //! // 14 | //! // Traditional technique: http://iquilezles.org/www/articles/rmshadows/rmshadows.htm 15 | //! // 16 | //! // Go to lines 54 to compare both. 17 | //! ``` 18 | 19 | use shared::*; 20 | use spirv_std::glam::{vec2, vec3, Mat3, Vec2, Vec2Swizzles, Vec3, Vec3Swizzles, Vec4}; 21 | 22 | // Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but 23 | // we tie #[no_std] above to the same condition, so it's fine. 24 | #[cfg(target_arch = "spirv")] 25 | use spirv_std::num_traits::Float; 26 | 27 | pub struct Inputs { 28 | pub resolution: Vec3, 29 | pub time: f32, 30 | } 31 | 32 | // make this 1 is your machine is too slow 33 | const AA: usize = 2; 34 | 35 | //------------------------------------------------------------------ 36 | 37 | fn sd_plane(p: Vec3) -> f32 { 38 | p.y 39 | } 40 | 41 | fn sd_box(p: Vec3, b: Vec3) -> f32 { 42 | let d: Vec3 = p.abs() - b; 43 | d.x.max(d.y.max(d.z)).min(0.0) + d.max(Vec3::ZERO).length() 44 | } 45 | 46 | //------------------------------------------------------------------ 47 | 48 | fn map(pos: Vec3) -> f32 { 49 | let qos: Vec3 = vec3((pos.x + 0.5).fract_gl() - 0.5, pos.y, pos.z); 50 | return sd_plane(pos - vec3(0.0, 0.00, 0.0)) 51 | .min(sd_box(qos - vec3(0.0, 0.25, 0.0), vec3(0.2, 0.5, 0.2))); 52 | } 53 | 54 | //------------------------------------------------------------------ 55 | 56 | fn calc_softshadow(ro: Vec3, rd: Vec3, mint: f32, tmax: f32, technique: i32) -> f32 { 57 | let mut res: f32 = 1.0; 58 | let mut t: f32 = mint; 59 | let mut ph: f32 = 1e10; // big, such that y = 0 on the first iteration 60 | for _ in 0..32 { 61 | let h: f32 = map(ro + rd * t); 62 | 63 | // traditional technique 64 | if technique == 0 { 65 | res = res.min(10.0 * h / t); 66 | } 67 | // improved technique 68 | else { 69 | // use this if you are getting artifact on the first iteration, or unroll the 70 | // first iteration out of the loop 71 | //float y = (i==0) ? 0.0 : h*h/(2.0*ph); 72 | 73 | let y: f32 = h * h / (2.0 * ph); 74 | let d: f32 = (h * h - y * y).sqrt(); 75 | res = res.min(10.0 * d / (t - y).max(0.0)); 76 | ph = h; 77 | } 78 | t += h; 79 | 80 | if res < 0.0001 || t > tmax { 81 | break; 82 | } 83 | } 84 | res.clamp(0.0, 1.0) 85 | } 86 | 87 | fn calc_normal(pos: Vec3) -> Vec3 { 88 | let e: Vec2 = vec2(1.0, -1.0) * 0.5773 * 0.0005; 89 | (e.xyy() * map(pos + e.xyy()) 90 | + e.yyx() * map(pos + e.yyx()) 91 | + e.yxy() * map(pos + e.yxy()) 92 | + e.xxx() * map(pos + e.xxx())) 93 | .normalize() 94 | } 95 | 96 | fn cast_ray(ro: Vec3, rd: Vec3) -> f32 { 97 | let mut tmin: f32 = 1.0; 98 | let mut tmax: f32 = 20.0; 99 | 100 | if true { 101 | // bounding volume 102 | let tp1: f32 = (0.0 - ro.y) / rd.y; 103 | if tp1 > 0.0 { 104 | tmax = tmax.min(tp1); 105 | } 106 | let tp2: f32 = (1.0 - ro.y) / rd.y; 107 | if tp2 > 0.0 { 108 | if ro.y > 1.0 { 109 | tmin = tmin.max(tp2); 110 | } else { 111 | tmax = tmax.min(tp2); 112 | } 113 | } 114 | } 115 | let mut t: f32 = tmin; 116 | for _ in 0..64 { 117 | let precis: f32 = 0.0005 * t; 118 | let res: f32 = map(ro + rd * t); 119 | if res < precis || t > tmax { 120 | break; 121 | } 122 | t += res; 123 | } 124 | 125 | if t > tmax { 126 | t = -1.0; 127 | } 128 | t 129 | } 130 | 131 | fn calc_ao(pos: Vec3, nor: Vec3) -> f32 { 132 | let mut occ: f32 = 0.0; 133 | let mut sca: f32 = 1.0; 134 | for i in 0..5 { 135 | let h: f32 = 0.001 + 0.15 * i as f32 / 4.0; 136 | let d: f32 = map(pos + h * nor); 137 | occ += (h - d) * sca; 138 | sca *= 0.95; 139 | } 140 | (1.0 - 1.5 * occ).clamp(0.0, 1.0) 141 | } 142 | 143 | fn render(ro: Vec3, rd: Vec3, technique: i32) -> Vec3 { 144 | let mut col: Vec3 = Vec3::ZERO; 145 | let t: f32 = cast_ray(ro, rd); 146 | 147 | if t > -0.5 { 148 | let pos: Vec3 = ro + t * rd; 149 | let nor: Vec3 = calc_normal(pos); 150 | 151 | // material 152 | let mate: Vec3 = Vec3::splat(0.3); 153 | // key light 154 | let lig: Vec3 = vec3(-0.1, 0.3, 0.6).normalize(); 155 | let hal: Vec3 = (lig - rd).normalize(); 156 | let dif: f32 = 157 | nor.dot(lig).clamp(0.0, 1.0) * calc_softshadow(pos, lig, 0.01, 3.0, technique); 158 | 159 | let spe: f32 = nor.dot(hal).clamp(0.0, 1.0).powf(16.0) 160 | * dif 161 | * (0.04 + 0.96 * (1.0 + hal.dot(rd)).clamp(0.0, 1.0).powf(5.0)); 162 | 163 | col = mate * 4.0 * dif * vec3(1.00, 0.70, 0.5); 164 | col += 12.0 * spe * vec3(1.00, 0.70, 0.5); 165 | 166 | // ambient light 167 | let occ: f32 = calc_ao(pos, nor); 168 | let amb: f32 = (0.5 + 0.5 * nor.y).clamp(0.0, 1.0); 169 | col += mate * amb * occ * vec3(0.0, 0.08, 0.1); 170 | 171 | // fog 172 | col *= (-0.0005 * t * t * t).exp(); 173 | } 174 | 175 | col 176 | } 177 | 178 | fn set_camera(ro: Vec3, ta: Vec3, cr: f32) -> Mat3 { 179 | let cw: Vec3 = (ta - ro).normalize(); 180 | let cp: Vec3 = vec3(cr.sin(), cr.cos(), 0.0); 181 | let cu: Vec3 = cw.cross(cp).normalize(); 182 | let cv: Vec3 = cu.cross(cw).normalize(); 183 | Mat3::from_cols(cu, cv, cw) 184 | } 185 | 186 | impl Inputs { 187 | pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { 188 | // camera 189 | let an: f32 = 12.0 - (0.1 * self.time).sin(); 190 | let ro: Vec3 = vec3(3.0 * (0.1 * an).cos(), 1.0, -3.0 * (0.1 * an).sin()); 191 | let ta: Vec3 = vec3(0.0, -0.4, 0.0); 192 | // camera-to-world transformation 193 | let ca: Mat3 = set_camera(ro, ta, 0.0); 194 | 195 | let technique: i32 = if (self.time / 2.0).fract_gl() > 0.5 { 196 | 1 197 | } else { 198 | 0 199 | }; 200 | 201 | let mut tot: Vec3 = Vec3::ZERO; 202 | 203 | for m in 0..AA { 204 | for n in 0..AA { 205 | // pixel coordinates 206 | let o: Vec2 = vec2(m as f32, n as f32) / AA as f32 - Vec2::splat(0.5); 207 | let p: Vec2 = (-self.resolution.xy() + 2.0 * (frag_coord + o)) / self.resolution.y; 208 | 209 | // ray direction 210 | let rd: Vec3 = ca * p.extend(2.0).normalize(); 211 | 212 | // render 213 | let mut col: Vec3 = render(ro, rd, technique); 214 | 215 | // gamma 216 | col = col.powf(0.4545); 217 | 218 | tot += col; 219 | } 220 | } 221 | tot /= (AA * AA) as f32; 222 | 223 | *frag_color = tot.extend(1.0); 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /shaders/src/tileable_water_caustic.rs: -------------------------------------------------------------------------------- 1 | //! Ported to Rust from 2 | //! 3 | //! Original comment: 4 | //! ```glsl 5 | //! // Found this on GLSL sandbox. I really liked it, changed a few things and made it tileable. 6 | //! // :) 7 | //! // by David Hoskins. 8 | //! 9 | //! 10 | //! // Water turbulence effect by joltz0r 2013-07-04, improved 2013-07-07 11 | //! ``` 12 | 13 | use shared::*; 14 | use spirv_std::glam::{vec2, vec3, Vec2, Vec3, Vec3Swizzles, Vec4}; 15 | 16 | // Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but 17 | // we tie #[no_std] above to the same condition, so it's fine. 18 | #[cfg(target_arch = "spirv")] 19 | use spirv_std::num_traits::Float; 20 | 21 | pub struct Inputs { 22 | pub resolution: Vec3, 23 | pub time: f32, 24 | } 25 | 26 | // Redefine below to see the tiling... 27 | const SHOW_TILING: bool = false; 28 | 29 | use core::f32::consts::TAU; 30 | const MAX_ITER: usize = 5; 31 | 32 | impl Inputs { 33 | pub fn main_image(&self, frag_color: &mut Vec4, frag_coord: Vec2) { 34 | let time: f32 = self.time * 0.5 + 23.0; 35 | // uv should be the 0-1 uv of texture... 36 | let mut uv: Vec2 = frag_coord / self.resolution.xy(); 37 | 38 | let p: Vec2 = if SHOW_TILING { 39 | (uv * TAU * 2.0).rem_euclid(Vec2::splat(TAU)) - Vec2::splat(250.0) 40 | } else { 41 | (uv * TAU).rem_euclid(Vec2::splat(TAU)) - Vec2::splat(250.0) 42 | }; 43 | let mut i: Vec2 = p; 44 | let mut c: f32 = 1.0; 45 | let inten: f32 = 0.005; 46 | 47 | for n in 0..MAX_ITER { 48 | let t: f32 = time * (1.1 - (3.5 / (n + 1) as f32)); 49 | i = p + vec2( 50 | (t - i.x).cos() + (t + i.y).sin(), 51 | (t - i.y).sin() + (t + i.x).cos(), 52 | ); 53 | c += 1.0 54 | / vec2( 55 | p.x / ((i.x + t).sin() / inten), 56 | p.y / ((i.y + t).cos() / inten), 57 | ) 58 | .length(); 59 | } 60 | c /= MAX_ITER as f32; 61 | c = 1.17 - c.powf(1.4); 62 | let mut colour: Vec3 = Vec3::splat(c.abs().powf(8.0)); 63 | colour = (colour + vec3(0.0, 0.35, 0.5)).clamp(Vec3::ZERO, Vec3::ONE); 64 | 65 | if SHOW_TILING { 66 | let pixel: Vec2 = 2.0 / self.resolution.xy(); 67 | uv *= 2.0; 68 | 69 | let f: f32 = (self.time * 0.5).rem_euclid(2.0).floor(); // Flash value. 70 | let first: Vec2 = pixel.step(uv) * f; // Rule out first screen pixels and flash. 71 | uv = uv.fract_gl().step(pixel); // Add one line of pixels per tile. 72 | colour = mix( 73 | colour, 74 | vec3(1.0, 1.0, 0.0), 75 | (uv.x + uv.y) * first.x * first.y, 76 | ); // Yellow line 77 | } 78 | 79 | *frag_color = colour.extend(1.0); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /shaders/src/tokyo.rs: -------------------------------------------------------------------------------- 1 | //! Ported to Rust from 2 | //! 3 | //! Original comment: 4 | //! ```glsl 5 | //! // Created by Reinder Nijhoff 2014 6 | //! // Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 7 | //! // @reindernijhoff 8 | //! // 9 | //! // https://www.shadertoy.com/view/Xtf3zn 10 | //! // 11 | //! // Tokyo by night in the rain. The car model is made by Eiffie 12 | //! // (Shiny Toy': https://www.shadertoy.com/view/ldsGWB). 13 | //! // I have never been in Tokyo btw. 14 | //! ``` 15 | 16 | use shared::*; 17 | use spirv_std::glam::{ 18 | mat2, vec2, vec3, vec4, Mat2, Vec2, Vec2Swizzles, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles, 19 | }; 20 | 21 | // Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but 22 | // we tie #[no_std] above to the same condition, so it's fine. 23 | #[cfg(target_arch = "spirv")] 24 | use spirv_std::num_traits::Float; 25 | 26 | #[derive(Clone, Copy)] 27 | pub struct Inputs { 28 | pub resolution: Vec3, 29 | pub time: f32, 30 | } 31 | 32 | pub struct State { 33 | inputs: Inputs, 34 | 35 | d_l: f32, // minimal distance to light 36 | 37 | int1: Vec3, 38 | int2: Vec3, 39 | nor1: Vec3, 40 | lint1: Vec4, 41 | lint2: Vec4, 42 | } 43 | 44 | impl State { 45 | pub fn new(inputs: Inputs) -> State { 46 | State { 47 | inputs, 48 | 49 | d_l: 0.0, 50 | 51 | int1: Vec3::ZERO, 52 | int2: Vec3::ZERO, 53 | nor1: Vec3::ZERO, 54 | lint1: Vec4::ZERO, 55 | lint2: Vec4::ZERO, 56 | } 57 | } 58 | } 59 | 60 | const BUMPMAP: bool = false; 61 | const MARCHSTEPS: i32 = 128; 62 | const MARCHSTEPSREFLECTION: i32 = 48; 63 | const LIGHTINTENSITY: f32 = 5.0; 64 | 65 | //---------------------------------------------------------------------- 66 | 67 | //const backgroundColor: Vec3 = const_vec3!(0.2,0.4,0.6) * 0.09; 68 | const BACKGROUND_COLOR: Vec3 = vec3(0.2 * 0.09, 0.4 * 0.09, 0.6 * 0.09); 69 | impl State { 70 | fn time(&self) -> f32 { 71 | self.inputs.time + 90.0 72 | } 73 | } 74 | 75 | //---------------------------------------------------------------------- 76 | // noises 77 | 78 | fn hash(n: f32) -> f32 { 79 | (n.sin() * 687.3123).fract_gl() 80 | } 81 | 82 | fn noise(x: Vec2) -> f32 { 83 | let p: Vec2 = x.floor(); 84 | let mut f: Vec2 = x.fract_gl(); 85 | f = f * f * (Vec2::splat(3.0) - 2.0 * f); 86 | let n: f32 = p.x + p.y * 157.0; 87 | mix( 88 | mix(hash(n + 0.0), hash(n + 1.0), f.x), 89 | mix(hash(n + 157.0), hash(n + 158.0), f.x), 90 | f.y, 91 | ) 92 | } 93 | 94 | const M2: Mat2 = mat2(vec2(0.80, -0.60), vec2(0.60, 0.80)); 95 | 96 | fn fbm(mut p: Vec2) -> f32 { 97 | let mut f: f32 = 0.0; 98 | f += 0.5000 * noise(p); 99 | p = M2 * p * 2.02; 100 | f += 0.2500 * noise(p); 101 | p = M2 * p * 2.03; 102 | f += 0.1250 * noise(p); 103 | // p = M2 * p * 2.01; 104 | // f += 0.0625*noise( p ); 105 | 106 | f / 0.9375 107 | } 108 | 109 | //---------------------------------------------------------------------- 110 | // distance primitives 111 | 112 | fn ud_round_box(p: Vec3, b: Vec3, r: f32) -> f32 { 113 | (p.abs() - b).max(Vec3::ZERO).length() - r 114 | } 115 | 116 | fn sd_box(p: Vec3, b: Vec3) -> f32 { 117 | let d: Vec3 = p.abs() - b; 118 | d.x.max(d.y.max(d.z)).min(0.0) + d.max(Vec3::ZERO).length() 119 | } 120 | 121 | fn sd_sphere(p: Vec3, s: f32) -> f32 { 122 | p.length() - s 123 | } 124 | 125 | fn sd_cylinder(p: Vec3, h: Vec2) -> f32 { 126 | let d: Vec2 = vec2(p.xz().length(), p.y).abs() - h; 127 | d.x.max(d.y).min(0.0) + d.max(Vec2::ZERO).length() 128 | } 129 | 130 | //---------------------------------------------------------------------- 131 | // distance operators 132 | 133 | fn op_u(d2: f32, d1: f32) -> f32 { 134 | d1.min(d2) 135 | } 136 | fn op_s(d2: f32, d1: f32) -> f32 { 137 | (-d1).max(d2) 138 | } 139 | fn smin(a: f32, b: f32, k: f32) -> f32 { 140 | //from iq 141 | -((-k * a).exp() + (-k * b).exp()).ln() / k 142 | } 143 | 144 | //---------------------------------------------------------------------- 145 | // Map functions 146 | 147 | // car model is made by Eiffie 148 | // shader 'Shiny Toy': https://www.shadertoy.com/view/ldsGWB 149 | 150 | fn map_car(p0: Vec3) -> f32 { 151 | let mut p: Vec3 = p0 + vec3(0.0, 1.24, 0.0); 152 | let mut r: f32 = p.yz().length(); 153 | let mut d: f32 = vec3(p.x.abs() - 0.35, r - 1.92, -p.y + 1.4) 154 | .max(Vec3::ZERO) 155 | .length() 156 | - 0.05; 157 | d = d.max(p.z - 1.0); 158 | p = p0 + vec3(0.0, -0.22, 0.39); 159 | p = (p.xz().abs() - vec2(0.5300, 0.9600)).extend(p.y).xzy(); 160 | p.x = p.x.abs(); 161 | r = p.yz().length(); 162 | d = smin( 163 | d, 164 | vec3(p.x - 0.08, r - 0.25, -p.y - 0.08) 165 | .max(Vec3::ZERO) 166 | .length() 167 | - 0.04, 168 | 8.0, 169 | ); 170 | d = d.max(-(p.x - 0.165).max(r - 0.24)); 171 | let d2: f32 = vec2((p.x - 0.13).max(0.0), r - 0.2).length() - 0.02; 172 | d = d.min(d2); 173 | 174 | d 175 | } 176 | 177 | impl State { 178 | fn map(&mut self, p: Vec3) -> f32 { 179 | let mut pd: Vec3 = p; 180 | let mut d: f32; 181 | 182 | pd.x = pd.x.abs(); 183 | pd.z *= -p.x.sign_gl(); 184 | 185 | let ch: f32 = hash(((pd.z + 18.0 * self.time()) / 40.0).floor()); 186 | let lh: f32 = hash((pd.z / 13.0).floor()); 187 | 188 | let pdm: Vec3 = vec3(pd.x, pd.y, pd.z.rem_euclid(10.0) - 5.0); 189 | self.d_l = sd_sphere(vec3(pdm.x - 8.1, pdm.y - 4.5, pdm.z), 0.1); 190 | 191 | self.d_l = op_u( 192 | self.d_l, 193 | sd_box( 194 | vec3(pdm.x - 12.0, pdm.y - 9.5 - lh, pd.z.rem_euclid(91.0) - 45.5), 195 | vec3(0.2, 4.5, 0.2), 196 | ), 197 | ); 198 | self.d_l = op_u( 199 | self.d_l, 200 | sd_box( 201 | vec3( 202 | pdm.x - 12.0, 203 | pdm.y - 11.5 + lh, 204 | pd.z.rem_euclid(31.0) - 15.5, 205 | ), 206 | vec3(0.22, 5.5, 0.2), 207 | ), 208 | ); 209 | self.d_l = op_u( 210 | self.d_l, 211 | sd_box( 212 | vec3(pdm.x - 12.0, pdm.y - 8.5 - lh, pd.z.rem_euclid(41.0) - 20.5), 213 | vec3(0.24, 3.5, 0.2), 214 | ), 215 | ); 216 | 217 | if lh > 0.5 { 218 | self.d_l = op_u( 219 | self.d_l, 220 | sd_box( 221 | vec3(pdm.x - 12.5, pdm.y - 2.75 - lh, pd.z.rem_euclid(13.) - 6.5), 222 | vec3(0.1, 0.25, 3.2), 223 | ), 224 | ); 225 | } 226 | 227 | let pm: Vec3 = vec3( 228 | (pd.x + (pd.z * 4.0).floor() * 0.25).rem_euclid(0.5) - 0.25, 229 | pd.y, 230 | pd.z.rem_euclid(0.25) - 0.125, 231 | ); 232 | d = ud_round_box(pm, vec3(0.245, 0.1, 0.12), 0.005); 233 | 234 | d = op_s(d, -(p.x + 8.)); 235 | d = op_u(d, pd.y); 236 | 237 | let mut pdc: Vec3 = vec3( 238 | pd.x, 239 | pd.y, 240 | (pd.z + 18.0 * self.time()).rem_euclid(40.0) - 20.0, 241 | ); 242 | 243 | // car 244 | if ch > 0.75 { 245 | pdc.x += (ch - 0.75) * 4.0; 246 | self.d_l = op_u( 247 | self.d_l, 248 | sd_sphere(vec3((pdc.x - 5.0).abs() - 1.05, pdc.y - 0.55, pdc.z), 0.025), 249 | ); 250 | self.d_l = op_u( 251 | self.d_l, 252 | sd_sphere( 253 | vec3((pdc.x - 5.0).abs() - 1.2, pdc.y - 0.65, pdc.z + 6.05), 254 | 0.025, 255 | ), 256 | ); 257 | 258 | d = op_u(d, map_car((pdc - vec3(5.0, -0.025, -2.3)) * 0.45)); 259 | } 260 | 261 | d = op_u(d, 13. - pd.x); 262 | d = op_u( 263 | d, 264 | sd_cylinder(vec3(pdm.x - 8.5, pdm.y, pdm.z), vec2(0.075, 4.5)), 265 | ); 266 | d = op_u(d, self.d_l); 267 | 268 | d 269 | } 270 | 271 | //---------------------------------------------------------------------- 272 | 273 | fn calc_normal_simple(&mut self, pos: Vec3) -> Vec3 { 274 | let e: Vec2 = vec2(1.0, -1.0) * 0.005; 275 | 276 | let n: Vec3 = (e.xyy() * self.map(pos + e.xyy()) 277 | + e.yyx() * self.map(pos + e.yyx()) 278 | + e.yxy() * self.map(pos + e.yxy()) 279 | + e.xxx() * self.map(pos + e.xxx())) 280 | .normalize(); 281 | n 282 | } 283 | 284 | fn calc_normal(&mut self, pos: Vec3) -> Vec3 { 285 | let mut n: Vec3 = self.calc_normal_simple(pos); 286 | if pos.y > 0.12 { 287 | return n; 288 | } 289 | 290 | if BUMPMAP { 291 | let mut oc: Vec2 = 292 | (vec2(pos.x + (pos.z * 4.0).floor() * 0.25, pos.z) * vec2(2.0, 4.0)).floor(); 293 | 294 | if pos.x.abs() < 8. { 295 | oc = pos.xz(); 296 | } 297 | 298 | let p: Vec3 = pos * 250.; 299 | let mut xn: Vec3 = 0.05 * vec3(noise(p.xz()) - 0.5, 0., noise(p.zx()) - 0.5); 300 | xn += 0.1 * vec3(fbm(oc) - 0.5, 0., fbm(oc.yx()) - 0.5); 301 | 302 | n = (xn + n).normalize(); 303 | } 304 | 305 | n 306 | } 307 | 308 | fn intersect(&mut self, mut ro: Vec3, mut rd: Vec3) -> f32 { 309 | let precis: f32 = 0.001; 310 | let mut h: f32; 311 | let mut t: f32 = 0.0; 312 | self.int1 = Vec3::splat(-500.0); 313 | self.int2 = Vec3::splat(-500.0); 314 | self.lint1 = Vec4::splat(-500.0); 315 | self.lint2 = Vec4::splat(-500.0); 316 | let mut mld: f32 = 100.0; 317 | 318 | for _ in 0..MARCHSTEPS { 319 | h = self.map(ro + rd * t); 320 | if self.d_l < mld { 321 | mld = self.d_l; 322 | self.lint1 = (ro + rd * t).extend(self.lint1.w); 323 | self.lint1.w = self.d_l.abs(); 324 | } 325 | if h < precis { 326 | self.int1 = ro + rd * t; 327 | break; 328 | } 329 | t += h.max(precis * 2.); 330 | } 331 | 332 | if self.int1.z < -400.0 || t > 300.0 { 333 | // check intersection with plane y = -0.1; 334 | let d: f32 = -(ro.y + 0.1) / rd.y; 335 | if d > 0.0 { 336 | self.int1 = ro + rd * d; 337 | } else { 338 | return -1.0; 339 | } 340 | } 341 | 342 | ro = ro + rd * t; 343 | self.nor1 = self.calc_normal(ro); 344 | ro += 0.01 * self.nor1; 345 | rd = rd.reflect(self.nor1); 346 | t = 0.0; 347 | mld = 100.; 348 | 349 | for _ in 0..MARCHSTEPSREFLECTION { 350 | h = self.map(ro + rd * t); 351 | if self.d_l < mld { 352 | mld = self.d_l; 353 | self.lint2 = (ro + rd * t).extend(self.lint2.w); 354 | self.lint2.w = self.d_l.abs(); 355 | } 356 | if h < precis { 357 | self.int2 = ro + rd * t; 358 | return 1.0; 359 | } 360 | t += h.max(precis * 2.0); 361 | } 362 | 363 | 0.0 364 | } 365 | 366 | //---------------------------------------------------------------------- 367 | // shade 368 | 369 | fn shade(&self, ro: Vec3, pos: Vec3, nor: Vec3) -> Vec3 { 370 | let mut col: Vec3 = Vec3::splat(0.5); 371 | 372 | if pos.x.abs() > 15.0 || pos.x.abs() < 8.0 { 373 | col = Vec3::splat(0.02); 374 | } 375 | if pos.y < 0.01 { 376 | if self.int1.x.abs() < 0.1 { 377 | col = Vec3::splat(0.9); 378 | } 379 | if (self.int1.x.abs() - 7.4).abs() < 0.1 { 380 | col = Vec3::splat(0.9); 381 | } 382 | } 383 | 384 | let sh: f32 = nor.dot(vec3(-0.3, 0.3, -0.5).normalize()).clamp(0.0, 1.0); 385 | col *= sh * BACKGROUND_COLOR; 386 | 387 | if pos.x.abs() > 12.9 && pos.y > 9. { 388 | // windows 389 | let ha: f32 = hash(133.1234 * (pos.y / 3.0).floor() + ((pos.z) / 3.0).floor()); 390 | if ha > 0.95 { 391 | col = ((ha - 0.95) * 10.) * vec3(1., 0.7, 0.4); 392 | } 393 | } 394 | 395 | col = mix( 396 | BACKGROUND_COLOR, 397 | col, 398 | ((0.1 * pos.y).max(0.25) - 0.065 * pos.distance(ro)) 399 | .min(0.0) 400 | .exp(), 401 | ); 402 | 403 | col 404 | } 405 | 406 | fn get_light_color(&self, pos: Vec3) -> Vec3 { 407 | let mut lcol: Vec3 = vec3(1.0, 0.7, 0.5); 408 | 409 | let mut pd: Vec3 = pos; 410 | pd.x = pd.x.abs(); 411 | pd.z *= -pos.x.sign_gl(); 412 | 413 | let ch: f32 = hash(((pd.z + 18. * self.time()) / 40.0).floor()); 414 | let mut pdc: Vec3 = vec3( 415 | pd.x, 416 | pd.y, 417 | (pd.z + 18.0 * self.time()).rem_euclid(40.0) - 20.0, 418 | ); 419 | 420 | if ch > 0.75 { 421 | // car 422 | pdc.x += (ch - 0.75) * 4.; 423 | if sd_sphere(vec3((pdc.x - 5.0).abs() - 1.05, pdc.y - 0.55, pdc.z), 0.25) < 2. { 424 | lcol = vec3(1., 0.05, 0.01); 425 | } 426 | } 427 | if pd.y > 2.0 && pd.x.abs() > 10.0 && pd.y < 5.0 { 428 | let fl: f32 = (pd.z / 13.0).floor(); 429 | lcol = 0.4 * lcol + 0.5 * vec3(hash(0.1562 + fl), hash(0.423134 + fl), 0.0); 430 | } 431 | if pd.x.abs() > 10. && pd.y > 5.0 { 432 | let fl: f32 = (pd.z / 2.0).floor(); 433 | lcol = 0.5 * lcol 434 | + 0.5 * vec3(hash(0.1562 + fl), hash(0.923134 + fl), hash(0.423134 + fl)); 435 | } 436 | 437 | lcol 438 | } 439 | } 440 | 441 | fn random_start(co: Vec2) -> f32 { 442 | 0.8 + 0.2 * hash(co.dot(vec2(123.42, 117.853)) * 412.453) 443 | } 444 | 445 | impl State { 446 | //---------------------------------------------------------------------- 447 | // main 448 | 449 | pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { 450 | let mut q: Vec2 = frag_coord / self.inputs.resolution.xy(); 451 | let mut p: Vec2 = -Vec2::ONE + 2.0 * q; 452 | p.x *= self.inputs.resolution.x / self.inputs.resolution.y; 453 | 454 | if q.y < 0.12 || q.y >= 0.88 { 455 | *frag_color = vec4(0.0, 0.0, 0.0, 1.0); 456 | return; 457 | } else { 458 | // camera 459 | let z: f32 = self.time(); 460 | let x: f32 = -10.9 + 1. * (self.time() * 0.2).sin(); 461 | let ro: Vec3 = vec3(x, 1.3 + 0.3 * (self.time() * 0.26).cos(), z - 1.); 462 | let ta: Vec3 = vec3( 463 | -8.0, 464 | 1.3 + 0.4 * (self.time() * 0.26).cos(), 465 | z + 4. + (self.time() * 0.04).cos(), 466 | ); 467 | 468 | let ww: Vec3 = (ta - ro).normalize(); 469 | let uu: Vec3 = ww.cross(vec3(0.0, 1.0, 0.0)).normalize(); 470 | let vv: Vec3 = uu.cross(ww).normalize(); 471 | let rd: Vec3 = (-p.x * uu + p.y * vv + 2.2 * ww).normalize(); 472 | 473 | let mut col: Vec3 = BACKGROUND_COLOR; 474 | 475 | // raymarch 476 | let ints: f32 = self.intersect(ro + random_start(p) * rd, rd); 477 | if ints > -0.5 { 478 | // calculate reflectance 479 | let mut r: f32 = 0.09; 480 | if self.int1.y > 0.129 { 481 | r = 0.025 482 | * hash( 483 | 133.1234 * (self.int1.y / 3.0).floor() + (self.int1.z / 3.0).floor(), 484 | ); 485 | } 486 | if self.int1.x.abs() < 8.0 { 487 | if self.int1.y < 0.01 { 488 | // road 489 | r = 0.007 * fbm(self.int1.xz()); 490 | } else { 491 | // car 492 | r = 0.02; 493 | } 494 | } 495 | if self.int1.x.abs() < 0.1 { 496 | r *= 4.0; 497 | } 498 | if (self.int1.x.abs() - 7.4).abs() < 0.1 { 499 | r *= 4.0; 500 | } 501 | 502 | r *= 2.0; 503 | 504 | col = self.shade(ro, self.int1, self.nor1); 505 | 506 | if ints > 0.5 { 507 | let tmp = self.calc_normal_simple(self.int2); 508 | col += r * self.shade(self.int1, self.int2, tmp); 509 | } 510 | if self.lint2.w > 0. { 511 | col += (r * LIGHTINTENSITY * (-self.lint2.w * 7.0).exp()) 512 | * self.get_light_color(self.lint2.xyz()); 513 | } 514 | } 515 | 516 | // Rain (by Dave Hoskins) 517 | let st: Vec2 = 256. 518 | * (p * vec2(0.5, 0.01) + vec2(self.time() * 0.13 - q.y * 0.6, self.time() * 0.13)); 519 | let mut f: f32 = noise(st) * noise(st * 0.773) * 1.55; 520 | f = 0.25 + (f.abs().powf(13.0) * 13.0).clamp(0.0, q.y * 0.14); 521 | 522 | if self.lint1.w > 0.0 { 523 | col += (f * LIGHTINTENSITY * (-self.lint1.w * 7.0).exp()) 524 | * self.get_light_color(self.lint1.xyz()); 525 | } 526 | 527 | col += 0.25 * f * (Vec3::splat(0.2) + BACKGROUND_COLOR); 528 | 529 | // post processing 530 | col = col.clamp(Vec3::ZERO, Vec3::ONE).powf(0.4545); 531 | col *= 1.2 * vec3(1.0, 0.99, 0.95); 532 | col = (1.06 * col - Vec3::splat(0.03)).clamp(Vec3::ZERO, Vec3::ONE); 533 | q.y = (q.y - 0.12) * (1. / 0.76); 534 | col *= Vec3::splat(0.5) 535 | + Vec3::splat(0.5) * (16.0 * q.x * q.y * (1.0 - q.x) * (1.0 - q.y)).powf(0.1); 536 | 537 | *frag_color = col.extend(1.0); 538 | } 539 | } 540 | } 541 | -------------------------------------------------------------------------------- /shaders/src/two_tweets.rs: -------------------------------------------------------------------------------- 1 | //! Ported to Rust from 2 | //! 3 | //! Original comment: 4 | //! ```glsl 5 | //! // Created by inigo quilez - iq/2013 6 | //! // License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. 7 | //! ``` 8 | 9 | use spirv_std::glam::{vec3, Vec2, Vec3, Vec4}; 10 | 11 | // Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but 12 | // we tie #[no_std] above to the same condition, so it's fine. 13 | #[cfg(target_arch = "spirv")] 14 | use spirv_std::num_traits::Float; 15 | 16 | pub struct Inputs { 17 | pub resolution: Vec3, 18 | pub time: f32, 19 | } 20 | 21 | impl Inputs { 22 | fn f(&self, mut p: Vec3) -> f32 { 23 | p.z += self.time; 24 | (Vec3::splat(0.05 * (9. * p.y * p.x).cos()) + vec3(p.x.cos(), p.y.cos(), p.z.cos()) 25 | - Vec3::splat(0.1 * (9. * (p.z + 0.3 * p.x - p.y)).cos())) 26 | .length() 27 | - 1. 28 | } 29 | 30 | pub fn main_image(&self, c: &mut Vec4, p: Vec2) { 31 | let d: Vec3 = Vec3::splat(0.5) - p.extend(1.0) / self.resolution.x; 32 | let mut o: Vec3 = d; 33 | for _ in 0..128 { 34 | o += self.f(o) * d; 35 | } 36 | *c = ((self.f(o - d) * vec3(0.0, 1.0, 2.0) 37 | + Vec3::splat(self.f(o - Vec3::splat(0.6))) * vec3(2.0, 1.0, 0.0)) 38 | .abs() 39 | * (1. - 0.1 * o.z)) 40 | .extend(1.0); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /shaders/src/voxel_pac_man.rs: -------------------------------------------------------------------------------- 1 | //! Ported to Rust from 2 | //! 3 | //! Original comment: 4 | //! ```glsl 5 | //! /////////////////////////////////////////////////////////////////////////////// 6 | //! // // 7 | //! // GGGG IIIII AAA N N TTTTT PPPP AAA CCCC M M AAA N N // 8 | //! // G I A A NN N T P P A A C MM MM A A NN N // 9 | //! // G GG I AAAAA N N N T PPPP AAAAA C --- M M M AAAAA N N N // 10 | //! // G G I A A N NN T P A A C M M A A N NN // 11 | //! // GGGG IIIII A A N N T P A A CCCC M M A A N N // 12 | //! // // 13 | //! /////////////////////////////////////////////////////////////////////////////// 14 | //! */ 15 | //! ``` 16 | 17 | use shared::*; 18 | use spirv_std::glam::{vec2, vec3, Mat3, Vec2, Vec2Swizzles, Vec3, Vec3Swizzles, Vec4}; 19 | 20 | // Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but 21 | // we tie #[no_std] above to the same condition, so it's fine. 22 | #[cfg(target_arch = "spirv")] 23 | use spirv_std::num_traits::Float; 24 | 25 | pub struct Inputs { 26 | pub resolution: Vec3, 27 | pub time: f32, 28 | pub mouse: Vec4, 29 | } 30 | 31 | pub struct State { 32 | inputs: Inputs, 33 | 34 | // Global variable to handle the glow effect 35 | glow_counter: f32, 36 | } 37 | 38 | impl State { 39 | pub fn new(inputs: Inputs) -> State { 40 | State { 41 | inputs, 42 | 43 | glow_counter: 0.0, 44 | } 45 | } 46 | } 47 | 48 | // Parameters 49 | const VOXEL_RESOLUTION: f32 = 1.5; 50 | const VOXEL_LIGHTING: bool = true; 51 | const SHADOW: bool = true; 52 | const GROUND: bool = true; 53 | const GHOST: bool = true; 54 | const MOUSE: bool = true; 55 | const HSV2RGB_FAST: bool = true; 56 | const HSV2RGB_SAFE: bool = false; 57 | 58 | const CAMERA_FOCAL_LENGTH: f32 = 8.0; 59 | const DELTA: f32 = 0.01; 60 | const RAY_LENGTH_MAX: f32 = 500.0; 61 | const RAY_STEP_MAX: u32 = 100; 62 | const AMBIENT: f32 = 0.2; 63 | const SPECULAR_POWER: f32 = 2.0; 64 | const SPECULAR_INTENSITY: f32 = 0.3; 65 | const SHADOW_LENGTH: f32 = 150.0; 66 | const SHADOW_POWER: f32 = 3.0; 67 | const FADE_POWER: f32 = 1.0; 68 | const BACKGROUND: f32 = 0.7; 69 | const GLOW: f32 = 0.4; 70 | const GAMMA: f32 = 0.8; 71 | 72 | // Math constants 73 | const PI: f32 = 3.14159265359; 74 | const SQRT3: f32 = 1.73205080757; 75 | 76 | // PRNG (from https://www.shadertoy.com/view/4djSRW) 77 | fn rand(mut seed: Vec3) -> f32 { 78 | seed = (seed * vec3(5.3983, 5.4427, 6.9371)).fract_gl(); 79 | seed += Vec3::splat(seed.yzx().dot(seed + vec3(21.5351, 14.3137, 15.3219))); 80 | (seed.x * seed.y * seed.z * 95.4337).fract_gl() 81 | } 82 | 83 | impl State { 84 | // Distance to the voxel 85 | fn dist_voxel(&mut self, p: Vec3) -> f32 { 86 | // Update the glow counter 87 | self.glow_counter += 1.0; 88 | 89 | // Rounded box 90 | let voxel_radius: f32 = 0.25; 91 | (p.abs() + Vec3::splat(-0.5 + voxel_radius)) 92 | .max(Vec3::ZERO) 93 | .length() 94 | - voxel_radius 95 | } 96 | 97 | // Distance to the scene and color of the closest point 98 | fn dist_scene(&mut self, mut p: Vec3, p2: &mut Vec3) -> Vec2 { 99 | // Update the glow counter 100 | self.glow_counter += 1.0; 101 | 102 | // Scaling 103 | p *= VOXEL_RESOLUTION; 104 | 105 | // Velocity, period of the waves, spacing of the gums 106 | let mut v: f32 = VOXEL_RESOLUTION * (self.inputs.time * 100.0 / VOXEL_RESOLUTION).floor(); 107 | let k1: f32 = 0.05; 108 | let k2: f32 = 60.0; 109 | 110 | // Giant Pac-Man 111 | let mut body: f32 = p.length(); 112 | body = (body - 32.0).max(27.0 - body); 113 | let mut eyes: f32 = 6.0 - vec3(p.x.abs() - 12.5, p.y - 19.5, p.z - 20.0).length(); 114 | let mut mouth_angle: f32 = PI * (0.07 + 0.07 * (2.0 * v * PI / k2).cos()); 115 | let mouth_top: f32 = p.dot(vec3(0.0, -mouth_angle.cos(), mouth_angle.sin())) - 2.0; 116 | mouth_angle *= 2.5; 117 | let mouth_bottom: f32 = p.dot(vec3(0.0, mouth_angle.cos(), mouth_angle.sin())); 118 | let pac_man: f32 = body.max(eyes).max(mouth_top.min(mouth_bottom)); 119 | let mut d: Vec2 = vec2(pac_man, 0.13); 120 | *p2 = p; 121 | 122 | // Gums 123 | let mut q: Vec3 = p.xy().extend((p.z + v).rem_euclid(k2) - k2 * 0.5); 124 | let gum: f32 = (q.length() - 6.0).max(-p.z); 125 | if gum < d.x { 126 | d = vec2(gum, 0.35); 127 | *p2 = q; 128 | } 129 | 130 | // Ground 131 | if GROUND { 132 | q = p.xy().extend(p.z + v); 133 | let ground: f32 = (q.y + 50.0 + 14.0 * (q.x * k1).cos() * (q.z * k1).cos()) * 0.7; 134 | if ground < d.x { 135 | d = vec2(ground, 0.55); 136 | *p2 = q; 137 | } 138 | } 139 | 140 | // Ghost 141 | if GHOST { 142 | v = VOXEL_RESOLUTION 143 | * ((130.0 + 60.0 * (self.inputs.time * 3.0).cos()) / VOXEL_RESOLUTION).floor(); 144 | q = p.xy().extend(p.z + v); 145 | body = vec3(q.x, (q.y - 4.0).max(0.0), q.z).length(); 146 | body = (body - 28.0).max(22.0 - body); 147 | eyes = 8.0 - vec3(q.x.abs() - 12.0, q.y - 10.0, q.z - 22.0).length(); 148 | let bottom: f32 = (q.y + 28.0 + 4.0 * (p.x * 0.4).cos() * (p.z * 0.4).cos()) * 0.7; 149 | let ghost: f32 = body.max(eyes).max(-bottom); 150 | if ghost < d.x { 151 | d = vec2(ghost, 0.76); 152 | *p2 = q; 153 | } 154 | } 155 | 156 | // Scaling 157 | d.x /= VOXEL_RESOLUTION; 158 | d 159 | } 160 | 161 | // Distance to the (voxelized?) scene 162 | fn dist(&mut self, p: &mut Vec3, ray: Vec3, voxelized: f32, ray_length_max: f32) -> Vec4 { 163 | let mut p2: Vec3 = *p; 164 | let mut d: Vec2 = vec2(1.0 / 0.0, 0.0); 165 | let mut ray_length: f32 = 0.0; 166 | let mut ray_length_in_voxel: f32 = 0.0; 167 | let mut ray_length_check_voxel: f32 = 0.0; 168 | let ray_sign: Vec3 = ray.sign_gl(); 169 | let ray_delta_voxel: Vec3 = ray_sign / ray; 170 | for _ in 0..RAY_STEP_MAX { 171 | if ray_length < ray_length_in_voxel { 172 | d.x = self.dist_voxel((*p + Vec3::splat(0.5)).fract_gl() - Vec3::splat(0.5)); 173 | if d.x < DELTA { 174 | break; 175 | } 176 | } else if ray_length < ray_length_check_voxel { 177 | let mut ray_delta: Vec3 = (Vec3::splat(0.5) 178 | - ray_sign * ((*p + Vec3::splat(0.5)).fract_gl() - Vec3::splat(0.5))) 179 | * ray_delta_voxel; 180 | let d_next: f32 = ray_delta.x.min(ray_delta.y.min(ray_delta.z)); 181 | d = self.dist_scene((*p + Vec3::splat(0.5)).floor(), &mut p2); 182 | if d.x < 0.0 { 183 | ray_delta = ray_delta_voxel - ray_delta; 184 | d.x = (ray_length_in_voxel - ray_length) 185 | .max(DELTA - ray_delta.x.min(ray_delta.y.min(ray_delta.z))); 186 | ray_length_in_voxel = ray_length + d_next; 187 | } else { 188 | d.x = DELTA + d_next; 189 | } 190 | } else { 191 | d = self.dist_scene(*p, &mut p2); 192 | if voxelized > 0.5 { 193 | if d.x < SQRT3 * 0.5 { 194 | ray_length_check_voxel = ray_length + d.x.abs() + SQRT3 * 0.5; 195 | d.x = (ray_length_in_voxel - ray_length + DELTA).max(d.x - SQRT3 * 0.5); 196 | } 197 | } else if d.x < DELTA { 198 | break; 199 | } 200 | } 201 | ray_length += d.x; 202 | if ray_length > ray_length_max { 203 | break; 204 | } 205 | *p += d.x * ray; 206 | } 207 | d.extend(ray_length).extend(rand(p2)) 208 | } 209 | 210 | // Normal at a given point 211 | fn normal(&mut self, mut p: Vec3, voxelized: f32) -> Vec3 { 212 | let h: Vec2 = vec2(DELTA, -DELTA); 213 | let mut n: Vec3 = Vec3::ZERO; 214 | if voxelized > 0.5 { 215 | p = (p + Vec3::splat(0.5)).fract_gl() - Vec3::splat(0.5); 216 | n = h.xxx() * self.dist_voxel(p + h.xxx()) 217 | + h.xyy() * self.dist_voxel(p + h.xyy()) 218 | + h.yxy() * self.dist_voxel(p + h.yxy()) 219 | + h.yyx() * self.dist_voxel(p + h.yyx()); 220 | } else { 221 | n = h.xxx() * self.dist_scene(p + h.xxx(), &mut n).x 222 | + h.xyy() * self.dist_scene(p + h.xyy(), &mut n).x 223 | + h.yxy() * self.dist_scene(p + h.yxy(), &mut n).x 224 | + h.yyx() * self.dist_scene(p + h.yyx(), &mut n).x; 225 | } 226 | n.normalize() 227 | } 228 | } 229 | 230 | // HSV to RGB 231 | fn hsv2rgb(mut hsv: Vec3) -> Vec3 { 232 | if HSV2RGB_SAFE { 233 | hsv = hsv.yz().clamp(Vec2::ZERO, Vec2::ONE).extend(hsv.x).zxy(); 234 | } 235 | if HSV2RGB_FAST { 236 | hsv.z 237 | * (Vec3::ONE 238 | + 0.5 239 | * hsv.y 240 | * ((2.0 * PI * (Vec3::splat(hsv.x) + vec3(0.0, 2.0 / 3.0, 1.0 / 3.0))).cos() 241 | - Vec3::ONE)) 242 | } else { 243 | hsv.z 244 | * (Vec3::ONE 245 | + Vec3::splat(hsv.y) 246 | * (((Vec3::splat(hsv.x) + vec3(0.0, 2.0 / 3.0, 1.0 / 3.0)).fract_gl() * 6.0 247 | - Vec3::splat(3.0)) 248 | .abs() 249 | - Vec3::splat(2.0)) 250 | .clamp(-Vec3::ONE, Vec3::ZERO)) 251 | } 252 | } 253 | 254 | impl State { 255 | // Main function 256 | pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { 257 | // Get the fragment 258 | let frag: Vec2 = 259 | (2.0 * frag_coord - self.inputs.resolution.xy()) / self.inputs.resolution.y; 260 | 261 | // Define the rendering mode 262 | let mut mode_timing: f32 = self.inputs.time * 0.234; 263 | let mut mode_angle: f32 = PI * (self.inputs.time * 0.2).cos(); 264 | mode_angle = (frag - vec2((self.inputs.time * 2.0).cos(), 0.0)) 265 | .dot(vec2(mode_angle.cos(), mode_angle.sin())); 266 | let mut mode_voxel: f32 = 0.5.step((mode_timing / (4.0 * PI)).fract_gl()); 267 | mode_timing = mode_timing.cos(); 268 | let mode_3d: f32 = smoothstep(0.8, 0.5, mode_timing); 269 | let mode_switch: f32 = smoothstep(0.995, 1.0, mode_timing) 270 | + smoothstep(0.02, 0.0, mode_angle.abs()) * mode_voxel; 271 | mode_voxel = 1.0 + (0.0.step(mode_angle) - 1.0) * mode_voxel; 272 | 273 | // Define the ray corresponding to this fragment 274 | let mut ray: Vec3 = frag 275 | .extend(mix(8.0, CAMERA_FOCAL_LENGTH, mode_3d)) 276 | .normalize(); 277 | 278 | // Compute the orientation of the camera 279 | let mut yaw_angle: f32 = PI * (1.2 + 0.2 * (self.inputs.time * 0.5).cos()); 280 | let mut pitch_angle: f32 = PI * (0.1 * (self.inputs.time * 0.3).cos() - 0.05); 281 | if MOUSE { 282 | yaw_angle += 4.0 * PI * self.inputs.mouse.x / self.inputs.resolution.x; 283 | pitch_angle += PI * 0.3 * (1.0 - self.inputs.mouse.y / self.inputs.resolution.y); 284 | } 285 | yaw_angle = mix(PI * 1.5, yaw_angle, mode_3d); 286 | pitch_angle *= mode_3d; 287 | 288 | let cos_yaw: f32 = yaw_angle.cos(); 289 | let sin_yaw: f32 = yaw_angle.sin(); 290 | let cos_pitch: f32 = pitch_angle.cos(); 291 | let sin_pitch: f32 = pitch_angle.sin(); 292 | 293 | let mut camera_orientation: Mat3 = Mat3::ZERO; 294 | camera_orientation.x_axis = vec3(cos_yaw, 0.0, -sin_yaw); 295 | camera_orientation.y_axis = vec3(sin_yaw * sin_pitch, cos_pitch, cos_yaw * sin_pitch); 296 | camera_orientation.z_axis = vec3(sin_yaw * cos_pitch, -sin_pitch, cos_yaw * cos_pitch); 297 | 298 | ray = camera_orientation * ray; 299 | 300 | // Compute the origin of the ray 301 | let camera_dist: f32 = mix( 302 | 300.0, 303 | 195.0 + 150.0 * (self.inputs.time * 0.8).cos(), 304 | mode_3d, 305 | ); 306 | let mut origin: Vec3 = (vec3(0.0, 0.0, 40.0 * (self.inputs.time * 0.2).sin()) 307 | - camera_orientation.z_axis * camera_dist) 308 | / VOXEL_RESOLUTION; 309 | 310 | // Compute the distance to the scene 311 | self.glow_counter = 0.0; 312 | let d: Vec4 = self.dist( 313 | &mut origin, 314 | ray, 315 | mode_voxel, 316 | RAY_LENGTH_MAX / VOXEL_RESOLUTION, 317 | ); 318 | 319 | // Set the background color 320 | let mut final_color: Vec3 = hsv2rgb(vec3( 321 | 0.2 * ray.y + 0.4 * mode_voxel - 0.37, 322 | 1.0, 323 | mode_3d * BACKGROUND, 324 | )); 325 | let glow_color: Vec3 = GLOW * vec3(1.0, 0.3, 0.0) * self.glow_counter / RAY_STEP_MAX as f32; 326 | if d.x < DELTA { 327 | // Set the object color 328 | let mut color: Vec3 = hsv2rgb(vec3( 329 | d.y + 0.1 * d.w * mode_voxel, 330 | 0.5 + 0.5 * mode_voxel, 331 | 1.0, 332 | )); 333 | 334 | // Lighting 335 | let l: Vec3 = mix( 336 | vec3(1.0, 0.0, 0.0), 337 | vec3(1.25 + (self.inputs.time * 0.2).cos(), 1.0, 1.0), 338 | mode_3d, 339 | ) 340 | .normalize(); 341 | if VOXEL_LIGHTING { 342 | if mode_voxel > 0.5 { 343 | let n: Vec3 = self.normal((origin + Vec3::splat(0.5)).floor(), 0.0); 344 | let diffuse: f32 = n.dot(l).max(0.0); 345 | let specular: f32 = 346 | ray.reflect(n).dot(l).max(0.0).powf(SPECULAR_POWER) * SPECULAR_INTENSITY; 347 | color = (AMBIENT + diffuse) * color + Vec3::splat(specular); 348 | } 349 | } 350 | let n: Vec3 = self.normal(origin, mode_voxel); 351 | let mut diffuse: f32 = n.dot(l); 352 | let mut specular: f32; 353 | if diffuse < 0.0 { 354 | diffuse = 0.0; 355 | specular = 0.0; 356 | } else { 357 | specular = ray.reflect(n).dot(l).max(0.0).powf(SPECULAR_POWER) * SPECULAR_INTENSITY; 358 | if SHADOW { 359 | origin += n * DELTA * 2.0; 360 | let mut shadow: Vec4 = 361 | self.dist(&mut origin, l, mode_voxel, SHADOW_LENGTH / VOXEL_RESOLUTION); 362 | if shadow.x < DELTA { 363 | shadow.z = (shadow.z * VOXEL_RESOLUTION / SHADOW_LENGTH) 364 | .min(1.0) 365 | .powf(SHADOW_POWER); 366 | diffuse *= shadow.z; 367 | specular *= shadow.z; 368 | } 369 | } 370 | } 371 | color = (AMBIENT + diffuse) * color + Vec3::splat(specular); 372 | 373 | // Fading 374 | let fade: f32 = (1.0 - d.z * VOXEL_RESOLUTION / RAY_LENGTH_MAX) 375 | .max(0.0) 376 | .powf(FADE_POWER); 377 | final_color = mix(final_color, color, fade); 378 | } 379 | 380 | // Set the fragment color 381 | final_color = mix(final_color.powf(GAMMA) + glow_color, Vec3::ONE, mode_switch); 382 | *frag_color = final_color.extend(1.0); 383 | } 384 | } 385 | -------------------------------------------------------------------------------- /shadertoys.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rust-GPU/rust-gpu-shadertoys/56774fd0aa9018ad9880bab95e0f09b5d9ece3c4/shadertoys.jpg -------------------------------------------------------------------------------- /shared/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shared" 3 | version = "0.0.0" 4 | authors = [] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | spirv-std.workspace = true 9 | bytemuck = { version = "1.20.0", features = ["derive"] } 10 | 11 | [lints] 12 | workspace = true 13 | -------------------------------------------------------------------------------- /shared/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Ported to Rust from https://github.com/Tw1ddle/Sky-Shader/blob/master/src/shaders/glsl/sky.fragment 2 | 3 | #![cfg_attr(target_arch = "spirv", no_std)] 4 | #![feature(asm_experimental_arch)] 5 | 6 | use bytemuck::{Pod, Zeroable}; 7 | use core::f32::consts::PI; 8 | use core::ops::{Add, Mul, Sub}; 9 | use spirv_std::glam::{vec2, vec3, vec4, Vec2, Vec3, Vec4}; 10 | 11 | // Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but 12 | // we tie #[no_std] above to the same condition, so it's fine. 13 | #[cfg(target_arch = "spirv")] 14 | use spirv_std::num_traits::Float; 15 | 16 | #[repr(C)] 17 | #[derive(Copy, Clone, Pod, Zeroable)] 18 | #[allow(unused_attributes)] 19 | pub struct ShaderConstants { 20 | pub width: u32, 21 | pub height: u32, 22 | pub time: f32, 23 | pub cursor_x: f32, 24 | pub cursor_y: f32, 25 | pub drag_start_x: f32, 26 | pub drag_start_y: f32, 27 | pub drag_end_x: f32, 28 | pub drag_end_y: f32, 29 | pub mouse_left_pressed: u32, 30 | pub mouse_left_clicked: u32, 31 | } 32 | 33 | pub fn saturate(x: f32) -> f32 { 34 | x.max(0.0).min(1.0) 35 | } 36 | 37 | /// Based on: https://seblagarde.wordpress.com/2014/12/01/inverse-trigonometric-functions-gpu-optimization-for-amd-gcn-architecture/ 38 | pub fn acos_approx(v: f32) -> f32 { 39 | let x = v.abs(); 40 | let mut res = -0.155972 * x + 1.56467; // p(x) 41 | res *= (1.0f32 - x).sqrt(); 42 | 43 | if v >= 0.0 { 44 | res 45 | } else { 46 | PI - res 47 | } 48 | } 49 | 50 | pub fn smoothstep(edge0: f32, edge1: f32, x: f32) -> f32 { 51 | // Scale, bias and saturate x to 0..1 range 52 | let x = saturate((x - edge0) / (edge1 - edge0)); 53 | // Evaluate polynomial 54 | x * x * (3.0 - 2.0 * x) 55 | } 56 | 57 | pub fn mix + Add + Sub, A: Copy>( 58 | x: X, 59 | y: X, 60 | a: A, 61 | ) -> X { 62 | x - x * a + y * a 63 | } 64 | 65 | pub trait Clamp { 66 | fn clamp(self, min: Self, max: Self) -> Self; 67 | } 68 | 69 | impl Clamp for f32 { 70 | fn clamp(self, min: Self, max: Self) -> Self { 71 | self.max(min).min(max) 72 | } 73 | } 74 | 75 | pub trait FloatExt { 76 | fn fract_gl(self) -> Self; 77 | fn rem_euclid(self, rhs: Self) -> Self; 78 | fn sign_gl(self) -> Self; 79 | fn step(self, x: Self) -> Self; 80 | } 81 | 82 | impl FloatExt for f32 { 83 | fn fract_gl(self) -> f32 { 84 | self - self.floor() 85 | } 86 | 87 | fn rem_euclid(self, rhs: f32) -> f32 { 88 | let r = self % rhs; 89 | if r < 0.0 { 90 | r + rhs.abs() 91 | } else { 92 | r 93 | } 94 | } 95 | 96 | fn sign_gl(self) -> f32 { 97 | if self < 0.0 { 98 | -1.0 99 | } else if self == 0.0 { 100 | 0.0 101 | } else { 102 | 1.0 103 | } 104 | } 105 | 106 | fn step(self, x: f32) -> f32 { 107 | if x < self { 108 | 0.0 109 | } else { 110 | 1.0 111 | } 112 | } 113 | } 114 | 115 | pub trait VecExt { 116 | fn sin(self) -> Self; 117 | fn cos(self) -> Self; 118 | fn powf_vec(self, p: Self) -> Self; 119 | fn sqrt(self) -> Self; 120 | fn ln(self) -> Self; 121 | fn step(self, other: Self) -> Self; 122 | fn sign_gl(self) -> Self; 123 | } 124 | 125 | impl VecExt for Vec2 { 126 | fn sin(self) -> Vec2 { 127 | vec2(self.x.sin(), self.y.sin()) 128 | } 129 | 130 | fn cos(self) -> Vec2 { 131 | vec2(self.x.cos(), self.y.cos()) 132 | } 133 | 134 | fn powf_vec(self, p: Vec2) -> Vec2 { 135 | vec2(self.x.powf(p.x), self.y.powf(p.y)) 136 | } 137 | 138 | fn sqrt(self) -> Vec2 { 139 | vec2(self.x.sqrt(), self.y.sqrt()) 140 | } 141 | 142 | fn ln(self) -> Vec2 { 143 | vec2(self.x.ln(), self.y.ln()) 144 | } 145 | 146 | fn step(self, other: Vec2) -> Vec2 { 147 | vec2(self.x.step(other.x), self.y.step(other.y)) 148 | } 149 | 150 | fn sign_gl(self) -> Vec2 { 151 | vec2(self.x.sign_gl(), self.y.sign_gl()) 152 | } 153 | } 154 | 155 | impl VecExt for Vec3 { 156 | fn sin(self) -> Vec3 { 157 | vec3(self.x.sin(), self.y.sin(), self.z.sin()) 158 | } 159 | 160 | fn cos(self) -> Vec3 { 161 | vec3(self.x.cos(), self.y.cos(), self.z.cos()) 162 | } 163 | 164 | fn powf_vec(self, p: Vec3) -> Vec3 { 165 | vec3(self.x.powf(p.x), self.y.powf(p.y), self.z.powf(p.z)) 166 | } 167 | 168 | fn sqrt(self) -> Vec3 { 169 | vec3(self.x.sqrt(), self.y.sqrt(), self.z.sqrt()) 170 | } 171 | 172 | fn ln(self) -> Vec3 { 173 | vec3(self.x.ln(), self.y.ln(), self.z.ln()) 174 | } 175 | 176 | fn step(self, other: Vec3) -> Vec3 { 177 | vec3( 178 | self.x.step(other.x), 179 | self.y.step(other.y), 180 | self.z.step(other.z), 181 | ) 182 | } 183 | 184 | fn sign_gl(self) -> Vec3 { 185 | vec3(self.x.sign_gl(), self.y.sign_gl(), self.z.sign_gl()) 186 | } 187 | } 188 | 189 | impl VecExt for Vec4 { 190 | fn sin(self) -> Vec4 { 191 | vec4(self.x.sin(), self.y.sin(), self.z.sin(), self.w.sin()) 192 | } 193 | 194 | fn cos(self) -> Vec4 { 195 | vec4(self.x.cos(), self.y.cos(), self.z.cos(), self.w.cos()) 196 | } 197 | 198 | fn powf_vec(self, p: Vec4) -> Vec4 { 199 | vec4( 200 | self.x.powf(p.x), 201 | self.y.powf(p.y), 202 | self.z.powf(p.z), 203 | self.w.powf(p.w), 204 | ) 205 | } 206 | 207 | fn sqrt(self) -> Vec4 { 208 | vec4(self.x.sqrt(), self.y.sqrt(), self.z.sqrt(), self.w.sqrt()) 209 | } 210 | 211 | fn ln(self) -> Vec4 { 212 | vec4(self.x.ln(), self.y.ln(), self.z.ln(), self.w.ln()) 213 | } 214 | 215 | fn step(self, other: Vec4) -> Vec4 { 216 | vec4( 217 | self.x.step(other.x), 218 | self.y.step(other.y), 219 | self.z.step(other.z), 220 | self.w.step(other.w), 221 | ) 222 | } 223 | 224 | fn sign_gl(self) -> Vec4 { 225 | vec4( 226 | self.x.sign_gl(), 227 | self.y.sign_gl(), 228 | self.z.sign_gl(), 229 | self.w.sign_gl(), 230 | ) 231 | } 232 | } 233 | 234 | pub fn discard() { 235 | unsafe { spirv_std::arch::demote_to_helper_invocation() } 236 | } 237 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use futures::executor::block_on; 2 | use ouroboros::self_referencing; 3 | use std::error::Error; 4 | use std::time::Instant; 5 | use wgpu::{self, InstanceDescriptor}; 6 | use wgpu::{include_spirv, include_spirv_raw}; 7 | use winit::application::ApplicationHandler; 8 | use winit::dpi::LogicalSize; 9 | use winit::event::{ElementState, MouseButton, WindowEvent}; 10 | use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; 11 | use winit::keyboard::NamedKey; 12 | use winit::window::{Window, WindowAttributes, WindowId}; 13 | 14 | #[self_referencing] 15 | struct WindowSurface { 16 | window: Box, 17 | #[borrows(window)] 18 | #[covariant] 19 | surface: wgpu::Surface<'this>, 20 | } 21 | 22 | struct ShaderToyApp { 23 | device: Option, 24 | queue: Option, 25 | window_surface: Option, 26 | config: Option, 27 | render_pipeline: Option, 28 | shader_module: Option, 29 | close_requested: bool, 30 | start: Instant, 31 | // Mouse state. 32 | cursor_x: f32, 33 | cursor_y: f32, 34 | drag_start_x: f32, 35 | drag_start_y: f32, 36 | drag_end_x: f32, 37 | drag_end_y: f32, 38 | mouse_left_pressed: bool, 39 | mouse_left_clicked: bool, 40 | } 41 | 42 | impl Default for ShaderToyApp { 43 | fn default() -> Self { 44 | Self { 45 | device: None, 46 | queue: None, 47 | window_surface: None, 48 | config: None, 49 | render_pipeline: None, 50 | shader_module: None, 51 | close_requested: false, 52 | start: Instant::now(), 53 | cursor_x: 0.0, 54 | cursor_y: 0.0, 55 | drag_start_x: 0.0, 56 | drag_start_y: 0.0, 57 | drag_end_x: 0.0, 58 | drag_end_y: 0.0, 59 | mouse_left_pressed: false, 60 | mouse_left_clicked: false, 61 | } 62 | } 63 | } 64 | 65 | impl ShaderToyApp { 66 | async fn init(&mut self, event_loop: &dyn ActiveEventLoop) -> Result<(), Box> { 67 | let window_attributes = WindowAttributes::default() 68 | .with_title("Rust GPU - wgpu") 69 | .with_surface_size(LogicalSize::new(1280.0, 720.0)); 70 | let window_box = event_loop.create_window(window_attributes)?; 71 | let mut instance_flags = wgpu::InstanceFlags::default(); 72 | // Turn off validation as the shaders are trusted. 73 | instance_flags.remove(wgpu::InstanceFlags::VALIDATION); 74 | // Disable debugging info to speed things up. 75 | instance_flags.remove(wgpu::InstanceFlags::DEBUG); 76 | let instance = wgpu::Instance::new(&InstanceDescriptor { 77 | flags: instance_flags, 78 | ..Default::default() 79 | }); 80 | 81 | let window_surface = WindowSurfaceBuilder { 82 | window: window_box, 83 | surface_builder: |window| { 84 | instance 85 | .create_surface(window) 86 | .expect("Failed to create surface") 87 | }, 88 | } 89 | .build(); 90 | 91 | let window_size = window_surface.borrow_window().surface_size(); 92 | let surface = window_surface.borrow_surface(); 93 | 94 | let adapter = instance 95 | .request_adapter(&wgpu::RequestAdapterOptions { 96 | power_preference: wgpu::PowerPreference::HighPerformance, 97 | compatible_surface: Some(surface), 98 | force_fallback_adapter: false, 99 | }) 100 | .await?; 101 | let mut required_features = wgpu::Features::PUSH_CONSTANTS; 102 | if adapter 103 | .features() 104 | .contains(wgpu::Features::SPIRV_SHADER_PASSTHROUGH) 105 | { 106 | required_features |= wgpu::Features::SPIRV_SHADER_PASSTHROUGH; 107 | } 108 | let required_limits = wgpu::Limits { 109 | max_push_constant_size: 256, 110 | ..Default::default() 111 | }; 112 | let (device, queue) = adapter 113 | .request_device(&wgpu::DeviceDescriptor { 114 | label: None, 115 | required_features, 116 | required_limits, 117 | ..Default::default() 118 | }) 119 | .await?; 120 | let shader_module = if device 121 | .features() 122 | .contains(wgpu::Features::SPIRV_SHADER_PASSTHROUGH) 123 | { 124 | let x = include_spirv_raw!(env!("shadertoys_shaders.spv")); 125 | unsafe { device.create_shader_module_passthrough(x) } 126 | } else { 127 | device.create_shader_module(include_spirv!(env!("shadertoys_shaders.spv"))) 128 | }; 129 | let swapchain_format = surface.get_capabilities(&adapter).formats[0]; 130 | let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { 131 | label: None, 132 | bind_group_layouts: &[], 133 | push_constant_ranges: &[wgpu::PushConstantRange { 134 | stages: wgpu::ShaderStages::VERTEX_FRAGMENT, 135 | range: 0..std::mem::size_of::() as u32, 136 | }], 137 | }); 138 | let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { 139 | label: None, 140 | layout: Some(&pipeline_layout), 141 | vertex: wgpu::VertexState { 142 | module: &shader_module, 143 | entry_point: Some("main_vs"), 144 | buffers: &[], 145 | compilation_options: Default::default(), 146 | }, 147 | fragment: Some(wgpu::FragmentState { 148 | module: &shader_module, 149 | entry_point: Some("main_fs"), 150 | targets: &[Some(wgpu::ColorTargetState { 151 | format: swapchain_format, 152 | blend: Some(wgpu::BlendState::REPLACE), 153 | write_mask: wgpu::ColorWrites::ALL, 154 | })], 155 | compilation_options: Default::default(), 156 | }), 157 | primitive: wgpu::PrimitiveState { 158 | topology: wgpu::PrimitiveTopology::TriangleList, 159 | ..Default::default() 160 | }, 161 | depth_stencil: None, 162 | multisample: wgpu::MultisampleState::default(), 163 | multiview: None, 164 | cache: None, 165 | }); 166 | let config = wgpu::SurfaceConfiguration { 167 | usage: wgpu::TextureUsages::RENDER_ATTACHMENT, 168 | format: swapchain_format, 169 | width: window_size.width, 170 | height: window_size.height, 171 | present_mode: wgpu::PresentMode::Fifo, 172 | alpha_mode: wgpu::CompositeAlphaMode::Auto, 173 | view_formats: vec![], 174 | desired_maximum_frame_latency: Default::default(), 175 | }; 176 | surface.configure(&device, &config); 177 | 178 | self.device = Some(device); 179 | self.queue = Some(queue); 180 | self.window_surface = Some(window_surface); 181 | self.config = Some(config); 182 | self.render_pipeline = Some(render_pipeline); 183 | self.shader_module = Some(shader_module); 184 | self.start = Instant::now(); 185 | Ok(()) 186 | } 187 | 188 | fn render(&mut self) { 189 | let window_surface = match &self.window_surface { 190 | Some(ws) => ws, 191 | None => return, 192 | }; 193 | 194 | let window = window_surface.borrow_window(); 195 | let current_size = window.surface_size(); 196 | let surface = window_surface.borrow_surface(); 197 | let device = self.device.as_ref().unwrap(); 198 | let queue = self.queue.as_ref().unwrap(); 199 | let frame = match surface.get_current_texture() { 200 | Ok(frame) => frame, 201 | Err(e) => { 202 | eprintln!("Failed to acquire texture: {:?}", e); 203 | return; 204 | } 205 | }; 206 | let view = frame 207 | .texture 208 | .create_view(&wgpu::TextureViewDescriptor::default()); 209 | let mut encoder = 210 | device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); 211 | { 212 | let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { 213 | label: None, 214 | color_attachments: &[Some(wgpu::RenderPassColorAttachment { 215 | view: &view, 216 | resolve_target: None, 217 | ops: wgpu::Operations { 218 | load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), 219 | store: wgpu::StoreOp::Store, 220 | }, 221 | })], 222 | depth_stencil_attachment: None, 223 | timestamp_writes: None, 224 | occlusion_query_set: None, 225 | }); 226 | rpass.set_viewport( 227 | 0.0, 228 | 0.0, 229 | current_size.width as f32, 230 | current_size.height as f32, 231 | 0.0, 232 | 1.0, 233 | ); 234 | let push_constants = shared::ShaderConstants { 235 | width: current_size.width, 236 | height: current_size.height, 237 | time: self.start.elapsed().as_secs_f32(), 238 | cursor_x: self.cursor_x, 239 | cursor_y: self.cursor_y, 240 | drag_start_x: self.drag_start_x, 241 | drag_start_y: self.drag_start_y, 242 | drag_end_x: self.drag_end_x, 243 | drag_end_y: self.drag_end_y, 244 | mouse_left_pressed: self.mouse_left_pressed as u32, 245 | mouse_left_clicked: self.mouse_left_clicked as u32, 246 | }; 247 | self.mouse_left_clicked = false; 248 | rpass.set_pipeline(self.render_pipeline.as_ref().unwrap()); 249 | rpass.set_push_constants( 250 | wgpu::ShaderStages::VERTEX_FRAGMENT, 251 | 0, 252 | bytemuck::bytes_of(&push_constants), 253 | ); 254 | rpass.draw(0..3, 0..1); 255 | } 256 | queue.submit(Some(encoder.finish())); 257 | frame.present(); 258 | } 259 | } 260 | 261 | impl ApplicationHandler for ShaderToyApp { 262 | fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) { 263 | if let Err(e) = block_on(self.init(event_loop)) { 264 | eprintln!("Initialization error: {e}"); 265 | event_loop.exit(); 266 | } 267 | } 268 | 269 | fn window_event( 270 | &mut self, 271 | event_loop: &dyn ActiveEventLoop, 272 | _window_id: WindowId, 273 | event: WindowEvent, 274 | ) { 275 | match event { 276 | WindowEvent::CloseRequested => self.close_requested = true, 277 | WindowEvent::SurfaceResized(new_size) => { 278 | if let Some(config) = self.config.as_mut() { 279 | config.width = new_size.width; 280 | config.height = new_size.height; 281 | if let Some(ws) = &self.window_surface { 282 | let surface = ws.borrow_surface(); 283 | if let Some(device) = self.device.as_ref() { 284 | surface.configure(device, config); 285 | } 286 | } 287 | } 288 | } 289 | WindowEvent::PointerMoved { position, .. } => { 290 | self.cursor_x = position.x as f32; 291 | self.cursor_y = position.y as f32; 292 | if self.mouse_left_pressed { 293 | self.drag_end_x = self.cursor_x; 294 | self.drag_end_y = self.cursor_y; 295 | } 296 | } 297 | WindowEvent::PointerButton { state, button, .. } => { 298 | if button.mouse_button() == MouseButton::Left { 299 | self.mouse_left_pressed = state == ElementState::Pressed; 300 | if self.mouse_left_pressed { 301 | self.drag_start_x = self.cursor_x; 302 | self.drag_start_y = self.cursor_y; 303 | self.drag_end_x = self.cursor_x; 304 | self.drag_end_y = self.cursor_y; 305 | self.mouse_left_clicked = true; 306 | } 307 | } 308 | } 309 | WindowEvent::MouseWheel { delta, .. } => { 310 | if let winit::event::MouseScrollDelta::LineDelta(x, y) = delta { 311 | self.drag_end_x = x * 0.1; 312 | self.drag_end_y = y * 0.1; 313 | } 314 | } 315 | WindowEvent::KeyboardInput { event, .. } => { 316 | if event.logical_key == NamedKey::Escape && event.state == ElementState::Pressed { 317 | self.close_requested = true; 318 | } 319 | } 320 | WindowEvent::RedrawRequested => self.render(), 321 | _ => {} 322 | } 323 | if self.close_requested { 324 | event_loop.exit(); 325 | } else if let Some(ws) = &self.window_surface { 326 | ws.borrow_window().request_redraw(); 327 | } 328 | event_loop.set_control_flow(ControlFlow::Poll); 329 | } 330 | 331 | fn about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) { 332 | if self.close_requested { 333 | event_loop.exit(); 334 | } else if let Some(ws) = &self.window_surface { 335 | ws.borrow_window().request_redraw(); 336 | } 337 | event_loop.set_control_flow(ControlFlow::Poll); 338 | } 339 | } 340 | 341 | fn main() -> Result<(), Box> { 342 | env_logger::init(); 343 | let event_loop = EventLoop::new()?; 344 | let mut app = ShaderToyApp::default(); 345 | event_loop.run_app(&mut app).map_err(Into::into) 346 | } 347 | --------------------------------------------------------------------------------