diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..e7254ec --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +# From: https://docs.github.com/en/github/getting-started-with-github/configuring-git-to-handle-line-endings +# Set the default behavior for line endings. +* text=auto eol=lf + +# Declare files that will always have CRLF line endings on checkout. +*.sln text eol=crlf + +# Denote all files that are truly binary and should not be modified. +*.png binary +*.jpg binary +*.ttf binary diff --git a/.gitignore b/.gitignore index ea8c4bf..fac57f6 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +rustc-ice-*.txt diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..65980e2 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "rust-analyzer.cargo.targetDir": true, + //"rust-analyzer.check.command": "clippy", +} diff --git a/Cargo.lock b/Cargo.lock index 3f6a7b2..84f0bbf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -251,9 +251,9 @@ checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "bytemuck" -version = "1.22.0" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" +checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c" dependencies = [ "bytemuck_derive", ] @@ -1869,7 +1869,7 @@ dependencies = [ name = "shadertoys-shaders" version = "0.0.0" dependencies = [ - "shared", + "bytemuck", "spirv-std", ] @@ -1881,7 +1881,7 @@ dependencies = [ "env_logger", "futures", "ouroboros", - "shared", + "shadertoys-shaders", "spirv-builder", "wgpu", "winit", @@ -1896,14 +1896,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shared" -version = "0.0.0" -dependencies = [ - "bytemuck", - "spirv-std", -] - [[package]] name = "shlex" version = "1.3.0" diff --git a/Cargo.toml b/Cargo.toml index 6683f46..26afd70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,17 +11,14 @@ use-installed-tools = ["spirv-builder/use-installed-tools"] use-compiled-tools = ["spirv-builder/use-compiled-tools"] [dependencies] -shared = { path = "shared" } +shadertoys-shaders = { path = "shaders" } futures = { version = "0.3", default-features = false, features = [ "std", - "executor" -] } -wgpu = { version = "25.0.0", features = [ - "spirv", - "vulkan-portability" + "executor", ] } +wgpu = { version = "25.0.0", features = ["spirv", "vulkan-portability"] } winit = { git = "https://github.com/rust-windowing/winit.git", rev = "cdbdd974fbf79b82b3fb1a4bc84ed717312a3bd2" } -bytemuck = "1.20.0" +bytemuck = "1.23.0" env_logger = "0.11.6" ouroboros = "0.18.5" @@ -29,7 +26,7 @@ ouroboros = "0.18.5" spirv-builder.workspace = true [workspace] -members = ["shaders", "shared"] +members = ["shaders"] [workspace.dependencies] spirv-builder = { git = "https://github.com/Rust-GPU/rust-gpu", rev = "0b37696e9f5edde8fa0c1363a88e6c8cb8e6ff68", default-features = false } @@ -53,7 +50,74 @@ opt-level = 3 [profile.release.package."shadertoys-shaders"] opt-level = 0 +[workspace.lints.clippy] +# disabled because shader code does this often +cast_precision_loss = "allow" +cast_possible_truncation = "allow" +excessive_precision = "allow" +missing_const_for_fn = "allow" +many_single_char_names = "allow" +similar_names = "allow" +too_many_arguments = "allow" +suboptimal_flops = "allow" +too_many_lines = "allow" +cognitive_complexity = "allow" +# disabled because of rust gpu limitatoins +manual_range_contains = "allow" # Rust gpu does not like the core range checks +needless_range_loop = "allow" # Rust gpu does not like iterators very much +manual_swap = "allow" # Rust gpu does not like the core swap function +# temporarily disabled rules +inline_always = "allow" # need some hard numbers for this +unreadable_literal = "allow" # Maybe fix this? +useless_let_if_seq = "allow" # Maybe fix this? +used_underscore_items = "allow" # Maybe fix this? +no_effect_underscore_binding = "allow" # Maybe fix this? + +# standard rules for idiomatic Rust code +let_and_return = "allow" +needless_lifetimes = "allow" +option_if_let_else = "allow" +# see: https://github.com/bevyengine/bevy/pull/15375#issuecomment-2366966219 +too_long_first_doc_paragraph = "allow" +missing_panics_doc = "allow" +doc-markdown = "allow" + +nursery = { priority = -1, level = "warn" } +pedantic = { priority = -1, level = "warn" } +doc_markdown = "warn" +manual_let_else = "warn" +match_same_arms = "warn" +redundant_closure_for_method_calls = "warn" +redundant_else = "warn" +semicolon_if_nothing_returned = "warn" +type_complexity = "allow" +undocumented_unsafe_blocks = "warn" +unwrap_or_default = "warn" + +ptr_as_ptr = "warn" +ptr_cast_constness = "warn" +ref_as_ptr = "warn" + +std_instead_of_core = "warn" +std_instead_of_alloc = "warn" +alloc_instead_of_core = "warn" + [workspace.lints.rust] +nonstandard-style = "warn" +future-incompatible = "warn" +missing_docs = "allow" # TODO: warn +unused = { priority = -1, level = "warn" } +rust_2018_idioms = { priority = -1, level = "warn" } +rust-2024-compatibility = "warn" +array-into-iter = "warn" +bare-trait-objects = "warn" +ellipsis-inclusive-range-patterns = "warn" +non-fmt-panics = "warn" +explicit-outlives-requirements = "warn" +unused-extern-crates = "warn" +unsafe_code = "allow" # TODO: forbid +unsafe_op_in_unsafe_fn = "warn" +unused_qualifications = "warn" unexpected_cfgs = { level = "allow", check-cfg = [ - 'cfg(target_arch, values("spirv"))' + 'cfg(target_arch, values("spirv"))', ] } diff --git a/build.rs b/build.rs index f90eec3..2d26a4d 100644 --- a/build.rs +++ b/build.rs @@ -2,14 +2,14 @@ use spirv_builder::{MetadataPrintout, SpirvBuilder}; use std::error::Error; fn build_shader(path_to_crate: &str) -> Result<(), Box> { - let builder = SpirvBuilder::new(path_to_crate, "spirv-unknown-vulkan1.2") - .print_metadata(MetadataPrintout::Full); + let builder = SpirvBuilder::new(path_to_crate, "spirv-unknown-vulkan1.2") + .print_metadata(MetadataPrintout::Full); - let _result = builder.build()?; - Ok(()) + let _result = builder.build()?; + Ok(()) } fn main() -> Result<(), Box> { - build_shader("shaders")?; - Ok(()) + build_shader("shaders")?; + Ok(()) } diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000..8483b87 --- /dev/null +++ b/clippy.toml @@ -0,0 +1 @@ +check-private-items = true diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..4f1f020 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,13 @@ +match_block_trailing_comma = true +tab_spaces = 2 +condense_wildcard_suffixes = false +newline_style = "Unix" + +# The options below are unstable +unstable_features = true +imports_granularity = "Crate" +normalize_comments = false # Often doesn't do what you want + +# these options seem poorly implemented and cause churn, so, try to avoid them +# wrap_comments = true +# comment_width = 100 diff --git a/shaders/Cargo.toml b/shaders/Cargo.toml index 7640fd6..0db5533 100644 --- a/shaders/Cargo.toml +++ b/shaders/Cargo.toml @@ -9,7 +9,7 @@ crate-type = ["dylib"] [dependencies] spirv-std.workspace = true -shared = { path = "../shared" } +bytemuck = { version = "1.23.0", features = ["derive"] } [lints] workspace = true diff --git a/shaders/src/a_lot_of_spheres.rs b/shaders/src/a_lot_of_spheres.rs deleted file mode 100644 index 9094fd5..0000000 --- a/shaders/src/a_lot_of_spheres.rs +++ /dev/null @@ -1,310 +0,0 @@ -//! Ported to Rust from -//! -//! Original comment: -//! ```glsl -//! // A lot of spheres. Created by Reinder Nijhoff 2013 -//! // Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. -//! // @reindernijhoff -//! // -//! // https://www.shadertoy.com/view/lsX3WH -//! // -//! */ -//! ``` - -use shared::*; -use spirv_std::glam::{mat2, vec2, vec3, Mat2, Vec2, Vec3, Vec3Swizzles, Vec4}; - -// Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but -// we tie #[no_std] above to the same condition, so it's fine. -#[cfg(target_arch = "spirv")] -use spirv_std::num_traits::Float; - -pub struct Inputs { - pub resolution: Vec3, - pub time: f32, -} - -const SHADOW: bool = true; -const REFLECTION: bool = true; - -const RAYCASTSTEPS: usize = 40; - -const EXPOSURE: f32 = 0.9; -const EPSILON: f32 = 0.0001; -const MAXDISTANCE: f32 = 400.0; -const GRIDSIZE: f32 = 8.0; -const GRIDSIZESMALL: f32 = 5.0; -const MAXHEIGHT: f32 = 30.0; -const SPEED: f32 = 0.5; - -// -// math functions -// - -const _MR: Mat2 = mat2(vec2(0.84147, 0.54030), vec2(0.54030, -0.84147)); -fn hash(n: f32) -> f32 { - (n.sin() * 43758.5453).fract_gl() -} -fn _hash2(n: f32) -> Vec2 { - (vec2(n, n + 1.0).sin() * vec2(2.1459123, 3.3490423)).fract_gl() -} -fn hash2_vec(n: Vec2) -> Vec2 { - (vec2(n.x * n.y, n.x + n.y).sin() * vec2(2.1459123, 3.3490423)).fract_gl() -} -fn _hash3(n: f32) -> Vec3 { - (vec3(n, n + 1.0, n + 2.0).sin() * vec3(3.5453123, 4.1459123, 1.3490423)).fract_gl() -} -fn hash3_vec(n: Vec2) -> Vec3 { - (vec3(n.x, n.y, n.x + 2.0).sin() * vec3(3.5453123, 4.1459123, 1.3490423)).fract_gl() -} - -// -// intersection functions -// - -fn intersect_plane(ro: Vec3, rd: Vec3, height: f32, dist: &mut f32) -> bool { - if rd.y == 0.0 { - return false; - } - - let mut d: f32 = -(ro.y - height) / rd.y; - d = d.min(100000.0); - if d > 0.0 { - *dist = d; - return true; - } - false -} - -fn intersect_unit_sphere(ro: Vec3, rd: Vec3, sph: Vec3, dist: &mut f32, normal: &mut Vec3) -> bool { - let ds: Vec3 = ro - sph; - let bs: f32 = rd.dot(ds); - let cs: f32 = ds.dot(ds) - 1.0; - let mut ts: f32 = bs * bs - cs; - - if ts > 0.0 { - ts = -bs - ts.sqrt(); - if ts > 0.0 { - *normal = ((ro + ts * rd) - sph).normalize(); - *dist = ts; - return true; - } - } - false -} - -// -// Scene -// - -fn get_sphere_offset(grid: Vec2, center: &mut Vec2) { - *center = (hash2_vec(grid + vec2(43.12, 1.23)) - Vec2::splat(0.5)) * (GRIDSIZESMALL); -} - -impl Inputs { - fn get_moving_sphere_position(&self, grid: Vec2, sphere_offset: Vec2, center: &mut Vec3) { - // falling? - let s: f32 = 0.1 + hash(grid.x * 1.23114 + 5.342 + 74.324231 * grid.y); - let t: f32 = (14. * s + self.time / s * 0.3).fract_gl(); - - let y: f32 = s * MAXHEIGHT * (4.0 * t * (1. - t)).abs(); - let offset: Vec2 = grid + sphere_offset; - - *center = vec3(offset.x, y, offset.y) + 0.5 * vec3(GRIDSIZE, 2.0, GRIDSIZE); - } -} -fn get_sphere_position(grid: Vec2, sphere_offset: Vec2, center: &mut Vec3) { - let offset: Vec2 = grid + sphere_offset; - *center = vec3(offset.x, 0.0, offset.y) + 0.5 * vec3(GRIDSIZE, 2.0, GRIDSIZE); -} -fn get_sphere_color(grid: Vec2) -> Vec3 { - hash3_vec(grid + vec2(43.12 * grid.y, 12.23 * grid.x)).normalize() -} - -impl Inputs { - fn trace( - &self, - ro: Vec3, - rd: Vec3, - intersection: &mut Vec3, - normal: &mut Vec3, - dist: &mut f32, - material: &mut i32, - ) -> Vec3 { - *material = 0; // sky - *dist = MAXDISTANCE; - let mut distcheck: f32 = 0.0; - - let mut sphere_center: Vec3 = Vec3::ZERO; - let mut col: Vec3; - let mut normalcheck: Vec3 = Vec3::ZERO; - if intersect_plane(ro, rd, 0.0, &mut distcheck) && distcheck < MAXDISTANCE { - *dist = distcheck; - *material = 1; - *normal = vec3(0.0, 1.0, 0.0); - col = Vec3::ONE; - } else { - col = Vec3::ZERO; - } - - // trace grid - let mut pos: Vec3 = (ro / GRIDSIZE).floor() * GRIDSIZE; - let ri: Vec3 = 1.0 / rd; - let rs: Vec3 = rd.sign_gl() * GRIDSIZE; - let mut dis: Vec3 = (pos - ro + 0.5 * Vec3::splat(GRIDSIZE) + rs * 0.5) * ri; - let mut mm: Vec3; - - for _ in 0..RAYCASTSTEPS { - if *material > 1 || ro.xz().distance(pos.xz()) > *dist + GRIDSIZE { - break; - } - let mut offset: Vec2 = Vec2::ZERO; - get_sphere_offset(pos.xz(), &mut offset); - - self.get_moving_sphere_position(pos.xz(), -offset, &mut sphere_center); - - if intersect_unit_sphere(ro, rd, sphere_center, &mut distcheck, &mut normalcheck) - && distcheck < *dist - { - *dist = distcheck; - *normal = normalcheck; - *material = 2; - } - - get_sphere_position(pos.xz(), offset, &mut sphere_center); - if intersect_unit_sphere(ro, rd, sphere_center, &mut distcheck, &mut normalcheck) - && distcheck < *dist - { - *dist = distcheck; - *normal = normalcheck; - col = Vec3::splat(2.0); - *material = 3; - } - mm = dis.step(dis.zyx()); - dis += mm * rs * ri; - pos += mm * rs; - } - - let mut color: Vec3 = Vec3::ZERO; - if *material > 0 { - *intersection = ro + rd * *dist; - let map: Vec2 = (intersection.xz() / GRIDSIZE).floor() * GRIDSIZE; - - if *material == 1 || *material == 3 { - // lightning - let c: Vec3 = vec3(-GRIDSIZE, 0.0, GRIDSIZE); - for x in 0..3 { - for y in 0..3 { - let mapoffset: Vec2 = map + vec2([c.x, c.y, c.z][x], [c.x, c.y, c.z][y]); - let mut offset: Vec2 = Vec2::ZERO; - get_sphere_offset(mapoffset, &mut offset); - let lcolor: Vec3 = get_sphere_color(mapoffset); - let mut lpos: Vec3 = Vec3::ZERO; - self.get_moving_sphere_position(mapoffset, -offset, &mut lpos); - - let mut shadow: f32 = 1.0; - - if SHADOW && *material == 1 { - for _ in 0..3 { - for _ in 0..3 { - if shadow < 1.0 { - continue; - } - - let smapoffset: Vec2 = - map + vec2([c.x, c.y, c.z][x], [c.x, c.y, c.z][y]); - let mut soffset: Vec2 = Vec2::ZERO; - get_sphere_offset(smapoffset, &mut soffset); - let mut slpos: Vec3 = Vec3::ZERO; - let mut sn: Vec3 = Vec3::ZERO; - get_sphere_position(smapoffset, soffset, &mut slpos); - let mut sd: f32 = 0.0; - if intersect_unit_sphere( - *intersection, - (lpos - *intersection).normalize(), - slpos, - &mut sd, - &mut sn, - ) { - shadow = 0.0; - } - } - } - } - color += col - * lcolor - * (shadow - * ((lpos - *intersection).normalize().dot(*normal)).max(0.0) - * (1. - (lpos.distance(*intersection) / GRIDSIZE).clamp(0.0, 1.))); - } - } - } else { - // emitter - color = (1.5 + normal.dot(vec3(0.5, 0.5, -0.5))) * get_sphere_color(map); - } - } - color - } - - pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { - let q: Vec2 = frag_coord / self.resolution.xy(); - let mut p: Vec2 = Vec2::splat(-1.0) + 2.0 * q; - p.x *= self.resolution.x / self.resolution.y; - - // camera - let ce: Vec3 = vec3( - (0.232 * self.time).cos() * 10.0, - 6. + 3.0 * (0.3 * self.time).cos(), - GRIDSIZE * (self.time / SPEED), - ); - let ro: Vec3 = ce; - let ta: Vec3 = ro - + vec3( - -(0.232 * self.time).sin() * 10., - -2.0 + (0.23 * self.time).cos(), - 10.0, - ); - - let roll: f32 = -0.15 * (0.5 * self.time).sin(); - // camera tx - let cw: Vec3 = (ta - ro).normalize(); - let cp: Vec3 = vec3(roll.sin(), roll.cos(), 0.0); - let cu: Vec3 = (cw.cross(cp)).normalize(); - let cv: Vec3 = (cu.cross(cw)).normalize(); - let mut rd: Vec3 = (p.x * cu + p.y * cv + 1.5 * cw).normalize(); - // raytrace - let mut material: i32 = 0; - let mut normal: Vec3 = Vec3::ZERO; - let mut intersection: Vec3 = Vec3::ZERO; - let mut dist: f32 = 0.0; - - let mut col: Vec3 = self.trace( - ro, - rd, - &mut intersection, - &mut normal, - &mut dist, - &mut material, - ); - if material > 0 && REFLECTION { - let ro: Vec3 = intersection + EPSILON * normal; - rd = rd.reflect(normal); - col += 0.05 - * self.trace( - ro, - rd, - &mut intersection, - &mut normal, - &mut dist, - &mut material, - ); - } - - col = col.powf_vec(vec3(EXPOSURE, EXPOSURE, EXPOSURE)); - col = col.clamp(Vec3::ZERO, Vec3::ONE); - // vigneting - col *= 0.25 + 0.75 * (16.0 * q.x * q.y * (1.0 - q.x) * (1.0 - q.y)).powf(0.15); - - *frag_color = col.extend(1.0); - } -} diff --git a/shaders/src/a_question_of_time.rs b/shaders/src/a_question_of_time.rs deleted file mode 100644 index c54484e..0000000 --- a/shaders/src/a_question_of_time.rs +++ /dev/null @@ -1,316 +0,0 @@ -//! Ported to Rust from -//! -//! Original comment: -//! ```glsl -//! // Author: Rigel rui@gil.com -//! // licence: https://creativecommons.org/licenses/by/4.0/ -//! // link: https://www.shadertoy.com/view/lljfRD -//! -//! -//! /* -//! This was a study on circles, inspired by this artwork -//! http://www.dailymail.co.uk/news/article-1236380/Worlds-largest-artwork-etched-desert-sand.html -//! -//! and implemented with the help of this article -//! http://www.ams.org/samplings/feature-column/fcarc-kissing -//! -//! The structure is called an apollonian packing (or gasket) -//! https://en.m.wikipedia.org/wiki/Apollonian_gasket -//! -//! There is a lot of apollonians in shadertoy, but not many quite like the image above. -//! This one by klems is really cool. He uses a technique called a soddy circle. -//! https://www.shadertoy.com/view/4s2czK -//! -//! This shader uses another technique called a Descartes Configuration. -//! The only thing that makes this technique interesting is that it can be generalized to higher dimensions. -//! */ -//! ``` - -use shared::*; -use spirv_std::glam::{ - vec2, vec3, Mat2, Vec2, Vec2Swizzles, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles, -}; - -// Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but -// we tie #[no_std] above to the same condition, so it's fine. -#[cfg(target_arch = "spirv")] -use spirv_std::num_traits::Float; - -pub struct Inputs { - pub resolution: Vec3, - pub time: f32, - pub mouse: Vec4, -} - -// a few utility functions -// a signed distance function for a rectangle -fn sdf_rect(uv: Vec2, s: Vec2) -> f32 { - let auv: Vec2 = uv.abs(); - (auv.x - s.x).max(auv.y - s.y) -} -// a signed distance function for a circle -fn sdf_circle(uv: Vec2, c: Vec2, r: f32) -> f32 { - (uv - c).length() - r -} -// fills an sdf in 2d -fn fill(d: f32, s: f32, i: f32) -> f32 { - (smoothstep(0.0, s, d) - i).abs() -} -// makes a stroke of an sdf at the zero boundary -fn stroke(d: f32, w: f32, s: f32, i: f32) -> f32 { - (smoothstep(0.0, s, d.abs() - (w * 0.5)) - i).abs() -} -// a simple palette -fn pal(d: f32) -> Vec3 { - 0.5 * ((6.283 * d * vec3(2.0, 2.0, 1.0) + vec3(0.0, 1.4, 0.0)).cos() + Vec3::ONE) -} -// 2d rotation matrix -fn uvr_rotate(a: f32) -> Mat2 { - Mat2::from_cols_array(&[a.cos(), a.sin(), -a.sin(), a.cos()]) -} -// circle inversion -fn inversion(uv: Vec2, r: f32) -> Vec2 { - (r * r * uv) / Vec2::splat(uv.dot(uv)) -} -// seeded random number -fn hash(s: Vec2) -> f32 { - ((s.dot(vec2(12.9898, 78.2333))).sin() * 43758.5453123).fract_gl() -} - -// this is an algorithm to construct an apollonian packing with a descartes configuration -// remaps the plane to a circle at the origin and a specific radius. vec3(x,y,radius) -fn apollonian(uv: Vec2) -> Vec3 { - // the algorithm is recursive and must start with a initial descartes configuration - // each vec3 represents a circle with the form vec3(centerx, centery, 1./radius) - // the signed inverse radius is also called the bend (refer to the article above) - let mut dec: [Vec3; 4] = [Vec3::ZERO, Vec3::ZERO, Vec3::ZERO, Vec3::ZERO]; - // a DEC is a configuration of 4 circles tangent to each other - // the easiest way to build the initial one it to construct a symetric Steiner Chain. - // http://mathworld.wolfram.com/SteinerChain.html - let a: f32 = 6.283 / 3.; - let ra: f32 = 1.0 + (a * 0.5).sin(); - let rb: f32 = 1.0 - (a * 0.5).sin(); - dec[0] = vec3(0.0, 0.0, -1.0 / ra); - let radius: f32 = 0.5 * (ra - rb); - let bend: f32 = 1.0 / radius; - for i in 1..4 { - dec[i] = vec3((i as f32 * a).cos(), (i as f32 * a).sin(), bend); - // if the point is in one of the starting circles we have already found our solution - if (uv - dec[i].xy()).length() < radius { - return (uv - dec[i].xy()).extend(radius); - } - } - - // Now that we have a starting DEC we are going to try to - // find the solution for the current point - for _ in 0..7 { - // find the circle that is further away from the point uv, using euclidean distance - let mut fi: usize = 0; - let mut d: f32 = uv.distance(dec[0].xy()) - (1.0 / dec[0].z).abs(); - // for some reason, the euclidean distance doesn't work for the circle with negative bend - // can anyone with proper math skills, explain me why? - d *= if dec[0].z < 0.0 { -0.5 } else { 1.0 }; // just scale it to make it work... - for j in 1..4 { - let mut fd: f32 = uv.distance(dec[j].xy()) - (1. / dec[j].z).abs(); - fd *= if dec[j].z < 0.0 { -0.5 } else { 1.0 }; - if fd > d { - fi = j; - d = fd; - } - } - // put the cicle found in the last slot, to generate a solution - // in the "direction" of the point - let c: Vec3 = dec[3]; - dec[3] = dec[fi]; - dec[fi] = c; - // generate a new solution - let bend: f32 = (2.0 * (dec[0].z + dec[1].z + dec[2].z)) - dec[3].z; - let center: Vec2 = (2.0 - * (dec[0].z * dec[0].xy() + dec[1].z * dec[1].xy() + dec[2].z * dec[2].xy()) - - dec[3].z * dec[3].xy()) - / bend; - - let solution: Vec3 = center.extend(bend); - // is the solution radius is to small, quit - if (1. / bend).abs() < 0.01 { - break; - } - // if the solution contains the point return the circle - if (uv - solution.xy()).length() < 1. / bend { - return (uv - solution.xy()).extend(1. / bend); - } - // else update the descartes configuration, - dec[3] = solution; - // and repeat... - } - // if nothing is found we return by default the inner circle of the Steiner chain - uv.extend(rb) -} - -impl Inputs { - fn scene(&self, mut uv: Vec2, ms: Vec4) -> Vec3 { - let mut ci: Vec2 = Vec2::ZERO; - - // drag your mouse to apply circle inversion - if ms.y != -2.0 && ms.w > -2.0 { - uv = inversion(uv, 60.0.to_radians().cos()); - ci = ms.xy(); - } - - // remap uv to appolonian packing - let uv_apo: Vec3 = apollonian(uv - ci); - - let d: f32 = 6.2830 / 360.0; - let a: f32 = uv_apo.y.atan2(uv_apo.x); - let r: f32 = uv_apo.xy().length(); - - let circle: f32 = sdf_circle(uv, uv - uv_apo.xy(), uv_apo.z); - - // background - let mut c: Vec3 = uv.length() * pal(0.7) * 0.2; - - // drawing the clocks - if uv_apo.z > 0.3 { - c = mix(c, pal(0.75 - r * 0.1) * 0.8, fill(circle + 0.02, 0.01, 1.0)); // clock - c = mix( - c, - pal(0.4 + r * 0.1), - stroke(circle + (uv_apo.z * 0.03), uv_apo.z * 0.01, 0.005, 1.), - ); // dial - let h: f32 = stroke( - (a + d * 15.0).rem_euclid(d * 30.) - d * 15.0, - 0.02, - 0.01, - 1.0, - ); - c = mix( - c, - pal(0.4 + r * 0.1), - h * stroke(circle + (uv_apo.z * 0.16), uv_apo.z * 0.25, 0.005, 1.0), - ); // hours - - let m: f32 = stroke( - (a + d * 15.0).rem_euclid(d * 6.0) - d * 3.0, - 0.005, - 0.01, - 1.0, - ); - c = mix( - c, - pal(0.45 + r * 0.1), - (1.0 - h) * m * stroke(circle + (uv_apo.z * 0.15), uv_apo.z * 0.1, 0.005, 1.0), - ); // minutes, - - // needles rotation - let uvrh: Vec2 = uvr_rotate( - (hash(Vec2::splat(uv_apo.z)) * d * 180.0).cos().sign_gl() - * d - * self.time - * (1.0 / uv_apo.z * 10.0) - - d * 90.0, - ) - .transpose() - * uv_apo.xy(); - let uvrm: Vec2 = uvr_rotate( - (hash(Vec2::splat(uv_apo.z) * 4.0) * d * 180.0) - .cos() - .sign_gl() - * d - * self.time - * (1.0 / uv_apo.z * 120.0) - - d * 90.0, - ) - .transpose() - * uv_apo.xy(); - // draw needles - c = mix( - c, - pal(0.85), - stroke( - sdf_rect( - uvrh + vec2(uv_apo.z - (uv_apo.z * 0.8), 0.0), - uv_apo.z * vec2(0.4, 0.03), - ), - uv_apo.z * 0.01, - 0.005, - 1.0, - ), - ); - c = mix( - c, - pal(0.9), - fill( - sdf_rect( - uvrm + vec2(uv_apo.z - (uv_apo.z * 0.65), 0.0), - uv_apo.z * vec2(0.5, 0.002), - ), - 0.005, - 1.0, - ), - ); - c = mix( - c, - pal(0.5 + r * 10.0), - fill(circle + uv_apo.z - 0.02, 0.005, 1.0), - ); // center - // drawing the gears - } else if uv_apo.z > 0.05 { - let uvrg: Vec2 = uvr_rotate( - (hash(Vec2::splat(uv_apo.z + 2.0)) * d * 180.0) - .cos() - .sign_gl() - * d - * self.time - * (1.0 / uv_apo.z * 20.0), - ) - .transpose() - * uv_apo.xy(); - let g: f32 = stroke( - (uvrg.y.atan2(uvrg.x) + d * 22.5).rem_euclid(d * 45.) - d * 22.5, - 0.3, - 0.05, - 1.0, - ); - let size: Vec2 = uv_apo.z * vec2(0.45, 0.08); - c = mix( - c, - pal(0.55 - r * 0.6), - fill(circle + g * (uv_apo.z * 0.2) + 0.01, 0.001, 1.) - * fill(circle + (uv_apo.z * 0.6), 0.005, 0.0), - ); - c = mix( - c, - pal(0.55 - r * 0.6), - fill( - sdf_rect(uvrg, size).min(sdf_rect(uvrg, size.yx())), - 0.005, - 1., - ), - ); - // drawing the screws - } else { - let size: Vec2 = uv_apo.z * vec2(0.5, 0.1); - c = mix( - c, - pal(0.85 - (uv_apo.z * 2.0)), - fill(circle + 0.01, 0.007, 1.0), - ); - c = mix( - c, - pal(0.8 - (uv_apo.z * 3.)), - fill( - sdf_rect(uv_apo.xy(), size).min(sdf_rect(uv_apo.xy(), size.yx())), - 0.002, - 1.0, - ), - ); - } - c - } - - pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { - let uv: Vec2 = (frag_coord - self.resolution.xy() * 0.5) / self.resolution.y; - let ms: Vec4 = (self.mouse - self.resolution.xyxy() * 0.5) / self.resolution.y; - *frag_color = self.scene(uv * 4., ms * 4.).extend(1.0); - } -} diff --git a/shaders/src/apollonian.rs b/shaders/src/apollonian.rs deleted file mode 100644 index fa0f093..0000000 --- a/shaders/src/apollonian.rs +++ /dev/null @@ -1,185 +0,0 @@ -//! Ported to Rust from -//! -//! Original comment: -//! ```glsl -//! // Created by inigo quilez - iq/2013 -//! // License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. -//! // -//! // I can't recall where I learnt about this fractal. -//! // -//! // Coloring and fake occlusions are done by orbit trapping, as usual. -//! ``` - -use shared::*; -use spirv_std::glam::{vec2, vec3, Vec2, Vec2Swizzles, Vec3, Vec3Swizzles, Vec4}; - -// Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but -// we tie #[no_std] above to the same condition, so it's fine. -#[cfg(target_arch = "spirv")] -use spirv_std::num_traits::Float; - -pub struct Inputs { - pub resolution: Vec3, - pub time: f32, - pub mouse: Vec4, -} - -pub struct State { - inputs: Inputs, - orb: Vec4, -} - -impl State { - pub fn new(inputs: Inputs) -> Self { - State { - inputs, - orb: Vec4::ZERO, - } - } -} - -const HW_PERFORMANCE: usize = 1; - -// Antialiasing level -const AA: usize = if HW_PERFORMANCE == 0 { - 1 -} else { - 2 // Make it 3 if you have a fast machine -}; - -impl State { - fn map(&mut self, mut p: Vec3, s: f32) -> f32 { - let mut scale: f32 = 1.0; - self.orb = Vec4::splat(1000.0); - for _ in 0..8 { - p = Vec3::splat(-1.0) + 2.0 * (0.5 * p + Vec3::splat(0.5)).fract_gl(); - - let r2: f32 = p.dot(p); - - self.orb = self.orb.min(p.abs().extend(r2)); - - let k: f32 = s / r2; - p *= k; - scale *= k; - } - 0.25 * p.y.abs() / scale - } - - fn trace(&mut self, ro: Vec3, rd: Vec3, s: f32) -> f32 { - let maxd = 30.0; - let mut t: f32 = 0.01; - - for _ in 0..512 { - let precis = 0.001 * t; - - let h: f32 = self.map(ro + rd * t, s); - if h < precis || t > maxd { - break; - } - t += h; - } - if t > maxd { - t = -1.0; - } - t - } - - fn calc_normal(&mut self, pos: Vec3, t: f32, s: f32) -> Vec3 { - let precis: f32 = 0.001 * t; - - let e: Vec2 = vec2(1.0, -1.0) * precis; - (e.xyy() * self.map(pos + e.xyy(), s) - + e.yyx() * self.map(pos + e.yyx(), s) - + e.yxy() * self.map(pos + e.yxy(), s) - + e.xxx() * self.map(pos + e.xxx(), s)) - .normalize() - } - - fn render(&mut self, ro: Vec3, rd: Vec3, anim: f32) -> Vec3 { - // trace - let mut col: Vec3 = Vec3::ZERO; - let t: f32 = self.trace(ro, rd, anim); - if t > 0.0 { - let tra: Vec4 = self.orb; - let pos: Vec3 = ro + t * rd; - let nor: Vec3 = self.calc_normal(pos, t, anim); - - // lighting - let light1: Vec3 = vec3(0.577, 0.577, -0.577); - let light2: Vec3 = vec3(-0.707, 0.000, 0.707); - let key: f32 = light1.dot(nor).clamp(0.0, 1.0); - let bac: f32 = (0.2 + 0.8 * light2.dot(nor)).clamp(0.0, 1.0); - let amb: f32 = 0.7 + 0.3 * nor.y; - let ao: f32 = (tra.w * 2.0).clamp(0.0, 1.0).powf(1.2); - - let mut brdf: Vec3 = 1.0 * vec3(0.40, 0.40, 0.40) * amb * ao; - brdf += 1.0 * vec3(1.00, 1.00, 1.00) * key * ao; - brdf += 1.0 * vec3(0.40, 0.40, 0.40) * bac * ao; - - // material - let mut rgb: Vec3 = Vec3::ONE; - rgb = mix(rgb, vec3(1.0, 0.80, 0.2), (6.0 * tra.y).clamp(0.0, 1.0)); - rgb = mix( - rgb, - vec3(1.0, 0.55, 0.0), - (1.0 - 2.0 * tra.z).clamp(0.0, 1.0).powf(8.0), - ); - - // color - col = rgb * brdf * (-0.2 * t).exp(); - } - - col.sqrt() - } - - pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { - let time: f32 = self.inputs.time * 0.25 + 0.01 * self.inputs.mouse.x; - let anim: f32 = 1.1 + 0.5 * smoothstep(-0.3, 0.3, (0.1 * self.inputs.time).cos()); - let mut tot: Vec3 = Vec3::ZERO; - - for jj in 0..AA { - for ii in 0..AA { - let q: Vec2 = frag_coord + vec2(ii as f32, jj as f32) / AA as f32; - let p: Vec2 = (2.0 * q - self.inputs.resolution.xy()) / self.inputs.resolution.y; - - // camera - let ro: Vec3 = vec3( - 2.8 * (0.1 + 0.33 * time).cos(), - 0.4 + 0.30 * (0.37 * time).cos(), - 2.8 * (0.5 + 0.35 * time).cos(), - ); - let ta: Vec3 = vec3( - 1.9 * (1.2 + 0.41 * time).cos(), - 0.4 + 0.10 * (0.27 * time).cos(), - 1.9 * (2.0 + 0.38 * time).cos(), - ); - let roll: f32 = 0.2 * (0.1 * time).cos(); - let cw: Vec3 = (ta - ro).normalize(); - let cp: Vec3 = vec3(roll.sin(), roll.cos(), 0.0); - let cu: Vec3 = cw.cross(cp).normalize(); - let cv: Vec3 = cu.cross(cw).normalize(); - let rd: Vec3 = (p.x * cu + p.y * cv + 2.0 * cw).normalize(); - - tot += self.render(ro, rd, anim); - } - } - - tot = tot / (AA * AA) as f32; - - *frag_color = tot.extend(1.0); - } - - pub fn main_vr( - &mut self, - frag_color: &mut Vec4, - _frag_coord: Vec2, - frag_ray_ori: Vec3, - freag_ray_dir: Vec3, - ) { - let _time: f32 = self.inputs.time * 0.25 + 0.01 * self.inputs.mouse.x; - let anim: f32 = 1.1 + 0.5 * smoothstep(-0.3, 0.3, (0.1 * self.inputs.time).cos()); - - let col: Vec3 = self.render(frag_ray_ori + vec3(0.82, 1.2, -0.3), freag_ray_dir, anim); - *frag_color = col.extend(1.0); - } -} diff --git a/shaders/src/atmosphere_system_test.rs b/shaders/src/atmosphere_system_test.rs deleted file mode 100644 index 0bd6b11..0000000 --- a/shaders/src/atmosphere_system_test.rs +++ /dev/null @@ -1,286 +0,0 @@ -//! Ported to Rust from -//! -//! Original comment: -//! ```glsl -//! // ---------------------------------------------------------------------------- -//! // Rayleigh and Mie scattering atmosphere system -//! // -//! // implementation of the techniques described here: -//! // http://www.scratchapixel.com/old/lessons/3d-advanced-lessons/simulating-the-colors-of-the-sky/atmospheric-scattering/ -//! // ---------------------------------------------------------------------------- -//! ``` - -use spirv_std::glam::{vec2, vec3, Mat3, Vec2, Vec3, Vec3Swizzles, Vec4}; - -// Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but -// we tie #[no_std] above to the same condition, so it's fine. -#[cfg(target_arch = "spirv")] -use spirv_std::num_traits::Float; - -pub struct Inputs { - pub resolution: Vec3, - pub time: f32, - pub mouse: Vec4, -} - -pub struct State { - inputs: Inputs, - sun_dir: Vec3, -} - -impl State { - pub fn new(inputs: Inputs) -> Self { - State { - inputs, - sun_dir: vec3(0.0, 1.0, 0.0), - } - } -} - -use core::f32::consts::PI; - -#[derive(Copy, Clone)] -struct Ray { - origin: Vec3, - direction: Vec3, -} -const _BIAS: f32 = 1e-4; // small offset to avoid self-intersections - -struct Sphere { - origin: Vec3, - radius: f32, - _material: i32, -} - -struct _Plane { - direction: Vec3, - distance: f32, - material: i32, -} - -fn rotate_around_x(angle_degrees: f32) -> Mat3 { - let angle: f32 = angle_degrees.to_radians(); - let _sin: f32 = angle.sin(); - let _cos: f32 = angle.cos(); - Mat3::from_cols_array(&[1.0, 0.0, 0.0, 0.0, _cos, -_sin, 0.0, _sin, _cos]) -} - -fn get_primary_ray(cam_local_point: Vec3, cam_origin: &mut Vec3, cam_look_at: &mut Vec3) -> Ray { - let fwd: Vec3 = (*cam_look_at - *cam_origin).normalize(); - let mut up: Vec3 = vec3(0.0, 1.0, 0.0); - let right: Vec3 = up.cross(fwd); - up = fwd.cross(right); - - Ray { - origin: *cam_origin, - direction: (fwd + up * cam_local_point.y + right * cam_local_point.x).normalize(), - } -} - -fn isect_sphere(ray: Ray, sphere: Sphere, t0: &mut f32, t1: &mut f32) -> bool { - let rc: Vec3 = sphere.origin - ray.origin; - let radius2: f32 = sphere.radius * sphere.radius; - let tca: f32 = rc.dot(ray.direction); - let d2: f32 = rc.dot(rc) - tca * tca; - if d2 > radius2 { - return false; - } - let thc: f32 = (radius2 - d2).sqrt(); - *t0 = tca - thc; - *t1 = tca + thc; - true -} - -// scattering coefficients at sea level (m) -const BETA_R: Vec3 = vec3(5.5e-6, 13.0e-6, 22.4e-6); // Rayleigh -const BETA_M: Vec3 = vec3(21e-6, 21e-6, 21e-6); // Mie - -// scale height (m) -// thickness of the atmosphere if its density were uniform -const H_R: f32 = 7994.0; // Rayleigh -const H_M: f32 = 1200.0; // Mie - -fn rayleigh_phase_func(mu: f32) -> f32 { - 3.0 * (1.0 + mu*mu) - / //------------------------ - (16.0 * PI) -} - -// Henyey-Greenstein phase function factor [-1, 1] -// represents the average cosine of the scattered directions -// 0 is isotropic scattering -// > 1 is forward scattering, < 1 is backwards -const G: f32 = 0.76; -fn henyey_greenstein_phase_func(mu: f32) -> f32 { - (1. - G*G) - / //--------------------------------------------- - ((4. * PI) * (1. + G*G - 2.*G*mu).powf(1.5)) -} - -// Schlick Phase Function factor -// Pharr and Humphreys [2004] equivalence to g above -const K: f32 = 1.55 * G - 0.55 * (G * G * G); -fn schlick_phase_func(mu: f32) -> f32 { - (1. - K*K) - / //------------------------------------------- - (4. * PI * (1. + K*mu) * (1. + K*mu)) -} - -const EARTH_RADIUS: f32 = 6360e3; // (m) -const ATMOSPHERE_RADIUS: f32 = 6420e3; // (m) - -const SUN_POWER: f32 = 20.0; - -const ATMOSPHERE: Sphere = Sphere { - origin: Vec3::ZERO, - radius: ATMOSPHERE_RADIUS, - _material: 0, -}; - -const NUM_SAMPLES: i32 = 16; -const NUM_SAMPLES_LIGHT: i32 = 8; - -fn get_sun_light(ray: Ray, optical_depth_r: &mut f32, optical_depth_m: &mut f32) -> bool { - let mut t0: f32 = 0.0; - let mut t1: f32 = 0.0; - isect_sphere(ray, ATMOSPHERE, &mut t0, &mut t1); - - let mut march_pos: f32 = 0.0; - let march_step: f32 = t1 / NUM_SAMPLES_LIGHT as f32; - - for _ in 0..NUM_SAMPLES_LIGHT { - let s: Vec3 = ray.origin + ray.direction * (march_pos + 0.5 * march_step); - let height: f32 = s.length() - EARTH_RADIUS; - if height < 0.0 { - return false; - } - - *optical_depth_r += (-height / H_R).exp() * march_step; - *optical_depth_m += (-height / H_M).exp() * march_step; - - march_pos += march_step; - } - true -} - -impl State { - fn get_incident_light(&self, ray: Ray) -> Vec3 { - // "pierce" the atmosphere with the viewing ray - let mut t0: f32 = 0.0; - let mut t1: f32 = 0.0; - if !isect_sphere(ray, ATMOSPHERE, &mut t0, &mut t1) { - return Vec3::ZERO; - } - - let march_step: f32 = t1 / NUM_SAMPLES as f32; - - // cosine of angle between view and light directions - let mu: f32 = ray.direction.dot(self.sun_dir); - - // Rayleigh and Mie phase functions - // A black box indicating how light is interacting with the material - // Similar to BRDF except - // * it usually considers a single angle - // (the phase angle between 2 directions) - // * integrates to 1 over the entire sphere of directions - let phase_r: f32 = rayleigh_phase_func(mu); - let phase_m: f32 = if true { - henyey_greenstein_phase_func(mu) - } else { - schlick_phase_func(mu) - }; - - // optical depth (or "average density") - // represents the accumulated extinction coefficients - // along the path, multiplied by the length of that path - let mut optical_depth_r: f32 = 0.0; - let mut optical_depth_m: f32 = 0.0; - - let mut sum_r: Vec3 = Vec3::ZERO; - let mut sum_m: Vec3 = Vec3::ZERO; - let mut march_pos: f32 = 0.0; - - for _ in 0..NUM_SAMPLES { - let s: Vec3 = ray.origin + ray.direction * (march_pos + 0.5 * march_step); - let height: f32 = s.length() - EARTH_RADIUS; - - // integrate the height scale - let hr: f32 = (-height / H_R).exp() * march_step; - let hm: f32 = (-height / H_M).exp() * march_step; - optical_depth_r += hr; - optical_depth_m += hm; - - // gather the sunlight - let light_ray: Ray = Ray { - origin: s, - direction: self.sun_dir, - }; - let mut optical_depth_light_r: f32 = 0.0; - let mut optical_depth_light_m: f32 = 0.0; - let overground: bool = get_sun_light( - light_ray, - &mut optical_depth_light_r, - &mut optical_depth_light_m, - ); - - if overground { - let tau: Vec3 = BETA_R * (optical_depth_r + optical_depth_light_r) - + BETA_M * 1.1 * (optical_depth_m + optical_depth_light_m); - let attenuation: Vec3 = Vec3::exp(-tau); - - sum_r += hr * attenuation; - sum_m += hm * attenuation; - } - - march_pos += march_step; - } - - SUN_POWER * (sum_r * phase_r * BETA_R + sum_m * phase_m * BETA_M) - } - - pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { - let aspect_ratio: Vec2 = vec2(self.inputs.resolution.x / self.inputs.resolution.y, 1.0); - let fov: f32 = 45.0.to_radians().tan(); - let point_ndc: Vec2 = frag_coord / self.inputs.resolution.xy(); - let point_cam: Vec3 = ((2.0 * point_ndc - Vec2::ONE) * aspect_ratio * fov).extend(-1.0); - - let col: Vec3; - - // sun - let rot: Mat3 = rotate_around_x(-(self.inputs.time / 2.0).sin().abs() * 90.0); - self.sun_dir = rot.transpose() * self.sun_dir; - - if self.inputs.mouse.z < 0.1 { - // sky dome angles - let p: Vec3 = point_cam; - let z2: f32 = p.x * p.x + p.y * p.y; - let phi: f32 = p.y.atan2(p.x); - let theta: f32 = (1.0 - z2).acos(); - let dir: Vec3 = vec3( - theta.sin() * phi.cos(), - theta.cos(), - theta.sin() * phi.sin(), - ); - - let ray: Ray = Ray { - origin: vec3(0.0, EARTH_RADIUS + 1.0, 0.0), - direction: dir, - }; - - col = self.get_incident_light(ray); - } else { - let mut eye: Vec3 = vec3(0.0, EARTH_RADIUS + 1.0, 0.0); - let mut look_at: Vec3 = vec3(0.0, EARTH_RADIUS + 1.5, -1.0); - - let ray: Ray = get_primary_ray(point_cam, &mut eye, &mut look_at); - - if ray.direction.dot(vec3(0.0, 1.0, 0.0)) > 0.0 { - col = self.get_incident_light(ray); - } else { - col = Vec3::splat(0.333); - } - } - - *frag_color = col.extend(1.0); - } -} diff --git a/shaders/src/bubble_buckey_balls.rs b/shaders/src/bubble_buckey_balls.rs deleted file mode 100644 index c3dd5ae..0000000 --- a/shaders/src/bubble_buckey_balls.rs +++ /dev/null @@ -1,628 +0,0 @@ -//! Ported to Rust from -//! -//! Original comment: -//! ```glsl -//! // mplanck -//! // Tested on 13-inch Powerbook -//! // Tested on Late 2013 iMac -//! // Tested on Nvidia GTX 780 Windows 7 -//! ``` - -use crate::SampleCube; -use shared::*; -use spirv_std::glam::{vec2, vec3, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles}; - -// Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but -// we tie #[no_std] above to the same condition, so it's fine. -#[cfg(target_arch = "spirv")] -use spirv_std::num_traits::Float; - -pub struct Inputs { - pub resolution: Vec3, - pub time: f32, - pub mouse: Vec4, - pub channel0: C0, - pub channel1: C1, -} - -pub struct State { - inputs: Inputs, - - cam_point_at: Vec3, - cam_origin: Vec3, - time: f32, - ldir: Vec3, -} - -impl State { - pub fn new(inputs: Inputs) -> Self { - State { - inputs, - cam_point_at: Vec3::ZERO, - cam_origin: Vec3::ZERO, - time: 0.0, - ldir: vec3(0.8, 1.0, 0.0), - } - } -} - -// ************************************************************************** -// CONSTANTS - -const PI: f32 = 3.14159; -const TWO_PI: f32 = 6.28318; -const _PI_OVER_TWO: f32 = 1.570796; -const ONE_OVER_PI: f32 = 0.318310; -const GR: f32 = 1.61803398; - -const SMALL_FLOAT: f32 = 0.0001; -const BIG_FLOAT: f32 = 1000000.0; - -// ************************************************************************** -// MATERIAL DEFINES - -const SPHERE_MATL: f32 = 1.0; -const CHAMBER_MATL: f32 = 2.0; -const BOND_MATL: f32 = 3.0; - -// ************************************************************************** -// UTILITIES - -// Rotate the input point around the y-axis by the angle given as a -// cos(angle) and sin(angle) argument. There are many times where I want to -// reuse the same angle on different points, so why do the heavy trig twice. -// Range of outputs := ([-1.,-1.,-1.] -> [1.,1.,1.]) - -fn rotate_around_y_axis(point: Vec3, cosangle: f32, sinangle: f32) -> Vec3 { - return vec3( - point.x * cosangle + point.z * sinangle, - point.y, - point.x * -sinangle + point.z * cosangle, - ); -} - -// Rotate the input point around the x-axis by the angle given as a -// cos(angle) and sin(angle) argument. There are many times where I want to -// reuse the same angle on different points, so why do the heavy trig twice. -// Range of outputs := ([-1.,-1.,-1.] -> [1.,1.,1.]) - -fn rotate_around_x_axis(point: Vec3, cosangle: f32, sinangle: f32) -> Vec3 { - vec3( - point.x, - point.y * cosangle - point.z * sinangle, - point.y * sinangle + point.z * cosangle, - ) -} - -fn pow5(v: f32) -> f32 { - let tmp: f32 = v * v; - tmp * tmp * v -} - -// convert a 3d point to two polar coordinates. -// First coordinate is elevation angle (angle from the plane going through x+z) -// Second coordinate is azimuth (rotation around the y axis) -// Range of outputs - ([PI/2, -PI/2], [-PI, PI]) -fn _cartesian_to_polar(p: Vec3) -> Vec2 { - vec2(PI / 2. - (p.y / p.length()).acos(), p.z.atan2(p.x)) -} - -fn mergeobjs(a: Vec2, b: Vec2) -> Vec2 { - if a.x < b.x { - return a; - } else { - return b; - } - - // XXX: Some architectures have bad optimization paths - // that will cause inappropriate branching if you DON'T - // use an if statement here. - - //return mix(b, a, step(a.x, b.x)); -} - -// ************************************************************************** -// DISTANCE FIELDS - -fn spheredf(pos: Vec3, r: f32) -> f32 { - pos.length() - r -} - -fn segmentdf(p: Vec3, a: Vec3, b: Vec3, r: f32) -> f32 { - let ba: Vec3 = b - a; - let mut t: f32 = ba.dot(p - a) / SMALL_FLOAT.max(ba.dot(ba)); - t = t.clamp(0., 1.); - return (ba * t + a - p).length() - r; -} - -// ************************************************************************** -// SCENE MARCHING - -fn buckeyballsobj(p: Vec3, mr: f32) -> Vec2 { - let mut ballsobj: Vec2 = vec2(BIG_FLOAT, SPHERE_MATL); - let ap: Vec3 = p.abs(); - //let ap: Vec3 = p; - - // vertices - // fully positive hexagon - let p1: Vec3 = vec3(0.66, 0.33 + 0.66 * GR, 0.33 * GR); - let p2: Vec3 = vec3(0.33, 0.66 + 0.33 * GR, 0.66 * GR); - let p3: Vec3 = vec3(0.33 * GR, 0.66, 0.33 + 0.66 * GR); - let p4: Vec3 = vec3(0.66 * GR, 0.33, 0.66 + 0.33 * GR); - let p5: Vec3 = vec3(0.33 + 0.66 * GR, 0.33 * GR, 0.66); - let p6: Vec3 = vec3(0.66 + 0.33 * GR, 0.66 * GR, 0.33); - - // fully positive connectors - let p7: Vec3 = vec3(0.33, GR, 0.0); - let p8: Vec3 = vec3(GR, 0.0, 0.33); - let p9: Vec3 = vec3(0.0, 0.33, GR); - - ballsobj.x = ballsobj.x.min(spheredf(ap - p1, mr)); - ballsobj.x = ballsobj.x.min(spheredf(ap - p2, mr)); - ballsobj.x = ballsobj.x.min(spheredf(ap - p3, mr)); - ballsobj.x = ballsobj.x.min(spheredf(ap - p4, mr)); - ballsobj.x = ballsobj.x.min(spheredf(ap - p5, mr)); - ballsobj.x = ballsobj.x.min(spheredf(ap - p6, mr)); - ballsobj.x = ballsobj.x.min(spheredf(ap - p7, mr)); - ballsobj.x = ballsobj.x.min(spheredf(ap - p8, mr)); - ballsobj.x = ballsobj.x.min(spheredf(ap - p9, mr)); - - let mut bondsobj: Vec2 = vec2(BIG_FLOAT, BOND_MATL); - - let br: f32 = 0.2 * mr; - bondsobj.x = bondsobj.x.min(segmentdf(ap, p1, p2, br)); - bondsobj.x = bondsobj.x.min(segmentdf(ap, p2, p3, br)); - bondsobj.x = bondsobj.x.min(segmentdf(ap, p3, p4, br)); - bondsobj.x = bondsobj.x.min(segmentdf(ap, p4, p5, br)); - bondsobj.x = bondsobj.x.min(segmentdf(ap, p5, p6, br)); - bondsobj.x = bondsobj.x.min(segmentdf(ap, p6, p1, br)); - - bondsobj.x = bondsobj.x.min(segmentdf(ap, p1, p7, br)); - bondsobj.x = bondsobj.x.min(segmentdf(ap, p5, p8, br)); - bondsobj.x = bondsobj.x.min(segmentdf(ap, p3, p9, br)); - - // bond neighbors - let p10: Vec3 = vec3(-0.33, 0.66 + 0.33 * GR, 0.66 * GR); - - let p11: Vec3 = vec3(0.66 * GR, -0.33, 0.66 + 0.33 * GR); - - let p12: Vec3 = vec3(0.66 + 0.33 * GR, 0.66 * GR, -0.33); - - let p13: Vec3 = vec3(-0.33, GR, 0.0); - let p14: Vec3 = vec3(0.66, 0.33 + 0.66 * GR, -0.33 * GR); - - let p15: Vec3 = vec3(GR, 0.0, -0.33); - let p16: Vec3 = vec3(0.33 + 0.66 * GR, -0.33 * GR, 0.66); - - let p17: Vec3 = vec3(0.0, -0.33, GR); - let p18: Vec3 = vec3(-0.33 * GR, 0.66, 0.33 + 0.66 * GR); - - bondsobj.x = bondsobj.x.min(segmentdf(ap, p2, p10, br)); - bondsobj.x = bondsobj.x.min(segmentdf(ap, p4, p11, br)); - bondsobj.x = bondsobj.x.min(segmentdf(ap, p6, p12, br)); - - bondsobj.x = bondsobj.x.min(segmentdf(ap, p7, p13, br)); - bondsobj.x = bondsobj.x.min(segmentdf(ap, p7, p14, br)); - - bondsobj.x = bondsobj.x.min(segmentdf(ap, p8, p15, br)); - bondsobj.x = bondsobj.x.min(segmentdf(ap, p8, p16, br)); - - bondsobj.x = bondsobj.x.min(segmentdf(ap, p9, p17, br)); - bondsobj.x = bondsobj.x.min(segmentdf(ap, p9, p18, br)); - - mergeobjs(ballsobj, bondsobj) -} - -fn chamberobj(p: Vec3) -> Vec2 { - vec2(20.0 - p.length(), CHAMBER_MATL) -} - -impl State { - fn scenedf(&self, p: Vec3) -> Vec2 { - //let mp: Vec3 = p; - //let bbi: f32 = 0.0; - - let mut mp: Vec3 = p + Vec3::splat(3.0); - let bbi: f32 = Vec3::ONE.dot((mp / 6.).floor()); - let mr: f32 = 0.4 * (0.7 + 0.5 * (2.0 * self.time - 1.0 * p.y + 6281.0 * bbi).sin()); - - mp = mp.rem_euclid(Vec3::splat(6.0)) - Vec3::splat(3.0); - - let obj: Vec2 = buckeyballsobj(mp, mr); - - mergeobjs(chamberobj(p), obj) - } -} - -const DISTMARCH_STEPS: usize = 60; -const DISTMARCH_MAXDIST: f32 = 50.0; - -impl State { - fn distmarch(&self, ro: Vec3, rd: Vec3, maxd: f32) -> Vec2 { - let epsilon: f32 = 0.001; - let mut dist: f32 = 10.0 * epsilon; - let mut t: f32 = 0.0; - let mut material: f32 = 0.0; - for _ in 0..DISTMARCH_STEPS { - if dist.abs() < epsilon || t > maxd { - break; - } - // advance the distance of the last lookup - t += dist; - let dfresult: Vec2 = self.scenedf(ro + t * rd); - dist = dfresult.x; - material = dfresult.y; - } - - if t > maxd { - material = -1.0; - } - return vec2(t, material); - } -} - -// ************************************************************************** -// SHADOWING & NORMALS - -const SOFTSHADOW_STEPS: usize = 40; -const SOFTSHADOW_STEPSIZE: f32 = 0.1; - -impl State { - fn calc_soft_shadow(&self, ro: Vec3, rd: Vec3, mint: f32, maxt: f32, k: f32) -> f32 { - let mut shadow: f32 = 1.0; - let mut t: f32 = mint; - - for _ in 0..SOFTSHADOW_STEPS { - if t < maxt { - let h: f32 = self.scenedf(ro + rd * t).x; - shadow = shadow.min(k * h / t); - t += SOFTSHADOW_STEPSIZE; - } - } - shadow.clamp(0.0, 1.0) - } -} - -const AO_NUMSAMPLES: usize = 6; -const AO_STEPSIZE: f32 = 0.1; -const AO_STEPSCALE: f32 = 0.4; - -impl State { - fn calc_ao(&self, p: Vec3, n: Vec3) -> f32 { - let mut ao: f32 = 0.0; - let mut aoscale: f32 = 1.0; - - for aoi in 0..AO_NUMSAMPLES { - let step: f32 = 0.01 + AO_STEPSIZE * aoi as f32; - let aop: Vec3 = n * step + p; - - let d: f32 = self.scenedf(aop).x; - ao += -(d - step) * aoscale; - aoscale *= AO_STEPSCALE; - } - - ao.clamp(0.0, 1.0) - } - - // ************************************************************************** - // CAMERA & GLOBALS - - fn animate_globals(&mut self) { - // remap the mouse click ([-1, 1], [-1/ar, 1/ar]) - let mut click: Vec2 = self.inputs.mouse.xy() / self.inputs.resolution.xx(); - click = 2.0 * click - Vec2::ONE; - - self.time = 0.8 * self.inputs.time - 10.0; - - // camera position - self.cam_origin = vec3(4.5, 0.0, 4.5); - - let rotx: f32 = -1. * PI * (0.5 * click.y + 0.45) + 0.05 * self.time; - let cosrotx: f32 = rotx.cos(); - let sinrotx: f32 = rotx.sin(); - - let roty: f32 = TWO_PI * click.x + 0.05 * self.time; - let cosroty: f32 = roty.cos(); - let sinroty: f32 = roty.sin(); - - // Rotate the camera around the origin - self.cam_origin = rotate_around_x_axis(self.cam_origin, cosrotx, sinrotx); - self.cam_origin = rotate_around_y_axis(self.cam_origin, cosroty, sinroty); - - self.cam_point_at = Vec3::ZERO; - - let lroty: f32 = 0.9 * self.time; - let coslroty: f32 = lroty.cos(); - let sinlroty: f32 = lroty.sin(); - - // Rotate the light around the origin - self.ldir = rotate_around_y_axis(self.ldir, coslroty, sinlroty); - } -} - -struct CameraData { - origin: Vec3, - dir: Vec3, - _st: Vec2, -} - -impl State { - fn setup_camera(&self, frag_coord: Vec2) -> CameraData { - // aspect ratio - let invar: f32 = self.inputs.resolution.y / self.inputs.resolution.x; - let mut st: Vec2 = frag_coord / self.inputs.resolution.xy() - Vec2::splat(0.5); - st.y *= invar; - - // calculate the ray origin and ray direction that represents - // mapping the image plane towards the scene - let iu: Vec3 = vec3(0., 1., 0.); - - let iz: Vec3 = (self.cam_point_at - self.cam_origin).normalize(); - let ix: Vec3 = (iz.cross(iu)).normalize(); - let iy: Vec3 = ix.cross(iz); - - let dir: Vec3 = (st.x * ix + st.y * iy + 0.7 * iz).normalize(); - - CameraData { - origin: self.cam_origin, - dir, - _st: st, - } - } -} -// ************************************************************************** -// SHADING - -#[derive(Clone, Copy)] -struct SurfaceData { - point: Vec3, - normal: Vec3, - basecolor: Vec3, - roughness: f32, - metallic: f32, -} - -impl SurfaceData { - fn init_surf(p: Vec3, n: Vec3) -> Self { - SurfaceData { - point: p, - normal: n, - basecolor: Vec3::ZERO, - roughness: 0.0, - metallic: 0.0, - } - } -} - -impl State { - fn calc_normal(&self, p: Vec3) -> Vec3 { - let epsilon: Vec3 = vec3(0.001, 0.0, 0.0); - let n: Vec3 = vec3( - self.scenedf(p + epsilon.xyy()).x - self.scenedf(p - epsilon.xyy()).x, - self.scenedf(p + epsilon.yxy()).x - self.scenedf(p - epsilon.yxy()).x, - self.scenedf(p + epsilon.yyx()).x - self.scenedf(p - epsilon.yyx()).x, - ); - n.normalize() - } -} -fn material(surfid: f32, surf: &mut SurfaceData) { - let _surfcol: Vec3 = Vec3::ONE; - if surfid - 0.5 < SPHERE_MATL { - surf.basecolor = vec3(0.8, 0.2, 0.5); - surf.roughness = 0.5; - surf.metallic = 0.8; - } else if surfid - 0.5 < CHAMBER_MATL { - surf.basecolor = Vec3::ZERO; - surf.roughness = 1.0; - } else if surfid - 0.5 < BOND_MATL { - surf.basecolor = vec3(0.02, 0.02, 0.05); - surf.roughness = 0.2; - surf.metallic = 0.0; - } -} - -impl State { - fn integrate_dir_light(&self, ldir: Vec3, lcolor: Vec3, surf: SurfaceData) -> Vec3 { - let vdir: Vec3 = (self.cam_origin - surf.point).normalize(); - - // The half vector of a microfacet model - let hdir: Vec3 = (ldir + vdir).normalize(); - - // cos(theta_h) - theta_h is angle between half vector and normal - let costh: f32 = -SMALL_FLOAT.max(surf.normal.dot(hdir)); - // cos(theta_d) - theta_d is angle between half vector and light dir/view dir - let costd: f32 = -SMALL_FLOAT.max(ldir.dot(hdir)); - // cos(theta_l) - theta_l is angle between the light vector and normal - let costl: f32 = -SMALL_FLOAT.max(surf.normal.dot(ldir)); - // cos(theta_v) - theta_v is angle between the viewing vector and normal - let costv: f32 = -SMALL_FLOAT.max(surf.normal.dot(vdir)); - - let ndl: f32 = costl.clamp(0.0, 1.); - - let mut cout: Vec3 = Vec3::ZERO; - - if ndl > 0. { - let frk: f32 = 0.5 + 2.0 * costd * costd * surf.roughness; - let diff: Vec3 = surf.basecolor - * ONE_OVER_PI - * (1. + (frk - 1.) * pow5(1. - costl)) - * (1. + (frk - 1.) * pow5(1. - costv)); - //let diff: Vec3 = surf.basecolor * ONE_OVER_PI; // lambert - - // D(h) factor - // using the GGX approximation where the gamma factor is 2. - - // Clamping roughness so that a directional light has a specular - // response. A roughness of perfectly 0 will create light - // singularities. - let r: f32 = surf.roughness.max(0.05); - let alpha: f32 = r * r; - let denom: f32 = costh * costh * (alpha * alpha - 1.) + 1.; - let d: f32 = (alpha * alpha) / (PI * denom * denom); - - // using the GTR approximation where the gamma factor is generalized - // let alpha: f32 = surf.roughness * surf.roughness; - // let gamma: f32 = 2.0; - // let sinth: f32 = length(cross(surf.normal, hdir)); - // let D: f32 = 1.0/(alpha*alpha*costh*costh + sinth*sinth).powf(gamma); - - // G(h,l,v) factor - let k: f32 = ((r + 1.) * (r + 1.)) / 8.; - let gl: f32 = costv / (costv * (1. - k) + k); - let gv: f32 = costl / (costl * (1. - k) + k); - let g: f32 = gl * gv; - - // F(h,l) factor - let f0: Vec3 = mix(Vec3::splat(0.5), surf.basecolor, surf.metallic); - let f: Vec3 = f0 + (Vec3::ONE - f0) * pow5(1.0 - costd); - - let spec: Vec3 = d * f * g / (4.0 * costl * costv); - - let shd: f32 = self.calc_soft_shadow(surf.point, ldir, 0.1, 20.0, 5.0); - - cout += diff * ndl * shd * lcolor; - cout += spec * ndl * shd * lcolor; - } - - cout - } - - fn sample_env_light(&self, ldir: Vec3, lcolor: Vec3, surf: SurfaceData) -> Vec3 { - let vdir: Vec3 = (self.cam_origin - surf.point).normalize(); - - // The half vector of a microfacet model - let hdir: Vec3 = (ldir + vdir).normalize(); - - // cos(theta_h) - theta_h is angle between half vector and normal - let costh: f32 = surf.normal.dot(hdir); - // cos(theta_d) - theta_d is angle between half vector and light dir/view dir - let costd: f32 = ldir.dot(hdir); - // cos(theta_l) - theta_l is angle between the light vector and normal - let costl: f32 = surf.normal.dot(ldir); - // cos(theta_v) - theta_v is angle between the viewing vector and normal - let costv: f32 = surf.normal.dot(vdir); - - let ndl: f32 = costl.clamp(0.0, 1.0); - let mut cout: Vec3 = Vec3::ZERO; - if ndl > 0. { - let r: f32 = surf.roughness; - // G(h,l,v) factor - let k: f32 = r * r / 2.; - let gl: f32 = costv / (costv * (1. - k) + k); - let gv: f32 = costl / (costl * (1. - k) + k); - let g: f32 = gl * gv; - - // F(h,l) factor - let f0: Vec3 = mix(Vec3::splat(0.5), surf.basecolor, surf.metallic); - let f: Vec3 = f0 + (Vec3::ONE - f0) * pow5(1. - costd); - - // Combines the BRDF as well as the pdf of this particular - // sample direction. - let spec: Vec3 = lcolor * g * f * costd / (costh * costv); - - let shd: f32 = self.calc_soft_shadow(surf.point, ldir, 0.02, 20.0, 7.0); - - cout = spec * shd * lcolor; - } - - cout - } - - fn integrate_env_light(&self, surf: SurfaceData) -> Vec3 { - let vdir: Vec3 = (surf.point - self.cam_origin).normalize(); - let envdir: Vec3 = vdir.reflect(surf.normal); - let specolor: Vec4 = Vec4::splat(0.4) - * mix( - self.inputs.channel0.sample_cube(envdir), - self.inputs.channel1.sample_cube(envdir), - surf.roughness, - ); - - self.sample_env_light(envdir, specolor.xyz(), surf) - } - - fn shade_surface(&self, surf: SurfaceData) -> Vec3 { - let amb: Vec3 = surf.basecolor * 0.04; - // ambient occlusion is amount of occlusion. So 1 is fully occluded - // and 0 is not occluded at all. Makes math easier when mixing - // shadowing effects. - let ao: f32 = self.calc_ao(surf.point, surf.normal); - - let centerldir: Vec3 = (-surf.point).normalize(); - - let mut cout: Vec3 = Vec3::ZERO; - if surf.basecolor.dot(Vec3::ONE) > SMALL_FLOAT { - cout += self.integrate_dir_light(self.ldir, Vec3::splat(0.3), surf); - cout += self.integrate_dir_light(centerldir, vec3(0.3, 0.5, 1.0), surf); - cout += self.integrate_env_light(surf) * (1.0 - 3.5 * ao); - cout += amb * (1.0 - 5.5 * ao); - } - cout - } - - // ************************************************************************** - // MAIN - - pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { - // ---------------------------------------------------------------------- - // Animate globals - - self.animate_globals(); - - // ---------------------------------------------------------------------- - // Setup Camera - - let cam: CameraData = self.setup_camera(frag_coord); - - // ---------------------------------------------------------------------- - // SCENE MARCHING - - let scenemarch: Vec2 = self.distmarch(cam.origin, cam.dir, DISTMARCH_MAXDIST); - - // ---------------------------------------------------------------------- - // SHADING - - let mut scenecol: Vec3 = Vec3::ZERO; - if scenemarch.y > SMALL_FLOAT { - let mp: Vec3 = cam.origin + scenemarch.x * cam.dir; - let mn: Vec3 = self.calc_normal(mp); - - let mut curr_surf: SurfaceData = SurfaceData::init_surf(mp, mn); - - material(scenemarch.y, &mut curr_surf); - scenecol = self.shade_surface(curr_surf); - } - - // ---------------------------------------------------------------------- - // POST PROCESSING - - // fall off exponentially into the distance (as if there is a spot light - // on the point of interest). - scenecol *= (-0.01 * (scenemarch.x * scenemarch.x - 300.0)).exp(); - - // brighten - scenecol *= 1.3; - - // distance fog - scenecol = mix( - scenecol, - 0.02 * vec3(1.0, 0.2, 0.8), - smoothstep(10.0, 30.0, scenemarch.x), - ); - - // Gamma correct - scenecol = scenecol.powf_vec(Vec3::splat(0.45)); - - // Contrast adjust - cute trick learned from iq - scenecol = mix( - scenecol, - Vec3::splat(scenecol.dot(Vec3::splat(0.333))), - -0.6, - ); - - // color tint - scenecol = 0.5 * scenecol + 0.5 * scenecol * vec3(1.0, 1.0, 0.9); - - *frag_color = scenecol.extend(1.0); - } -} diff --git a/shaders/src/clouds.rs b/shaders/src/clouds.rs deleted file mode 100644 index 6179f7f..0000000 --- a/shaders/src/clouds.rs +++ /dev/null @@ -1,142 +0,0 @@ -//! Ported to Rust from - -use shared::*; -use spirv_std::glam::{mat2, vec2, vec3, Mat2, Vec2, Vec3, Vec3Swizzles, Vec4}; - -pub struct Inputs { - pub resolution: Vec3, - pub time: f32, -} - -const CLOUD_SCALE: f32 = 1.1; -const SPEED: f32 = 0.03; -const CLOUD_DARK: f32 = 0.5; -const CLOUD_LIGHT: f32 = 0.3; -const CLOUD_COVER: f32 = 0.2; -const CLOUD_ALPHA: f32 = 8.0; -const SKY_TINT: f32 = 0.5; -const SKY_COLOUR1: Vec3 = vec3(0.2, 0.4, 0.6); -const SKY_COLOUR2: Vec3 = vec3(0.4, 0.7, 1.0); - -const M: Mat2 = mat2(vec2(1.6, 1.2), vec2(-1.2, 1.6)); - -fn hash(mut p: Vec2) -> Vec2 { - p = vec2(p.dot(vec2(127.1, 311.7)), p.dot(vec2(269.5, 183.3))); - Vec2::splat(-1.0) + 2.0 * (p.sin() * 43758.5453123).fract_gl() -} - -fn noise(p: Vec2) -> f32 { - const K1: f32 = 0.366025404; // (sqrt(3)-1)/2; - const K2: f32 = 0.211324865; // (3-sqrt(3))/6; - let i: Vec2 = (p + Vec2::splat((p.x + p.y) * K1)).floor(); - let a: Vec2 = p - i + Vec2::splat((i.x + i.y) * K2); - let o: Vec2 = if a.x > a.y { - vec2(1.0, 0.0) - } else { - vec2(0.0, 1.0) - }; //vec2 of = 0.5 + 0.5*vec2(sign(a.x-a.y), sign(a.y-a.x)); - let b: Vec2 = a - o + Vec2::splat(K2); - let c: Vec2 = a - Vec2::splat(1.0 - 2.0 * K2); - let h: Vec3 = (Vec3::splat(0.5) - vec3(a.dot(a), b.dot(b), c.dot(c))).max(Vec3::ZERO); - let n: Vec3 = (h * h * h * h) - * vec3( - a.dot(hash(i + Vec2::ZERO)), - b.dot(hash(i + o)), - c.dot(hash(i + Vec2::splat(1.0))), - ); - n.dot(Vec3::splat(70.0)) -} - -fn fbm(mut n: Vec2) -> f32 { - let mut total: f32 = 0.0; - let mut amplitude: f32 = 0.1; - for _ in 0..7 { - total += noise(n) * amplitude; - let m = M; - n = m.transpose() * n; - amplitude *= 0.4; - } - total -} - -// ----------------------------------------------- - -impl Inputs { - pub fn main_image(&self, frag_color: &mut Vec4, frag_coord: Vec2) { - let p: Vec2 = frag_coord / self.resolution.xy(); - let mut uv: Vec2 = p * vec2(self.resolution.x / self.resolution.y, 1.0); - let mut time: f32 = self.time * SPEED; - let q: f32 = fbm(uv * CLOUD_SCALE * 0.5); - - //ridged noise shape - let mut r: f32 = 0.0; - uv *= CLOUD_SCALE; - uv -= Vec2::splat(q - time); - let mut weight: f32 = 0.8; - for _ in 0..8 { - r += (weight * noise(uv)).abs(); - let m = M; - uv = m.transpose() * uv + Vec2::splat(time); - weight *= 0.7; - } - - //noise shape - let mut f: f32 = 0.0; - uv = p * vec2(self.resolution.x / self.resolution.y, 1.0); - uv *= CLOUD_SCALE; - uv -= Vec2::splat(q - time); - weight = 0.7; - for _ in 0..8 { - f += weight * noise(uv); - let m = M; - uv = m.transpose() * uv + Vec2::splat(time); - weight *= 0.6; - } - - f *= r + f; - - //noise colour - let mut c: f32 = 0.0; - time = self.time * SPEED * 2.0; - uv = p * vec2(self.resolution.x / self.resolution.y, 1.0); - uv *= CLOUD_SCALE * 2.0; - uv -= Vec2::splat(q - time); - weight = 0.4; - for _ in 0..7 { - c += weight * noise(uv); - let m = M; - uv = m.transpose() * uv + Vec2::splat(time); - weight *= 0.6; - } - - //noise ridge colour - let mut c1: f32 = 0.0; - time = self.time * SPEED * 3.0; - uv = p * vec2(self.resolution.x / self.resolution.y, 1.0); - uv *= CLOUD_SCALE * 3.0; - uv -= Vec2::splat(q - time); - weight = 0.4; - for _ in 0..7 { - c1 += (weight * noise(uv)).abs(); - let m = M; - uv = m.transpose() * uv + Vec2::splat(time); - weight *= 0.6; - } - - c += c1; - - let skycolour: Vec3 = mix(SKY_COLOUR2, SKY_COLOUR1, p.y); - let cloudcolour: Vec3 = - vec3(1.1, 1.1, 0.9) * (CLOUD_DARK + CLOUD_LIGHT * c).clamp(0.0, 1.0); - - f = CLOUD_COVER + CLOUD_ALPHA * f * r; - - let result: Vec3 = mix( - skycolour, - (SKY_TINT * skycolour + cloudcolour).clamp(Vec3::ZERO, Vec3::splat(1.0)), - (f + c).clamp(0.0, 1.0), - ); - - *frag_color = result.extend(1.0); - } -} diff --git a/shaders/src/filtering_procedurals.rs b/shaders/src/filtering_procedurals.rs deleted file mode 100644 index 4d4f806..0000000 --- a/shaders/src/filtering_procedurals.rs +++ /dev/null @@ -1,425 +0,0 @@ -//! Ported to Rust from -//! -//! Original comment: -//! ```glsl -//! // The MIT License -//! // Copyright © 2013 Inigo Quilez -//! // 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. -//! -//! // A test on using ray differentials (only primary rays for now) to choose texture filtering -//! // footprint, and adaptively supersample/filter the procedural texture/patter (up to a rate -//! // of 10x10). -//! -//! // This solves texture aliasing without resorting to full-screen 10x10 supersampling, which would -//! // involve doing raytracing and lighting 10x10 times (not realtime at all). -//! -//! // The tecnique should be used to filter every texture independently. The ratio of the supersampling -//! // could be inveresely proportional to the screen/lighing supersampling rate such that the cost -//! // of texturing would be constant no matter the final image quality settings. -//! */ -//! ``` - -use shared::*; -use spirv_std::arch::Derivative; -use spirv_std::glam::{vec2, vec3, vec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles}; - -// Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but -// we tie #[no_std] above to the same condition, so it's fine. -#[cfg(target_arch = "spirv")] -use spirv_std::num_traits::Float; - -pub struct Inputs { - pub resolution: Vec3, - pub time: f32, - pub mouse: Vec4, -} - -//=============================================================================================== -//=============================================================================================== - -const MAX_SAMPLES: i32 = 10; // 10*10 - -//=============================================================================================== -//=============================================================================================== -// noise implementation -//=============================================================================================== -//=============================================================================================== - -fn hash3(mut p: Vec3) -> Vec3 { - p = vec3( - p.dot(vec3(127.1, 311.7, 74.7)), - p.dot(vec3(269.5, 183.3, 246.1)), - p.dot(vec3(113.5, 271.9, 124.6)), - ); - - -Vec3::ONE + 2.0 * (p.sin() * 13.5453123).fract_gl() -} - -fn noise(p: Vec3) -> f32 { - let i: Vec3 = p.floor(); - let f: Vec3 = p.fract_gl(); - - let u: Vec3 = f * f * (Vec3::splat(3.0) - 2.0 * f); - - mix( - mix( - mix( - hash3(i + vec3(0.0, 0.0, 0.0)).dot(f - vec3(0.0, 0.0, 0.0)), - hash3(i + vec3(1.0, 0.0, 0.0)).dot(f - vec3(1.0, 0.0, 0.0)), - u.x, - ), - mix( - hash3(i + vec3(0.0, 1.0, 0.0)).dot(f - vec3(0.0, 1.0, 0.0)), - hash3(i + vec3(1.0, 1.0, 0.0)).dot(f - vec3(1.0, 1.0, 0.0)), - u.x, - ), - u.y, - ), - mix( - mix( - hash3(i + vec3(0.0, 0.0, 1.0)).dot(f - vec3(0.0, 0.0, 1.0)), - hash3(i + vec3(1.0, 0.0, 1.0)).dot(f - vec3(1.0, 0.0, 1.0)), - u.x, - ), - mix( - hash3(i + vec3(0.0, 1.0, 1.0)).dot(f - vec3(0.0, 1.0, 1.0)), - hash3(i + vec3(1.0, 1.0, 1.0)).dot(f - vec3(1.0, 1.0, 1.0)), - u.x, - ), - u.y, - ), - u.z, - ) -} - -//=============================================================================================== -//=============================================================================================== -// sphere implementation -//=============================================================================================== -//=============================================================================================== - -fn soft_shadow_sphere(ro: Vec3, rd: Vec3, sph: Vec4) -> f32 { - let oc: Vec3 = sph.xyz() - ro; - let b: f32 = oc.dot(rd); - - let mut res: f32 = 1.0; - if b > 0.0 { - let h: f32 = oc.dot(oc) - b * b - sph.w * sph.w; - res = smoothstep(0.0, 1.0, 2.0 * h / b); - } - res -} - -fn occ_sphere(sph: Vec4, pos: Vec3, nor: Vec3) -> f32 { - let di: Vec3 = sph.xyz() - pos; - let l: f32 = di.length(); - 1.0 - nor.dot(di / l) * sph.w * sph.w / (l * l) -} - -fn i_sphere(ro: Vec3, rd: Vec3, sph: Vec4) -> f32 { - let mut t: f32 = -1.0; - let ce: Vec3 = ro - sph.xyz(); - let b: f32 = rd.dot(ce); - let c: f32 = ce.dot(ce) - sph.w * sph.w; - let h: f32 = b * b - c; - if h > 0.0 { - t = -b - h.sqrt(); - } - - t -} - -//=============================================================================================== -//=============================================================================================== -// scene -//=============================================================================================== -//=============================================================================================== - -// spheres -const SC0: Vec4 = vec4(0.0, 1.0, 0.0, 1.0); -const SC1: Vec4 = vec4(0.0, 1.0, 14.0, 4.0); -const SC2: Vec4 = vec4(-11.0, 1.0, 12.0, 4.0); -const SC3: Vec4 = vec4(13.0, 1.0, -10.0, 4.0); - -fn intersect( - ro: Vec3, - rd: Vec3, - pos: &mut Vec3, - nor: &mut Vec3, - occ: &mut f32, - matid: &mut f32, -) -> f32 { - // raytrace - let mut tmin: f32 = 10000.0; - *nor = Vec3::ZERO; - *occ = 1.0; - *pos = Vec3::ZERO; - - // raytrace-plane - let mut h: f32 = (0.0 - ro.y) / rd.y; - if h > 0.0 { - tmin = h; - *nor = vec3(0.0, 1.0, 0.0); - *pos = ro + h * rd; - *matid = 0.0; - *occ = occ_sphere(SC0, *pos, *nor) - * occ_sphere(SC1, *pos, *nor) - * occ_sphere(SC2, *pos, *nor) - * occ_sphere(SC3, *pos, *nor); - } - - // raytrace-sphere - h = i_sphere(ro, rd, SC0); - if h > 0.0 && h < tmin { - tmin = h; - *pos = ro + h * rd; - *nor = (*pos - SC0.xyz()).normalize(); - *matid = 1.0; - *occ = 0.5 + 0.5 * nor.y; - } - - h = i_sphere(ro, rd, SC1); - if h > 0.0 && h < tmin { - tmin = h; - *pos = ro + tmin * rd; - *nor = (ro + h * rd - SC1.xyz()).normalize(); - *matid = 1.0; - *occ = 0.5 + 0.5 * nor.y; - } - - h = i_sphere(ro, rd, SC2); - if h > 0.0 && h < tmin { - tmin = h; - *pos = ro + tmin * rd; - *nor = (ro + h * rd - SC2.xyz()).normalize(); - *matid = 1.0; - *occ = 0.5 + 0.5 * nor.y; - } - - h = i_sphere(ro, rd, SC3); - if h > 0.0 && h < tmin { - tmin = h; - *pos = ro + tmin * rd; - *nor = (ro + h * rd - SC3.xyz()).normalize(); - *matid = 1.0; - *occ = 0.5 + 0.5 * nor.y; - } - - tmin -} - -fn tex_coords(p: Vec3) -> Vec3 { - 64.0 * p -} - -fn mytexture(mut p: Vec3, _n: Vec3, matid: f32) -> Vec3 { - p += Vec3::splat(0.1); - let ip: Vec3 = (p / 20.0).floor(); - let fp: Vec3 = (Vec3::splat(0.5) + p / 20.0).fract_gl(); - - let mut id: f32 = ((ip.dot(vec3(127.1, 311.7, 74.7))).sin() * 58.5453123).fract_gl(); - id = mix(id, 0.3, matid); - - let f: f32 = (ip.x + (ip.y + ip.z.rem_euclid(2.0)).rem_euclid(2.0)).rem_euclid(2.0); - - let mut g: f32 = - 0.5 + 1.0 * noise(p * mix(vec3(0.2 + 0.8 * f, 1.0, 1.0 - 0.8 * f), Vec3::ONE, matid)); - - g *= mix( - smoothstep(0.03, 0.04, (fp.x - 0.5).abs() / 0.5) - * smoothstep(0.03, 0.04, (fp.z - 0.5).abs() / 0.5), - 1.0, - matid, - ); - - let col: Vec3 = - Vec3::splat(0.5) + 0.5 * (Vec3::splat(1.0 + 2.0 * id) + vec3(0.0, 1.0, 2.0)).sin(); - - col * g -} - -impl Inputs { - fn calc_camera(&self, ro: &mut Vec3, ta: &mut Vec3) { - let an: f32 = 0.1 * self.time; - *ro = vec3(5.5 * an.cos(), 1.0, 5.5 * an.sin()); - *ta = vec3(0.0, 1.0, 0.0); - } -} - -fn do_lighting(pos: Vec3, nor: Vec3, occ: f32, rd: Vec3) -> Vec3 { - let sh: f32 = soft_shadow_sphere(pos, Vec3::splat(0.57703), SC0) - .min(soft_shadow_sphere(pos, Vec3::splat(0.57703), SC1)) - .min(soft_shadow_sphere(pos, Vec3::splat(0.57703), SC2)) - .min(soft_shadow_sphere(pos, Vec3::splat(0.57703), SC3)); - let dif: f32 = nor.dot(Vec3::splat(0.57703)).clamp(0.0, 1.0); - let bac: f32 = nor.dot(vec3(-0.707, 0.0, -0.707)).clamp(0.0, 1.0); - let mut lin: Vec3 = dif * vec3(1.50, 1.40, 1.30) * sh; - lin += occ * vec3(0.15, 0.20, 0.30); - lin += bac * vec3(0.20, 0.20, 0.20); - lin += Vec3::splat( - sh * 0.8 - * rd.reflect(nor) - .dot(Vec3::splat(0.57703)) - .clamp(0.0, 1.0) - .powf(12.0), - ); - - lin -} -//=============================================================================================== -//=============================================================================================== -// render -//=============================================================================================== -//=============================================================================================== -impl Inputs { - fn calc_ray_for_pixel(&self, pix: Vec2, res_ro: &mut Vec3, res_rd: &mut Vec3) { - let p: Vec2 = (-self.resolution.xy() + 2.0 * pix) / self.resolution.y; - // camera movement - let mut ro: Vec3 = Vec3::ZERO; - let mut ta: Vec3 = Vec3::ZERO; - self.calc_camera(&mut ro, &mut ta); - // camera matrix - let ww: Vec3 = (ta - ro).normalize(); - let uu: Vec3 = ww.cross(vec3(0.0, 1.0, 0.0)).normalize(); - let vv: Vec3 = uu.cross(ww).normalize(); - // create view ray - let rd: Vec3 = (p.x * uu + p.y * vv + 1.5 * ww).normalize(); - - *res_ro = ro; - *res_rd = rd; - } -} - -// sample a procedural texture with filtering -fn sample_texture_with_filter( - uvw: Vec3, - ddx_uvw: Vec3, - ddy_uvw: Vec3, - nor: Vec3, - mid: f32, -) -> Vec3 { - let sx: i32 = 1 + (4.0 * (ddx_uvw - uvw).length()).clamp(0.0, (MAX_SAMPLES - 1) as f32) as i32; - let sy: i32 = 1 + (4.0 * (ddy_uvw - uvw).length()).clamp(0.0, (MAX_SAMPLES - 1) as f32) as i32; - - let mut no: Vec3 = Vec3::ZERO; - - if true { - for j in 0..MAX_SAMPLES { - for i in 0..MAX_SAMPLES { - if j < sy && i < sx { - let st: Vec2 = vec2(i as f32, j as f32) / vec2(sx as f32, sy as f32); - no += mytexture( - uvw + st.x * (ddx_uvw - uvw) + st.y * (ddy_uvw - uvw), - nor, - mid, - ); - } - } - } - } else { - for j in 0..sy { - for i in 0..sx { - let st: Vec2 = vec2(i as f32, j as f32) / vec2(sx as f32, sy as f32); - no += mytexture( - uvw + st.x * (ddx_uvw - uvw) + st.y * (ddy_uvw - uvw), - nor, - mid, - ); - } - } - } - - no / (sx * sy) as f32 -} - -fn sample_texture(uvw: Vec3, nor: Vec3, mid: f32) -> Vec3 { - mytexture(uvw, nor, mid) -} - -impl Inputs { - pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { - let p: Vec2 = (-self.resolution.xy() + 2.0 * frag_coord) / self.resolution.y; - let mut th: f32 = (-self.resolution.x + 2.0 * self.mouse.x) / self.resolution.y; - - if self.mouse.z < 0.01 { - th = 0.5 / self.resolution.y; - } - - let mut ro: Vec3 = Vec3::ZERO; - let mut rd: Vec3 = Vec3::ZERO; - let mut ddx_ro: Vec3 = Vec3::ZERO; - let mut ddx_rd: Vec3 = Vec3::ZERO; - let mut ddy_ro: Vec3 = Vec3::ZERO; - let mut ddy_rd: Vec3 = Vec3::ZERO; - self.calc_ray_for_pixel(frag_coord + vec2(0.0, 0.0), &mut ro, &mut rd); - self.calc_ray_for_pixel(frag_coord + vec2(1.0, 0.0), &mut ddx_ro, &mut ddx_rd); - self.calc_ray_for_pixel(frag_coord + vec2(0.0, 1.0), &mut ddy_ro, &mut ddy_rd); - - // trace - let mut pos: Vec3 = Vec3::ZERO; - let mut nor: Vec3 = Vec3::ZERO; - let mut occ: f32 = 0.0; - let mut mid: f32 = 0.0; - let t: f32 = intersect(ro, rd, &mut pos, &mut nor, &mut occ, &mut mid); - - let mut col: Vec3 = Vec3::splat(0.9); - - let uvw: Vec3; - let ddx_uvw: Vec3; - let ddy_uvw: Vec3; - - if t < 100.0 { - if true { - // ----------------------------------------------------------------------- - // compute ray differentials by intersecting the tangent plane to the - // surface. - // ----------------------------------------------------------------------- - - // computer ray differentials - let ddx_pos: Vec3 = ddx_ro - ddx_rd * (ddx_ro - pos).dot(nor) / ddx_rd.dot(nor); - let ddy_pos: Vec3 = ddy_ro - ddy_rd * (ddy_ro - pos).dot(nor) / ddy_rd.dot(nor); - - // calc texture sampling footprint - uvw = tex_coords(pos); - ddx_uvw = tex_coords(ddx_pos); - ddy_uvw = tex_coords(ddy_pos); - } else { - // ----------------------------------------------------------------------- - // Because we are in the GPU, we do have access to differentials directly - // This wouldn't be the case in a regular raytrace. - // It wouldn't work as well in shaders doing interleaved calculations in - // pixels (such as some of the 3D/stereo shaders here in Shadertoy) - // ----------------------------------------------------------------------- - uvw = tex_coords(pos); - - // calc texture sampling footprint - ddx_uvw = uvw + uvw.dfdx(); - ddy_uvw = uvw + uvw.dfdy(); - } - // shading - let mate: Vec3; - - if p.x > th { - mate = sample_texture(uvw, nor, mid); - } else { - mate = sample_texture_with_filter(uvw, ddx_uvw, ddy_uvw, nor, mid); - } - - // lighting - let lin: Vec3 = do_lighting(pos, nor, occ, rd); - - // combine lighting with material - col = mate * lin; - - // fog - col = mix(col, Vec3::splat(0.9), 1.0 - (-0.0002 * t * t).exp()); - } - - // gamma correction - col = col.powf(0.4545); - - col *= smoothstep(0.006, 0.008, (p.x - th).abs()); - - *frag_color = col.extend(1.0); - } -} diff --git a/shaders/src/flappy_bird.rs b/shaders/src/flappy_bird.rs deleted file mode 100644 index bc6be58..0000000 --- a/shaders/src/flappy_bird.rs +++ /dev/null @@ -1,1243 +0,0 @@ -//! Ported to Rust from -//! -//! Original comment: -//! ```glsl -//! // FlappyBird by Ben Raziel. Feb 2014 -//! -//! // Based on the "Super Mario Bros" shader by HLorenzi -//! // https://www.shadertoy.com/view/Msj3zD -//! ``` - -use spirv_std::glam::{vec2, vec4, Vec2, Vec3, Vec4}; - -// Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but -// we tie #[no_std] above to the same condition, so it's fine. -#[cfg(target_arch = "spirv")] -use {shared::*, spirv_std::num_traits::Float}; - -pub struct Inputs { - pub resolution: Vec3, - pub time: f32, -} - -pub struct State { - inputs: Inputs, - - frag_color: Vec4, -} - -impl State { - pub fn new(inputs: Inputs) -> State { - State { - inputs, - - frag_color: Vec4::ZERO, - } - } -} - -// Helper functions for drawing sprites -fn rgb(r: i32, g: i32, b: i32) -> Vec4 { - vec4(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, 1.0) -} -fn sprrow( - x: i32, - a: f32, - b: f32, - c: f32, - d: f32, - e: f32, - f: f32, - g: f32, - h: f32, - i: f32, - j: f32, - k: f32, - l: f32, - m: f32, - n: f32, - o: f32, - p: f32, -) -> f32 { - if x <= 7 { - sprrow_h(a, b, c, d, e, f, g, h) - } else { - sprrow_h(i, j, k, l, m, n, o, p) - } -} -fn sprrow_h(a: f32, b: f32, c: f32, d: f32, e: f32, f: f32, g: f32, h: f32) -> f32 { - a + 4.0 * (b + 4.0 * (c + 4.0 * (d + 4.0 * (e + 4.0 * (f + 4.0 * (g + 4.0 * (h))))))) -} -fn _secrow(x: i32, a: f32, b: f32, c: f32, d: f32, e: f32, f: f32, g: f32, h: f32) -> f32 { - if x <= 3 { - _secrow_h(a, b, c, d) - } else { - _secrow_h(e, f, g, h) - } -} -fn _secrow_h(a: f32, b: f32, c: f32, d: f32) -> f32 { - a + 8.0 * (b + 8.0 * (c + 8.0 * (d))) -} -fn select(x: i32, i: f32) -> f32 { - (i / 4.0_f32.powf(x as f32)).floor().rem_euclid(4.0) -} -fn _selectsec(x: i32, i: f32) -> f32 { - (i / 8.0_f32.powf(x as f32)).floor().rem_euclid(8.0) -} - -// drawing consts -const PIPE_WIDTH: f32 = 26.0; // px -const PIPE_BOTTOM: f32 = 39.0; // px -const PIPE_HOLE_HEIGHT: f32 = 12.0; // px - -// const PIPE_OUTLINE_COLOR: Vec4 = RGB(84, 56, 71); -const PIPE_OUTLINE_COLOR: Vec4 = vec4(84 as f32 / 255.0, 56 as f32 / 255.0, 71 as f32 / 255.0, 1.0); - -// gameplay consts -const HORZ_PIPE_DISTANCE: f32 = 100.0; // px; -const VERT_PIPE_DISTANCE: f32 = 55.0; // px; -const PIPE_MIN: f32 = 20.0; -const PIPE_MAX: f32 = 70.0; -const PIPE_PER_CYCLE: f32 = 8.0; - -impl State { - fn draw_horz_rect(&mut self, y_coord: f32, min_y: f32, max_y: f32, color: Vec4) { - if (y_coord >= min_y) && (y_coord < max_y) { - self.frag_color = color; - } - } - - fn draw_low_bush(&mut self, x: i32, y: i32) { - if y < 0 || y > 3 || x < 0 || x > 15 { - return; - } - - let mut col: f32 = 0.0; // 0 = transparent - - if y == 3 { - col = sprrow( - x, 0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 0., 0., 0., 0., 0., 0., - ); - } - if y == 2 { - col = sprrow( - x, 0., 0., 0., 0., 1., 1., 2., 2., 2., 2., 1., 1., 0., 0., 0., 0., - ); - } - if y == 1 { - col = sprrow( - x, 0., 0., 0., 1., 1., 2., 2., 2., 2., 2., 2., 1., 1., 0., 0., 0., - ); - } - if y == 0 { - col = sprrow( - x, 0., 0., 1., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 1., 0., 0., - ); - } - - // i made this tho a i32 cast bc it seems as this thing(SELECT(i32, f32)) uses a i 32 - col = select((x as f32).rem_euclid(8.0) as i32, col); - if col == 1.0 { - self.frag_color = rgb(87, 201, 111); - } else if col == 2.0 { - self.frag_color = rgb(100, 224, 117); - } - } - - fn draw_high_bush(&mut self, x: i32, y: i32) { - if y < 0 || y > 6 || x < 0 || x > 15 { - return; - } - - let mut col: f32 = 0.0; // 0 = transparent - - if y == 6 { - col = sprrow( - x, 0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 0., 0., 0., 0., 0., 0., - ); - } - if y == 5 { - col = sprrow( - x, 0., 0., 0., 0., 1., 1., 2., 2., 2., 2., 1., 1., 0., 0., 0., 0., - ); - } - if y == 4 { - col = sprrow( - x, 0., 0., 1., 1., 2., 2., 2., 2., 2., 2., 2., 2., 1., 1., 0., 0., - ); - } - if y == 3 { - col = sprrow( - x, 0., 1., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 1., 0., - ); - } - if y == 2 { - col = sprrow( - x, 0., 1., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 1., 0., - ); - } - if y == 1 { - col = sprrow( - x, 1., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 1., - ); - } - if y == 0 { - col = sprrow( - x, 1., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 1., - ); - } - - col = select((x as f32).rem_euclid(8.0) as i32, col); - if col == 1.0 { - self.frag_color = rgb(87, 201, 111); - } else if col == 2.0 { - self.frag_color = rgb(100, 224, 117); - } - } - - fn draw_cloud(&mut self, x: i32, y: i32) { - if y < 0 || y > 6 || x < 0 || x > 15 { - return; - } - - let mut col: f32 = 0.0; // 0 = transparent - - if y == 6 { - col = sprrow( - x, 0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 0., 0., 0., 0., 0., 0., - ); - } - if y == 5 { - col = sprrow( - x, 0., 0., 0., 0., 1., 1., 2., 2., 2., 2., 1., 1., 0., 0., 0., 0., - ); - } - if y == 4 { - col = sprrow( - x, 0., 0., 1., 1., 2., 2., 2., 2., 2., 2., 2., 2., 1., 1., 0., 0., - ); - } - if y == 3 { - col = sprrow( - x, 0., 1., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 1., 0., - ); - } - if y == 2 { - col = sprrow( - x, 0., 1., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 1., 0., - ); - } - if y == 1 { - col = sprrow( - x, 1., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 1., - ); - } - if y == 0 { - col = sprrow( - x, 1., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 1., - ); - } - - col = select((x as f32).rem_euclid(8.0) as i32, col); - if col == 1.0 { - self.frag_color = rgb(218, 246, 216); - } else if col == 2.0 { - self.frag_color = rgb(233, 251, 218); - } - } - - fn draw_bird_f0(&mut self, x: i32, y: i32) { - if y < 0 || y > 11 || x < 0 || x > 15 { - return; - } - - // pass 0 - draw black, white and yellow - let mut col: f32 = 0.0; // 0 = transparent - if y == 11 { - col = sprrow( - x, 0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 1., 1., 0., 0., 0., 0., - ); - } - if y == 10 { - col = sprrow( - x, 0., 0., 0., 0., 1., 1., 3., 3., 3., 1., 2., 2., 1., 0., 0., 0., - ); - } - if y == 9 { - col = sprrow( - x, 0., 0., 0., 1., 3., 3., 3., 3., 1., 2., 2., 2., 2., 1., 0., 0., - ); - } - if y == 8 { - col = sprrow( - x, 0., 0., 1., 3., 3., 3., 3., 3., 1., 2., 2., 2., 1., 2., 1., 0., - ); - } - if y == 7 { - col = sprrow( - x, 0., 1., 3., 3., 3., 3., 3., 3., 1., 2., 2., 2., 1., 2., 1., 0., - ); - } - if y == 6 { - col = sprrow( - x, 0., 1., 3., 3., 3., 3., 3., 3., 3., 1., 2., 2., 2., 2., 1., 0., - ); - } - if y == 5 { - col = sprrow( - x, 0., 1., 1., 1., 1., 1., 3., 3., 3., 3., 1., 1., 1., 1., 1., 1., - ); - } - if y == 4 { - col = sprrow( - x, 1., 2., 2., 2., 2., 2., 1., 3., 3., 1., 2., 2., 2., 2., 2., 1., - ); - } - if y == 3 { - col = sprrow( - x, 1., 2., 2., 2., 2., 1., 3., 3., 1., 2., 1., 1., 1., 1., 1., 1., - ); - } - if y == 2 { - col = sprrow( - x, 1., 2., 2., 2., 1., 3., 3., 3., 3., 1., 2., 2., 2., 2., 1., 0., - ); - } - if y == 1 { - col = sprrow( - x, 0., 1., 1., 1., 1., 3., 3., 3., 3., 3., 1., 1., 1., 1., 1., 0., - ); - } - if y == 0 { - col = sprrow( - x, 0., 0., 0., 0., 0., 1., 1., 1., 1., 1., 0., 0., 0., 0., 0., 0., - ); - } - - col = select((x as f32).rem_euclid(8.0) as i32, col); - if col == 1.0 { - self.frag_color = rgb(82, 56, 70); // outline color (black) - } else if col == 2.0 { - self.frag_color = rgb(250, 250, 250); // eye color (white) - } else if col == 3.0 { - self.frag_color = rgb(247, 182, 67); // normal yellow color - } - - // pass 1 - draw red, light yellow and dark yellow - col = 0.0; // 0 = transparent - if y == 11 { - col = sprrow( - x, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., - ); - } - if y == 10 { - col = sprrow( - x, 0., 0., 0., 0., 0., 0., 3., 3., 3., 0., 0., 0., 0., 0., 0., 0., - ); - } - if y == 9 { - col = sprrow( - x, 0., 0., 0., 0., 3., 3., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., - ); - } - if y == 8 { - col = sprrow( - x, 0., 0., 0., 3., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., - ); - } - if y == 7 { - col = sprrow( - x, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., - ); - } - if y == 6 { - col = sprrow( - x, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., - ); - } - if y == 5 { - col = sprrow( - x, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., - ); - } - if y == 4 { - col = sprrow( - x, 0., 3., 0., 0., 0., 3., 0., 0., 0., 0., 1., 1., 1., 1., 1., 0., - ); - } - if y == 3 { - col = sprrow( - x, 0., 0., 0., 0., 0., 0., 2., 2., 0., 1., 0., 0., 0., 0., 0., 0., - ); - } - if y == 2 { - col = sprrow( - x, 0., 0., 0., 3., 0., 2., 2., 2., 2., 0., 1., 1., 1., 1., 0., 0., - ); - } - if y == 1 { - col = sprrow( - x, 0., 0., 0., 0., 0., 2., 2., 2., 2., 2., 0., 0., 0., 0., 0., 0., - ); - } - if y == 0 { - col = sprrow( - x, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., - ); - } - - col = select((x as f32).rem_euclid(8.0) as i32, col); - if col == 1.0 { - self.frag_color = rgb(249, 58, 28); // mouth color (red) - } else if col == 2.0 { - self.frag_color = rgb(222, 128, 55); // brown - } else if col == 3.0 { - self.frag_color = rgb(249, 214, 145); // light yellow - } - } - - fn draw_bird_f1(&mut self, x: i32, y: i32) { - if y < 0 || y > 11 || x < 0 || x > 15 { - return; - } - - // pass 0 - draw black, white and yellow - let mut col: f32 = 0.0; // 0 = transparent - if y == 11 { - col = sprrow( - x, 0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 1., 1., 0., 0., 0., 0., - ); - } - if y == 10 { - col = sprrow( - x, 0., 0., 0., 0., 1., 1., 3., 3., 3., 1., 2., 2., 1., 0., 0., 0., - ); - } - if y == 9 { - col = sprrow( - x, 0., 0., 0., 1., 3., 3., 3., 3., 1., 2., 2., 2., 2., 1., 0., 0., - ); - } - if y == 8 { - col = sprrow( - x, 0., 0., 1., 3., 3., 3., 3., 3., 1., 2., 2., 2., 1., 2., 1., 0., - ); - } - if y == 7 { - col = sprrow( - x, 0., 1., 3., 3., 3., 3., 3., 3., 1., 2., 2., 2., 1., 2., 1., 0., - ); - } - if y == 6 { - col = sprrow( - x, 0., 1., 1., 1., 1., 1., 3., 3., 3., 1., 2., 2., 2., 2., 1., 0., - ); - } - if y == 5 { - col = sprrow( - x, 1., 2., 2., 2., 2., 2., 1., 3., 3., 3., 1., 1., 1., 1., 1., 1., - ); - } - if y == 4 { - col = sprrow( - x, 1., 2., 2., 2., 2., 2., 1., 3., 3., 1., 2., 2., 2., 2., 2., 1., - ); - } - if y == 3 { - col = sprrow( - x, 0., 1., 1., 1., 1., 1., 3., 3., 1., 2., 1., 1., 1., 1., 1., 1., - ); - } - if y == 2 { - col = sprrow( - x, 0., 0., 1., 3., 3., 3., 3., 3., 3., 1., 2., 2., 2., 2., 1., 0., - ); - } - if y == 1 { - col = sprrow( - x, 0., 0., 0., 1., 1., 3., 3., 3., 3., 3., 1., 1., 1., 1., 1., 0., - ); - } - if y == 0 { - col = sprrow( - x, 0., 0., 0., 0., 0., 1., 1., 1., 1., 1., 0., 0., 0., 0., 0., 0., - ); - } - - col = select((x as f32).rem_euclid(8.0) as i32, col); - if col == 1.0 { - self.frag_color = rgb(82, 56, 70); // outline color (black) - } else if col == 2.0 { - self.frag_color = rgb(250, 250, 250); // eye color (white) - } else if col == 3.0 { - self.frag_color = rgb(247, 182, 67); // normal yellow color - } - - // pass 1 - draw red, light yellow and dark yellow - col = 0.0; // 0 = transparent - if y == 11 { - col = sprrow( - x, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., - ); - } - if y == 10 { - col = sprrow( - x, 0., 0., 0., 0., 0., 0., 3., 3., 3., 0., 0., 0., 0., 0., 0., 0., - ); - } - if y == 9 { - col = sprrow( - x, 0., 0., 0., 0., 3., 3., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., - ); - } - if y == 8 { - col = sprrow( - x, 0., 0., 0., 3., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., - ); - } - if y == 7 { - col = sprrow( - x, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., - ); - } - if y == 6 { - col = sprrow( - x, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., - ); - } - if y == 5 { - col = sprrow( - x, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., - ); - } - if y == 4 { - col = sprrow( - x, 0., 3., 0., 0., 0., 3., 0., 0., 0., 0., 1., 1., 1., 1., 1., 0., - ); - } - if y == 3 { - col = sprrow( - x, 0., 0., 0., 0., 0., 0., 2., 2., 0., 1., 0., 0., 0., 0., 0., 0., - ); - } - if y == 2 { - col = sprrow( - x, 0., 0., 0., 2., 2., 2., 2., 2., 2., 0., 1., 1., 1., 1., 0., 0., - ); - } - if y == 1 { - col = sprrow( - x, 0., 0., 0., 0., 0., 2., 2., 2., 2., 2., 0., 0., 0., 0., 0., 0., - ); - } - if y == 0 { - col = sprrow( - x, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., - ); - } - - col = select((x as f32).rem_euclid(8.0) as i32, col); - if col == 1.0 { - self.frag_color = rgb(249, 58, 28); // mouth color (red) - } else if col == 2.0 { - self.frag_color = rgb(222, 128, 55); // brown - } else if col == 3.0 { - self.frag_color = rgb(249, 214, 145); // light yellow - } - } - - fn draw_bird_f2(&mut self, x: i32, y: i32) { - if y < 0 || y > 11 || x < 0 || x > 15 { - return; - } - - // pass 0 - draw black, white and yellow - let mut col: f32 = 0.0; // 0 = transparent - if y == 11 { - col = sprrow( - x, 0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 1., 1., 0., 0., 0., 0., - ); - } - if y == 10 { - col = sprrow( - x, 0., 0., 0., 0., 1., 1., 3., 3., 3., 1., 2., 2., 1., 0., 0., 0., - ); - } - if y == 9 { - col = sprrow( - x, 0., 0., 0., 1., 3., 3., 3., 3., 1., 2., 2., 2., 2., 1., 0., 0., - ); - } - if y == 8 { - col = sprrow( - x, 0., 1., 1., 1., 3., 3., 3., 3., 1., 2., 2., 2., 1., 2., 1., 0., - ); - } - if y == 7 { - col = sprrow( - x, 1., 2., 2., 2., 1., 3., 3., 3., 1., 2., 2., 2., 1., 2., 1., 0., - ); - } - if y == 6 { - col = sprrow( - x, 1., 2., 2., 2., 2., 1., 3., 3., 3., 1., 2., 2., 2., 2., 1., 0., - ); - } - if y == 5 { - col = sprrow( - x, 1., 2., 2., 2., 2., 1., 3., 3., 3., 3., 1., 1., 1., 1., 1., 1., - ); - } - if y == 4 { - col = sprrow( - x, 0., 1., 2., 2., 2., 1., 3., 3., 3., 1., 2., 2., 2., 2., 2., 1., - ); - } - if y == 3 { - col = sprrow( - x, 0., 1., 1., 1., 1., 3., 3., 3., 1., 2., 1., 1., 1., 1., 1., 1., - ); - } - if y == 2 { - col = sprrow( - x, 0., 0., 1., 3., 3., 3., 3., 3., 3., 1., 2., 2., 2., 2., 1., 0., - ); - } - if y == 1 { - col = sprrow( - x, 0., 0., 0., 1., 1., 3., 3., 3., 3., 3., 1., 1., 1., 1., 1., 0., - ); - } - if y == 0 { - col = sprrow( - x, 0., 0., 0., 0., 0., 1., 1., 1., 1., 1., 0., 0., 0., 0., 0., 0., - ); - } - - col = select((x as f32).rem_euclid(8.0) as i32, col); - if col == 1.0 { - self.frag_color = rgb(82, 56, 70); // outline color (black) - } else if col == 2.0 { - self.frag_color = rgb(250, 250, 250); // eye color (white) - } else if col == 3.0 { - self.frag_color = rgb(247, 182, 67); // normal yellow color - } - - // pass 1 - draw red, light yellow and dark yellow - col = 0.0; // 0 = transparent - if y == 11 { - col = sprrow( - x, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., - ); - } - if y == 10 { - col = sprrow( - x, 0., 0., 0., 0., 0., 0., 3., 3., 3., 0., 0., 0., 0., 0., 0., 0., - ); - } - if y == 9 { - col = sprrow( - x, 0., 0., 0., 0., 3., 3., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., - ); - } - if y == 8 { - col = sprrow( - x, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., - ); - } - if y == 7 { - col = sprrow( - x, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., - ); - } - if y == 6 { - col = sprrow( - x, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., - ); - } - if y == 5 { - col = sprrow( - x, 0., 3., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., - ); - } - if y == 4 { - col = sprrow( - x, 0., 0., 3., 3., 3., 0., 0., 0., 0., 0., 1., 1., 1., 1., 1., 0., - ); - } - if y == 3 { - col = sprrow( - x, 0., 0., 0., 0., 0., 2., 2., 2., 0., 1., 0., 0., 0., 0., 0., 0., - ); - } - if y == 2 { - col = sprrow( - x, 0., 0., 0., 2., 2., 2., 2., 2., 2., 0., 1., 1., 1., 1., 0., 0., - ); - } - if y == 1 { - col = sprrow( - x, 0., 0., 0., 0., 0., 2., 2., 2., 2., 2., 0., 0., 0., 0., 0., 0., - ); - } - if y == 0 { - col = sprrow( - x, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., - ); - } - - col = select((x as f32).rem_euclid(8.0) as i32, col); - if col == 1.0 { - self.frag_color = rgb(249, 58, 28); // mouth color (red) - } else if col == 2.0 { - self.frag_color = rgb(222, 128, 55); // brown - } else if col == 3.0 { - self.frag_color = rgb(249, 214, 145); // light yellow - } - } - - fn get_level_pixel(&self, frag_coord: Vec2) -> Vec2 { - // Get the current game pixel - // (Each game pixel is two screen pixels) - // (or four, if the screen is larger) - let mut x: f32 = frag_coord.x / 2.0; - let mut y: f32 = frag_coord.y / 2.0; - - if self.inputs.resolution.y >= 640.0 { - x /= 2.0; - y /= 2.0; - } - - if self.inputs.resolution.y < 200.0 { - x *= 2.0; - y *= 2.0; - } - - vec2(x, y) - } - - fn get_level_bounds(&self) -> Vec2 { - // same logic as getLevelPixel, but returns the boundaries of the screen - - let mut x: f32 = self.inputs.resolution.x / 2.0; - let mut y: f32 = self.inputs.resolution.y / 2.0; - - if self.inputs.resolution.y >= 640.0 { - x /= 2.0; - y /= 2.0; - } - - if self.inputs.resolution.y < 200.0 { - x *= 2.0; - y *= 2.0; - } - - vec2(x, y) - } - - fn draw_ground(&mut self, co: Vec2) { - self.draw_horz_rect(co.y, 0.0, 31.0, rgb(221, 216, 148)); - self.draw_horz_rect(co.y, 31.0, 32.0, rgb(208, 167, 84)); // shadow below the green sprites - } - - fn draw_green_stripes(&mut self, co: Vec2) { - let f: i32 = (self.inputs.time * 60.0).rem_euclid(6.0) as i32; - - self.draw_horz_rect(co.y, 32.0, 33.0, rgb(86, 126, 41)); // shadow blow - - let min_y: f32 = 33.0; - let height: f32 = 6.0; - - let dark_green: Vec4 = rgb(117, 189, 58); - let light_green: Vec4 = rgb(158, 228, 97); - - // draw diagonal stripes, and animate them - if (co.y >= min_y) && (co.y < min_y + height) { - let y_pos: f32 = co.y - min_y - f as f32; - let x_pos: f32 = (co.x - y_pos).rem_euclid(height); - - if x_pos >= height / 2.0 { - self.frag_color = dark_green; - } else { - self.frag_color = light_green; - } - } - - self.draw_horz_rect(co.y, 37.0, 38.0, rgb(228, 250, 145)); // shadow highlight above - self.draw_horz_rect(co.y, 38.0, 39.0, rgb(84, 56, 71)); // black separator - } - - fn draw_tile(&mut self, type_: i32, tile_corner: Vec2, co: Vec2) { - if (co.x < tile_corner.x) - || (co.x > (tile_corner.x + 16.0)) - || (co.y < tile_corner.y) - || (co.y > (tile_corner.y + 16.0)) - { - return; - } - - let mod_x: i32 = (co.x - tile_corner.x).rem_euclid(16.0) as i32; - let mod_y: i32 = (co.y - tile_corner.y).rem_euclid(16.0) as i32; - - if type_ == 0 { - self.draw_low_bush(mod_x, mod_y); - } else if type_ == 1 { - self.draw_high_bush(mod_x, mod_y); - } else if type_ == 2 { - self.draw_cloud(mod_x, mod_y); - } else if type_ == 3 { - self.draw_bird_f0(mod_x, mod_y); - } else if type_ == 4 { - self.draw_bird_f1(mod_x, mod_y); - } else if type_ == 5 { - self.draw_bird_f2(mod_x, mod_y); - } - } - - fn draw_vert_line(&mut self, co: Vec2, x_pos: f32, y_start: f32, y_end: f32, color: Vec4) { - if (co.x >= x_pos) && (co.x < (x_pos + 1.0)) && (co.y >= y_start) && (co.y < y_end) { - self.frag_color = color; - } - } - - fn draw_horz_line(&mut self, co: Vec2, y_pos: f32, x_start: f32, x_end: f32, color: Vec4) { - if (co.y >= y_pos) && (co.y < (y_pos + 1.0)) && (co.x >= x_start) && (co.x < x_end) { - self.frag_color = color; - } - } - - fn draw_horz_gradient_rect( - &mut self, - co: Vec2, - bottom_left: Vec2, - top_right: Vec2, - left_color: Vec4, - right_color: Vec4, - ) { - if (co.x < bottom_left.x) - || (co.y < bottom_left.y) - || (co.x > top_right.x) - || (co.y > top_right.y) - { - return; - } - - let distance_ratio: f32 = (co.x - bottom_left.x) / (top_right.x - bottom_left.x); - - self.frag_color = (1.0 - distance_ratio) * left_color + distance_ratio * right_color; - } - - fn draw_bottom_pipe(&mut self, co: Vec2, x_pos: f32, height: f32) { - if (co.x < x_pos) - || (co.x > (x_pos + PIPE_WIDTH)) - || (co.y < PIPE_BOTTOM) - || (co.y > (PIPE_BOTTOM + height)) - { - return; - } - - // draw the bottom part of the pipe - // outlines - let bottom_part_end: f32 = PIPE_BOTTOM - PIPE_HOLE_HEIGHT + height; - self.draw_vert_line( - co, - x_pos + 1.0, - PIPE_BOTTOM, - bottom_part_end, - PIPE_OUTLINE_COLOR, - ); - self.draw_vert_line( - co, - x_pos + PIPE_WIDTH - 2.0, - PIPE_WIDTH, - bottom_part_end, - PIPE_OUTLINE_COLOR, - ); - - // gradient fills - self.draw_horz_gradient_rect( - co, - vec2(x_pos + 2.0, PIPE_BOTTOM), - vec2(x_pos + 10.0, bottom_part_end), - rgb(133, 168, 75), - rgb(228, 250, 145), - ); - self.draw_horz_gradient_rect( - co, - vec2(x_pos + 10.0, PIPE_BOTTOM), - vec2(x_pos + 20.0, bottom_part_end), - rgb(228, 250, 145), - rgb(86, 126, 41), - ); - self.draw_horz_gradient_rect( - co, - vec2(x_pos + 20.0, PIPE_BOTTOM), - vec2(x_pos + 24.0, bottom_part_end), - rgb(86, 126, 41), - rgb(86, 126, 41), - ); - - // shadows - self.draw_horz_line( - co, - bottom_part_end - 1.0, - x_pos + 2.0, - x_pos + PIPE_WIDTH - 2.0, - rgb(86, 126, 41), - ); - - // draw the pipe opening - // outlines - self.draw_vert_line( - co, - x_pos, - bottom_part_end, - bottom_part_end + PIPE_HOLE_HEIGHT, - PIPE_OUTLINE_COLOR, - ); - self.draw_vert_line( - co, - x_pos + PIPE_WIDTH - 1.0, - bottom_part_end, - bottom_part_end + PIPE_HOLE_HEIGHT, - PIPE_OUTLINE_COLOR, - ); - self.draw_horz_line( - co, - bottom_part_end, - x_pos, - x_pos + PIPE_WIDTH - 1.0, - PIPE_OUTLINE_COLOR, - ); - self.draw_horz_line( - co, - bottom_part_end + PIPE_HOLE_HEIGHT - 1.0, - x_pos, - x_pos + PIPE_WIDTH - 1.0, - PIPE_OUTLINE_COLOR, - ); - - // gradient fills - let gradient_bottom: f32 = bottom_part_end + 1.0; - let gradient_top: f32 = bottom_part_end + PIPE_HOLE_HEIGHT - 1.0; - self.draw_horz_gradient_rect( - co, - vec2(x_pos + 1.0, gradient_bottom), - vec2(x_pos + 5.0, gradient_top), - rgb(221, 234, 131), - rgb(228, 250, 145), - ); - self.draw_horz_gradient_rect( - co, - vec2(x_pos + 5.0, gradient_bottom), - vec2(x_pos + 22.0, gradient_top), - rgb(228, 250, 145), - rgb(86, 126, 41), - ); - self.draw_horz_gradient_rect( - co, - vec2(x_pos + 22.0, gradient_bottom), - vec2(x_pos + 25.0, gradient_top), - rgb(86, 126, 41), - rgb(86, 126, 41), - ); - - // shadows - self.draw_horz_line( - co, - gradient_bottom, - x_pos + 1.0, - x_pos + 25.0, - rgb(86, 126, 41), - ); - self.draw_horz_line( - co, - gradient_top - 1.0, - x_pos + 1.0, - x_pos + 25.0, - rgb(122, 158, 67), - ); - } - - fn draw_top_pipe(&mut self, co: Vec2, x_pos: f32, height: f32) { - let bounds: Vec2 = self.get_level_bounds(); - - if (co.x < x_pos) - || (co.x > (x_pos + PIPE_WIDTH)) - || (co.y < (bounds.y - height)) - || (co.y > bounds.y) - { - return; - } - - // draw the bottom part of the pipe - // outlines - let bottom_part_end: f32 = bounds.y + PIPE_HOLE_HEIGHT - height; - self.draw_vert_line( - co, - x_pos + 1.0, - bottom_part_end, - bounds.y, - PIPE_OUTLINE_COLOR, - ); - self.draw_vert_line( - co, - x_pos + PIPE_WIDTH - 2.0, - bottom_part_end, - bounds.y, - PIPE_OUTLINE_COLOR, - ); - - // gradient fills - self.draw_horz_gradient_rect( - co, - vec2(x_pos + 2.0, bottom_part_end), - vec2(x_pos + 10.0, bounds.y), - rgb(133, 168, 75), - rgb(228, 250, 145), - ); - self.draw_horz_gradient_rect( - co, - vec2(x_pos + 10.0, bottom_part_end), - vec2(x_pos + 20.0, bounds.y), - rgb(228, 250, 145), - rgb(86, 126, 41), - ); - self.draw_horz_gradient_rect( - co, - vec2(x_pos + 20.0, bottom_part_end), - vec2(x_pos + 24.0, bounds.y), - rgb(86, 126, 41), - rgb(86, 126, 41), - ); - - // shadows - self.draw_horz_line( - co, - bottom_part_end + 1.0, - x_pos + 2.0, - x_pos + PIPE_WIDTH - 2.0, - rgb(86, 126, 41), - ); - - // draw the pipe opening - // outlines - self.draw_vert_line( - co, - x_pos, - bottom_part_end - PIPE_HOLE_HEIGHT, - bottom_part_end, - PIPE_OUTLINE_COLOR, - ); - self.draw_vert_line( - co, - x_pos + PIPE_WIDTH - 1.0, - bottom_part_end - PIPE_HOLE_HEIGHT, - bottom_part_end, - PIPE_OUTLINE_COLOR, - ); - self.draw_horz_line( - co, - bottom_part_end, - x_pos, - x_pos + PIPE_WIDTH, - PIPE_OUTLINE_COLOR, - ); - self.draw_horz_line( - co, - bottom_part_end - PIPE_HOLE_HEIGHT, - x_pos, - x_pos + PIPE_WIDTH - 1.0, - PIPE_OUTLINE_COLOR, - ); - - // gradient fills - let gradient_bottom: f32 = bottom_part_end - PIPE_HOLE_HEIGHT + 1.0; - let gradient_top: f32 = bottom_part_end; - self.draw_horz_gradient_rect( - co, - vec2(x_pos + 1.0, gradient_bottom), - vec2(x_pos + 5.0, gradient_top), - rgb(221, 234, 131), - rgb(228, 250, 145), - ); - self.draw_horz_gradient_rect( - co, - vec2(x_pos + 5.0, gradient_bottom), - vec2(x_pos + 22.0, gradient_top), - rgb(228, 250, 145), - rgb(86, 126, 41), - ); - self.draw_horz_gradient_rect( - co, - vec2(x_pos + 22.0, gradient_bottom), - vec2(x_pos + 25.0, gradient_top), - rgb(86, 126, 41), - rgb(86, 126, 41), - ); - - // shadows - self.draw_horz_line( - co, - gradient_bottom, - x_pos + 1.0, - x_pos + 25.0, - rgb(122, 158, 67), - ); - self.draw_horz_line( - co, - gradient_top - 1.0, - x_pos + 1.0, - x_pos + 25.0, - rgb(86, 126, 41), - ); - } - - fn draw_bush_group(&mut self, mut bottom_corner: Vec2, co: Vec2) { - self.draw_tile(0, bottom_corner, co); - bottom_corner.x += 13.0; - - self.draw_tile(1, bottom_corner, co); - bottom_corner.x += 13.0; - - self.draw_tile(0, bottom_corner, co); - } - - fn draw_bushes(&mut self, co: Vec2) { - self.draw_horz_rect(co.y, 39.0, 70.0, rgb(100, 224, 117)); - - for i in 0..20 { - let x_offset: f32 = i as f32 * 45.0; - self.draw_bush_group(vec2(x_offset, 70.0), co); - self.draw_bush_group(vec2(x_offset + 7.0, 68.0), co); - self.draw_bush_group(vec2(x_offset - 16.0, 65.0), co); - } - } - - fn draw_clouds(&mut self, co: Vec2) { - for i in 0..20 { - let x_offset: f32 = i as f32 * 40.0; - self.draw_tile(2, vec2(x_offset, 95.0), co); - self.draw_tile(2, vec2(x_offset + 14.0, 91.0), co); - self.draw_tile(2, vec2(x_offset + 28.0, 93.0), co); - } - - self.draw_horz_rect(co.y, 70.0, 95.0, rgb(233, 251, 218)); - } - - fn draw_pipe_pair(&mut self, co: Vec2, x_pos: f32, bottom_pipe_height: f32) { - let bounds: Vec2 = self.get_level_bounds(); - let top_pipe_height: f32 = - bounds.y - (VERT_PIPE_DISTANCE + PIPE_BOTTOM + bottom_pipe_height); - - self.draw_bottom_pipe(co, x_pos, bottom_pipe_height); - self.draw_top_pipe(co, x_pos, top_pipe_height); - } - - fn draw_pipes(&mut self, co: Vec2) { - // calculate the starting position of the pipes according to the current frame - let animation_cycle_length: f32 = HORZ_PIPE_DISTANCE * PIPE_PER_CYCLE; // the number of frames after which the animation should repeat itself - let f: i32 = (self.inputs.time * 60.0).rem_euclid(animation_cycle_length) as i32; - let mut x_pos: f32 = -f as f32; - - let center: f32 = (PIPE_MAX + PIPE_MIN) / 2.0; - let half_top: f32 = (center + PIPE_MAX) / 2.0; - let half_bottom: f32 = (center + PIPE_MIN) / 2.0; - - for i in 0..12 { - let mut y_pos: f32 = center; - let cycle: i32 = (i as f32).rem_euclid(8.0) as i32; - - if (cycle == 1) || (cycle == 3) { - y_pos = half_top; - } else if cycle == 2 { - y_pos = PIPE_MAX; - } else if (cycle == 5) || (cycle == 7) { - y_pos = half_bottom; - } else if cycle == 6 { - y_pos = PIPE_MIN; - } - - self.draw_pipe_pair(co, x_pos, y_pos); - x_pos += HORZ_PIPE_DISTANCE; - } - } - - fn draw_bird(&mut self, co: Vec2) { - let animation_cycle_length: f32 = HORZ_PIPE_DISTANCE * PIPE_PER_CYCLE; // the number of frames after which the animation should repeat itself - let cycle_frame: i32 = (self.inputs.time * 60.0).rem_euclid(animation_cycle_length) as i32; - let f_cycle_frame: f32 = cycle_frame as f32; - - let start_pos: f32 = 110.0; - let speed: f32 = 2.88; - let updown_delta: f32 = 0.16; - let acceleration: f32 = -0.0975; - let jump_frame: f32 = (self.inputs.time * 60.0).rem_euclid(30.0) as i32 as f32; - let horz_dist: i32 = HORZ_PIPE_DISTANCE as i32; - - // calculate the "jumping" effect on the Y axis. - // Using equations of motion, const acceleration: x = x0 + v0*t + 1/2at^2 - let mut y_pos: f32 = start_pos + speed * jump_frame + acceleration * jump_frame.powf(2.0); - - let speed_delta: f32 = updown_delta * f_cycle_frame.rem_euclid(HORZ_PIPE_DISTANCE); - let mut prev_up_cycles: i32 = 0; - let mut prev_down_cycles: i32 = 0; - - // count the number of pipes we've already passed. - // for each such pipe, we deduce if we went "up" or "down" in Y - let cycle_count: i32 = (f_cycle_frame / HORZ_PIPE_DISTANCE) as i32; - - for i in 0..10 { - if i <= cycle_count { - if i == 1 { - prev_up_cycles += 1; - } - - if (i >= 2) && (i < 6) { - prev_down_cycles += 1; - } - if i >= 6 { - prev_up_cycles += 1; - } - } - } - - // add up/down delta from all the previous pipes - y_pos += ((prev_up_cycles - prev_down_cycles) as f32) * HORZ_PIPE_DISTANCE * updown_delta; - - // calculate the up/down delta for the current two pipes, and add it to the previous result - if ((cycle_frame >= 0) && (cycle_frame < horz_dist)) - || ((cycle_frame >= 5 * horz_dist) && (cycle_frame < 9 * horz_dist)) - { - y_pos += speed_delta; - } else { - y_pos -= speed_delta; - } - - let anim_frame: i32 = (self.inputs.time * 7.0).rem_euclid(3.0) as i32; - if anim_frame == 0 { - self.draw_tile(3, vec2(105.0, y_pos as i32 as f32), co); - } - if anim_frame == 1 { - self.draw_tile(4, vec2(105.0, y_pos as i32 as f32), co); - } - if anim_frame == 2 { - self.draw_tile(5, vec2(105.0, y_pos as i32 as f32), co); - } - } - - pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { - let level_pixel: Vec2 = self.get_level_pixel(frag_coord); - - self.frag_color = rgb(113, 197, 207); // draw the blue sky background - - self.draw_ground(level_pixel); - self.draw_green_stripes(level_pixel); - self.draw_clouds(level_pixel); - self.draw_bushes(level_pixel); - self.draw_pipes(level_pixel); - self.draw_bird(level_pixel); - - *frag_color = self.frag_color; - } -} diff --git a/shaders/src/galaxy_of_universes.rs b/shaders/src/galaxy_of_universes.rs deleted file mode 100644 index 5a2e100..0000000 --- a/shaders/src/galaxy_of_universes.rs +++ /dev/null @@ -1,71 +0,0 @@ -//! Ported to Rust from -//! -//! Original comment: -//! ```glsl -//! // https://www.shadertoy.com/view/MdXSzS -//! // The Big Bang - just a small explosion somewhere in a massive Galaxy of Universes. -//! // Outside of this there's a massive galaxy of 'Galaxy of Universes'... etc etc. :D -//! -//! // To fake a perspective it takes advantage of the screen being wider than it is tall. -//! ``` - -use shared::*; -use spirv_std::glam::{vec3, Mat2, Vec2, Vec3, Vec3Swizzles, Vec4}; - -// Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but -// we tie #[no_std] above to the same condition, so it's fine. -#[cfg(target_arch = "spirv")] -use spirv_std::num_traits::Float; - -pub struct Inputs { - pub resolution: Vec3, - pub time: f32, -} - -impl Inputs { - pub fn main_image(&self, frag_color: &mut Vec4, frag_coord: Vec2) { - let uv: Vec2 = (frag_coord / self.resolution.xy()) - Vec2::splat(0.5); - let t: f32 = self.time * 0.1 - + ((0.25 + 0.05 * (self.time * 0.1).sin()) / (uv.length() + 0.07)) * 2.2; - let si: f32 = t.sin(); - let co: f32 = t.cos(); - let ma: Mat2 = Mat2::from_cols_array(&[co, si, -si, co]); - - let mut v1: f32 = 0.0; - let mut v2: f32 = 0.0; - let mut v3: f32 = 0.0; - - let mut s: f32 = 0.0; - - for _ in 0..90 { - let mut p: Vec3 = s * uv.extend(0.0); - p = (ma.transpose() * p.xy()).extend(p.z); - p += vec3(0.22, 0.3, s - 1.5 - (self.time * 0.13).sin() * 0.1); - for _ in 0..8 { - p = p.abs() / p.dot(p) - Vec3::splat(0.659); - } - v1 += p.dot(p) * 0.0015 * (1.8 + ((uv * 13.0).length() + 0.5 - self.time * 0.2).sin()); - v2 += p.dot(p) * 0.0013 * (1.5 + ((uv * 14.5).length() + 1.2 - self.time * 0.3).sin()); - v3 += (p.xy() * 10.0).length() * 0.0003; - s += 0.035; - } - - let len: f32 = uv.length(); - v1 *= smoothstep(0.7, 0.0, len); - v2 *= smoothstep(0.5, 0.0, len); - v3 *= smoothstep(0.9, 0.0, len); - - let col: Vec3 = vec3( - v3 * (1.5 + (self.time * 0.2).sin() * 0.4), - (v1 + v3) * 0.3, - v2, - ) + Vec3::splat(smoothstep(0.2, 0.0, len) * 0.85) - + Vec3::splat(smoothstep(0.0, 0.6, v3) * 0.3); - - *frag_color = col - .abs() - .powf_vec(Vec3::splat(1.2)) - .min(Vec3::splat(1.0)) - .extend(1.0); - } -} diff --git a/shaders/src/geodesic_tiling.rs b/shaders/src/geodesic_tiling.rs deleted file mode 100644 index 36d96b8..0000000 --- a/shaders/src/geodesic_tiling.rs +++ /dev/null @@ -1,798 +0,0 @@ -//! Ported to Rust from - -use shared::*; -use spirv_std::glam::{mat2, vec2, vec3, Mat2, Mat3, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles}; - -// Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but -// we tie #[no_std] above to the same condition, so it's fine. -#[cfg(target_arch = "spirv")] -use spirv_std::num_traits::Float; - -pub struct Inputs { - pub resolution: Vec3, - pub time: f32, - pub mouse: Vec4, -} - -pub struct State { - inputs: Inputs, - - face_plane: Vec3, - u_plane: Vec3, - v_plane: Vec3, - - nc: Vec3, - pab: Vec3, - pbc: Vec3, - pca: Vec3, - - time: f32, -} - -impl State { - pub fn new(inputs: Inputs) -> State { - State { - inputs, - face_plane: Vec3::ZERO, - u_plane: Vec3::ZERO, - v_plane: Vec3::ZERO, - nc: Vec3::ZERO, - pab: Vec3::ZERO, - pbc: Vec3::ZERO, - pca: Vec3::ZERO, - time: 0.0, - } - } -} - -const MODEL_ROTATION: Vec2 = vec2(0.3, 0.25); -const CAMERA_ROTATION: Vec2 = vec2(0.5, 0.5); - -// 0: Defaults -// 1: Model -// 2: Camera -const MOUSE_CONTROL: i32 = 1; - -const DEBUG: bool = false; - -// 1, 2, or 3 -const LOOP: usize = 0; - -// -------------------------------------------------------- -// HG_SDF -// https://www.shadertoy.com/view/Xs3GRB -// -------------------------------------------------------- - -fn p_r(p: &mut Vec2, a: f32) { - *p = a.cos() * *p + a.sin() * vec2(p.y, -p.x); -} - -fn p_reflect(p: &mut Vec3, plane_normal: Vec3, offset: f32) -> f32 { - let t: f32 = p.dot(plane_normal) + offset; - if t < 0.0 { - *p = *p - (2. * t) * plane_normal; - } - t.sign_gl() -} - -fn smax(a: f32, b: f32, r: f32) -> f32 { - let m: f32 = a.max(b); - if (-a < r) && (-b < r) { - m.max(-(r - ((r + a) * (r + a) + (r + b) * (r + b)).sqrt())) - } else { - m - } -} - -// -------------------------------------------------------- -// Icosahedron domain mirroring -// Adapted from knighty https://www.shadertoy.com/view/MsKGzw -// -------------------------------------------------------- - -use core::f32::consts::PI; - -const TYPE: i32 = 5; - -impl State { - fn init_icosahedron(&mut self) { - //setup folding planes and vertex - let cospin: f32 = (PI / TYPE as f32).cos(); - let scospin: f32 = (0.75 - cospin * cospin).sqrt(); - self.nc = vec3(-0.5, -cospin, scospin); //3rd folding plane. The two others are xz and yz planes - self.pbc = vec3(scospin, 0., 0.5); //No normalization in order to have 'barycentric' coordinates work evenly - self.pca = vec3(0., scospin, cospin); - self.pbc = self.pbc.normalize(); - self.pca = self.pca.normalize(); //for slightly better DE. In reality it's not necesary to apply normalization :) - self.pab = vec3(0.0, 0.0, 1.0); - - self.face_plane = self.pca; - self.u_plane = vec3(1.0, 0.0, 0.0).cross(self.face_plane); - self.v_plane = vec3(1.0, 0.0, 0.0); - } - - fn p_mod_icosahedron(&self, p: &mut Vec3) { - *p = p.abs(); - p_reflect(p, self.nc, 0.0); - *p = p.xy().abs().extend(p.z); - p_reflect(p, self.nc, 0.0); - *p = p.xy().abs().extend(p.z); - p_reflect(p, self.nc, 0.0); - } -} - -// -------------------------------------------------------- -// Triangle tiling -// Adapted from mattz https://www.shadertoy.com/view/4d2GzV -// -------------------------------------------------------- - -const SQRT3: f32 = 1.7320508075688772; -const I3: f32 = 0.5773502691896258; - -const CART2HEX: Mat2 = mat2(vec2(1.0, 0.0), vec2(I3, 2.0 * I3)); -const HEX2CART: Mat2 = mat2(vec2(1.0, 0.0), vec2(-0.5, 0.5 * SQRT3)); - -const _PHI: f32 = 1.618033988749895; -const _TAU: f32 = 6.283185307179586; - -struct TriPoints { - a: Vec2, - b: Vec2, - c: Vec2, - center: Vec2, - ab: Vec2, - bc: Vec2, - ca: Vec2, -} - -fn closest_tri_points(p: Vec2) -> TriPoints { - let p_tri: Vec2 = CART2HEX * p; - let pi: Vec2 = p_tri.floor(); - let pf: Vec2 = p_tri.fract_gl(); - - let split1: f32 = pf.y.step(pf.x); - let split2: f32 = pf.x.step(pf.y); - - let mut a: Vec2 = vec2(split1, 1.0); - let mut b: Vec2 = vec2(1.0, split2); - let mut c: Vec2 = vec2(0.0, 0.0); - - a += pi; - b += pi; - c += pi; - - a = HEX2CART * a; - b = HEX2CART * b; - c = HEX2CART * c; - - let center: Vec2 = (a + b + c) / 3.; - - let ab: Vec2 = (a + b) / 2.; - let bc: Vec2 = (b + c) / 2.; - let ca: Vec2 = (c + a) / 2.; - - TriPoints { - a, - b, - c, - center, - ab, - bc, - ca, - } -} - -// -------------------------------------------------------- -// Geodesic tiling -// -------------------------------------------------------- - -struct TriPoints3D { - a: Vec3, - b: Vec3, - c: Vec3, - center: Vec3, - ab: Vec3, - bc: Vec3, - ca: Vec3, -} - -fn intersection(n: Vec3, plane_normal: Vec3, plane_offset: f32) -> Vec3 { - let denominator: f32 = plane_normal.dot(n); - let t: f32 = (Vec3::ZERO.dot(plane_normal) + plane_offset) / -denominator; - n * t -} - -//// Edge length of an icosahedron with an inscribed sphere of radius of 1 -//float edgeLength = 1. / ((sqrt(3.) / 12.) * (3. + sqrt(5.))); -//// Inner radius of the icosahedron's face -//float faceRadius = (1./6.) * sqrt(3.) * edgeLength; -const FACE_RADIUS: f32 = 0.3819660112501051; - -impl State { - // 2D coordinates on the icosahedron face - fn icosahedron_face_coordinates(&self, p: Vec3) -> Vec2 { - let pn: Vec3 = p.normalize(); - let i: Vec3 = intersection(pn, self.face_plane, -1.0); - vec2(i.dot(self.u_plane), i.dot(self.v_plane)) - } - - // Project 2D icosahedron face coordinates onto a sphere - fn face_to_sphere(&self, face_point: Vec2) -> Vec3 { - (self.face_plane + (self.u_plane * face_point.x) + (self.v_plane * face_point.y)) - .normalize() - } - - fn geodesic_tri_points(&self, p: Vec3, subdivisions: f32) -> TriPoints3D { - // Get 2D cartesian coordiantes on that face - let uv: Vec2 = self.icosahedron_face_coordinates(p); - - // Get points on the nearest triangle tile - let uv_scale: f32 = subdivisions / FACE_RADIUS / 2.0; - let points: TriPoints = closest_tri_points(uv * uv_scale); - - // Project 2D triangle coordinates onto a sphere - let a: Vec3 = self.face_to_sphere(points.a / uv_scale); - let b: Vec3 = self.face_to_sphere(points.b / uv_scale); - let c: Vec3 = self.face_to_sphere(points.c / uv_scale); - let center: Vec3 = self.face_to_sphere(points.center / uv_scale); - let ab: Vec3 = self.face_to_sphere(points.ab / uv_scale); - let bc: Vec3 = self.face_to_sphere(points.bc / uv_scale); - let ca: Vec3 = self.face_to_sphere(points.ca / uv_scale); - - TriPoints3D { - a, - b, - c, - center, - ab, - bc, - ca, - } - } -} - -// -------------------------------------------------------- -// Spectrum colour palette -// IQ https://www.shadertoy.com/view/ll2GD3 -// -------------------------------------------------------- - -fn pal(t: f32, a: Vec3, b: Vec3, c: Vec3, d: Vec3) -> Vec3 { - a + b * (6.28318 * (c * t + d)).cos() -} - -fn spectrum(n: f32) -> Vec3 { - pal( - n, - vec3(0.5, 0.5, 0.5), - vec3(0.5, 0.5, 0.5), - vec3(1.0, 1.0, 1.0), - vec3(0.0, 0.33, 0.67), - ) -} - -// -------------------------------------------------------- -// Model/Camera Rotation -// -------------------------------------------------------- - -fn spherical_matrix(theta: f32, phi: f32) -> Mat3 { - let cx: f32 = theta.cos(); - let cy: f32 = phi.cos(); - let sx: f32 = theta.sin(); - let sy: f32 = phi.sin(); - Mat3::from_cols_array(&[cy, -sy * -sx, -sy * cx, 0.0, cx, sx, sy, cy * -sx, cy * cx]) -} - -impl State { - fn mouse_rotation(&self, enable: bool, mut xy: Vec2) -> Mat3 { - if enable { - let mouse: Vec2 = self.inputs.mouse.xy() / self.inputs.resolution.xy(); - - if mouse.x != 0. && mouse.y != 0. { - xy.x = mouse.x; - xy.y = mouse.y; - } - } - let rx: f32; - let ry: f32; - - rx = (xy.y + 0.5) * PI; - ry = (-xy.x) * 2.0 * PI; - - spherical_matrix(rx, ry) - } - - fn model_rotation(&self) -> Mat3 { - self.mouse_rotation(MOUSE_CONTROL == 1, MODEL_ROTATION) - } - - fn camera_rotation(&self) -> Mat3 { - self.mouse_rotation(MOUSE_CONTROL == 2, CAMERA_ROTATION) - } -} - -// -------------------------------------------------------- -// Animation -// -------------------------------------------------------- - -const SCENE_DURATION: f32 = 6.0; -const CROSSFADE_DURATION: f32 = 2.0; - -struct HexSpec { - round_top: f32, - round_corner: f32, - height: f32, - thickness: f32, - gap: f32, -} - -fn new_hex_spec(subdivisions: f32) -> HexSpec { - HexSpec { - round_top: 0.05 / subdivisions, - round_corner: 0.1 / subdivisions, - height: 2.0, - thickness: 2.0, - gap: 0.005, - } -} - -impl State { - // Animation 1 - - fn anim_subdivisions1(&self) -> f32 { - mix(2.4, 3.4, (self.time * PI).cos() * 0.5 + 0.5) - } - - fn anim_hex1(&self, hex_center: Vec3, subdivisions: f32) -> HexSpec { - let mut spec: HexSpec = new_hex_spec(subdivisions); - - let mut offset: f32 = self.time * 3. * PI; - offset -= subdivisions; - let mut blend: f32 = hex_center.dot(self.pca); - blend = (blend * 30. + offset).cos() * 0.5 + 0.5; - spec.height = mix(1.75, 2., blend); - - spec.thickness = spec.height; - - spec - } - // Animation 2 - - fn anim_subdivisions2(&self) -> f32 { - mix(1., 2.3, (self.time * PI / 2.).sin() * 0.5 + 0.5) - } - - fn anim_hex2(&self, hex_center: Vec3, subdivisions: f32) -> HexSpec { - let mut spec: HexSpec = new_hex_spec(subdivisions); - - let blend: f32 = hex_center.y; - spec.height = mix(1.6, 2., (blend * 10. + self.time * PI).sin() * 0.5 + 0.5); - - spec.round_top = 0.02 / subdivisions; - spec.round_corner = 0.09 / subdivisions; - spec.thickness = spec.round_top * 4.0; - spec.gap = 0.01; - - spec - } - - // Animation 3 - - fn anim_subdivisions3(&self) -> f32 { - 5.0 - } - - fn anim_hex3(&self, hex_center: Vec3, subdivisions: f32) -> HexSpec { - let mut spec: HexSpec = new_hex_spec(subdivisions); - - let mut blend: f32 = hex_center.dot(self.pab).acos() * 10.0; - blend = (blend + self.time * PI).cos() * 0.5 + 0.5; - spec.gap = mix(0.01, 0.4, blend) / subdivisions; - - spec.thickness = spec.round_top * 2.; - - spec - } -} - -// Transition between animations - -fn sine_in_out(t: f32) -> f32 { - -0.5 * ((PI * t).cos() - 1.0) -} - -impl State { - fn transition_values(&self, a: f32, b: f32, c: f32) -> f32 { - if LOOP != 0 { - if LOOP == 1 { - return a; - } - if LOOP == 2 { - return b; - } - if LOOP == 3 { - return c; - } - } - let t: f32 = self.time / SCENE_DURATION; - let scene: f32 = t.rem_euclid(3.0).floor(); - let mut blend: f32 = t.fract_gl(); - let delay: f32 = (SCENE_DURATION - CROSSFADE_DURATION) / SCENE_DURATION; - blend = (blend - delay).max(0.0) / (1.0 - delay); - blend = sine_in_out(blend); - let ab: f32 = mix(a, b, blend); - let bc: f32 = mix(b, c, blend); - let cd: f32 = mix(c, a, blend); - let mut result: f32 = mix(ab, bc, scene.min(1.0)); - result = mix(result, cd, (scene - 1.0).max(0.0)); - result - } - - fn transition_hex_specs(&self, a: HexSpec, b: HexSpec, c: HexSpec) -> HexSpec { - let round_top: f32 = self.transition_values(a.round_top, b.round_top, c.round_top); - let round_corner: f32 = - self.transition_values(a.round_corner, b.round_corner, c.round_corner); - let height: f32 = self.transition_values(a.height, b.height, c.height); - let thickness: f32 = self.transition_values(a.thickness, b.thickness, c.thickness); - let gap: f32 = self.transition_values(a.gap, b.gap, c.gap); - HexSpec { - round_top, - round_corner, - height, - thickness, - gap, - } - } -} - -// -------------------------------------------------------- -// Modelling -// -------------------------------------------------------- - -const FACE_COLOR: Vec3 = vec3(0.9, 0.9, 1.0); -const BACK_COLOR: Vec3 = vec3(0.1, 0.1, 0.15); -const BACKGROUND_COLOR: Vec3 = vec3(0.0, 0.005, 0.03); - -#[derive(Clone, Copy, Default)] -struct Model { - dist: f32, - albedo: Vec3, - glow: f32, -} - -impl State { - fn hex_model( - &self, - p: Vec3, - hex_center: Vec3, - edge_a: Vec3, - edge_b: Vec3, - spec: HexSpec, - ) -> Model { - let mut d: f32; - - let edge_a_dist: f32 = p.dot(edge_a) + spec.gap; - let edge_b_dist: f32 = p.dot(edge_b) - spec.gap; - let edge_dist: f32 = smax(edge_a_dist, -edge_b_dist, spec.round_corner); - - let outer_dist: f32 = p.length() - spec.height; - d = smax(edge_dist, outer_dist, spec.round_top); - - let inner_dist: f32 = p.length() - spec.height + spec.thickness; - d = smax(d, -inner_dist, spec.round_top); - - let mut color: Vec3; - - let mut face_blend: f32 = (spec.height - p.length()) / spec.thickness; - face_blend = face_blend.clamp(0.0, 1.0); - color = mix(FACE_COLOR, BACK_COLOR, 0.5.step(face_blend)); - - let edge_color: Vec3 = spectrum(hex_center.dot(self.pca) * 5.0 + p.length() + 0.8); - let edge_blend: f32 = smoothstep(-0.04, -0.005, edge_dist); - color = mix(color, edge_color, edge_blend); - - Model { - dist: d, - albedo: color, - glow: edge_blend, - } - } -} - -// checks to see which intersection is closer -fn op_u(m1: Model, m2: Model) -> Model { - if m1.dist < m2.dist { - m1 - } else { - m2 - } -} - -impl State { - fn geodesic_model(&self, mut p: Vec3) -> Model { - self.p_mod_icosahedron(&mut p); - - let subdivisions: f32 = self.transition_values( - self.anim_subdivisions1(), - self.anim_subdivisions2(), - self.anim_subdivisions3(), - ); - let points: TriPoints3D = self.geodesic_tri_points(p, subdivisions); - - let edge_ab: Vec3 = points.center.cross(points.ab).normalize(); - let edge_bc: Vec3 = points.center.cross(points.bc).normalize(); - let edge_ca: Vec3 = points.center.cross(points.ca).normalize(); - - let mut model: Model; - let mut part: Model; - let mut spec: HexSpec; - - spec = self.transition_hex_specs( - self.anim_hex1(points.b, subdivisions), - self.anim_hex2(points.b, subdivisions), - self.anim_hex3(points.b, subdivisions), - ); - part = self.hex_model(p, points.b, edge_ab, edge_bc, spec); - model = part; - - spec = self.transition_hex_specs( - self.anim_hex1(points.c, subdivisions), - self.anim_hex2(points.c, subdivisions), - self.anim_hex3(points.c, subdivisions), - ); - part = self.hex_model(p, points.c, edge_bc, edge_ca, spec); - model = op_u(model, part); - - spec = self.transition_hex_specs( - self.anim_hex1(points.a, subdivisions), - self.anim_hex2(points.a, subdivisions), - self.anim_hex3(points.a, subdivisions), - ); - part = self.hex_model(p, points.a, edge_ca, edge_ab, spec); - model = op_u(model, part); - - model - } - - fn map(&self, mut p: Vec3) -> Model { - let m: Mat3 = self.model_rotation(); - p = m.transpose() * p; - if LOOP == 0 { - p_r(&mut p.xz(), self.time * PI / 16.); - } - let model: Model = self.geodesic_model(p); - model - } -} - -// -------------------------------------------------------- -// LIGHTING -// Adapted from IQ https://www.shadertoy.com/view/Xds3zN -// -------------------------------------------------------- - -fn do_lighting(model: Model, _pos: Vec3, nor: Vec3, _ref: Vec3, rd: Vec3) -> Vec3 { - let light_pos: Vec3 = vec3(0.5, 0.5, -1.0).normalize(); - let back_light_pos: Vec3 = vec3(-0.5, -0.3, 1.0).normalize(); - let ambient_pos: Vec3 = vec3(0.0, 1.0, 0.0); - - let lig: Vec3 = light_pos; - let amb: f32 = ((nor.dot(ambient_pos) + 1.0) / 2.0).clamp(0.0, 1.0); - let dif: f32 = nor.dot(lig).clamp(0.0, 1.0); - let bac: f32 = nor.dot(back_light_pos).clamp(0.0, 1.0).powf(1.5); - let fre: f32 = (1.0 + nor.dot(rd)).clamp(0.0, 1.0).powf(2.0); - - let mut lin: Vec3 = Vec3::ZERO; - lin += 1.20 * dif * Vec3::splat(0.9); - lin += 0.80 * amb * vec3(0.5, 0.7, 0.8); - lin += 0.30 * bac * Vec3::splat(0.25); - lin += 0.20 * fre * Vec3::ONE; - - let albedo: Vec3 = model.albedo; - let col: Vec3 = mix(albedo * lin, albedo, model.glow); - - col -} - -// -------------------------------------------------------- -// Ray Marching -// Adapted from cabbibo https://www.shadertoy.com/view/Xl2XWt -// -------------------------------------------------------- - -const MAX_TRACE_DISTANCE: f32 = 8.0; // max trace distance -const INTERSECTION_PRECISION: f32 = 0.001; // precision of the intersection -const NUM_OF_TRACE_STEPS: i32 = 100; -const FUDGE_FACTOR: f32 = 0.9; // Default is 1, reduce to fix overshoots - -struct CastRay { - origin: Vec3, - direction: Vec3, -} - -struct Ray { - origin: Vec3, - direction: Vec3, - len: f32, -} - -struct Hit { - ray: Ray, - model: Model, - pos: Vec3, - is_background: bool, - normal: Vec3, - color: Vec3, -} - -impl State { - fn calc_normal(&self, pos: Vec3) -> Vec3 { - let eps: Vec3 = vec3(0.001, 0.0, 0.0); - let nor: Vec3 = vec3( - self.map(pos + eps.xyy()).dist - self.map(pos - eps.xyy()).dist, - self.map(pos + eps.yxy()).dist - self.map(pos - eps.yxy()).dist, - self.map(pos + eps.yyx()).dist - self.map(pos - eps.yyx()).dist, - ); - nor.normalize() - } - - fn raymarch(&self, cast_ray: CastRay) -> Hit { - let mut current_dist: f32 = INTERSECTION_PRECISION * 2.0; - let mut model: Model = Model::default(); - - let mut ray: Ray = Ray { - origin: cast_ray.origin, - direction: cast_ray.direction, - len: 0.0, - }; - - for _ in 0..NUM_OF_TRACE_STEPS { - if current_dist < INTERSECTION_PRECISION || ray.len > MAX_TRACE_DISTANCE { - break; - } - model = self.map(ray.origin + ray.direction * ray.len); - current_dist = model.dist; - ray.len += current_dist * FUDGE_FACTOR; - } - - let mut is_background: bool = false; - let mut pos: Vec3 = Vec3::ZERO; - let mut normal: Vec3 = Vec3::ZERO; - let color: Vec3 = Vec3::ZERO; - - if ray.len > MAX_TRACE_DISTANCE { - is_background = true; - } else { - pos = ray.origin + ray.direction * ray.len; - normal = self.calc_normal(pos); - } - - Hit { - ray, - model, - pos, - is_background, - normal, - color, - } - } -} - -// -------------------------------------------------------- -// Rendering -// -------------------------------------------------------- - -fn shade_surface(hit: &mut Hit) { - let mut color: Vec3 = BACKGROUND_COLOR; - - if hit.is_background { - hit.color = color; - return; - } - - let _ref: Vec3 = hit.ray.direction.reflect(hit.normal); - - if DEBUG { - color = hit.normal * 0.5 + Vec3::splat(0.5); - } else { - color = do_lighting(hit.model, hit.pos, hit.normal, _ref, hit.ray.direction); - } - hit.color = color; -} - -fn render(mut hit: Hit) -> Vec3 { - shade_surface(&mut hit); - hit.color -} - -// -------------------------------------------------------- -// Camera -// https://www.shadertoy.com/view/Xl2XWt -// -------------------------------------------------------- - -fn calc_look_at_matrix(ro: Vec3, ta: Vec3, roll: f32) -> Mat3 { - let ww: Vec3 = (ta - ro).normalize(); - let uu: Vec3 = ww.cross(vec3(roll.sin(), roll.cos(), 0.0)).normalize(); - let vv: Vec3 = uu.cross(ww).normalize(); - Mat3::from_cols(uu, vv, ww) -} - -impl State { - fn do_camera( - &self, - cam_pos: &mut Vec3, - cam_tar: &mut Vec3, - cam_roll: &mut f32, - _time: f32, - _mouse: Vec2, - ) { - let dist: f32 = 5.5; - *cam_roll = 0.0; - *cam_tar = vec3(0.0, 0.0, 0.0); - *cam_pos = vec3(0.0, 0.0, -dist); - *cam_pos = self.camera_rotation().transpose() * *cam_pos; - *cam_pos += *cam_tar; - } -} - -// -------------------------------------------------------- -// Gamma -// https://www.shadertoy.com/view/Xds3zN -// -------------------------------------------------------- - -const GAMMA: f32 = 2.2; - -fn gamma(color: Vec3, g: f32) -> Vec3 { - color.powf(g) -} - -fn linear_to_screen(linear_rgb: Vec3) -> Vec3 { - gamma(linear_rgb, 1.0 / GAMMA) -} - -impl State { - pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { - self.time = self.inputs.time; - - if LOOP != 0 { - if LOOP == 1 { - self.time = self.time.rem_euclid(2.0); - } - - if LOOP == 2 { - self.time = self.time.rem_euclid(4.0); - } - - if LOOP == 3 { - self.time = self.time.rem_euclid(2.0); - } - } - - self.init_icosahedron(); - - let p: Vec2 = (-self.inputs.resolution.xy() + 2.0 * frag_coord) / self.inputs.resolution.y; - let m: Vec2 = self.inputs.mouse.xy() / self.inputs.resolution.xy(); - - let mut cam_pos: Vec3 = vec3(0.0, 0.0, 2.0); - let mut cam_tar: Vec3 = vec3(0.0, 0.0, 0.0); - let mut cam_roll: f32 = 0.0; - - // camera movement - self.do_camera(&mut cam_pos, &mut cam_tar, &mut cam_roll, self.time, m); - - // camera matrix - let cam_mat: Mat3 = calc_look_at_matrix(cam_pos, cam_tar, cam_roll); // 0.0 is the camera roll - - // create view ray - let rd: Vec3 = (cam_mat * p.extend(2.0)).normalize(); // 2.0 is the lens length - - let hit: Hit = self.raymarch(CastRay { - origin: cam_pos, - direction: rd, - }); - - let mut color: Vec3 = render(hit); - - if !DEBUG { - color = linear_to_screen(color); - } - - *frag_color = color.extend(1.0); - } -} diff --git a/shaders/src/heart.rs b/shaders/src/heart.rs deleted file mode 100644 index 0706dca..0000000 --- a/shaders/src/heart.rs +++ /dev/null @@ -1,64 +0,0 @@ -//! Ported to Rust from -//! -//! Original comment: -//! ```glsl -//! // Created by inigo quilez - iq/2013 -//! // License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. -//! ``` - -use shared::*; -use spirv_std::glam::{vec2, vec3, Vec2, Vec3, Vec3Swizzles, Vec4}; - -// Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but -// we tie #[no_std] above to the same condition, so it's fine. -#[cfg(target_arch = "spirv")] -use spirv_std::num_traits::Float; - -pub struct Inputs { - pub resolution: Vec3, - pub time: f32, -} - -impl Inputs { - pub fn main_image(&self, frag_color: &mut Vec4, frag_coord: Vec2) { - let mut p: Vec2 = - (2.0 * frag_coord - self.resolution.xy()) / (self.resolution.y.min(self.resolution.x)); - - // background color - let bcol: Vec3 = vec3(1.0, 0.8, 0.7 - 0.07 * p.y) * (1.0 - 0.25 * p.length()); - - // animate - let tt: f32 = self.time.rem_euclid(1.5) / 1.5; - let mut ss: f32 = tt.powf(0.2) * 0.5 + 0.5; - ss = 1.0 + ss * 0.5 * (tt * 6.2831 * 3.0 + p.y * 0.5).sin() * (-tt * 4.0).exp(); - p *= vec2(0.5, 1.5) + ss * vec2(0.5, -0.5); - - // shape - let r: f32; - let d: f32; - - if false { - p *= 0.8; - p.y = -0.1 - p.y * 1.2 + p.x.abs() * (1.0 - p.x.abs()); - r = p.length(); - d = 0.5; - } else { - p.y -= 0.25; - let a: f32 = p.x.atan2(p.y) / 3.141593; - r = p.length(); - let h: f32 = a.abs(); - d = (13.0 * h - 22.0 * h * h + 10.0 * h * h * h) / (6.0 - 5.0 * h); - } - - // color - let mut s: f32 = 0.75 + 0.75 * p.x; - s *= 1.0 - 0.4 * r; - s = 0.3 + 0.7 * s; - s *= 0.5 + 0.5 * (1.0 - (r / d).clamp(0.0, 1.0)).powf(0.1); - let hcol: Vec3 = vec3(1.0, 0.5 * r, 0.3) * s; - - let col: Vec3 = mix(bcol, hcol, smoothstep(-0.01, 0.01, d - r)); - - *frag_color = col.extend(1.0); - } -} diff --git a/shaders/src/lib.rs b/shaders/src/lib.rs index bd959aa..742b387 100644 --- a/shaders/src/lib.rs +++ b/shaders/src/lib.rs @@ -1,250 +1,147 @@ #![cfg_attr(target_arch = "spirv", no_std)] -use shared::*; -use spirv_std::glam::{vec2, vec3, vec4, Vec2, Vec3, Vec4}; -use spirv_std::spirv; - -pub mod a_lot_of_spheres; -pub mod a_question_of_time; -pub mod apollonian; -pub mod atmosphere_system_test; -pub mod bubble_buckey_balls; -pub mod clouds; -pub mod filtering_procedurals; -pub mod flappy_bird; -pub mod galaxy_of_universes; -pub mod geodesic_tiling; -pub mod heart; -pub mod luminescence; -pub mod mandelbrot_smooth; -pub mod miracle_snowflakes; -pub mod morphing; -pub mod moving_square; -pub mod on_off_spikes; -pub mod phantom_star; -pub mod playing_marble; -pub mod protean_clouds; -pub mod raymarching_primitives; -pub mod seascape; -pub mod skyline; -pub mod soft_shadow_variation; -pub mod tileable_water_caustic; -pub mod tokyo; -pub mod two_tweets; -pub mod voxel_pac_man; - -pub trait SampleCube: Copy { - fn sample_cube(self, p: Vec3) -> Vec4; -} +pub mod shader_prelude; +use shader_prelude::*; +pub mod shaders; +pub mod shared_data; -#[derive(Copy, Clone)] -struct ConstantColor { - color: Vec4, -} +// Compute optimal grid layout (rows, cols) for cell count while attempting to keep the aspect ratio close to the provided aspect ratio. +fn optimal_grid(cell_count: usize, aspect: Vec2) -> (usize, usize) { + // Handle edge cases for 0 or 1 cells. + if cell_count == 0 { + return (0, 0); + } + if cell_count == 1 { + return (1, 1); + } -impl SampleCube for ConstantColor { - fn sample_cube(self, _: Vec3) -> Vec4 { - self.color - } -} + // The target aspect ratio (width / height). Add a small epsilon to avoid division by zero. + let target_aspect = aspect.x / (aspect.y + f32::EPSILON); -#[derive(Copy, Clone)] -struct RgbCube { - alpha: f32, - intensity: f32, -} + let mut best_layout = (1, cell_count); + let mut min_aspect_diff = f32::INFINITY; + + // Iterate through all possible row counts from 1 to cell_count. + // This is a simple and robust way to find the global optimum. + for rows in 1..=cell_count { + // Calculate the number of columns needed to fit all cells for the current row count. + // This is equivalent to `ceil(cell_count / rows)`. + let cols = cell_count.div_ceil(rows); -impl SampleCube for RgbCube { - fn sample_cube(self, p: Vec3) -> Vec4 { - (p.abs() * self.intensity).extend(self.alpha) + // The aspect ratio of the current grid layout. + let grid_aspect = cols as f32 / rows as f32; + + // Calculate the difference from the target aspect ratio. + let diff = (grid_aspect - target_aspect).abs(); + + // If this layout is better than the best one we've found so far, update it. + if diff < min_aspect_diff { + min_aspect_diff = diff; + best_layout = (rows, cols); } + } + + best_layout } +#[inline(always)] +#[must_use] pub fn fs(constants: &ShaderConstants, mut frag_coord: Vec2) -> Vec4 { - const COLS: usize = 6; - const ROWS: usize = 5; + let resolution = vec3(constants.width as f32, constants.height as f32, 0.0); + let time = constants.time; + let mut mouse = vec4( + constants.drag_end_x, + constants.drag_end_y, + constants.drag_start_x, + constants.drag_start_y, + ); + if mouse != Vec4::ZERO { + mouse.y = resolution.y - mouse.y; + mouse.w = resolution.y - mouse.w; + } + if constants.mouse_left_pressed != 1 { + mouse.z *= -1.0; + } + if constants.mouse_left_clicked != 1 { + mouse.w *= -1.0; + } - let resolution = vec3( - constants.width as f32 / COLS as f32, - constants.height as f32 / ROWS as f32, - 0.0, - ); - let time = constants.time; - let mut mouse = vec4( - constants.drag_end_x / COLS as f32, - constants.drag_end_y / ROWS as f32, - constants.drag_start_x / COLS as f32, - constants.drag_start_y / ROWS as f32, + frag_coord.x %= resolution.x; + frag_coord.y = resolution.y - frag_coord.y % resolution.y; + + let shader_count = shaders::SHADER_DEFINITIONS.len(); + + let shader_index; + let shader_input: ShaderInput; + let shader_output = &mut ShaderResult { color: Vec4::ZERO }; + + if constants.grid_mode == 0 { + shader_input = ShaderInput { + resolution, + time, + frag_coord, + mouse, + }; + shader_index = constants.shader_to_show as usize; + } else { + // Render all shaders in a grid layout + // ignore shader_to_show + let (rows, cols) = optimal_grid(shader_count, vec2(resolution.x, resolution.y)); + + let cell_width = resolution.x / cols as f32; + let cell_height = resolution.y / rows as f32; + + #[expect(clippy::cast_sign_loss)] + let col = (frag_coord.x / cell_width).floor() as usize; + #[expect(clippy::cast_sign_loss)] + let row = (frag_coord.y / cell_height).floor() as usize; + shader_index = row + col * rows; + + let cell_resolution = vec3(cell_width, cell_height, 0.0); + let cell_frag_coord = vec2( + (col as f32).mul_add(-cell_width, frag_coord.x), + (row as f32).mul_add(-cell_height, frag_coord.y), ); - if mouse != Vec4::ZERO { - mouse.y = resolution.y - mouse.y; - mouse.w = resolution.y - mouse.w; - } - if !(constants.mouse_left_pressed == 1) { - mouse.z *= -1.0; - } - if !(constants.mouse_left_clicked == 1) { - mouse.w *= -1.0; - } + let cell_mouse = mouse / vec4(cols as f32, rows as f32, cols as f32, rows as f32); - let col = (frag_coord.x / resolution.x) as usize; - let row = (frag_coord.y / resolution.y) as usize; - let i = row * COLS + col; - - frag_coord.x %= resolution.x; - frag_coord.y = resolution.y - frag_coord.y % resolution.y; - - let mut color = Vec4::ZERO; - match i { - 0 => two_tweets::Inputs { resolution, time }.main_image(&mut color, frag_coord), - 1 => heart::Inputs { resolution, time }.main_image(&mut color, frag_coord), - 2 => clouds::Inputs { resolution, time }.main_image(&mut color, frag_coord), - 3 => mandelbrot_smooth::Inputs { resolution, time }.main_image(&mut color, frag_coord), - 4 => protean_clouds::State::new(protean_clouds::Inputs { - resolution, - time, - mouse, - }) - .main_image(&mut color, frag_coord), - 5 => tileable_water_caustic::Inputs { resolution, time }.main_image(&mut color, frag_coord), - 6 => apollonian::State::new(apollonian::Inputs { - resolution, - time, - mouse, - }) - .main_image(&mut color, frag_coord), - 7 => phantom_star::Inputs { resolution, time }.main_image(&mut color, frag_coord), - 8 => seascape::Inputs { - resolution, - time, - mouse, - } - .main_image(&mut color, frag_coord), - 9 => playing_marble::Inputs { - resolution, - time, - mouse, - channel0: RgbCube { - alpha: 1.0, - intensity: 1.0, - }, - } - .main_image(&mut color, frag_coord), - 10 => a_lot_of_spheres::Inputs { resolution, time }.main_image(&mut color, frag_coord), - 11 => a_question_of_time::Inputs { - resolution, - time, - mouse, - } - .main_image(&mut color, frag_coord), - 12 => galaxy_of_universes::Inputs { resolution, time }.main_image(&mut color, frag_coord), - 13 => atmosphere_system_test::State::new(atmosphere_system_test::Inputs { - resolution, - time, - mouse, - }) - .main_image(&mut color, frag_coord), - 14 => soft_shadow_variation::Inputs { resolution, time }.main_image(&mut color, frag_coord), - 15 => miracle_snowflakes::State::new(miracle_snowflakes::Inputs { - resolution, - time, - mouse, - }) - .main_image(&mut color, frag_coord), - 16 => morphing::State::new(morphing::Inputs { - resolution, - time, - mouse, - }) - .main_image(&mut color, frag_coord), - 17 => bubble_buckey_balls::State::new(bubble_buckey_balls::Inputs { - resolution, - time, - mouse, - channel0: RgbCube { - alpha: 1.0, - intensity: 0.5, - }, - channel1: ConstantColor { color: Vec4::ONE }, - }) - .main_image(&mut color, frag_coord), - 18 => raymarching_primitives::Inputs { - resolution, - frame: (time * 60.0) as i32, - time, - mouse, - } - .main_image(&mut color, frag_coord), - 19 => moving_square::Inputs { resolution, time }.main_image(&mut color, frag_coord), - 20 => skyline::State::new(skyline::Inputs { - resolution, - time, - mouse, - channel0: RgbCube { - alpha: 1.0, - intensity: 1.0, - }, - }) - .main_image(&mut color, frag_coord), - 21 => filtering_procedurals::Inputs { - resolution, - time, - mouse, - } - .main_image(&mut color, frag_coord), - 22 => geodesic_tiling::State::new(geodesic_tiling::Inputs { - resolution, - time, - mouse, - }) - .main_image(&mut color, frag_coord), - 23 => flappy_bird::State::new(flappy_bird::Inputs { resolution, time }) - .main_image(&mut color, frag_coord), - 24 => { - tokyo::State::new(tokyo::Inputs { resolution, time }).main_image(&mut color, frag_coord) - } - 25 => on_off_spikes::State::new(on_off_spikes::Inputs { - resolution, - time, - mouse, - }) - .main_image(&mut color, frag_coord), - 26 => luminescence::State::new(luminescence::Inputs { - resolution, - time, - mouse, - }) - .main_image(&mut color, frag_coord), - 27 => voxel_pac_man::State::new(voxel_pac_man::Inputs { - resolution, - time, - mouse, - }) - .main_image(&mut color, frag_coord), - _ => {} - } - Vec3::powf(color.truncate(), 2.2).extend(color.w) + shader_input = ShaderInput { + resolution: cell_resolution, + time, + frag_coord: cell_frag_coord, + mouse: cell_mouse, + }; + } + + if shader_index < shader_count { + shaders::render_shader(shader_index as u32, &shader_input, shader_output); + } else { + // If the shader index is out of bounds, just return a default color + shader_output.color = Vec4::new(0.0, 0.0, 0.0, 1.0); + } + + let color = shader_output.color; + Vec3::powf(color.truncate(), 2.2).extend(color.w) } #[allow(unused_attributes)] #[spirv(fragment)] pub fn main_fs( - #[spirv(frag_coord)] in_frag_coord: Vec4, - #[spirv(push_constant)] constants: &ShaderConstants, - output: &mut Vec4, + #[spirv(frag_coord)] in_frag_coord: Vec4, + #[spirv(push_constant)] constants: &ShaderConstants, + output: &mut Vec4, ) { - let frag_coord = vec2(in_frag_coord.x, in_frag_coord.y); - let color = fs(constants, frag_coord); - *output = color; + let frag_coord = vec2(in_frag_coord.x, in_frag_coord.y); + let color = fs(constants, frag_coord); + *output = color; } #[allow(unused_attributes)] #[spirv(vertex)] pub fn main_vs(#[spirv(vertex_index)] vert_idx: i32, #[spirv(position)] builtin_pos: &mut Vec4) { - // Create a "full screen triangle" by mapping the vertex index. - // ported from https://www.saschawillems.de/blog/2016/08/13/vulkan-tutorial-on-rendering-a-fullscreen-quad-without-buffers/ - let uv = vec2(((vert_idx << 1) & 2) as f32, (vert_idx & 2) as f32); - let pos = 2.0 * uv - Vec2::ONE; + // Create a "full screen triangle" by mapping the vertex index. + // ported from https://www.saschawillems.de/blog/2016/08/13/vulkan-tutorial-on-rendering-a-fullscreen-quad-without-buffers/ + let uv = vec2(((vert_idx << 1) & 2) as f32, (vert_idx & 2) as f32); + let pos = 2.0 * uv - Vec2::ONE; - *builtin_pos = pos.extend(0.0).extend(1.0); + *builtin_pos = pos.extend(0.0).extend(1.0); } diff --git a/shaders/src/luminescence.rs b/shaders/src/luminescence.rs deleted file mode 100644 index c0dd9b3..0000000 --- a/shaders/src/luminescence.rs +++ /dev/null @@ -1,604 +0,0 @@ -//! Ported to Rust from -//! -//! Original comment: -//! ```glsl -//! // Luminescence by Martijn Steinrucken aka BigWings - 2017 -//! // Email:countfrolic@gmail.com Twitter:@The_ArtOfCode -//! // License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. -//! -//! // My entry for the monthly challenge (May 2017) on r/proceduralgeneration -//! // Use the mouse to look around. Uncomment the SINGLE define to see one specimen by itself. -//! // Code is a bit of a mess, too lazy to clean up. Hope you like it! -//! -//! // Music by Klaus Lunde -//! // https://soundcloud.com/klauslunde/zebra-tribute -//! -//! // YouTube: The Art of Code -> https://www.youtube.com/channel/UCcAlTqd9zID6aNX3TzwxJXg -//! // Twitter: @The_ArtOfCode -//! ``` - -use shared::*; -use spirv_std::glam::{vec2, vec3, Mat3, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles}; - -// Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but -// we tie #[no_std] above to the same condition, so it's fine. -#[cfg(target_arch = "spirv")] -use spirv_std::num_traits::Float; - -#[derive(Clone, Copy)] -pub struct Inputs { - pub resolution: Vec3, - pub time: f32, - pub mouse: Vec4, -} - -pub struct State { - inputs: Inputs, - - bg: Vec3, // global background color - accent: Vec3, // color of the phosphorecence - - cam: Camera, -} - -impl State { - pub fn new(inputs: Inputs) -> State { - State { - inputs, - - bg: Vec3::ZERO, - accent: Vec3::ZERO, - - cam: Camera::default(), - } - } -} - -const INVERTMOUSE: f32 = -1.0; - -const MAX_STEPS: u32 = 100; -const VOLUME_STEPS: u32 = 8; -const SINGLE: bool = false; -const _MIN_DISTANCE: f32 = 0.1; -const MAX_DISTANCE: f32 = 100.0; -const HIT_DISTANCE: f32 = 0.01; - -fn b(x: f32, y: f32, z: f32, w: f32) -> f32 { - smoothstep(x - z, x + z, w) * smoothstep(y + z, y - z, w) -} -fn sat(x: f32) -> f32 { - x.clamp(0.0, 1.0) -} -fn sin(x: f32) -> f32 { - x.sin() * 0.5 + 0.5 -} - -const _LF: Vec3 = vec3(1.0, 0.0, 0.0); -const UP: Vec3 = vec3(0.0, 1.0, 0.0); -const _FW: Vec3 = vec3(0.0, 0.0, 1.0); - -const _HALF_PI: f32 = 1.570796326794896619; -const PI: f32 = 3.141592653589793238; -const TWO_PI: f32 = 6.283185307179586; - -const ACCENT_COLOR1: Vec3 = vec3(1.0, 0.1, 0.5); -const SECOND_COLOR1: Vec3 = vec3(0.1, 0.5, 1.0); -const ACCENT_COLOR2: Vec3 = vec3(1.0, 0.5, 0.1); -const SECOND_COLOR2: Vec3 = vec3(0.1, 0.5, 0.6); - -fn _n1(x: f32) -> f32 { - (x.sin() * 5346.1764).fract_gl() -} -fn _n2(x: f32, y: f32) -> f32 { - _n1(x + y * 23414.324) -} - -fn n3(mut p: Vec3) -> f32 { - p = (p * 0.3183099 + Vec3::splat(0.1)).fract_gl(); - p *= 17.0; - (p.x * p.y * p.z * (p.x + p.y + p.z)).fract_gl() -} - -#[derive(Clone, Copy, Default)] -struct Ray { - o: Vec3, - d: Vec3, -} - -#[derive(Default)] -struct Camera { - p: Vec3, // the position of the camera - forward: Vec3, // the camera forward vector - left: Vec3, // the camera left vector - up: Vec3, // the camera up vector - - center: Vec3, // the center of the screen, in world coords - i: Vec3, // where the current ray intersects the screen, in world coords - ray: Ray, // the current ray: from cam pos, through current uv projected on screen - look_at: Vec3, // the lookat point - zoom: f32, // the zoom factor -} - -#[derive(Clone, Copy, Default)] -struct De { - // data type used to pass the various bits of information used to shade a de object - d: f32, // final distance to field - m: f32, // material - uv: Vec3, - pump: f32, - - id: Vec3, - pos: Vec3, // the world-space coordinate of the fragment -} - -#[derive(Default)] -struct Rc { - // data type used to handle a repeated coordinate - id: Vec3, // holds the floor'ed coordinate of each cell. Used to identify the cell. - h: Vec3, // half of the size of the cell - p: Vec3, // the repeated coordinate - // c: Vec3; // the center of the cell, world coordinates -} - -fn repeat(pos: Vec3, size: Vec3) -> Rc { - let mut o: Rc = Rc::default(); - o.h = size * 0.5; - o.id = (pos / size).floor(); // used to give a unique id to each cell - - o.p = pos.rem_euclid(size) - o.h; - //o.c = o.id*size+o.h; - - o -} - -impl State { - fn camera_setup(&mut self, uv: Vec2, position: Vec3, look_at: Vec3, zoom: f32) { - self.cam.p = position; - self.cam.look_at = look_at; - self.cam.forward = (self.cam.look_at - self.cam.p).normalize(); - self.cam.left = UP.cross(self.cam.forward); - self.cam.up = self.cam.forward.cross(self.cam.left); - self.cam.zoom = zoom; - - self.cam.center = self.cam.p + self.cam.forward * self.cam.zoom; - self.cam.i = self.cam.center + self.cam.left * uv.x + self.cam.up * uv.y; - - self.cam.ray.o = self.cam.p; // ray origin = camera position - self.cam.ray.d = (self.cam.i - self.cam.p).normalize(); // ray direction is the vector from the cam pos through the point on the imaginary screen - } -} - -// ============== Functions I borrowed ;) - -// 3 out, 1 in... DAVE HOSKINS -fn n31(p: f32) -> Vec3 { - let mut p3: Vec3 = (Vec3::splat(p) * vec3(0.1031, 0.11369, 0.13787)).fract_gl(); - p3 += Vec3::splat(p3.dot(p3.yzx() + Vec3::splat(19.19))); - vec3( - (p3.x + p3.y) * p3.z, - (p3.x + p3.z) * p3.y, - (p3.y + p3.z) * p3.x, - ) - .fract_gl() -} - -// DE functions from IQ -fn smin(a: f32, b: f32, k: f32) -> f32 { - let h: f32 = (0.5 + 0.5 * (b - a) / k).clamp(0.0, 1.0); - mix(b, a, h) - k * h * (1.0 - h) -} - -fn smax(a: f32, b: f32, k: f32) -> f32 { - let h: f32 = (0.5 + 0.5 * (b - a) / k).clamp(0.0, 1.0); - mix(a, b, h) + k * h * (1.0 - h) -} - -fn sd_sphere(p: Vec3, pos: Vec3, s: f32) -> f32 { - (p - pos).length() - s -} - -// From http://mercury.sexy/hg_sdf -fn p_mod_polar(p: &mut Vec2, repetitions: f32, fix: f32) -> Vec2 { - let angle: f32 = TWO_PI / repetitions; - let mut a: f32 = p.y.atan2(p.x) + angle / 2.; - let r: f32 = p.length(); - let _c: f32 = (a / angle).floor(); - a = a.rem_euclid(angle) - (angle / 2.) * fix; - *p = vec2(a.cos(), a.sin()) * r; - - *p -} - -// ------------------------- - -fn dist(p: Vec2, p0: Vec2, p1: Vec2) -> f32 { - //2d point-line distance - - let v: Vec2 = p1 - p0; - let w: Vec2 = p - p0; - - let c1: f32 = w.dot(v); - let c2: f32 = v.dot(v); - - // before P0 - if c1 <= 0. { - return (p - p0).length(); - } - - let b: f32 = c1 / c2; - let pb: Vec2 = p0 + b * v; - (p - pb).length() -} - -fn _closest_point(ro: Vec3, rd: Vec3, p: Vec3) -> Vec3 { - // returns the closest point on ray r to point p - ro + (p - ro).dot(rd).max(0.0) * rd -} - -fn _ray_ray_ts(ro1: Vec3, rd1: Vec3, ro2: Vec3, rd2: Vec3) -> Vec2 { - // returns the two t's for the closest point between two rays - // ro+rd*t1 = ro2+rd2*t2 - - let d_o: Vec3 = ro2 - ro1; - let c_d: Vec3 = rd1.cross(rd2); - let v: f32 = c_d.dot(c_d); - - let t1: f32 = d_o.cross(rd2).dot(c_d) / v; - let t2: f32 = d_o.cross(rd1).dot(c_d) / v; - vec2(t1, t2) -} - -fn _dist_ray_segment(ro: Vec3, rd: Vec3, p1: Vec3, p2: Vec3) -> f32 { - // returns the distance from ray r to line segment p1-p2 - let rd2: Vec3 = p2 - p1; - let mut t: Vec2 = _ray_ray_ts(ro, rd, p1, rd2); - - t.x = t.x.max(0.0); - t.y = t.y.clamp(0.0, rd2.length()); - - let rp: Vec3 = ro + rd * t.x; - let sp: Vec3 = p1 + rd2 * t.y; - - (rp - sp).length() -} - -fn sph(ro: Vec3, rd: Vec3, pos: Vec3, radius: f32) -> Vec2 { - // does a ray sphere intersection - // returns a vec2 with distance to both intersections - // if both a and b are MAX_DISTANCE then there is no intersection - - let oc: Vec3 = pos - ro; - let l: f32 = rd.dot(oc); - let det: f32 = l * l - oc.dot(oc) + radius * radius; - if det < 0.0 { - return Vec2::splat(MAX_DISTANCE); - } - - let d: f32 = det.sqrt(); - let a: f32 = l - d; - let b: f32 = l + d; - - vec2(a, b) -} - -impl State { - fn background(&self, r: Vec3) -> Vec3 { - let x: f32 = r.x.atan2(r.z); // from -pi to pi - let y: f32 = PI * 0.5 - r.y.acos(); // from -1/2pi to 1/2pi - - let mut col: Vec3 = self.bg * (1.0 + y); - - let t: f32 = self.inputs.time; // add god rays - - let a: f32 = r.x.sin(); - - let mut beam: f32 = sat((10.0 * x + a * y * 5.0 + t).sin()); - beam *= sat((7.0 * x + a * y * 3.5 - t).sin()); - - let mut beam2: f32 = sat((42.0 * x + a * y * 21.0 - t).sin()); - beam2 *= sat((34.0 * x + a * y * 17.0 + t).sin()); - - beam += beam2; - col *= 1.0 + beam * 0.05; - - col - } -} - -fn remap(a: f32, b: f32, c: f32, d: f32, t: f32) -> f32 { - ((t - a) / (b - a)) * (d - c) + c -} - -impl State { - fn map(&self, mut p: Vec3, id: Vec3) -> De { - let t: f32 = self.inputs.time * 2.0; - - let n: f32 = n3(id); - - let mut o: De = De::default(); - o.m = 0.0; - - let mut x: f32 = (p.y + n * TWO_PI) * 1. + t; - let r: f32 = 1.; - - let pump: f32 = (x + x.cos()).cos() + (2.0 * x).sin() * 0.2 + (4.0 * x).sin() * 0.02; - - x = t + n * TWO_PI; - p.y -= ((x + x.cos()).cos() + (2.0 * x).sin() * 0.2) * 0.6; - p = (p.xz() * (1.0 + pump * 0.2)).extend(p.y).xzy(); - - let d1: f32 = sd_sphere(p, vec3(0.0, 0.0, 0.0), r); - let d2: f32 = sd_sphere(p, vec3(0.0, -0.5, 0.0), r); - - o.d = smax(d1, -d2, 0.1); - o.m = 1.0; - - if p.y < 0.5 { - let sway: f32 = (t + p.y + n * TWO_PI).sin() * smoothstep(0.5, -3.0, p.y) * n * 0.3; - p.x += sway * n; // add some sway to the tentacles - p.z += sway * (1. - n); - - let mut mp: Vec3 = p; - mp = p_mod_polar(&mut mp.xz(), 6.0, 0.0).extend(mp.y).xzy(); - - let mut d3: f32 = - (mp.xz() - vec2(0.2, 0.1)).length() - remap(0.5, -3.5, 0.1, 0.01, mp.y); - if d3 < o.d { - o.m = 2.; - } - d3 += ((mp.y * 10.0).sin() + (mp.y * 23.0).sin()) * 0.03; - - let d32: f32 = - (mp.xz() - vec2(0.2, 0.1)).length() - remap(0.5, -3.5, 0.1, 0.04, mp.y) * 0.5; - d3 = d3.min(d32); - o.d = smin(o.d, d3, 0.5); - - if p.y < 0.2 { - let mut op: Vec3 = p; - op = p_mod_polar(&mut op.xz(), 13.0, 1.0).extend(op.y).xzy(); - - let d4: f32 = - (op.xz() - vec2(0.85, 0.0)).length() - remap(0.5, -3.0, 0.04, 0.0, op.y); - if d4 < o.d { - o.m = 3.0; - } - o.d = smin(o.d, d4, 0.15); - } - } - o.pump = pump; - o.uv = p; - - o.d *= 0.8; - o - } - fn calc_normal(&self, o: De) -> Vec3 { - let eps: Vec3 = vec3(0.01, 0.0, 0.0); - let nor: Vec3 = vec3( - self.map(o.pos + eps.xyy(), o.id).d - self.map(o.pos - eps.xyy(), o.id).d, - self.map(o.pos + eps.yxy(), o.id).d - self.map(o.pos - eps.yxy(), o.id).d, - self.map(o.pos + eps.yyx(), o.id).d - self.map(o.pos - eps.yyx(), o.id).d, - ); - nor.normalize() - } - - fn cast_ray(&self, r: Ray) -> De { - let mut d: f32 = 0.0; - let _d_s: f32 = MAX_DISTANCE; - - let _pos: Vec3 = vec3(0.0, 0.0, 0.0); - let _n: Vec3 = Vec3::ZERO; - let mut o: De = De::default(); - let mut s: De = De::default(); - - let mut d_c: f32 = MAX_DISTANCE; - let mut p: Vec3 = Vec3::ZERO; - let mut q: Rc = Rc::default(); - let t: f32 = self.inputs.time; - let grid: Vec3 = vec3(6.0, 30.0, 6.0); - - for _ in 0..MAX_STEPS { - p = r.o + r.d * d; - - if SINGLE { - s = self.map(p, Vec3::ZERO); - } else { - p.y -= t; // make the move up - p.x += t; // make cam fly forward - - q = repeat(p, grid); - - let r_c: Vec3 = ((2. * Vec3::ZERO.step(r.d) - Vec3::ONE) * q.h - q.p) / r.d; // ray to cell boundary - d_c = r_c.x.min(r_c.y).min(r_c.z) + 0.01; // distance to cell just past boundary - - let n: f32 = n3(q.id); - q.p += (n31(n) - Vec3::splat(0.5)) * grid * vec3(0.5, 0.7, 0.5); - - if dist(q.p.xz(), r.d.xz(), Vec2::ZERO) < 1.1 { - //if(DistRaySegment(q.p, r.d, vec3(0., -6., 0.), vec3(0., -3.3, 0)) <1.1) - s = self.map(q.p, q.id); - } else { - s.d = d_c; - } - } - - if s.d < HIT_DISTANCE || d > MAX_DISTANCE { - break; - } - d += s.d.min(d_c); // move to distance to next cell or surface, whichever is closest - } - - if s.d < HIT_DISTANCE { - o.m = s.m; - o.d = d; - o.id = q.id; - o.uv = s.uv; - o.pump = s.pump; - - if SINGLE { - o.pos = p; - } else { - o.pos = q.p; - } - } - - o - } - - fn vol_tex(&self, uv: Vec3, mut p: Vec3, scale: f32, pump: f32) -> f32 { - // uv = the surface pos - // p = the volume shell pos - - p.y *= scale; - - let mut s2: f32 = 5. * p.x / TWO_PI; - let _id: f32 = s2.floor(); - s2 = s2.fract_gl(); - let ep: Vec2 = vec2(s2 - 0.5, p.y - 0.6); - let ed: f32 = ep.length(); - let e: f32 = b(0.35, 0.45, 0.05, ed); - - let mut s: f32 = sin(s2 * TWO_PI * 15.0); - s = s * s; - s = s * s; - s *= smoothstep(1.4, -0.3, uv.y - (s2 * TWO_PI).cos() * 0.2 + 0.3) - * smoothstep(-0.6, -0.3, uv.y); - - let t: f32 = self.inputs.time * 5.0; - let mask: f32 = sin(p.x * TWO_PI * 2.0 + t); - s *= mask * mask * 2.0; - - s + e * pump * 2.0 - } -} - -fn jelly_tex(mut p: Vec3) -> Vec4 { - let s: Vec3 = vec3(p.x.atan2(p.z), p.xz().length(), p.y); - - let mut b: f32 = 0.75 + (s.x * 6.0).sin() * 0.25; - b = mix(1., b, s.y * s.y); - - p.x += (s.z * 10.0).sin() * 0.1; - let mut b2: f32 = (s.x * 26.0).cos() - s.z - 0.7; - - b2 = smoothstep(0.1, 0.6, b2); - Vec4::splat(b + b2) -} - -impl State { - fn render(&mut self, _uv: Vec2, cam_ray: Ray, _depth: f32) -> Vec3 { - // outputs a color - - self.bg = self.background(self.cam.ray.d); - - let mut col: Vec3 = self.bg; - let o: De = self.cast_ray(cam_ray); - - let _t: f32 = self.inputs.time; - let l: Vec3 = UP; - - if o.m > 0.0 { - let n: Vec3 = self.calc_normal(o); - let lambert: f32 = sat(n.dot(l)); - let r: Vec3 = cam_ray.d.reflect(n); - let fresnel: f32 = sat(1.0 + cam_ray.d.dot(n)); - let _trans: f32 = (1.0 - fresnel) * 0.5; - let ref_: Vec3 = self.background(r); - let mut fade: f32 = 0.0; - - if o.m == 1.0 { - // hood color - let mut density: f32 = 0.0; - for i in 0..VOLUME_STEPS { - let sd: f32 = sph(o.uv, cam_ray.d, Vec3::ZERO, 0.8 + i as f32 * 0.015).x; - if sd != MAX_DISTANCE { - let intersect: Vec2 = o.uv.xz() + cam_ray.d.xz() * sd; - - let uv: Vec3 = - vec3(intersect.x.atan2(intersect.y), intersect.length(), o.uv.z); - density += self.vol_tex(o.uv, uv, 1.4 + i as f32 * 0.03, o.pump); - } - } - let vol_tex: Vec4 = self.accent.extend(density / VOLUME_STEPS as f32); - - let mut dif: Vec3 = jelly_tex(o.uv).xyz(); - dif *= lambert.max(0.2); - - col = mix(col, vol_tex.xyz(), vol_tex.w); - col = mix(col, dif, 0.25); - - col += fresnel * ref_ * sat(UP.dot(n)); - - //fade - fade = fade.max(smoothstep(0.0, 1.0, fresnel)); - } else if o.m == 2.0 { - // inside tentacles - let dif: Vec3 = self.accent; - col = mix(self.bg, dif, fresnel); - - col *= mix(0.6, 1.0, smoothstep(0.0, -1.5, o.uv.y)); - - let mut prop: f32 = o.pump + 0.25; - prop *= prop * prop; - col += (1.0 - fresnel).powf(20.0) * dif * prop; - - fade = fresnel; - } else if o.m == 3.0 { - // outside tentacles - let dif: Vec3 = self.accent; - let d: f32 = smoothstep(100.0, 13.0, o.d); - col = mix(self.bg, dif, (1.0 - fresnel).powf(5.0) * d); - } - - fade = fade.max(smoothstep(0.0, 100.0, o.d)); - col = mix(col, self.bg, fade); - - if o.m == 4. { - col = vec3(1.0, 0.0, 0.0); - } - } else { - col = self.bg; - } - - col - } - - pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { - let t: f32 = self.inputs.time * 0.04; - - let mut uv: Vec2 = frag_coord / self.inputs.resolution.xy(); - uv -= Vec2::splat(0.5); - uv.y *= self.inputs.resolution.y / self.inputs.resolution.x; - - let mut m: Vec2 = self.inputs.mouse.xy() / self.inputs.resolution.xy(); - - if m.x < 0.05 || m.x > 0.95 { - // move cam automatically when mouse is not used - m = vec2(t * 0.25, sin(t * PI) * 0.5 + 0.5); - } - - self.accent = mix(ACCENT_COLOR1, ACCENT_COLOR2, sin(t * 15.456)); - self.bg = mix(SECOND_COLOR1, SECOND_COLOR2, sin(t * 7.345231)); - - let turn: f32 = (0.1 - m.x) * TWO_PI; - let s: f32 = turn.sin(); - let c: f32 = turn.cos(); - let rot_x: Mat3 = Mat3::from_cols_array(&[c, 0.0, s, 0.0, 1.0, 0.0, s, 0.0, -c]); - - let cam_dist: f32 = if SINGLE { -10.0 } else { -0.1 }; - - let look_at: Vec3 = vec3(0.0, -1.0, 0.0); - - let cam_pos: Vec3 = - rot_x.transpose() * vec3(0.0, INVERTMOUSE * cam_dist * ((m.y) * PI).cos(), cam_dist); - - self.camera_setup(uv, cam_pos + look_at, look_at, 1.0); - - let mut col: Vec3 = self.render(uv, self.cam.ray, 0.0); - - col = col.powf_vec(Vec3::splat(mix(1.5, 2.6, sin(t + PI)))); // post-processing - let d: f32 = 1.0 - uv.dot(uv); // vignette - col *= (d * d * d) + 0.1; - - *frag_color = col.extend(1.0); - } -} diff --git a/shaders/src/mandelbrot_smooth.rs b/shaders/src/mandelbrot_smooth.rs deleted file mode 100644 index 67abf99..0000000 --- a/shaders/src/mandelbrot_smooth.rs +++ /dev/null @@ -1,93 +0,0 @@ -//! Ported to Rust from -//! -//! Original comment: -//! ```glsl -//! // Created by inigo quilez - iq/2013 -//! // License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. -//! -//! // See here for more information on smooth iteration count: -//! // -//! // http://iquilezles.org/www/articles/mset_smooth/mset_smooth.htm -//! ``` - -use shared::*; -use spirv_std::glam::{vec2, vec3, Vec2, Vec3, Vec3Swizzles, Vec4}; - -// Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but -// we tie #[no_std] above to the same condition, so it's fine. -#[cfg(target_arch = "spirv")] -use spirv_std::num_traits::Float; - -pub struct Inputs { - pub resolution: Vec3, - pub time: f32, -} - -// increase this if you have a very fast GPU -const AA: usize = 2; - -impl Inputs { - fn mandelbrot(&self, c: Vec2) -> f32 { - if true { - let c2: f32 = c.dot(c); - // skip computation inside M1 - http://iquilezles.org/www/articles/mset_1bulb/mset1bulb.htm - if 256.0 * c2 * c2 - 96.0 * c2 + 32.0 * c.x - 3.0 < 0.0 { - return 0.0; - } - // skip computation inside M2 - http://iquilezles.org/www/articles/mset_2bulb/mset2bulb.htm - if (16.0 * (c2 + 2.0 * c.x + 1.0) - 1.0) < 0.0 { - return 0.0; - } - } - const B: f32 = 256.0; - let mut l: f32 = 0.0; - let mut z: Vec2 = Vec2::ZERO; - for _ in 0..512 { - z = vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y) + c; - if z.dot(z) > (B * B) { - break; - } - l += 1.0; - } - - if l > 511.0 { - return 0.0; - } - // ------------------------------------------------------ - // smooth interation count - //float sl = l - log(log(length(z))/log(B))/log(2.0); - // - // equivalent optimized smooth interation count - let sl: f32 = l - z.dot(z).log2().log2() + 4.0; - - let al: f32 = smoothstep(-0.1, 0.0, (self.time * 0.5 * 6.2831).sin()); - mix(l, sl, al) - } - - pub fn main_image(&self, frag_color: &mut Vec4, frag_coord: Vec2) { - let mut col: Vec3 = Vec3::ZERO; - - for m in 0..AA { - for n in 0..AA { - let p: Vec2 = (-self.resolution.xy() - + Vec2::splat(2.0) * (frag_coord + vec2(m as f32, n as f32) / AA as f32)) - / self.resolution.y; - let w: f32 = (AA * m + n) as f32; - let time: f32 = self.time + 0.5 * (1.0 / 24.0) * w / (AA * AA) as f32; - - let mut zoo: f32 = 0.62 + 0.38 * (0.07 * time).cos(); - let coa: f32 = (0.15 * (1.0 - zoo) * time).cos(); - let sia = (0.15 * (1.0 - zoo) * time).sin(); - zoo = zoo.powf(8.0); - let xy: Vec2 = vec2(p.x * coa - p.y * sia, p.x * sia + p.y * coa); - let c: Vec2 = vec2(-0.745, 0.186) + xy * zoo; - - let l: f32 = self.mandelbrot(c); - col += Vec3::splat(0.5) - + Vec3::splat(0.5) * (Vec3::splat(3.0 + l * 0.15) + vec3(0.0, 0.6, 1.0)).cos(); - } - } - col /= (AA * AA) as f32; - *frag_color = col.extend(1.0); - } -} diff --git a/shaders/src/miracle_snowflakes.rs b/shaders/src/miracle_snowflakes.rs deleted file mode 100644 index e3f7879..0000000 --- a/shaders/src/miracle_snowflakes.rs +++ /dev/null @@ -1,372 +0,0 @@ -//! Ported to Rust from -//! -//! Original comment: -//! ```glsl -//! /* -//! // -//! /* Panteleymonov Aleksandr Konstantinovich 2015 -//! // -//! // if i write this string my code will be 0 chars, :) */ -//! */ -//! ``` - -use shared::*; -use spirv_std::glam::{ - vec2, vec3, vec4, Mat3, Vec2, Vec2Swizzles, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles, -}; - -// Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but -// we tie #[no_std] above to the same condition, so it's fine. -#[cfg(target_arch = "spirv")] -use spirv_std::num_traits::Float; - -pub struct Inputs { - pub resolution: Vec3, - pub time: f32, - pub mouse: Vec4, -} - -const ITERATIONS: u32 = 15; -const DEPTH: f32 = 0.0125; -const LAYERS: f32 = 8.0; -const LAYERSBLOB: i32 = 20; -const STEP: f32 = 1.0; -const FAR: f32 = 10000.0; - -pub struct State { - inputs: Inputs, - radius: f32, - zoom: f32, - - light: Vec3, - seed: Vec2, - iteratorc: f32, - powr: f32, - res: f32, - - nray: Vec3, - nray1: Vec3, - nray2: Vec3, - mxc: f32, -} - -impl State { - pub fn new(inputs: Inputs) -> Self { - State { - inputs, - radius: 0.25, // radius of Snowflakes. maximum for this demo 0.25. - zoom: 4.0, // use this to change details. optimal 0.1 - 4.0. - light: vec3(0.0, 0.0, 1.0), - seed: vec2(0.0, 0.0), - iteratorc: ITERATIONS as f32, - powr: 0.0, - res: 0.0, - - nray: Vec3::ZERO, - nray1: Vec3::ZERO, - nray2: Vec3::ZERO, - mxc: 1.0, - } - } -} - -const NC0: Vec4 = vec4(0.0, 157.0, 113.0, 270.0); -const NC1: Vec4 = vec4(1.0, 158.0, 114.0, 271.0); - -fn hash4(n: Vec4) -> Vec4 { - (n.sin() * 1399763.5453123).fract_gl() -} -fn noise2(x: Vec2) -> f32 { - let p: Vec2 = x.floor(); - let mut f: Vec2 = x.fract_gl(); - f = f * f * (Vec2::splat(3.0) - 2.0 * f); - let n: f32 = p.x + p.y * 157.0; - let nc0 = NC0; - let nc1 = NC1; - let h: Vec4 = hash4(Vec4::splat(n) + vec4(nc0.x, nc0.y, nc1.x, nc1.y)); - let s1: Vec2 = mix(h.xy(), h.zw(), f.xx()); - mix(s1.x, s1.y, f.y) -} - -fn noise222(x: Vec2, y: Vec2, z: Vec2) -> f32 { - let lx: Vec4 = vec4(x.x * y.x, x.y * y.x, x.x * y.y, x.y * y.y); - let p: Vec4 = lx.floor(); - let mut f: Vec4 = lx.fract_gl(); - f = f * f * (Vec4::splat(3.0) - 2.0 * f); - let n: Vec2 = p.xz() + p.yw() * 157.0; - let h: Vec4 = mix( - hash4(n.xxyy() + NC0.xyxy()), - hash4(n.xxyy() + NC1.xyxy()), - f.xxzz(), - ); - mix(h.xz(), h.yw(), f.yw()).dot(z) -} - -fn noise3(x: Vec3) -> f32 { - let p: Vec3 = x.floor(); - let mut f: Vec3 = x.fract_gl(); - f = f * f * (Vec3::splat(3.0) - 2.0 * f); - let n: f32 = p.x + p.yz().dot(vec2(157.0, 113.0)); - let s1: Vec4 = mix( - hash4(Vec4::splat(n) + NC0), - hash4(Vec4::splat(n) + NC1), - f.xxxx(), - ); - return mix(mix(s1.x, s1.y, f.y), mix(s1.z, s1.w, f.y), f.z); -} -fn noise3_2(x: Vec3) -> Vec2 { - vec2(noise3(x), noise3(x + Vec3::splat(100.0))) -} - -impl State { - fn map(&self, rad: Vec2) -> f32 { - let a: f32; - if self.res < 0.0015 { - //a = noise2(rad.xy*20.6)*0.9+noise2(rad.xy*100.6)*0.1; - a = noise222(rad, vec2(20.6, 100.6), vec2(0.9, 0.1)); - } else if self.res < 0.005 { - //let a1: f32 = mix(noise2(rad.xy()*10.6),1.0,l); - //a = texture(iChannel0,rad*0.3).x; - a = noise2(rad * 20.6); - //if a1 Vec3 { - let rq: f32 = r * r; - let mut dist: Vec3 = ray * FAR; - - let norm: Vec3 = vec3(0.0, 0.0, 1.0); - let invn: f32 = 1.0 / norm.dot(ray); - let mut depthi: f32 = DEPTH; - if invn < 0.0 { - depthi = -depthi; - } - let mut ds: f32 = 2.0 * depthi * invn; - let mut r1: Vec3 = ray * (norm.dot(pos) - depthi) * invn - pos; - let op1: Vec3 = r1 + norm * depthi; - let len1: f32 = op1.dot(op1); - let mut r2: Vec3 = r1 + ray * ds; - let op2: Vec3 = r2 - norm * depthi; - let len2: f32 = op2.dot(op2); - let n: Vec3 = ray.cross(norm).normalize(); - let mind: f32 = pos.dot(n); - let n2: Vec3 = ray.cross(n); - let d: f32 = n2.dot(pos) / n2.dot(norm); - let invd: f32 = 0.2 / DEPTH; - - if (len1 < rq || len2 < rq) || (mind.abs() < r && d <= DEPTH && d >= -DEPTH) { - let _r3: Vec3 = r2; - let len: f32 = len1; - if len >= rq { - let n3: Vec3 = norm.cross(n); - let a: f32 = 1.0 / (rq - mind * mind).sqrt() * ray.dot(n3).abs(); - let dt: Vec3 = ray / a; - r1 = -d * norm - mind * n - dt; - if len2 >= rq { - r2 = -d * norm - mind * n + dt; - } - ds = (r2 - r1).dot(ray); - } - ds = (ds.abs() + 0.1) / (ITERATIONS as f32); - ds = mix(DEPTH, ds, 0.2); - if ds > 0.01 { - ds = 0.01; - } - let ir: f32 = 0.35 / r; - r *= self.zoom; - ray = ray * ds * 5.0; - for m in 0..ITERATIONS { - if m as f32 >= self.iteratorc { - break; - } - let mut l: f32 = r1.xy().length(); //r1.xy().dot(r1.xy()).sqrt(); - let mut c3: Vec2 = (r1.xy() / l).abs(); - if c3.x > 0.5 { - c3 = (c3 * 0.5 + vec2(-c3.y, c3.x) * 0.86602540).abs(); - } - let g: f32 = l + c3.x * c3.x; //*1.047197551; - l *= self.zoom; - let mut h: f32 = l - r - 0.1; - l = l.powf(self.powr) + 0.1; - 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 - if (h < self.res * 20.0) || r1.z.abs() > DEPTH + 0.01 { - break; - } - r1 += ray * h; - ray *= 0.99; - } - if r1.z.abs() < DEPTH + 0.01 { - dist = r1 + pos; - } - } - dist - } - - fn filter_flake( - &mut self, - mut color: Vec4, - pos: Vec3, - ray: Vec3, - ray1: Vec3, - ray2: Vec3, - ) -> Vec4 { - let d: Vec3 = self.dist_obj(pos, ray, self.radius, self.seed); - let n1: Vec3 = self.dist_obj(pos, ray1, self.radius, self.seed); - let n2: Vec3 = self.dist_obj(pos, ray2, self.radius, self.seed); - - let lq: Vec3 = vec3(d.dot(d), n1.dot(n1), n2.dot(n2)); - if lq.x < FAR || lq.y < FAR || lq.z < FAR { - let n: Vec3 = (n1 - d).cross(n2 - d).normalize(); - if lq.x < FAR && lq.y < FAR && lq.z < FAR { - self.nray = n; //(self.nray+n).normalize(); - //self.nray1 = (ray1+n).normalize(); - //self.nray2 = (ray2+n).normalize(); - } - let da: f32 = n.dot(self.light).abs().powf(3.0); - let mut cf: Vec3 = mix(vec3(0.0, 0.4, 1.0), color.xyz() * 10.0, n.dot(ray).abs()); - cf = mix(cf, Vec3::splat(2.0), da); - color = (mix( - color.xyz(), - cf, - self.mxc * self.mxc * (0.5 + n.dot(ray).abs() * 0.5), - )) - .extend(color.w); - } - - color - } - - pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { - let time: f32 = self.inputs.time * 0.2; //*0.1; - self.res = 1.0 / self.inputs.resolution.y; - let p: Vec2 = (-self.inputs.resolution.xy() + 2.0 * frag_coord) * self.res; - - let mut rotate: Vec3; - let mut mr: Mat3; - let mut ray: Vec3; - let mut ray1: Vec3; - let mut ray2: Vec3; - let mut pos: Vec3 = vec3(0.0, 0.0, 1.0); - - *frag_color = vec4(0.0, 0.0, 0.0, 0.0); - self.nray = Vec3::ZERO; - self.nray1 = Vec3::ZERO; - self.nray2 = Vec3::ZERO; - - let mut refcolor: Vec4 = Vec4::ZERO; - self.iteratorc = ITERATIONS as f32 - LAYERS; - - let mut addrot: Vec2 = Vec2::ZERO; - if self.inputs.mouse.z > 0.0 { - addrot = (self.inputs.mouse.xy() - self.inputs.resolution.xy() * 0.5) * self.res; - } - - let mut mxcl: f32 = 1.0; - let mut addpos: Vec3 = Vec3::ZERO; - pos.z = 1.0; - self.mxc = 1.0; - self.radius = 0.25; - let mzd: f32 = (self.zoom - 0.1) / LAYERS; - for i in 0..LAYERSBLOB { - let p2: Vec2 = p - Vec2::splat(0.25) + Vec2::splat(0.1 * i as f32); - ray = p2.extend(2.0) - self.nray * 2.0; - //ray = self.nray;//*0.6; - ray1 = (ray + vec3(0.0, self.res * 2.0, 0.0)).normalize(); - ray2 = (ray + vec3(self.res * 2.0, 0.0, 0.0)).normalize(); - ray = ray.normalize(); - let mut sb: Vec2 = ray.xy() * pos.length() / pos.normalize().dot(ray) + vec2(0.0, time); - self.seed = (sb + vec2(0.0, pos.z)).floor() + Vec2::splat(pos.z); - let mut seedn: Vec3 = self.seed.extend(pos.z); - sb = sb.floor(); - if noise3(seedn) > 0.2 && i < LAYERS as i32 { - self.powr = noise3(seedn * 10.0) * 1.9 + 0.1; - rotate = (((Vec2::splat(0.5) - noise3_2(seedn)) * time * 5.0).sin() * 0.3 + addrot) - .extend(0.0); - rotate.z = (0.5 - noise3(seedn + vec3(10.0, 3.0, 1.0))) * time * 5.0; - seedn.z += time * 0.5; - addpos = (sb + vec2(0.25, 0.25 - time) + noise3_2(seedn) * 0.5).extend(addpos.z); - let sins: Vec3 = rotate.sin(); - let coss: Vec3 = rotate.cos(); - mr = Mat3::from_cols( - vec3(coss.x, 0.0, sins.x), - vec3(0.0, 1.0, 0.0), - vec3(-sins.x, 0.0, coss.x), - ); - mr = Mat3::from_cols( - vec3(1.0, 0.0, 0.0), - vec3(0.0, coss.y, sins.y), - vec3(0.0, -sins.y, coss.y), - ) * mr; - mr = Mat3::from_cols( - vec3(coss.z, sins.z, 0.0), - vec3(-sins.z, coss.z, 0.0), - vec3(0.0, 0.0, 1.0), - ) * mr; - - self.light = mr.transpose() * vec3(1.0, 0.0, 1.0).normalize(); - // let cc: Vec4 = self.filter_flake( - // *frag_color, - // mr.transpose() * (pos + addpos), - // (mr.transpose() * ray + self.nray * 0.1).normalize(), - // (mr.transpose() * ray1 + self.nray * 0.1).normalize(), - // (mr.transpose() * ray2 + self.nray * 0.1).normalize(), - // ); - let mut cc: Vec4 = self.filter_flake( - *frag_color, - mr.transpose() * (pos + addpos), - mr.transpose() * ray, - mr.transpose() * ray1, - mr.transpose() * ray2, - ); - if false { - if i > 0 - && self.nray.dot(self.nray) != 0.0 - && self.nray1.dot(self.nray1) != 0.0 - && self.nray2.dot(self.nray2) != 0.0 - { - refcolor = self.filter_flake( - refcolor, - mr.transpose() * (pos + addpos), - self.nray, - self.nray1, - self.nray2, - ); - } - cc += refcolor * 0.5; - } - *frag_color = mix(cc, *frag_color, frag_color.w.min(1.0)); - } - seedn = sb.extend(pos.z) + vec3(0.5, 1000.0, 300.0); - if noise3(seedn * 10.0) > 0.4 { - let raf: f32 = 0.3 + noise3(seedn * 100.0); - addpos = - (sb + vec2(0.2, 0.2 - time) + noise3_2(seedn * 100.0) * 0.6).extend(addpos.z); - let mut l: f32 = (ray * ray.dot(pos + addpos) - pos - addpos).length(); - l = (1.0 - l * 10.0 * raf).max(0.0); - *frag_color += - vec4(1.0, 1.2, 3.0, 1.0) * l.powf(5.0) * ((0.6 + raf).powf(2.0) - 0.6) * mxcl; - } - self.mxc -= 1.1 / LAYERS; - pos.z += STEP; - self.iteratorc += 2.0; - mxcl -= 1.1 / LAYERSBLOB as f32; - self.zoom -= mzd; - } - - let cr: Vec3 = mix(Vec3::ZERO, vec3(0.0, 0.0, 0.4), (-0.55 + p.y) * 2.0); - *frag_color = (frag_color.xyz() - + mix( - (cr - frag_color.xyz()) * 0.1, - vec3(0.2, 0.5, 1.0), - ((-p.y + 1.0) * 0.5).clamp(0.0, 1.0), - )) - .extend(frag_color.z); - - *frag_color = Vec4::ONE.min(*frag_color); - } -} diff --git a/shaders/src/morphing.rs b/shaders/src/morphing.rs deleted file mode 100644 index 192ba43..0000000 --- a/shaders/src/morphing.rs +++ /dev/null @@ -1,293 +0,0 @@ -//! Ported to Rust from -//! -//! Original comment: -//! ```glsl -//! // Created by Sebastien Durand - 2014 -//! // License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. -//! ``` - -use shared::*; -use spirv_std::glam::{ - vec2, vec3, Mat2, Vec2, Vec2Swizzles, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles, -}; - -// Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but -// we tie #[no_std] above to the same condition, so it's fine. -#[cfg(target_arch = "spirv")] -use spirv_std::num_traits::Float; - -pub struct Inputs { - pub resolution: Vec3, - pub time: f32, - pub mouse: Vec4, -} - -pub struct State { - inputs: Inputs, - - a: [Vec2; 15], - t1: [Vec2; 5], - t2: [Vec2; 5], - - l: Vec3, - - t_morph: f32, - mat2_rot: Mat2, -} - -impl State { - pub fn new(inputs: Inputs) -> Self { - State { - inputs, - - a: [ - Vec2::ZERO, - Vec2::ZERO, - Vec2::ZERO, - Vec2::ZERO, - Vec2::ZERO, - Vec2::ZERO, - Vec2::ZERO, - Vec2::ZERO, - Vec2::ZERO, - Vec2::ZERO, - Vec2::ZERO, - Vec2::ZERO, - Vec2::ZERO, - Vec2::ZERO, - Vec2::ZERO, - ], - t1: [Vec2::ZERO, Vec2::ZERO, Vec2::ZERO, Vec2::ZERO, Vec2::ZERO], - t2: [Vec2::ZERO, Vec2::ZERO, Vec2::ZERO, Vec2::ZERO, Vec2::ZERO], - - l: vec3(1.0, 0.72, 1.0).normalize(), - - t_morph: 0.0, - mat2_rot: Mat2::ZERO, - } - } -} - -fn u(a: Vec2, b: Vec2) -> f32 { - a.x * b.y - b.x * a.y -} - -const Y: Vec3 = vec3(0.0, 1.0, 0.0); -// const E: Vec3 = Y * 0.01; -const _E: Vec3 = vec3(0.0, 0.01, 0.0); - -// Distance to Bezier -// inspired by [iq:https://www.shadertoy.com/view/ldj3Wh] -// calculate distance to 2D bezier curve on xy but without forgeting the z component of p -// total distance is corrected using pytagore just before return -fn bezier(mut m: Vec2, mut n: Vec2, mut o: Vec2, p: Vec3) -> Vec2 { - let q: Vec2 = p.xy(); - m -= q; - n -= q; - o -= q; - let x: f32 = u(m, o); - let y: f32 = 2.0 * u(n, m); - let z: f32 = 2.0 * u(o, n); - let i: Vec2 = o - m; - let j: Vec2 = o - n; - let k: Vec2 = n - m; - let s: Vec2 = 2. * (x * i + y * j + z * k); - let mut r: Vec2 = m + (y * z - x * x) * vec2(s.y, -s.x) / s.dot(s); - let t: f32 = ((u(r, i) + 2.0 * u(k, r)) / (x + x + y + z)).clamp(0.0, 1.0); // parametric position on curve - r = m + t * (k + k + t * (j - k)); // distance on 2D xy space - vec2((r.dot(r) + p.z * p.z).sqrt(), t) // distance on 3D space -} - -fn smin(a: f32, b: f32, k: f32) -> f32 { - let h: f32 = (0.5 + 0.5 * (b - a) / k).clamp(0.0, 1.0); - mix(b, a, h) - k * h * (1. - h) -} - -impl State { - // Distance to scene - fn m(&self, mut p: Vec3) -> f32 { - // Distance to Teapot --------------------------------------------------- - // precalcul first part of teapot spout - let h: Vec2 = bezier(self.t1[2], self.t1[3], self.t1[4], p); - let mut a: f32 = 99.0; - // distance to teapot handle (-.06 => make the thickness) - let b: f32 = (bezier(self.t2[0], self.t2[1], self.t2[2], p) - .x - .min(bezier(self.t2[2], self.t2[3], self.t2[4], p).x) - - 0.06) - // max p.y-.9 => cut the end of the spout - .min( - (p.y - 0.9).max( - // distance to second part of teapot spout (abs(dist,r1)-dr) => enable to make the spout hole - ((bezier(self.t1[0], self.t1[1], self.t1[2], p).x - 0.07).abs() - 0.01) - // distance to first part of teapot spout (tickness incrase with pos on curve) - .min(h.x * (1. - 0.75 * h.y) - 0.08), - ), - ); - - // distance to teapot body => use rotation symetry to simplify calculation to a distance to 2D bezier curve - let qq: Vec3 = vec3((p.dot(p) - p.y * p.y).sqrt(), p.y, 0.0); - // the substraction of .015 enable to generate a small thickness arround bezier to help convergance - // the .8 factor help convergance - let mut i = 0; - while i < 13 { - a = a.min((bezier(self.a[i], self.a[i + 1], self.a[i + 2], qq).x - 0.015) * 0.7); - i += 2; - } - // smooth minimum to improve quality at junction of handle and spout to the body - let d_teapot: f32 = smin(a, b, 0.02); - - // Distance to other shapes --------------------------------------------- - let mut d_shape: f32; - let id_morph: i32 = ((0.5 + (self.inputs.time) / (2.0 * 3.141592658)).floor() % 3.0) as i32; - - if id_morph == 1 { - p = (self.mat2_rot.transpose() * p.xz()).extend(p.y).xzy(); - let d: Vec3 = (p - vec3(0.0, 0.5, 0.0)).abs() - vec3(0.8, 0.7, 0.8); - d_shape = d.x.max(d.y.max(d.z)).min(0.0) + d.max(Vec3::ZERO).length(); - } else if id_morph == 2 { - p -= vec3(0.0, 0.55, 0.0); - let d1: Vec3 = p.abs() - vec3(0.67, 0.67, 0.67 * 1.618); - let d3: Vec3 = p.abs() - vec3(0.67 * 1.618, 0.67, 0.67); - d_shape = d1.x.max(d1.y.max(d1.z)).min(0.0) + d1.max(Vec3::ZERO).length(); - d_shape = d_shape.min(d3.x.max(d3.y.max(d3.z)).min(0.0) + d3.max(Vec3::ZERO).length()); - } else { - d_shape = (p - vec3(0.0, 0.45, 0.0)).length() - 1.1; - } - - // !!! The morphing is here !!! - mix(d_teapot, d_shape, self.t_morph.abs()) - } -} - -// HSV to RGB conversion -// [iq: https://www.shadertoy.com/view/MsS3Wc] -fn hsv2rgb_smooth(x: f32, y: f32, z: f32) -> Vec3 { - let mut rgb: Vec3 = (((x * Vec3::splat(6.0) + vec3(0.0, 4.0, 2.0)) - .rem_euclid(Vec3::splat(6.0)) - - Vec3::splat(3.0)) - .abs() - - Vec3::ONE) - .clamp(Vec3::ZERO, Vec3::ONE); - rgb = rgb * rgb * (Vec3::splat(3.0) - 2.0 * rgb); // cubic smoothing - z * mix(Vec3::ONE, rgb, y) -} - -impl State { - fn normal(&self, p: Vec3, ray: Vec3, t: f32) -> Vec3 { - let pitch: f32 = 0.4 * t / self.inputs.resolution.x; - let d: Vec2 = vec2(-1.0, 1.0) * pitch; - // tetrahedral offsets - let p0: Vec3 = p + d.xxx(); - let p1: Vec3 = p + d.xyy(); - let p2: Vec3 = p + d.yxy(); - let p3: Vec3 = p + d.yyx(); - let f0: f32 = self.m(p0); - let f1: f32 = self.m(p1); - let f2: f32 = self.m(p2); - let f3: f32 = self.m(p3); - let grad: Vec3 = p0 * f0 + p1 * f1 + p2 * f2 + p3 * f3 - p * (f0 + f1 + f2 + f3); - // prevent normals pointing away from camera (caused by precision errors) - (grad - (grad.dot(ray).max(0.0)) * ray).normalize() - } - - pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { - let aa: f32 = 3.14159 / 4.0; - self.mat2_rot = Mat2::from_cols_array(&[aa.cos(), aa.sin(), -aa.sin(), aa.cos()]); - - // Morphing step - self.t_morph = (self.inputs.time * 0.5).cos(); - self.t_morph *= self.t_morph * self.t_morph * self.t_morph * self.t_morph; - - // Teapot body profil (8 quadratic curves) - self.a[0] = vec2(0.0, 0.0); - self.a[1] = vec2(0.64, 0.0); - self.a[2] = vec2(0.64, 0.03); - self.a[3] = vec2(0.8, 0.12); - self.a[4] = vec2(0.8, 0.3); - self.a[5] = vec2(0.8, 0.48); - self.a[6] = vec2(0.64, 0.9); - self.a[7] = vec2(0.6, 0.93); - self.a[8] = vec2(0.56, 0.9); - self.a[9] = vec2(0.56, 0.96); - self.a[10] = vec2(0.12, 1.02); - self.a[11] = vec2(0.0, 1.05); - self.a[12] = vec2(0.16, 1.14); - self.a[13] = vec2(0.2, 1.2); - self.a[14] = vec2(0.0, 1.2); - // Teapot spout (2 quadratic curves) - self.t1[0] = vec2(1.16, 0.96); - self.t1[1] = vec2(1.04, 0.9); - self.t1[2] = vec2(1.0, 0.72); - self.t1[3] = vec2(0.92, 0.48); - self.t1[4] = vec2(0.72, 0.42); - // Teapot handle (2 quadratic curves) - self.t2[0] = vec2(-0.6, 0.78); - self.t2[1] = vec2(-1.16, 0.84); - self.t2[2] = vec2(-1.16, 0.63); - self.t2[3] = vec2(-1.2, 0.42); - self.t2[4] = vec2(-0.72, 0.24); - - // Configure camera - let r: Vec2 = self.inputs.resolution.xy(); - let m: Vec2 = self.inputs.mouse.xy() / r; - let q: Vec2 = frag_coord / r; - let mut p: Vec2 = q + q - Vec2::ONE; - p.x *= r.x / r.y; - let mut j: f32 = 0.0; - let mut s: f32 = 1.0; - let mut h: f32 = 0.1; - let mut t: f32 = 5.0 + 0.2 * self.inputs.time + 4.0 * m.x; - let o: Vec3 = 2.9 * vec3(t.cos(), 0.7 - m.y, t.sin()); - let w: Vec3 = (Y * 0.4 - o).normalize(); - let u: Vec3 = w.cross(Y).normalize(); - let v: Vec3 = u.cross(w); - let d: Vec3 = (p.x * u + p.y * v + w + w).normalize(); - let n: Vec3; - let x: Vec3; - - // Ray marching - t = 0.0; - for _ in 0..48 { - if h < 0.0001 || t > 4.7 { - break; - } - h = self.m(o + d * t); - t += h; - } - - // Background colour change as teapot complementaries colours (using HSV) - let mut c: Vec3 = mix( - hsv2rgb_smooth(0.5 + self.inputs.time * 0.02, 0.35, 0.4), - hsv2rgb_smooth(-0.5 + self.inputs.time * 0.02, 0.35, 0.7), - q.y, - ); - - // Calculate color on point - if h < 0.001 { - x = o + t * d; - 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))); - - // Calculate Shadows - for _ in 0..20 { - j += 0.02; - s = s.min(self.m(x + self.l * j) / j); - } - // Teapot color rotation in HSV color space - let c1: Vec3 = hsv2rgb_smooth(0.9 + self.inputs.time * 0.02, 1.0, 1.0); - // Shading - c = mix( - c, - mix( - (((3.0 * s).clamp(0.0, 1.0) + 0.3) * c1).sqrt(), - Vec3::splat(self.l.reflect(n).dot(d).max(0.0).powf(99.0)), - 0.4, - ), - 2.0 * n.dot(-d), - ); - } - - c *= (16.0 * q.x * q.y * (1.0 - q.x) * (1.0 - q.y)).powf(0.16); // Vigneting - *frag_color = c.extend(1.0); - } -} diff --git a/shaders/src/moving_square.rs b/shaders/src/moving_square.rs deleted file mode 100644 index b695be6..0000000 --- a/shaders/src/moving_square.rs +++ /dev/null @@ -1,39 +0,0 @@ -//! Ported to Rust from - -use spirv_std::glam::{vec3, Mat2, Vec2, Vec3, Vec3Swizzles, Vec4}; - -// Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but -// we tie #[no_std] above to the same condition, so it's fine. -#[cfg(target_arch = "spirv")] -use spirv_std::num_traits::Float; - -pub struct Inputs { - pub resolution: Vec3, - pub time: f32, -} - -fn rect(uv: Vec2, pos: Vec2, r: f32) -> Vec4 { - let re_c: Vec2 = (uv - pos).abs(); - let dif1: Vec2 = re_c - Vec2::splat(r / 2.); - let dif2: Vec2 = (re_c - Vec2::splat(r / 2.)).clamp(Vec2::ZERO, Vec2::ONE); - let d1: f32 = (dif1.x + dif1.y).clamp(0.0, 1.0); - let _d2: f32 = (dif2.x + dif2.y).clamp(0.0, 1.0); - - Vec4::splat(d1) -} - -impl Inputs { - pub fn main_image(&self, frag_color: &mut Vec4, frag_coord: Vec2) { - let mut uv: Vec2 = frag_coord; - let t: f32 = self.time.sin(); - - let c: Vec2 = self.resolution.xy() * 0.5; // + sin(iTime) * 50.; - - uv = Mat2::from_cols_array(&[t.cos(), -t.sin(), t.sin(), t.cos()]) * (uv - c) + c; - - *frag_color = rect(uv, c, (self.time * 10.).sin() * 50. + 50.); - *frag_color *= vec3(0.5, 0.2, 1.).extend(1.); - *frag_color += rect(uv, c, self.time.sin() * 50. + 50.); - *frag_color *= vec3(0.5, 0.8, 1.).extend(1.); - } -} diff --git a/shaders/src/on_off_spikes.rs b/shaders/src/on_off_spikes.rs deleted file mode 100644 index e269543..0000000 --- a/shaders/src/on_off_spikes.rs +++ /dev/null @@ -1,436 +0,0 @@ -//! Ported to Rust from -//! -//! Original comment: -//! ```glsl -//! // On/Off Spikes, fragment shader by movAX13h, oct 2014 -//! ``` - -use shared::*; -use spirv_std::glam::{ - vec2, vec3, Mat2, Mat3, Vec2, Vec2Swizzles, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles, -}; - -// Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but -// we tie #[no_std] above to the same condition, so it's fine. -#[cfg(target_arch = "spirv")] -use spirv_std::num_traits::Float; - -pub struct Inputs { - pub resolution: Vec3, - pub time: f32, - pub mouse: Vec4, -} - -pub struct State { - inputs: Inputs, - - // globals - glow: f32, - bite: f32, - sphere_col: Vec3, - sun: Vec3, - focus: f32, - far: f32, -} - -impl State { - pub fn new(inputs: Inputs) -> State { - State { - inputs, - - glow: 0.0, - bite: 0.0, - sphere_col: Vec3::ZERO, - sun: SUN_POS.normalize(), - focus: 5.0, - far: 23.0, - } - } -} - -const HARD_SHADOW: bool = true; -const GLOW: bool = true; -const EDGES: bool = true; -const NUM_TENTACLES: i32 = 6; -const BUMPS: bool = true; -const NUM_BUMPS: i32 = 8; -const BACKGROUND: bool = true; -const SUN_POS: Vec3 = vec3(15.0, 15.0, -15.0); -const SUN_SPHERE: bool = false; - -const SPHERE_COL: Vec3 = vec3(0.6, 0.3, 0.1); -const MOUTH_COL: Vec3 = vec3(0.9, 0.6, 0.1); -const TENTACLE_COL: Vec3 = vec3(0.06, 0.06, 0.06); - -const GAMMA: f32 = 2.2; - -//--- -const PI2: f32 = 6.283185307179586476925286766559; -const PIH: f32 = 1.5707963267949; - -// Using the nebula function of the "Star map shader" by morgan3d -// as environment map and light sphere texture (https://www.shadertoy.com/view/4sBXzG) -const _PI: f32 = 3.1415927; -const NUM_OCTAVES: i32 = 4; -fn hash(n: f32) -> f32 { - (n.sin() * 1e4).fract_gl() -} -fn hash_vec2(p: Vec2) -> f32 { - (1e4 * (17.0 * p.x + p.y * 0.1).sin() * (0.1 + (p.y * 13.0 + p.x).sin().abs())).fract_gl() -} -fn noise(x: f32) -> f32 { - let i: f32 = x.floor(); - let f: f32 = x.fract_gl(); - let u: f32 = f * f * (3.0 - 2.0 * f); - mix(hash(i), hash(i + 1.0), u) -} -fn noise_vec2(x: Vec2) -> f32 { - let i: Vec2 = x.floor(); - let f: Vec2 = x.fract_gl(); - let a: f32 = hash_vec2(i); - let b: f32 = hash_vec2(i + vec2(1.0, 0.0)); - let c: f32 = hash_vec2(i + vec2(0.0, 1.0)); - let d: f32 = hash_vec2(i + vec2(1.0, 1.0)); - let u: Vec2 = f * f * (Vec2::splat(3.0) - 2.0 * f); - mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y -} -fn noise2(mut x: Vec2) -> f32 { - let mut v: f32 = 0.0; - let mut a: f32 = 0.5; - let shift: Vec2 = Vec2::splat(100.0); - let rot: Mat2 = - Mat2::from_cols_array(&[0.5_f32.cos(), 0.5_f32.sin(), -0.5_f32.sin(), 0.50_f32.cos()]); - - for _ in 0..NUM_OCTAVES { - v += a * noise_vec2(x); - x = rot * x * 2.0 + shift; - a *= 0.5; - } - v -} -fn square(x: f32) -> f32 { - x * x -} -fn rotation(yaw: f32, pitch: f32) -> Mat3 { - Mat3::from_cols_array(&[ - yaw.cos(), - 0.0, - -yaw.sin(), - 0.0, - 1.0, - 0.0, - yaw.sin(), - 0.0, - yaw.cos(), - ]) * Mat3::from_cols_array(&[ - 1.0, - 0.0, - 0.0, - 0.0, - pitch.cos(), - pitch.sin(), - 0.0, - -pitch.sin(), - pitch.cos(), - ]) -} -fn nebula(dir: Vec3) -> Vec3 { - let purple: f32 = dir.x.abs(); - let yellow: f32 = noise(dir.y); - let streaky_hue: Vec3 = vec3(purple + yellow, yellow * 0.7, purple); - let puffy_hue: Vec3 = vec3(0.8, 0.1, 1.0); - let streaky: f32 = 1.0_f32.min( - 8.0 * (noise2( - dir.yz() * square(dir.x) * 13.0 + dir.xy() * square(dir.z) * 7.0 + vec2(150.0, 2.0), - )) - .powf(10.0), - ); - let puffy: f32 = square(noise2(dir.xz() * 4.0 + vec2(30.0, 10.0)) * dir.y); - - (puffy_hue * puffy * (1.0 - streaky) + streaky * streaky_hue) - .clamp(Vec3::ZERO, Vec3::ONE) - .powf(1.0 / 2.2) -} -// --- - -fn sd_box(p: Vec3, b: Vec3) -> f32 { - let d: Vec3 = p.abs() - b; - d.x.max(d.y.max(d.z)).min(0.0) + d.max(Vec3::ZERO).length() -} - -fn sd_sphere(p: Vec3, r: f32) -> f32 { - p.length() - r -} - -fn sd_capped_cylinder(p: Vec3, h: Vec2) -> f32 { - let d: Vec2 = vec2(p.xy().length(), p.z).abs() - h; - d.x.max(d.y).min(0.0) + d.max(Vec2::ZERO).length() -} - -fn rotate(p: Vec2, a: f32) -> Vec2 { - let mut r: Vec2 = Vec2::ZERO; - r.x = p.x * a.cos() - p.y * a.sin(); - r.y = p.x * a.sin() + p.y * a.cos(); - r -} - -// polynomial smooth min (k = 0.1); by iq -fn smin(a: f32, b: f32, k: f32) -> f32 { - let h: f32 = (0.5 + 0.5 * (b - a) / k).clamp(0.0, 1.0); - mix(b, a, h) - k * h * (1.0 - h) -} - -#[derive(Clone, Copy, Default)] -struct Hit { - d: f32, - color: Vec3, - edge: f32, -} - -impl State { - fn scene(&self, p: Vec3) -> Hit { - let mut d: f32; - let mut d1: f32; - let mut d2: f32; - let d3: f32; - let f: f32; - let mut e: f32 = 0.15; - - let mut q: Vec3 = p; - q = rotate(q.xy(), 1.5).extend(q.z); - - // center sphere - d1 = sd_sphere(q, 0.3); - // d = d1; - let mut col: Vec3 = self.sphere_col; - - // tentacles - let r: f32 = q.length(); - let mut a: f32 = q.z.atan2(q.x); - a += 0.4 * (r - self.inputs.time).sin(); - - q = vec3(a * NUM_TENTACLES as f32 / PI2, q.y, q.xz().length()); // circular domain - q = vec3(q.x.rem_euclid(1.0) - 0.5 * 1.0, q.y, q.z); // repetition - - d3 = sd_capped_cylinder( - q - vec3(0.0, 0.0, 0.9 + self.bite), - vec2(0.1 - (r - self.bite) / 18.0, 0.8), - ); - d2 = d3.min(sd_box( - q - vec3(0.0, 0.0, 0.1 + self.bite), - vec3(0.2, 0.2, 0.2), - )); // close box - d2 = smin( - d2, - sd_box(q - vec3(0.0, 0.0, 0.4 + self.bite), vec3(0.2, 0.05, 0.4)), - 0.1, - ); // wide box - - f = smoothstep(0.11, 0.28, d2 - d1); - col = mix(MOUTH_COL, col, f); - e = mix(e, 0.0, f); - d = smin(d1, d2, 0.24); - - col = mix(TENTACLE_COL, col, smoothstep(0.0, 0.48, d3 - d)); - - if SUN_SPHERE { - d = d.min(sd_sphere(p - self.sun, 0.1)); - } - - if BUMPS { - for i in 0..NUM_BUMPS { - d2 = i as f32; - d1 = sd_sphere( - p - 0.18 - * smoothstep(0.1, 1.0, self.glow) - * vec3( - (4.0 * self.inputs.time + d2 * 0.6).sin(), - (5.3 * self.inputs.time + d2 * 1.4).sin(), - (5.8 * self.inputs.time + d2 * 0.6).cos(), - ), - 0.03, - ); - - d = smin(d1, d, 0.2); - //d = min(d1, d); - } - } - - if BACKGROUND { - q = p; - q = q.yz().rem_euclid(Vec2::ONE).extend(q.x).zxy(); - q -= vec3(-0.6, 0.5, 0.5); - d1 = sd_box(q, vec3(0.1, 0.48, 0.48)); - if d1 < d { - d = d1; - col = Vec3::splat(0.1); - } - } - - Hit { - d, - color: col, - edge: e, - } - } - - fn normal(&self, p: Vec3) -> Vec3 { - let c: f32 = self.scene(p).d; - let h: Vec2 = vec2(0.01, 0.0); - vec3( - self.scene(p + h.xyy()).d - c, - self.scene(p + h.yxy()).d - c, - self.scene(p + h.yyx()).d - c, - ) - .normalize() - } - - // by srtuss - fn edges(&self, p: Vec3) -> f32 { - let mut acc: f32 = 0.0; - let h: f32 = 0.01; - acc += self.scene(p + vec3(-h, -h, -h)).d; - acc += self.scene(p + vec3(-h, -h, h)).d; - acc += self.scene(p + vec3(-h, h, -h)).d; - acc += self.scene(p + vec3(-h, h, h)).d; - acc += self.scene(p + vec3(h, -h, -h)).d; - acc += self.scene(p + vec3(h, -h, h)).d; - acc += self.scene(p + vec3(h, h, -h)).d; - acc += self.scene(p + vec3(h, h, h)).d; - acc / h - } - - fn colorize(&self, hit: Hit, n: Vec3, dir: Vec3, light_pos: Vec3) -> Vec3 { - let diffuse: f32 = 0.3 * n.dot(light_pos).max(0.0); - - let ref_: Vec3 = dir.reflect(n).normalize(); - let specular: f32 = 0.4 * ref_.dot(light_pos).max(0.0).powf(6.5); - - hit.color + diffuse * Vec3::splat(0.9) + specular * Vec3::splat(1.0) - } - - pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { - //self.time = self.inputs.time; - self.glow = (2.0 * (self.inputs.time * 0.7 - 5.0).sin()) - .min(1.0) - .max(0.0); - self.bite = smoothstep(0.0, 1.0, 1.6 * (self.inputs.time * 0.7).sin()); - self.sphere_col = SPHERE_COL * self.glow; - - let pos: Vec2 = (frag_coord * 2.0 - self.inputs.resolution.xy()) / self.inputs.resolution.y; - - let d: f32 = (1.5 * (0.3 * self.inputs.time).sin()).clamp(0.5, 1.0); - let mut cp: Vec3 = vec3( - 10.0 * d, - -2.3 * d, - -6.2 * d + 4.0 * (2.0 * (self.inputs.time * 0.5).sin().clamp(0.0, 1.0)), - ); // anim curious spectator - - if self.inputs.mouse.z > 0.5 { - let mrel: Vec2 = - self.inputs.mouse.xy() / self.inputs.resolution.xy() - Vec2::splat(0.5); - let mdis: f32 = 8.0 + 6.0 * mrel.y; - cp = vec3( - mdis * (-mrel.x * PIH).cos(), - 4.0 * mrel.y, - mdis * (-mrel.x * PIH).sin(), - ); - } - - let ct: Vec3 = vec3(0.0, 0.0, 0.0); - let cd: Vec3 = (ct - cp).normalize(); - let cu: Vec3 = vec3(0.0, 1.0, 0.0); - let cs: Vec3 = cd.cross(cu); - let mut dir: Vec3 = (cs * pos.x + cu * pos.y + cd * self.focus).normalize(); - - let mut h: Hit = Hit::default(); - let mut col: Vec3; - let mut ray: Vec3 = cp; - let mut dist: f32 = 0.0; - - // raymarch scene - for _ in 0..60 { - h = self.scene(ray); - - if h.d < 0.0001 { - break; - } - - dist += h.d; - ray += dir * h.d * 0.9; - - if dist > self.far { - dist = self.far; - break; - } - } - - let m: f32 = 1.0 - dist / self.far; - let n: Vec3 = self.normal(ray); - col = self.colorize(h, n, dir, self.sun) * m; - - if EDGES { - let edge: f32 = self.edges(ray); - col = mix( - col, - Vec3::ZERO, - h.edge * edge * smoothstep(0.3, 0.35, ray.length()), - ); - } - - let neb: Vec3 = nebula(n); - col += self.glow.min(0.1) * neb.zxy(); - - // HARD SHADOW with low number of rm iterations (from obj to sun) - if HARD_SHADOW { - let mut ray1: Vec3 = ray; - dir = (SUN_POS - ray1).normalize(); - ray1 += n * 0.002; - - let sun_dist: f32 = (SUN_POS - ray1).length(); - dist = 0.0; - - for _ in 0..35 { - h = self.scene(ray1 + dir * dist); - dist += h.d; - if h.d.abs() < 0.001 { - break; - } - } - - col -= Vec3::splat( - 0.24 * smoothstep(0.5, -0.3, dist.min(sun_dist) / sun_dist.max(0.0001)), - ); - } - - // ILLUMINATION & free shadow with low number of rm iterations (from obj to sphere) - if GLOW { - dir = (-ray).normalize(); - ray += n * 0.002; - - let sphere_dist: f32 = (ray.length() - 0.3).max(0.0001); - dist = 0.0; - - for _ in 0..35 { - h = self.scene(ray + dir * dist); - dist += h.d; - if h.d.abs() < 0.001 { - break; - } - } - - let neb1: Vec3 = nebula(rotation(0.0, self.inputs.time * 0.4).transpose() * dir).zxy(); - - col += (0.7 * self.sphere_col + self.glow * neb1) - * (0.6 * (smoothstep(3.0, 0.0, sphere_dist)) * dist.min(sphere_dist) / sphere_dist - + 0.6 * smoothstep(0.1, 0.0, sphere_dist)); - } - - col -= Vec3::splat(0.2 * smoothstep(0.6, 3.7, pos.length())); - col = col.clamp(Vec3::ZERO, Vec3::ONE); - col = col.powf_vec(vec3(2.2, 2.4, 2.5)) * 3.9; - col = col.powf_vec(Vec3::splat(1.0 / GAMMA)); - - *frag_color = col.extend(1.0); - } -} diff --git a/shaders/src/phantom_star.rs b/shaders/src/phantom_star.rs deleted file mode 100644 index 6d76980..0000000 --- a/shaders/src/phantom_star.rs +++ /dev/null @@ -1,98 +0,0 @@ -//! Ported to Rust from - -use spirv_std::glam::{vec3, Mat2, Vec2, Vec3, Vec3Swizzles, Vec4}; - -use core::f32::consts::PI; - -// Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but -// we tie #[no_std] above to the same condition, so it's fine. -#[cfg(target_arch = "spirv")] -use {shared::FloatExt, spirv_std::num_traits::Float}; - -pub struct Inputs { - pub resolution: Vec3, - pub time: f32, -} - -fn rot(a: f32) -> Mat2 { - let c: f32 = a.cos(); - let s: f32 = a.sin(); - Mat2::from_cols_array(&[c, s, -s, c]) -} - -//const pi: f32 = (-1.0).acos(); -const PI_: f32 = PI; -const PI2: f32 = PI_ * 2.0; - -fn pmod(p: Vec2, r: f32) -> Vec2 { - let mut a: f32 = p.x.atan2(p.y) + PI_ / r; - let n: f32 = PI2 / r; - a = (a / n).floor() * n; - rot(-a).transpose() * p -} - -fn box_(p: Vec3, b: Vec3) -> f32 { - let d: Vec3 = p.abs() - b; - d.x.max(d.y.max(d.z)).min(0.0) + d.max(Vec3::ZERO).length() -} - -impl Inputs { - fn ifs_box(&self, mut p: Vec3) -> f32 { - for _ in 0..5 { - p = p.abs() - Vec3::splat(1.0); - p = (rot(self.time * 0.3).transpose() * p.xy()).extend(p.z); - p = (rot(self.time * 0.1).transpose() * p.xz()) - .extend(p.y) - .xzy(); - } - p = (rot(self.time).transpose() * p.xz()).extend(p.y).xzy(); - box_(p, vec3(0.4, 0.8, 0.3)) - } - - fn map(&self, p: Vec3, _c_pos: Vec3) -> f32 { - let mut p1: Vec3 = p; - p1.x = (p1.x - 5.0).rem_euclid(10.0) - 5.0; - p1.y = (p1.y - 5.0).rem_euclid(10.0) - 5.0; - p1.z = p1.z.rem_euclid(16.0) - 8.0; - p1 = pmod(p1.xy(), 5.0).extend(p1.z); - self.ifs_box(p1) - } - - pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { - let p: Vec2 = - (frag_coord * 2.0 - self.resolution.xy()) / self.resolution.x.min(self.resolution.y); - - let c_pos: Vec3 = vec3(0.0, 0.0, -3.0 * self.time); - // let c_pos: Vec3 = vec3(0.3 * (self.time * 0.8).sin(), 0.4 * (self.time * 0.3).cos(), -6.0 * self.time,); - let c_dir: Vec3 = vec3(0.0, 0.0, -1.0).normalize(); - let c_up: Vec3 = vec3(self.time.sin(), 1.0, 0.0); - let c_side: Vec3 = c_dir.cross(c_up); - - let ray: Vec3 = (c_side * p.x + c_up * p.y + c_dir).normalize(); - - // Phantom Mode https://www.shadertoy.com/view/MtScWW by aiekick - let mut acc: f32 = 0.0; - let mut acc2: f32 = 0.0; - let mut t: f32 = 0.0; - - for _ in 0..99 { - let pos: Vec3 = c_pos + ray * t; - let mut dist: f32 = self.map(pos, c_pos); - dist = dist.abs().max(0.02); - let mut a: f32 = (-dist * 3.0).exp(); - if (pos.length() + 24.0 * self.time).rem_euclid(30.0) < 3.0 { - a *= 2.0; - acc2 += a; - } - acc += a; - t += dist * 0.5; - } - - let col: Vec3 = vec3( - acc * 0.01, - acc * 0.011 + acc2 * 0.002, - acc * 0.012 + acc2 * 0.005, - ); - *frag_color = col.extend(1.0 - t * 0.03); - } -} diff --git a/shaders/src/playing_marble.rs b/shaders/src/playing_marble.rs deleted file mode 100644 index 3da5435..0000000 --- a/shaders/src/playing_marble.rs +++ /dev/null @@ -1,128 +0,0 @@ -//! Ported to Rust from -//! -//! Original comment: -//! ```glsl -//! // License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. -//! // Created by S. Guillitte 2015 -//! ``` - -use crate::SampleCube; -use shared::*; -use spirv_std::glam::{vec2, vec3, vec4, Mat2, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles}; - -// Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but -// we tie #[no_std] above to the same condition, so it's fine. -#[cfg(target_arch = "spirv")] -use spirv_std::num_traits::Float; - -pub struct Inputs { - pub resolution: Vec3, - pub time: f32, - pub mouse: Vec4, - pub channel0: C0, -} - -const ZOOM: f32 = 1.0; - -fn _cmul(a: Vec2, b: Vec2) -> Vec2 { - vec2(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x) -} -fn csqr(a: Vec2) -> Vec2 { - vec2(a.x * a.x - a.y * a.y, 2. * a.x * a.y) -} - -fn rot(a: f32) -> Mat2 { - Mat2::from_cols_array(&[a.cos(), a.sin(), -a.sin(), a.cos()]) -} - -//from iq -fn i_sphere(ro: Vec3, rd: Vec3, sph: Vec4) -> Vec2 { - let oc: Vec3 = ro - sph.xyz(); - let b: f32 = oc.dot(rd); - let c: f32 = oc.dot(oc) - sph.w * sph.w; - let mut h: f32 = b * b - c; - if h < 0.0 { - return Vec2::splat(-1.0); - } - h = h.sqrt(); - vec2(-b - h, -b + h) -} - -fn map(mut p: Vec3) -> f32 { - let mut res: f32 = 0.0; - let c: Vec3 = p; - for _ in 0..10 { - p = 0.7 * p.abs() / p.dot(p) - Vec3::splat(0.7); - p = csqr(p.yz()).extend(p.x).zxy(); - p = p.zxy(); - res += (-19.0 * p.dot(c).abs()).exp(); - } - res / 2.0 -} - -impl Inputs { - fn raymarch(&self, ro: Vec3, rd: Vec3, tminmax: Vec2) -> Vec3 { - let mut t: f32 = tminmax.x; - let dt: f32 = 0.02; - //let dt: f32 = 0.2 - 0.195 * (self.time * 0.05).cos(); //animated - let mut col: Vec3 = Vec3::ZERO; - let mut c: f32 = 0.0; - for _ in 0..64 { - t += dt * (-2.0 * c).exp(); - if t > tminmax.y { - break; - } - let _pos: Vec3 = ro + t * rd; - - c = map(ro + t * rd); - - col = 0.99 * col + 0.08 * vec3(c * c, c, c * c * c); //green - - // col = 0.99 * col + 0.08 * vec3(c * c * c, c * c, c); //blue - } - col - } - pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { - let time: f32 = self.time; - let q: Vec2 = frag_coord / self.resolution.xy(); - let mut p: Vec2 = Vec2::splat(-1.0) + 2.0 * q; - p.x *= self.resolution.x / self.resolution.y; - let mut m: Vec2 = Vec2::ZERO; - if self.mouse.z > 0.0 { - m = self.mouse.xy() / self.resolution.xy() * 3.14; - } - m = m - Vec2::splat(0.5); - - // camera - - let mut ro: Vec3 = ZOOM * Vec3::splat(4.0); - ro = (rot(m.y).transpose() * ro.yz()).extend(ro.x).zxy(); - ro = (rot(m.x + 0.1 * time).transpose() * ro.xz()) - .extend(ro.y) - .xzy(); - let ta: Vec3 = Vec3::ZERO; - let ww: Vec3 = (ta - ro).normalize(); - let uu: Vec3 = (ww.cross(vec3(0.0, 1.0, 0.0))).normalize(); - let vv: Vec3 = (uu.cross(ww)).normalize(); - let rd: Vec3 = (p.x * uu + p.y * vv + 4.0 * ww).normalize(); - - let tmm: Vec2 = i_sphere(ro, rd, vec4(0.0, 0.0, 0.0, 2.0)); - // raymarch - let mut col: Vec3 = self.raymarch(ro, rd, tmm); - if tmm.x < 0.0 { - col = self.channel0.sample_cube(rd).xyz(); - } else { - let mut nor: Vec3 = (ro + tmm.x * rd) / 2.; - nor = rd.reflect(nor); - let fre: f32 = (0.5 + nor.dot(rd).clamp(0.0, 1.0)).powf(3.0) * 1.3; - col += self.channel0.sample_cube(nor).xyz() * fre; - } - - //shade - - col = 0.5 * (Vec3::ONE + col).ln(); - col = col.clamp(Vec3::ZERO, Vec3::ONE); - - *frag_color = col.extend(1.0); - } -} diff --git a/shaders/src/protean_clouds.rs b/shaders/src/protean_clouds.rs deleted file mode 100644 index 5b4dfd9..0000000 --- a/shaders/src/protean_clouds.rs +++ /dev/null @@ -1,225 +0,0 @@ -//! Ported to Rust from -//! -//! Original comment: -//! ```glsl -//! // Protean clouds by nimitz (twitter: @stormoid) -//! // https://www.shadertoy.com/view/3l23Rh -//! // License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License -//! // Contact the author for other licensing options -//! -//! /* -//! Technical details: -//! -//! The main volume noise is generated from a deformed periodic grid, which can produce -//! a large range of noise-like patterns at very cheap evalutation cost. Allowing for multiple -//! fetches of volume gradient computation for improved lighting. -//! -//! To further accelerate marching, since the volume is smooth, more than half the the density -//! information isn't used to rendering or shading but only as an underlying volume distance to -//! determine dynamic step size, by carefully selecting an equation (polynomial for speed) to -//! step as a function of overall density (not necessarialy rendered) the visual results can be -//! the same as a naive implementation with ~40% increase in rendering performance. -//! -//! Since the dynamic marching step size is even less uniform due to steps not being rendered at all -//! the fog is evaluated as the difference of the fog integral at each rendered step. -//! -//! */ -//! ``` - -use shared::*; -use spirv_std::glam::{ - mat3, vec2, vec3, vec4, Mat2, Mat3, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles, -}; - -// Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but -// we tie #[no_std] above to the same condition, so it's fine. -#[cfg(target_arch = "spirv")] -use spirv_std::num_traits::Float; - -pub struct Inputs { - pub resolution: Vec3, - pub time: f32, - pub mouse: Vec4, -} - -pub struct State { - inputs: Inputs, - prm1: f32, - bs_mo: Vec2, -} - -impl State { - pub fn new(inputs: Inputs) -> Self { - State { - inputs, - prm1: 0.0, - bs_mo: Vec2::ZERO, - } - } -} - -fn rot(a: f32) -> Mat2 { - let c: f32 = a.cos(); - let s: f32 = a.sin(); - Mat2::from_cols_array(&[c, s, -s, c]) -} - -// const m3: Mat3 = const_mat3!([ -// 0.33338, 0.56034, -0.71817, -0.87887, 0.32651, -0.15323, 0.15162, 0.69596, 0.61339 -// ]) * 1.93; - -const M3: Mat3 = mat3( - Vec3::new(0.33338 * 1.93, -0.87887 * 1.93, 0.15162 * 1.93), - Vec3::new(0.56034 * 1.93, 0.32651 * 1.93, 0.69596 * 1.93), - Vec3::new(-0.71817 * 1.93, -0.15323 * 1.93, 0.61339 * 1.93), -); - -fn mag2(p: Vec2) -> f32 { - p.dot(p) -} -fn linstep(mn: f32, mx: f32, x: f32) -> f32 { - ((x - mn) / (mx - mn)).clamp(0.0, 1.0) -} - -fn disp(t: f32) -> Vec2 { - vec2((t * 0.22).sin() * 1.0, (t * 0.175).cos() * 1.0) * 2.0 -} - -impl State { - fn map(&self, mut p: Vec3) -> Vec2 { - let mut p2: Vec3 = p; - p2 = (p2.xy() - disp(p.z)).extend(p2.z); - p = (rot( - (p.z + self.inputs.time).sin() * (0.1 + self.prm1 * 0.05) + self.inputs.time * 0.09 - ) - .transpose() - * p.xy()) - .extend(p.z); - let cl: f32 = mag2(p2.xy()); - let mut d: f32 = 0.0; - p *= 0.61; - let mut z: f32 = 1.0; - let mut trk: f32 = 1.0; - let dsp_amp: f32 = 0.1 + self.prm1 * 0.2; - for _ in 0..5 { - p += (p.zxy() * 0.75 * trk + Vec3::splat(self.inputs.time) * trk * 0.8).sin() * dsp_amp; - d -= (p.cos().dot(p.yzx().sin()) * z).abs(); - z *= 0.57; - trk *= 1.4; - let m3 = M3; - p = m3.transpose() * p; - } - d = (d + self.prm1 * 3.0).abs() + self.prm1 * 0.3 - 2.5 + self.bs_mo.y; - vec2(d + cl * 0.2 + 0.25, cl) - } - - fn render(&self, ro: Vec3, rd: Vec3, time: f32) -> Vec4 { - let mut rez: Vec4 = Vec4::ZERO; - const LDST: f32 = 8.0; - let _lpos: Vec3 = (disp(time + LDST) * 0.5).extend(time + LDST); - let mut t: f32 = 1.5; - let mut fog_t: f32 = 0.0; - - for _ in 0..130 { - if rez.w > 0.99 { - break; - } - - let pos: Vec3 = ro + t * rd; - let mpv: Vec2 = self.map(pos); - let den: f32 = (mpv.x - 0.3).clamp(0.0, 1.0) * 1.12; - let dn: f32 = (mpv.x + 2.0).clamp(0.0, 3.0); - - let mut col: Vec4 = Vec4::ZERO; - if mpv.x > 0.6 { - col = ((vec3(5.0, 0.4, 0.2) - + Vec3::splat(mpv.y * 0.1 + (pos.z * 0.4).sin() * 0.5 + 1.8)) - .sin() - * 0.5 - + Vec3::splat(0.5)) - .extend(0.08); - col *= den * den * den; - col = (col.xyz() * linstep(4.0, -2.5, mpv.x) * 2.3).extend(col.w); - let mut dif: f32 = - ((den - self.map(pos + Vec3::splat(0.8)).x) / 9.0).clamp(0.001, 1.0); - dif += ((den - self.map(pos + Vec3::splat(0.35)).x) / 2.5).clamp(0.001, 1.0); - col = (col.xyz() - * den - * (vec3(0.005, 0.045, 0.075) + 1.5 * vec3(0.033, 0.07, 0.03) * dif)) - .extend(col.w); - } - - let fog_c = (t * 0.2 - 2.2).exp(); - col += vec4(0.06, 0.11, 0.11, 0.1) * (fog_c - fog_t).clamp(0.0, 1.0); - fog_t = fog_c; - rez = rez + col * (1.0 - rez.w); - t += (0.5 - dn * dn * 0.05).clamp(0.09, 0.3); - } - - rez.clamp(Vec4::ZERO, Vec4::ONE) - } -} - -fn getsat(c: Vec3) -> f32 { - let mi: f32 = c.x.min(c.y).min(c.z); - let ma: f32 = c.x.max(c.y).max(c.z); - (ma - mi) / (ma + 1e-7) -} - -//from my "Will it blend" shader (https://www.shadertoy.com/view/lsdGzN) -fn i_lerp(a: Vec3, b: Vec3, x: f32) -> Vec3 { - let mut ic: Vec3 = mix(a, b, x) + vec3(1e-6, 0.0, 0.0); - let sd: f32 = (getsat(ic) - mix(getsat(a), getsat(b), x)).abs(); - let dir: Vec3 = vec3( - 2.0 * ic.x - ic.y - ic.z, - 2.0 * ic.y - ic.x - ic.z, - 2.0 * ic.z - ic.y - ic.x, - ) - .normalize(); - let lgt: f32 = Vec3::splat(1.0).dot(ic); - let ff: f32 = dir.dot(ic.normalize()); - ic += 1.5 * dir * sd * ff * lgt; - ic.clamp(Vec3::ZERO, Vec3::ONE) -} - -impl State { - pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { - let q: Vec2 = frag_coord / self.inputs.resolution.xy(); - let p: Vec2 = (frag_coord - 0.5 * self.inputs.resolution.xy()) / self.inputs.resolution.y; - self.bs_mo = - (self.inputs.mouse.xy() - 0.5 * self.inputs.resolution.xy()) / self.inputs.resolution.y; - - let time: f32 = self.inputs.time * 3.; - let mut ro: Vec3 = vec3(0.0, 0.0, time); - - ro += vec3( - self.inputs.time.sin() * 0.5, - (self.inputs.time.sin() * 1.0) * 0.0, - 0.0, - ); - - let dsp_amp: f32 = 0.85; - ro = (ro.xy() + disp(ro.z) * dsp_amp).extend(ro.z); - let tgt_dst: f32 = 3.5; - - let target: Vec3 = - (ro - (disp(time + tgt_dst) * dsp_amp).extend(time + tgt_dst)).normalize(); - ro.x -= self.bs_mo.x * 2.; - let mut rightdir: Vec3 = target.cross(vec3(0.0, 1.0, 0.0)).normalize(); - let updir: Vec3 = rightdir.cross(target).normalize(); - rightdir = updir.cross(target).normalize(); - let mut rd: Vec3 = ((p.x * rightdir + p.y * updir) * 1.0 - target).normalize(); - rd = (rot(-disp(time + 3.5).x * 0.2 + self.bs_mo.x).transpose() * rd.xy()).extend(rd.z); - self.prm1 = smoothstep(-0.4, 0.4, (self.inputs.time * 0.3).sin()); - let scn: Vec4 = self.render(ro, rd, time); - - let mut col: Vec3 = scn.xyz(); - col = i_lerp(col.zyx(), col, (1.0 - self.prm1).clamp(0.05, 1.0)); - - col = col.powf_vec(vec3(0.55, 0.65, 0.6)) * vec3(1.0, 0.97, 0.9); - - col *= (16.0 * q.x * q.y * (1.0 - q.x) * (1.0 - q.y)).powf(0.12) * 0.7 + 0.3; //Vign - - *frag_color = col.extend(1.0); - } -} diff --git a/shaders/src/raymarching_primitives.rs b/shaders/src/raymarching_primitives.rs deleted file mode 100644 index c8e1ac2..0000000 --- a/shaders/src/raymarching_primitives.rs +++ /dev/null @@ -1,844 +0,0 @@ -//! Ported to Rust from -//! -//! Original comment: -//! ```glsl -//! // The MIT License -//! // Copyright © 2013 Inigo Quilez -//! // 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. -//! -//! // A list of useful distance function to simple primitives. All -//! // these functions (except for ellipsoid) return an exact -//! // euclidean distance, meaning they produce a better SDF than -//! // what you'd get if you were constructing them from boolean -//! // operations. -//! // -//! // More info here: -//! // -//! // https://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm -//! ``` - -use shared::*; -use spirv_std::glam::{ - vec2, vec3, Mat3, Vec2, Vec2Swizzles, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles, -}; - -// Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but -// we tie #[no_std] above to the same condition, so it's fine. -#[cfg(target_arch = "spirv")] -use spirv_std::num_traits::Float; - -pub struct Inputs { - pub resolution: Vec3, - pub frame: i32, - pub time: f32, - pub mouse: Vec4, -} - -const HW_PERFORMANCE: usize = 1; -const AA: i32 = if HW_PERFORMANCE == 0 { - 1 -} else { - 2 // make this 2 or 3 for antialiasing -}; - -//------------------------------------------------------------------ -fn dot2_vec2(v: Vec2) -> f32 { - v.dot(v) -} -fn dot2_vec3(v: Vec3) -> f32 { - v.dot(v) -} -fn ndot(a: Vec2, b: Vec2) -> f32 { - a.x * b.x - a.y * b.y -} - -fn _sd_plane(p: Vec3) -> f32 { - p.y -} - -fn sd_sphere(p: Vec3, s: f32) -> f32 { - p.length() - s -} - -fn sd_box(p: Vec3, b: Vec3) -> f32 { - let d: Vec3 = p.abs() - b; - d.x.max(d.y.max(d.z)).min(0.0) + d.max(Vec3::ZERO).length() -} - -fn sd_bounding_box(mut p: Vec3, b: Vec3, e: f32) -> f32 { - p = p.abs() - b; - let q: Vec3 = (p + Vec3::splat(e)).abs() - Vec3::splat(e); - - (vec3(p.x, q.y, q.z).max(Vec3::ZERO).length() + p.x.max(q.y.max(q.z)).min(0.0)) - .min(vec3(q.x, p.y, q.z).max(Vec3::ZERO).length() + q.x.max(p.y.max(q.z)).min(0.0)) - .min(vec3(q.x, q.y, p.z).max(Vec3::ZERO).length() + q.x.max(q.y.max(p.z)).min(0.0)) -} -// approximated -fn sd_ellipsoid(p: Vec3, r: Vec3) -> f32 { - let k0: f32 = (p / r).length(); - let k1: f32 = (p / (r * r)).length(); - return k0 * (k0 - 1.0) / k1; -} - -fn sd_torus(p: Vec3, t: Vec2) -> f32 { - (vec2(p.xz().length() - t.x, p.y)).length() - t.y -} - -fn sd_capped_torus(mut p: Vec3, sc: Vec2, ra: f32, rb: f32) -> f32 { - p.x = p.x.abs(); - let k: f32 = if sc.y * p.x > sc.x * p.y { - p.xy().dot(sc) - } else { - p.xy().length() - }; - (p.dot(p) + ra * ra - 2.0 * ra * k).sqrt() - rb -} - -fn sd_hex_prism(mut p: Vec3, h: Vec2) -> f32 { - let _q: Vec3 = p.abs(); - - let k: Vec3 = vec3(-0.8660254, 0.5, 0.57735); - p = p.abs(); - p = (p.xy() - 2.0 * k.xy().dot(p.xy()).min(0.0) * k.xy()).extend(p.z); - let d: Vec2 = vec2( - (p.xy() - vec2(p.x.clamp(-k.z * h.x, k.z * h.x), h.x)).length() * (p.y - h.x).sign_gl(), - p.z - h.y, - ); - d.x.max(d.y).min(0.0) + d.max(Vec2::ZERO).length() -} - -fn sd_octogon_prism(mut p: Vec3, r: f32, h: f32) -> f32 { - let k: Vec3 = vec3( - -0.9238795325, // sqrt(2+sqrt(2))/2 - 0.3826834323, // sqrt(2-sqrt(2))/2 - 0.4142135623, - ); // sqrt(2)-1 - // reflections - p = p.abs(); - p = (p.xy() - 2.0 * vec2(k.x, k.y).dot(p.xy()).min(0.0) * vec2(k.x, k.y)).extend(p.z); - p = (p.xy() - 2.0 * vec2(-k.x, k.y).dot(p.xy()).min(0.0) * vec2(-k.x, k.y)).extend(p.z); - // polygon side - p = (p.xy() - vec2(p.x.clamp(-k.z * r, k.z * r), r)).extend(p.z); - let d: Vec2 = vec2(p.xy().length() * p.y.sign_gl(), p.z - h); - d.x.max(d.y).min(0.0) + d.max(Vec2::ZERO).length() -} - -fn sd_capsule(p: Vec3, a: Vec3, b: Vec3, r: f32) -> f32 { - let pa: Vec3 = p - a; - let ba: Vec3 = b - a; - let h: f32 = (pa.dot(ba) / ba.dot(ba)).clamp(0.0, 1.0); - (pa - ba * h).length() - r -} - -fn sd_round_cone_vertical(p: Vec3, r1: f32, r2: f32, h: f32) -> f32 { - let q: Vec2 = vec2(p.xz().length(), p.y); - - let b: f32 = (r1 - r2) / h; - let a: f32 = (1.0 - b * b).sqrt(); - let k: f32 = q.dot(vec2(-b, a)); - - if k < 0.0 { - return q.length() - r1; - } - if k > a * h { - return (q - vec2(0.0, h)).length() - r2; - } - - q.dot(vec2(a, b)) - r1 -} - -fn sd_round_cone(p: Vec3, a: Vec3, b: Vec3, r1: f32, r2: f32) -> f32 { - // sampling independent computations (only depend on shape) - let ba: Vec3 = b - a; - let l2: f32 = ba.dot(ba); - let rr: f32 = r1 - r2; - let a2: f32 = l2 - rr * rr; - let il2: f32 = 1.0 / l2; - - // sampling dependant computations - let pa: Vec3 = p - a; - let y: f32 = pa.dot(ba); - let z: f32 = y - l2; - let x2: f32 = dot2_vec3(pa * l2 - ba * y); - let y2: f32 = y * y * l2; - let z2: f32 = z * z * l2; - - // single square root! - let k: f32 = rr.sign_gl() * rr * rr * x2; - if z.sign_gl() * a2 * z2 > k { - return (x2 + z2).sqrt() * il2 - r2; - } - if y.sign_gl() * a2 * y2 < k { - return (x2 + y2).sqrt() * il2 - r1; - } - ((x2 * a2 * il2).sqrt() + y * rr) * il2 - r1 -} - -fn sd_tri_prism(mut p: Vec3, mut h: Vec2) -> f32 { - let k: f32 = 3.0_f32.sqrt(); - h.x *= 0.5 * k; - p = (p.xy() / h.x).extend(p.z); - p.x = p.x.abs() - 1.0; - p.y = p.y + 1.0 / k; - if p.x + k * p.y > 0.0 { - p = (vec2(p.x - k * p.y, -k * p.x - p.y) / 2.0).extend(p.z); - } - p.x -= p.x.clamp(-2.0, 0.0); - let d1: f32 = p.xy().length() * (-p.y).sign_gl() * h.x; - let d2: f32 = p.z.abs() - h.y; - vec2(d1, d2).max(Vec2::ZERO).length() + d1.max(d2).min(0.0) -} - -// vertical -fn sd_cylinder_vertical(p: Vec3, h: Vec2) -> f32 { - let d: Vec2 = vec2(p.xz().length(), p.y).abs() - h; - d.x.max(d.y).min(0.0) + d.max(Vec2::ZERO).length() -} - -// arbitrary orientation -fn sd_cylinder(p: Vec3, a: Vec3, b: Vec3, r: f32) -> f32 { - let pa: Vec3 = p - a; - let ba: Vec3 = b - a; - let baba: f32 = ba.dot(ba); - let paba: f32 = pa.dot(ba); - - let x: f32 = (pa * baba - ba * paba).length() - r * baba; - let y: f32 = (paba - baba * 0.5).abs() - baba * 0.5; - let x2: f32 = x * x; - let y2: f32 = y * y * baba; - let d: f32 = if x.max(y) < 0.0 { - -x2.min(y2) - } else { - (if x > 0.0 { x2 } else { 0.0 }) + if y > 0.0 { y2 } else { 0.0 } - }; - d.sign_gl() * d.abs().sqrt() / baba -} - -// vertical -fn sd_cone(p: Vec3, c: Vec2, h: f32) -> f32 { - let q: Vec2 = h * vec2(c.x, -c.y) / c.y; - let w: Vec2 = vec2(p.xz().length(), p.y); - - let a: Vec2 = w - q * (w.dot(q) / q.dot(q)).clamp(0.0, 1.0); - let b: Vec2 = w - q * vec2((w.x / q.x).clamp(0.0, 1.0), 1.0); - let k: f32 = q.y.sign_gl(); - let d: f32 = a.dot(a).min(b.dot(b)); - let s: f32 = (k * (w.x * q.y - w.y * q.x)).max(k * (w.y - q.y)); - d.sqrt() * s.sign_gl() -} - -fn sd_capped_cone_vertical(p: Vec3, h: f32, r1: f32, r2: f32) -> f32 { - let q: Vec2 = vec2(p.xz().length(), p.y); - - let k1: Vec2 = vec2(r2, h); - let k2: Vec2 = vec2(r2 - r1, 2.0 * h); - let ca: Vec2 = vec2( - q.x - q.x.min(if q.y < 0.0 { r1 } else { r2 }), - q.y.abs() - h, - ); - let cb: Vec2 = q - k1 + k2 * ((k1 - q).dot(k2) / dot2_vec2(k2)).clamp(0.0, 1.0); - let s: f32 = if cb.x < 0.0 && ca.y < 0.0 { -1.0 } else { 1.0 }; - s * dot2_vec2(ca).min(dot2_vec2(cb)).sqrt() -} - -fn sd_capped_cone(p: Vec3, a: Vec3, b: Vec3, ra: f32, rb: f32) -> f32 { - let rba: f32 = rb - ra; - let baba: f32 = (b - a).dot(b - a); - let papa: f32 = (p - a).dot(p - a); - let paba: f32 = (p - a).dot(b - a) / baba; - - let x: f32 = (papa - paba * paba * baba).sqrt(); - - let cax: f32 = 0.0_f32.max(x - (if paba < 0.5 { ra } else { rb })); - let cay: f32 = (paba - 0.5).abs() - 0.5; - - let k: f32 = rba * rba + baba; - let f: f32 = ((rba * (x - ra) + paba * baba) / k).clamp(0.0, 1.0); - - let cbx: f32 = x - ra - f * rba; - let cby: f32 = paba - f; - - let s: f32 = if cbx < 0.0 && cay < 0.0 { -1.0 } else { 1.0 }; - - s * (cax * cax + cay * cay * baba) - .min(cbx * cbx + cby * cby * baba) - .sqrt() -} - -// c is the sin/cos of the desired cone angle -fn sd_solid_angle(pos: Vec3, c: Vec2, ra: f32) -> f32 { - let p: Vec2 = vec2(pos.xz().length(), pos.y); - let l: f32 = p.length() - ra; - let m: f32 = (p - c * p.dot(c).clamp(0.0, ra)).length(); - l.max(m * (c.y * p.x - c.x * p.y).sign_gl()) -} - -fn sd_octahedron(mut p: Vec3, s: f32) -> f32 { - p = p.abs(); - let m: f32 = p.x + p.y + p.z - s; - - // exact distance - if false { - let mut o: Vec3 = (3.0 * p - Vec3::splat(m)).min(Vec3::ZERO); - o = (6.0 * p - Vec3::splat(m) * 2.0 - o * 3.0 + Vec3::splat(o.x + o.y + o.z)) - .max(Vec3::ZERO); - return (p - s * o / (o.x + o.y + o.z)).length(); - } - - // exact distance - if true { - let q: Vec3; - if 3.0 * p.x < m { - q = p; - } else if 3.0 * p.y < m { - q = p.yzx(); - } else if 3.0 * p.z < m { - q = p.zxy(); - } else { - return m * 0.57735027; - } - let k: f32 = (0.5 * (q.z - q.y + s)).clamp(0.0, s); - return vec3(q.x, q.y - s + k, q.z - k).length(); - } - - // bound, not exact - if false { - return m * 0.57735027; - } - - unreachable!(); -} - -fn sd_pyramid(mut p: Vec3, h: f32) -> f32 { - let m2: f32 = h * h + 0.25; - - // symmetry - p = p.xz().abs().extend(p.y).xzy(); - p = if p.z > p.x { p.zx() } else { p.xz() }.extend(p.y).xzy(); - p = (p.xz() - Vec2::splat(0.5)).extend(p.y).xzy(); - - // project into face plane (2D) - let q: Vec3 = vec3(p.z, h * p.y - 0.5 * p.x, h * p.x + 0.5 * p.y); - - let s: f32 = (-q.x).max(0.0); - let t: f32 = ((q.y - 0.5 * p.z) / (m2 + 0.25)).clamp(0.0, 1.0); - - let a: f32 = m2 * (q.x + s) * (q.x + s) + q.y * q.y; - let b: f32 = m2 * (q.x + 0.5 * t) * (q.x + 0.5 * t) + (q.y - m2 * t) * (q.y - m2 * t); - - let d2: f32 = if q.y.min(-q.x * m2 - q.y * 0.5) > 0.0 { - 0.0 - } else { - a.min(b) - }; - - // recover 3D and scale, and add sign - ((d2 + q.z * q.z) / m2).sqrt() * q.z.max(-p.y).sign_gl() -} - -// la,lb=semi axis, h=height, ra=corner -fn sd_rhombus(mut p: Vec3, la: f32, lb: f32, h: f32, ra: f32) -> f32 { - p = p.abs(); - let b: Vec2 = vec2(la, lb); - let f: f32 = (ndot(b, b - 2.0 * p.xz()) / b.dot(b)).clamp(-1.0, 1.0); - let q: Vec2 = vec2( - (p.xz() - 0.5 * b * vec2(1.0 - f, 1.0 + f)).length() - * (p.x * b.y + p.z * b.x - b.x * b.y).sign_gl() - - ra, - p.y - h, - ); - q.x.max(q.y).min(0.0) + q.max(Vec2::ZERO).length() -} - -//------------------------------------------------------------------ - -fn op_u(d1: Vec2, d2: Vec2) -> Vec2 { - if d1.x < d2.x { - d1 - } else { - d2 - } -} - -//------------------------------------------------------------------ - -impl Inputs { - fn zero(&self) -> i32 { - let frame = self.frame; - if frame >= 0 { - 0 - } else { - frame - } - } -} - -//------------------------------------------------------------------ - -fn map(pos: Vec3) -> Vec2 { - let mut res: Vec2 = vec2(1e10, 0.0); - - res = op_u( - res, - vec2(sd_sphere(pos - vec3(-2.0, 0.25, 0.0), 0.25), 26.9), - ); - - // bounding box - if sd_box(pos - vec3(0.0, 0.3, -1.0), vec3(0.35, 0.3, 2.5)) < res.x { - // more primitives - res = op_u( - res, - vec2( - sd_bounding_box(pos - vec3(0.0, 0.25, 0.0), vec3(0.3, 0.25, 0.2), 0.025), - 16.9, - ), - ); - res = op_u( - res, - vec2( - sd_torus((pos - vec3(0.0, 0.30, 1.0)).xzy(), vec2(0.25, 0.05)), - 25.0, - ), - ); - res = op_u( - res, - vec2( - sd_cone(pos - vec3(0.0, 0.45, -1.0), vec2(0.6, 0.8), 0.45), - 55.0, - ), - ); - res = op_u( - res, - vec2( - sd_capped_cone_vertical(pos - vec3(0.0, 0.25, -2.0), 0.25, 0.25, 0.1), - 13.67, - ), - ); - res = op_u( - res, - vec2( - sd_solid_angle(pos - vec3(0.0, 0.00, -3.0), vec2(3.0, 4.0) / 5.0, 0.4), - 49.13, - ), - ); - } - - // bounding box - if sd_box(pos - vec3(1.0, 0.3, -1.0), vec3(0.35, 0.3, 2.5)) < res.x { - // more primitives - res = op_u( - res, - vec2( - sd_capped_torus( - (pos - vec3(1.0, 0.30, 1.0)) * vec3(1.0, -1.0, 1.0), - vec2(0.866025, -0.5), - 0.25, - 0.05, - ), - 8.5, - ), - ); - res = op_u( - res, - vec2( - sd_box(pos - vec3(1.0, 0.25, 0.0), vec3(0.3, 0.25, 0.1)), - 3.0, - ), - ); - res = op_u( - res, - vec2( - sd_capsule( - pos - vec3(1.0, 0.00, -1.0), - vec3(-0.1, 0.1, -0.1), - vec3(0.2, 0.4, 0.2), - 0.1, - ), - 31.9, - ), - ); - res = op_u( - res, - vec2( - sd_cylinder_vertical(pos - vec3(1.0, 0.25, -2.0), vec2(0.15, 0.25)), - 8.0, - ), - ); - res = op_u( - res, - vec2( - sd_hex_prism(pos - vec3(1.0, 0.2, -3.0), vec2(0.2, 0.05)), - 18.4, - ), - ); - } - - // bounding box - if sd_box(pos - vec3(-1.0, 0.3, -1.0), vec3(0.35, 0.35, 2.5)) < res.x { - // more primitives - res = op_u( - res, - vec2(sd_pyramid(pos - vec3(-1.0, -0.6, -3.0), 1.0), 13.56), - ); - res = op_u( - res, - vec2(sd_octahedron(pos - vec3(-1.0, 0.15, -2.0), 0.35), 23.56), - ); - res = op_u( - res, - vec2( - sd_tri_prism(pos - vec3(-1.0, 0.15, -1.0), vec2(0.3, 0.05)), - 43.5, - ), - ); - res = op_u( - res, - vec2( - sd_ellipsoid(pos - vec3(-1.0, 0.25, 0.0), vec3(0.2, 0.25, 0.05)), - 43.17, - ), - ); - res = op_u( - res, - vec2( - sd_rhombus((pos - vec3(-1.0, 0.34, 1.0)).xzy(), 0.15, 0.25, 0.04, 0.08), - 17.0, - ), - ); - } - // bounding box - if sd_box(pos - vec3(2.0, 0.3, -1.0), vec3(0.35, 0.3, 2.5)) < res.x { - // more primitives - res = op_u( - res, - vec2( - sd_octogon_prism(pos - vec3(2.0, 0.2, -3.0), 0.2, 0.05), - 51.8, - ), - ); - res = op_u( - res, - vec2( - sd_cylinder( - pos - vec3(2.0, 0.15, -2.0), - vec3(0.1, -0.1, 0.0), - vec3(-0.2, 0.35, 0.1), - 0.08, - ), - 31.2, - ), - ); - res = op_u( - res, - vec2( - sd_capped_cone( - pos - vec3(2.0, 0.10, -1.0), - vec3(0.1, 0.0, 0.0), - vec3(-0.2, 0.40, 0.1), - 0.15, - 0.05, - ), - 46.1, - ), - ); - res = op_u( - res, - vec2( - sd_round_cone( - pos - vec3(2.0, 0.15, 0.0), - vec3(0.1, 0.0, 0.0), - vec3(-0.1, 0.35, 0.1), - 0.15, - 0.05, - ), - 51.7, - ), - ); - res = op_u( - res, - vec2( - sd_round_cone_vertical(pos - vec3(2.0, 0.20, 1.0), 0.2, 0.1, 0.3), - 37.0, - ), - ); - } - - res -} - -// http://iquilezles.org/www/articles/boxfunctions/boxfunctions.htm -fn i_box(ro: Vec3, rd: Vec3, rad: Vec3) -> Vec2 { - let m: Vec3 = 1.0 / rd; - let n: Vec3 = m * ro; - let k: Vec3 = m.abs() * rad; - let t1: Vec3 = -n - k; - let t2: Vec3 = -n + k; - vec2(t1.x.max(t1.y).max(t1.z), t2.x.min(t2.y).min(t2.z)) -} - -fn raycast(ro: Vec3, rd: Vec3) -> Vec2 { - let mut res: Vec2 = vec2(-1.0, -1.0); - - let mut tmin: f32 = 1.0; - let mut tmax: f32 = 20.0; - - // raytrace floor plane - let tp1: f32 = (0.0 - ro.y) / rd.y; - if tp1 > 0.0 { - tmax = tmax.min(tp1); - res = vec2(tp1, 1.0); - } - //else return res; - - // raymarch primitives - let tb: Vec2 = i_box(ro - vec3(0.0, 0.4, -0.5), rd, vec3(2.5, 0.41, 3.0)); - if tb.x < tb.y && tb.y > 0.0 && tb.x < tmax { - //return vec2(tb.x,2.0); - tmin = tb.x.max(tmin); - tmax = tb.y.min(tmax); - - let mut t: f32 = tmin; - let mut i = 0; - while i < 70 && t < tmax { - let h: Vec2 = map(ro + rd * t); - if h.x.abs() < (0.0001 * t) { - res = vec2(t, h.y); - break; - } - t += h.x; - i += 1; - } - } - - res -} - -impl Inputs { - // http://iquilezles.org/www/articles/rmshadows/rmshadows.htm - fn calc_softshadow(&self, ro: Vec3, rd: Vec3, mint: f32, mut tmax: f32) -> f32 { - // bounding volume - let tp: f32 = (0.8 - ro.y) / rd.y; - if tp > 0.0 { - tmax = tmax.min(tp); - } - - let mut res: f32 = 1.0; - let mut t: f32 = mint; - for _ in 0..24 { - let h: f32 = map(ro + rd * t).x; - let s: f32 = (8.0 * h / t).clamp(0.0, 1.0); - res = res.min(s * s * (3.0 - 2.0 * s)); - t += h.clamp(0.02, 0.2); - if res < 0.004 || t > tmax { - break; - } - } - res.clamp(0.0, 1.0) - } - - // http://iquilezles.org/www/articles/normalsSDF/normalsSDF.htm - fn calc_normal(&self, pos: Vec3) -> Vec3 { - if false { - let e: Vec2 = vec2(1.0, -1.0) * 0.5773 * 0.0005; - (e.xyy() * map(pos + e.xyy()).x - + e.yyx() * map(pos + e.yyx()).x - + e.yxy() * map(pos + e.yxy()).x - + e.xxx() * map(pos + e.xxx()).x) - .normalize() - } else { - // inspired by tdhooper and klems - a way to prevent the compiler from inlining map() 4 times - let mut n: Vec3 = Vec3::ZERO; - for i in 0..4 { - let e: Vec3 = 0.5773 - * (2.0 - * vec3( - (((i + 3) >> 1) & 1) as f32, - ((i >> 1) & 1) as f32, - (i & 1) as f32, - ) - - Vec3::ONE); - n += e * map(pos + 0.0005 * e).x; - //if n.x+n.y+n.z>100.0 {break;} - } - n.normalize() - } - } - - fn calc_ao(&self, pos: Vec3, nor: Vec3) -> f32 { - let mut occ: f32 = 0.0; - let mut sca: f32 = 1.0; - for i in 0..5 { - let h: f32 = 0.01 + 0.12 * i as f32 / 4.0; - let d: f32 = map(pos + h * nor).x; - occ += (h - d) * sca; - sca *= 0.95; - if occ > 0.35 { - break; - } - } - (1.0 - 3.0 * occ).clamp(0.0, 1.0) * (0.5 + 0.5 * nor.y) - } -} - -// http://iquilezles.org/www/articles/checkerfiltering/checkerfiltering.htm -fn checkers_grad_box(p: Vec2, dpdx: Vec2, dpdy: Vec2) -> f32 { - // filter kernel - let w: Vec2 = dpdx.abs() + dpdy.abs() + Vec2::splat(0.001); - // analytical integral (box filter) - let i: Vec2 = 2.0 - * ((((p - 0.5 * w) * 0.5).fract_gl() - Vec2::splat(0.5)).abs() - - (((p + 0.5 * w) * 0.5).fract_gl() - Vec2::splat(0.5)).abs()) - / w; - // xor pattern - 0.5 - 0.5 * i.x * i.y -} - -impl Inputs { - fn render(&self, ro: Vec3, rd: Vec3, rdx: Vec3, rdy: Vec3) -> Vec3 { - // background - let mut col: Vec3 = vec3(0.7, 0.7, 0.9) - Vec3::splat(rd.y.max(0.0) * 0.3); - - // raycast scene - let res: Vec2 = raycast(ro, rd); - let t: f32 = res.x; - let m: f32 = res.y; - if m > -0.5 { - let pos: Vec3 = ro + t * rd; - let nor: Vec3 = if m < 1.5 { - vec3(0.0, 1.0, 0.0) - } else { - self.calc_normal(pos) - }; - let ref_: Vec3 = rd.reflect(nor); - - // material - col = Vec3::splat(0.2) + 0.2 * (Vec3::splat(m) * 2.0 + vec3(0.0, 1.0, 2.0)).sin(); - let mut ks: f32 = 1.0; - - if m < 1.5 { - // project pixel footprint into the plane - let dpdx: Vec3 = ro.y * (rd / rd.y - rdx / rdx.y); - let dpdy: Vec3 = ro.y * (rd / rd.y - rdy / rdy.y); - - let f: f32 = checkers_grad_box(3.0 * pos.xz(), 3.0 * dpdx.xz(), 3.0 * dpdy.xz()); - col = Vec3::splat(0.15) + f * Vec3::splat(0.05); - ks = 0.4; - } - - // lighting - let occ: f32 = self.calc_ao(pos, nor); - - let mut lin: Vec3 = Vec3::ZERO; - - // sun - { - let lig: Vec3 = (vec3(-0.5, 0.4, -0.6)).normalize(); - let hal: Vec3 = (lig - rd).normalize(); - let mut dif: f32 = nor.dot(lig).clamp(0.0, 1.0); - //if( dif>0.0001 ) - dif *= self.calc_softshadow(pos, lig, 0.02, 2.5); - let mut spe: f32 = nor.dot(hal).clamp(0.0, 1.0).powf(16.0); - spe *= dif; - spe *= 0.04 + 0.96 * (1.0 - hal.dot(lig)).clamp(0.0, 1.0).powf(5.0); - lin += col * 2.20 * dif * vec3(1.30, 1.00, 0.70); - lin += 5.00 * spe * vec3(1.30, 1.00, 0.70) * ks; - } - // sky - { - let mut dif: f32 = (0.5 + 0.5 * nor.y).clamp(0.0, 1.0).sqrt(); - dif *= occ; - let mut spe: f32 = smoothstep(-0.2, 0.2, ref_.y); - spe *= dif; - spe *= 0.04 + 0.96 * (1.0 + nor.dot(rd)).clamp(0.0, 1.0).powf(5.0); - //if( spe>0.001 ) - spe *= self.calc_softshadow(pos, ref_, 0.02, 2.5); - lin += col * 0.60 * dif * vec3(0.40, 0.60, 1.15); - lin += 2.00 * spe * vec3(0.40, 0.60, 1.30) * ks; - } - // back - { - let mut dif: f32 = nor.dot(vec3(0.5, 0.0, 0.6).normalize()).clamp(0.0, 1.0) - * (1.0 - pos.y).clamp(0.0, 1.0); - dif *= occ; - lin += col * 0.55 * dif * vec3(0.25, 0.25, 0.25); - } - // sss - { - let mut dif: f32 = (1.0 + nor.dot(rd)).clamp(0.0, 1.0).powf(2.0); - dif *= occ; - lin += col * 0.25 * dif * vec3(1.00, 1.00, 1.00); - } - - col = lin; - - col = mix(col, vec3(0.7, 0.7, 0.9), 1.0 - (-0.0001 * t * t * t).exp()); - } - col.clamp(Vec3::ZERO, Vec3::ONE) - } -} - -fn set_camera(ro: Vec3, ta: Vec3, cr: f32) -> Mat3 { - let cw: Vec3 = (ta - ro).normalize(); - let cp: Vec3 = vec3(cr.sin(), cr.cos(), 0.0); - let cu: Vec3 = cw.cross(cp).normalize(); - let cv: Vec3 = cu.cross(cw); - Mat3::from_cols(cu, cv, cw) -} - -impl Inputs { - pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { - let mo: Vec2 = self.mouse.xy() / self.resolution.xy(); - let time: f32 = 32.0 + self.time * 1.5; - - // camera - let ta: Vec3 = vec3(0.5, -0.5, -0.6); - let ro: Vec3 = ta - + vec3( - 4.5 * (0.1 * time + 7.0 * mo.x).cos(), - 1.3 + 2.0 * mo.y, - 4.5 * (0.1 * time + 7.0 * mo.x).sin(), - ); - // camera-to-world transformation - let ca: Mat3 = set_camera(ro, ta, 0.0); - - let mut tot: Vec3 = Vec3::ZERO; - let mut p: Vec2; - - for m in 0..AA { - for n in 0..AA { - // pixel coordinates - let o: Vec2 = vec2(m as f32, n as f32) / AA as f32 - Vec2::splat(0.5); - if AA > 1 { - p = (2.0 * (frag_coord + o) - self.resolution.xy()) / self.resolution.y; - } else { - p = (2.0 * frag_coord - self.resolution.xy()) / self.resolution.y; - } - - // ray direction - let rd: Vec3 = ca * p.extend(2.5).normalize(); - - // ray differentials - let px: Vec2 = (2.0 * (frag_coord + vec2(1.0, 0.0)) - self.resolution.xy()) - / self.resolution.y; - let py: Vec2 = (2.0 * (frag_coord + vec2(0.0, 1.0)) - self.resolution.xy()) - / self.resolution.y; - let rdx: Vec3 = ca * px.extend(2.5).normalize(); - let rdy: Vec3 = ca * py.extend(2.5).normalize(); - - // render - let mut col: Vec3 = self.render(ro, rd, rdx, rdy); - - // gain - // col = col*3.0/(2.5+col); - - // gamma - col = col.powf_vec(Vec3::splat(0.4545)); - - tot += col; - } - } - tot /= (AA * AA) as f32; - - *frag_color = tot.extend(1.0); - } -} diff --git a/shaders/src/seascape.rs b/shaders/src/seascape.rs deleted file mode 100644 index 400edba..0000000 --- a/shaders/src/seascape.rs +++ /dev/null @@ -1,251 +0,0 @@ -//! Ported to Rust from -//! -//! Original comment: -//! ```glsl -//! /* -//! * "Seascape" by Alexander Alekseev aka TDM - 2014 -//! * License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. -//! * Contact: tdmaav@gmail.com -//! */ -//! ``` - -use shared::*; -use spirv_std::glam::{mat2, vec2, vec3, Mat2, Mat3, Vec2, Vec3, Vec3Swizzles, Vec4}; - -// Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but -// we tie #[no_std] above to the same condition, so it's fine. -#[cfg(target_arch = "spirv")] -use spirv_std::num_traits::Float; - -pub struct Inputs { - pub resolution: Vec3, - pub time: f32, - pub mouse: Vec4, -} - -const NUM_STEPS: usize = 8; -const PI: f32 = 3.141592; -const _EPSILON: f32 = 1e-3; -impl Inputs { - fn epsilon_nrm(&self) -> f32 { - 0.1 / self.resolution.x - } -} -const AA: bool = true; - -// sea -const ITER_GEOMETRY: usize = 3; -const ITER_FRAGMENT: usize = 5; -const SEA_HEIGHT: f32 = 0.6; -const SEA_CHOPPY: f32 = 4.0; -const SEA_SPEED: f32 = 0.8; -const SEA_FREQ: f32 = 0.16; -const SEA_BASE: Vec3 = vec3(0.0, 0.09, 0.18); -// const SEA_WATER_COLOR: Vec3 = const_vec3!([0.8, 0.9, 0.6]) * 0.6; -const SEA_WATER_COLOR: Vec3 = vec3(0.8 * 0.6, 0.9 * 0.6, 0.6 * 0.6); -impl Inputs { - fn sea_time(&self) -> f32 { - 1.0 + self.time * SEA_SPEED - } -} -const OCTAVE_M: Mat2 = mat2(vec2(1.6, 1.2), vec2(-1.2, 1.6)); - -// math -fn from_euler(ang: Vec3) -> Mat3 { - let a1: Vec2 = vec2(ang.x.sin(), ang.x.cos()); - let a2: Vec2 = vec2(ang.y.sin(), ang.y.cos()); - let a3: Vec2 = vec2(ang.z.sin(), ang.z.cos()); - Mat3::from_cols( - vec3( - a1.y * a3.y + a1.x * a2.x * a3.x, - a1.y * a2.x * a3.x + a3.y * a1.x, - -a2.y * a3.x, - ), - vec3(-a2.y * a1.x, a1.y * a2.y, a2.x), - vec3( - a3.y * a1.x * a2.x + a1.y * a3.x, - a1.x * a3.x - a1.y * a3.y * a2.x, - a2.y * a3.y, - ), - ) -} -fn hash(p: Vec2) -> f32 { - let h: f32 = p.dot(vec2(127.1, 311.7)); - (h.sin() * 43758.5453123).fract_gl() -} -fn noise(p: Vec2) -> f32 { - let i: Vec2 = p.floor(); - let f: Vec2 = p.fract_gl(); - let u: Vec2 = f * f * (Vec2::splat(3.0) - 2.0 * f); - -1.0 + 2.0 - * mix( - mix(hash(i + vec2(0.0, 0.0)), hash(i + vec2(1.0, 0.0)), u.x), - mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), u.x), - u.y, - ) -} - -// lighting -fn diffuse(n: Vec3, l: Vec3, p: f32) -> f32 { - (n.dot(l) * 0.4 + 0.6).powf(p) -} -fn specular(n: Vec3, l: Vec3, e: Vec3, s: f32) -> f32 { - let nrm: f32 = (s + 8.0) / (PI * 8.0); - (e.reflect(n).dot(l).max(0.0)).powf(s) * nrm -} - -// sky -fn get_sky_color(mut e: Vec3) -> Vec3 { - e.y = (e.y.max(0.0) * 0.8 + 0.2) * 0.8; - vec3((1.0 - e.y).powf(2.0), 1.0 - e.y, 0.6 + (1.0 - e.y) * 0.4) * 1.1 -} - -// sea -fn sea_octave(mut uv: Vec2, choppy: f32) -> f32 { - uv += Vec2::splat(noise(uv)); - let mut wv: Vec2 = Vec2::ONE - uv.sin().abs(); - let swv: Vec2 = uv.cos().abs(); - wv = mix(wv, swv, wv); - (1.0 - (wv.x * wv.y).powf(0.65)).powf(choppy) -} - -impl Inputs { - fn map(&self, p: Vec3) -> f32 { - let mut freq: f32 = SEA_FREQ; - let mut amp: f32 = SEA_HEIGHT; - let mut choppy: f32 = SEA_CHOPPY; - let mut uv: Vec2 = p.xz(); - uv.x *= 0.75; - - let mut d: f32; - let mut h: f32 = 0.0; - - for _ in 0..ITER_GEOMETRY { - d = sea_octave((uv + Vec2::splat(self.sea_time())) * freq, choppy); - d += sea_octave((uv - Vec2::splat(self.sea_time())) * freq, choppy); - h += d * amp; - let octave_m = OCTAVE_M; - uv = octave_m.transpose() * uv; - freq *= 1.9; - amp *= 0.22; - choppy = mix(choppy, 1.0, 0.2); - } - p.y - h - } - - fn map_detailed(&self, p: Vec3) -> f32 { - let mut freq: f32 = SEA_FREQ; - let mut amp: f32 = SEA_HEIGHT; - let mut choppy: f32 = SEA_CHOPPY; - let mut uv: Vec2 = p.xz(); - uv.x *= 0.75; - let mut d: f32; - let mut h: f32 = 0.0; - for _ in 0..ITER_FRAGMENT { - d = sea_octave((uv + Vec2::splat(self.sea_time())) * freq, choppy); - d += sea_octave((uv - Vec2::splat(self.sea_time())) * freq, choppy); - h += d * amp; - let octave_m = OCTAVE_M; - uv = octave_m.transpose() * uv; - freq *= 1.9; - amp *= 0.22; - choppy = mix(choppy, 1.0, 0.2); - } - p.y - h - } -} - -fn get_sea_color(p: Vec3, n: Vec3, l: Vec3, eye: Vec3, dist: Vec3) -> Vec3 { - let mut fresnel: f32 = (1.0 - n.dot(-eye)).clamp(0.0, 1.0); - fresnel = fresnel.powf(3.0) * 0.5; - - let reflected: Vec3 = get_sky_color(eye.reflect(n)); - let refracted: Vec3 = SEA_BASE + diffuse(n, l, 80.0) * SEA_WATER_COLOR * 0.12; - - let mut color: Vec3 = mix(refracted, reflected, fresnel); - let atten: f32 = (1.0 - dist.dot(dist) * 0.001).max(0.0); - color += SEA_WATER_COLOR * (p.y - SEA_HEIGHT) * 0.18 * atten; - - color += Vec3::splat(specular(n, l, eye, 60.0)); - color -} - -impl Inputs { - // tracing - fn get_normal(&self, p: Vec3, eps: f32) -> Vec3 { - let mut n: Vec3 = Vec3::ZERO; - n.y = self.map_detailed(p); - n.x = self.map_detailed(vec3(p.x + eps, p.y, p.z)) - n.y; - n.z = self.map_detailed(vec3(p.x, p.y, p.z + eps)) - n.y; - n.y = eps; - n.normalize() - } - - fn height_map_tracing(&self, ori: Vec3, dir: Vec3, p: &mut Vec3) -> f32 { - let mut tm: f32 = 0.0; - let mut tx: f32 = 1000.0; - let mut hx: f32 = self.map(ori + dir * tx); - if hx > 0.0 { - return tx; - } - let mut hm: f32 = self.map(ori + dir * tm); - let mut tmid: f32 = 0.0; - for _ in 0..NUM_STEPS { - tmid = mix(tm, tx, hm / (hm - hx)); - *p = ori + dir * tmid; - let hmid: f32 = self.map(*p); - if hmid < 0.0 { - tx = tmid; - hx = hmid; - } else { - tm = tmid; - hm = hmid; - } - } - tmid - } - - fn get_pixel(&self, coord: Vec2, time: f32) -> Vec3 { - let mut uv: Vec2 = coord / self.resolution.xy(); - uv = uv * 2.0 - Vec2::ONE; - uv.x *= self.resolution.x / self.resolution.y; - // ray - let ang: Vec3 = vec3((time * 3.0).sin() * 0.1, time.sin() * 0.2 + 0.3, time); - let ori: Vec3 = vec3(0.0, 3.5, time * 5.0); - let mut dir: Vec3 = uv.extend(-2.0).normalize(); - dir.z += uv.length() * 0.14; - dir = from_euler(ang).transpose() * dir.normalize(); - // tracing - let mut p: Vec3 = Vec3::ZERO; - self.height_map_tracing(ori, dir, &mut p); - let dist: Vec3 = p - ori; - let n: Vec3 = self.get_normal(p, dist.dot(dist) * self.epsilon_nrm()); - let light: Vec3 = vec3(0.0, 1.0, 0.8).normalize(); - // color - mix( - get_sky_color(dir), - get_sea_color(p, n, light, dir, dist), - smoothstep(0.0, -0.02, dir.y).powf(0.2), - ) - } - - // main - pub fn main_image(&self, frag_color: &mut Vec4, frag_coord: Vec2) { - let time: f32 = self.time * 0.3 + self.mouse.x * 0.01; - let mut color: Vec3; - if AA { - color = Vec3::ZERO; - for i in -1..=1 { - for j in -1..=1 { - let uv: Vec2 = frag_coord + vec2(i as f32, j as f32) / 3.0; - color += self.get_pixel(uv, time); - } - } - color /= 9.0; - } else { - color = self.get_pixel(frag_coord, time); - } - // post - *frag_color = color.powf(0.65).extend(1.0); - } -} diff --git a/shaders/src/shader_prelude.rs b/shaders/src/shader_prelude.rs new file mode 100644 index 0000000..18da845 --- /dev/null +++ b/shaders/src/shader_prelude.rs @@ -0,0 +1,324 @@ +pub use core::f32::consts::{FRAC_1_PI, FRAC_PI_2, PI, TAU}; +use core::ops::{Add, Mul, Sub}; +/// We can't use the `f32::consts::SQRT_3` constant here because it is an unstable library feature +pub const SQRT_3: f32 = 1.732_050_807_568_877_2; + +pub use crate::shared_data::ShaderConstants; +pub use spirv_std::{ + arch::Derivative, + glam::{ + mat2, mat3, vec2, vec3, vec4, Mat2, Mat3, Vec2, Vec2Swizzles, Vec3, Vec3Swizzles, Vec4, + Vec4Swizzles, + }, + spirv, +}; + +// Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but +// we tie #[no_std] above to the same condition, so it's fine. +#[cfg(target_arch = "spirv")] +pub use spirv_std::num_traits::Float; + +pub trait SampleCube: Copy { + fn sample_cube(self, p: Vec3) -> Vec4; +} + +#[derive(Copy, Clone)] +pub struct ConstantColor { + pub color: Vec4, +} + +impl SampleCube for ConstantColor { + fn sample_cube(self, _: Vec3) -> Vec4 { + self.color + } +} + +#[derive(Copy, Clone)] +pub struct RgbCube { + pub alpha: f32, + pub intensity: f32, +} + +impl SampleCube for RgbCube { + fn sample_cube(self, p: Vec3) -> Vec4 { + (p.abs() * self.intensity).extend(self.alpha) + } +} + +pub struct ShaderInput { + pub resolution: Vec3, + pub time: f32, + pub frag_coord: Vec2, + /// https://www.shadertoy.com/view/Mss3zH + pub mouse: Vec4, +} + +pub struct ShaderResult { + pub color: Vec4, +} + +pub struct ShaderDefinition { + pub name: &'static str, +} + +#[inline(always)] +#[must_use] +pub fn saturate_vec3(a: Vec3) -> Vec3 { + a.clamp(Vec3::ZERO, Vec3::ONE) +} +#[inline(always)] +#[must_use] +pub fn saturate_vec2(a: Vec2) -> Vec2 { + a.clamp(Vec2::ZERO, Vec2::ONE) +} +#[inline(always)] +#[must_use] +pub fn saturate(a: f32) -> f32 { + a.clamp(0.0, 1.0) +} + +/// Based on: https://seblagarde.wordpress.com/2014/12/01/inverse-trigonometric-functions-gpu-optimization-for-amd-gcn-architecture/ +#[inline] +#[must_use] +pub fn acos_approx(v: f32) -> f32 { + let x = v.abs(); + let mut res = (-0.155_972_f32).mul_add(x, 1.56467); // p(x) + res *= (1.0f32 - x).sqrt(); + + if v >= 0.0 { + res + } else { + PI - res + } +} + +#[inline(always)] +#[must_use] +pub fn smoothstep(edge0: f32, edge1: f32, x: f32) -> f32 { + // Scale, bias and saturate x to 0..1 range + let x = saturate((x - edge0) / (edge1 - edge0)); + // Evaluate polynomial + x * x * 2.0f32.mul_add(-x, 3.0) +} + +#[inline(always)] +#[must_use] +pub fn mix + Add + Sub, A: Copy>( + x: X, + y: X, + a: A, +) -> X { + x - x * a + y * a +} + +pub trait Clamp { + #[must_use] + fn clamp(self, min: Self, max: Self) -> Self; +} + +impl Clamp for f32 { + #[inline(always)] + fn clamp(self, min: Self, max: Self) -> Self { + self.max(min).min(max) + } +} + +pub trait FloatExt { + #[must_use] + fn fract_gl(self) -> Self; + #[must_use] + fn rem_euclid(self, rhs: Self) -> Self; + #[must_use] + fn sign_gl(self) -> Self; + #[must_use] + fn step(self, x: Self) -> Self; +} + +impl FloatExt for f32 { + #[inline] + fn fract_gl(self) -> Self { + self - self.floor() + } + + #[inline] + fn rem_euclid(self, rhs: Self) -> Self { + let r = self % rhs; + if r < 0.0 { + r + rhs.abs() + } else { + r + } + } + + #[inline] + fn sign_gl(self) -> Self { + if self < 0.0 { + -1.0 + } else if self == 0.0 { + 0.0 + } else { + 1.0 + } + } + + #[inline] + fn step(self, x: Self) -> Self { + if x < self { + 0.0 + } else { + 1.0 + } + } +} + +pub trait VecExt { + #[must_use] + fn sin(self) -> Self; + #[must_use] + fn cos(self) -> Self; + #[must_use] + fn powf_vec(self, p: Self) -> Self; + #[must_use] + fn sqrt(self) -> Self; + #[must_use] + fn ln(self) -> Self; + #[must_use] + fn step(self, other: Self) -> Self; + #[must_use] + fn sign_gl(self) -> Self; +} + +impl VecExt for Vec2 { + #[inline] + fn sin(self) -> Self { + vec2(self.x.sin(), self.y.sin()) + } + + #[inline] + fn cos(self) -> Self { + vec2(self.x.cos(), self.y.cos()) + } + + #[inline] + fn powf_vec(self, p: Self) -> Self { + vec2(self.x.powf(p.x), self.y.powf(p.y)) + } + + #[inline] + fn sqrt(self) -> Self { + vec2(self.x.sqrt(), self.y.sqrt()) + } + + #[inline] + fn ln(self) -> Self { + vec2(self.x.ln(), self.y.ln()) + } + + #[inline] + fn step(self, other: Self) -> Self { + vec2(self.x.step(other.x), self.y.step(other.y)) + } + + #[inline] + fn sign_gl(self) -> Self { + vec2(self.x.sign_gl(), self.y.sign_gl()) + } +} + +impl VecExt for Vec3 { + #[inline] + fn sin(self) -> Self { + vec3(self.x.sin(), self.y.sin(), self.z.sin()) + } + + #[inline] + fn cos(self) -> Self { + vec3(self.x.cos(), self.y.cos(), self.z.cos()) + } + + #[inline] + fn powf_vec(self, p: Self) -> Self { + vec3(self.x.powf(p.x), self.y.powf(p.y), self.z.powf(p.z)) + } + + #[inline] + fn sqrt(self) -> Self { + vec3(self.x.sqrt(), self.y.sqrt(), self.z.sqrt()) + } + + #[inline] + fn ln(self) -> Self { + vec3(self.x.ln(), self.y.ln(), self.z.ln()) + } + + #[inline] + fn step(self, other: Self) -> Self { + vec3( + self.x.step(other.x), + self.y.step(other.y), + self.z.step(other.z), + ) + } + + #[inline] + fn sign_gl(self) -> Self { + vec3(self.x.sign_gl(), self.y.sign_gl(), self.z.sign_gl()) + } +} + +impl VecExt for Vec4 { + #[inline] + fn sin(self) -> Self { + vec4(self.x.sin(), self.y.sin(), self.z.sin(), self.w.sin()) + } + + #[inline] + fn cos(self) -> Self { + vec4(self.x.cos(), self.y.cos(), self.z.cos(), self.w.cos()) + } + + #[inline] + fn powf_vec(self, p: Self) -> Self { + vec4( + self.x.powf(p.x), + self.y.powf(p.y), + self.z.powf(p.z), + self.w.powf(p.w), + ) + } + + #[inline] + fn sqrt(self) -> Self { + vec4(self.x.sqrt(), self.y.sqrt(), self.z.sqrt(), self.w.sqrt()) + } + + #[inline] + fn ln(self) -> Self { + vec4(self.x.ln(), self.y.ln(), self.z.ln(), self.w.ln()) + } + + #[inline] + fn step(self, other: Self) -> Self { + vec4( + self.x.step(other.x), + self.y.step(other.y), + self.z.step(other.z), + self.w.step(other.w), + ) + } + + #[inline] + fn sign_gl(self) -> Self { + vec4( + self.x.sign_gl(), + self.y.sign_gl(), + self.z.sign_gl(), + self.w.sign_gl(), + ) + } +} + +#[inline(always)] +pub fn discard() { + unsafe { spirv_std::arch::demote_to_helper_invocation() } +} diff --git a/shaders/src/shaders/a_lot_of_spheres.rs b/shaders/src/shaders/a_lot_of_spheres.rs new file mode 100644 index 0000000..7e58eef --- /dev/null +++ b/shaders/src/shaders/a_lot_of_spheres.rs @@ -0,0 +1,318 @@ +//! Ported to Rust from +//! +//! Original comment: +//! ```glsl +//! // A lot of spheres. Created by Reinder Nijhoff 2013 +//! // Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. +//! // @reindernijhoff +//! // +//! // https://www.shadertoy.com/view/lsX3WH +//! // +//! */ +//! ``` + +use crate::shader_prelude::*; + +pub const SHADER_DEFINITION: ShaderDefinition = ShaderDefinition { + name: "A Lot of Spheres", +}; + +pub fn shader_fn(render_instruction: &ShaderInput, render_result: &mut ShaderResult) { + let color = &mut render_result.color; + let &ShaderInput { + resolution, + time, + frag_coord, + .. + } = render_instruction; + Inputs { resolution, time }.main_image(color, frag_coord); +} + +struct Inputs { + resolution: Vec3, + time: f32, +} + +const SHADOW: bool = true; +const REFLECTION: bool = true; + +const RAYCASTSTEPS: usize = 40; + +const EXPOSURE: f32 = 0.9; +const EPSILON: f32 = 0.0001; +const MAXDISTANCE: f32 = 400.0; +const GRIDSIZE: f32 = 8.0; +const GRIDSIZESMALL: f32 = 5.0; +const MAXHEIGHT: f32 = 30.0; +const SPEED: f32 = 0.5; + +// +// math functions +// + +const _MR: Mat2 = mat2(vec2(0.84147, 0.54030), vec2(0.54030, -0.84147)); +fn hash(n: f32) -> f32 { + (n.sin() * 43758.5453).fract_gl() +} +fn _hash2(n: f32) -> Vec2 { + (vec2(n, n + 1.0).sin() * vec2(2.1459123, 3.3490423)).fract_gl() +} +fn hash2_vec(n: Vec2) -> Vec2 { + (vec2(n.x * n.y, n.x + n.y).sin() * vec2(2.1459123, 3.3490423)).fract_gl() +} +fn _hash3(n: f32) -> Vec3 { + (vec3(n, n + 1.0, n + 2.0).sin() * vec3(3.5453123, 4.1459123, 1.3490423)).fract_gl() +} +fn hash3_vec(n: Vec2) -> Vec3 { + (vec3(n.x, n.y, n.x + 2.0).sin() * vec3(3.5453123, 4.1459123, 1.3490423)).fract_gl() +} + +// +// intersection functions +// + +fn intersect_plane(ro: Vec3, rd: Vec3, height: f32, dist: &mut f32) -> bool { + if rd.y == 0.0 { + return false; + } + + let mut d: f32 = -(ro.y - height) / rd.y; + d = d.min(100_000.0); + if d > 0.0 { + *dist = d; + return true; + } + false +} + +fn intersect_unit_sphere(ro: Vec3, rd: Vec3, sph: Vec3, dist: &mut f32, normal: &mut Vec3) -> bool { + let ds: Vec3 = ro - sph; + let bs: f32 = rd.dot(ds); + let cs: f32 = ds.dot(ds) - 1.0; + let mut ts: f32 = bs * bs - cs; + + if ts > 0.0 { + ts = -bs - ts.sqrt(); + if ts > 0.0 { + *normal = ((ro + ts * rd) - sph).normalize(); + *dist = ts; + return true; + } + } + false +} + +// +// Scene +// + +fn get_sphere_offset(grid: Vec2, center: &mut Vec2) { + *center = (hash2_vec(grid + vec2(43.12, 1.23)) - Vec2::splat(0.5)) * (GRIDSIZESMALL); +} + +impl Inputs { + fn get_moving_sphere_position(&self, grid: Vec2, sphere_offset: Vec2, center: &mut Vec3) { + // falling? + let s: f32 = 0.1 + hash(grid.x * 1.23114 + 5.342 + 74.324231 * grid.y); + let t: f32 = (14. * s + self.time / s * 0.3).fract_gl(); + + let y: f32 = s * MAXHEIGHT * (4.0 * t * (1. - t)).abs(); + let offset: Vec2 = grid + sphere_offset; + + *center = vec3(offset.x, y, offset.y) + 0.5 * vec3(GRIDSIZE, 2.0, GRIDSIZE); + } +} +fn get_sphere_position(grid: Vec2, sphere_offset: Vec2, center: &mut Vec3) { + let offset: Vec2 = grid + sphere_offset; + *center = vec3(offset.x, 0.0, offset.y) + 0.5 * vec3(GRIDSIZE, 2.0, GRIDSIZE); +} +fn get_sphere_color(grid: Vec2) -> Vec3 { + hash3_vec(grid + vec2(43.12 * grid.y, 12.23 * grid.x)).normalize() +} + +impl Inputs { + fn trace( + &self, + ro: Vec3, + rd: Vec3, + intersection: &mut Vec3, + normal: &mut Vec3, + dist: &mut f32, + material: &mut i32, + ) -> Vec3 { + *material = 0; // sky + *dist = MAXDISTANCE; + let mut distcheck: f32 = 0.0; + + let mut sphere_center: Vec3 = Vec3::ZERO; + let mut col: Vec3; + let mut normalcheck: Vec3 = Vec3::ZERO; + if intersect_plane(ro, rd, 0.0, &mut distcheck) && distcheck < MAXDISTANCE { + *dist = distcheck; + *material = 1; + *normal = vec3(0.0, 1.0, 0.0); + col = Vec3::ONE; + } else { + col = Vec3::ZERO; + } + + // trace grid + let mut pos: Vec3 = (ro / GRIDSIZE).floor() * GRIDSIZE; + let ri: Vec3 = 1.0 / rd; + let rs: Vec3 = rd.sign_gl() * GRIDSIZE; + let mut dis: Vec3 = (pos - ro + 0.5 * Vec3::splat(GRIDSIZE) + rs * 0.5) * ri; + let mut mm: Vec3; + + for _ in 0..RAYCASTSTEPS { + if *material > 1 || ro.xz().distance(pos.xz()) > *dist + GRIDSIZE { + break; + } + let mut offset: Vec2 = Vec2::ZERO; + get_sphere_offset(pos.xz(), &mut offset); + + self.get_moving_sphere_position(pos.xz(), -offset, &mut sphere_center); + + if intersect_unit_sphere(ro, rd, sphere_center, &mut distcheck, &mut normalcheck) + && distcheck < *dist + { + *dist = distcheck; + *normal = normalcheck; + *material = 2; + } + + get_sphere_position(pos.xz(), offset, &mut sphere_center); + if intersect_unit_sphere(ro, rd, sphere_center, &mut distcheck, &mut normalcheck) + && distcheck < *dist + { + *dist = distcheck; + *normal = normalcheck; + col = Vec3::splat(2.0); + *material = 3; + } + mm = dis.step(dis.zyx()); + dis += mm * rs * ri; + pos += mm * rs; + } + + let mut color: Vec3 = Vec3::ZERO; + if *material > 0 { + *intersection = ro + rd * *dist; + let map: Vec2 = (intersection.xz() / GRIDSIZE).floor() * GRIDSIZE; + + if *material == 1 || *material == 3 { + // lightning + let c: Vec3 = vec3(-GRIDSIZE, 0.0, GRIDSIZE); + for x in 0..3 { + for y in 0..3 { + let mapoffset: Vec2 = map + vec2([c.x, c.y, c.z][x], [c.x, c.y, c.z][y]); + let mut offset: Vec2 = Vec2::ZERO; + get_sphere_offset(mapoffset, &mut offset); + let lcolor: Vec3 = get_sphere_color(mapoffset); + let mut lpos: Vec3 = Vec3::ZERO; + self.get_moving_sphere_position(mapoffset, -offset, &mut lpos); + + let mut shadow: f32 = 1.0; + + if SHADOW && *material == 1 { + for _ in 0..3 { + for _ in 0..3 { + if shadow < 1.0 { + continue; + } + + let smapoffset: Vec2 = map + vec2([c.x, c.y, c.z][x], [c.x, c.y, c.z][y]); + let mut soffset: Vec2 = Vec2::ZERO; + get_sphere_offset(smapoffset, &mut soffset); + let mut slpos: Vec3 = Vec3::ZERO; + let mut sn: Vec3 = Vec3::ZERO; + get_sphere_position(smapoffset, soffset, &mut slpos); + let mut sd: f32 = 0.0; + if intersect_unit_sphere( + *intersection, + (lpos - *intersection).normalize(), + slpos, + &mut sd, + &mut sn, + ) { + shadow = 0.0; + } + } + } + } + color += col + * lcolor + * (shadow + * ((lpos - *intersection).normalize().dot(*normal)).max(0.0) + * (1. - (lpos.distance(*intersection) / GRIDSIZE).clamp(0.0, 1.))); + } + } + } else { + // emitter + color = (1.5 + normal.dot(vec3(0.5, 0.5, -0.5))) * get_sphere_color(map); + } + } + color + } + + fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { + let q: Vec2 = frag_coord / self.resolution.xy(); + let mut p: Vec2 = Vec2::splat(-1.0) + 2.0 * q; + p.x *= self.resolution.x / self.resolution.y; + + // camera + let ce: Vec3 = vec3( + (0.232 * self.time).cos() * 10.0, + 6. + 3.0 * (0.3 * self.time).cos(), + GRIDSIZE * (self.time / SPEED), + ); + let ro: Vec3 = ce; + let ta: Vec3 = ro + + vec3( + -(0.232 * self.time).sin() * 10., + -2.0 + (0.23 * self.time).cos(), + 10.0, + ); + + let roll: f32 = -0.15 * (0.5 * self.time).sin(); + // camera tx + let cw: Vec3 = (ta - ro).normalize(); + let cp: Vec3 = vec3(roll.sin(), roll.cos(), 0.0); + let cu: Vec3 = (cw.cross(cp)).normalize(); + let cv: Vec3 = (cu.cross(cw)).normalize(); + let mut rd: Vec3 = (p.x * cu + p.y * cv + 1.5 * cw).normalize(); + // raytrace + let mut material: i32 = 0; + let mut normal: Vec3 = Vec3::ZERO; + let mut intersection: Vec3 = Vec3::ZERO; + let mut dist: f32 = 0.0; + + let mut col: Vec3 = self.trace( + ro, + rd, + &mut intersection, + &mut normal, + &mut dist, + &mut material, + ); + if material > 0 && REFLECTION { + let ro: Vec3 = intersection + EPSILON * normal; + rd = rd.reflect(normal); + col += 0.05 + * self.trace( + ro, + rd, + &mut intersection, + &mut normal, + &mut dist, + &mut material, + ); + } + + col = col.powf_vec(vec3(EXPOSURE, EXPOSURE, EXPOSURE)); + col = col.clamp(Vec3::ZERO, Vec3::ONE); + // vigneting + col *= 0.25 + 0.75 * (16.0 * q.x * q.y * (1.0 - q.x) * (1.0 - q.y)).powf(0.15); + + *frag_color = col.extend(1.0); + } +} diff --git a/shaders/src/shaders/a_question_of_time.rs b/shaders/src/shaders/a_question_of_time.rs new file mode 100644 index 0000000..fdfe8c6 --- /dev/null +++ b/shaders/src/shaders/a_question_of_time.rs @@ -0,0 +1,329 @@ +//! Ported to Rust from +//! +//! Original comment: +//! ```glsl +//! // Author: Rigel rui@gil.com +//! // licence: https://creativecommons.org/licenses/by/4.0/ +//! // link: https://www.shadertoy.com/view/lljfRD +//! +//! +//! /* +//! This was a study on circles, inspired by this artwork +//! http://www.dailymail.co.uk/news/article-1236380/Worlds-largest-artwork-etched-desert-sand.html +//! +//! and implemented with the help of this article +//! http://www.ams.org/samplings/feature-column/fcarc-kissing +//! +//! The structure is called an apollonian packing (or gasket) +//! https://en.m.wikipedia.org/wiki/Apollonian_gasket +//! +//! There is a lot of apollonians in shadertoy, but not many quite like the image above. +//! This one by klems is really cool. He uses a technique called a soddy circle. +//! https://www.shadertoy.com/view/4s2czK +//! +//! This shader uses another technique called a Descartes Configuration. +//! The only thing that makes this technique interesting is that it can be generalized to higher dimensions. +//! */ +//! ``` + +use crate::shader_prelude::*; + +pub const SHADER_DEFINITION: ShaderDefinition = ShaderDefinition { + name: "A Question of Time", +}; + +pub fn shader_fn(render_instruction: &ShaderInput, render_result: &mut ShaderResult) { + let color = &mut render_result.color; + let &ShaderInput { + resolution, + time, + frag_coord, + mouse, + .. + } = render_instruction; + Inputs { + resolution, + time, + mouse, + } + .main_image(color, frag_coord); +} + +struct Inputs { + resolution: Vec3, + time: f32, + mouse: Vec4, +} + +// a few utility functions +// a signed distance function for a rectangle +fn sdf_rect(uv: Vec2, s: Vec2) -> f32 { + let auv: Vec2 = uv.abs(); + (auv.x - s.x).max(auv.y - s.y) +} +// a signed distance function for a circle +fn sdf_circle(uv: Vec2, c: Vec2, r: f32) -> f32 { + (uv - c).length() - r +} +// fills an sdf in 2d +fn fill(d: f32, s: f32, i: f32) -> f32 { + (smoothstep(0.0, s, d) - i).abs() +} +// makes a stroke of an sdf at the zero boundary +fn stroke(d: f32, w: f32, s: f32, i: f32) -> f32 { + (smoothstep(0.0, s, d.abs() - (w * 0.5)) - i).abs() +} +// a simple palette +fn pal(d: f32) -> Vec3 { + 0.5 * ((TAU * d * vec3(2.0, 2.0, 1.0) + vec3(0.0, 1.4, 0.0)).cos() + Vec3::ONE) +} +// 2d rotation matrix +fn uvr_rotate(a: f32) -> Mat2 { + Mat2::from_cols_array(&[a.cos(), a.sin(), -a.sin(), a.cos()]) +} +// circle inversion +fn inversion(uv: Vec2, r: f32) -> Vec2 { + (r * r * uv) / Vec2::splat(uv.dot(uv)) +} +// seeded random number +fn hash(s: Vec2) -> f32 { + ((s.dot(vec2(12.9898, 78.2333))).sin() * 43758.5453123).fract_gl() +} + +// this is an algorithm to construct an apollonian packing with a descartes configuration +// remaps the plane to a circle at the origin and a specific radius. vec3(x,y,radius) +fn apollonian(uv: Vec2) -> Vec3 { + // the algorithm is recursive and must start with a initial descartes configuration + // each vec3 represents a circle with the form vec3(centerx, centery, 1./radius) + // the signed inverse radius is also called the bend (refer to the article above) + let mut dec: [Vec3; 4] = [Vec3::ZERO, Vec3::ZERO, Vec3::ZERO, Vec3::ZERO]; + // a DEC is a configuration of 4 circles tangent to each other + // the easiest way to build the initial one it to construct a symetric Steiner Chain. + // http://mathworld.wolfram.com/SteinerChain.html + let a: f32 = TAU / 3.; + let ra: f32 = 1.0 + (a * 0.5).sin(); + let rb: f32 = 1.0 - (a * 0.5).sin(); + dec[0] = vec3(0.0, 0.0, -1.0 / ra); + let radius: f32 = 0.5 * (ra - rb); + let bend: f32 = 1.0 / radius; + for i in 1..4 { + dec[i] = vec3((i as f32 * a).cos(), (i as f32 * a).sin(), bend); + // if the point is in one of the starting circles we have already found our solution + if (uv - dec[i].xy()).length() < radius { + return (uv - dec[i].xy()).extend(radius); + } + } + + // Now that we have a starting DEC we are going to try to + // find the solution for the current point + for _ in 0..7 { + // find the circle that is further away from the point uv, using euclidean distance + let mut fi: usize = 0; + let mut d: f32 = uv.distance(dec[0].xy()) - (1.0 / dec[0].z).abs(); + // for some reason, the euclidean distance doesn't work for the circle with negative bend + // can anyone with proper math skills, explain me why? + d *= if dec[0].z < 0.0 { -0.5 } else { 1.0 }; // just scale it to make it work... + for j in 1..4 { + let mut fd: f32 = uv.distance(dec[j].xy()) - (1. / dec[j].z).abs(); + fd *= if dec[j].z < 0.0 { -0.5 } else { 1.0 }; + if fd > d { + fi = j; + d = fd; + } + } + // put the cicle found in the last slot, to generate a solution + // in the "direction" of the point + let c: Vec3 = dec[3]; + dec[3] = dec[fi]; + dec[fi] = c; + // generate a new solution + let bend: f32 = (2.0 * (dec[0].z + dec[1].z + dec[2].z)) - dec[3].z; + let center: Vec2 = (2.0 + * (dec[0].z * dec[0].xy() + dec[1].z * dec[1].xy() + dec[2].z * dec[2].xy()) + - dec[3].z * dec[3].xy()) + / bend; + + let solution: Vec3 = center.extend(bend); + // is the solution radius is to small, quit + if (1. / bend).abs() < 0.01 { + break; + } + // if the solution contains the point return the circle + if (uv - solution.xy()).length() < 1. / bend { + return (uv - solution.xy()).extend(1. / bend); + } + // else update the descartes configuration, + dec[3] = solution; + // and repeat... + } + // if nothing is found we return by default the inner circle of the Steiner chain + uv.extend(rb) +} + +impl Inputs { + fn scene(&self, mut uv: Vec2, ms: Vec4) -> Vec3 { + let mut ci: Vec2 = Vec2::ZERO; + + // drag your mouse to apply circle inversion + if ms.y != -2.0 && ms.w > -2.0 { + uv = inversion(uv, 60.0_f32.to_radians().cos()); + ci = ms.xy(); + } + + // remap uv to appolonian packing + let uv_apo: Vec3 = apollonian(uv - ci); + + let d: f32 = 6.2830 / 360.0; + let a: f32 = uv_apo.y.atan2(uv_apo.x); + let r: f32 = uv_apo.xy().length(); + + let circle: f32 = sdf_circle(uv, uv - uv_apo.xy(), uv_apo.z); + + // background + let mut c: Vec3 = uv.length() * pal(0.7) * 0.2; + + // drawing the clocks + if uv_apo.z > 0.3 { + c = mix(c, pal(0.75 - r * 0.1) * 0.8, fill(circle + 0.02, 0.01, 1.0)); // clock + c = mix( + c, + pal(0.4 + r * 0.1), + stroke(circle + (uv_apo.z * 0.03), uv_apo.z * 0.01, 0.005, 1.), + ); // dial + let h: f32 = stroke( + (a + d * 15.0).rem_euclid(d * 30.) - d * 15.0, + 0.02, + 0.01, + 1.0, + ); + c = mix( + c, + pal(0.4 + r * 0.1), + h * stroke(circle + (uv_apo.z * 0.16), uv_apo.z * 0.25, 0.005, 1.0), + ); // hours + + let m: f32 = stroke( + (a + d * 15.0).rem_euclid(d * 6.0) - d * 3.0, + 0.005, + 0.01, + 1.0, + ); + c = mix( + c, + pal(0.45 + r * 0.1), + (1.0 - h) * m * stroke(circle + (uv_apo.z * 0.15), uv_apo.z * 0.1, 0.005, 1.0), + ); // minutes, + + // needles rotation + let uvrh: Vec2 = uvr_rotate( + (hash(Vec2::splat(uv_apo.z)) * d * 180.0).cos().sign_gl() + * d + * self.time + * (1.0 / uv_apo.z * 10.0) + - d * 90.0, + ) + .transpose() + * uv_apo.xy(); + let uvrm: Vec2 = uvr_rotate( + (hash(Vec2::splat(uv_apo.z) * 4.0) * d * 180.0) + .cos() + .sign_gl() + * d + * self.time + * (1.0 / uv_apo.z * 120.0) + - d * 90.0, + ) + .transpose() + * uv_apo.xy(); + // draw needles + c = mix( + c, + pal(0.85), + stroke( + sdf_rect( + uvrh + vec2(uv_apo.z - (uv_apo.z * 0.8), 0.0), + uv_apo.z * vec2(0.4, 0.03), + ), + uv_apo.z * 0.01, + 0.005, + 1.0, + ), + ); + c = mix( + c, + pal(0.9), + fill( + sdf_rect( + uvrm + vec2(uv_apo.z - (uv_apo.z * 0.65), 0.0), + uv_apo.z * vec2(0.5, 0.002), + ), + 0.005, + 1.0, + ), + ); + c = mix( + c, + pal(0.5 + r * 10.0), + fill(circle + uv_apo.z - 0.02, 0.005, 1.0), + ); // center + // drawing the gears + } else if uv_apo.z > 0.05 { + let uvrg: Vec2 = uvr_rotate( + (hash(Vec2::splat(uv_apo.z + 2.0)) * d * 180.0) + .cos() + .sign_gl() + * d + * self.time + * (1.0 / uv_apo.z * 20.0), + ) + .transpose() + * uv_apo.xy(); + let g: f32 = stroke( + (uvrg.y.atan2(uvrg.x) + d * 22.5).rem_euclid(d * 45.) - d * 22.5, + 0.3, + 0.05, + 1.0, + ); + let size: Vec2 = uv_apo.z * vec2(0.45, 0.08); + c = mix( + c, + pal(0.55 - r * 0.6), + fill(circle + g * (uv_apo.z * 0.2) + 0.01, 0.001, 1.) + * fill(circle + (uv_apo.z * 0.6), 0.005, 0.0), + ); + c = mix( + c, + pal(0.55 - r * 0.6), + fill( + sdf_rect(uvrg, size).min(sdf_rect(uvrg, size.yx())), + 0.005, + 1., + ), + ); + // drawing the screws + } else { + let size: Vec2 = uv_apo.z * vec2(0.5, 0.1); + c = mix( + c, + pal(0.85 - (uv_apo.z * 2.0)), + fill(circle + 0.01, 0.007, 1.0), + ); + c = mix( + c, + pal(0.8 - (uv_apo.z * 3.)), + fill( + sdf_rect(uv_apo.xy(), size).min(sdf_rect(uv_apo.xy(), size.yx())), + 0.002, + 1.0, + ), + ); + } + c + } + + fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { + let uv: Vec2 = (frag_coord - self.resolution.xy() * 0.5) / self.resolution.y; + let ms: Vec4 = (self.mouse - self.resolution.xyxy() * 0.5) / self.resolution.y; + *frag_color = self.scene(uv * 4., ms * 4.).extend(1.0); + } +} diff --git a/shaders/src/shaders/apollonian.rs b/shaders/src/shaders/apollonian.rs new file mode 100644 index 0000000..29503e9 --- /dev/null +++ b/shaders/src/shaders/apollonian.rs @@ -0,0 +1,201 @@ +//! Ported to Rust from +//! +//! Original comment: +//! ```glsl +//! // Created by inigo quilez - iq/2013 +//! // License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. +//! // +//! // I can't recall where I learnt about this fractal. +//! // +//! // Coloring and fake occlusions are done by orbit trapping, as usual. +//! ``` + +use crate::shader_prelude::*; + +pub const SHADER_DEFINITION: ShaderDefinition = ShaderDefinition { + name: "Apollonian Fractal", +}; + +pub fn shader_fn(render_instruction: &ShaderInput, render_result: &mut ShaderResult) { + let color = &mut render_result.color; + let &ShaderInput { + resolution, + time, + frag_coord, + mouse, + .. + } = render_instruction; + State::new(Inputs { + resolution, + time, + mouse, + }) + .main_image(color, frag_coord); +} + +struct Inputs { + resolution: Vec3, + time: f32, + mouse: Vec4, +} + +struct State { + inputs: Inputs, + orb: Vec4, +} + +impl State { + #[must_use] + fn new(inputs: Inputs) -> Self { + Self { + inputs, + orb: Vec4::ZERO, + } + } +} + +const HW_PERFORMANCE: usize = 1; + +// Antialiasing level +const AA: usize = if HW_PERFORMANCE == 0 { + 1 +} else { + 2 // Make it 3 if you have a fast machine +}; + +impl State { + fn map(&mut self, mut p: Vec3, s: f32) -> f32 { + let mut scale: f32 = 1.0; + self.orb = Vec4::splat(1000.0); + for _ in 0..8 { + p = Vec3::splat(-1.0) + 2.0 * (0.5 * p + Vec3::splat(0.5)).fract_gl(); + + let r2: f32 = p.dot(p); + + self.orb = self.orb.min(p.abs().extend(r2)); + + let k: f32 = s / r2; + p *= k; + scale *= k; + } + 0.25 * p.y.abs() / scale + } + + fn trace(&mut self, ro: Vec3, rd: Vec3, s: f32) -> f32 { + let maxd = 30.0; + let mut t: f32 = 0.01; + + for _ in 0..512 { + let precis = 0.001 * t; + + let h: f32 = self.map(ro + rd * t, s); + if h < precis || t > maxd { + break; + } + t += h; + } + if t > maxd { + t = -1.0; + } + t + } + + fn calc_normal(&mut self, pos: Vec3, t: f32, s: f32) -> Vec3 { + let precis: f32 = 0.001 * t; + + let e: Vec2 = vec2(1.0, -1.0) * precis; + (e.xyy() * self.map(pos + e.xyy(), s) + + e.yyx() * self.map(pos + e.yyx(), s) + + e.yxy() * self.map(pos + e.yxy(), s) + + e.xxx() * self.map(pos + e.xxx(), s)) + .normalize() + } + + fn render(&mut self, ro: Vec3, rd: Vec3, anim: f32) -> Vec3 { + // trace + let mut col: Vec3 = Vec3::ZERO; + let t: f32 = self.trace(ro, rd, anim); + if t > 0.0 { + let tra: Vec4 = self.orb; + let pos: Vec3 = ro + t * rd; + let nor: Vec3 = self.calc_normal(pos, t, anim); + + // lighting + let light1: Vec3 = vec3(0.577, 0.577, -0.577); + let light2: Vec3 = vec3(-0.707, 0.000, 0.707); + let key: f32 = light1.dot(nor).clamp(0.0, 1.0); + let bac: f32 = (0.2 + 0.8 * light2.dot(nor)).clamp(0.0, 1.0); + let amb: f32 = 0.7 + 0.3 * nor.y; + let ao: f32 = (tra.w * 2.0).clamp(0.0, 1.0).powf(1.2); + + let mut brdf: Vec3 = 1.0 * vec3(0.40, 0.40, 0.40) * amb * ao; + brdf += 1.0 * vec3(1.00, 1.00, 1.00) * key * ao; + brdf += 1.0 * vec3(0.40, 0.40, 0.40) * bac * ao; + + // material + let mut rgb: Vec3 = Vec3::ONE; + rgb = mix(rgb, vec3(1.0, 0.80, 0.2), (6.0 * tra.y).clamp(0.0, 1.0)); + rgb = mix( + rgb, + vec3(1.0, 0.55, 0.0), + (1.0 - 2.0 * tra.z).clamp(0.0, 1.0).powf(8.0), + ); + + // color + col = rgb * brdf * (-0.2 * t).exp(); + } + + col.sqrt() + } + + fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { + let time: f32 = self.inputs.time * 0.25 + 0.01 * self.inputs.mouse.x; + let anim: f32 = 1.1 + 0.5 * smoothstep(-0.3, 0.3, (0.1 * self.inputs.time).cos()); + let mut tot: Vec3 = Vec3::ZERO; + + for jj in 0..AA { + for ii in 0..AA { + let q: Vec2 = frag_coord + vec2(ii as f32, jj as f32) / AA as f32; + let p: Vec2 = (2.0 * q - self.inputs.resolution.xy()) / self.inputs.resolution.y; + + // camera + let ro: Vec3 = vec3( + 2.8 * (0.1 + 0.33 * time).cos(), + 0.4 + 0.30 * (0.37 * time).cos(), + 2.8 * (0.5 + 0.35 * time).cos(), + ); + let ta: Vec3 = vec3( + 1.9 * (1.2 + 0.41 * time).cos(), + 0.4 + 0.10 * (0.27 * time).cos(), + 1.9 * (2.0 + 0.38 * time).cos(), + ); + let roll: f32 = 0.2 * (0.1 * time).cos(); + let cw: Vec3 = (ta - ro).normalize(); + let cp: Vec3 = vec3(roll.sin(), roll.cos(), 0.0); + let cu: Vec3 = cw.cross(cp).normalize(); + let cv: Vec3 = cu.cross(cw).normalize(); + let rd: Vec3 = (p.x * cu + p.y * cv + 2.0 * cw).normalize(); + + tot += self.render(ro, rd, anim); + } + } + + tot /= (AA * AA) as f32; + + *frag_color = tot.extend(1.0); + } + + fn _main_vr( + &mut self, + frag_color: &mut Vec4, + _frag_coord: Vec2, + frag_ray_ori: Vec3, + freag_ray_dir: Vec3, + ) { + let _time: f32 = self.inputs.time * 0.25 + 0.01 * self.inputs.mouse.x; + let anim: f32 = 1.1 + 0.5 * smoothstep(-0.3, 0.3, (0.1 * self.inputs.time).cos()); + + let col: Vec3 = self.render(frag_ray_ori + vec3(0.82, 1.2, -0.3), freag_ray_dir, anim); + *frag_color = col.extend(1.0); + } +} diff --git a/shaders/src/shaders/atmosphere_system_test.rs b/shaders/src/shaders/atmosphere_system_test.rs new file mode 100644 index 0000000..a54f7ca --- /dev/null +++ b/shaders/src/shaders/atmosphere_system_test.rs @@ -0,0 +1,303 @@ +//! Ported to Rust from +//! +//! Original comment: +//! ```glsl +//! // ---------------------------------------------------------------------------- +//! // Rayleigh and Mie scattering atmosphere system +//! // +//! // implementation of the techniques described here: +//! // http://www.scratchapixel.com/old/lessons/3d-advanced-lessons/simulating-the-colors-of-the-sky/atmospheric-scattering/ +//! // ---------------------------------------------------------------------------- +//! ``` + +use crate::shader_prelude::*; + +pub const SHADER_DEFINITION: ShaderDefinition = ShaderDefinition { + name: "Atmosphere System Test", +}; + +pub fn shader_fn(render_instruction: &ShaderInput, render_result: &mut ShaderResult) { + let color = &mut render_result.color; + let &ShaderInput { + resolution, + time, + frag_coord, + mouse, + .. + } = render_instruction; + State::new(Inputs { + resolution, + time, + mouse, + }) + .main_image(color, frag_coord); +} + +struct Inputs { + resolution: Vec3, + time: f32, + mouse: Vec4, +} + +struct State { + inputs: Inputs, + sun_dir: Vec3, +} + +impl State { + #[must_use] + fn new(inputs: Inputs) -> Self { + Self { + inputs, + sun_dir: vec3(0.0, 1.0, 0.0), + } + } +} + +use core::f32::consts::PI; + +#[derive(Copy, Clone)] +struct Ray { + origin: Vec3, + direction: Vec3, +} +const _BIAS: f32 = 1e-4; // small offset to avoid self-intersections + +struct Sphere { + origin: Vec3, + radius: f32, + _material: i32, +} + +struct _Plane { + direction: Vec3, + distance: f32, + material: i32, +} + +fn rotate_around_x(angle_degrees: f32) -> Mat3 { + let angle: f32 = angle_degrees.to_radians(); + let sin: f32 = angle.sin(); + let cos: f32 = angle.cos(); + Mat3::from_cols_array(&[1.0, 0.0, 0.0, 0.0, cos, -sin, 0.0, sin, cos]) +} + +fn get_primary_ray(cam_local_point: Vec3, cam_origin: &Vec3, cam_look_at: &Vec3) -> Ray { + let fwd: Vec3 = (*cam_look_at - *cam_origin).normalize(); + let mut up: Vec3 = vec3(0.0, 1.0, 0.0); + let right: Vec3 = up.cross(fwd); + up = fwd.cross(right); + + Ray { + origin: *cam_origin, + direction: (fwd + up * cam_local_point.y + right * cam_local_point.x).normalize(), + } +} + +fn isect_sphere(ray: Ray, sphere: &Sphere, t0: &mut f32, t1: &mut f32) -> bool { + let rc: Vec3 = sphere.origin - ray.origin; + let radius2: f32 = sphere.radius * sphere.radius; + let tca: f32 = rc.dot(ray.direction); + let d2: f32 = rc.dot(rc) - tca * tca; + if d2 > radius2 { + return false; + } + let thc: f32 = (radius2 - d2).sqrt(); + *t0 = tca - thc; + *t1 = tca + thc; + true +} + +// scattering coefficients at sea level (m) +const BETA_R: Vec3 = vec3(5.5e-6, 13.0e-6, 22.4e-6); // Rayleigh +const BETA_M: Vec3 = vec3(21e-6, 21e-6, 21e-6); // Mie + +// scale height (m) +// thickness of the atmosphere if its density were uniform +const H_R: f32 = 7994.0; // Rayleigh +const H_M: f32 = 1200.0; // Mie + +fn rayleigh_phase_func(mu: f32) -> f32 { + 3.0 * (1.0 + mu*mu) + / //------------------------ + (16.0 * PI) +} + +// Henyey-Greenstein phase function factor [-1, 1] +// represents the average cosine of the scattered directions +// 0 is isotropic scattering +// > 1 is forward scattering, < 1 is backwards +const G: f32 = 0.76; +fn henyey_greenstein_phase_func(mu: f32) -> f32 { + (1. - G*G) + / //--------------------------------------------- + ((4. * PI) * (1. + G*G - 2.*G*mu).powf(1.5)) +} + +// Schlick Phase Function factor +// Pharr and Humphreys [2004] equivalence to g above +const K: f32 = 1.55 * G - 0.55 * (G * G * G); +fn schlick_phase_func(mu: f32) -> f32 { + (1. - K*K) + / //------------------------------------------- + (4. * PI * (1. + K*mu) * (1. + K*mu)) +} + +const EARTH_RADIUS: f32 = 6360e3; // (m) +const ATMOSPHERE_RADIUS: f32 = 6420e3; // (m) + +const SUN_POWER: f32 = 20.0; + +const ATMOSPHERE: Sphere = Sphere { + origin: Vec3::ZERO, + radius: ATMOSPHERE_RADIUS, + _material: 0, +}; + +const NUM_SAMPLES: i32 = 16; +const NUM_SAMPLES_LIGHT: i32 = 8; + +fn get_sun_light(ray: Ray, optical_depth_r: &mut f32, optical_depth_m: &mut f32) -> bool { + let mut t0: f32 = 0.0; + let mut t1: f32 = 0.0; + isect_sphere(ray, &ATMOSPHERE, &mut t0, &mut t1); + + let mut march_pos: f32 = 0.0; + let march_step: f32 = t1 / NUM_SAMPLES_LIGHT as f32; + + for _ in 0..NUM_SAMPLES_LIGHT { + let s: Vec3 = ray.origin + ray.direction * (march_pos + 0.5 * march_step); + let height: f32 = s.length() - EARTH_RADIUS; + if height < 0.0 { + return false; + } + + *optical_depth_r += (-height / H_R).exp() * march_step; + *optical_depth_m += (-height / H_M).exp() * march_step; + + march_pos += march_step; + } + true +} + +impl State { + fn get_incident_light(&self, ray: Ray) -> Vec3 { + // "pierce" the atmosphere with the viewing ray + let mut t0: f32 = 0.0; + let mut t1: f32 = 0.0; + if !isect_sphere(ray, &ATMOSPHERE, &mut t0, &mut t1) { + return Vec3::ZERO; + } + + let march_step: f32 = t1 / NUM_SAMPLES as f32; + + // cosine of angle between view and light directions + let mu: f32 = ray.direction.dot(self.sun_dir); + + // Rayleigh and Mie phase functions + // A black box indicating how light is interacting with the material + // Similar to BRDF except + // * it usually considers a single angle + // (the phase angle between 2 directions) + // * integrates to 1 over the entire sphere of directions + let phase_r: f32 = rayleigh_phase_func(mu); + let phase_m: f32 = if true { + henyey_greenstein_phase_func(mu) + } else { + schlick_phase_func(mu) + }; + + // optical depth (or "average density") + // represents the accumulated extinction coefficients + // along the path, multiplied by the length of that path + let mut optical_depth_r: f32 = 0.0; + let mut optical_depth_m: f32 = 0.0; + + let mut sum_r: Vec3 = Vec3::ZERO; + let mut sum_m: Vec3 = Vec3::ZERO; + let mut march_pos: f32 = 0.0; + + for _ in 0..NUM_SAMPLES { + let s: Vec3 = ray.origin + ray.direction * (march_pos + 0.5 * march_step); + let height: f32 = s.length() - EARTH_RADIUS; + + // integrate the height scale + let hr: f32 = (-height / H_R).exp() * march_step; + let hm: f32 = (-height / H_M).exp() * march_step; + optical_depth_r += hr; + optical_depth_m += hm; + + // gather the sunlight + let light_ray: Ray = Ray { + origin: s, + direction: self.sun_dir, + }; + let mut optical_depth_light_r: f32 = 0.0; + let mut optical_depth_light_m: f32 = 0.0; + let overground: bool = get_sun_light( + light_ray, + &mut optical_depth_light_r, + &mut optical_depth_light_m, + ); + + if overground { + let tau: Vec3 = BETA_R * (optical_depth_r + optical_depth_light_r) + + BETA_M * 1.1 * (optical_depth_m + optical_depth_light_m); + let attenuation: Vec3 = Vec3::exp(-tau); + + sum_r += hr * attenuation; + sum_m += hm * attenuation; + } + + march_pos += march_step; + } + + SUN_POWER * (sum_r * phase_r * BETA_R + sum_m * phase_m * BETA_M) + } + + fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { + let aspect_ratio: Vec2 = vec2(self.inputs.resolution.x / self.inputs.resolution.y, 1.0); + let fov: f32 = 45.0_f32.to_radians().tan(); + let point_ndc: Vec2 = frag_coord / self.inputs.resolution.xy(); + let point_cam: Vec3 = ((2.0 * point_ndc - Vec2::ONE) * aspect_ratio * fov).extend(-1.0); + + let col: Vec3; + + // sun + let rot: Mat3 = rotate_around_x(-(self.inputs.time / 2.0).sin().abs() * 90.0); + self.sun_dir = rot.transpose() * self.sun_dir; + + if self.inputs.mouse.z < 0.1 { + // sky dome angles + let p: Vec3 = point_cam; + let z2: f32 = p.x * p.x + p.y * p.y; + let phi: f32 = p.y.atan2(p.x); + let theta: f32 = (1.0 - z2).acos(); + let dir: Vec3 = vec3( + theta.sin() * phi.cos(), + theta.cos(), + theta.sin() * phi.sin(), + ); + + let ray: Ray = Ray { + origin: vec3(0.0, EARTH_RADIUS + 1.0, 0.0), + direction: dir, + }; + + col = self.get_incident_light(ray); + } else { + let eye: Vec3 = vec3(0.0, EARTH_RADIUS + 1.0, 0.0); + let look_at: Vec3 = vec3(0.0, EARTH_RADIUS + 1.5, -1.0); + + let ray: Ray = get_primary_ray(point_cam, &eye, &look_at); + + if ray.direction.dot(vec3(0.0, 1.0, 0.0)) > 0.0 { + col = self.get_incident_light(ray); + } else { + col = Vec3::splat(0.333); + } + } + + *frag_color = col.extend(1.0); + } +} diff --git a/shaders/src/shaders/bubble_buckey_balls.rs b/shaders/src/shaders/bubble_buckey_balls.rs new file mode 100644 index 0000000..aa03dce --- /dev/null +++ b/shaders/src/shaders/bubble_buckey_balls.rs @@ -0,0 +1,645 @@ +//! Ported to Rust from +//! +//! Original comment: +//! ```glsl +//! // mplanck +//! // Tested on 13-inch Powerbook +//! // Tested on Late 2013 iMac +//! // Tested on Nvidia GTX 780 Windows 7 +//! ``` + +use crate::shader_prelude::*; + +pub const SHADER_DEFINITION: ShaderDefinition = ShaderDefinition { + name: "Bubble Buckey Balls", +}; + +pub fn shader_fn(render_instruction: &ShaderInput, render_result: &mut ShaderResult) { + let color = &mut render_result.color; + let &ShaderInput { + resolution, + time, + frag_coord, + mouse, + .. + } = render_instruction; + State::new(Inputs { + resolution, + time, + mouse, + channel0: RgbCube { + alpha: 1.0, + intensity: 0.5, + }, + channel1: ConstantColor { color: Vec4::ONE }, + }) + .main_image(color, frag_coord); +} + +struct Inputs { + resolution: Vec3, + time: f32, + mouse: Vec4, + channel0: C0, + channel1: C1, +} + +struct State { + inputs: Inputs, + + cam_point_at: Vec3, + cam_origin: Vec3, + time: f32, + ldir: Vec3, +} + +impl State { + #[must_use] + fn new(inputs: Inputs) -> Self { + Self { + inputs, + cam_point_at: Vec3::ZERO, + cam_origin: Vec3::ZERO, + time: 0.0, + ldir: vec3(0.8, 1.0, 0.0), + } + } +} + +// ************************************************************************** +// CONSTANTS + +const GR: f32 = 1.61803398; + +const SMALL_FLOAT: f32 = 0.0001; +const BIG_FLOAT: f32 = 1000000.0; + +// ************************************************************************** +// MATERIAL DEFINES + +const SPHERE_MATL: f32 = 1.0; +const CHAMBER_MATL: f32 = 2.0; +const BOND_MATL: f32 = 3.0; + +// ************************************************************************** +// UTILITIES + +// Rotate the input point around the y-axis by the angle given as a +// cos(angle) and sin(angle) argument. There are many times where I want to +// reuse the same angle on different points, so why do the heavy trig twice. +// Range of outputs := ([-1.,-1.,-1.] -> [1.,1.,1.]) + +fn rotate_around_y_axis(point: Vec3, cosangle: f32, sinangle: f32) -> Vec3 { + vec3( + point.x * cosangle + point.z * sinangle, + point.y, + point.x * -sinangle + point.z * cosangle, + ) +} + +// Rotate the input point around the x-axis by the angle given as a +// cos(angle) and sin(angle) argument. There are many times where I want to +// reuse the same angle on different points, so why do the heavy trig twice. +// Range of outputs := ([-1.,-1.,-1.] -> [1.,1.,1.]) + +fn rotate_around_x_axis(point: Vec3, cosangle: f32, sinangle: f32) -> Vec3 { + vec3( + point.x, + point.y * cosangle - point.z * sinangle, + point.y * sinangle + point.z * cosangle, + ) +} + +fn pow5(v: f32) -> f32 { + let tmp: f32 = v * v; + tmp * tmp * v +} + +// convert a 3d point to two polar coordinates. +// First coordinate is elevation angle (angle from the plane going through x+z) +// Second coordinate is azimuth (rotation around the y axis) +// Range of outputs - ([PI/2, -PI/2], [-PI, PI]) +fn _cartesian_to_polar(p: Vec3) -> Vec2 { + vec2(PI / 2. - (p.y / p.length()).acos(), p.z.atan2(p.x)) +} + +fn mergeobjs(a: Vec2, b: Vec2) -> Vec2 { + if a.x < b.x { + a + } else { + b + } + + // XXX: Some architectures have bad optimization paths + // that will cause inappropriate branching if you DON'T + // use an if statement here. + + //return mix(b, a, step(a.x, b.x)); +} + +// ************************************************************************** +// DISTANCE FIELDS + +fn spheredf(pos: Vec3, r: f32) -> f32 { + pos.length() - r +} + +fn segmentdf(p: Vec3, a: Vec3, b: Vec3, r: f32) -> f32 { + let ba: Vec3 = b - a; + let mut t: f32 = ba.dot(p - a) / SMALL_FLOAT.max(ba.dot(ba)); + t = t.clamp(0., 1.); + (ba * t + a - p).length() - r +} + +// ************************************************************************** +// SCENE MARCHING + +fn buckeyballsobj(p: Vec3, mr: f32) -> Vec2 { + let mut ballsobj: Vec2 = vec2(BIG_FLOAT, SPHERE_MATL); + let ap: Vec3 = p.abs(); + //let ap: Vec3 = p; + + // vertices + // fully positive hexagon + let p1: Vec3 = vec3(0.66, 0.33 + 0.66 * GR, 0.33 * GR); + let p2: Vec3 = vec3(0.33, 0.66 + 0.33 * GR, 0.66 * GR); + let p3: Vec3 = vec3(0.33 * GR, 0.66, 0.33 + 0.66 * GR); + let p4: Vec3 = vec3(0.66 * GR, 0.33, 0.66 + 0.33 * GR); + let p5: Vec3 = vec3(0.33 + 0.66 * GR, 0.33 * GR, 0.66); + let p6: Vec3 = vec3(0.66 + 0.33 * GR, 0.66 * GR, 0.33); + + // fully positive connectors + let p7: Vec3 = vec3(0.33, GR, 0.0); + let p8: Vec3 = vec3(GR, 0.0, 0.33); + let p9: Vec3 = vec3(0.0, 0.33, GR); + + ballsobj.x = ballsobj.x.min(spheredf(ap - p1, mr)); + ballsobj.x = ballsobj.x.min(spheredf(ap - p2, mr)); + ballsobj.x = ballsobj.x.min(spheredf(ap - p3, mr)); + ballsobj.x = ballsobj.x.min(spheredf(ap - p4, mr)); + ballsobj.x = ballsobj.x.min(spheredf(ap - p5, mr)); + ballsobj.x = ballsobj.x.min(spheredf(ap - p6, mr)); + ballsobj.x = ballsobj.x.min(spheredf(ap - p7, mr)); + ballsobj.x = ballsobj.x.min(spheredf(ap - p8, mr)); + ballsobj.x = ballsobj.x.min(spheredf(ap - p9, mr)); + + let mut bondsobj: Vec2 = vec2(BIG_FLOAT, BOND_MATL); + + let br: f32 = 0.2 * mr; + bondsobj.x = bondsobj.x.min(segmentdf(ap, p1, p2, br)); + bondsobj.x = bondsobj.x.min(segmentdf(ap, p2, p3, br)); + bondsobj.x = bondsobj.x.min(segmentdf(ap, p3, p4, br)); + bondsobj.x = bondsobj.x.min(segmentdf(ap, p4, p5, br)); + bondsobj.x = bondsobj.x.min(segmentdf(ap, p5, p6, br)); + bondsobj.x = bondsobj.x.min(segmentdf(ap, p6, p1, br)); + + bondsobj.x = bondsobj.x.min(segmentdf(ap, p1, p7, br)); + bondsobj.x = bondsobj.x.min(segmentdf(ap, p5, p8, br)); + bondsobj.x = bondsobj.x.min(segmentdf(ap, p3, p9, br)); + + // bond neighbors + let p10: Vec3 = vec3(-0.33, 0.66 + 0.33 * GR, 0.66 * GR); + + let p11: Vec3 = vec3(0.66 * GR, -0.33, 0.66 + 0.33 * GR); + + let p12: Vec3 = vec3(0.66 + 0.33 * GR, 0.66 * GR, -0.33); + + let p13: Vec3 = vec3(-0.33, GR, 0.0); + let p14: Vec3 = vec3(0.66, 0.33 + 0.66 * GR, -0.33 * GR); + + let p15: Vec3 = vec3(GR, 0.0, -0.33); + let p16: Vec3 = vec3(0.33 + 0.66 * GR, -0.33 * GR, 0.66); + + let p17: Vec3 = vec3(0.0, -0.33, GR); + let p18: Vec3 = vec3(-0.33 * GR, 0.66, 0.33 + 0.66 * GR); + + bondsobj.x = bondsobj.x.min(segmentdf(ap, p2, p10, br)); + bondsobj.x = bondsobj.x.min(segmentdf(ap, p4, p11, br)); + bondsobj.x = bondsobj.x.min(segmentdf(ap, p6, p12, br)); + + bondsobj.x = bondsobj.x.min(segmentdf(ap, p7, p13, br)); + bondsobj.x = bondsobj.x.min(segmentdf(ap, p7, p14, br)); + + bondsobj.x = bondsobj.x.min(segmentdf(ap, p8, p15, br)); + bondsobj.x = bondsobj.x.min(segmentdf(ap, p8, p16, br)); + + bondsobj.x = bondsobj.x.min(segmentdf(ap, p9, p17, br)); + bondsobj.x = bondsobj.x.min(segmentdf(ap, p9, p18, br)); + + mergeobjs(ballsobj, bondsobj) +} + +fn chamberobj(p: Vec3) -> Vec2 { + vec2(20.0 - p.length(), CHAMBER_MATL) +} + +impl State { + fn scenedf(&self, p: Vec3) -> Vec2 { + //let mp: Vec3 = p; + //let bbi: f32 = 0.0; + + let mut mp: Vec3 = p + Vec3::splat(3.0); + let bbi: f32 = Vec3::ONE.dot((mp / 6.).floor()); + let mr: f32 = 0.4 * (0.7 + 0.5 * (2.0 * self.time - 1.0 * p.y + 6281.0 * bbi).sin()); + + mp = mp.rem_euclid(Vec3::splat(6.0)) - Vec3::splat(3.0); + + let obj: Vec2 = buckeyballsobj(mp, mr); + + mergeobjs(chamberobj(p), obj) + } +} + +const DISTMARCH_STEPS: usize = 60; +const DISTMARCH_MAXDIST: f32 = 50.0; + +impl State { + fn distmarch(&self, ro: Vec3, rd: Vec3, maxd: f32) -> Vec2 { + let epsilon: f32 = 0.001; + let mut dist: f32 = 10.0 * epsilon; + let mut t: f32 = 0.0; + let mut material: f32 = 0.0; + for _ in 0..DISTMARCH_STEPS { + if dist.abs() < epsilon || t > maxd { + break; + } + // advance the distance of the last lookup + t += dist; + let dfresult: Vec2 = self.scenedf(ro + t * rd); + dist = dfresult.x; + material = dfresult.y; + } + + if t > maxd { + material = -1.0; + } + vec2(t, material) + } +} + +// ************************************************************************** +// SHADOWING & NORMALS + +const SOFTSHADOW_STEPS: usize = 40; +const SOFTSHADOW_STEPSIZE: f32 = 0.1; + +impl State { + fn calc_soft_shadow(&self, ro: Vec3, rd: Vec3, mint: f32, maxt: f32, k: f32) -> f32 { + let mut shadow: f32 = 1.0; + let mut t: f32 = mint; + + for _ in 0..SOFTSHADOW_STEPS { + if t < maxt { + let h: f32 = self.scenedf(ro + rd * t).x; + shadow = shadow.min(k * h / t); + t += SOFTSHADOW_STEPSIZE; + } + } + shadow.clamp(0.0, 1.0) + } +} + +const AO_NUMSAMPLES: usize = 6; +const AO_STEPSIZE: f32 = 0.1; +const AO_STEPSCALE: f32 = 0.4; + +impl State { + fn calc_ao(&self, p: Vec3, n: Vec3) -> f32 { + let mut ao: f32 = 0.0; + let mut aoscale: f32 = 1.0; + + for aoi in 0..AO_NUMSAMPLES { + let step: f32 = 0.01 + AO_STEPSIZE * aoi as f32; + let aop: Vec3 = n * step + p; + + let d: f32 = self.scenedf(aop).x; + ao += -(d - step) * aoscale; + aoscale *= AO_STEPSCALE; + } + + ao.clamp(0.0, 1.0) + } + + // ************************************************************************** + // CAMERA & GLOBALS + + fn animate_globals(&mut self) { + // remap the mouse click ([-1, 1], [-1/ar, 1/ar]) + let mut click: Vec2 = self.inputs.mouse.xy() / self.inputs.resolution.xx(); + click = 2.0 * click - Vec2::ONE; + + self.time = 0.8 * self.inputs.time - 10.0; + + // camera position + self.cam_origin = vec3(4.5, 0.0, 4.5); + + let rotx: f32 = -1. * PI * (0.5 * click.y + 0.45) + 0.05 * self.time; + let cosrotx: f32 = rotx.cos(); + let sinrotx: f32 = rotx.sin(); + + let roty: f32 = TAU * click.x + 0.05 * self.time; + let cosroty: f32 = roty.cos(); + let sinroty: f32 = roty.sin(); + + // Rotate the camera around the origin + self.cam_origin = rotate_around_x_axis(self.cam_origin, cosrotx, sinrotx); + self.cam_origin = rotate_around_y_axis(self.cam_origin, cosroty, sinroty); + + self.cam_point_at = Vec3::ZERO; + + let lroty: f32 = 0.9 * self.time; + let coslroty: f32 = lroty.cos(); + let sinlroty: f32 = lroty.sin(); + + // Rotate the light around the origin + self.ldir = rotate_around_y_axis(self.ldir, coslroty, sinlroty); + } +} + +struct CameraData { + origin: Vec3, + dir: Vec3, + _st: Vec2, +} + +impl State { + fn setup_camera(&self, frag_coord: Vec2) -> CameraData { + // aspect ratio + let invar: f32 = self.inputs.resolution.y / self.inputs.resolution.x; + let mut st: Vec2 = frag_coord / self.inputs.resolution.xy() - Vec2::splat(0.5); + st.y *= invar; + + // calculate the ray origin and ray direction that represents + // mapping the image plane towards the scene + let iu: Vec3 = vec3(0., 1., 0.); + + let iz: Vec3 = (self.cam_point_at - self.cam_origin).normalize(); + let ix: Vec3 = (iz.cross(iu)).normalize(); + let iy: Vec3 = ix.cross(iz); + + let dir: Vec3 = (st.x * ix + st.y * iy + 0.7 * iz).normalize(); + + CameraData { + origin: self.cam_origin, + dir, + _st: st, + } + } +} +// ************************************************************************** +// SHADING + +#[derive(Clone, Copy)] +struct SurfaceData { + point: Vec3, + normal: Vec3, + basecolor: Vec3, + roughness: f32, + metallic: f32, +} + +impl SurfaceData { + #[must_use] + fn init_surf(p: Vec3, n: Vec3) -> Self { + Self { + point: p, + normal: n, + basecolor: Vec3::ZERO, + roughness: 0.0, + metallic: 0.0, + } + } +} + +impl State { + fn calc_normal(&self, p: Vec3) -> Vec3 { + let epsilon: Vec3 = vec3(0.001, 0.0, 0.0); + let n: Vec3 = vec3( + self.scenedf(p + epsilon.xyy()).x - self.scenedf(p - epsilon.xyy()).x, + self.scenedf(p + epsilon.yxy()).x - self.scenedf(p - epsilon.yxy()).x, + self.scenedf(p + epsilon.yyx()).x - self.scenedf(p - epsilon.yyx()).x, + ); + n.normalize() + } +} +fn material(surfid: f32, surf: &mut SurfaceData) { + let _surfcol: Vec3 = Vec3::ONE; + if surfid - 0.5 < SPHERE_MATL { + surf.basecolor = vec3(0.8, 0.2, 0.5); + surf.roughness = 0.5; + surf.metallic = 0.8; + } else if surfid - 0.5 < CHAMBER_MATL { + surf.basecolor = Vec3::ZERO; + surf.roughness = 1.0; + } else if surfid - 0.5 < BOND_MATL { + surf.basecolor = vec3(0.02, 0.02, 0.05); + surf.roughness = 0.2; + surf.metallic = 0.0; + } +} + +impl State { + fn integrate_dir_light(&self, ldir: Vec3, lcolor: Vec3, surf: SurfaceData) -> Vec3 { + let vdir: Vec3 = (self.cam_origin - surf.point).normalize(); + + // The half vector of a microfacet model + let hdir: Vec3 = (ldir + vdir).normalize(); + + // cos(theta_h) - theta_h is angle between half vector and normal + let costh: f32 = -SMALL_FLOAT.max(surf.normal.dot(hdir)); + // cos(theta_d) - theta_d is angle between half vector and light dir/view dir + let costd: f32 = -SMALL_FLOAT.max(ldir.dot(hdir)); + // cos(theta_l) - theta_l is angle between the light vector and normal + let costl: f32 = -SMALL_FLOAT.max(surf.normal.dot(ldir)); + // cos(theta_v) - theta_v is angle between the viewing vector and normal + let costv: f32 = -SMALL_FLOAT.max(surf.normal.dot(vdir)); + + let ndl: f32 = costl.clamp(0.0, 1.); + + let mut cout: Vec3 = Vec3::ZERO; + + if ndl > 0. { + let frk: f32 = 0.5 + 2.0 * costd * costd * surf.roughness; + let diff: Vec3 = surf.basecolor + * FRAC_1_PI + * (1. + (frk - 1.) * pow5(1. - costl)) + * (1. + (frk - 1.) * pow5(1. - costv)); + //let diff: Vec3 = surf.basecolor * FRAC_1_PI; // lambert + + // D(h) factor + // using the GGX approximation where the gamma factor is 2. + + // Clamping roughness so that a directional light has a specular + // response. A roughness of perfectly 0 will create light + // singularities. + let r: f32 = surf.roughness.max(0.05); + let alpha: f32 = r * r; + let denom: f32 = costh * costh * (alpha * alpha - 1.) + 1.; + let d: f32 = (alpha * alpha) / (PI * denom * denom); + + // using the GTR approximation where the gamma factor is generalized + // let alpha: f32 = surf.roughness * surf.roughness; + // let gamma: f32 = 2.0; + // let sinth: f32 = length(cross(surf.normal, hdir)); + // let D: f32 = 1.0/(alpha*alpha*costh*costh + sinth*sinth).powf(gamma); + + // G(h,l,v) factor + let k: f32 = ((r + 1.) * (r + 1.)) / 8.; + let gl: f32 = costv / (costv * (1. - k) + k); + let gv: f32 = costl / (costl * (1. - k) + k); + let g: f32 = gl * gv; + + // F(h,l) factor + let f0: Vec3 = mix(Vec3::splat(0.5), surf.basecolor, surf.metallic); + let f: Vec3 = f0 + (Vec3::ONE - f0) * pow5(1.0 - costd); + + let spec: Vec3 = d * f * g / (4.0 * costl * costv); + + let shd: f32 = self.calc_soft_shadow(surf.point, ldir, 0.1, 20.0, 5.0); + + cout += diff * ndl * shd * lcolor; + cout += spec * ndl * shd * lcolor; + } + + cout + } + + fn sample_env_light(&self, ldir: Vec3, lcolor: Vec3, surf: SurfaceData) -> Vec3 { + let vdir: Vec3 = (self.cam_origin - surf.point).normalize(); + + // The half vector of a microfacet model + let hdir: Vec3 = (ldir + vdir).normalize(); + + // cos(theta_h) - theta_h is angle between half vector and normal + let costh: f32 = surf.normal.dot(hdir); + // cos(theta_d) - theta_d is angle between half vector and light dir/view dir + let costd: f32 = ldir.dot(hdir); + // cos(theta_l) - theta_l is angle between the light vector and normal + let costl: f32 = surf.normal.dot(ldir); + // cos(theta_v) - theta_v is angle between the viewing vector and normal + let costv: f32 = surf.normal.dot(vdir); + + let ndl: f32 = costl.clamp(0.0, 1.0); + let mut cout: Vec3 = Vec3::ZERO; + if ndl > 0. { + let r: f32 = surf.roughness; + // G(h,l,v) factor + let k: f32 = r * r / 2.; + let gl: f32 = costv / (costv * (1. - k) + k); + let gv: f32 = costl / (costl * (1. - k) + k); + let g: f32 = gl * gv; + + // F(h,l) factor + let f0: Vec3 = mix(Vec3::splat(0.5), surf.basecolor, surf.metallic); + let f: Vec3 = f0 + (Vec3::ONE - f0) * pow5(1. - costd); + + // Combines the BRDF as well as the pdf of this particular + // sample direction. + let spec: Vec3 = lcolor * g * f * costd / (costh * costv); + + let shd: f32 = self.calc_soft_shadow(surf.point, ldir, 0.02, 20.0, 7.0); + + cout = spec * shd * lcolor; + } + + cout + } + + fn integrate_env_light(&self, surf: SurfaceData) -> Vec3 { + let vdir: Vec3 = (surf.point - self.cam_origin).normalize(); + let envdir: Vec3 = vdir.reflect(surf.normal); + let specolor: Vec4 = Vec4::splat(0.4) + * mix( + self.inputs.channel0.sample_cube(envdir), + self.inputs.channel1.sample_cube(envdir), + surf.roughness, + ); + + self.sample_env_light(envdir, specolor.xyz(), surf) + } + + fn shade_surface(&self, surf: SurfaceData) -> Vec3 { + let amb: Vec3 = surf.basecolor * 0.04; + // ambient occlusion is amount of occlusion. So 1 is fully occluded + // and 0 is not occluded at all. Makes math easier when mixing + // shadowing effects. + let ao: f32 = self.calc_ao(surf.point, surf.normal); + + let centerldir: Vec3 = (-surf.point).normalize(); + + let mut cout: Vec3 = Vec3::ZERO; + if surf.basecolor.dot(Vec3::ONE) > SMALL_FLOAT { + cout += self.integrate_dir_light(self.ldir, Vec3::splat(0.3), surf); + cout += self.integrate_dir_light(centerldir, vec3(0.3, 0.5, 1.0), surf); + cout += self.integrate_env_light(surf) * (1.0 - 3.5 * ao); + cout += amb * (1.0 - 5.5 * ao); + } + cout + } + + // ************************************************************************** + // MAIN + + fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { + // ---------------------------------------------------------------------- + // Animate globals + + self.animate_globals(); + + // ---------------------------------------------------------------------- + // Setup Camera + + let cam: CameraData = self.setup_camera(frag_coord); + + // ---------------------------------------------------------------------- + // SCENE MARCHING + + let scenemarch: Vec2 = self.distmarch(cam.origin, cam.dir, DISTMARCH_MAXDIST); + + // ---------------------------------------------------------------------- + // SHADING + + let mut scenecol: Vec3 = Vec3::ZERO; + if scenemarch.y > SMALL_FLOAT { + let mp: Vec3 = cam.origin + scenemarch.x * cam.dir; + let mn: Vec3 = self.calc_normal(mp); + + let mut curr_surf: SurfaceData = SurfaceData::init_surf(mp, mn); + + material(scenemarch.y, &mut curr_surf); + scenecol = self.shade_surface(curr_surf); + } + + // ---------------------------------------------------------------------- + // POST PROCESSING + + // fall off exponentially into the distance (as if there is a spot light + // on the point of interest). + scenecol *= (-0.01 * (scenemarch.x * scenemarch.x - 300.0)).exp(); + + // brighten + scenecol *= 1.3; + + // distance fog + scenecol = mix( + scenecol, + 0.02 * vec3(1.0, 0.2, 0.8), + smoothstep(10.0, 30.0, scenemarch.x), + ); + + // Gamma correct + scenecol = scenecol.powf_vec(Vec3::splat(0.45)); + + // Contrast adjust - cute trick learned from iq + scenecol = mix( + scenecol, + Vec3::splat(scenecol.dot(Vec3::splat(0.333))), + -0.6, + ); + + // color tint + scenecol = 0.5 * scenecol + 0.5 * scenecol * vec3(1.0, 1.0, 0.9); + + *frag_color = scenecol.extend(1.0); + } +} diff --git a/shaders/src/shaders/clouds.rs b/shaders/src/shaders/clouds.rs new file mode 100644 index 0000000..f795149 --- /dev/null +++ b/shaders/src/shaders/clouds.rs @@ -0,0 +1,153 @@ +//! Ported to Rust from + +use crate::shader_prelude::*; + +pub const SHADER_DEFINITION: ShaderDefinition = ShaderDefinition { name: "Clouds" }; + +pub fn shader_fn(render_instruction: &ShaderInput, render_result: &mut ShaderResult) { + let color = &mut render_result.color; + let &ShaderInput { + resolution, + time, + frag_coord, + .. + } = render_instruction; + Inputs { resolution, time }.main_image(color, frag_coord); +} + +struct Inputs { + resolution: Vec3, + time: f32, +} + +const CLOUD_SCALE: f32 = 1.1; +const SPEED: f32 = 0.03; +const CLOUD_DARK: f32 = 0.5; +const CLOUD_LIGHT: f32 = 0.3; +const CLOUD_COVER: f32 = 0.2; +const CLOUD_ALPHA: f32 = 8.0; +const SKY_TINT: f32 = 0.5; +const SKY_COLOUR1: Vec3 = vec3(0.2, 0.4, 0.6); +const SKY_COLOUR2: Vec3 = vec3(0.4, 0.7, 1.0); + +const M: Mat2 = mat2(vec2(1.6, 1.2), vec2(-1.2, 1.6)); + +fn hash(mut p: Vec2) -> Vec2 { + p = vec2(p.dot(vec2(127.1, 311.7)), p.dot(vec2(269.5, 183.3))); + Vec2::splat(-1.0) + 2.0 * (p.sin() * 43758.5453123).fract_gl() +} + +fn noise(p: Vec2) -> f32 { + const K1: f32 = 0.366025404; // (sqrt(3)-1)/2; + const K2: f32 = 0.211324865; // (3-sqrt(3))/6; + let i: Vec2 = (p + Vec2::splat((p.x + p.y) * K1)).floor(); + let a: Vec2 = p - i + Vec2::splat((i.x + i.y) * K2); + let o: Vec2 = if a.x > a.y { + vec2(1.0, 0.0) + } else { + vec2(0.0, 1.0) + }; //vec2 of = 0.5 + 0.5*vec2(sign(a.x-a.y), sign(a.y-a.x)); + let b: Vec2 = a - o + Vec2::splat(K2); + let c: Vec2 = a - Vec2::splat(1.0 - 2.0 * K2); + let h: Vec3 = (Vec3::splat(0.5) - vec3(a.dot(a), b.dot(b), c.dot(c))).max(Vec3::ZERO); + let n: Vec3 = (h * h * h * h) + * vec3( + a.dot(hash(i + Vec2::ZERO)), + b.dot(hash(i + o)), + c.dot(hash(i + Vec2::splat(1.0))), + ); + n.dot(Vec3::splat(70.0)) +} + +fn fbm(mut n: Vec2) -> f32 { + let mut total: f32 = 0.0; + let mut amplitude: f32 = 0.1; + for _ in 0..7 { + total += noise(n) * amplitude; + let m = M; + n = m.transpose() * n; + amplitude *= 0.4; + } + total +} + +// ----------------------------------------------- + +impl Inputs { + fn main_image(&self, frag_color: &mut Vec4, frag_coord: Vec2) { + let p: Vec2 = frag_coord / self.resolution.xy(); + let mut uv: Vec2 = p * vec2(self.resolution.x / self.resolution.y, 1.0); + let mut time: f32 = self.time * SPEED; + let q: f32 = fbm(uv * CLOUD_SCALE * 0.5); + + //ridged noise shape + let mut r: f32 = 0.0; + uv *= CLOUD_SCALE; + uv -= Vec2::splat(q - time); + let mut weight: f32 = 0.8; + for _ in 0..8 { + r += (weight * noise(uv)).abs(); + let m = M; + uv = m.transpose() * uv + Vec2::splat(time); + weight *= 0.7; + } + + //noise shape + let mut f: f32 = 0.0; + uv = p * vec2(self.resolution.x / self.resolution.y, 1.0); + uv *= CLOUD_SCALE; + uv -= Vec2::splat(q - time); + weight = 0.7; + for _ in 0..8 { + f += weight * noise(uv); + let m = M; + uv = m.transpose() * uv + Vec2::splat(time); + weight *= 0.6; + } + + f *= r + f; + + //noise colour + let mut c: f32 = 0.0; + time = self.time * SPEED * 2.0; + uv = p * vec2(self.resolution.x / self.resolution.y, 1.0); + uv *= CLOUD_SCALE * 2.0; + uv -= Vec2::splat(q - time); + weight = 0.4; + for _ in 0..7 { + c += weight * noise(uv); + let m = M; + uv = m.transpose() * uv + Vec2::splat(time); + weight *= 0.6; + } + + //noise ridge colour + let mut c1: f32 = 0.0; + time = self.time * SPEED * 3.0; + uv = p * vec2(self.resolution.x / self.resolution.y, 1.0); + uv *= CLOUD_SCALE * 3.0; + uv -= Vec2::splat(q - time); + weight = 0.4; + for _ in 0..7 { + c1 += (weight * noise(uv)).abs(); + let m = M; + uv = m.transpose() * uv + Vec2::splat(time); + weight *= 0.6; + } + + c += c1; + + let skycolour: Vec3 = mix(SKY_COLOUR2, SKY_COLOUR1, p.y); + let cloudcolour: Vec3 = vec3(1.1, 1.1, 0.9) * (CLOUD_DARK + CLOUD_LIGHT * c).clamp(0.0, 1.0); + + f = CLOUD_COVER + CLOUD_ALPHA * f * r; + + let result: Vec3 = mix( + skycolour, + (SKY_TINT * skycolour + cloudcolour).clamp(Vec3::ZERO, Vec3::splat(1.0)), + (f + c).clamp(0.0, 1.0), + ); + + *frag_color = result.extend(1.0); + } +} diff --git a/shaders/src/shaders/filtering_procedurals.rs b/shaders/src/shaders/filtering_procedurals.rs new file mode 100644 index 0000000..50b59e4 --- /dev/null +++ b/shaders/src/shaders/filtering_procedurals.rs @@ -0,0 +1,439 @@ +//! Ported to Rust from +//! +//! Original comment: +//! ```glsl +//! // The MIT License +//! // Copyright © 2013 Inigo Quilez +//! // 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. +//! +//! // A test on using ray differentials (only primary rays for now) to choose texture filtering +//! // footprint, and adaptively supersample/filter the procedural texture/patter (up to a rate +//! // of 10x10). +//! +//! // This solves texture aliasing without resorting to full-screen 10x10 supersampling, which would +//! // involve doing raytracing and lighting 10x10 times (not realtime at all). +//! +//! // The tecnique should be used to filter every texture independently. The ratio of the supersampling +//! // could be inveresely proportional to the screen/lighing supersampling rate such that the cost +//! // of texturing would be constant no matter the final image quality settings. +//! */ +//! ``` + +use crate::shader_prelude::*; + +pub const SHADER_DEFINITION: ShaderDefinition = ShaderDefinition { + name: "Filtering Procedurals", +}; + +pub fn shader_fn(render_instruction: &ShaderInput, render_result: &mut ShaderResult) { + let color = &mut render_result.color; + let &ShaderInput { + resolution, + time, + frag_coord, + mouse, + .. + } = render_instruction; + Inputs { + resolution, + time, + mouse, + } + .main_image(color, frag_coord); +} + +struct Inputs { + resolution: Vec3, + time: f32, + mouse: Vec4, +} + +//=============================================================================================== +//=============================================================================================== + +const MAX_SAMPLES: i32 = 10; // 10*10 + +//=============================================================================================== +//=============================================================================================== +// noise implementation +//=============================================================================================== +//=============================================================================================== + +fn hash3(mut p: Vec3) -> Vec3 { + p = vec3( + p.dot(vec3(127.1, 311.7, 74.7)), + p.dot(vec3(269.5, 183.3, 246.1)), + p.dot(vec3(113.5, 271.9, 124.6)), + ); + + -Vec3::ONE + 2.0 * (p.sin() * 13.5453123).fract_gl() +} + +fn noise(p: Vec3) -> f32 { + let i: Vec3 = p.floor(); + let f: Vec3 = p.fract_gl(); + + let u: Vec3 = f * f * (Vec3::splat(3.0) - 2.0 * f); + + mix( + mix( + mix( + hash3(i + vec3(0.0, 0.0, 0.0)).dot(f - vec3(0.0, 0.0, 0.0)), + hash3(i + vec3(1.0, 0.0, 0.0)).dot(f - vec3(1.0, 0.0, 0.0)), + u.x, + ), + mix( + hash3(i + vec3(0.0, 1.0, 0.0)).dot(f - vec3(0.0, 1.0, 0.0)), + hash3(i + vec3(1.0, 1.0, 0.0)).dot(f - vec3(1.0, 1.0, 0.0)), + u.x, + ), + u.y, + ), + mix( + mix( + hash3(i + vec3(0.0, 0.0, 1.0)).dot(f - vec3(0.0, 0.0, 1.0)), + hash3(i + vec3(1.0, 0.0, 1.0)).dot(f - vec3(1.0, 0.0, 1.0)), + u.x, + ), + mix( + hash3(i + vec3(0.0, 1.0, 1.0)).dot(f - vec3(0.0, 1.0, 1.0)), + hash3(i + vec3(1.0, 1.0, 1.0)).dot(f - vec3(1.0, 1.0, 1.0)), + u.x, + ), + u.y, + ), + u.z, + ) +} + +//=============================================================================================== +//=============================================================================================== +// sphere implementation +//=============================================================================================== +//=============================================================================================== + +fn soft_shadow_sphere(ro: Vec3, rd: Vec3, sph: Vec4) -> f32 { + let oc: Vec3 = sph.xyz() - ro; + let b: f32 = oc.dot(rd); + + let mut res: f32 = 1.0; + if b > 0.0 { + let h: f32 = oc.dot(oc) - b * b - sph.w * sph.w; + res = smoothstep(0.0, 1.0, 2.0 * h / b); + } + res +} + +fn occ_sphere(sph: Vec4, pos: Vec3, nor: Vec3) -> f32 { + let di: Vec3 = sph.xyz() - pos; + let l: f32 = di.length(); + 1.0 - nor.dot(di / l) * sph.w * sph.w / (l * l) +} + +fn i_sphere(ro: Vec3, rd: Vec3, sph: Vec4) -> f32 { + let mut t: f32 = -1.0; + let ce: Vec3 = ro - sph.xyz(); + let b: f32 = rd.dot(ce); + let c: f32 = ce.dot(ce) - sph.w * sph.w; + let h: f32 = b * b - c; + if h > 0.0 { + t = -b - h.sqrt(); + } + + t +} + +//=============================================================================================== +//=============================================================================================== +// scene +//=============================================================================================== +//=============================================================================================== + +// spheres +const SC0: Vec4 = vec4(0.0, 1.0, 0.0, 1.0); +const SC1: Vec4 = vec4(0.0, 1.0, 14.0, 4.0); +const SC2: Vec4 = vec4(-11.0, 1.0, 12.0, 4.0); +const SC3: Vec4 = vec4(13.0, 1.0, -10.0, 4.0); + +fn intersect( + ro: Vec3, + rd: Vec3, + pos: &mut Vec3, + nor: &mut Vec3, + occ: &mut f32, + matid: &mut f32, +) -> f32 { + // raytrace + let mut tmin: f32 = 10000.0; + *nor = Vec3::ZERO; + *occ = 1.0; + *pos = Vec3::ZERO; + + // raytrace-plane + let mut h: f32 = (0.0 - ro.y) / rd.y; + if h > 0.0 { + tmin = h; + *nor = vec3(0.0, 1.0, 0.0); + *pos = ro + h * rd; + *matid = 0.0; + *occ = occ_sphere(SC0, *pos, *nor) + * occ_sphere(SC1, *pos, *nor) + * occ_sphere(SC2, *pos, *nor) + * occ_sphere(SC3, *pos, *nor); + } + + // raytrace-sphere + h = i_sphere(ro, rd, SC0); + if h > 0.0 && h < tmin { + tmin = h; + *pos = ro + h * rd; + *nor = (*pos - SC0.xyz()).normalize(); + *matid = 1.0; + *occ = 0.5 + 0.5 * nor.y; + } + + h = i_sphere(ro, rd, SC1); + if h > 0.0 && h < tmin { + tmin = h; + *pos = ro + tmin * rd; + *nor = (ro + h * rd - SC1.xyz()).normalize(); + *matid = 1.0; + *occ = 0.5 + 0.5 * nor.y; + } + + h = i_sphere(ro, rd, SC2); + if h > 0.0 && h < tmin { + tmin = h; + *pos = ro + tmin * rd; + *nor = (ro + h * rd - SC2.xyz()).normalize(); + *matid = 1.0; + *occ = 0.5 + 0.5 * nor.y; + } + + h = i_sphere(ro, rd, SC3); + if h > 0.0 && h < tmin { + tmin = h; + *pos = ro + tmin * rd; + *nor = (ro + h * rd - SC3.xyz()).normalize(); + *matid = 1.0; + *occ = 0.5 + 0.5 * nor.y; + } + + tmin +} + +fn tex_coords(p: Vec3) -> Vec3 { + 64.0 * p +} + +fn mytexture(mut p: Vec3, _n: Vec3, matid: f32) -> Vec3 { + p += Vec3::splat(0.1); + let ip: Vec3 = (p / 20.0).floor(); + let fp: Vec3 = (Vec3::splat(0.5) + p / 20.0).fract_gl(); + + let mut id: f32 = ((ip.dot(vec3(127.1, 311.7, 74.7))).sin() * 58.5453123).fract_gl(); + id = mix(id, 0.3, matid); + + let f: f32 = (ip.x + (ip.y + ip.z.rem_euclid(2.0)).rem_euclid(2.0)).rem_euclid(2.0); + + let mut g: f32 = + 0.5 + 1.0 * noise(p * mix(vec3(0.2 + 0.8 * f, 1.0, 1.0 - 0.8 * f), Vec3::ONE, matid)); + + g *= mix( + smoothstep(0.03, 0.04, (fp.x - 0.5).abs() / 0.5) + * smoothstep(0.03, 0.04, (fp.z - 0.5).abs() / 0.5), + 1.0, + matid, + ); + + let col: Vec3 = + Vec3::splat(0.5) + 0.5 * (Vec3::splat(1.0 + 2.0 * id) + vec3(0.0, 1.0, 2.0)).sin(); + + col * g +} + +impl Inputs { + fn calc_camera(&self, ro: &mut Vec3, ta: &mut Vec3) { + let an: f32 = 0.1 * self.time; + *ro = vec3(5.5 * an.cos(), 1.0, 5.5 * an.sin()); + *ta = vec3(0.0, 1.0, 0.0); + } +} + +fn do_lighting(pos: Vec3, nor: Vec3, occ: f32, rd: Vec3) -> Vec3 { + let sh: f32 = soft_shadow_sphere(pos, Vec3::splat(0.57703), SC0) + .min(soft_shadow_sphere(pos, Vec3::splat(0.57703), SC1)) + .min(soft_shadow_sphere(pos, Vec3::splat(0.57703), SC2)) + .min(soft_shadow_sphere(pos, Vec3::splat(0.57703), SC3)); + let dif: f32 = nor.dot(Vec3::splat(0.57703)).clamp(0.0, 1.0); + let bac: f32 = nor.dot(vec3(-0.707, 0.0, -0.707)).clamp(0.0, 1.0); + let mut lin: Vec3 = dif * vec3(1.50, 1.40, 1.30) * sh; + lin += occ * vec3(0.15, 0.20, 0.30); + lin += bac * vec3(0.20, 0.20, 0.20); + lin += Vec3::splat( + sh * 0.8 + * rd + .reflect(nor) + .dot(Vec3::splat(0.57703)) + .clamp(0.0, 1.0) + .powf(12.0), + ); + + lin +} +//=============================================================================================== +//=============================================================================================== +// render +//=============================================================================================== +//=============================================================================================== +impl Inputs { + fn calc_ray_for_pixel(&self, pix: Vec2, res_ro: &mut Vec3, res_rd: &mut Vec3) { + let p: Vec2 = (-self.resolution.xy() + 2.0 * pix) / self.resolution.y; + // camera movement + let mut ro: Vec3 = Vec3::ZERO; + let mut ta: Vec3 = Vec3::ZERO; + self.calc_camera(&mut ro, &mut ta); + // camera matrix + let ww: Vec3 = (ta - ro).normalize(); + let uu: Vec3 = ww.cross(vec3(0.0, 1.0, 0.0)).normalize(); + let vv: Vec3 = uu.cross(ww).normalize(); + // create view ray + let rd: Vec3 = (p.x * uu + p.y * vv + 1.5 * ww).normalize(); + + *res_ro = ro; + *res_rd = rd; + } +} + +// sample a procedural texture with filtering +fn sample_texture_with_filter( + uvw: Vec3, + ddx_uvw: Vec3, + ddy_uvw: Vec3, + nor: Vec3, + mid: f32, +) -> Vec3 { + let sx: i32 = 1 + (4.0 * (ddx_uvw - uvw).length()).clamp(0.0, (MAX_SAMPLES - 1) as f32) as i32; + let sy: i32 = 1 + (4.0 * (ddy_uvw - uvw).length()).clamp(0.0, (MAX_SAMPLES - 1) as f32) as i32; + + let mut no: Vec3 = Vec3::ZERO; + + if true { + for j in 0..MAX_SAMPLES { + for i in 0..MAX_SAMPLES { + if j < sy && i < sx { + let st: Vec2 = vec2(i as f32, j as f32) / vec2(sx as f32, sy as f32); + no += mytexture( + uvw + st.x * (ddx_uvw - uvw) + st.y * (ddy_uvw - uvw), + nor, + mid, + ); + } + } + } + } else { + for j in 0..sy { + for i in 0..sx { + let st: Vec2 = vec2(i as f32, j as f32) / vec2(sx as f32, sy as f32); + no += mytexture( + uvw + st.x * (ddx_uvw - uvw) + st.y * (ddy_uvw - uvw), + nor, + mid, + ); + } + } + } + + no / (sx * sy) as f32 +} + +fn sample_texture(uvw: Vec3, nor: Vec3, mid: f32) -> Vec3 { + mytexture(uvw, nor, mid) +} + +impl Inputs { + fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { + let p: Vec2 = (-self.resolution.xy() + 2.0 * frag_coord) / self.resolution.y; + let mut th: f32 = (-self.resolution.x + 2.0 * self.mouse.x) / self.resolution.y; + + if self.mouse.z < 0.01 { + th = 0.5 / self.resolution.y; + } + + let mut ro: Vec3 = Vec3::ZERO; + let mut rd: Vec3 = Vec3::ZERO; + let mut ddx_ro: Vec3 = Vec3::ZERO; + let mut ddx_rd: Vec3 = Vec3::ZERO; + let mut ddy_ro: Vec3 = Vec3::ZERO; + let mut ddy_rd: Vec3 = Vec3::ZERO; + self.calc_ray_for_pixel(frag_coord + vec2(0.0, 0.0), &mut ro, &mut rd); + self.calc_ray_for_pixel(frag_coord + vec2(1.0, 0.0), &mut ddx_ro, &mut ddx_rd); + self.calc_ray_for_pixel(frag_coord + vec2(0.0, 1.0), &mut ddy_ro, &mut ddy_rd); + + // trace + let mut pos: Vec3 = Vec3::ZERO; + let mut nor: Vec3 = Vec3::ZERO; + let mut occ: f32 = 0.0; + let mut mid: f32 = 0.0; + let t: f32 = intersect(ro, rd, &mut pos, &mut nor, &mut occ, &mut mid); + + let mut col: Vec3 = Vec3::splat(0.9); + + let uvw: Vec3; + let ddx_uvw: Vec3; + let ddy_uvw: Vec3; + + if t < 100.0 { + if true { + // ----------------------------------------------------------------------- + // compute ray differentials by intersecting the tangent plane to the + // surface. + // ----------------------------------------------------------------------- + + // computer ray differentials + let ddx_pos: Vec3 = ddx_ro - ddx_rd * (ddx_ro - pos).dot(nor) / ddx_rd.dot(nor); + let ddy_pos: Vec3 = ddy_ro - ddy_rd * (ddy_ro - pos).dot(nor) / ddy_rd.dot(nor); + + // calc texture sampling footprint + uvw = tex_coords(pos); + ddx_uvw = tex_coords(ddx_pos); + ddy_uvw = tex_coords(ddy_pos); + } else { + // ----------------------------------------------------------------------- + // Because we are in the GPU, we do have access to differentials directly + // This wouldn't be the case in a regular raytrace. + // It wouldn't work as well in shaders doing interleaved calculations in + // pixels (such as some of the 3D/stereo shaders here in Shadertoy) + // ----------------------------------------------------------------------- + uvw = tex_coords(pos); + + // calc texture sampling footprint + ddx_uvw = uvw + uvw.dfdx(); + ddy_uvw = uvw + uvw.dfdy(); + } + // shading + + let mate = if p.x > th { + sample_texture(uvw, nor, mid) + } else { + sample_texture_with_filter(uvw, ddx_uvw, ddy_uvw, nor, mid) + }; + + // lighting + let lin: Vec3 = do_lighting(pos, nor, occ, rd); + + // combine lighting with material + col = mate * lin; + + // fog + col = mix(col, Vec3::splat(0.9), 1.0 - (-0.0002 * t * t).exp()); + } + + // gamma correction + col = col.powf(0.4545); + + col *= smoothstep(0.006, 0.008, (p.x - th).abs()); + + *frag_color = col.extend(1.0); + } +} diff --git a/shaders/src/shaders/flappy_bird.rs b/shaders/src/shaders/flappy_bird.rs new file mode 100644 index 0000000..6756693 --- /dev/null +++ b/shaders/src/shaders/flappy_bird.rs @@ -0,0 +1,1253 @@ +//! Ported to Rust from +//! +//! Original comment: +//! ```glsl +//! // FlappyBird by Ben Raziel. Feb 2014 +//! +//! // Based on the "Super Mario Bros" shader by HLorenzi +//! // https://www.shadertoy.com/view/Msj3zD +//! ``` +#![allow(clippy::float_cmp)] +use crate::shader_prelude::*; + +pub const SHADER_DEFINITION: ShaderDefinition = ShaderDefinition { + name: "Flappy Bird", +}; + +pub fn shader_fn(render_instruction: &ShaderInput, render_result: &mut ShaderResult) { + let color = &mut render_result.color; + let &ShaderInput { + resolution, + time, + frag_coord, + .. + } = render_instruction; + State::new(Inputs { resolution, time }).main_image(color, frag_coord); +} + +struct Inputs { + resolution: Vec3, + time: f32, +} + +struct State { + inputs: Inputs, + + frag_color: Vec4, +} + +impl State { + #[must_use] + fn new(inputs: Inputs) -> Self { + Self { + inputs, + + frag_color: Vec4::ZERO, + } + } +} + +// Helper functions for drawing sprites +fn rgb(r: i32, g: i32, b: i32) -> Vec4 { + vec4(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, 1.0) +} +fn sprrow( + x: i32, + a: f32, + b: f32, + c: f32, + d: f32, + e: f32, + f: f32, + g: f32, + h: f32, + i: f32, + j: f32, + k: f32, + l: f32, + m: f32, + n: f32, + o: f32, + p: f32, +) -> f32 { + if x <= 7 { + sprrow_h(a, b, c, d, e, f, g, h) + } else { + sprrow_h(i, j, k, l, m, n, o, p) + } +} +fn sprrow_h(a: f32, b: f32, c: f32, d: f32, e: f32, f: f32, g: f32, h: f32) -> f32 { + a + 4.0 * (b + 4.0 * (c + 4.0 * (d + 4.0 * (e + 4.0 * (f + 4.0 * (g + 4.0 * (h))))))) +} +fn _secrow(x: i32, a: f32, b: f32, c: f32, d: f32, e: f32, f: f32, g: f32, h: f32) -> f32 { + if x <= 3 { + _secrow_h(a, b, c, d) + } else { + _secrow_h(e, f, g, h) + } +} +fn _secrow_h(a: f32, b: f32, c: f32, d: f32) -> f32 { + a + 8.0 * (b + 8.0 * (c + 8.0 * (d))) +} +fn select(x: i32, i: f32) -> f32 { + (i / 4.0_f32.powf(x as f32)).floor().rem_euclid(4.0) +} +fn _selectsec(x: i32, i: f32) -> f32 { + (i / 8.0_f32.powf(x as f32)).floor().rem_euclid(8.0) +} + +// drawing consts +const PIPE_WIDTH: f32 = 26.0; // px +const PIPE_BOTTOM: f32 = 39.0; // px +const PIPE_HOLE_HEIGHT: f32 = 12.0; // px + +// const PIPE_OUTLINE_COLOR: Vec4 = RGB(84, 56, 71); +const PIPE_OUTLINE_COLOR: Vec4 = vec4(84.0 / 255.0, 56.0 / 255.0, 71.0 / 255.0, 1.0); + +// gameplay consts +const HORZ_PIPE_DISTANCE: f32 = 100.0; // px; +const VERT_PIPE_DISTANCE: f32 = 55.0; // px; +const PIPE_MIN: f32 = 20.0; +const PIPE_MAX: f32 = 70.0; +const PIPE_PER_CYCLE: f32 = 8.0; + +impl State { + fn draw_horz_rect(&mut self, y_coord: f32, min_y: f32, max_y: f32, color: Vec4) { + if (y_coord >= min_y) && (y_coord < max_y) { + self.frag_color = color; + } + } + + fn draw_low_bush(&mut self, x: i32, y: i32) { + if y < 0 || y > 3 || x < 0 || x > 15 { + return; + } + + let mut col: f32 = 0.0; // 0 = transparent + + if y == 3 { + col = sprrow( + x, 0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 0., 0., 0., 0., 0., 0., + ); + } + if y == 2 { + col = sprrow( + x, 0., 0., 0., 0., 1., 1., 2., 2., 2., 2., 1., 1., 0., 0., 0., 0., + ); + } + if y == 1 { + col = sprrow( + x, 0., 0., 0., 1., 1., 2., 2., 2., 2., 2., 2., 1., 1., 0., 0., 0., + ); + } + if y == 0 { + col = sprrow( + x, 0., 0., 1., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 1., 0., 0., + ); + } + + // i made this tho a i32 cast bc it seems as this thing(SELECT(i32, f32)) uses a i 32 + col = select((x as f32).rem_euclid(8.0) as i32, col); + if col == 1.0 { + self.frag_color = rgb(87, 201, 111); + } else if col == 2.0 { + self.frag_color = rgb(100, 224, 117); + } + } + + fn draw_high_bush(&mut self, x: i32, y: i32) { + if y < 0 || y > 6 || x < 0 || x > 15 { + return; + } + + let mut col: f32 = 0.0; // 0 = transparent + + if y == 6 { + col = sprrow( + x, 0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 0., 0., 0., 0., 0., 0., + ); + } + if y == 5 { + col = sprrow( + x, 0., 0., 0., 0., 1., 1., 2., 2., 2., 2., 1., 1., 0., 0., 0., 0., + ); + } + if y == 4 { + col = sprrow( + x, 0., 0., 1., 1., 2., 2., 2., 2., 2., 2., 2., 2., 1., 1., 0., 0., + ); + } + if y == 3 { + col = sprrow( + x, 0., 1., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 1., 0., + ); + } + if y == 2 { + col = sprrow( + x, 0., 1., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 1., 0., + ); + } + if y == 1 { + col = sprrow( + x, 1., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 1., + ); + } + if y == 0 { + col = sprrow( + x, 1., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 1., + ); + } + + col = select((x as f32).rem_euclid(8.0) as i32, col); + if col == 1.0 { + self.frag_color = rgb(87, 201, 111); + } else if col == 2.0 { + self.frag_color = rgb(100, 224, 117); + } + } + + fn draw_cloud(&mut self, x: i32, y: i32) { + if y < 0 || y > 6 || x < 0 || x > 15 { + return; + } + + let mut col: f32 = 0.0; // 0 = transparent + + if y == 6 { + col = sprrow( + x, 0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 0., 0., 0., 0., 0., 0., + ); + } + if y == 5 { + col = sprrow( + x, 0., 0., 0., 0., 1., 1., 2., 2., 2., 2., 1., 1., 0., 0., 0., 0., + ); + } + if y == 4 { + col = sprrow( + x, 0., 0., 1., 1., 2., 2., 2., 2., 2., 2., 2., 2., 1., 1., 0., 0., + ); + } + if y == 3 { + col = sprrow( + x, 0., 1., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 1., 0., + ); + } + if y == 2 { + col = sprrow( + x, 0., 1., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 1., 0., + ); + } + if y == 1 { + col = sprrow( + x, 1., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 1., + ); + } + if y == 0 { + col = sprrow( + x, 1., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 1., + ); + } + + col = select((x as f32).rem_euclid(8.0) as i32, col); + if col == 1.0 { + self.frag_color = rgb(218, 246, 216); + } else if col == 2.0 { + self.frag_color = rgb(233, 251, 218); + } + } + + fn draw_bird_f0(&mut self, x: i32, y: i32) { + if y < 0 || y > 11 || x < 0 || x > 15 { + return; + } + + // pass 0 - draw black, white and yellow + let mut col: f32 = 0.0; // 0 = transparent + if y == 11 { + col = sprrow( + x, 0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 1., 1., 0., 0., 0., 0., + ); + } + if y == 10 { + col = sprrow( + x, 0., 0., 0., 0., 1., 1., 3., 3., 3., 1., 2., 2., 1., 0., 0., 0., + ); + } + if y == 9 { + col = sprrow( + x, 0., 0., 0., 1., 3., 3., 3., 3., 1., 2., 2., 2., 2., 1., 0., 0., + ); + } + if y == 8 { + col = sprrow( + x, 0., 0., 1., 3., 3., 3., 3., 3., 1., 2., 2., 2., 1., 2., 1., 0., + ); + } + if y == 7 { + col = sprrow( + x, 0., 1., 3., 3., 3., 3., 3., 3., 1., 2., 2., 2., 1., 2., 1., 0., + ); + } + if y == 6 { + col = sprrow( + x, 0., 1., 3., 3., 3., 3., 3., 3., 3., 1., 2., 2., 2., 2., 1., 0., + ); + } + if y == 5 { + col = sprrow( + x, 0., 1., 1., 1., 1., 1., 3., 3., 3., 3., 1., 1., 1., 1., 1., 1., + ); + } + if y == 4 { + col = sprrow( + x, 1., 2., 2., 2., 2., 2., 1., 3., 3., 1., 2., 2., 2., 2., 2., 1., + ); + } + if y == 3 { + col = sprrow( + x, 1., 2., 2., 2., 2., 1., 3., 3., 1., 2., 1., 1., 1., 1., 1., 1., + ); + } + if y == 2 { + col = sprrow( + x, 1., 2., 2., 2., 1., 3., 3., 3., 3., 1., 2., 2., 2., 2., 1., 0., + ); + } + if y == 1 { + col = sprrow( + x, 0., 1., 1., 1., 1., 3., 3., 3., 3., 3., 1., 1., 1., 1., 1., 0., + ); + } + if y == 0 { + col = sprrow( + x, 0., 0., 0., 0., 0., 1., 1., 1., 1., 1., 0., 0., 0., 0., 0., 0., + ); + } + + col = select((x as f32).rem_euclid(8.0) as i32, col); + if col == 1.0 { + self.frag_color = rgb(82, 56, 70); // outline color (black) + } else if col == 2.0 { + self.frag_color = rgb(250, 250, 250); // eye color (white) + } else if col == 3.0 { + self.frag_color = rgb(247, 182, 67); // normal yellow color + } + + // pass 1 - draw red, light yellow and dark yellow + col = 0.0; // 0 = transparent + if y == 11 { + col = sprrow( + x, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + ); + } + if y == 10 { + col = sprrow( + x, 0., 0., 0., 0., 0., 0., 3., 3., 3., 0., 0., 0., 0., 0., 0., 0., + ); + } + if y == 9 { + col = sprrow( + x, 0., 0., 0., 0., 3., 3., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + ); + } + if y == 8 { + col = sprrow( + x, 0., 0., 0., 3., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + ); + } + if y == 7 { + col = sprrow( + x, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + ); + } + if y == 6 { + col = sprrow( + x, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + ); + } + if y == 5 { + col = sprrow( + x, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + ); + } + if y == 4 { + col = sprrow( + x, 0., 3., 0., 0., 0., 3., 0., 0., 0., 0., 1., 1., 1., 1., 1., 0., + ); + } + if y == 3 { + col = sprrow( + x, 0., 0., 0., 0., 0., 0., 2., 2., 0., 1., 0., 0., 0., 0., 0., 0., + ); + } + if y == 2 { + col = sprrow( + x, 0., 0., 0., 3., 0., 2., 2., 2., 2., 0., 1., 1., 1., 1., 0., 0., + ); + } + if y == 1 { + col = sprrow( + x, 0., 0., 0., 0., 0., 2., 2., 2., 2., 2., 0., 0., 0., 0., 0., 0., + ); + } + if y == 0 { + col = sprrow( + x, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + ); + } + + col = select((x as f32).rem_euclid(8.0) as i32, col); + if col == 1.0 { + self.frag_color = rgb(249, 58, 28); // mouth color (red) + } else if col == 2.0 { + self.frag_color = rgb(222, 128, 55); // brown + } else if col == 3.0 { + self.frag_color = rgb(249, 214, 145); // light yellow + } + } + + fn draw_bird_f1(&mut self, x: i32, y: i32) { + if y < 0 || y > 11 || x < 0 || x > 15 { + return; + } + + // pass 0 - draw black, white and yellow + let mut col: f32 = 0.0; // 0 = transparent + if y == 11 { + col = sprrow( + x, 0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 1., 1., 0., 0., 0., 0., + ); + } + if y == 10 { + col = sprrow( + x, 0., 0., 0., 0., 1., 1., 3., 3., 3., 1., 2., 2., 1., 0., 0., 0., + ); + } + if y == 9 { + col = sprrow( + x, 0., 0., 0., 1., 3., 3., 3., 3., 1., 2., 2., 2., 2., 1., 0., 0., + ); + } + if y == 8 { + col = sprrow( + x, 0., 0., 1., 3., 3., 3., 3., 3., 1., 2., 2., 2., 1., 2., 1., 0., + ); + } + if y == 7 { + col = sprrow( + x, 0., 1., 3., 3., 3., 3., 3., 3., 1., 2., 2., 2., 1., 2., 1., 0., + ); + } + if y == 6 { + col = sprrow( + x, 0., 1., 1., 1., 1., 1., 3., 3., 3., 1., 2., 2., 2., 2., 1., 0., + ); + } + if y == 5 { + col = sprrow( + x, 1., 2., 2., 2., 2., 2., 1., 3., 3., 3., 1., 1., 1., 1., 1., 1., + ); + } + if y == 4 { + col = sprrow( + x, 1., 2., 2., 2., 2., 2., 1., 3., 3., 1., 2., 2., 2., 2., 2., 1., + ); + } + if y == 3 { + col = sprrow( + x, 0., 1., 1., 1., 1., 1., 3., 3., 1., 2., 1., 1., 1., 1., 1., 1., + ); + } + if y == 2 { + col = sprrow( + x, 0., 0., 1., 3., 3., 3., 3., 3., 3., 1., 2., 2., 2., 2., 1., 0., + ); + } + if y == 1 { + col = sprrow( + x, 0., 0., 0., 1., 1., 3., 3., 3., 3., 3., 1., 1., 1., 1., 1., 0., + ); + } + if y == 0 { + col = sprrow( + x, 0., 0., 0., 0., 0., 1., 1., 1., 1., 1., 0., 0., 0., 0., 0., 0., + ); + } + + col = select((x as f32).rem_euclid(8.0) as i32, col); + if col == 1.0 { + self.frag_color = rgb(82, 56, 70); // outline color (black) + } else if col == 2.0 { + self.frag_color = rgb(250, 250, 250); // eye color (white) + } else if col == 3.0 { + self.frag_color = rgb(247, 182, 67); // normal yellow color + } + + // pass 1 - draw red, light yellow and dark yellow + col = 0.0; // 0 = transparent + if y == 11 { + col = sprrow( + x, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + ); + } + if y == 10 { + col = sprrow( + x, 0., 0., 0., 0., 0., 0., 3., 3., 3., 0., 0., 0., 0., 0., 0., 0., + ); + } + if y == 9 { + col = sprrow( + x, 0., 0., 0., 0., 3., 3., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + ); + } + if y == 8 { + col = sprrow( + x, 0., 0., 0., 3., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + ); + } + if y == 7 { + col = sprrow( + x, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + ); + } + if y == 6 { + col = sprrow( + x, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + ); + } + if y == 5 { + col = sprrow( + x, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + ); + } + if y == 4 { + col = sprrow( + x, 0., 3., 0., 0., 0., 3., 0., 0., 0., 0., 1., 1., 1., 1., 1., 0., + ); + } + if y == 3 { + col = sprrow( + x, 0., 0., 0., 0., 0., 0., 2., 2., 0., 1., 0., 0., 0., 0., 0., 0., + ); + } + if y == 2 { + col = sprrow( + x, 0., 0., 0., 2., 2., 2., 2., 2., 2., 0., 1., 1., 1., 1., 0., 0., + ); + } + if y == 1 { + col = sprrow( + x, 0., 0., 0., 0., 0., 2., 2., 2., 2., 2., 0., 0., 0., 0., 0., 0., + ); + } + if y == 0 { + col = sprrow( + x, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + ); + } + + col = select((x as f32).rem_euclid(8.0) as i32, col); + if col == 1.0 { + self.frag_color = rgb(249, 58, 28); // mouth color (red) + } else if col == 2.0 { + self.frag_color = rgb(222, 128, 55); // brown + } else if col == 3.0 { + self.frag_color = rgb(249, 214, 145); // light yellow + } + } + + fn draw_bird_f2(&mut self, x: i32, y: i32) { + if y < 0 || y > 11 || x < 0 || x > 15 { + return; + } + + // pass 0 - draw black, white and yellow + let mut col: f32 = 0.0; // 0 = transparent + if y == 11 { + col = sprrow( + x, 0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 1., 1., 0., 0., 0., 0., + ); + } + if y == 10 { + col = sprrow( + x, 0., 0., 0., 0., 1., 1., 3., 3., 3., 1., 2., 2., 1., 0., 0., 0., + ); + } + if y == 9 { + col = sprrow( + x, 0., 0., 0., 1., 3., 3., 3., 3., 1., 2., 2., 2., 2., 1., 0., 0., + ); + } + if y == 8 { + col = sprrow( + x, 0., 1., 1., 1., 3., 3., 3., 3., 1., 2., 2., 2., 1., 2., 1., 0., + ); + } + if y == 7 { + col = sprrow( + x, 1., 2., 2., 2., 1., 3., 3., 3., 1., 2., 2., 2., 1., 2., 1., 0., + ); + } + if y == 6 { + col = sprrow( + x, 1., 2., 2., 2., 2., 1., 3., 3., 3., 1., 2., 2., 2., 2., 1., 0., + ); + } + if y == 5 { + col = sprrow( + x, 1., 2., 2., 2., 2., 1., 3., 3., 3., 3., 1., 1., 1., 1., 1., 1., + ); + } + if y == 4 { + col = sprrow( + x, 0., 1., 2., 2., 2., 1., 3., 3., 3., 1., 2., 2., 2., 2., 2., 1., + ); + } + if y == 3 { + col = sprrow( + x, 0., 1., 1., 1., 1., 3., 3., 3., 1., 2., 1., 1., 1., 1., 1., 1., + ); + } + if y == 2 { + col = sprrow( + x, 0., 0., 1., 3., 3., 3., 3., 3., 3., 1., 2., 2., 2., 2., 1., 0., + ); + } + if y == 1 { + col = sprrow( + x, 0., 0., 0., 1., 1., 3., 3., 3., 3., 3., 1., 1., 1., 1., 1., 0., + ); + } + if y == 0 { + col = sprrow( + x, 0., 0., 0., 0., 0., 1., 1., 1., 1., 1., 0., 0., 0., 0., 0., 0., + ); + } + + col = select((x as f32).rem_euclid(8.0) as i32, col); + if col == 1.0 { + self.frag_color = rgb(82, 56, 70); // outline color (black) + } else if col == 2.0 { + self.frag_color = rgb(250, 250, 250); // eye color (white) + } else if col == 3.0 { + self.frag_color = rgb(247, 182, 67); // normal yellow color + } + + // pass 1 - draw red, light yellow and dark yellow + col = 0.0; // 0 = transparent + if y == 11 { + col = sprrow( + x, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + ); + } + if y == 10 { + col = sprrow( + x, 0., 0., 0., 0., 0., 0., 3., 3., 3., 0., 0., 0., 0., 0., 0., 0., + ); + } + if y == 9 { + col = sprrow( + x, 0., 0., 0., 0., 3., 3., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + ); + } + if y == 8 { + col = sprrow( + x, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + ); + } + if y == 7 { + col = sprrow( + x, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + ); + } + if y == 6 { + col = sprrow( + x, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + ); + } + if y == 5 { + col = sprrow( + x, 0., 3., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + ); + } + if y == 4 { + col = sprrow( + x, 0., 0., 3., 3., 3., 0., 0., 0., 0., 0., 1., 1., 1., 1., 1., 0., + ); + } + if y == 3 { + col = sprrow( + x, 0., 0., 0., 0., 0., 2., 2., 2., 0., 1., 0., 0., 0., 0., 0., 0., + ); + } + if y == 2 { + col = sprrow( + x, 0., 0., 0., 2., 2., 2., 2., 2., 2., 0., 1., 1., 1., 1., 0., 0., + ); + } + if y == 1 { + col = sprrow( + x, 0., 0., 0., 0., 0., 2., 2., 2., 2., 2., 0., 0., 0., 0., 0., 0., + ); + } + if y == 0 { + col = sprrow( + x, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + ); + } + + col = select((x as f32).rem_euclid(8.0) as i32, col); + if col == 1.0 { + self.frag_color = rgb(249, 58, 28); // mouth color (red) + } else if col == 2.0 { + self.frag_color = rgb(222, 128, 55); // brown + } else if col == 3.0 { + self.frag_color = rgb(249, 214, 145); // light yellow + } + } + + fn get_level_pixel(&self, frag_coord: Vec2) -> Vec2 { + // Get the current game pixel + // (Each game pixel is two screen pixels) + // (or four, if the screen is larger) + let mut x: f32 = frag_coord.x / 2.0; + let mut y: f32 = frag_coord.y / 2.0; + + if self.inputs.resolution.y >= 640.0 { + x /= 2.0; + y /= 2.0; + } + + if self.inputs.resolution.y < 200.0 { + x *= 2.0; + y *= 2.0; + } + + vec2(x, y) + } + + fn get_level_bounds(&self) -> Vec2 { + // same logic as getLevelPixel, but returns the boundaries of the screen + + let mut x: f32 = self.inputs.resolution.x / 2.0; + let mut y: f32 = self.inputs.resolution.y / 2.0; + + if self.inputs.resolution.y >= 640.0 { + x /= 2.0; + y /= 2.0; + } + + if self.inputs.resolution.y < 200.0 { + x *= 2.0; + y *= 2.0; + } + + vec2(x, y) + } + + fn draw_ground(&mut self, co: Vec2) { + self.draw_horz_rect(co.y, 0.0, 31.0, rgb(221, 216, 148)); + self.draw_horz_rect(co.y, 31.0, 32.0, rgb(208, 167, 84)); // shadow below the green sprites + } + + fn draw_green_stripes(&mut self, co: Vec2) { + let f: i32 = (self.inputs.time * 60.0).rem_euclid(6.0) as i32; + + self.draw_horz_rect(co.y, 32.0, 33.0, rgb(86, 126, 41)); // shadow blow + + let min_y: f32 = 33.0; + let height: f32 = 6.0; + + let dark_green: Vec4 = rgb(117, 189, 58); + let light_green: Vec4 = rgb(158, 228, 97); + + // draw diagonal stripes, and animate them + if (co.y >= min_y) && (co.y < min_y + height) { + let y_pos: f32 = co.y - min_y - f as f32; + let x_pos: f32 = (co.x - y_pos).rem_euclid(height); + + if x_pos >= height / 2.0 { + self.frag_color = dark_green; + } else { + self.frag_color = light_green; + } + } + + self.draw_horz_rect(co.y, 37.0, 38.0, rgb(228, 250, 145)); // shadow highlight above + self.draw_horz_rect(co.y, 38.0, 39.0, rgb(84, 56, 71)); // black separator + } + + fn draw_tile(&mut self, type_: i32, tile_corner: Vec2, co: Vec2) { + if (co.x < tile_corner.x) + || (co.x > (tile_corner.x + 16.0)) + || (co.y < tile_corner.y) + || (co.y > (tile_corner.y + 16.0)) + { + return; + } + + let mod_x: i32 = (co.x - tile_corner.x).rem_euclid(16.0) as i32; + let mod_y: i32 = (co.y - tile_corner.y).rem_euclid(16.0) as i32; + + if type_ == 0 { + self.draw_low_bush(mod_x, mod_y); + } else if type_ == 1 { + self.draw_high_bush(mod_x, mod_y); + } else if type_ == 2 { + self.draw_cloud(mod_x, mod_y); + } else if type_ == 3 { + self.draw_bird_f0(mod_x, mod_y); + } else if type_ == 4 { + self.draw_bird_f1(mod_x, mod_y); + } else if type_ == 5 { + self.draw_bird_f2(mod_x, mod_y); + } + } + + fn draw_vert_line(&mut self, co: Vec2, x_pos: f32, y_start: f32, y_end: f32, color: Vec4) { + if (co.x >= x_pos) && (co.x < (x_pos + 1.0)) && (co.y >= y_start) && (co.y < y_end) { + self.frag_color = color; + } + } + + fn draw_horz_line(&mut self, co: Vec2, y_pos: f32, x_start: f32, x_end: f32, color: Vec4) { + if (co.y >= y_pos) && (co.y < (y_pos + 1.0)) && (co.x >= x_start) && (co.x < x_end) { + self.frag_color = color; + } + } + + fn draw_horz_gradient_rect( + &mut self, + co: Vec2, + bottom_left: Vec2, + top_right: Vec2, + left_color: Vec4, + right_color: Vec4, + ) { + if (co.x < bottom_left.x) + || (co.y < bottom_left.y) + || (co.x > top_right.x) + || (co.y > top_right.y) + { + return; + } + + let distance_ratio: f32 = (co.x - bottom_left.x) / (top_right.x - bottom_left.x); + + self.frag_color = (1.0 - distance_ratio) * left_color + distance_ratio * right_color; + } + + fn draw_bottom_pipe(&mut self, co: Vec2, x_pos: f32, height: f32) { + if (co.x < x_pos) + || (co.x > (x_pos + PIPE_WIDTH)) + || (co.y < PIPE_BOTTOM) + || (co.y > (PIPE_BOTTOM + height)) + { + return; + } + + // draw the bottom part of the pipe + // outlines + let bottom_part_end: f32 = PIPE_BOTTOM - PIPE_HOLE_HEIGHT + height; + self.draw_vert_line( + co, + x_pos + 1.0, + PIPE_BOTTOM, + bottom_part_end, + PIPE_OUTLINE_COLOR, + ); + self.draw_vert_line( + co, + x_pos + PIPE_WIDTH - 2.0, + PIPE_WIDTH, + bottom_part_end, + PIPE_OUTLINE_COLOR, + ); + + // gradient fills + self.draw_horz_gradient_rect( + co, + vec2(x_pos + 2.0, PIPE_BOTTOM), + vec2(x_pos + 10.0, bottom_part_end), + rgb(133, 168, 75), + rgb(228, 250, 145), + ); + self.draw_horz_gradient_rect( + co, + vec2(x_pos + 10.0, PIPE_BOTTOM), + vec2(x_pos + 20.0, bottom_part_end), + rgb(228, 250, 145), + rgb(86, 126, 41), + ); + self.draw_horz_gradient_rect( + co, + vec2(x_pos + 20.0, PIPE_BOTTOM), + vec2(x_pos + 24.0, bottom_part_end), + rgb(86, 126, 41), + rgb(86, 126, 41), + ); + + // shadows + self.draw_horz_line( + co, + bottom_part_end - 1.0, + x_pos + 2.0, + x_pos + PIPE_WIDTH - 2.0, + rgb(86, 126, 41), + ); + + // draw the pipe opening + // outlines + self.draw_vert_line( + co, + x_pos, + bottom_part_end, + bottom_part_end + PIPE_HOLE_HEIGHT, + PIPE_OUTLINE_COLOR, + ); + self.draw_vert_line( + co, + x_pos + PIPE_WIDTH - 1.0, + bottom_part_end, + bottom_part_end + PIPE_HOLE_HEIGHT, + PIPE_OUTLINE_COLOR, + ); + self.draw_horz_line( + co, + bottom_part_end, + x_pos, + x_pos + PIPE_WIDTH - 1.0, + PIPE_OUTLINE_COLOR, + ); + self.draw_horz_line( + co, + bottom_part_end + PIPE_HOLE_HEIGHT - 1.0, + x_pos, + x_pos + PIPE_WIDTH - 1.0, + PIPE_OUTLINE_COLOR, + ); + + // gradient fills + let gradient_bottom: f32 = bottom_part_end + 1.0; + let gradient_top: f32 = bottom_part_end + PIPE_HOLE_HEIGHT - 1.0; + self.draw_horz_gradient_rect( + co, + vec2(x_pos + 1.0, gradient_bottom), + vec2(x_pos + 5.0, gradient_top), + rgb(221, 234, 131), + rgb(228, 250, 145), + ); + self.draw_horz_gradient_rect( + co, + vec2(x_pos + 5.0, gradient_bottom), + vec2(x_pos + 22.0, gradient_top), + rgb(228, 250, 145), + rgb(86, 126, 41), + ); + self.draw_horz_gradient_rect( + co, + vec2(x_pos + 22.0, gradient_bottom), + vec2(x_pos + 25.0, gradient_top), + rgb(86, 126, 41), + rgb(86, 126, 41), + ); + + // shadows + self.draw_horz_line( + co, + gradient_bottom, + x_pos + 1.0, + x_pos + 25.0, + rgb(86, 126, 41), + ); + self.draw_horz_line( + co, + gradient_top - 1.0, + x_pos + 1.0, + x_pos + 25.0, + rgb(122, 158, 67), + ); + } + + fn draw_top_pipe(&mut self, co: Vec2, x_pos: f32, height: f32) { + let bounds: Vec2 = self.get_level_bounds(); + + if (co.x < x_pos) + || (co.x > (x_pos + PIPE_WIDTH)) + || (co.y < (bounds.y - height)) + || (co.y > bounds.y) + { + return; + } + + // draw the bottom part of the pipe + // outlines + let bottom_part_end: f32 = bounds.y + PIPE_HOLE_HEIGHT - height; + self.draw_vert_line( + co, + x_pos + 1.0, + bottom_part_end, + bounds.y, + PIPE_OUTLINE_COLOR, + ); + self.draw_vert_line( + co, + x_pos + PIPE_WIDTH - 2.0, + bottom_part_end, + bounds.y, + PIPE_OUTLINE_COLOR, + ); + + // gradient fills + self.draw_horz_gradient_rect( + co, + vec2(x_pos + 2.0, bottom_part_end), + vec2(x_pos + 10.0, bounds.y), + rgb(133, 168, 75), + rgb(228, 250, 145), + ); + self.draw_horz_gradient_rect( + co, + vec2(x_pos + 10.0, bottom_part_end), + vec2(x_pos + 20.0, bounds.y), + rgb(228, 250, 145), + rgb(86, 126, 41), + ); + self.draw_horz_gradient_rect( + co, + vec2(x_pos + 20.0, bottom_part_end), + vec2(x_pos + 24.0, bounds.y), + rgb(86, 126, 41), + rgb(86, 126, 41), + ); + + // shadows + self.draw_horz_line( + co, + bottom_part_end + 1.0, + x_pos + 2.0, + x_pos + PIPE_WIDTH - 2.0, + rgb(86, 126, 41), + ); + + // draw the pipe opening + // outlines + self.draw_vert_line( + co, + x_pos, + bottom_part_end - PIPE_HOLE_HEIGHT, + bottom_part_end, + PIPE_OUTLINE_COLOR, + ); + self.draw_vert_line( + co, + x_pos + PIPE_WIDTH - 1.0, + bottom_part_end - PIPE_HOLE_HEIGHT, + bottom_part_end, + PIPE_OUTLINE_COLOR, + ); + self.draw_horz_line( + co, + bottom_part_end, + x_pos, + x_pos + PIPE_WIDTH, + PIPE_OUTLINE_COLOR, + ); + self.draw_horz_line( + co, + bottom_part_end - PIPE_HOLE_HEIGHT, + x_pos, + x_pos + PIPE_WIDTH - 1.0, + PIPE_OUTLINE_COLOR, + ); + + // gradient fills + let gradient_bottom: f32 = bottom_part_end - PIPE_HOLE_HEIGHT + 1.0; + let gradient_top: f32 = bottom_part_end; + self.draw_horz_gradient_rect( + co, + vec2(x_pos + 1.0, gradient_bottom), + vec2(x_pos + 5.0, gradient_top), + rgb(221, 234, 131), + rgb(228, 250, 145), + ); + self.draw_horz_gradient_rect( + co, + vec2(x_pos + 5.0, gradient_bottom), + vec2(x_pos + 22.0, gradient_top), + rgb(228, 250, 145), + rgb(86, 126, 41), + ); + self.draw_horz_gradient_rect( + co, + vec2(x_pos + 22.0, gradient_bottom), + vec2(x_pos + 25.0, gradient_top), + rgb(86, 126, 41), + rgb(86, 126, 41), + ); + + // shadows + self.draw_horz_line( + co, + gradient_bottom, + x_pos + 1.0, + x_pos + 25.0, + rgb(122, 158, 67), + ); + self.draw_horz_line( + co, + gradient_top - 1.0, + x_pos + 1.0, + x_pos + 25.0, + rgb(86, 126, 41), + ); + } + + fn draw_bush_group(&mut self, mut bottom_corner: Vec2, co: Vec2) { + self.draw_tile(0, bottom_corner, co); + bottom_corner.x += 13.0; + + self.draw_tile(1, bottom_corner, co); + bottom_corner.x += 13.0; + + self.draw_tile(0, bottom_corner, co); + } + + fn draw_bushes(&mut self, co: Vec2) { + self.draw_horz_rect(co.y, 39.0, 70.0, rgb(100, 224, 117)); + + for i in 0..20 { + let x_offset: f32 = i as f32 * 45.0; + self.draw_bush_group(vec2(x_offset, 70.0), co); + self.draw_bush_group(vec2(x_offset + 7.0, 68.0), co); + self.draw_bush_group(vec2(x_offset - 16.0, 65.0), co); + } + } + + fn draw_clouds(&mut self, co: Vec2) { + for i in 0..20 { + let x_offset: f32 = i as f32 * 40.0; + self.draw_tile(2, vec2(x_offset, 95.0), co); + self.draw_tile(2, vec2(x_offset + 14.0, 91.0), co); + self.draw_tile(2, vec2(x_offset + 28.0, 93.0), co); + } + + self.draw_horz_rect(co.y, 70.0, 95.0, rgb(233, 251, 218)); + } + + fn draw_pipe_pair(&mut self, co: Vec2, x_pos: f32, bottom_pipe_height: f32) { + let bounds: Vec2 = self.get_level_bounds(); + let top_pipe_height: f32 = bounds.y - (VERT_PIPE_DISTANCE + PIPE_BOTTOM + bottom_pipe_height); + + self.draw_bottom_pipe(co, x_pos, bottom_pipe_height); + self.draw_top_pipe(co, x_pos, top_pipe_height); + } + + fn draw_pipes(&mut self, co: Vec2) { + // calculate the starting position of the pipes according to the current frame + let animation_cycle_length: f32 = HORZ_PIPE_DISTANCE * PIPE_PER_CYCLE; // the number of frames after which the animation should repeat itself + let f: i32 = (self.inputs.time * 60.0).rem_euclid(animation_cycle_length) as i32; + let mut x_pos: f32 = -f as f32; + + let center: f32 = (PIPE_MAX + PIPE_MIN) / 2.0; + let half_top: f32 = (center + PIPE_MAX) / 2.0; + let half_bottom: f32 = (center + PIPE_MIN) / 2.0; + + for i in 0..12 { + let mut y_pos: f32 = center; + let cycle: i32 = (i as f32).rem_euclid(8.0) as i32; + + if (cycle == 1) || (cycle == 3) { + y_pos = half_top; + } else if cycle == 2 { + y_pos = PIPE_MAX; + } else if (cycle == 5) || (cycle == 7) { + y_pos = half_bottom; + } else if cycle == 6 { + y_pos = PIPE_MIN; + } + + self.draw_pipe_pair(co, x_pos, y_pos); + x_pos += HORZ_PIPE_DISTANCE; + } + } + + fn draw_bird(&mut self, co: Vec2) { + let animation_cycle_length: f32 = HORZ_PIPE_DISTANCE * PIPE_PER_CYCLE; // the number of frames after which the animation should repeat itself + let cycle_frame: i32 = (self.inputs.time * 60.0).rem_euclid(animation_cycle_length) as i32; + let f_cycle_frame: f32 = cycle_frame as f32; + + let start_pos: f32 = 110.0; + let speed: f32 = 2.88; + let updown_delta: f32 = 0.16; + let acceleration: f32 = -0.0975; + let jump_frame: f32 = (self.inputs.time * 60.0).rem_euclid(30.0) as i32 as f32; + let horz_dist: i32 = HORZ_PIPE_DISTANCE as i32; + + // calculate the "jumping" effect on the Y axis. + // Using equations of motion, const acceleration: x = x0 + v0*t + 1/2at^2 + let mut y_pos: f32 = start_pos + speed * jump_frame + acceleration * jump_frame.powf(2.0); + + let speed_delta: f32 = updown_delta * f_cycle_frame.rem_euclid(HORZ_PIPE_DISTANCE); + let mut prev_up_cycles: i32 = 0; + let mut prev_down_cycles: i32 = 0; + + // count the number of pipes we've already passed. + // for each such pipe, we deduce if we went "up" or "down" in Y + let cycle_count: i32 = (f_cycle_frame / HORZ_PIPE_DISTANCE) as i32; + + for i in 0..10 { + if i <= cycle_count { + if i == 1 { + prev_up_cycles += 1; + } + + if (i >= 2) && (i < 6) { + prev_down_cycles += 1; + } + if i >= 6 { + prev_up_cycles += 1; + } + } + } + + // add up/down delta from all the previous pipes + y_pos += ((prev_up_cycles - prev_down_cycles) as f32) * HORZ_PIPE_DISTANCE * updown_delta; + + // calculate the up/down delta for the current two pipes, and add it to the previous result + if ((cycle_frame >= 0) && (cycle_frame < horz_dist)) + || ((cycle_frame >= 5 * horz_dist) && (cycle_frame < 9 * horz_dist)) + { + y_pos += speed_delta; + } else { + y_pos -= speed_delta; + } + + let anim_frame: i32 = (self.inputs.time * 7.0).rem_euclid(3.0) as i32; + if anim_frame == 0 { + self.draw_tile(3, vec2(105.0, y_pos as i32 as f32), co); + } + if anim_frame == 1 { + self.draw_tile(4, vec2(105.0, y_pos as i32 as f32), co); + } + if anim_frame == 2 { + self.draw_tile(5, vec2(105.0, y_pos as i32 as f32), co); + } + } + + fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { + let level_pixel: Vec2 = self.get_level_pixel(frag_coord); + + self.frag_color = rgb(113, 197, 207); // draw the blue sky background + + self.draw_ground(level_pixel); + self.draw_green_stripes(level_pixel); + self.draw_clouds(level_pixel); + self.draw_bushes(level_pixel); + self.draw_pipes(level_pixel); + self.draw_bird(level_pixel); + + *frag_color = self.frag_color; + } +} diff --git a/shaders/src/shaders/galaxy_of_universes.rs b/shaders/src/shaders/galaxy_of_universes.rs new file mode 100644 index 0000000..5f2432a --- /dev/null +++ b/shaders/src/shaders/galaxy_of_universes.rs @@ -0,0 +1,80 @@ +//! Ported to Rust from +//! +//! Original comment: +//! ```glsl +//! // https://www.shadertoy.com/view/MdXSzS +//! // The Big Bang - just a small explosion somewhere in a massive Galaxy of Universes. +//! // Outside of this there's a massive galaxy of 'Galaxy of Universes'... etc etc. :D +//! +//! // To fake a perspective it takes advantage of the screen being wider than it is tall. +//! ``` + +use crate::shader_prelude::*; + +pub const SHADER_DEFINITION: ShaderDefinition = ShaderDefinition { + name: "Galaxy of Universes", +}; + +pub fn shader_fn(render_instruction: &ShaderInput, render_result: &mut ShaderResult) { + let color = &mut render_result.color; + let &ShaderInput { + resolution, + time, + frag_coord, + .. + } = render_instruction; + Inputs { resolution, time }.main_image(color, frag_coord); +} + +struct Inputs { + resolution: Vec3, + time: f32, +} + +impl Inputs { + fn main_image(&self, frag_color: &mut Vec4, frag_coord: Vec2) { + let uv: Vec2 = (frag_coord / self.resolution.xy()) - Vec2::splat(0.5); + let t: f32 = + self.time * 0.1 + ((0.25 + 0.05 * (self.time * 0.1).sin()) / (uv.length() + 0.07)) * 2.2; + let si: f32 = t.sin(); + let co: f32 = t.cos(); + let ma: Mat2 = Mat2::from_cols_array(&[co, si, -si, co]); + + let mut v1: f32 = 0.0; + let mut v2: f32 = 0.0; + let mut v3: f32 = 0.0; + + let mut s: f32 = 0.0; + + for _ in 0..90 { + let mut p: Vec3 = s * uv.extend(0.0); + p = (ma.transpose() * p.xy()).extend(p.z); + p += vec3(0.22, 0.3, s - 1.5 - (self.time * 0.13).sin() * 0.1); + for _ in 0..8 { + p = p.abs() / p.dot(p) - Vec3::splat(0.659); + } + v1 += p.dot(p) * 0.0015 * (1.8 + ((uv * 13.0).length() + 0.5 - self.time * 0.2).sin()); + v2 += p.dot(p) * 0.0013 * (1.5 + ((uv * 14.5).length() + 1.2 - self.time * 0.3).sin()); + v3 += (p.xy() * 10.0).length() * 0.0003; + s += 0.035; + } + + let len: f32 = uv.length(); + v1 *= smoothstep(0.7, 0.0, len); + v2 *= smoothstep(0.5, 0.0, len); + v3 *= smoothstep(0.9, 0.0, len); + + let col: Vec3 = vec3( + v3 * (1.5 + (self.time * 0.2).sin() * 0.4), + (v1 + v3) * 0.3, + v2, + ) + Vec3::splat(smoothstep(0.2, 0.0, len) * 0.85) + + Vec3::splat(smoothstep(0.0, 0.6, v3) * 0.3); + + *frag_color = col + .abs() + .powf_vec(Vec3::splat(1.2)) + .min(Vec3::splat(1.0)) + .extend(1.0); + } +} diff --git a/shaders/src/shaders/geodesic_tiling.rs b/shaders/src/shaders/geodesic_tiling.rs new file mode 100644 index 0000000..d445a53 --- /dev/null +++ b/shaders/src/shaders/geodesic_tiling.rs @@ -0,0 +1,810 @@ +//! Ported to Rust from + +use crate::shader_prelude::*; + +pub const SHADER_DEFINITION: ShaderDefinition = ShaderDefinition { + name: "Geodesic Tiling", +}; + +pub fn shader_fn(render_instruction: &ShaderInput, render_result: &mut ShaderResult) { + let color = &mut render_result.color; + let &ShaderInput { + resolution, + time, + frag_coord, + mouse, + .. + } = render_instruction; + State::new(Inputs { + resolution, + time, + mouse, + }) + .main_image(color, frag_coord); +} + +struct Inputs { + resolution: Vec3, + time: f32, + mouse: Vec4, +} + +struct State { + inputs: Inputs, + + face_plane: Vec3, + u_plane: Vec3, + v_plane: Vec3, + + nc: Vec3, + pab: Vec3, + pbc: Vec3, + pca: Vec3, + + time: f32, +} + +impl State { + #[must_use] + fn new(inputs: Inputs) -> Self { + Self { + inputs, + face_plane: Vec3::ZERO, + u_plane: Vec3::ZERO, + v_plane: Vec3::ZERO, + nc: Vec3::ZERO, + pab: Vec3::ZERO, + pbc: Vec3::ZERO, + pca: Vec3::ZERO, + time: 0.0, + } + } +} + +const MODEL_ROTATION: Vec2 = vec2(0.3, 0.25); +const CAMERA_ROTATION: Vec2 = vec2(0.5, 0.5); + +// 0: Defaults +// 1: Model +// 2: Camera +const MOUSE_CONTROL: i32 = 1; + +const DEBUG: bool = false; + +// 1, 2, or 3 +const LOOP: usize = 0; + +// -------------------------------------------------------- +// HG_SDF +// https://www.shadertoy.com/view/Xs3GRB +// -------------------------------------------------------- + +fn p_r(p: &mut Vec2, a: f32) { + *p = a.cos() * *p + a.sin() * vec2(p.y, -p.x); +} + +fn p_reflect(p: &mut Vec3, plane_normal: Vec3, offset: f32) -> f32 { + let t: f32 = p.dot(plane_normal) + offset; + if t < 0.0 { + *p -= (2. * t) * plane_normal; + } + t.sign_gl() +} + +fn smax(a: f32, b: f32, r: f32) -> f32 { + let m: f32 = a.max(b); + if (-a < r) && (-b < r) { + #[expect( + clippy::imprecise_flops, + reason = "Rust GPU does not yet support hypot from libm" + )] + m.max(-(r - ((r + a) * (r + a) + (r + b) * (r + b)).sqrt())) + } else { + m + } +} + +// -------------------------------------------------------- +// Icosahedron domain mirroring +// Adapted from knighty https://www.shadertoy.com/view/MsKGzw +// -------------------------------------------------------- + +use core::f32::consts::PI; + +const TYPE: i32 = 5; + +impl State { + fn init_icosahedron(&mut self) { + //setup folding planes and vertex + let cospin: f32 = (PI / TYPE as f32).cos(); + let scospin: f32 = (0.75 - cospin * cospin).sqrt(); + self.nc = vec3(-0.5, -cospin, scospin); //3rd folding plane. The two others are xz and yz planes + self.pbc = vec3(scospin, 0., 0.5); //No normalization in order to have 'barycentric' coordinates work evenly + self.pca = vec3(0., scospin, cospin); + self.pbc = self.pbc.normalize(); + self.pca = self.pca.normalize(); //for slightly better DE. In reality it's not necesary to apply normalization :) + self.pab = vec3(0.0, 0.0, 1.0); + + self.face_plane = self.pca; + self.u_plane = vec3(1.0, 0.0, 0.0).cross(self.face_plane); + self.v_plane = vec3(1.0, 0.0, 0.0); + } + + fn p_mod_icosahedron(&self, p: &mut Vec3) { + *p = p.abs(); + p_reflect(p, self.nc, 0.0); + *p = p.xy().abs().extend(p.z); + p_reflect(p, self.nc, 0.0); + *p = p.xy().abs().extend(p.z); + p_reflect(p, self.nc, 0.0); + } +} + +// -------------------------------------------------------- +// Triangle tiling +// Adapted from mattz https://www.shadertoy.com/view/4d2GzV +// -------------------------------------------------------- + +const I3: f32 = 0.5773502691896258; + +const CART2HEX: Mat2 = mat2(vec2(1.0, 0.0), vec2(I3, 2.0 * I3)); +const HEX2CART: Mat2 = mat2(vec2(1.0, 0.0), vec2(-0.5, 0.5 * SQRT_3)); + +struct TriPoints { + a: Vec2, + b: Vec2, + c: Vec2, + center: Vec2, + ab: Vec2, + bc: Vec2, + ca: Vec2, +} + +fn closest_tri_points(p: Vec2) -> TriPoints { + let p_tri: Vec2 = CART2HEX * p; + let pi: Vec2 = p_tri.floor(); + let pf: Vec2 = p_tri.fract_gl(); + + let split1: f32 = pf.y.step(pf.x); + let split2: f32 = pf.x.step(pf.y); + + let mut a: Vec2 = vec2(split1, 1.0); + let mut b: Vec2 = vec2(1.0, split2); + let mut c: Vec2 = vec2(0.0, 0.0); + + a += pi; + b += pi; + c += pi; + + a = HEX2CART * a; + b = HEX2CART * b; + c = HEX2CART * c; + + let center: Vec2 = (a + b + c) / 3.; + + let ab: Vec2 = (a + b) / 2.; + let bc: Vec2 = (b + c) / 2.; + let ca: Vec2 = (c + a) / 2.; + + TriPoints { + a, + b, + c, + center, + ab, + bc, + ca, + } +} + +// -------------------------------------------------------- +// Geodesic tiling +// -------------------------------------------------------- + +struct TriPoints3D { + a: Vec3, + b: Vec3, + c: Vec3, + center: Vec3, + ab: Vec3, + bc: Vec3, + ca: Vec3, +} + +fn intersection(n: Vec3, plane_normal: Vec3, plane_offset: f32) -> Vec3 { + let denominator: f32 = plane_normal.dot(n); + let t: f32 = (Vec3::ZERO.dot(plane_normal) + plane_offset) / -denominator; + n * t +} + +//// Edge length of an icosahedron with an inscribed sphere of radius of 1 +//float edgeLength = 1. / ((sqrt(3.) / 12.) * (3. + sqrt(5.))); +//// Inner radius of the icosahedron's face +//float faceRadius = (1./6.) * sqrt(3.) * edgeLength; +const FACE_RADIUS: f32 = 0.3819660112501051; + +impl State { + // 2D coordinates on the icosahedron face + fn icosahedron_face_coordinates(&self, p: Vec3) -> Vec2 { + let pn: Vec3 = p.normalize(); + let i: Vec3 = intersection(pn, self.face_plane, -1.0); + vec2(i.dot(self.u_plane), i.dot(self.v_plane)) + } + + // Project 2D icosahedron face coordinates onto a sphere + fn face_to_sphere(&self, face_point: Vec2) -> Vec3 { + (self.face_plane + (self.u_plane * face_point.x) + (self.v_plane * face_point.y)).normalize() + } + + fn geodesic_tri_points(&self, p: Vec3, subdivisions: f32) -> TriPoints3D { + // Get 2D cartesian coordiantes on that face + let uv: Vec2 = self.icosahedron_face_coordinates(p); + + // Get points on the nearest triangle tile + let uv_scale: f32 = subdivisions / FACE_RADIUS / 2.0; + let points: TriPoints = closest_tri_points(uv * uv_scale); + + // Project 2D triangle coordinates onto a sphere + let a: Vec3 = self.face_to_sphere(points.a / uv_scale); + let b: Vec3 = self.face_to_sphere(points.b / uv_scale); + let c: Vec3 = self.face_to_sphere(points.c / uv_scale); + let center: Vec3 = self.face_to_sphere(points.center / uv_scale); + let ab: Vec3 = self.face_to_sphere(points.ab / uv_scale); + let bc: Vec3 = self.face_to_sphere(points.bc / uv_scale); + let ca: Vec3 = self.face_to_sphere(points.ca / uv_scale); + + TriPoints3D { + a, + b, + c, + center, + ab, + bc, + ca, + } + } +} + +// -------------------------------------------------------- +// Spectrum colour palette +// IQ https://www.shadertoy.com/view/ll2GD3 +// -------------------------------------------------------- + +fn pal(t: f32, a: Vec3, b: Vec3, c: Vec3, d: Vec3) -> Vec3 { + a + b * (TAU * (c * t + d)).cos() +} + +fn spectrum(n: f32) -> Vec3 { + pal( + n, + vec3(0.5, 0.5, 0.5), + vec3(0.5, 0.5, 0.5), + vec3(1.0, 1.0, 1.0), + vec3(0.0, 0.33, 0.67), + ) +} + +// -------------------------------------------------------- +// Model/Camera Rotation +// -------------------------------------------------------- + +fn spherical_matrix(theta: f32, phi: f32) -> Mat3 { + let cx: f32 = theta.cos(); + let cy: f32 = phi.cos(); + let sx: f32 = theta.sin(); + let sy: f32 = phi.sin(); + Mat3::from_cols_array(&[cy, -sy * -sx, -sy * cx, 0.0, cx, sx, sy, cy * -sx, cy * cx]) +} + +impl State { + fn mouse_rotation(&self, enable: bool, mut xy: Vec2) -> Mat3 { + if enable { + let mouse: Vec2 = self.inputs.mouse.xy() / self.inputs.resolution.xy(); + + if mouse.x != 0. && mouse.y != 0. { + xy.x = mouse.x; + xy.y = mouse.y; + } + } + + let rx = (xy.y + 0.5) * PI; + let ry = (-xy.x) * 2.0 * PI; + + spherical_matrix(rx, ry) + } + + fn model_rotation(&self) -> Mat3 { + self.mouse_rotation(MOUSE_CONTROL == 1, MODEL_ROTATION) + } + + fn camera_rotation(&self) -> Mat3 { + self.mouse_rotation(MOUSE_CONTROL == 2, CAMERA_ROTATION) + } +} + +// -------------------------------------------------------- +// Animation +// -------------------------------------------------------- + +const SCENE_DURATION: f32 = 6.0; +const CROSSFADE_DURATION: f32 = 2.0; + +struct HexSpec { + round_top: f32, + round_corner: f32, + height: f32, + thickness: f32, + gap: f32, +} + +fn new_hex_spec(subdivisions: f32) -> HexSpec { + HexSpec { + round_top: 0.05 / subdivisions, + round_corner: 0.1 / subdivisions, + height: 2.0, + thickness: 2.0, + gap: 0.005, + } +} + +impl State { + // Animation 1 + + fn anim_subdivisions1(&self) -> f32 { + mix(2.4, 3.4, (self.time * PI).cos() * 0.5 + 0.5) + } + + fn anim_hex1(&self, hex_center: Vec3, subdivisions: f32) -> HexSpec { + let mut spec: HexSpec = new_hex_spec(subdivisions); + + let mut offset: f32 = self.time * 3. * PI; + offset -= subdivisions; + let mut blend: f32 = hex_center.dot(self.pca); + blend = (blend * 30. + offset).cos() * 0.5 + 0.5; + spec.height = mix(1.75, 2., blend); + + spec.thickness = spec.height; + + spec + } + // Animation 2 + + fn anim_subdivisions2(&self) -> f32 { + mix(1., 2.3, (self.time * PI / 2.).sin() * 0.5 + 0.5) + } + + fn anim_hex2(&self, hex_center: Vec3, subdivisions: f32) -> HexSpec { + let mut spec: HexSpec = new_hex_spec(subdivisions); + + let blend: f32 = hex_center.y; + spec.height = mix(1.6, 2., (blend * 10. + self.time * PI).sin() * 0.5 + 0.5); + + spec.round_top = 0.02 / subdivisions; + spec.round_corner = 0.09 / subdivisions; + spec.thickness = spec.round_top * 4.0; + spec.gap = 0.01; + + spec + } + + // Animation 3 + + fn anim_subdivisions3(&self) -> f32 { + 5.0 + } + + fn anim_hex3(&self, hex_center: Vec3, subdivisions: f32) -> HexSpec { + let mut spec: HexSpec = new_hex_spec(subdivisions); + + let mut blend: f32 = hex_center.dot(self.pab).acos() * 10.0; + blend = (blend + self.time * PI).cos() * 0.5 + 0.5; + spec.gap = mix(0.01, 0.4, blend) / subdivisions; + + spec.thickness = spec.round_top * 2.; + + spec + } +} + +// Transition between animations + +fn sine_in_out(t: f32) -> f32 { + -0.5 * ((PI * t).cos() - 1.0) +} + +impl State { + fn transition_values(&self, a: f32, b: f32, c: f32) -> f32 { + if LOOP != 0 { + if LOOP == 1 { + return a; + } + if LOOP == 2 { + return b; + } + if LOOP == 3 { + return c; + } + } + let t: f32 = self.time / SCENE_DURATION; + let scene: f32 = t.rem_euclid(3.0).floor(); + let mut blend: f32 = t.fract_gl(); + let delay: f32 = (SCENE_DURATION - CROSSFADE_DURATION) / SCENE_DURATION; + blend = (blend - delay).max(0.0) / (1.0 - delay); + blend = sine_in_out(blend); + let ab: f32 = mix(a, b, blend); + let bc: f32 = mix(b, c, blend); + let cd: f32 = mix(c, a, blend); + let mut result: f32 = mix(ab, bc, scene.min(1.0)); + result = mix(result, cd, (scene - 1.0).max(0.0)); + result + } + + fn transition_hex_specs(&self, a: HexSpec, b: HexSpec, c: HexSpec) -> HexSpec { + let round_top: f32 = self.transition_values(a.round_top, b.round_top, c.round_top); + let round_corner: f32 = self.transition_values(a.round_corner, b.round_corner, c.round_corner); + let height: f32 = self.transition_values(a.height, b.height, c.height); + let thickness: f32 = self.transition_values(a.thickness, b.thickness, c.thickness); + let gap: f32 = self.transition_values(a.gap, b.gap, c.gap); + HexSpec { + round_top, + round_corner, + height, + thickness, + gap, + } + } +} + +// -------------------------------------------------------- +// Modelling +// -------------------------------------------------------- + +const FACE_COLOR: Vec3 = vec3(0.9, 0.9, 1.0); +const BACK_COLOR: Vec3 = vec3(0.1, 0.1, 0.15); +const BACKGROUND_COLOR: Vec3 = vec3(0.0, 0.005, 0.03); + +#[derive(Clone, Copy, Default)] +struct Model { + dist: f32, + albedo: Vec3, + glow: f32, +} + +impl State { + fn hex_model( + &self, + p: Vec3, + hex_center: Vec3, + edge_a: Vec3, + edge_b: Vec3, + spec: HexSpec, + ) -> Model { + let mut d: f32; + + let edge_a_dist: f32 = p.dot(edge_a) + spec.gap; + let edge_b_dist: f32 = p.dot(edge_b) - spec.gap; + let edge_dist: f32 = smax(edge_a_dist, -edge_b_dist, spec.round_corner); + + let outer_dist: f32 = p.length() - spec.height; + d = smax(edge_dist, outer_dist, spec.round_top); + + let inner_dist: f32 = p.length() - spec.height + spec.thickness; + d = smax(d, -inner_dist, spec.round_top); + + let mut color: Vec3; + + let mut face_blend: f32 = (spec.height - p.length()) / spec.thickness; + face_blend = face_blend.clamp(0.0, 1.0); + color = mix(FACE_COLOR, BACK_COLOR, 0.5.step(face_blend)); + + let edge_color: Vec3 = spectrum(hex_center.dot(self.pca) * 5.0 + p.length() + 0.8); + let edge_blend: f32 = smoothstep(-0.04, -0.005, edge_dist); + color = mix(color, edge_color, edge_blend); + + Model { + dist: d, + albedo: color, + glow: edge_blend, + } + } +} + +// checks to see which intersection is closer +fn op_u(m1: Model, m2: Model) -> Model { + if m1.dist < m2.dist { + m1 + } else { + m2 + } +} + +impl State { + fn geodesic_model(&self, mut p: Vec3) -> Model { + self.p_mod_icosahedron(&mut p); + + let subdivisions: f32 = self.transition_values( + self.anim_subdivisions1(), + self.anim_subdivisions2(), + self.anim_subdivisions3(), + ); + let points: TriPoints3D = self.geodesic_tri_points(p, subdivisions); + + let edge_ab: Vec3 = points.center.cross(points.ab).normalize(); + let edge_bc: Vec3 = points.center.cross(points.bc).normalize(); + let edge_ca: Vec3 = points.center.cross(points.ca).normalize(); + + let mut model: Model; + let mut part: Model; + let mut spec: HexSpec; + + spec = self.transition_hex_specs( + self.anim_hex1(points.b, subdivisions), + self.anim_hex2(points.b, subdivisions), + self.anim_hex3(points.b, subdivisions), + ); + part = self.hex_model(p, points.b, edge_ab, edge_bc, spec); + model = part; + + spec = self.transition_hex_specs( + self.anim_hex1(points.c, subdivisions), + self.anim_hex2(points.c, subdivisions), + self.anim_hex3(points.c, subdivisions), + ); + part = self.hex_model(p, points.c, edge_bc, edge_ca, spec); + model = op_u(model, part); + + spec = self.transition_hex_specs( + self.anim_hex1(points.a, subdivisions), + self.anim_hex2(points.a, subdivisions), + self.anim_hex3(points.a, subdivisions), + ); + part = self.hex_model(p, points.a, edge_ca, edge_ab, spec); + model = op_u(model, part); + + model + } + + fn map(&self, mut p: Vec3) -> Model { + let m: Mat3 = self.model_rotation(); + p = m.transpose() * p; + if LOOP == 0 { + p_r(&mut p.xz(), self.time * PI / 16.); + } + let model: Model = self.geodesic_model(p); + model + } +} + +// -------------------------------------------------------- +// LIGHTING +// Adapted from IQ https://www.shadertoy.com/view/Xds3zN +// -------------------------------------------------------- + +fn do_lighting(model: Model, _pos: Vec3, nor: Vec3, _ref: Vec3, rd: Vec3) -> Vec3 { + let light_pos: Vec3 = vec3(0.5, 0.5, -1.0).normalize(); + let back_light_pos: Vec3 = vec3(-0.5, -0.3, 1.0).normalize(); + let ambient_pos: Vec3 = vec3(0.0, 1.0, 0.0); + + let lig: Vec3 = light_pos; + let amb: f32 = ((nor.dot(ambient_pos) + 1.0) / 2.0).clamp(0.0, 1.0); + let dif: f32 = nor.dot(lig).clamp(0.0, 1.0); + let bac: f32 = nor.dot(back_light_pos).clamp(0.0, 1.0).powf(1.5); + let fre: f32 = (1.0 + nor.dot(rd)).clamp(0.0, 1.0).powf(2.0); + + let mut lin: Vec3 = Vec3::ZERO; + lin += 1.20 * dif * Vec3::splat(0.9); + lin += 0.80 * amb * vec3(0.5, 0.7, 0.8); + lin += 0.30 * bac * Vec3::splat(0.25); + lin += 0.20 * fre * Vec3::ONE; + + let albedo: Vec3 = model.albedo; + let col: Vec3 = mix(albedo * lin, albedo, model.glow); + + col +} + +// -------------------------------------------------------- +// Ray Marching +// Adapted from cabbibo https://www.shadertoy.com/view/Xl2XWt +// -------------------------------------------------------- + +const MAX_TRACE_DISTANCE: f32 = 8.0; // max trace distance +const INTERSECTION_PRECISION: f32 = 0.001; // precision of the intersection +const NUM_OF_TRACE_STEPS: i32 = 100; +const FUDGE_FACTOR: f32 = 0.9; // Default is 1, reduce to fix overshoots + +struct CastRay { + origin: Vec3, + direction: Vec3, +} + +struct Ray { + origin: Vec3, + direction: Vec3, + len: f32, +} + +struct Hit { + ray: Ray, + model: Model, + pos: Vec3, + is_background: bool, + normal: Vec3, + color: Vec3, +} + +impl State { + fn calc_normal(&self, pos: Vec3) -> Vec3 { + let eps: Vec3 = vec3(0.001, 0.0, 0.0); + let nor: Vec3 = vec3( + self.map(pos + eps.xyy()).dist - self.map(pos - eps.xyy()).dist, + self.map(pos + eps.yxy()).dist - self.map(pos - eps.yxy()).dist, + self.map(pos + eps.yyx()).dist - self.map(pos - eps.yyx()).dist, + ); + nor.normalize() + } + + fn raymarch(&self, cast_ray: CastRay) -> Hit { + let mut current_dist: f32 = INTERSECTION_PRECISION * 2.0; + let mut model: Model = Model::default(); + + let mut ray: Ray = Ray { + origin: cast_ray.origin, + direction: cast_ray.direction, + len: 0.0, + }; + + for _ in 0..NUM_OF_TRACE_STEPS { + if current_dist < INTERSECTION_PRECISION || ray.len > MAX_TRACE_DISTANCE { + break; + } + model = self.map(ray.origin + ray.direction * ray.len); + current_dist = model.dist; + ray.len += current_dist * FUDGE_FACTOR; + } + + let mut is_background: bool = false; + let mut pos: Vec3 = Vec3::ZERO; + let mut normal: Vec3 = Vec3::ZERO; + let color: Vec3 = Vec3::ZERO; + + if ray.len > MAX_TRACE_DISTANCE { + is_background = true; + } else { + pos = ray.origin + ray.direction * ray.len; + normal = self.calc_normal(pos); + } + + Hit { + ray, + model, + pos, + is_background, + normal, + color, + } + } +} + +// -------------------------------------------------------- +// Rendering +// -------------------------------------------------------- + +fn shade_surface(hit: &mut Hit) { + let mut color: Vec3 = BACKGROUND_COLOR; + + if hit.is_background { + hit.color = color; + return; + } + + let _ref: Vec3 = hit.ray.direction.reflect(hit.normal); + + if DEBUG { + color = hit.normal * 0.5 + Vec3::splat(0.5); + } else { + color = do_lighting(hit.model, hit.pos, hit.normal, _ref, hit.ray.direction); + } + hit.color = color; +} + +fn render(mut hit: Hit) -> Vec3 { + shade_surface(&mut hit); + hit.color +} + +// -------------------------------------------------------- +// Camera +// https://www.shadertoy.com/view/Xl2XWt +// -------------------------------------------------------- + +fn calc_look_at_matrix(ro: Vec3, ta: Vec3, roll: f32) -> Mat3 { + let ww: Vec3 = (ta - ro).normalize(); + let uu: Vec3 = ww.cross(vec3(roll.sin(), roll.cos(), 0.0)).normalize(); + let vv: Vec3 = uu.cross(ww).normalize(); + Mat3::from_cols(uu, vv, ww) +} + +impl State { + fn do_camera( + &self, + cam_pos: &mut Vec3, + cam_tar: &mut Vec3, + cam_roll: &mut f32, + _time: f32, + _mouse: Vec2, + ) { + let dist: f32 = 5.5; + *cam_roll = 0.0; + *cam_tar = vec3(0.0, 0.0, 0.0); + *cam_pos = vec3(0.0, 0.0, -dist); + *cam_pos = self.camera_rotation().transpose() * *cam_pos; + *cam_pos += *cam_tar; + } +} + +// -------------------------------------------------------- +// Gamma +// https://www.shadertoy.com/view/Xds3zN +// -------------------------------------------------------- + +const GAMMA: f32 = 2.2; + +fn gamma(color: Vec3, g: f32) -> Vec3 { + color.powf(g) +} + +fn linear_to_screen(linear_rgb: Vec3) -> Vec3 { + gamma(linear_rgb, 1.0 / GAMMA) +} + +impl State { + fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { + self.time = self.inputs.time; + + if LOOP != 0 { + if LOOP == 1 { + self.time = self.time.rem_euclid(2.0); + } + + if LOOP == 2 { + self.time = self.time.rem_euclid(4.0); + } + + if LOOP == 3 { + self.time = self.time.rem_euclid(2.0); + } + } + + self.init_icosahedron(); + + let p: Vec2 = (-self.inputs.resolution.xy() + 2.0 * frag_coord) / self.inputs.resolution.y; + let m: Vec2 = self.inputs.mouse.xy() / self.inputs.resolution.xy(); + + let mut cam_pos: Vec3 = vec3(0.0, 0.0, 2.0); + let mut cam_tar: Vec3 = vec3(0.0, 0.0, 0.0); + let mut cam_roll: f32 = 0.0; + + // camera movement + self.do_camera(&mut cam_pos, &mut cam_tar, &mut cam_roll, self.time, m); + + // camera matrix + let cam_mat: Mat3 = calc_look_at_matrix(cam_pos, cam_tar, cam_roll); // 0.0 is the camera roll + + // create view ray + let rd: Vec3 = (cam_mat * p.extend(2.0)).normalize(); // 2.0 is the lens length + + let hit: Hit = self.raymarch(CastRay { + origin: cam_pos, + direction: rd, + }); + + let mut color: Vec3 = render(hit); + + if !DEBUG { + color = linear_to_screen(color); + } + + *frag_color = color.extend(1.0); + } +} diff --git a/shaders/src/shaders/heart.rs b/shaders/src/shaders/heart.rs new file mode 100644 index 0000000..fd29bf0 --- /dev/null +++ b/shaders/src/shaders/heart.rs @@ -0,0 +1,71 @@ +//! Ported to Rust from +//! +//! Original comment: +//! ```glsl +//! // Created by inigo quilez - iq/2013 +//! // License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. +//! ``` + +use crate::shader_prelude::*; + +pub const SHADER_DEFINITION: ShaderDefinition = ShaderDefinition { name: "Hearts" }; + +pub fn shader_fn(render_instruction: &ShaderInput, render_result: &mut ShaderResult) { + let color = &mut render_result.color; + let &ShaderInput { + resolution, + time, + frag_coord, + .. + } = render_instruction; + Inputs { resolution, time }.main_image(color, frag_coord); +} + +struct Inputs { + resolution: Vec3, + time: f32, +} + +impl Inputs { + fn main_image(&self, frag_color: &mut Vec4, frag_coord: Vec2) { + let mut p: Vec2 = + (2.0 * frag_coord - self.resolution.xy()) / (self.resolution.xy().min_element()); + + // background color + let bcol: Vec3 = vec3(1.0, 0.8, 0.7 - 0.07 * p.y) * (1.0 - 0.25 * p.length()); + + // animate + let tt: f32 = self.time.rem_euclid(1.5) / 1.5; + let mut ss: f32 = tt.powf(0.2) * 0.5 + 0.5; + ss = 1.0 + ss * 0.5 * (tt * TAU * 3.0 + p.y * 0.5).sin() * (-tt * 4.0).exp(); + p *= vec2(0.5, 1.5) + ss * vec2(0.5, -0.5); + + // shape + let r: f32; + let d: f32; + + if false { + p *= 0.8; + p.y = -0.1 - p.y * 1.2 + p.x.abs() * (1.0 - p.x.abs()); + r = p.length(); + d = 0.5; + } else { + p.y -= 0.25; + let a: f32 = p.x.atan2(p.y) / PI; + r = p.length(); + let h: f32 = a.abs(); + d = (13.0 * h - 22.0 * h * h + 10.0 * h * h * h) / (6.0 - 5.0 * h); + } + + // color + let mut s: f32 = 0.75 + 0.75 * p.x; + s *= 1.0 - 0.4 * r; + s = 0.3 + 0.7 * s; + s *= 0.5 + 0.5 * (1.0 - (r / d).clamp(0.0, 1.0)).powf(0.1); + let hcol: Vec3 = vec3(1.0, 0.5 * r, 0.3) * s; + + let col: Vec3 = mix(bcol, hcol, smoothstep(-0.01, 0.01, d - r)); + + *frag_color = col.extend(1.0); + } +} diff --git a/shaders/src/shaders/luminescence.rs b/shaders/src/shaders/luminescence.rs new file mode 100644 index 0000000..6701fb4 --- /dev/null +++ b/shaders/src/shaders/luminescence.rs @@ -0,0 +1,610 @@ +//! Ported to Rust from +//! +//! Original comment: +//! ```glsl +//! // Luminescence by Martijn Steinrucken aka BigWings - 2017 +//! // Email:countfrolic@gmail.com Twitter:@The_ArtOfCode +//! // License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. +//! +//! // My entry for the monthly challenge (May 2017) on r/proceduralgeneration +//! // Use the mouse to look around. Uncomment the SINGLE define to see one specimen by itself. +//! // Code is a bit of a mess, too lazy to clean up. Hope you like it! +//! +//! // Music by Klaus Lunde +//! // https://soundcloud.com/klauslunde/zebra-tribute +//! +//! // YouTube: The Art of Code -> https://www.youtube.com/channel/UCcAlTqd9zID6aNX3TzwxJXg +//! // Twitter: @The_ArtOfCode +//! ``` + +use crate::shader_prelude::*; + +pub const SHADER_DEFINITION: ShaderDefinition = ShaderDefinition { + name: "Luminescence", +}; + +pub fn shader_fn(render_instruction: &ShaderInput, render_result: &mut ShaderResult) { + let color = &mut render_result.color; + let &ShaderInput { + resolution, + time, + frag_coord, + mouse, + .. + } = render_instruction; + State::new(Inputs { + resolution, + time, + mouse, + }) + .main_image(color, frag_coord); +} + +#[derive(Clone, Copy)] +struct Inputs { + resolution: Vec3, + time: f32, + mouse: Vec4, +} + +struct State { + inputs: Inputs, + + bg: Vec3, // global background color + accent: Vec3, // color of the phosphorecence + + cam: Camera, +} + +impl State { + #[must_use] + fn new(inputs: Inputs) -> Self { + Self { + inputs, + + bg: Vec3::ZERO, + accent: Vec3::ZERO, + + cam: Camera::default(), + } + } +} + +const INVERTMOUSE: f32 = -1.0; + +const MAX_STEPS: u32 = 100; +const VOLUME_STEPS: u32 = 8; +const SINGLE: bool = false; +const _MIN_DISTANCE: f32 = 0.1; +const MAX_DISTANCE: f32 = 100.0; +const HIT_DISTANCE: f32 = 0.01; + +fn b(x: f32, y: f32, z: f32, w: f32) -> f32 { + smoothstep(x - z, x + z, w) * smoothstep(y + z, y - z, w) +} +fn sat(x: f32) -> f32 { + x.clamp(0.0, 1.0) +} +fn sin(x: f32) -> f32 { + x.sin() * 0.5 + 0.5 +} + +const _LF: Vec3 = vec3(1.0, 0.0, 0.0); +const UP: Vec3 = vec3(0.0, 1.0, 0.0); +const _FW: Vec3 = vec3(0.0, 0.0, 1.0); + +const ACCENT_COLOR1: Vec3 = vec3(1.0, 0.1, 0.5); +const SECOND_COLOR1: Vec3 = vec3(0.1, 0.5, 1.0); +const ACCENT_COLOR2: Vec3 = vec3(1.0, 0.5, 0.1); +const SECOND_COLOR2: Vec3 = vec3(0.1, 0.5, 0.6); + +fn _n1(x: f32) -> f32 { + (x.sin() * 5346.1764).fract_gl() +} +fn _n2(x: f32, y: f32) -> f32 { + _n1(x + y * 23414.324) +} + +fn n3(mut p: Vec3) -> f32 { + p = (p * FRAC_1_PI + Vec3::splat(0.1)).fract_gl(); + p *= 17.0; + (p.x * p.y * p.z * (p.x + p.y + p.z)).fract_gl() +} + +#[derive(Clone, Copy, Default)] +struct Ray { + o: Vec3, + d: Vec3, +} + +#[derive(Default)] +struct Camera { + p: Vec3, // the position of the camera + forward: Vec3, // the camera forward vector + left: Vec3, // the camera left vector + up: Vec3, // the camera up vector + + center: Vec3, // the center of the screen, in world coords + i: Vec3, // where the current ray intersects the screen, in world coords + ray: Ray, // the current ray: from cam pos, through current uv projected on screen + look_at: Vec3, // the lookat point + zoom: f32, // the zoom factor +} + +#[derive(Clone, Copy, Default)] +struct De { + // data type used to pass the various bits of information used to shade a de object + d: f32, // final distance to field + m: f32, // material + uv: Vec3, + pump: f32, + + id: Vec3, + pos: Vec3, // the world-space coordinate of the fragment +} + +#[derive(Default)] +struct Rc { + // data type used to handle a repeated coordinate + id: Vec3, // holds the floor'ed coordinate of each cell. Used to identify the cell. + h: Vec3, // half of the size of the cell + p: Vec3, // the repeated coordinate + // c: Vec3; // the center of the cell, world coordinates +} + +fn repeat(pos: Vec3, size: Vec3) -> Rc { + let mut o: Rc = Rc::default(); + o.h = size * 0.5; + o.id = (pos / size).floor(); // used to give a unique id to each cell + + o.p = pos.rem_euclid(size) - o.h; + //o.c = o.id*size+o.h; + + o +} + +impl State { + fn camera_setup(&mut self, uv: Vec2, position: Vec3, look_at: Vec3, zoom: f32) { + self.cam.p = position; + self.cam.look_at = look_at; + self.cam.forward = (self.cam.look_at - self.cam.p).normalize(); + self.cam.left = UP.cross(self.cam.forward); + self.cam.up = self.cam.forward.cross(self.cam.left); + self.cam.zoom = zoom; + + self.cam.center = self.cam.p + self.cam.forward * self.cam.zoom; + self.cam.i = self.cam.center + self.cam.left * uv.x + self.cam.up * uv.y; + + self.cam.ray.o = self.cam.p; // ray origin = camera position + self.cam.ray.d = (self.cam.i - self.cam.p).normalize(); // ray direction is the vector from the cam pos through the point on the imaginary screen + } +} + +// ============== Functions I borrowed ;) + +// 3 out, 1 in... DAVE HOSKINS +fn n31(p: f32) -> Vec3 { + let mut p3: Vec3 = (Vec3::splat(p) * vec3(0.1031, 0.11369, 0.13787)).fract_gl(); + p3 += Vec3::splat(p3.dot(p3.yzx() + Vec3::splat(19.19))); + vec3( + (p3.x + p3.y) * p3.z, + (p3.x + p3.z) * p3.y, + (p3.y + p3.z) * p3.x, + ) + .fract_gl() +} + +// DE functions from IQ +fn smin(a: f32, b: f32, k: f32) -> f32 { + let h: f32 = (0.5 + 0.5 * (b - a) / k).clamp(0.0, 1.0); + mix(b, a, h) - k * h * (1.0 - h) +} + +fn smax(a: f32, b: f32, k: f32) -> f32 { + let h: f32 = (0.5 + 0.5 * (b - a) / k).clamp(0.0, 1.0); + mix(a, b, h) + k * h * (1.0 - h) +} + +fn sd_sphere(p: Vec3, pos: Vec3, s: f32) -> f32 { + (p - pos).length() - s +} + +// From http://mercury.sexy/hg_sdf +fn p_mod_polar(p: &mut Vec2, repetitions: f32, fix: f32) -> Vec2 { + let angle: f32 = TAU / repetitions; + let mut a: f32 = p.y.atan2(p.x) + angle / 2.; + let r: f32 = p.length(); + let _c: f32 = (a / angle).floor(); + a = a.rem_euclid(angle) - (angle / 2.) * fix; + *p = vec2(a.cos(), a.sin()) * r; + + *p +} + +// ------------------------- + +fn dist(p: Vec2, p0: Vec2, p1: Vec2) -> f32 { + //2d point-line distance + + let v: Vec2 = p1 - p0; + let w: Vec2 = p - p0; + + let c1: f32 = w.dot(v); + let c2: f32 = v.dot(v); + + // before P0 + if c1 <= 0. { + return (p - p0).length(); + } + + let b: f32 = c1 / c2; + let pb: Vec2 = p0 + b * v; + (p - pb).length() +} + +fn _closest_point(ro: Vec3, rd: Vec3, p: Vec3) -> Vec3 { + // returns the closest point on ray r to point p + ro + (p - ro).dot(rd).max(0.0) * rd +} + +fn _ray_ray_ts(ro1: Vec3, rd1: Vec3, ro2: Vec3, rd2: Vec3) -> Vec2 { + // returns the two t's for the closest point between two rays + // ro+rd*t1 = ro2+rd2*t2 + + let d_o: Vec3 = ro2 - ro1; + let c_d: Vec3 = rd1.cross(rd2); + let v: f32 = c_d.dot(c_d); + + let t1: f32 = d_o.cross(rd2).dot(c_d) / v; + let t2: f32 = d_o.cross(rd1).dot(c_d) / v; + vec2(t1, t2) +} + +fn _dist_ray_segment(ro: Vec3, rd: Vec3, p1: Vec3, p2: Vec3) -> f32 { + // returns the distance from ray r to line segment p1-p2 + let rd2: Vec3 = p2 - p1; + let mut t: Vec2 = _ray_ray_ts(ro, rd, p1, rd2); + + t.x = t.x.max(0.0); + t.y = t.y.clamp(0.0, rd2.length()); + + let rp: Vec3 = ro + rd * t.x; + let sp: Vec3 = p1 + rd2 * t.y; + + (rp - sp).length() +} + +fn sph(ro: Vec3, rd: Vec3, pos: Vec3, radius: f32) -> Vec2 { + // does a ray sphere intersection + // returns a vec2 with distance to both intersections + // if both a and b are MAX_DISTANCE then there is no intersection + + let oc: Vec3 = pos - ro; + let l: f32 = rd.dot(oc); + let det: f32 = l * l - oc.dot(oc) + radius * radius; + if det < 0.0 { + return Vec2::splat(MAX_DISTANCE); + } + + let d: f32 = det.sqrt(); + let a: f32 = l - d; + let b: f32 = l + d; + + vec2(a, b) +} + +impl State { + fn background(&self, r: Vec3) -> Vec3 { + let x: f32 = r.x.atan2(r.z); // from -pi to pi + let y: f32 = PI * 0.5 - r.y.acos(); // from -1/2pi to 1/2pi + + let mut col: Vec3 = self.bg * (1.0 + y); + + let t: f32 = self.inputs.time; // add god rays + + let a: f32 = r.x.sin(); + + let mut beam: f32 = sat((10.0 * x + a * y * 5.0 + t).sin()); + beam *= sat((7.0 * x + a * y * 3.5 - t).sin()); + + let mut beam2: f32 = sat((42.0 * x + a * y * 21.0 - t).sin()); + beam2 *= sat((34.0 * x + a * y * 17.0 + t).sin()); + + beam += beam2; + col *= 1.0 + beam * 0.05; + + col + } +} + +fn remap(a: f32, b: f32, c: f32, d: f32, t: f32) -> f32 { + ((t - a) / (b - a)) * (d - c) + c +} + +impl State { + fn map(&self, mut p: Vec3, id: Vec3) -> De { + let t: f32 = self.inputs.time * 2.0; + + let n: f32 = n3(id); + + let mut o: De = De::default(); + + let mut x: f32 = (p.y + n * TAU) * 1. + t; + let r: f32 = 1.; + + let pump: f32 = (x + x.cos()).cos() + (2.0 * x).sin() * 0.2 + (4.0 * x).sin() * 0.02; + + x = t + n * TAU; + p.y -= ((x + x.cos()).cos() + (2.0 * x).sin() * 0.2) * 0.6; + p = (p.xz() * (1.0 + pump * 0.2)).extend(p.y).xzy(); + + let d1: f32 = sd_sphere(p, vec3(0.0, 0.0, 0.0), r); + let d2: f32 = sd_sphere(p, vec3(0.0, -0.5, 0.0), r); + + o.d = smax(d1, -d2, 0.1); + o.m = 1.0; + + if p.y < 0.5 { + let sway: f32 = (t + p.y + n * TAU).sin() * smoothstep(0.5, -3.0, p.y) * n * 0.3; + p.x += sway * n; // add some sway to the tentacles + p.z += sway * (1. - n); + + let mut mp: Vec3 = p; + mp = p_mod_polar(&mut mp.xz(), 6.0, 0.0).extend(mp.y).xzy(); + + let mut d3: f32 = (mp.xz() - vec2(0.2, 0.1)).length() - remap(0.5, -3.5, 0.1, 0.01, mp.y); + if d3 < o.d { + o.m = 2.; + } + d3 += ((mp.y * 10.0).sin() + (mp.y * 23.0).sin()) * 0.03; + + let d32: f32 = (mp.xz() - vec2(0.2, 0.1)).length() - remap(0.5, -3.5, 0.1, 0.04, mp.y) * 0.5; + d3 = d3.min(d32); + o.d = smin(o.d, d3, 0.5); + + if p.y < 0.2 { + let mut op: Vec3 = p; + op = p_mod_polar(&mut op.xz(), 13.0, 1.0).extend(op.y).xzy(); + + let d4: f32 = (op.xz() - vec2(0.85, 0.0)).length() - remap(0.5, -3.0, 0.04, 0.0, op.y); + if d4 < o.d { + o.m = 3.0; + } + o.d = smin(o.d, d4, 0.15); + } + } + o.pump = pump; + o.uv = p; + + o.d *= 0.8; + o + } + fn calc_normal(&self, o: De) -> Vec3 { + let eps: Vec3 = vec3(0.01, 0.0, 0.0); + let nor: Vec3 = vec3( + self.map(o.pos + eps.xyy(), o.id).d - self.map(o.pos - eps.xyy(), o.id).d, + self.map(o.pos + eps.yxy(), o.id).d - self.map(o.pos - eps.yxy(), o.id).d, + self.map(o.pos + eps.yyx(), o.id).d - self.map(o.pos - eps.yyx(), o.id).d, + ); + nor.normalize() + } + + fn cast_ray(&self, r: Ray) -> De { + let mut d: f32 = 0.0; + let _d_s: f32 = MAX_DISTANCE; + + let _pos: Vec3 = vec3(0.0, 0.0, 0.0); + let _n: Vec3 = Vec3::ZERO; + let mut o: De = De::default(); + let mut s: De = De::default(); + + let mut d_c: f32 = MAX_DISTANCE; + let mut p: Vec3 = Vec3::ZERO; + let mut q: Rc = Rc::default(); + let t: f32 = self.inputs.time; + let grid: Vec3 = vec3(6.0, 30.0, 6.0); + + for _ in 0..MAX_STEPS { + p = r.o + r.d * d; + + if SINGLE { + s = self.map(p, Vec3::ZERO); + } else { + p.y -= t; // make the move up + p.x += t; // make cam fly forward + + q = repeat(p, grid); + + let r_c: Vec3 = ((2. * Vec3::ZERO.step(r.d) - Vec3::ONE) * q.h - q.p) / r.d; // ray to cell boundary + d_c = r_c.x.min(r_c.y).min(r_c.z) + 0.01; // distance to cell just past boundary + + let n: f32 = n3(q.id); + q.p += (n31(n) - Vec3::splat(0.5)) * grid * vec3(0.5, 0.7, 0.5); + + if dist(q.p.xz(), r.d.xz(), Vec2::ZERO) < 1.1 { + //if(DistRaySegment(q.p, r.d, vec3(0., -6., 0.), vec3(0., -3.3, 0)) <1.1) + s = self.map(q.p, q.id); + } else { + s.d = d_c; + } + } + + if s.d < HIT_DISTANCE || d > MAX_DISTANCE { + break; + } + d += s.d.min(d_c); // move to distance to next cell or surface, whichever is closest + } + + if s.d < HIT_DISTANCE { + o.m = s.m; + o.d = d; + o.id = q.id; + o.uv = s.uv; + o.pump = s.pump; + + if SINGLE { + o.pos = p; + } else { + o.pos = q.p; + } + } + + o + } + + fn vol_tex(&self, uv: Vec3, mut p: Vec3, scale: f32, pump: f32) -> f32 { + // uv = the surface pos + // p = the volume shell pos + + p.y *= scale; + + let mut s2: f32 = 5. * p.x / TAU; + let _id: f32 = s2.floor(); + s2 = s2.fract_gl(); + let ep: Vec2 = vec2(s2 - 0.5, p.y - 0.6); + let ed: f32 = ep.length(); + let e: f32 = b(0.35, 0.45, 0.05, ed); + + let mut s: f32 = sin(s2 * TAU * 15.0); + s = s * s; + s = s * s; + s *= smoothstep(1.4, -0.3, uv.y - (s2 * TAU).cos() * 0.2 + 0.3) * smoothstep(-0.6, -0.3, uv.y); + + let t: f32 = self.inputs.time * 5.0; + let mask: f32 = sin(p.x * TAU * 2.0 + t); + s *= mask * mask * 2.0; + + s + e * pump * 2.0 + } +} + +fn jelly_tex(mut p: Vec3) -> Vec4 { + let s: Vec3 = vec3(p.x.atan2(p.z), p.xz().length(), p.y); + + let mut b: f32 = 0.75 + (s.x * 6.0).sin() * 0.25; + b = mix(1., b, s.y * s.y); + + p.x += (s.z * 10.0).sin() * 0.1; + let mut b2: f32 = (s.x * 26.0).cos() - s.z - 0.7; + + b2 = smoothstep(0.1, 0.6, b2); + Vec4::splat(b + b2) +} + +impl State { + fn render(&mut self, _uv: Vec2, cam_ray: Ray, _depth: f32) -> Vec3 { + // outputs a color + + self.bg = self.background(self.cam.ray.d); + + let mut col: Vec3 = self.bg; + let o: De = self.cast_ray(cam_ray); + + let _t: f32 = self.inputs.time; + let l: Vec3 = UP; + + if o.m > 0.0 { + let n: Vec3 = self.calc_normal(o); + let lambert: f32 = sat(n.dot(l)); + let r: Vec3 = cam_ray.d.reflect(n); + let fresnel: f32 = sat(1.0 + cam_ray.d.dot(n)); + let _trans: f32 = (1.0 - fresnel) * 0.5; + let ref_: Vec3 = self.background(r); + let mut fade: f32 = 0.0; + + if o.m == 1.0 { + // hood color + let mut density: f32 = 0.0; + for i in 0..VOLUME_STEPS { + let sd: f32 = sph(o.uv, cam_ray.d, Vec3::ZERO, 0.8 + i as f32 * 0.015).x; + if sd != MAX_DISTANCE { + let intersect: Vec2 = o.uv.xz() + cam_ray.d.xz() * sd; + + let uv: Vec3 = vec3(intersect.x.atan2(intersect.y), intersect.length(), o.uv.z); + density += self.vol_tex(o.uv, uv, 1.4 + i as f32 * 0.03, o.pump); + } + } + let vol_tex: Vec4 = self.accent.extend(density / VOLUME_STEPS as f32); + + let mut dif: Vec3 = jelly_tex(o.uv).xyz(); + dif *= lambert.max(0.2); + + col = mix(col, vol_tex.xyz(), vol_tex.w); + col = mix(col, dif, 0.25); + + col += fresnel * ref_ * sat(UP.dot(n)); + + //fade + fade = fade.max(smoothstep(0.0, 1.0, fresnel)); + } else if o.m == 2.0 { + // inside tentacles + let dif: Vec3 = self.accent; + col = mix(self.bg, dif, fresnel); + + col *= mix(0.6, 1.0, smoothstep(0.0, -1.5, o.uv.y)); + + let mut prop: f32 = o.pump + 0.25; + prop *= prop * prop; + col += (1.0 - fresnel).powf(20.0) * dif * prop; + + fade = fresnel; + } else if o.m == 3.0 { + // outside tentacles + let dif: Vec3 = self.accent; + let d: f32 = smoothstep(100.0, 13.0, o.d); + col = mix(self.bg, dif, (1.0 - fresnel).powf(5.0) * d); + } + + fade = fade.max(smoothstep(0.0, 100.0, o.d)); + col = mix(col, self.bg, fade); + + if o.m == 4. { + col = vec3(1.0, 0.0, 0.0); + } + } else { + col = self.bg; + } + + col + } + + fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { + let t: f32 = self.inputs.time * 0.04; + + let mut uv: Vec2 = frag_coord / self.inputs.resolution.xy(); + uv -= Vec2::splat(0.5); + uv.y *= self.inputs.resolution.y / self.inputs.resolution.x; + + let mut m: Vec2 = self.inputs.mouse.xy() / self.inputs.resolution.xy(); + + if m.x < 0.05 || m.x > 0.95 { + // move cam automatically when mouse is not used + m = vec2(t * 0.25, sin(t * PI) * 0.5 + 0.5); + } + + self.accent = mix(ACCENT_COLOR1, ACCENT_COLOR2, sin(t * 15.456)); + self.bg = mix(SECOND_COLOR1, SECOND_COLOR2, sin(t * 7.345231)); + + let turn: f32 = (0.1 - m.x) * TAU; + let s: f32 = turn.sin(); + let c: f32 = turn.cos(); + let rot_x: Mat3 = Mat3::from_cols_array(&[c, 0.0, s, 0.0, 1.0, 0.0, s, 0.0, -c]); + + let cam_dist: f32 = if SINGLE { -10.0 } else { -0.1 }; + + let look_at: Vec3 = vec3(0.0, -1.0, 0.0); + + let cam_pos: Vec3 = + rot_x.transpose() * vec3(0.0, INVERTMOUSE * cam_dist * ((m.y) * PI).cos(), cam_dist); + + self.camera_setup(uv, cam_pos + look_at, look_at, 1.0); + + let mut col: Vec3 = self.render(uv, self.cam.ray, 0.0); + + col = col.powf_vec(Vec3::splat(mix(1.5, 2.6, sin(t + PI)))); // post-processing + let d: f32 = 1.0 - uv.dot(uv); // vignette + col *= (d * d * d) + 0.1; + + *frag_color = col.extend(1.0); + } +} diff --git a/shaders/src/shaders/mandelbrot_smooth.rs b/shaders/src/shaders/mandelbrot_smooth.rs new file mode 100644 index 0000000..d33fdc7 --- /dev/null +++ b/shaders/src/shaders/mandelbrot_smooth.rs @@ -0,0 +1,102 @@ +//! Ported to Rust from +//! +//! Original comment: +//! ```glsl +//! // Created by inigo quilez - iq/2013 +//! // License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. +//! +//! // See here for more information on smooth iteration count: +//! // +//! // http://iquilezles.org/www/articles/mset_smooth/mset_smooth.htm +//! ``` + +use crate::shader_prelude::*; + +pub const SHADER_DEFINITION: ShaderDefinition = ShaderDefinition { + name: "Mandelbrot Smooth", +}; + +pub fn shader_fn(render_instruction: &ShaderInput, render_result: &mut ShaderResult) { + let color = &mut render_result.color; + let &ShaderInput { + resolution, + time, + frag_coord, + .. + } = render_instruction; + Inputs { resolution, time }.main_image(color, frag_coord); +} + +struct Inputs { + resolution: Vec3, + time: f32, +} + +// increase this if you have a very fast GPU +const AA: usize = 2; + +impl Inputs { + fn mandelbrot(&self, c: Vec2) -> f32 { + if true { + let c2: f32 = c.dot(c); + // skip computation inside M1 - http://iquilezles.org/www/articles/mset_1bulb/mset1bulb.htm + if 256.0 * c2 * c2 - 96.0 * c2 + 32.0 * c.x - 3.0 < 0.0 { + return 0.0; + } + // skip computation inside M2 - http://iquilezles.org/www/articles/mset_2bulb/mset2bulb.htm + if (16.0 * (c2 + 2.0 * c.x + 1.0) - 1.0) < 0.0 { + return 0.0; + } + } + const B: f32 = 256.0; + let mut l: f32 = 0.0; + let mut z: Vec2 = Vec2::ZERO; + for _ in 0..512 { + z = vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y) + c; + if z.dot(z) > (B * B) { + break; + } + l += 1.0; + } + + if l > 511.0 { + return 0.0; + } + // ------------------------------------------------------ + // smooth interation count + //float sl = l - log(log(length(z))/log(B))/log(2.0); + // + // equivalent optimized smooth interation count + let sl: f32 = l - z.dot(z).log2().log2() + 4.0; + + let al: f32 = smoothstep(-0.1, 0.0, (self.time * 0.5 * TAU).sin()); + mix(l, sl, al) + } + + fn main_image(&self, frag_color: &mut Vec4, frag_coord: Vec2) { + let mut col: Vec3 = Vec3::ZERO; + + for m in 0..AA { + for n in 0..AA { + let p: Vec2 = (-self.resolution.xy() + + Vec2::splat(2.0) * (frag_coord + vec2(m as f32, n as f32) / AA as f32)) + / self.resolution.y; + let w: f32 = (AA * m + n) as f32; + let time: f32 = self.time + 0.5 * (1.0 / 24.0) * w / (AA * AA) as f32; + + let mut zoo: f32 = 0.62 + 0.38 * (0.07 * time).cos(); + let coa: f32 = (0.15 * (1.0 - zoo) * time).cos(); + let sia = (0.15 * (1.0 - zoo) * time).sin(); + zoo = zoo.powf(8.0); + let xy: Vec2 = vec2(p.x * coa - p.y * sia, p.x * sia + p.y * coa); + let c: Vec2 = vec2(-0.745, 0.186) + xy * zoo; + + let l: f32 = self.mandelbrot(c); + col += Vec3::splat(0.5) + + Vec3::splat(0.5) * (Vec3::splat(3.0 + l * 0.15) + vec3(0.0, 0.6, 1.0)).cos(); + } + } + col /= (AA * AA) as f32; + *frag_color = col.extend(1.0); + } +} diff --git a/shaders/src/shaders/miracle_snowflakes.rs b/shaders/src/shaders/miracle_snowflakes.rs new file mode 100644 index 0000000..12065f9 --- /dev/null +++ b/shaders/src/shaders/miracle_snowflakes.rs @@ -0,0 +1,385 @@ +//! Ported to Rust from +//! +//! Original comment: +//! ```glsl +//! /* +//! // +//! /* Panteleymonov Aleksandr Konstantinovich 2015 +//! // +//! // if i write this string my code will be 0 chars, :) */ +//! */ +//! ``` + +use crate::shader_prelude::*; + +pub const SHADER_DEFINITION: ShaderDefinition = ShaderDefinition { + name: "Playing Marble", +}; + +pub fn shader_fn(render_instruction: &ShaderInput, render_result: &mut ShaderResult) { + let color = &mut render_result.color; + let &ShaderInput { + resolution, + time, + frag_coord, + mouse, + .. + } = render_instruction; + State::new(Inputs { + resolution, + time, + mouse, + }) + .main_image(color, frag_coord); +} + +struct Inputs { + resolution: Vec3, + time: f32, + mouse: Vec4, +} + +const ITERATIONS: u32 = 15; +const DEPTH: f32 = 0.0125; +const LAYERS: f32 = 8.0; +const LAYERSBLOB: i32 = 20; +const STEP: f32 = 1.0; +const FAR: f32 = 10000.0; + +struct State { + inputs: Inputs, + radius: f32, + zoom: f32, + + light: Vec3, + seed: Vec2, + iteratorc: f32, + powr: f32, + res: f32, + + nray: Vec3, + nray1: Vec3, + nray2: Vec3, + mxc: f32, +} + +impl State { + #[must_use] + fn new(inputs: Inputs) -> Self { + Self { + inputs, + radius: 0.25, // radius of Snowflakes. maximum for this demo 0.25. + zoom: 4.0, // use this to change details. optimal 0.1 - 4.0. + light: vec3(0.0, 0.0, 1.0), + seed: vec2(0.0, 0.0), + iteratorc: ITERATIONS as f32, + powr: 0.0, + res: 0.0, + + nray: Vec3::ZERO, + nray1: Vec3::ZERO, + nray2: Vec3::ZERO, + mxc: 1.0, + } + } +} + +const NC0: Vec4 = vec4(0.0, 157.0, 113.0, 270.0); +const NC1: Vec4 = vec4(1.0, 158.0, 114.0, 271.0); + +fn hash4(n: Vec4) -> Vec4 { + (n.sin() * 1399763.5453123).fract_gl() +} +fn noise2(x: Vec2) -> f32 { + let p: Vec2 = x.floor(); + let mut f: Vec2 = x.fract_gl(); + f = f * f * (Vec2::splat(3.0) - 2.0 * f); + let n: f32 = p.x + p.y * 157.0; + let nc0 = NC0; + let nc1 = NC1; + let h: Vec4 = hash4(Vec4::splat(n) + vec4(nc0.x, nc0.y, nc1.x, nc1.y)); + let s1: Vec2 = mix(h.xy(), h.zw(), f.xx()); + mix(s1.x, s1.y, f.y) +} + +fn noise222(x: Vec2, y: Vec2, z: Vec2) -> f32 { + let lx: Vec4 = vec4(x.x * y.x, x.y * y.x, x.x * y.y, x.y * y.y); + let p: Vec4 = lx.floor(); + let mut f: Vec4 = lx.fract_gl(); + f = f * f * (Vec4::splat(3.0) - 2.0 * f); + let n: Vec2 = p.xz() + p.yw() * 157.0; + let h: Vec4 = mix( + hash4(n.xxyy() + NC0.xyxy()), + hash4(n.xxyy() + NC1.xyxy()), + f.xxzz(), + ); + mix(h.xz(), h.yw(), f.yw()).dot(z) +} + +fn noise3(x: Vec3) -> f32 { + let p: Vec3 = x.floor(); + let mut f: Vec3 = x.fract_gl(); + f = f * f * (Vec3::splat(3.0) - 2.0 * f); + let n: f32 = p.x + p.yz().dot(vec2(157.0, 113.0)); + let s1: Vec4 = mix( + hash4(Vec4::splat(n) + NC0), + hash4(Vec4::splat(n) + NC1), + f.xxxx(), + ); + mix(mix(s1.x, s1.y, f.y), mix(s1.z, s1.w, f.y), f.z) +} +fn noise3_2(x: Vec3) -> Vec2 { + vec2(noise3(x), noise3(x + Vec3::splat(100.0))) +} + +impl State { + fn map(&self, rad: Vec2) -> f32 { + let a: f32; + if self.res < 0.0015 { + //a = noise2(rad.xy*20.6)*0.9+noise2(rad.xy*100.6)*0.1; + a = noise222(rad, vec2(20.6, 100.6), vec2(0.9, 0.1)); + } else if self.res < 0.005 { + //let a1: f32 = mix(noise2(rad.xy()*10.6),1.0,l); + //a = texture(iChannel0,rad*0.3).x; + a = noise2(rad * 20.6); + //if a1 Vec3 { + let rq: f32 = r * r; + let mut dist: Vec3 = ray * FAR; + + let norm: Vec3 = vec3(0.0, 0.0, 1.0); + let invn: f32 = 1.0 / norm.dot(ray); + let mut depthi: f32 = DEPTH; + if invn < 0.0 { + depthi = -depthi; + } + let mut ds: f32 = 2.0 * depthi * invn; + let mut r1: Vec3 = ray * (norm.dot(pos) - depthi) * invn - pos; + let op1: Vec3 = r1 + norm * depthi; + let len1: f32 = op1.dot(op1); + let mut r2: Vec3 = r1 + ray * ds; + let op2: Vec3 = r2 - norm * depthi; + let len2: f32 = op2.dot(op2); + let n: Vec3 = ray.cross(norm).normalize(); + let mind: f32 = pos.dot(n); + let n2: Vec3 = ray.cross(n); + let d: f32 = n2.dot(pos) / n2.dot(norm); + let invd: f32 = 0.2 / DEPTH; + + if (len1 < rq || len2 < rq) || (mind.abs() < r && d <= DEPTH && d >= -DEPTH) { + let _r3: Vec3 = r2; + let len: f32 = len1; + if len >= rq { + let n3: Vec3 = norm.cross(n); + let a: f32 = 1.0 / (rq - mind * mind).sqrt() * ray.dot(n3).abs(); + let dt: Vec3 = ray / a; + r1 = -d * norm - mind * n - dt; + if len2 >= rq { + r2 = -d * norm - mind * n + dt; + } + ds = (r2 - r1).dot(ray); + } + ds = (ds.abs() + 0.1) / (ITERATIONS as f32); + ds = mix(DEPTH, ds, 0.2); + if ds > 0.01 { + ds = 0.01; + } + let ir: f32 = 0.35 / r; + r *= self.zoom; + ray = ray * ds * 5.0; + for m in 0..ITERATIONS { + if m as f32 >= self.iteratorc { + break; + } + let mut l: f32 = r1.xy().length(); //r1.xy().dot(r1.xy()).sqrt(); + let mut c3: Vec2 = (r1.xy() / l).abs(); + if c3.x > 0.5 { + c3 = (c3 * 0.5 + vec2(-c3.y, c3.x) * 0.86602540).abs(); + } + let g: f32 = l + c3.x * c3.x; //*1.047197551; + l *= self.zoom; + let mut h: f32 = l - r - 0.1; + l = l.powf(self.powr) + 0.1; + 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 + if (h < self.res * 20.0) || r1.z.abs() > DEPTH + 0.01 { + break; + } + r1 += ray * h; + ray *= 0.99; + } + if r1.z.abs() < DEPTH + 0.01 { + dist = r1 + pos; + } + } + dist + } + + fn filter_flake( + &mut self, + mut color: Vec4, + pos: Vec3, + ray: Vec3, + ray1: Vec3, + ray2: Vec3, + ) -> Vec4 { + let d: Vec3 = self.dist_obj(pos, ray, self.radius, self.seed); + let n1: Vec3 = self.dist_obj(pos, ray1, self.radius, self.seed); + let n2: Vec3 = self.dist_obj(pos, ray2, self.radius, self.seed); + + let lq: Vec3 = vec3(d.dot(d), n1.dot(n1), n2.dot(n2)); + if lq.x < FAR || lq.y < FAR || lq.z < FAR { + let n: Vec3 = (n1 - d).cross(n2 - d).normalize(); + if lq.x < FAR && lq.y < FAR && lq.z < FAR { + self.nray = n; //(self.nray+n).normalize(); + //self.nray1 = (ray1+n).normalize(); + //self.nray2 = (ray2+n).normalize(); + } + let da: f32 = n.dot(self.light).abs().powf(3.0); + let mut cf: Vec3 = mix(vec3(0.0, 0.4, 1.0), color.xyz() * 10.0, n.dot(ray).abs()); + cf = mix(cf, Vec3::splat(2.0), da); + color = (mix( + color.xyz(), + cf, + self.mxc * self.mxc * (0.5 + n.dot(ray).abs() * 0.5), + )) + .extend(color.w); + } + + color + } + + fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { + let time: f32 = self.inputs.time * 0.2; //*0.1; + self.res = 1.0 / self.inputs.resolution.y; + let p: Vec2 = (-self.inputs.resolution.xy() + 2.0 * frag_coord) * self.res; + + let mut rotate: Vec3; + let mut mr: Mat3; + let mut ray: Vec3; + let mut ray1: Vec3; + let mut ray2: Vec3; + let mut pos: Vec3 = vec3(0.0, 0.0, 1.0); + + *frag_color = vec4(0.0, 0.0, 0.0, 0.0); + self.nray = Vec3::ZERO; + self.nray1 = Vec3::ZERO; + self.nray2 = Vec3::ZERO; + + let mut refcolor: Vec4 = Vec4::ZERO; + self.iteratorc = ITERATIONS as f32 - LAYERS; + + let mut addrot: Vec2 = Vec2::ZERO; + if self.inputs.mouse.z > 0.0 { + addrot = (self.inputs.mouse.xy() - self.inputs.resolution.xy() * 0.5) * self.res; + } + + let mut mxcl: f32 = 1.0; + let mut addpos: Vec3 = Vec3::ZERO; + pos.z = 1.0; + self.mxc = 1.0; + self.radius = 0.25; + let mzd: f32 = (self.zoom - 0.1) / LAYERS; + for i in 0..LAYERSBLOB { + let p2: Vec2 = p - Vec2::splat(0.25) + Vec2::splat(0.1 * i as f32); + ray = p2.extend(2.0) - self.nray * 2.0; + //ray = self.nray;//*0.6; + ray1 = (ray + vec3(0.0, self.res * 2.0, 0.0)).normalize(); + ray2 = (ray + vec3(self.res * 2.0, 0.0, 0.0)).normalize(); + ray = ray.normalize(); + let mut sb: Vec2 = ray.xy() * pos.length() / pos.normalize().dot(ray) + vec2(0.0, time); + self.seed = (sb + vec2(0.0, pos.z)).floor() + Vec2::splat(pos.z); + let mut seedn: Vec3 = self.seed.extend(pos.z); + sb = sb.floor(); + if noise3(seedn) > 0.2 && i < LAYERS as i32 { + self.powr = noise3(seedn * 10.0) * 1.9 + 0.1; + rotate = + (((Vec2::splat(0.5) - noise3_2(seedn)) * time * 5.0).sin() * 0.3 + addrot).extend(0.0); + rotate.z = (0.5 - noise3(seedn + vec3(10.0, 3.0, 1.0))) * time * 5.0; + seedn.z += time * 0.5; + addpos = (sb + vec2(0.25, 0.25 - time) + noise3_2(seedn) * 0.5).extend(addpos.z); + let sins: Vec3 = rotate.sin(); + let coss: Vec3 = rotate.cos(); + mr = Mat3::from_cols( + vec3(coss.x, 0.0, sins.x), + vec3(0.0, 1.0, 0.0), + vec3(-sins.x, 0.0, coss.x), + ); + mr = Mat3::from_cols( + vec3(1.0, 0.0, 0.0), + vec3(0.0, coss.y, sins.y), + vec3(0.0, -sins.y, coss.y), + ) * mr; + mr = Mat3::from_cols( + vec3(coss.z, sins.z, 0.0), + vec3(-sins.z, coss.z, 0.0), + vec3(0.0, 0.0, 1.0), + ) * mr; + + self.light = mr.transpose() * vec3(1.0, 0.0, 1.0).normalize(); + // let cc: Vec4 = self.filter_flake( + // *frag_color, + // mr.transpose() * (pos + addpos), + // (mr.transpose() * ray + self.nray * 0.1).normalize(), + // (mr.transpose() * ray1 + self.nray * 0.1).normalize(), + // (mr.transpose() * ray2 + self.nray * 0.1).normalize(), + // ); + let mut cc: Vec4 = self.filter_flake( + *frag_color, + mr.transpose() * (pos + addpos), + mr.transpose() * ray, + mr.transpose() * ray1, + mr.transpose() * ray2, + ); + if false { + if i > 0 + && self.nray.dot(self.nray) != 0.0 + && self.nray1.dot(self.nray1) != 0.0 + && self.nray2.dot(self.nray2) != 0.0 + { + refcolor = self.filter_flake( + refcolor, + mr.transpose() * (pos + addpos), + self.nray, + self.nray1, + self.nray2, + ); + } + cc += refcolor * 0.5; + } + *frag_color = mix(cc, *frag_color, frag_color.w.min(1.0)); + } + seedn = sb.extend(pos.z) + vec3(0.5, 1000.0, 300.0); + if noise3(seedn * 10.0) > 0.4 { + let raf: f32 = 0.3 + noise3(seedn * 100.0); + addpos = (sb + vec2(0.2, 0.2 - time) + noise3_2(seedn * 100.0) * 0.6).extend(addpos.z); + let mut l: f32 = (ray * ray.dot(pos + addpos) - pos - addpos).length(); + l = (1.0 - l * 10.0 * raf).max(0.0); + *frag_color += + vec4(1.0, 1.2, 3.0, 1.0) * l.powf(5.0) * ((0.6 + raf).powf(2.0) - 0.6) * mxcl; + } + self.mxc -= 1.1 / LAYERS; + pos.z += STEP; + self.iteratorc += 2.0; + mxcl -= 1.1 / LAYERSBLOB as f32; + self.zoom -= mzd; + } + + let cr: Vec3 = mix(Vec3::ZERO, vec3(0.0, 0.0, 0.4), (-0.55 + p.y) * 2.0); + *frag_color = (frag_color.xyz() + + mix( + (cr - frag_color.xyz()) * 0.1, + vec3(0.2, 0.5, 1.0), + ((-p.y + 1.0) * 0.5).clamp(0.0, 1.0), + )) + .extend(frag_color.z); + + *frag_color = Vec4::ONE.min(*frag_color); + } +} diff --git a/shaders/src/shaders/mod.rs b/shaders/src/shaders/mod.rs new file mode 100644 index 0000000..b5856c9 --- /dev/null +++ b/shaders/src/shaders/mod.rs @@ -0,0 +1,89 @@ +use crate::shader_prelude::*; + +mod a_lot_of_spheres; +mod a_question_of_time; +mod apollonian; +mod atmosphere_system_test; +mod bubble_buckey_balls; +mod clouds; +mod filtering_procedurals; +mod flappy_bird; +mod galaxy_of_universes; +mod geodesic_tiling; +mod heart; +mod luminescence; +mod mandelbrot_smooth; +mod miracle_snowflakes; +mod morphing; +mod moving_square; +mod on_off_spikes; +mod phantom_star; +mod playing_marble; +mod protean_clouds; +mod raymarching_primitives; +mod seascape; +mod skyline; +mod soft_shadow_variation; +mod tileable_water_caustic; +mod tokyo; +mod two_tweets; +mod voxel_pac_man; + +#[allow(edition_2024_expr_fragment_specifier)] +macro_rules! match_index { + ($e:expr; $($result:expr),* $(,)?) => ({ + let mut i = 0..; + match $e { e => { + $(if e == i.next().unwrap() { $result } else)* + { unreachable!() } + }} + }) +} + +macro_rules! render_shader_macro { + ($($shader_name:ident),* $(,)?) => { + #[inline(always)] + pub fn render_shader(shader_index: u32, shader_input: &ShaderInput, shader_output: &mut ShaderResult) { + match_index!(shader_index; $( + $shader_name::shader_fn(shader_input, shader_output), + )*) + } + + pub const SHADER_DEFINITIONS: &[ShaderDefinition] = &[ + $( + $shader_name::SHADER_DEFINITION, + )* + ]; + }; +} + +render_shader_macro!( + miracle_snowflakes, + morphing, + voxel_pac_man, + luminescence, + seascape, + two_tweets, + heart, + clouds, + mandelbrot_smooth, + protean_clouds, + tileable_water_caustic, + apollonian, + phantom_star, + playing_marble, + a_lot_of_spheres, + a_question_of_time, + galaxy_of_universes, + atmosphere_system_test, + soft_shadow_variation, + bubble_buckey_balls, + raymarching_primitives, + moving_square, + skyline, + filtering_procedurals, + geodesic_tiling, + flappy_bird, + tokyo, + on_off_spikes, +); diff --git a/shaders/src/shaders/morphing.rs b/shaders/src/shaders/morphing.rs new file mode 100644 index 0000000..fbdf82c --- /dev/null +++ b/shaders/src/shaders/morphing.rs @@ -0,0 +1,306 @@ +//! Ported to Rust from +//! +//! Original comment: +//! ```glsl +//! // Created by Sebastien Durand - 2014 +//! // License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. +//! ``` + +use crate::shader_prelude::*; + +pub const SHADER_DEFINITION: ShaderDefinition = ShaderDefinition { + name: "Morphing Teapot", +}; + +pub fn shader_fn(render_instruction: &ShaderInput, render_result: &mut ShaderResult) { + let color = &mut render_result.color; + let &ShaderInput { + resolution, + time, + frag_coord, + mouse, + .. + } = render_instruction; + State::new(Inputs { + resolution, + time, + mouse, + }) + .main_image(color, frag_coord); +} + +struct Inputs { + resolution: Vec3, + time: f32, + mouse: Vec4, +} + +struct State { + inputs: Inputs, + + a: [Vec2; 15], + t1: [Vec2; 5], + t2: [Vec2; 5], + + l: Vec3, + + t_morph: f32, + mat2_rot: Mat2, +} + +impl State { + #[must_use] + fn new(inputs: Inputs) -> Self { + Self { + inputs, + + a: [ + Vec2::ZERO, + Vec2::ZERO, + Vec2::ZERO, + Vec2::ZERO, + Vec2::ZERO, + Vec2::ZERO, + Vec2::ZERO, + Vec2::ZERO, + Vec2::ZERO, + Vec2::ZERO, + Vec2::ZERO, + Vec2::ZERO, + Vec2::ZERO, + Vec2::ZERO, + Vec2::ZERO, + ], + t1: [Vec2::ZERO, Vec2::ZERO, Vec2::ZERO, Vec2::ZERO, Vec2::ZERO], + t2: [Vec2::ZERO, Vec2::ZERO, Vec2::ZERO, Vec2::ZERO, Vec2::ZERO], + + l: vec3(1.0, 0.72, 1.0).normalize(), + + t_morph: 0.0, + mat2_rot: Mat2::ZERO, + } + } +} + +fn u(a: Vec2, b: Vec2) -> f32 { + a.x * b.y - b.x * a.y +} + +const Y: Vec3 = vec3(0.0, 1.0, 0.0); +// const E: Vec3 = Y * 0.01; +const _E: Vec3 = vec3(0.0, 0.01, 0.0); + +// Distance to Bezier +// inspired by [iq:https://www.shadertoy.com/view/ldj3Wh] +// calculate distance to 2D bezier curve on xy but without forgetting the z component of p +// total distance is corrected using pytagore just before return +fn bezier(mut m: Vec2, mut n: Vec2, mut o: Vec2, p: Vec3) -> Vec2 { + let q: Vec2 = p.xy(); + m -= q; + n -= q; + o -= q; + let x: f32 = u(m, o); + let y: f32 = 2.0 * u(n, m); + let z: f32 = 2.0 * u(o, n); + let i: Vec2 = o - m; + let j: Vec2 = o - n; + let k: Vec2 = n - m; + let s: Vec2 = 2. * (x * i + y * j + z * k); + let mut r: Vec2 = m + (y * z - x * x) * vec2(s.y, -s.x) / s.dot(s); + let t: f32 = ((u(r, i) + 2.0 * u(k, r)) / (x + x + y + z)).clamp(0.0, 1.0); // parametric position on curve + r = m + t * (k + k + t * (j - k)); // distance on 2D xy space + vec2((r.dot(r) + p.z * p.z).sqrt(), t) // distance on 3D space +} + +fn smin(a: f32, b: f32, k: f32) -> f32 { + let h: f32 = (0.5 + 0.5 * (b - a) / k).clamp(0.0, 1.0); + mix(b, a, h) - k * h * (1. - h) +} + +impl State { + // Distance to scene + fn m(&self, mut p: Vec3) -> f32 { + // Distance to Teapot --------------------------------------------------- + // precalcul first part of teapot spout + let h: Vec2 = bezier(self.t1[2], self.t1[3], self.t1[4], p); + let mut a: f32 = 99.0; + // distance to teapot handle (-.06 => make the thickness) + let b: f32 = (bezier(self.t2[0], self.t2[1], self.t2[2], p) + .x + .min(bezier(self.t2[2], self.t2[3], self.t2[4], p).x) + - 0.06) + // max p.y-.9 => cut the end of the spout + .min( + (p.y - 0.9).max( + // distance to second part of teapot spout (abs(dist,r1)-dr) => enable to make the spout hole + ((bezier(self.t1[0], self.t1[1], self.t1[2], p).x - 0.07).abs() - 0.01) + // distance to first part of teapot spout (tickness incrase with pos on curve) + .min(h.x * (1. - 0.75 * h.y) - 0.08), + ), + ); + + // distance to teapot body => use rotation symetry to simplify calculation to a distance to 2D bezier curve + let qq: Vec3 = vec3((p.dot(p) - p.y * p.y).sqrt(), p.y, 0.0); + // the substraction of .015 enable to generate a small thickness arround bezier to help convergance + // the .8 factor help convergance + let mut i = 0; + while i < 13 { + a = a.min((bezier(self.a[i], self.a[i + 1], self.a[i + 2], qq).x - 0.015) * 0.7); + i += 2; + } + // smooth minimum to improve quality at junction of handle and spout to the body + let d_teapot: f32 = smin(a, b, 0.02); + + // Distance to other shapes --------------------------------------------- + let mut d_shape: f32; + let id_morph: i32 = ((0.5 + self.inputs.time / TAU).floor() % 3.0) as i32; + + if id_morph == 1 { + p = (self.mat2_rot.transpose() * p.xz()).extend(p.y).xzy(); + let d: Vec3 = (p - vec3(0.0, 0.5, 0.0)).abs() - vec3(0.8, 0.7, 0.8); + d_shape = d.x.max(d.y.max(d.z)).min(0.0) + d.max(Vec3::ZERO).length(); + } else if id_morph == 2 { + p -= vec3(0.0, 0.55, 0.0); + let d1: Vec3 = p.abs() - vec3(0.67, 0.67, 0.67 * 1.618); + let d3: Vec3 = p.abs() - vec3(0.67 * 1.618, 0.67, 0.67); + d_shape = d1.x.max(d1.y.max(d1.z)).min(0.0) + d1.max(Vec3::ZERO).length(); + d_shape = d_shape.min(d3.x.max(d3.y.max(d3.z)).min(0.0) + d3.max(Vec3::ZERO).length()); + } else { + d_shape = (p - vec3(0.0, 0.45, 0.0)).length() - 1.1; + } + + // !!! The morphing is here !!! + mix(d_teapot, d_shape, self.t_morph.abs()) + } +} + +// HSV to RGB conversion +// [iq: https://www.shadertoy.com/view/MsS3Wc] +fn hsv2rgb_smooth(x: f32, y: f32, z: f32) -> Vec3 { + let mut rgb: Vec3 = (((x * Vec3::splat(6.0) + vec3(0.0, 4.0, 2.0)).rem_euclid(Vec3::splat(6.0)) + - Vec3::splat(3.0)) + .abs() + - Vec3::ONE) + .clamp(Vec3::ZERO, Vec3::ONE); + rgb = rgb * rgb * (Vec3::splat(3.0) - 2.0 * rgb); // cubic smoothing + z * mix(Vec3::ONE, rgb, y) +} + +impl State { + fn normal(&self, p: Vec3, ray: Vec3, t: f32) -> Vec3 { + let pitch: f32 = 0.4 * t / self.inputs.resolution.x; + let d: Vec2 = vec2(-1.0, 1.0) * pitch; + // tetrahedral offsets + let p0: Vec3 = p + d.xxx(); + let p1: Vec3 = p + d.xyy(); + let p2: Vec3 = p + d.yxy(); + let p3: Vec3 = p + d.yyx(); + let f0: f32 = self.m(p0); + let f1: f32 = self.m(p1); + let f2: f32 = self.m(p2); + let f3: f32 = self.m(p3); + let grad: Vec3 = p0 * f0 + p1 * f1 + p2 * f2 + p3 * f3 - p * (f0 + f1 + f2 + f3); + // prevent normals pointing away from camera (caused by precision errors) + (grad - (grad.dot(ray).max(0.0)) * ray).normalize() + } + + fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { + let aa: f32 = PI / 4.0; + self.mat2_rot = Mat2::from_cols_array(&[aa.cos(), aa.sin(), -aa.sin(), aa.cos()]); + + // Morphing step + self.t_morph = (self.inputs.time * 0.5).cos(); + self.t_morph *= self.t_morph * self.t_morph * self.t_morph * self.t_morph; + + // Teapot body profil (8 quadratic curves) + self.a[0] = vec2(0.0, 0.0); + self.a[1] = vec2(0.64, 0.0); + self.a[2] = vec2(0.64, 0.03); + self.a[3] = vec2(0.8, 0.12); + self.a[4] = vec2(0.8, 0.3); + self.a[5] = vec2(0.8, 0.48); + self.a[6] = vec2(0.64, 0.9); + self.a[7] = vec2(0.6, 0.93); + self.a[8] = vec2(0.56, 0.9); + self.a[9] = vec2(0.56, 0.96); + self.a[10] = vec2(0.12, 1.02); + self.a[11] = vec2(0.0, 1.05); + self.a[12] = vec2(0.16, 1.14); + self.a[13] = vec2(0.2, 1.2); + self.a[14] = vec2(0.0, 1.2); + // Teapot spout (2 quadratic curves) + self.t1[0] = vec2(1.16, 0.96); + self.t1[1] = vec2(1.04, 0.9); + self.t1[2] = vec2(1.0, 0.72); + self.t1[3] = vec2(0.92, 0.48); + self.t1[4] = vec2(0.72, 0.42); + // Teapot handle (2 quadratic curves) + self.t2[0] = vec2(-0.6, 0.78); + self.t2[1] = vec2(-1.16, 0.84); + self.t2[2] = vec2(-1.16, 0.63); + self.t2[3] = vec2(-1.2, 0.42); + self.t2[4] = vec2(-0.72, 0.24); + + // Configure camera + let r: Vec2 = self.inputs.resolution.xy(); + let m: Vec2 = self.inputs.mouse.xy() / r; + let q: Vec2 = frag_coord / r; + let mut p: Vec2 = q + q - Vec2::ONE; + p.x *= r.x / r.y; + let mut j: f32 = 0.0; + let mut s: f32 = 1.0; + let mut h: f32 = 0.1; + let mut t: f32 = 5.0 + 0.2 * self.inputs.time + 4.0 * m.x; + let o: Vec3 = 2.9 * vec3(t.cos(), 0.7 - m.y, t.sin()); + let w: Vec3 = (Y * 0.4 - o).normalize(); + let u: Vec3 = w.cross(Y).normalize(); + let v: Vec3 = u.cross(w); + let d: Vec3 = (p.x * u + p.y * v + w + w).normalize(); + let n: Vec3; + let x: Vec3; + + // Ray marching + t = 0.0; + for _ in 0..48 { + if h < 0.0001 || t > 4.7 { + break; + } + h = self.m(o + d * t); + t += h; + } + + // Background colour change as teapot complementaries colours (using HSV) + let mut c: Vec3 = mix( + hsv2rgb_smooth(0.5 + self.inputs.time * 0.02, 0.35, 0.4), + hsv2rgb_smooth(-0.5 + self.inputs.time * 0.02, 0.35, 0.7), + q.y, + ); + + // Calculate color on point + if h < 0.001 { + x = o + t * d; + 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))); + + // Calculate Shadows + for _ in 0..20 { + j += 0.02; + s = s.min(self.m(x + self.l * j) / j); + } + // Teapot color rotation in HSV color space + let c1: Vec3 = hsv2rgb_smooth(0.9 + self.inputs.time * 0.02, 1.0, 1.0); + // Shading + c = mix( + c, + mix( + (((3.0 * s).clamp(0.0, 1.0) + 0.3) * c1).sqrt(), + Vec3::splat(self.l.reflect(n).dot(d).max(0.0).powf(99.0)), + 0.4, + ), + 2.0 * n.dot(-d), + ); + } + + c *= (16.0 * q.x * q.y * (1.0 - q.x) * (1.0 - q.y)).powf(0.16); // Vigneting + *frag_color = c.extend(1.0); + } +} diff --git a/shaders/src/shaders/moving_square.rs b/shaders/src/shaders/moving_square.rs new file mode 100644 index 0000000..5a50bd6 --- /dev/null +++ b/shaders/src/shaders/moving_square.rs @@ -0,0 +1,49 @@ +//! Ported to Rust from + +use crate::shader_prelude::*; + +pub const SHADER_DEFINITION: ShaderDefinition = ShaderDefinition { + name: "Moving Square", +}; + +pub fn shader_fn(render_instruction: &ShaderInput, render_result: &mut ShaderResult) { + let color = &mut render_result.color; + let &ShaderInput { + resolution, + time, + frag_coord, + .. + } = render_instruction; + Inputs { resolution, time }.main_image(color, frag_coord); +} + +struct Inputs { + resolution: Vec3, + time: f32, +} + +fn rect(uv: Vec2, pos: Vec2, r: f32) -> Vec4 { + let re_c: Vec2 = (uv - pos).abs(); + let dif1: Vec2 = re_c - Vec2::splat(r / 2.); + let dif2: Vec2 = (re_c - Vec2::splat(r / 2.)).clamp(Vec2::ZERO, Vec2::ONE); + let d1: f32 = (dif1.x + dif1.y).clamp(0.0, 1.0); + let _d2: f32 = (dif2.x + dif2.y).clamp(0.0, 1.0); + + Vec4::splat(d1) +} + +impl Inputs { + fn main_image(&self, frag_color: &mut Vec4, frag_coord: Vec2) { + let mut uv: Vec2 = frag_coord; + let t: f32 = self.time.sin(); + + let c: Vec2 = self.resolution.xy() * 0.5; // + sin(iTime) * 50.; + + uv = Mat2::from_cols_array(&[t.cos(), -t.sin(), t.sin(), t.cos()]) * (uv - c) + c; + + *frag_color = rect(uv, c, (self.time * 10.).sin() * 50. + 50.); + *frag_color *= vec3(0.5, 0.2, 1.).extend(1.); + *frag_color += rect(uv, c, self.time.sin() * 50. + 50.); + *frag_color *= vec3(0.5, 0.8, 1.).extend(1.); + } +} diff --git a/shaders/src/shaders/on_off_spikes.rs b/shaders/src/shaders/on_off_spikes.rs new file mode 100644 index 0000000..95a38cb --- /dev/null +++ b/shaders/src/shaders/on_off_spikes.rs @@ -0,0 +1,441 @@ +//! Ported to Rust from +//! +//! Original comment: +//! ```glsl +//! // On/Off Spikes, fragment shader by movAX13h, oct 2014 +//! ``` + +use crate::shader_prelude::*; + +pub const SHADER_DEFINITION: ShaderDefinition = ShaderDefinition { + name: "On/Off Spikes", +}; + +pub fn shader_fn(render_instruction: &ShaderInput, render_result: &mut ShaderResult) { + let color = &mut render_result.color; + let &ShaderInput { + resolution, + time, + frag_coord, + mouse, + .. + } = render_instruction; + State::new(Inputs { + resolution, + time, + mouse, + }) + .main_image(color, frag_coord); +} + +struct Inputs { + resolution: Vec3, + time: f32, + mouse: Vec4, +} + +struct State { + inputs: Inputs, + + // globals + glow: f32, + bite: f32, + sphere_col: Vec3, + sun: Vec3, + focus: f32, + far: f32, +} + +impl State { + #[must_use] + pub fn new(inputs: Inputs) -> Self { + Self { + inputs, + + glow: 0.0, + bite: 0.0, + sphere_col: Vec3::ZERO, + sun: SUN_POS.normalize(), + focus: 5.0, + far: 23.0, + } + } +} + +const HARD_SHADOW: bool = true; +const GLOW: bool = true; +const EDGES: bool = true; +const NUM_TENTACLES: i32 = 6; +const BUMPS: bool = true; +const NUM_BUMPS: i32 = 8; +const BACKGROUND: bool = true; +const SUN_POS: Vec3 = vec3(15.0, 15.0, -15.0); +const SUN_SPHERE: bool = false; + +const SPHERE_COL: Vec3 = vec3(0.6, 0.3, 0.1); +const MOUTH_COL: Vec3 = vec3(0.9, 0.6, 0.1); +const TENTACLE_COL: Vec3 = vec3(0.06, 0.06, 0.06); + +const GAMMA: f32 = 2.2; + +// Using the nebula function of the "Star map shader" by morgan3d +// as environment map and light sphere texture (https://www.shadertoy.com/view/4sBXzG) +const NUM_OCTAVES: i32 = 4; +fn hash(n: f32) -> f32 { + (n.sin() * 1e4).fract_gl() +} +fn hash_vec2(p: Vec2) -> f32 { + (1e4 * (17.0 * p.x + p.y * 0.1).sin() * (0.1 + (p.y * 13.0 + p.x).sin().abs())).fract_gl() +} +fn noise(x: f32) -> f32 { + let i: f32 = x.floor(); + let f: f32 = x.fract_gl(); + let u: f32 = f * f * (3.0 - 2.0 * f); + mix(hash(i), hash(i + 1.0), u) +} +fn noise_vec2(x: Vec2) -> f32 { + let i: Vec2 = x.floor(); + let f: Vec2 = x.fract_gl(); + let a: f32 = hash_vec2(i); + let b: f32 = hash_vec2(i + vec2(1.0, 0.0)); + let c: f32 = hash_vec2(i + vec2(0.0, 1.0)); + let d: f32 = hash_vec2(i + vec2(1.0, 1.0)); + let u: Vec2 = f * f * (Vec2::splat(3.0) - 2.0 * f); + mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y +} +fn noise2(mut x: Vec2) -> f32 { + let mut v: f32 = 0.0; + let mut a: f32 = 0.5; + let shift: Vec2 = Vec2::splat(100.0); + let rot: Mat2 = + Mat2::from_cols_array(&[0.5_f32.cos(), 0.5_f32.sin(), -0.5_f32.sin(), 0.50_f32.cos()]); + + for _ in 0..NUM_OCTAVES { + v += a * noise_vec2(x); + x = rot * x * 2.0 + shift; + a *= 0.5; + } + v +} +fn square(x: f32) -> f32 { + x * x +} +fn rotation(yaw: f32, pitch: f32) -> Mat3 { + Mat3::from_cols_array(&[ + yaw.cos(), + 0.0, + -yaw.sin(), + 0.0, + 1.0, + 0.0, + yaw.sin(), + 0.0, + yaw.cos(), + ]) * Mat3::from_cols_array(&[ + 1.0, + 0.0, + 0.0, + 0.0, + pitch.cos(), + pitch.sin(), + 0.0, + -pitch.sin(), + pitch.cos(), + ]) +} +fn nebula(dir: Vec3) -> Vec3 { + let purple: f32 = dir.x.abs(); + let yellow: f32 = noise(dir.y); + let streaky_hue: Vec3 = vec3(purple + yellow, yellow * 0.7, purple); + let puffy_hue: Vec3 = vec3(0.8, 0.1, 1.0); + let streaky: f32 = 1.0_f32.min( + 8.0 + * (noise2( + dir.yz() * square(dir.x) * 13.0 + dir.xy() * square(dir.z) * 7.0 + vec2(150.0, 2.0), + )) + .powf(10.0), + ); + let puffy: f32 = square(noise2(dir.xz() * 4.0 + vec2(30.0, 10.0)) * dir.y); + + (puffy_hue * puffy * (1.0 - streaky) + streaky * streaky_hue) + .clamp(Vec3::ZERO, Vec3::ONE) + .powf(1.0 / 2.2) +} +// --- + +fn sd_box(p: Vec3, b: Vec3) -> f32 { + let d: Vec3 = p.abs() - b; + d.x.max(d.y.max(d.z)).min(0.0) + d.max(Vec3::ZERO).length() +} + +fn sd_sphere(p: Vec3, r: f32) -> f32 { + p.length() - r +} + +fn sd_capped_cylinder(p: Vec3, h: Vec2) -> f32 { + let d: Vec2 = vec2(p.xy().length(), p.z).abs() - h; + d.x.max(d.y).min(0.0) + d.max(Vec2::ZERO).length() +} + +fn rotate(p: Vec2, a: f32) -> Vec2 { + let mut r: Vec2 = Vec2::ZERO; + r.x = p.x * a.cos() - p.y * a.sin(); + r.y = p.x * a.sin() + p.y * a.cos(); + r +} + +// polynomial smooth min (k = 0.1); by iq +fn smin(a: f32, b: f32, k: f32) -> f32 { + let h: f32 = (0.5 + 0.5 * (b - a) / k).clamp(0.0, 1.0); + mix(b, a, h) - k * h * (1.0 - h) +} + +#[derive(Clone, Copy, Default)] +struct Hit { + d: f32, + color: Vec3, + edge: f32, +} + +impl State { + fn scene(&self, p: Vec3) -> Hit { + let mut d: f32; + let mut d1: f32; + let mut d2: f32; + let d3: f32; + let f: f32; + let mut e: f32 = 0.15; + + let mut q: Vec3 = p; + q = rotate(q.xy(), 1.5).extend(q.z); + + // center sphere + d1 = sd_sphere(q, 0.3); + // d = d1; + let mut col: Vec3 = self.sphere_col; + + // tentacles + let r: f32 = q.length(); + let mut a: f32 = q.z.atan2(q.x); + a += 0.4 * (r - self.inputs.time).sin(); + + q = vec3(a * NUM_TENTACLES as f32 / TAU, q.y, q.xz().length()); // circular domain + q = vec3(q.x.rem_euclid(1.0) - 0.5 * 1.0, q.y, q.z); // repetition + + d3 = sd_capped_cylinder( + q - vec3(0.0, 0.0, 0.9 + self.bite), + vec2(0.1 - (r - self.bite) / 18.0, 0.8), + ); + d2 = d3.min(sd_box( + q - vec3(0.0, 0.0, 0.1 + self.bite), + vec3(0.2, 0.2, 0.2), + )); // close box + d2 = smin( + d2, + sd_box(q - vec3(0.0, 0.0, 0.4 + self.bite), vec3(0.2, 0.05, 0.4)), + 0.1, + ); // wide box + + f = smoothstep(0.11, 0.28, d2 - d1); + col = mix(MOUTH_COL, col, f); + e = mix(e, 0.0, f); + d = smin(d1, d2, 0.24); + + col = mix(TENTACLE_COL, col, smoothstep(0.0, 0.48, d3 - d)); + + if SUN_SPHERE { + d = d.min(sd_sphere(p - self.sun, 0.1)); + } + + if BUMPS { + for i in 0..NUM_BUMPS { + d2 = i as f32; + d1 = sd_sphere( + p - 0.18 + * smoothstep(0.1, 1.0, self.glow) + * vec3( + (4.0 * self.inputs.time + d2 * 0.6).sin(), + (5.3 * self.inputs.time + d2 * 1.4).sin(), + (5.8 * self.inputs.time + d2 * 0.6).cos(), + ), + 0.03, + ); + + d = smin(d1, d, 0.2); + //d = min(d1, d); + } + } + + if BACKGROUND { + q = p; + q = q.yz().rem_euclid(Vec2::ONE).extend(q.x).zxy(); + q -= vec3(-0.6, 0.5, 0.5); + d1 = sd_box(q, vec3(0.1, 0.48, 0.48)); + if d1 < d { + d = d1; + col = Vec3::splat(0.1); + } + } + + Hit { + d, + color: col, + edge: e, + } + } + + fn normal(&self, p: Vec3) -> Vec3 { + let c: f32 = self.scene(p).d; + let h: Vec2 = vec2(0.01, 0.0); + vec3( + self.scene(p + h.xyy()).d - c, + self.scene(p + h.yxy()).d - c, + self.scene(p + h.yyx()).d - c, + ) + .normalize() + } + + // by srtuss + fn edges(&self, p: Vec3) -> f32 { + let mut acc: f32 = 0.0; + let h: f32 = 0.01; + acc += self.scene(p + vec3(-h, -h, -h)).d; + acc += self.scene(p + vec3(-h, -h, h)).d; + acc += self.scene(p + vec3(-h, h, -h)).d; + acc += self.scene(p + vec3(-h, h, h)).d; + acc += self.scene(p + vec3(h, -h, -h)).d; + acc += self.scene(p + vec3(h, -h, h)).d; + acc += self.scene(p + vec3(h, h, -h)).d; + acc += self.scene(p + vec3(h, h, h)).d; + acc / h + } + + fn colorize(&self, hit: Hit, n: Vec3, dir: Vec3, light_pos: Vec3) -> Vec3 { + let diffuse: f32 = 0.3 * n.dot(light_pos).max(0.0); + + let ref_: Vec3 = dir.reflect(n).normalize(); + let specular: f32 = 0.4 * ref_.dot(light_pos).max(0.0).powf(6.5); + + hit.color + diffuse * Vec3::splat(0.9) + specular * Vec3::splat(1.0) + } + + fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { + //self.time = self.inputs.time; + self.glow = (2.0 * (self.inputs.time * 0.7 - 5.0).sin()).clamp(0.0, 1.0); + self.bite = smoothstep(0.0, 1.0, 1.6 * (self.inputs.time * 0.7).sin()); + self.sphere_col = SPHERE_COL * self.glow; + + let pos: Vec2 = (frag_coord * 2.0 - self.inputs.resolution.xy()) / self.inputs.resolution.y; + + let d: f32 = (1.5 * (0.3 * self.inputs.time).sin()).clamp(0.5, 1.0); + let mut cp: Vec3 = vec3( + 10.0 * d, + -2.3 * d, + -6.2 * d + 4.0 * (2.0 * (self.inputs.time * 0.5).sin().clamp(0.0, 1.0)), + ); // anim curious spectator + + if self.inputs.mouse.z > 0.5 { + let mrel: Vec2 = self.inputs.mouse.xy() / self.inputs.resolution.xy() - Vec2::splat(0.5); + let mdis: f32 = 8.0 + 6.0 * mrel.y; + cp = vec3( + mdis * (-mrel.x * FRAC_PI_2).cos(), + 4.0 * mrel.y, + mdis * (-mrel.x * FRAC_PI_2).sin(), + ); + } + + let ct: Vec3 = vec3(0.0, 0.0, 0.0); + let cd: Vec3 = (ct - cp).normalize(); + let cu: Vec3 = vec3(0.0, 1.0, 0.0); + let cs: Vec3 = cd.cross(cu); + let mut dir: Vec3 = (cs * pos.x + cu * pos.y + cd * self.focus).normalize(); + + let mut h: Hit = Hit::default(); + let mut col: Vec3; + let mut ray: Vec3 = cp; + let mut dist: f32 = 0.0; + + // raymarch scene + for _ in 0..60 { + h = self.scene(ray); + + if h.d < 0.0001 { + break; + } + + dist += h.d; + ray += dir * h.d * 0.9; + + if dist > self.far { + dist = self.far; + break; + } + } + + let m: f32 = 1.0 - dist / self.far; + let n: Vec3 = self.normal(ray); + col = self.colorize(h, n, dir, self.sun) * m; + + if EDGES { + let edge: f32 = self.edges(ray); + col = mix( + col, + Vec3::ZERO, + h.edge * edge * smoothstep(0.3, 0.35, ray.length()), + ); + } + + let neb: Vec3 = nebula(n); + col += self.glow.min(0.1) * neb.zxy(); + + // HARD SHADOW with low number of rm iterations (from obj to sun) + if HARD_SHADOW { + let mut ray1: Vec3 = ray; + dir = (SUN_POS - ray1).normalize(); + ray1 += n * 0.002; + + let sun_dist: f32 = (SUN_POS - ray1).length(); + dist = 0.0; + + for _ in 0..35 { + h = self.scene(ray1 + dir * dist); + dist += h.d; + if h.d.abs() < 0.001 { + break; + } + } + + col -= Vec3::splat(0.24 * smoothstep(0.5, -0.3, dist.min(sun_dist) / sun_dist.max(0.0001))); + } + + // ILLUMINATION & free shadow with low number of rm iterations (from obj to sphere) + if GLOW { + dir = (-ray).normalize(); + ray += n * 0.002; + + let sphere_dist: f32 = (ray.length() - 0.3).max(0.0001); + dist = 0.0; + + for _ in 0..35 { + h = self.scene(ray + dir * dist); + dist += h.d; + if h.d.abs() < 0.001 { + break; + } + } + + let neb1: Vec3 = nebula(rotation(0.0, self.inputs.time * 0.4).transpose() * dir).zxy(); + + col += (0.7 * self.sphere_col + self.glow * neb1) + * (0.6 * (smoothstep(3.0, 0.0, sphere_dist)) * dist.min(sphere_dist) / sphere_dist + + 0.6 * smoothstep(0.1, 0.0, sphere_dist)); + } + + col -= Vec3::splat(0.2 * smoothstep(0.6, 3.7, pos.length())); + col = col.clamp(Vec3::ZERO, Vec3::ONE); + col = col.powf_vec(vec3(2.2, 2.4, 2.5)) * 3.9; + col = col.powf_vec(Vec3::splat(1.0 / GAMMA)); + + *frag_color = col.extend(1.0); + } +} diff --git a/shaders/src/shaders/phantom_star.rs b/shaders/src/shaders/phantom_star.rs new file mode 100644 index 0000000..edbb9c3 --- /dev/null +++ b/shaders/src/shaders/phantom_star.rs @@ -0,0 +1,102 @@ +//! Ported to Rust from + +use crate::shader_prelude::*; + +pub const SHADER_DEFINITION: ShaderDefinition = ShaderDefinition { + name: "Phantom Star", +}; + +pub fn shader_fn(render_instruction: &ShaderInput, render_result: &mut ShaderResult) { + let color = &mut render_result.color; + let &ShaderInput { + resolution, + time, + frag_coord, + .. + } = render_instruction; + Inputs { resolution, time }.main_image(color, frag_coord); +} + +struct Inputs { + resolution: Vec3, + time: f32, +} + +fn rot(a: f32) -> Mat2 { + let c: f32 = a.cos(); + let s: f32 = a.sin(); + Mat2::from_cols_array(&[c, s, -s, c]) +} + +fn pmod(p: Vec2, r: f32) -> Vec2 { + let mut a: f32 = p.x.atan2(p.y) + PI / r; + let n: f32 = TAU / r; + a = (a / n).floor() * n; + rot(-a).transpose() * p +} + +fn box_(p: Vec3, b: Vec3) -> f32 { + let d: Vec3 = p.abs() - b; + d.x.max(d.y.max(d.z)).min(0.0) + d.max(Vec3::ZERO).length() +} + +impl Inputs { + fn ifs_box(&self, mut p: Vec3) -> f32 { + for _ in 0..5 { + p = p.abs() - Vec3::splat(1.0); + p = (rot(self.time * 0.3).transpose() * p.xy()).extend(p.z); + p = (rot(self.time * 0.1).transpose() * p.xz()) + .extend(p.y) + .xzy(); + } + p = (rot(self.time).transpose() * p.xz()).extend(p.y).xzy(); + box_(p, vec3(0.4, 0.8, 0.3)) + } + + fn map(&self, p: Vec3, _c_pos: Vec3) -> f32 { + let mut p1: Vec3 = p; + p1.x = (p1.x - 5.0).rem_euclid(10.0) - 5.0; + p1.y = (p1.y - 5.0).rem_euclid(10.0) - 5.0; + p1.z = p1.z.rem_euclid(16.0) - 8.0; + p1 = pmod(p1.xy(), 5.0).extend(p1.z); + self.ifs_box(p1) + } + + fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { + let p: Vec2 = + (frag_coord * 2.0 - self.resolution.xy()) / self.resolution.x.min(self.resolution.y); + + let c_pos: Vec3 = vec3(0.0, 0.0, -3.0 * self.time); + // let c_pos: Vec3 = vec3(0.3 * (self.time * 0.8).sin(), 0.4 * (self.time * 0.3).cos(), -6.0 * self.time,); + let c_dir: Vec3 = vec3(0.0, 0.0, -1.0).normalize(); + let c_up: Vec3 = vec3(self.time.sin(), 1.0, 0.0); + let c_side: Vec3 = c_dir.cross(c_up); + + let ray: Vec3 = (c_side * p.x + c_up * p.y + c_dir).normalize(); + + // Phantom Mode https://www.shadertoy.com/view/MtScWW by aiekick + let mut acc: f32 = 0.0; + let mut acc2: f32 = 0.0; + let mut t: f32 = 0.0; + + for _ in 0..99 { + let pos: Vec3 = c_pos + ray * t; + let mut dist: f32 = self.map(pos, c_pos); + dist = dist.abs().max(0.02); + let mut a: f32 = (-dist * 3.0).exp(); + if (pos.length() + 24.0 * self.time).rem_euclid(30.0) < 3.0 { + a *= 2.0; + acc2 += a; + } + acc += a; + t += dist * 0.5; + } + + let col: Vec3 = vec3( + acc * 0.01, + acc * 0.011 + acc2 * 0.002, + acc * 0.012 + acc2 * 0.005, + ); + *frag_color = col.extend(1.0 - t * 0.03); + } +} diff --git a/shaders/src/shaders/playing_marble.rs b/shaders/src/shaders/playing_marble.rs new file mode 100644 index 0000000..6dd9791 --- /dev/null +++ b/shaders/src/shaders/playing_marble.rs @@ -0,0 +1,146 @@ +//! Ported to Rust from +//! +//! Original comment: +//! ```glsl +//! // License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. +//! // Created by S. Guillitte 2015 +//! ``` + +use crate::shader_prelude::*; + +pub const SHADER_DEFINITION: ShaderDefinition = ShaderDefinition { + name: "Playing Marble", +}; + +pub fn shader_fn(render_instruction: &ShaderInput, render_result: &mut ShaderResult) { + let color = &mut render_result.color; + let &ShaderInput { + resolution, + time, + frag_coord, + mouse, + .. + } = render_instruction; + Inputs { + resolution, + time, + mouse, + channel0: RgbCube { + alpha: 1.0, + intensity: 1.0, + }, + } + .main_image(color, frag_coord); +} + +struct Inputs { + resolution: Vec3, + time: f32, + mouse: Vec4, + channel0: C0, +} + +const ZOOM: f32 = 1.0; + +fn _cmul(a: Vec2, b: Vec2) -> Vec2 { + vec2(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x) +} +fn csqr(a: Vec2) -> Vec2 { + vec2(a.x * a.x - a.y * a.y, 2. * a.x * a.y) +} + +fn rot(a: f32) -> Mat2 { + Mat2::from_cols_array(&[a.cos(), a.sin(), -a.sin(), a.cos()]) +} + +//from iq +fn i_sphere(ro: Vec3, rd: Vec3, sph: Vec4) -> Vec2 { + let oc: Vec3 = ro - sph.xyz(); + let b: f32 = oc.dot(rd); + let c: f32 = oc.dot(oc) - sph.w * sph.w; + let mut h: f32 = b * b - c; + if h < 0.0 { + return Vec2::splat(-1.0); + } + h = h.sqrt(); + vec2(-b - h, -b + h) +} + +fn map(mut p: Vec3) -> f32 { + let mut res: f32 = 0.0; + let c: Vec3 = p; + for _ in 0..10 { + p = 0.7 * p.abs() / p.dot(p) - Vec3::splat(0.7); + p = csqr(p.yz()).extend(p.x).zxy(); + p = p.zxy(); + res += (-19.0 * p.dot(c).abs()).exp(); + } + res / 2.0 +} + +impl Inputs { + fn raymarch(&self, ro: Vec3, rd: Vec3, tminmax: Vec2) -> Vec3 { + let mut t: f32 = tminmax.x; + let dt: f32 = 0.02; + //let dt: f32 = 0.2 - 0.195 * (self.time * 0.05).cos(); //animated + let mut col: Vec3 = Vec3::ZERO; + let mut c: f32 = 0.0; + for _ in 0..64 { + t += dt * (-2.0 * c).exp(); + if t > tminmax.y { + break; + } + let _pos: Vec3 = ro + t * rd; + + c = map(ro + t * rd); + + col = 0.99 * col + 0.08 * vec3(c * c, c, c * c * c); //green + + // col = 0.99 * col + 0.08 * vec3(c * c * c, c * c, c); //blue + } + col + } + fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { + let time: f32 = self.time; + let q: Vec2 = frag_coord / self.resolution.xy(); + let mut p: Vec2 = Vec2::splat(-1.0) + 2.0 * q; + p.x *= self.resolution.x / self.resolution.y; + let mut m: Vec2 = Vec2::ZERO; + if self.mouse.z > 0.0 { + m = self.mouse.xy() / self.resolution.xy() * PI; + } + m -= Vec2::splat(0.5); + + // camera + + let mut ro: Vec3 = ZOOM * Vec3::splat(4.0); + ro = (rot(m.y).transpose() * ro.yz()).extend(ro.x).zxy(); + ro = (rot(m.x + 0.1 * time).transpose() * ro.xz()) + .extend(ro.y) + .xzy(); + let ta: Vec3 = Vec3::ZERO; + let ww: Vec3 = (ta - ro).normalize(); + let uu: Vec3 = (ww.cross(vec3(0.0, 1.0, 0.0))).normalize(); + let vv: Vec3 = (uu.cross(ww)).normalize(); + let rd: Vec3 = (p.x * uu + p.y * vv + 4.0 * ww).normalize(); + + let tmm: Vec2 = i_sphere(ro, rd, vec4(0.0, 0.0, 0.0, 2.0)); + // raymarch + let mut col: Vec3 = self.raymarch(ro, rd, tmm); + if tmm.x < 0.0 { + col = self.channel0.sample_cube(rd).xyz(); + } else { + let mut nor: Vec3 = (ro + tmm.x * rd) / 2.; + nor = rd.reflect(nor); + let fre: f32 = (0.5 + nor.dot(rd).clamp(0.0, 1.0)).powf(3.0) * 1.3; + col += self.channel0.sample_cube(nor).xyz() * fre; + } + + //shade + + col = 0.5 * (Vec3::ONE + col).ln(); + col = col.clamp(Vec3::ZERO, Vec3::ONE); + + *frag_color = col.extend(1.0); + } +} diff --git a/shaders/src/shaders/protean_clouds.rs b/shaders/src/shaders/protean_clouds.rs new file mode 100644 index 0000000..36aacb9 --- /dev/null +++ b/shaders/src/shaders/protean_clouds.rs @@ -0,0 +1,232 @@ +//! Ported to Rust from +//! +//! Original comment: +//! ```glsl +//! // Protean clouds by nimitz (twitter: @stormoid) +//! // https://www.shadertoy.com/view/3l23Rh +//! // License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License +//! // Contact the author for other licensing options +//! +//! /* +//! Technical details: +//! +//! The main volume noise is generated from a deformed periodic grid, which can produce +//! a large range of noise-like patterns at very cheap evalutation cost. Allowing for multiple +//! fetches of volume gradient computation for improved lighting. +//! +//! To further accelerate marching, since the volume is smooth, more than half the the density +//! information isn't used to rendering or shading but only as an underlying volume distance to +//! determine dynamic step size, by carefully selecting an equation (polynomial for speed) to +//! step as a function of overall density (not necessarialy rendered) the visual results can be +//! the same as a naive implementation with ~40% increase in rendering performance. +//! +//! Since the dynamic marching step size is even less uniform due to steps not being rendered at all +//! the fog is evaluated as the difference of the fog integral at each rendered step. +//! +//! */ +//! ``` + +use crate::shader_prelude::*; + +pub const SHADER_DEFINITION: ShaderDefinition = ShaderDefinition { + name: "Protean Clouds", +}; + +pub fn shader_fn(render_instruction: &ShaderInput, render_result: &mut ShaderResult) { + let color = &mut render_result.color; + let &ShaderInput { + resolution, + time, + frag_coord, + mouse, + .. + } = render_instruction; + State::new(Inputs { + resolution, + time, + mouse, + }) + .main_image(color, frag_coord); +} + +struct Inputs { + resolution: Vec3, + time: f32, + mouse: Vec4, +} + +struct State { + inputs: Inputs, + prm1: f32, + bs_mo: Vec2, +} + +impl State { + #[must_use] + fn new(inputs: Inputs) -> Self { + Self { + inputs, + prm1: 0.0, + bs_mo: Vec2::ZERO, + } + } +} + +fn rot(a: f32) -> Mat2 { + let c: f32 = a.cos(); + let s: f32 = a.sin(); + Mat2::from_cols_array(&[c, s, -s, c]) +} + +// const m3: Mat3 = const_mat3!([ +// 0.33338, 0.56034, -0.71817, -0.87887, 0.32651, -0.15323, 0.15162, 0.69596, 0.61339 +// ]) * 1.93; + +const M3: Mat3 = mat3( + Vec3::new(0.33338 * 1.93, -0.87887 * 1.93, 0.15162 * 1.93), + Vec3::new(0.56034 * 1.93, 0.32651 * 1.93, 0.69596 * 1.93), + Vec3::new(-0.71817 * 1.93, -0.15323 * 1.93, 0.61339 * 1.93), +); + +fn mag2(p: Vec2) -> f32 { + p.dot(p) +} +fn linstep(mn: f32, mx: f32, x: f32) -> f32 { + ((x - mn) / (mx - mn)).clamp(0.0, 1.0) +} + +fn disp(t: f32) -> Vec2 { + vec2((t * 0.22).sin() * 1.0, (t * 0.175).cos() * 1.0) * 2.0 +} + +impl State { + fn map(&self, mut p: Vec3) -> Vec2 { + let mut p2: Vec3 = p; + p2 = (p2.xy() - disp(p.z)).extend(p2.z); + p = (rot((p.z + self.inputs.time).sin() * (0.1 + self.prm1 * 0.05) + self.inputs.time * 0.09) + .transpose() + * p.xy()) + .extend(p.z); + let cl: f32 = mag2(p2.xy()); + let mut d: f32 = 0.0; + p *= 0.61; + let mut z: f32 = 1.0; + let mut trk: f32 = 1.0; + let dsp_amp: f32 = 0.1 + self.prm1 * 0.2; + for _ in 0..5 { + p += (p.zxy() * 0.75 * trk + Vec3::splat(self.inputs.time) * trk * 0.8).sin() * dsp_amp; + d -= (p.cos().dot(p.yzx().sin()) * z).abs(); + z *= 0.57; + trk *= 1.4; + let m3 = M3; + p = m3.transpose() * p; + } + d = (d + self.prm1 * 3.0).abs() + self.prm1 * 0.3 - 2.5 + self.bs_mo.y; + vec2(d + cl * 0.2 + 0.25, cl) + } + + fn render(&self, ro: Vec3, rd: Vec3, time: f32) -> Vec4 { + let mut rez: Vec4 = Vec4::ZERO; + const LDST: f32 = 8.0; + let _lpos: Vec3 = (disp(time + LDST) * 0.5).extend(time + LDST); + let mut t: f32 = 1.5; + let mut fog_t: f32 = 0.0; + + for _ in 0..130 { + if rez.w > 0.99 { + break; + } + + let pos: Vec3 = ro + t * rd; + let mpv: Vec2 = self.map(pos); + let den: f32 = (mpv.x - 0.3).clamp(0.0, 1.0) * 1.12; + let dn: f32 = (mpv.x + 2.0).clamp(0.0, 3.0); + + let mut col: Vec4 = Vec4::ZERO; + if mpv.x > 0.6 { + col = ((vec3(5.0, 0.4, 0.2) + Vec3::splat(mpv.y * 0.1 + (pos.z * 0.4).sin() * 0.5 + 1.8)) + .sin() + * 0.5 + + Vec3::splat(0.5)) + .extend(0.08); + col *= den * den * den; + col = (col.xyz() * linstep(4.0, -2.5, mpv.x) * 2.3).extend(col.w); + let mut dif: f32 = ((den - self.map(pos + Vec3::splat(0.8)).x) / 9.0).clamp(0.001, 1.0); + dif += ((den - self.map(pos + Vec3::splat(0.35)).x) / 2.5).clamp(0.001, 1.0); + col = (col.xyz() * den * (vec3(0.005, 0.045, 0.075) + 1.5 * vec3(0.033, 0.07, 0.03) * dif)) + .extend(col.w); + } + + let fog_c = (t * 0.2 - 2.2).exp(); + col += vec4(0.06, 0.11, 0.11, 0.1) * (fog_c - fog_t).clamp(0.0, 1.0); + fog_t = fog_c; + rez = rez + col * (1.0 - rez.w); + t += (0.5 - dn * dn * 0.05).clamp(0.09, 0.3); + } + + rez.clamp(Vec4::ZERO, Vec4::ONE) + } +} + +fn getsat(c: Vec3) -> f32 { + let mi: f32 = c.x.min(c.y).min(c.z); + let ma: f32 = c.x.max(c.y).max(c.z); + (ma - mi) / (ma + 1e-7) +} + +//from my "Will it blend" shader (https://www.shadertoy.com/view/lsdGzN) +fn i_lerp(a: Vec3, b: Vec3, x: f32) -> Vec3 { + let mut ic: Vec3 = mix(a, b, x) + vec3(1e-6, 0.0, 0.0); + let sd: f32 = (getsat(ic) - mix(getsat(a), getsat(b), x)).abs(); + let dir: Vec3 = vec3( + 2.0 * ic.x - ic.y - ic.z, + 2.0 * ic.y - ic.x - ic.z, + 2.0 * ic.z - ic.y - ic.x, + ) + .normalize(); + let lgt: f32 = Vec3::splat(1.0).dot(ic); + let ff: f32 = dir.dot(ic.normalize()); + ic += 1.5 * dir * sd * ff * lgt; + ic.clamp(Vec3::ZERO, Vec3::ONE) +} + +impl State { + fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { + let q: Vec2 = frag_coord / self.inputs.resolution.xy(); + let p: Vec2 = (frag_coord - 0.5 * self.inputs.resolution.xy()) / self.inputs.resolution.y; + self.bs_mo = + (self.inputs.mouse.xy() - 0.5 * self.inputs.resolution.xy()) / self.inputs.resolution.y; + + let time: f32 = self.inputs.time * 3.; + let mut ro: Vec3 = vec3(0.0, 0.0, time); + + ro += vec3( + self.inputs.time.sin() * 0.5, + (self.inputs.time.sin() * 1.0) * 0.0, + 0.0, + ); + + let dsp_amp: f32 = 0.85; + ro = (ro.xy() + disp(ro.z) * dsp_amp).extend(ro.z); + let tgt_dst: f32 = 3.5; + + let target: Vec3 = (ro - (disp(time + tgt_dst) * dsp_amp).extend(time + tgt_dst)).normalize(); + ro.x -= self.bs_mo.x * 2.; + let mut rightdir: Vec3 = target.cross(vec3(0.0, 1.0, 0.0)).normalize(); + let updir: Vec3 = rightdir.cross(target).normalize(); + rightdir = updir.cross(target).normalize(); + let mut rd: Vec3 = ((p.x * rightdir + p.y * updir) * 1.0 - target).normalize(); + rd = (rot(-disp(time + 3.5).x * 0.2 + self.bs_mo.x).transpose() * rd.xy()).extend(rd.z); + self.prm1 = smoothstep(-0.4, 0.4, (self.inputs.time * 0.3).sin()); + let scn: Vec4 = self.render(ro, rd, time); + + let mut col: Vec3 = scn.xyz(); + col = i_lerp(col.zyx(), col, (1.0 - self.prm1).clamp(0.05, 1.0)); + + col = col.powf_vec(vec3(0.55, 0.65, 0.6)) * vec3(1.0, 0.97, 0.9); + + col *= (16.0 * q.x * q.y * (1.0 - q.x) * (1.0 - q.y)).powf(0.12) * 0.7 + 0.3; //Vign + + *frag_color = col.extend(1.0); + } +} diff --git a/shaders/src/shaders/raymarching_primitives.rs b/shaders/src/shaders/raymarching_primitives.rs new file mode 100644 index 0000000..9e71191 --- /dev/null +++ b/shaders/src/shaders/raymarching_primitives.rs @@ -0,0 +1,858 @@ +//! Ported to Rust from +//! +//! Original comment: +//! ```glsl +//! // The MIT License +//! // Copyright © 2013 Inigo Quilez +//! // 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. +//! +//! // A list of useful distance function to simple primitives. All +//! // these functions (except for ellipsoid) return an exact +//! // euclidean distance, meaning they produce a better SDF than +//! // what you'd get if you were constructing them from boolean +//! // operations. +//! // +//! // More info here: +//! // +//! // https://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm +//! ``` + +use crate::shader_prelude::*; + +pub const SHADER_DEFINITION: ShaderDefinition = ShaderDefinition { + name: "Raymarching Primitives", +}; + +pub fn shader_fn(render_instruction: &ShaderInput, render_result: &mut ShaderResult) { + let color = &mut render_result.color; + let &ShaderInput { + resolution, + time, + frag_coord, + mouse, + .. + } = render_instruction; + Inputs { + resolution, + frame: (time * 60.0) as i32, + time, + mouse, + } + .main_image(color, frag_coord); +} + +struct Inputs { + resolution: Vec3, + frame: i32, + time: f32, + mouse: Vec4, +} + +const HW_PERFORMANCE: usize = 1; +const AA: i32 = if HW_PERFORMANCE == 0 { + 1 +} else { + 2 // make this 2 or 3 for antialiasing +}; + +//------------------------------------------------------------------ +fn dot2_vec2(v: Vec2) -> f32 { + v.dot(v) +} +fn dot2_vec3(v: Vec3) -> f32 { + v.dot(v) +} +fn ndot(a: Vec2, b: Vec2) -> f32 { + a.x * b.x - a.y * b.y +} + +fn _sd_plane(p: Vec3) -> f32 { + p.y +} + +fn sd_sphere(p: Vec3, s: f32) -> f32 { + p.length() - s +} + +fn sd_box(p: Vec3, b: Vec3) -> f32 { + let d: Vec3 = p.abs() - b; + d.x.max(d.y.max(d.z)).min(0.0) + d.max(Vec3::ZERO).length() +} + +fn sd_bounding_box(mut p: Vec3, b: Vec3, e: f32) -> f32 { + p = p.abs() - b; + let q: Vec3 = (p + Vec3::splat(e)).abs() - Vec3::splat(e); + + (vec3(p.x, q.y, q.z).max(Vec3::ZERO).length() + p.x.max(q.y.max(q.z)).min(0.0)) + .min(vec3(q.x, p.y, q.z).max(Vec3::ZERO).length() + q.x.max(p.y.max(q.z)).min(0.0)) + .min(vec3(q.x, q.y, p.z).max(Vec3::ZERO).length() + q.x.max(q.y.max(p.z)).min(0.0)) +} +// approximated +fn sd_ellipsoid(p: Vec3, r: Vec3) -> f32 { + let k0: f32 = (p / r).length(); + let k1: f32 = (p / (r * r)).length(); + k0 * (k0 - 1.0) / k1 +} + +fn sd_torus(p: Vec3, t: Vec2) -> f32 { + (vec2(p.xz().length() - t.x, p.y)).length() - t.y +} + +fn sd_capped_torus(mut p: Vec3, sc: Vec2, ra: f32, rb: f32) -> f32 { + p.x = p.x.abs(); + let k: f32 = if sc.y * p.x > sc.x * p.y { + p.xy().dot(sc) + } else { + p.xy().length() + }; + (p.dot(p) + ra * ra - 2.0 * ra * k).sqrt() - rb +} + +fn sd_hex_prism(mut p: Vec3, h: Vec2) -> f32 { + let _q: Vec3 = p.abs(); + + let k: Vec3 = vec3(-0.8660254, 0.5, 0.57735); + p = p.abs(); + p = (p.xy() - 2.0 * k.xy().dot(p.xy()).min(0.0) * k.xy()).extend(p.z); + let d: Vec2 = vec2( + (p.xy() - vec2(p.x.clamp(-k.z * h.x, k.z * h.x), h.x)).length() * (p.y - h.x).sign_gl(), + p.z - h.y, + ); + d.x.max(d.y).min(0.0) + d.max(Vec2::ZERO).length() +} + +fn sd_octogon_prism(mut p: Vec3, r: f32, h: f32) -> f32 { + let k: Vec3 = vec3( + -0.9238795325, // sqrt(2+sqrt(2))/2 + 0.3826834323, // sqrt(2-sqrt(2))/2 + 0.4142135623, + ); // sqrt(2)-1 + // reflections + p = p.abs(); + p = (p.xy() - 2.0 * vec2(k.x, k.y).dot(p.xy()).min(0.0) * vec2(k.x, k.y)).extend(p.z); + p = (p.xy() - 2.0 * vec2(-k.x, k.y).dot(p.xy()).min(0.0) * vec2(-k.x, k.y)).extend(p.z); + // polygon side + p = (p.xy() - vec2(p.x.clamp(-k.z * r, k.z * r), r)).extend(p.z); + let d: Vec2 = vec2(p.xy().length() * p.y.sign_gl(), p.z - h); + d.x.max(d.y).min(0.0) + d.max(Vec2::ZERO).length() +} + +fn sd_capsule(p: Vec3, a: Vec3, b: Vec3, r: f32) -> f32 { + let pa: Vec3 = p - a; + let ba: Vec3 = b - a; + let h: f32 = (pa.dot(ba) / ba.dot(ba)).clamp(0.0, 1.0); + (pa - ba * h).length() - r +} + +fn sd_round_cone_vertical(p: Vec3, r1: f32, r2: f32, h: f32) -> f32 { + let q: Vec2 = vec2(p.xz().length(), p.y); + + let b: f32 = (r1 - r2) / h; + let a: f32 = (1.0 - b * b).sqrt(); + let k: f32 = q.dot(vec2(-b, a)); + + if k < 0.0 { + return q.length() - r1; + } + if k > a * h { + return (q - vec2(0.0, h)).length() - r2; + } + + q.dot(vec2(a, b)) - r1 +} + +fn sd_round_cone(p: Vec3, a: Vec3, b: Vec3, r1: f32, r2: f32) -> f32 { + // sampling independent computations (only depend on shape) + let ba: Vec3 = b - a; + let l2: f32 = ba.dot(ba); + let rr: f32 = r1 - r2; + let a2: f32 = l2 - rr * rr; + let il2: f32 = 1.0 / l2; + + // sampling dependant computations + let pa: Vec3 = p - a; + let y: f32 = pa.dot(ba); + let z: f32 = y - l2; + let x2: f32 = dot2_vec3(pa * l2 - ba * y); + let y2: f32 = y * y * l2; + let z2: f32 = z * z * l2; + + // single square root! + let k: f32 = rr.sign_gl() * rr * rr * x2; + if z.sign_gl() * a2 * z2 > k { + return (x2 + z2).sqrt() * il2 - r2; + } + if y.sign_gl() * a2 * y2 < k { + return (x2 + y2).sqrt() * il2 - r1; + } + ((x2 * a2 * il2).sqrt() + y * rr) * il2 - r1 +} + +fn sd_tri_prism(mut p: Vec3, mut h: Vec2) -> f32 { + let k: f32 = 3.0_f32.sqrt(); + h.x *= 0.5 * k; + p = (p.xy() / h.x).extend(p.z); + p.x = p.x.abs() - 1.0; + p.y += 1.0 / k; + if p.x + k * p.y > 0.0 { + p = (vec2(p.x - k * p.y, -k * p.x - p.y) / 2.0).extend(p.z); + } + p.x -= p.x.clamp(-2.0, 0.0); + let d1: f32 = p.xy().length() * (-p.y).sign_gl() * h.x; + let d2: f32 = p.z.abs() - h.y; + vec2(d1, d2).max(Vec2::ZERO).length() + d1.max(d2).min(0.0) +} + +// vertical +fn sd_cylinder_vertical(p: Vec3, h: Vec2) -> f32 { + let d: Vec2 = vec2(p.xz().length(), p.y).abs() - h; + d.x.max(d.y).min(0.0) + d.max(Vec2::ZERO).length() +} + +// arbitrary orientation +fn sd_cylinder(p: Vec3, a: Vec3, b: Vec3, r: f32) -> f32 { + let pa: Vec3 = p - a; + let ba: Vec3 = b - a; + let baba: f32 = ba.dot(ba); + let paba: f32 = pa.dot(ba); + + let x: f32 = (pa * baba - ba * paba).length() - r * baba; + let y: f32 = (paba - baba * 0.5).abs() - baba * 0.5; + let x2: f32 = x * x; + let y2: f32 = y * y * baba; + let d: f32 = if x.max(y) < 0.0 { + -x2.min(y2) + } else { + (if x > 0.0 { x2 } else { 0.0 }) + if y > 0.0 { y2 } else { 0.0 } + }; + d.sign_gl() * d.abs().sqrt() / baba +} + +// vertical +fn sd_cone(p: Vec3, c: Vec2, h: f32) -> f32 { + let q: Vec2 = h * vec2(c.x, -c.y) / c.y; + let w: Vec2 = vec2(p.xz().length(), p.y); + + let a: Vec2 = w - q * (w.dot(q) / q.dot(q)).clamp(0.0, 1.0); + let b: Vec2 = w - q * vec2((w.x / q.x).clamp(0.0, 1.0), 1.0); + let k: f32 = q.y.sign_gl(); + let d: f32 = a.dot(a).min(b.dot(b)); + let s: f32 = (k * (w.x * q.y - w.y * q.x)).max(k * (w.y - q.y)); + d.sqrt() * s.sign_gl() +} + +fn sd_capped_cone_vertical(p: Vec3, h: f32, r1: f32, r2: f32) -> f32 { + let q: Vec2 = vec2(p.xz().length(), p.y); + + let k1: Vec2 = vec2(r2, h); + let k2: Vec2 = vec2(r2 - r1, 2.0 * h); + let ca: Vec2 = vec2( + q.x - q.x.min(if q.y < 0.0 { r1 } else { r2 }), + q.y.abs() - h, + ); + let cb: Vec2 = q - k1 + k2 * ((k1 - q).dot(k2) / dot2_vec2(k2)).clamp(0.0, 1.0); + let s: f32 = if cb.x < 0.0 && ca.y < 0.0 { -1.0 } else { 1.0 }; + s * dot2_vec2(ca).min(dot2_vec2(cb)).sqrt() +} + +fn sd_capped_cone(p: Vec3, a: Vec3, b: Vec3, ra: f32, rb: f32) -> f32 { + let rba: f32 = rb - ra; + let baba: f32 = (b - a).dot(b - a); + let papa: f32 = (p - a).dot(p - a); + let paba: f32 = (p - a).dot(b - a) / baba; + + let x: f32 = (papa - paba * paba * baba).sqrt(); + + let cax: f32 = 0.0_f32.max(x - (if paba < 0.5 { ra } else { rb })); + let cay: f32 = (paba - 0.5).abs() - 0.5; + + let k: f32 = rba * rba + baba; + let f: f32 = ((rba * (x - ra) + paba * baba) / k).clamp(0.0, 1.0); + + let cbx: f32 = x - ra - f * rba; + let cby: f32 = paba - f; + + let s: f32 = if cbx < 0.0 && cay < 0.0 { -1.0 } else { 1.0 }; + + s * (cax * cax + cay * cay * baba) + .min(cbx * cbx + cby * cby * baba) + .sqrt() +} + +// c is the sin/cos of the desired cone angle +fn sd_solid_angle(pos: Vec3, c: Vec2, ra: f32) -> f32 { + let p: Vec2 = vec2(pos.xz().length(), pos.y); + let l: f32 = p.length() - ra; + let m: f32 = (p - c * p.dot(c).clamp(0.0, ra)).length(); + l.max(m * (c.y * p.x - c.x * p.y).sign_gl()) +} + +fn sd_octahedron(mut p: Vec3, s: f32) -> f32 { + p = p.abs(); + let m: f32 = p.x + p.y + p.z - s; + + // exact distance + if false { + let mut o: Vec3 = (3.0 * p - Vec3::splat(m)).min(Vec3::ZERO); + o = (6.0 * p - Vec3::splat(m) * 2.0 - o * 3.0 + Vec3::splat(o.x + o.y + o.z)).max(Vec3::ZERO); + return (p - s * o / (o.x + o.y + o.z)).length(); + } + + // exact distance + if true { + let q: Vec3; + if 3.0 * p.x < m { + q = p; + } else if 3.0 * p.y < m { + q = p.yzx(); + } else if 3.0 * p.z < m { + q = p.zxy(); + } else { + return m * 0.57735027; + } + let k: f32 = (0.5 * (q.z - q.y + s)).clamp(0.0, s); + return vec3(q.x, q.y - s + k, q.z - k).length(); + } + + // bound, not exact + if false { + return m * 0.57735027; + } + + unreachable!(); +} + +fn sd_pyramid(mut p: Vec3, h: f32) -> f32 { + let m2: f32 = h * h + 0.25; + + // symmetry + p = p.xz().abs().extend(p.y).xzy(); + p = if p.z > p.x { p.zx() } else { p.xz() }.extend(p.y).xzy(); + p = (p.xz() - Vec2::splat(0.5)).extend(p.y).xzy(); + + // project into face plane (2D) + let q: Vec3 = vec3(p.z, h * p.y - 0.5 * p.x, h * p.x + 0.5 * p.y); + + let s: f32 = (-q.x).max(0.0); + let t: f32 = ((q.y - 0.5 * p.z) / (m2 + 0.25)).clamp(0.0, 1.0); + + let a: f32 = m2 * (q.x + s) * (q.x + s) + q.y * q.y; + let b: f32 = m2 * (q.x + 0.5 * t) * (q.x + 0.5 * t) + (q.y - m2 * t) * (q.y - m2 * t); + + let d2: f32 = if q.y.min(-q.x * m2 - q.y * 0.5) > 0.0 { + 0.0 + } else { + a.min(b) + }; + + // recover 3D and scale, and add sign + ((d2 + q.z * q.z) / m2).sqrt() * q.z.max(-p.y).sign_gl() +} + +// la,lb=semi axis, h=height, ra=corner +fn sd_rhombus(mut p: Vec3, la: f32, lb: f32, h: f32, ra: f32) -> f32 { + p = p.abs(); + let b: Vec2 = vec2(la, lb); + let f: f32 = (ndot(b, b - 2.0 * p.xz()) / b.dot(b)).clamp(-1.0, 1.0); + let q: Vec2 = vec2( + (p.xz() - 0.5 * b * vec2(1.0 - f, 1.0 + f)).length() + * (p.x * b.y + p.z * b.x - b.x * b.y).sign_gl() + - ra, + p.y - h, + ); + q.x.max(q.y).min(0.0) + q.max(Vec2::ZERO).length() +} + +//------------------------------------------------------------------ + +fn op_u(d1: Vec2, d2: Vec2) -> Vec2 { + if d1.x < d2.x { + d1 + } else { + d2 + } +} + +//------------------------------------------------------------------ + +impl Inputs { + #[allow(unused)] + fn zero(&self) -> i32 { + let frame = self.frame; + if frame >= 0 { + 0 + } else { + frame + } + } +} + +//------------------------------------------------------------------ + +fn map(pos: Vec3) -> Vec2 { + let mut res: Vec2 = vec2(1e10, 0.0); + + res = op_u( + res, + vec2(sd_sphere(pos - vec3(-2.0, 0.25, 0.0), 0.25), 26.9), + ); + + // bounding box + if sd_box(pos - vec3(0.0, 0.3, -1.0), vec3(0.35, 0.3, 2.5)) < res.x { + // more primitives + res = op_u( + res, + vec2( + sd_bounding_box(pos - vec3(0.0, 0.25, 0.0), vec3(0.3, 0.25, 0.2), 0.025), + 16.9, + ), + ); + res = op_u( + res, + vec2( + sd_torus((pos - vec3(0.0, 0.30, 1.0)).xzy(), vec2(0.25, 0.05)), + 25.0, + ), + ); + res = op_u( + res, + vec2( + sd_cone(pos - vec3(0.0, 0.45, -1.0), vec2(0.6, 0.8), 0.45), + 55.0, + ), + ); + res = op_u( + res, + vec2( + sd_capped_cone_vertical(pos - vec3(0.0, 0.25, -2.0), 0.25, 0.25, 0.1), + 13.67, + ), + ); + res = op_u( + res, + vec2( + sd_solid_angle(pos - vec3(0.0, 0.00, -3.0), vec2(3.0, 4.0) / 5.0, 0.4), + 49.13, + ), + ); + } + + // bounding box + if sd_box(pos - vec3(1.0, 0.3, -1.0), vec3(0.35, 0.3, 2.5)) < res.x { + // more primitives + res = op_u( + res, + vec2( + sd_capped_torus( + (pos - vec3(1.0, 0.30, 1.0)) * vec3(1.0, -1.0, 1.0), + vec2(0.866025, -0.5), + 0.25, + 0.05, + ), + 8.5, + ), + ); + res = op_u( + res, + vec2( + sd_box(pos - vec3(1.0, 0.25, 0.0), vec3(0.3, 0.25, 0.1)), + 3.0, + ), + ); + res = op_u( + res, + vec2( + sd_capsule( + pos - vec3(1.0, 0.00, -1.0), + vec3(-0.1, 0.1, -0.1), + vec3(0.2, 0.4, 0.2), + 0.1, + ), + 31.9, + ), + ); + res = op_u( + res, + vec2( + sd_cylinder_vertical(pos - vec3(1.0, 0.25, -2.0), vec2(0.15, 0.25)), + 8.0, + ), + ); + res = op_u( + res, + vec2( + sd_hex_prism(pos - vec3(1.0, 0.2, -3.0), vec2(0.2, 0.05)), + 18.4, + ), + ); + } + + // bounding box + if sd_box(pos - vec3(-1.0, 0.3, -1.0), vec3(0.35, 0.35, 2.5)) < res.x { + // more primitives + res = op_u( + res, + vec2(sd_pyramid(pos - vec3(-1.0, -0.6, -3.0), 1.0), 13.56), + ); + res = op_u( + res, + vec2(sd_octahedron(pos - vec3(-1.0, 0.15, -2.0), 0.35), 23.56), + ); + res = op_u( + res, + vec2( + sd_tri_prism(pos - vec3(-1.0, 0.15, -1.0), vec2(0.3, 0.05)), + 43.5, + ), + ); + res = op_u( + res, + vec2( + sd_ellipsoid(pos - vec3(-1.0, 0.25, 0.0), vec3(0.2, 0.25, 0.05)), + 43.17, + ), + ); + res = op_u( + res, + vec2( + sd_rhombus((pos - vec3(-1.0, 0.34, 1.0)).xzy(), 0.15, 0.25, 0.04, 0.08), + 17.0, + ), + ); + } + // bounding box + if sd_box(pos - vec3(2.0, 0.3, -1.0), vec3(0.35, 0.3, 2.5)) < res.x { + // more primitives + res = op_u( + res, + vec2( + sd_octogon_prism(pos - vec3(2.0, 0.2, -3.0), 0.2, 0.05), + 51.8, + ), + ); + res = op_u( + res, + vec2( + sd_cylinder( + pos - vec3(2.0, 0.15, -2.0), + vec3(0.1, -0.1, 0.0), + vec3(-0.2, 0.35, 0.1), + 0.08, + ), + 31.2, + ), + ); + res = op_u( + res, + vec2( + sd_capped_cone( + pos - vec3(2.0, 0.10, -1.0), + vec3(0.1, 0.0, 0.0), + vec3(-0.2, 0.40, 0.1), + 0.15, + 0.05, + ), + 46.1, + ), + ); + res = op_u( + res, + vec2( + sd_round_cone( + pos - vec3(2.0, 0.15, 0.0), + vec3(0.1, 0.0, 0.0), + vec3(-0.1, 0.35, 0.1), + 0.15, + 0.05, + ), + 51.7, + ), + ); + res = op_u( + res, + vec2( + sd_round_cone_vertical(pos - vec3(2.0, 0.20, 1.0), 0.2, 0.1, 0.3), + 37.0, + ), + ); + } + + res +} + +// http://iquilezles.org/www/articles/boxfunctions/boxfunctions.htm +fn i_box(ro: Vec3, rd: Vec3, rad: Vec3) -> Vec2 { + let m: Vec3 = 1.0 / rd; + let n: Vec3 = m * ro; + let k: Vec3 = m.abs() * rad; + let t1: Vec3 = -n - k; + let t2: Vec3 = -n + k; + vec2(t1.x.max(t1.y).max(t1.z), t2.x.min(t2.y).min(t2.z)) +} + +fn raycast(ro: Vec3, rd: Vec3) -> Vec2 { + let mut res: Vec2 = vec2(-1.0, -1.0); + + let mut tmin: f32 = 1.0; + let mut tmax: f32 = 20.0; + + // raytrace floor plane + let tp1: f32 = (0.0 - ro.y) / rd.y; + if tp1 > 0.0 { + tmax = tmax.min(tp1); + res = vec2(tp1, 1.0); + } + //else return res; + + // raymarch primitives + let tb: Vec2 = i_box(ro - vec3(0.0, 0.4, -0.5), rd, vec3(2.5, 0.41, 3.0)); + if tb.x < tb.y && tb.y > 0.0 && tb.x < tmax { + //return vec2(tb.x,2.0); + tmin = tb.x.max(tmin); + tmax = tb.y.min(tmax); + + let mut t: f32 = tmin; + let mut i = 0; + while i < 70 && t < tmax { + let h: Vec2 = map(ro + rd * t); + if h.x.abs() < (0.0001 * t) { + res = vec2(t, h.y); + break; + } + t += h.x; + i += 1; + } + } + + res +} + +impl Inputs { + // http://iquilezles.org/www/articles/rmshadows/rmshadows.htm + fn calc_softshadow(&self, ro: Vec3, rd: Vec3, mint: f32, mut tmax: f32) -> f32 { + // bounding volume + let tp: f32 = (0.8 - ro.y) / rd.y; + if tp > 0.0 { + tmax = tmax.min(tp); + } + + let mut res: f32 = 1.0; + let mut t: f32 = mint; + for _ in 0..24 { + let h: f32 = map(ro + rd * t).x; + let s: f32 = (8.0 * h / t).clamp(0.0, 1.0); + res = res.min(s * s * (3.0 - 2.0 * s)); + t += h.clamp(0.02, 0.2); + if res < 0.004 || t > tmax { + break; + } + } + res.clamp(0.0, 1.0) + } + + // http://iquilezles.org/www/articles/normalsSDF/normalsSDF.htm + fn calc_normal(&self, pos: Vec3) -> Vec3 { + if false { + let e: Vec2 = vec2(1.0, -1.0) * 0.5773 * 0.0005; + (e.xyy() * map(pos + e.xyy()).x + + e.yyx() * map(pos + e.yyx()).x + + e.yxy() * map(pos + e.yxy()).x + + e.xxx() * map(pos + e.xxx()).x) + .normalize() + } else { + // inspired by tdhooper and klems - a way to prevent the compiler from inlining map() 4 times + let mut n: Vec3 = Vec3::ZERO; + for i in 0..4 { + let e: Vec3 = 0.5773 + * (2.0 + * vec3( + (((i + 3) >> 1) & 1) as f32, + ((i >> 1) & 1) as f32, + (i & 1) as f32, + ) + - Vec3::ONE); + n += e * map(pos + 0.0005 * e).x; + //if n.x+n.y+n.z>100.0 {break;} + } + n.normalize() + } + } + + fn calc_ao(&self, pos: Vec3, nor: Vec3) -> f32 { + let mut occ: f32 = 0.0; + let mut sca: f32 = 1.0; + for i in 0..5 { + let h: f32 = 0.01 + 0.12 * i as f32 / 4.0; + let d: f32 = map(pos + h * nor).x; + occ += (h - d) * sca; + sca *= 0.95; + if occ > 0.35 { + break; + } + } + (1.0 - 3.0 * occ).clamp(0.0, 1.0) * (0.5 + 0.5 * nor.y) + } +} + +// http://iquilezles.org/www/articles/checkerfiltering/checkerfiltering.htm +fn checkers_grad_box(p: Vec2, dpdx: Vec2, dpdy: Vec2) -> f32 { + // filter kernel + let w: Vec2 = dpdx.abs() + dpdy.abs() + Vec2::splat(0.001); + // analytical integral (box filter) + let i: Vec2 = 2.0 + * ((((p - 0.5 * w) * 0.5).fract_gl() - Vec2::splat(0.5)).abs() + - (((p + 0.5 * w) * 0.5).fract_gl() - Vec2::splat(0.5)).abs()) + / w; + // xor pattern + 0.5 - 0.5 * i.x * i.y +} + +impl Inputs { + fn render(&self, ro: Vec3, rd: Vec3, rdx: Vec3, rdy: Vec3) -> Vec3 { + // background + let mut col: Vec3 = vec3(0.7, 0.7, 0.9) - Vec3::splat(rd.y.max(0.0) * 0.3); + + // raycast scene + let res: Vec2 = raycast(ro, rd); + let t: f32 = res.x; + let m: f32 = res.y; + if m > -0.5 { + let pos: Vec3 = ro + t * rd; + let nor: Vec3 = if m < 1.5 { + vec3(0.0, 1.0, 0.0) + } else { + self.calc_normal(pos) + }; + let ref_: Vec3 = rd.reflect(nor); + + // material + col = Vec3::splat(0.2) + 0.2 * (Vec3::splat(m) * 2.0 + vec3(0.0, 1.0, 2.0)).sin(); + let mut ks: f32 = 1.0; + + if m < 1.5 { + // project pixel footprint into the plane + let dpdx: Vec3 = ro.y * (rd / rd.y - rdx / rdx.y); + let dpdy: Vec3 = ro.y * (rd / rd.y - rdy / rdy.y); + + let f: f32 = checkers_grad_box(3.0 * pos.xz(), 3.0 * dpdx.xz(), 3.0 * dpdy.xz()); + col = Vec3::splat(0.15) + f * Vec3::splat(0.05); + ks = 0.4; + } + + // lighting + let occ: f32 = self.calc_ao(pos, nor); + + let mut lin: Vec3 = Vec3::ZERO; + + // sun + { + let lig: Vec3 = (vec3(-0.5, 0.4, -0.6)).normalize(); + let hal: Vec3 = (lig - rd).normalize(); + let mut dif: f32 = nor.dot(lig).clamp(0.0, 1.0); + //if( dif>0.0001 ) + dif *= self.calc_softshadow(pos, lig, 0.02, 2.5); + let mut spe: f32 = nor.dot(hal).clamp(0.0, 1.0).powf(16.0); + spe *= dif; + spe *= 0.04 + 0.96 * (1.0 - hal.dot(lig)).clamp(0.0, 1.0).powf(5.0); + lin += col * 2.20 * dif * vec3(1.30, 1.00, 0.70); + lin += 5.00 * spe * vec3(1.30, 1.00, 0.70) * ks; + } + // sky + { + let mut dif: f32 = (0.5 + 0.5 * nor.y).clamp(0.0, 1.0).sqrt(); + dif *= occ; + let mut spe: f32 = smoothstep(-0.2, 0.2, ref_.y); + spe *= dif; + spe *= 0.04 + 0.96 * (1.0 + nor.dot(rd)).clamp(0.0, 1.0).powf(5.0); + //if( spe>0.001 ) + spe *= self.calc_softshadow(pos, ref_, 0.02, 2.5); + lin += col * 0.60 * dif * vec3(0.40, 0.60, 1.15); + lin += 2.00 * spe * vec3(0.40, 0.60, 1.30) * ks; + } + // back + { + let mut dif: f32 = + nor.dot(vec3(0.5, 0.0, 0.6).normalize()).clamp(0.0, 1.0) * (1.0 - pos.y).clamp(0.0, 1.0); + dif *= occ; + lin += col * 0.55 * dif * vec3(0.25, 0.25, 0.25); + } + // sss + { + let mut dif: f32 = (1.0 + nor.dot(rd)).clamp(0.0, 1.0).powf(2.0); + dif *= occ; + lin += col * 0.25 * dif * vec3(1.00, 1.00, 1.00); + } + + col = lin; + + col = mix(col, vec3(0.7, 0.7, 0.9), 1.0 - (-0.0001 * t * t * t).exp()); + } + col.clamp(Vec3::ZERO, Vec3::ONE) + } +} + +fn set_camera(ro: Vec3, ta: Vec3, cr: f32) -> Mat3 { + let cw: Vec3 = (ta - ro).normalize(); + let cp: Vec3 = vec3(cr.sin(), cr.cos(), 0.0); + let cu: Vec3 = cw.cross(cp).normalize(); + let cv: Vec3 = cu.cross(cw); + Mat3::from_cols(cu, cv, cw) +} + +impl Inputs { + fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { + let mo: Vec2 = self.mouse.xy() / self.resolution.xy(); + let time: f32 = 32.0 + self.time * 1.5; + + // camera + let ta: Vec3 = vec3(0.5, -0.5, -0.6); + let ro: Vec3 = ta + + vec3( + 4.5 * (0.1 * time + 7.0 * mo.x).cos(), + 1.3 + 2.0 * mo.y, + 4.5 * (0.1 * time + 7.0 * mo.x).sin(), + ); + // camera-to-world transformation + let ca: Mat3 = set_camera(ro, ta, 0.0); + + let mut tot: Vec3 = Vec3::ZERO; + let mut p: Vec2; + + for m in 0..AA { + for n in 0..AA { + // pixel coordinates + let o: Vec2 = vec2(m as f32, n as f32) / AA as f32 - Vec2::splat(0.5); + if AA > 1 { + p = (2.0 * (frag_coord + o) - self.resolution.xy()) / self.resolution.y; + } else { + p = (2.0 * frag_coord - self.resolution.xy()) / self.resolution.y; + } + + // ray direction + let rd: Vec3 = ca * p.extend(2.5).normalize(); + + // ray differentials + let px: Vec2 = + (2.0 * (frag_coord + vec2(1.0, 0.0)) - self.resolution.xy()) / self.resolution.y; + let py: Vec2 = + (2.0 * (frag_coord + vec2(0.0, 1.0)) - self.resolution.xy()) / self.resolution.y; + let rdx: Vec3 = ca * px.extend(2.5).normalize(); + let rdy: Vec3 = ca * py.extend(2.5).normalize(); + + // render + let mut col: Vec3 = self.render(ro, rd, rdx, rdy); + + // gain + // col = col*3.0/(2.5+col); + + // gamma + col = col.powf_vec(Vec3::splat(0.4545)); + + tot += col; + } + } + tot /= (AA * AA) as f32; + + *frag_color = tot.extend(1.0); + } +} diff --git a/shaders/src/shaders/seascape.rs b/shaders/src/shaders/seascape.rs new file mode 100644 index 0000000..decad67 --- /dev/null +++ b/shaders/src/shaders/seascape.rs @@ -0,0 +1,263 @@ +//! Ported to Rust from +//! +//! Original comment: +//! ```glsl +//! /* +//! * "Seascape" by Alexander Alekseev aka TDM - 2014 +//! * License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. +//! * Contact: tdmaav@gmail.com +//! */ +//! ``` + +use crate::shader_prelude::*; + +pub const SHADER_DEFINITION: ShaderDefinition = ShaderDefinition { name: "Seascape" }; + +pub fn shader_fn(render_instruction: &ShaderInput, render_result: &mut ShaderResult) { + let color = &mut render_result.color; + let &ShaderInput { + resolution, + time, + frag_coord, + mouse, + .. + } = render_instruction; + Inputs { + resolution, + time, + mouse, + } + .main_image(color, frag_coord); +} + +struct Inputs { + resolution: Vec3, + time: f32, + mouse: Vec4, +} + +const NUM_STEPS: usize = 8; +impl Inputs { + fn epsilon_nrm(&self) -> f32 { + 0.1 / self.resolution.x + } +} +const AA: bool = true; + +// sea +const ITER_GEOMETRY: usize = 3; +const ITER_FRAGMENT: usize = 5; +const SEA_HEIGHT: f32 = 0.6; +const SEA_CHOPPY: f32 = 4.0; +const SEA_SPEED: f32 = 0.8; +const SEA_FREQ: f32 = 0.16; +const SEA_BASE: Vec3 = vec3(0.0, 0.09, 0.18); +// const SEA_WATER_COLOR: Vec3 = const_vec3!([0.8, 0.9, 0.6]) * 0.6; +const SEA_WATER_COLOR: Vec3 = vec3(0.8 * 0.6, 0.9 * 0.6, 0.6 * 0.6); +impl Inputs { + fn sea_time(&self) -> f32 { + 1.0 + self.time * SEA_SPEED + } +} +const OCTAVE_M: Mat2 = mat2(vec2(1.6, 1.2), vec2(-1.2, 1.6)); + +// math +fn from_euler(ang: Vec3) -> Mat3 { + let a1: Vec2 = vec2(ang.x.sin(), ang.x.cos()); + let a2: Vec2 = vec2(ang.y.sin(), ang.y.cos()); + let a3: Vec2 = vec2(ang.z.sin(), ang.z.cos()); + Mat3::from_cols( + vec3( + a1.y * a3.y + a1.x * a2.x * a3.x, + a1.y * a2.x * a3.x + a3.y * a1.x, + -a2.y * a3.x, + ), + vec3(-a2.y * a1.x, a1.y * a2.y, a2.x), + vec3( + a3.y * a1.x * a2.x + a1.y * a3.x, + a1.x * a3.x - a1.y * a3.y * a2.x, + a2.y * a3.y, + ), + ) +} +fn hash(p: Vec2) -> f32 { + let h: f32 = p.dot(vec2(127.1, 311.7)); + (h.sin() * 43758.5453123).fract_gl() +} +fn noise(p: Vec2) -> f32 { + let i: Vec2 = p.floor(); + let f: Vec2 = p.fract_gl(); + let u: Vec2 = f * f * (Vec2::splat(3.0) - 2.0 * f); + -1.0 + + 2.0 + * mix( + mix(hash(i + vec2(0.0, 0.0)), hash(i + vec2(1.0, 0.0)), u.x), + mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), u.x), + u.y, + ) +} + +// lighting +fn diffuse(n: Vec3, l: Vec3, p: f32) -> f32 { + (n.dot(l) * 0.4 + 0.6).powf(p) +} +fn specular(n: Vec3, l: Vec3, e: Vec3, s: f32) -> f32 { + let nrm: f32 = (s + 8.0) / (PI * 8.0); + (e.reflect(n).dot(l).max(0.0)).powf(s) * nrm +} + +// sky +fn get_sky_color(mut e: Vec3) -> Vec3 { + e.y = (e.y.max(0.0) * 0.8 + 0.2) * 0.8; + vec3((1.0 - e.y).powf(2.0), 1.0 - e.y, 0.6 + (1.0 - e.y) * 0.4) * 1.1 +} + +// sea +fn sea_octave(mut uv: Vec2, choppy: f32) -> f32 { + uv += Vec2::splat(noise(uv)); + let mut wv: Vec2 = Vec2::ONE - uv.sin().abs(); + let swv: Vec2 = uv.cos().abs(); + wv = mix(wv, swv, wv); + (1.0 - (wv.x * wv.y).powf(0.65)).powf(choppy) +} + +impl Inputs { + fn map(&self, p: Vec3) -> f32 { + let mut freq: f32 = SEA_FREQ; + let mut amp: f32 = SEA_HEIGHT; + let mut choppy: f32 = SEA_CHOPPY; + let mut uv: Vec2 = p.xz(); + uv.x *= 0.75; + + let mut d: f32; + let mut h: f32 = 0.0; + + for _ in 0..ITER_GEOMETRY { + d = sea_octave((uv + Vec2::splat(self.sea_time())) * freq, choppy); + d += sea_octave((uv - Vec2::splat(self.sea_time())) * freq, choppy); + h += d * amp; + let octave_m = OCTAVE_M; + uv = octave_m.transpose() * uv; + freq *= 1.9; + amp *= 0.22; + choppy = mix(choppy, 1.0, 0.2); + } + p.y - h + } + + fn map_detailed(&self, p: Vec3) -> f32 { + let mut freq: f32 = SEA_FREQ; + let mut amp: f32 = SEA_HEIGHT; + let mut choppy: f32 = SEA_CHOPPY; + let mut uv: Vec2 = p.xz(); + uv.x *= 0.75; + let mut d: f32; + let mut h: f32 = 0.0; + for _ in 0..ITER_FRAGMENT { + d = sea_octave((uv + Vec2::splat(self.sea_time())) * freq, choppy); + d += sea_octave((uv - Vec2::splat(self.sea_time())) * freq, choppy); + h += d * amp; + let octave_m = OCTAVE_M; + uv = octave_m.transpose() * uv; + freq *= 1.9; + amp *= 0.22; + choppy = mix(choppy, 1.0, 0.2); + } + p.y - h + } +} + +fn get_sea_color(p: Vec3, n: Vec3, l: Vec3, eye: Vec3, dist: Vec3) -> Vec3 { + let mut fresnel: f32 = (1.0 - n.dot(-eye)).clamp(0.0, 1.0); + fresnel = fresnel.powf(3.0) * 0.5; + + let reflected: Vec3 = get_sky_color(eye.reflect(n)); + let refracted: Vec3 = SEA_BASE + diffuse(n, l, 80.0) * SEA_WATER_COLOR * 0.12; + + let mut color: Vec3 = mix(refracted, reflected, fresnel); + let atten: f32 = (1.0 - dist.dot(dist) * 0.001).max(0.0); + color += SEA_WATER_COLOR * (p.y - SEA_HEIGHT) * 0.18 * atten; + + color += Vec3::splat(specular(n, l, eye, 60.0)); + color +} + +impl Inputs { + // tracing + fn get_normal(&self, p: Vec3, eps: f32) -> Vec3 { + let mut n: Vec3 = Vec3::ZERO; + n.y = self.map_detailed(p); + n.x = self.map_detailed(vec3(p.x + eps, p.y, p.z)) - n.y; + n.z = self.map_detailed(vec3(p.x, p.y, p.z + eps)) - n.y; + n.y = eps; + n.normalize() + } + + fn height_map_tracing(&self, ori: Vec3, dir: Vec3, p: &mut Vec3) -> f32 { + let mut tm: f32 = 0.0; + let mut tx: f32 = 1000.0; + let mut hx: f32 = self.map(ori + dir * tx); + if hx > 0.0 { + return tx; + } + let mut hm: f32 = self.map(ori + dir * tm); + let mut tmid: f32 = 0.0; + for _ in 0..NUM_STEPS { + tmid = mix(tm, tx, hm / (hm - hx)); + *p = ori + dir * tmid; + let hmid: f32 = self.map(*p); + if hmid < 0.0 { + tx = tmid; + hx = hmid; + } else { + tm = tmid; + hm = hmid; + } + } + tmid + } + + fn get_pixel(&self, coord: Vec2, time: f32) -> Vec3 { + let mut uv: Vec2 = coord / self.resolution.xy(); + uv = uv * 2.0 - Vec2::ONE; + uv.x *= self.resolution.x / self.resolution.y; + // ray + let ang: Vec3 = vec3((time * 3.0).sin() * 0.1, time.sin() * 0.2 + 0.3, time); + let ori: Vec3 = vec3(0.0, 3.5, time * 5.0); + let mut dir: Vec3 = uv.extend(-2.0).normalize(); + dir.z += uv.length() * 0.14; + dir = from_euler(ang).transpose() * dir.normalize(); + // tracing + let mut p: Vec3 = Vec3::ZERO; + self.height_map_tracing(ori, dir, &mut p); + let dist: Vec3 = p - ori; + let n: Vec3 = self.get_normal(p, dist.dot(dist) * self.epsilon_nrm()); + let light: Vec3 = vec3(0.0, 1.0, 0.8).normalize(); + // color + mix( + get_sky_color(dir), + get_sea_color(p, n, light, dir, dist), + smoothstep(0.0, -0.02, dir.y).powf(0.2), + ) + } + + // main + fn main_image(&self, frag_color: &mut Vec4, frag_coord: Vec2) { + let time: f32 = self.time * 0.3 + self.mouse.x * 0.01; + let mut color: Vec3; + if AA { + color = Vec3::ZERO; + for i in -1..=1 { + for j in -1..=1 { + let uv: Vec2 = frag_coord + vec2(i as f32, j as f32) / 3.0; + color += self.get_pixel(uv, time); + } + } + color /= 9.0; + } else { + color = self.get_pixel(frag_coord, time); + } + // post + *frag_color = color.powf(0.65).extend(1.0); + } +} diff --git a/shaders/src/shaders/skyline.rs b/shaders/src/shaders/skyline.rs new file mode 100644 index 0000000..d70fc8c --- /dev/null +++ b/shaders/src/shaders/skyline.rs @@ -0,0 +1,1052 @@ +//! Ported to Rust from +//! +//! Original comment: +//! ```glsl +//! /*-------------------------------------------------------------------------------------- +//! License CC0 - http://creativecommons.org/publicdomain/zero/1.0/ +//! To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. This software is distributed without any warranty. +//! ---------------------------------------------------------------------------------------- +//! ^ This means do ANYTHING YOU WANT with this code. Because we are programmers, not lawyers. +//! -Otavio Good +//! */ +//! ``` +use crate::shader_prelude::*; + +pub const SHADER_DEFINITION: ShaderDefinition = ShaderDefinition { name: "Skyline" }; + +pub fn shader_fn(render_instruction: &ShaderInput, render_result: &mut ShaderResult) { + let color = &mut render_result.color; + let &ShaderInput { + resolution, + time, + frag_coord, + mouse, + .. + } = render_instruction; + State::new(Inputs { + resolution, + time, + mouse, + channel0: RgbCube { + alpha: 1.0, + intensity: 1.0, + }, + }) + .main_image(color, frag_coord); +} + +struct Inputs { + resolution: Vec3, + time: f32, + mouse: Vec4, + channel0: C0, +} + +struct State { + inputs: Inputs, + + // -------------------------------------------------------- + // These variables are for the non-realtime block renderer. + local_time: f32, + seed: f32, + + // Animation variables + fade: f32, + sun_dir: Vec3, + sun_col: Vec3, + exposure: f32, + sky_col: Vec3, + horizon_col: Vec3, + + // other + march_count: f32, +} + +impl State { + #[must_use] + fn new(inputs: Inputs) -> Self { + Self { + inputs, + + local_time: 0.0, + seed: 1.0, + + fade: 1.0, + sun_dir: Vec3::ZERO, + sun_col: Vec3::ZERO, + exposure: 1.0, + sky_col: Vec3::ZERO, + horizon_col: Vec3::ZERO, + + march_count: 0.0, + } + } +} + +// ---------------- Config ---------------- +// This is an option that lets you render high quality frames for screenshots. It enables +// stochastic antialiasing and motion blur automatically for any shader. +const NON_REALTIME_HQ_RENDER: bool = false; +const FRAME_TO_RENDER_HQ: f32 = 50.0; // Time in seconds of frame to render +const ANTIALIASING_SAMPLES: u32 = 16; // 16x antialiasing - too much might make the shader compiler angry. + +const MANUAL_CAMERA: bool = false; + +// ---- noise functions ---- +fn _v31(a: Vec3) -> f32 { + a.x + a.y * 37.0 + a.z * 521.0 +} +fn v21(a: Vec2) -> f32 { + a.x + a.y * 37.0 +} +fn hash11(a: f32) -> f32 { + (a.sin() * 10403.9).fract_gl() +} +fn hash21(uv: Vec2) -> f32 { + let f: f32 = uv.x + uv.y * 37.0; + (f.sin() * 104003.9).fract_gl() +} +fn hash22(uv: Vec2) -> Vec2 { + let f: f32 = uv.x + uv.y * 37.0; + (f.cos() * vec2(10003.579, 37049.7)).fract_gl() +} +fn _hash12(f: f32) -> Vec2 { + (f.cos() * vec2(10003.579, 37049.7)).fract_gl() +} +fn _hash1d(u: f32) -> f32 { + (u.sin() * 143.9).fract_gl() // scale this down to kill the jitters +} +fn hash2d(uv: Vec2) -> f32 { + let f: f32 = uv.x + uv.y * 37.0; + (f.sin() * 104003.9).fract_gl() +} +fn hash3d(uv: Vec3) -> f32 { + let f: f32 = uv.x + uv.y * 37.0 + uv.z * 521.0; + (f.sin() * 110003.9).fract_gl() +} +fn mix_p(f0: f32, f1: f32, a: f32) -> f32 { + mix(f0, f1, a * a * (3.0 - 2.0 * a)) +} +const ZERO_ONE: Vec2 = vec2(0.0, 1.0); +fn noise2d(uv: Vec2) -> f32 { + let fr: Vec2 = uv.fract_gl(); + let fl: Vec2 = uv.floor(); + let h00: f32 = hash2d(fl); + let h10: f32 = hash2d(fl + ZERO_ONE.yx()); + let h01: f32 = hash2d(fl + ZERO_ONE); + let h11: f32 = hash2d(fl + ZERO_ONE.yy()); + mix_p(mix_p(h00, h10, fr.x), mix_p(h01, h11, fr.x), fr.y) +} +fn noise(uv: Vec3) -> f32 { + let fr: Vec3 = uv.fract_gl(); + let fl: Vec3 = uv.floor(); + let h000: f32 = hash3d(fl); + let h100: f32 = hash3d(fl + ZERO_ONE.yxx()); + let h010: f32 = hash3d(fl + ZERO_ONE.xyx()); + let h110: f32 = hash3d(fl + ZERO_ONE.yyx()); + let h001: f32 = hash3d(fl + ZERO_ONE.xxy()); + let h101: f32 = hash3d(fl + ZERO_ONE.yxy()); + let h011: f32 = hash3d(fl + ZERO_ONE.xyy()); + let h111: f32 = hash3d(fl + ZERO_ONE.yyy()); + mix_p( + mix_p(mix_p(h000, h100, fr.x), mix_p(h010, h110, fr.x), fr.y), + mix_p(mix_p(h001, h101, fr.x), mix_p(h011, h111, fr.x), fr.y), + fr.z, + ) +} + +impl State { + // This function basically is a procedural environment map that makes the sun + fn get_sun_color_small(&self, ray_dir: Vec3, sun_dir: Vec3) -> Vec3 { + let local_ray: Vec3 = ray_dir.normalize(); + let dist: f32 = 1.0 - (local_ray.dot(sun_dir) * 0.5 + 0.5); + let mut sun_intensity: f32 = 0.05 / dist; + sun_intensity += (-dist * 150.0).exp() * 7000.0; + sun_intensity = sun_intensity.min(40000.0); + self.sun_col * sun_intensity * 0.025 + } + + fn get_env_map(&self, ray_dir: Vec3, sun_dir: Vec3) -> Vec3 { + // fade the sky color, multiply sunset dimming + let mut final_color: Vec3 = mix( + self.horizon_col, + self.sky_col, + saturate(ray_dir.y).powf(0.47), + ) * 0.95; + // make clouds - just a horizontal plane with noise + let mut n: f32 = noise2d(ray_dir.xz() / ray_dir.y * 1.0); + n += noise2d(ray_dir.xz() / ray_dir.y * 2.0) * 0.5; + n += noise2d(ray_dir.xz() / ray_dir.y * 4.0) * 0.25; + n += noise2d(ray_dir.xz() / ray_dir.y * 8.0) * 0.125; + n = n.abs().powf(3.0); + n = mix(n * 0.2, n, saturate((ray_dir.y * 8.0).abs())); // fade clouds in distance + final_color = mix( + final_color, + (Vec3::ONE + self.sun_col * 10.0) * 0.75 * saturate((ray_dir.y + 0.2) * 5.0), + saturate(n * 0.125), + ); + + // add the sun + final_color += self.get_sun_color_small(ray_dir, sun_dir); + final_color + } + + fn get_env_map_skyline(&self, ray_dir: Vec3, sun_dir: Vec3, height: f32) -> Vec3 { + let mut final_color: Vec3 = self.get_env_map(ray_dir, sun_dir); + + // Make a skyscraper skyline reflection. + let mut radial: f32 = ray_dir.z.atan2(ray_dir.x) * 4.0; + let mut skyline: f32 = + (((5.3456 * radial).sin() + (1.234 * radial).sin() + (2.177 * radial).sin()) * 0.6).floor(); + radial *= 4.0; + skyline += (((5.0 * radial).sin() + (1.234 * radial).sin() + (2.177 * radial).sin()) * 0.6) + .floor() + * 0.1; + let mut mask: f32 = saturate((ray_dir.y * 8.0 - skyline - 2.5 + height) * 24.0); + let vert: f32 = (radial * 32.0).sin().sign_gl() * 0.5 + 0.5; + let hor: f32 = (ray_dir.y * 256.0).sin().sign_gl() * 0.5 + 0.5; + mask = saturate(mask + (1.0 - hor * vert) * 0.05); + final_color = mix(final_color * vec3(0.1, 0.07, 0.05), final_color, mask); + + final_color + } +} + +// min function that supports materials in the y component +fn matmin(a: Vec2, b: Vec2) -> Vec2 { + if a.x < b.x { + a + } else { + b + } +} + +// ---- shapes defined by distance fields ---- +// See this site for a reference to more distance functions... +// http://iquilezles.org/www/articles/distfunctions/distfunctions.htm + +// signed box distance field +fn sd_box(p: Vec3, radius: Vec3) -> f32 { + let dist: Vec3 = p.abs() - radius; + dist.x.max(dist.y.max(dist.z)).min(0.0) + dist.max(Vec3::ZERO).length() +} + +// capped cylinder distance field +fn cyl_cap(p: Vec3, r: f32, len_rad: f32) -> f32 { + let mut a: f32 = p.xy().length() - r; + a = a.max(p.z.abs() - len_rad); + a +} + +// k should be negative. -4.0 works nicely. +// smooth blending function +fn smin(a: f32, b: f32, k: f32) -> f32 { + ((k * a).exp2() + (k * b).exp2()).log2() / k +} + +fn repeat(a: f32, len: f32) -> f32 { + a.rem_euclid(len) - 0.5 * len +} + +// Distance function that defines the car. +// Basically it's 2 boxes smooth-blended together and a mirrored cylinder for the wheels. +fn car(base_center: Vec3, unique: f32) -> Vec2 { + // bottom box + let mut car: f32 = sd_box( + base_center + vec3(0.0, -0.008, 0.001), + vec3(0.01, 0.00225, 0.0275), + ); + // top box smooth blended + car = smin( + car, + sd_box( + base_center + vec3(0.0, -0.016, 0.008), + vec3(0.005, 0.0005, 0.01), + ), + -160.0, + ); + // mirror the z axis to duplicate the cylinders for wheels + let mut w_mirror: Vec3 = base_center + vec3(0.0, -0.005, 0.0); + w_mirror.z = w_mirror.z.abs() - 0.02; + let wheels: f32 = cyl_cap((w_mirror).zyx(), 0.004, 0.0135); + // Set materials + let mut dist_and_mat: Vec2 = vec2(wheels, 3.0); // car wheels + // Car material is some big number that's unique to each car + // so I can have each car be a different color + dist_and_mat = matmin(dist_and_mat, vec2(car, 100000.0 + unique)); // car + dist_and_mat +} + +// How much space between voxel borders and geometry for voxel ray march optimization +const VOXEL_PAD: f32 = 0.2; +// p should be in [0..1] range on xz plane +// pint is an integer pair saying which city block you are on +fn city_block(p: Vec3, pint: Vec2) -> Vec2 { + // Get random numbers for this block by hashing the city block variable + let mut rand: Vec4 = Vec4::ZERO; + rand = hash22(pint).extend(rand.z).extend(rand.w); + rand = hash22(rand.xy()).extend(rand.x).extend(rand.y).zwxy(); + let mut rand2: Vec2 = hash22(rand.zw()); + + // Radius of the building + let mut base_rad: f32 = 0.2 + (rand.x) * 0.1; + base_rad = (base_rad * 20.0 + 0.5).floor() / 20.0; // try to snap this for window texture + + // make position relative to the middle of the block + let base_center: Vec3 = p - vec3(0.5, 0.0, 0.5); + let mut height: f32 = rand.w * rand.z + 0.1; // height of first building block + // Make the city skyline higher in the middle of the city. + let downtown: f32 = saturate(4.0 / pint.length()); + height *= downtown; + height *= 1.5 + (base_rad - 0.15) * 20.0; + height += 0.1; // minimum building height + //height += sin(iTime + pint.x); // animate the building heights if you're feeling silly + height = (height * 20.0).floor() * 0.05; // height is in floor units - each floor is 0.05 high. + let mut d: f32 = sd_box(base_center, vec3(base_rad, height, base_rad)); // large building piece + + // road + d = d.min(p.y); + + //if (length(pint.xy) > 8.0) return vec2(d, mat); // Hack to LOD in the distance + + // height of second building section + let mut height2: f32 = (rand.y * 2.0 - 1.0).max(0.0) * downtown; + height2 = (height2 * 20.0).floor() * 0.05; // floor units + rand2 = (rand2 * 20.0).floor() * 0.05; // floor units + // size pieces of building + d = d.min(sd_box( + base_center - vec3(0.0, height, 0.0), + vec3(base_rad, height2 - rand2.y, base_rad * 0.4), + )); + d = d.min(sd_box( + base_center - vec3(0.0, height, 0.0), + vec3(base_rad * 0.4, height2 - rand2.x, base_rad), + )); + // second building section + if rand2.y > 0.25 { + d = d.min(sd_box( + base_center - vec3(0.0, height, 0.0), + vec3(base_rad * 0.8, height2, base_rad * 0.8), + )); + // subtract off piece from top so it looks like there's a wall around the roof. + let mut top_width: f32 = base_rad; + if height2 > 0.0 { + top_width = base_rad * 0.8; + } + d = d.max(-sd_box( + base_center - vec3(0.0, height + height2, 0.0), + vec3(top_width - 0.0125, 0.015, top_width - 0.0125), + )); + } else { + // Cylinder top section of building + if height2 > 0.0 { + d = d.min(cyl_cap( + (base_center - vec3(0.0, height, 0.0)).xzy(), + base_rad * 0.8, + height2, + )); + } + } + // mini elevator shaft boxes on top of building + d = d.min(sd_box( + base_center + - vec3( + (rand.x - 0.5) * base_rad, + height + height2, + (rand.y - 0.5) * base_rad, + ), + vec3( + base_rad * 0.3 * rand.z, + 0.1 * rand2.y, + base_rad * 0.3 * rand2.x + 0.025, + ), + )); + // mirror another box (and scale it) so we get 2 boxes for the price of 1. + let mut box_pos: Vec3 = base_center + - vec3( + (rand2.x - 0.5) * base_rad, + height + height2, + (rand2.y - 0.5) * base_rad, + ); + let big: f32 = box_pos.x.sign_gl(); + box_pos.x = box_pos.x.abs() - 0.02 - base_rad * 0.3 * rand.w; + d = d.min(sd_box( + box_pos, + vec3( + base_rad * 0.3 * rand.w, + 0.07 * rand.y, + base_rad * 0.2 * rand.x + big * 0.025, + ), + )); + + // Put domes on some building tops for variety + if rand.y < 0.04 { + d = d.min((base_center - vec3(0.0, height, 0.0)).length() - base_rad * 0.8); + } + + //d = max(d, p.y); // flatten the city for debugging cars + + // Need to make a material variable. + let mut dist_and_mat: Vec2 = vec2(d, 0.0); + // sidewalk box with material + dist_and_mat = matmin( + dist_and_mat, + vec2(sd_box(base_center, vec3(0.35, 0.005, 0.35)), 1.0), + ); + + dist_and_mat +} + +impl State { + // This is the distance function that defines all the scene's geometry. + // The input is a position in space. + // The output is the distance to the nearest surface and a material index. + fn distance_to_object(&self, p: Vec3) -> Vec2 { + //p.y += noise2d((p.xz)*0.0625)*8.0; // Hills + let mut rep: Vec3 = p; + rep = (p.xz().fract_gl()).extend(rep.y).xzy(); // [0..1] for representing the position in the city block + let mut dist_and_mat: Vec2 = city_block(rep, p.xz().floor()); + + // Set up the cars. This is doing a lot of mirroring and repeating because I + // only want to do a single call to the car distance function for all the + // cars in the scene. And there's a lot of traffic! + let mut p2: Vec3 = p; + rep = p2; + let car_time: f32 = self.local_time * 0.2; // Speed of car driving + let mut cross_street: f32 = 1.0; // whether we are north/south or east/west + let mut repeat_dist: f32 = 0.25; // Car density bumper to bumper + // If we are going north/south instead of east/west (?) make cars that are + // stopped in the street so we don't have collisions. + if (rep.x.fract_gl() - 0.5).abs() < 0.35 { + p2.x += 0.05; + p2 = (p2.zx() * vec2(-1.0, 1.0)).extend(p2.y).xzy(); // Rotate 90 degrees + rep = p2.xz().extend(rep.y).xzy(); + cross_street = 0.0; + repeat_dist = 0.1; // Denser traffic on cross streets + } + + rep.z += p2.x.floor(); // shift so less repitition between parallel blocks + rep.x = repeat(p2.x - 0.5, 1.0); // repeat every block + rep.z *= rep.x.sign_gl(); // mirror but keep cars facing the right way + rep.x = (rep.x * rep.x.sign_gl()) - 0.09; + rep.z -= car_time * cross_street; // make cars move + let unique_id: f32 = (rep.z / repeat_dist).floor(); // each car gets a unique ID that we can use for colors + rep.z = repeat(rep.z, repeat_dist); // repeat the line of cars every quarter block + rep.x += hash11(unique_id) * 0.075 - 0.01; // nudge cars left and right to take both lanes + let mut front_back: f32 = hash11(unique_id * 0.987) * 0.18 - 0.09; + front_back *= (self.local_time * 2.0 + unique_id).sin(); + rep.z += front_back * cross_street; // nudge cars forward back for variation + let car_dist: Vec2 = car(rep, unique_id); // car distance function + + // Drop the cars in the scene with materials + dist_and_mat = matmin(dist_and_mat, car_dist); + + dist_and_mat + } +} + +// This basically makes a procedural texture map for the sides of the buildings. +// It makes a texture, a normal for normal mapping, and a mask for window reflection. +fn calc_windows( + block: Vec2, + pos: Vec3, + tex_color: &mut Vec3, + window_ref: &mut f32, + normal: &mut Vec3, +) { + let hue: Vec3 = vec3( + hash21(block) * 0.8, + hash21(block * 7.89) * 0.4, + hash21(block * 37.89) * 0.5, + ); + *tex_color += hue * 0.4; + *tex_color *= 0.75; + let mut window: f32 = 0.0; + window = window.max(mix( + 0.2, + 1.0, + ((pos.y * 20.0 - 0.35).fract_gl() * 2.0 + 0.1).floor(), + )); + if pos.y < 0.05 { + window = 1.0; + } + let mut win_width: f32 = hash21(block * 4.321) * 2.0; + if (win_width < 1.3) && (win_width >= 1.0) { + win_width = 1.3; + } + window = window.max(mix( + 0.2, + 1.0, + ((pos.x * 40.0 + 0.05).fract_gl() * win_width).floor(), + )); + window = window.max(mix( + 0.2, + 1.0, + ((pos.z * 40.0 + 0.05).fract_gl() * win_width).floor(), + )); + if window < 0.5 { + *window_ref += 1.0; + } + window *= hash21(block * 1.123); + *tex_color *= window; + + let wave: f32 = (((pos.y * 40.0 - 0.1) * PI).sin() * 0.505 - 0.5).floor() + 1.0; + normal.y -= (-1.0_f32).max(1.0_f32.min(-wave * 0.5)); + let mut pits: f32 = 1.0_f32.min(((pos.z * 80.0) * PI).sin().abs() * 4.0) - 1.0; + normal.z += pits * 0.25; + pits = 1.0_f32.min(((pos.x * 80.0) * PI).sin().abs() * 4.0) - 1.0; + normal.x += pits * 0.25; +} + +impl State { + // Input is UV coordinate of pixel to render. + // Output is RGB color. + fn ray_trace(&mut self, frag_coord: Vec2) -> Vec3 { + self.march_count = 0.0; + // -------------------------------- animate --------------------------------------- + self.sun_col = vec3(258.0, 248.0, 200.0) / 3555.0; + self.sun_dir = vec3(0.93, 1.0, 1.0).normalize(); + self.horizon_col = vec3(1.0, 0.95, 0.85) * 0.9; + self.sky_col = vec3(0.3, 0.5, 0.95); + self.exposure = 1.0; + self.fade = 1.0; + + let mut cam_pos: Vec3 = Vec3::ZERO; + let mut cam_up: Vec3 = Vec3::ZERO; + let mut cam_lookat: Vec3 = Vec3::ZERO; + // ------------------- Set up the camera rays for ray marching -------------------- + // Map uv to [-1.0..1.0] + let mut uv: Vec2 = frag_coord / self.inputs.resolution.xy() * 2.0 - Vec2::ONE; + uv /= 2.0; // zoom in + + if MANUAL_CAMERA { + // Camera up vector. + cam_up = vec3(0.0, 1.0, 0.0); + + // Camera lookat. + cam_lookat = vec3(0.0, 0.0, 0.0); + + // debugging camera + let mx: f32 = -self.inputs.mouse.x / self.inputs.resolution.x * PI * 2.0; // + localTime * 0.05; + let my: f32 = self.inputs.mouse.y / self.inputs.resolution.y * PI * 0.5 + PI / 2.0; // + sin(localTime * 0.3)*0.8+0.1;//*PI/2.01; + cam_pos = vec3(my.cos() * mx.cos(), my.sin(), my.cos() * mx.sin()) * 7.35; + //7.35 + } else { + // Do the camera fly-by animation and different scenes. + // Time variables for start and end of each scene + let t0: f32 = 0.0; + let t1: f32 = 8.0; + let t2: f32 = 14.0; + let t3: f32 = 24.0; + let t4: f32 = 38.0; + let t5: f32 = 56.0; + let t6: f32 = 58.0; + /*let t0: f32 = 0.0; + let t1: f32 = 0.0; + let t2: f32 = 0.0; + let t3: f32 = 0.0; + let t4: f32 = 0.0; + let t5: f32 = 16.0; + let t6: f32 = 18.0;*/ + // Repeat the animation after time t6 + self.local_time = (self.local_time / t6).fract_gl() * t6; + if self.local_time < t1 { + let time: f32 = self.local_time - t0; + let alpha: f32 = time / (t1 - t0); + self.fade = saturate(time); + self.fade *= saturate(t1 - self.local_time); + cam_pos = vec3(13.0, 3.3, -3.5); + cam_pos.x -= smoothstep(0.0, 1.0, alpha) * 4.8; + cam_up = vec3(0.0, 1.0, 0.0); + cam_lookat = vec3(0.0, 1.5, 1.5); + } else if self.local_time < t2 { + let time: f32 = self.local_time - t1; + let alpha: f32 = time / (t2 - t1); + self.fade = saturate(time); + self.fade *= saturate(t2 - self.local_time); + cam_pos = vec3(26.0, 0.05 + smoothstep(0.0, 1.0, alpha) * 0.4, 2.0); + cam_pos.z -= alpha * 2.8; + cam_up = vec3(0.0, 1.0, 0.0); + cam_lookat = vec3(cam_pos.x - 0.3, -8.15, -40.0); + + self.sun_dir = vec3(0.95, 0.6, 1.0).normalize(); + self.sun_col = vec3(258.0, 248.0, 160.0) / 3555.0; + self.exposure *= 0.7; + self.sky_col *= 1.5; + } else if self.local_time < t3 { + let time: f32 = self.local_time - t2; + let alpha: f32 = time / (t3 - t2); + self.fade = saturate(time); + self.fade *= saturate(t3 - self.local_time); + cam_pos = vec3(12.0, 6.3, -0.5); + cam_pos.y -= alpha * 5.5; + cam_pos.x = (alpha * 1.0).cos() * 5.2; + cam_pos.z = (alpha * 1.0).sin() * 5.2; + cam_up = vec3(0.0, 1.0, -0.5 + alpha * 0.5).normalize(); + cam_lookat = vec3(0.0, 1.0, -0.5); + } else if self.local_time < t4 { + let time: f32 = self.local_time - t3; + let alpha: f32 = time / (t4 - t3); + self.fade = saturate(time); + self.fade *= saturate(t4 - self.local_time); + cam_pos = vec3(2.15 - alpha * 0.5, 0.02, -1.0 - alpha * 0.2); + cam_pos.y += smoothstep(0.0, 1.0, alpha * alpha) * 3.4; + cam_up = vec3(0.0, 1.0, 0.0).normalize(); + cam_lookat = vec3(0.0, 0.5 + alpha, alpha * 5.0); + } else if self.local_time < t5 { + let time: f32 = self.local_time - t4; + let alpha: f32 = time / (t5 - t4); + self.fade = saturate(time); + self.fade *= saturate(t5 - self.local_time); + cam_pos = vec3(-2.0, 1.3 - alpha * 1.2, -10.5 - alpha * 0.5); + cam_up = vec3(0.0, 1.0, 0.0).normalize(); + cam_lookat = vec3(-2.0, 0.3 + alpha, -0.0); + self.sun_dir = vec3(0.5 - alpha * 0.6, 0.3 - alpha * 0.3, 1.0).normalize(); + self.sun_col = vec3(258.0, 148.0, 60.0) / 3555.0; + self.local_time *= 16.0; + self.exposure *= 0.4; + self.horizon_col = vec3(1.0, 0.5, 0.35) * 2.0; + self.sky_col = vec3(0.75, 0.5, 0.95); + } else if self.local_time < t6 { + self.fade = 0.0; + cam_pos = vec3(26.0, 100.0, 2.0); + cam_up = vec3(0.0, 1.0, 0.0); + cam_lookat = vec3(0.3, 0.15, 0.0); + } + } + + // Camera setup for ray tracing / marching + let cam_vec: Vec3 = (cam_lookat - cam_pos).normalize(); + let side_norm: Vec3 = cam_up.cross(cam_vec).normalize(); + let up_norm: Vec3 = cam_vec.cross(side_norm); + let world_facing: Vec3 = cam_pos + cam_vec; + let world_pix: Vec3 = world_facing + + uv.x * side_norm * (self.inputs.resolution.x / self.inputs.resolution.y) + + uv.y * up_norm; + let ray_vec: Vec3 = (world_pix - cam_pos).normalize(); + + // ----------------------------- Ray march the scene ------------------------------ + let mut dist_and_mat: Vec2 = Vec2::ZERO; // Distance and material + let mut t: f32 = 0.05; // + Hash2d(uv)*0.1; // random dither-fade things close to the camera + let max_depth: f32 = 45.0; // farthest distance rays will travel + let mut pos: Vec3 = Vec3::ZERO; + let small_val: f32 = 0.000625; + + // ray marching time + for _ in 0..250 { + // This is the count of the max times the ray actually marches. + self.march_count += 1.0; + // Step along the ray. + pos = cam_pos + ray_vec * t; + // This is _the_ function that defines the "distance field". + // It's really what makes the scene geometry. The idea is that the + // distance field returns the distance to the closest object, and then + // we know we are safe to "march" along the ray by that much distance + // without hitting anything. We repeat this until we get really close + // and then break because we have effectively hit the object. + dist_and_mat = self.distance_to_object(pos); + + // 2d voxel walk through the city blocks. + // The distance function is not continuous at city block boundaries, + // so we have to pause our ray march at each voxel boundary. + let mut walk: f32 = dist_and_mat.x; + let mut dx: f32 = -pos.x.fract_gl(); + if ray_vec.x > 0.0 { + dx = (-pos.x).fract_gl(); + } + let mut dz: f32 = -pos.z.fract_gl(); + if ray_vec.z > 0.0 { + dz = (-pos.z).fract_gl(); + } + let mut nearest_voxel: f32 = + (dx / ray_vec.x).fract_gl().min((dz / ray_vec.z).fract_gl()) + VOXEL_PAD; + nearest_voxel = VOXEL_PAD.max(nearest_voxel); // hack that assumes streets and sidewalks are this wide. + //nearestVoxel = nearestVoxel.max(t * 0.02); // hack to stop voxel walking in the distance. + walk = walk.min(nearest_voxel); + + // move down the ray a safe amount + t += walk; + // If we are very close to the object, let's call it a hit and exit this loop. + if (t > max_depth) || (dist_and_mat.x.abs() < small_val) { + break; + } + } + + // Ray trace a ground plane to infinity + let alpha: f32 = -cam_pos.y / ray_vec.y; + if (t > max_depth) && (ray_vec.y < -0.0) { + pos = (cam_pos.xz() + ray_vec.xz() * alpha).extend(pos.y).xzy(); + pos.y = -0.0; + t = alpha; + dist_and_mat.y = 0.0; + dist_and_mat.x = 0.0; + } + // -------------------------------------------------------------------------------- + // Now that we have done our ray marching, let's put some color on this geometry. + let mut final_color: Vec3; + + // If a ray actually hit the object, let's light it. + if (t <= max_depth) || (t == alpha) { + let dist: f32 = dist_and_mat.x; + // calculate the normal from the distance field. The distance field is a volume, so if you + // sample the current point and neighboring points, you can use the difference to get + // the normal. + let small_vec: Vec3 = vec3(small_val, 0.0, 0.0); + let normal_u: Vec3 = vec3( + dist - self.distance_to_object(pos - small_vec.xyy()).x, + dist - self.distance_to_object(pos - small_vec.yxy()).x, + dist - self.distance_to_object(pos - small_vec.yyx()).x, + ); + let mut normal: Vec3 = normal_u.normalize(); + + // calculate 2 ambient occlusion values. One for global stuff and one + // for local stuff + let mut ambient_s: f32 = 1.0; + ambient_s *= saturate(self.distance_to_object(pos + normal * 0.0125).x * 80.0); + ambient_s *= saturate(self.distance_to_object(pos + normal * 0.025).x * 40.0); + ambient_s *= saturate(self.distance_to_object(pos + normal * 0.05).x * 20.0); + ambient_s *= saturate(self.distance_to_object(pos + normal * 0.1).x * 10.0); + ambient_s *= saturate(self.distance_to_object(pos + normal * 0.2).x * 5.0); + ambient_s *= saturate(self.distance_to_object(pos + normal * 0.4).x * 2.5); + //ambientS *= saturate_f32(DistanceToObject(pos + normal * 0.8).x*1.25); + let mut ambient: f32 = ambient_s; // * saturate_f32(DistanceToObject(pos + normal * 1.6).x*1.25*0.5); + //ambient *= saturate_f32(DistanceToObject(pos + normal * 3.2)*1.25*0.25); + //ambient *= saturate_f32(DistanceToObject(pos + normal * 6.4)*1.25*0.125); + ambient = ambient.powf(0.5).max(0.025); // tone down ambient with a pow and min clamp it. + ambient = saturate(ambient); + + // calculate the reflection vector for highlights + let ref_: Vec3 = ray_vec.reflect(normal); + + // Trace a ray toward the sun for sun shadows + let mut sun_shadow: f32 = 1.0; + let mut iter: f32 = 0.01; + let nudge_pos: Vec3 = pos + normal * 0.002; // don't start tracing too close or inside the object + for _ in 0..40 { + let shadow_pos: Vec3 = nudge_pos + self.sun_dir * iter; + let temp_dist: f32 = self.distance_to_object(shadow_pos).x; + sun_shadow *= saturate(temp_dist * 150.0); // Shadow hardness + if temp_dist <= 0.0 { + break; + } + + let mut walk: f32 = temp_dist; + let mut dx: f32 = -shadow_pos.x.fract_gl(); + if self.sun_dir.x > 0.0 { + dx = (-shadow_pos.x).fract_gl(); + } + let mut dz: f32 = -shadow_pos.z.fract_gl(); + if self.sun_dir.z > 0.0 { + dz = (-shadow_pos.z).fract_gl(); + } + let mut nearest_voxel: f32 = (dx / self.sun_dir.x) + .fract_gl() + .min((dz / self.sun_dir.z).fract_gl()) + + small_val; + nearest_voxel = nearest_voxel.max(0.2); // hack that assumes streets and sidewalks are this wide. + walk = walk.min(nearest_voxel); + + iter += walk.max(0.01); + if iter > 4.5 { + break; + } + } + sun_shadow = saturate(sun_shadow); + + // make a few frequencies of noise to give it some texture + let mut n: f32 = 0.0; + n += noise(pos * 32.0); + n += noise(pos * 64.0); + n += noise(pos * 128.0); + n += noise(pos * 256.0); + n += noise(pos * 512.0); + n = mix(0.7, 0.95, n); + + // ------ Calculate texture color ------ + let block: Vec2 = pos.xz().floor(); + let mut tex_color: Vec3 = vec3(0.95, 1.0, 1.0); + tex_color *= 0.8; + let mut window_ref: f32 = 0.0; + // texture map the sides of buildings + if (normal.y < 0.1) && (dist_and_mat.y == 0.0) { + let posdx: Vec3 = pos.dfdx(); + let posdy: Vec3 = pos.dfdy(); + let _pos_grad: Vec3 = posdx * hash21(uv) + posdy * hash21(uv * 7.6543); + + // Quincunx antialias the building texture and normal map. + // I guess procedural textures are hard to mipmap. + let mut col_total: Vec3; + let mut col_temp: Vec3 = tex_color; + let mut n_temp: Vec3 = Vec3::ZERO; + calc_windows(block, pos, &mut col_temp, &mut window_ref, &mut n_temp); + col_total = col_temp; + + col_temp = tex_color; + calc_windows( + block, + pos + posdx * 0.666, + &mut col_temp, + &mut window_ref, + &mut n_temp, + ); + col_total += col_temp; + + col_temp = tex_color; + calc_windows( + block, + pos + posdx * 0.666 + posdy * 0.666, + &mut col_temp, + &mut window_ref, + &mut n_temp, + ); + col_total += col_temp; + + col_temp = tex_color; + calc_windows( + block, + pos + posdy * 0.666, + &mut col_temp, + &mut window_ref, + &mut n_temp, + ); + col_total += col_temp; + + col_temp = tex_color; + calc_windows( + block, + pos + posdx * 0.333 + posdy * 0.333, + &mut col_temp, + &mut window_ref, + &mut n_temp, + ); + col_total += col_temp; + + tex_color = col_total * 0.2; + window_ref *= 0.2; + + normal = (normal + n_temp * 0.2).normalize(); + } else { + // Draw the road + let xroad: f32 = ((pos.x + 0.5).fract_gl() - 0.5).abs(); + let zroad: f32 = ((pos.z + 0.5).fract_gl() - 0.5).abs(); + let road: f32 = saturate((xroad.min(zroad) - 0.143) * 480.0); + tex_color *= 1.0 - normal.y * 0.95 * hash21(block * 9.87) * road; // change rooftop color + tex_color *= mix(0.1, 1.0, road); + + // double yellow line in middle of road + let mut yellow_line: f32 = saturate(1.0 - (xroad.min(zroad) - 0.002) * 480.0); + yellow_line *= saturate((xroad.min(zroad) - 0.0005) * 480.0); + yellow_line *= saturate((xroad * xroad + zroad * zroad - 0.05) * 880.0); + tex_color = mix(tex_color, vec3(1.0, 0.8, 0.3), yellow_line); + + // white dashed lines on road + let mut white_line: f32 = saturate(1.0 - (xroad.min(zroad) - 0.06) * 480.0); + white_line *= saturate((xroad.min(zroad) - 0.056) * 480.0); + white_line *= saturate((xroad * xroad + zroad * zroad - 0.05) * 880.0); + white_line *= saturate(1.0 - ((zroad * 8.0).fract_gl() - 0.5) * 280.0); // dotted line + white_line *= saturate(1.0 - ((xroad * 8.0).fract_gl() - 0.5) * 280.0); + tex_color = mix(tex_color, Vec3::splat(0.5), white_line); + + white_line = saturate(1.0 - (xroad.min(zroad) - 0.11) * 480.0); + white_line *= saturate((xroad.min(zroad) - 0.106) * 480.0); + white_line *= saturate((xroad * xroad + zroad * zroad - 0.06) * 880.0); + tex_color = mix(tex_color, Vec3::splat(0.5), white_line); + + // crosswalk + let mut cross_walk: f32 = saturate(1.0 - ((xroad * 40.0).fract_gl() - 0.5) * 280.0); + cross_walk *= saturate((zroad - 0.15) * 880.0); + cross_walk *= saturate((-zroad + 0.21) * 880.0) * (1.0 - road); + cross_walk *= n * n; + tex_color = mix(tex_color, Vec3::splat(0.25), cross_walk); + cross_walk = saturate(1.0 - ((zroad * 40.0).fract_gl() - 0.5) * 280.0); + cross_walk *= saturate((xroad - 0.15) * 880.0); + cross_walk *= saturate((-xroad + 0.21) * 880.0) * (1.0 - road); + cross_walk *= n * n; + tex_color = mix(tex_color, Vec3::splat(0.25), cross_walk); + + { + // sidewalk cracks + let mut sidewalk: f32 = 1.0; + let mut block_size: Vec2 = Vec2::splat(100.0); + if pos.y > 0.1 { + block_size = vec2(10.0, 50.0); + } + //sidewalk *= (pos.x*block_size).sin()abs().pow(0.025); + //sidewalk *= (pos.z*block_size).sin()abs().pow(0.025); + sidewalk *= saturate(((pos.z * block_size.x).sin() * 800.0 / block_size.x).abs()); + sidewalk *= saturate(((pos.x * block_size.y).sin() * 800.0 / block_size.y).abs()); + sidewalk = saturate(mix(0.7, 1.0, sidewalk)); + sidewalk = saturate((1.0 - road) + sidewalk); + tex_color *= sidewalk; + } + } + // Car tires are almost black to not call attention to their ugly. + if dist_and_mat.y == 3.0 { + tex_color = Vec3::splat(0.05); + } + + // apply noise + tex_color *= Vec3::ONE * n * 0.05; + tex_color *= 0.7; + tex_color = saturate_vec3(tex_color); + + let mut window_mask: f32 = 0.0; + if dist_and_mat.y >= 100.0 { + // car texture and windows + tex_color = vec3( + hash11(dist_and_mat.y) * 1.0, + hash11(dist_and_mat.y * 8.765), + hash11(dist_and_mat.y * 17.731), + ) * 0.1; + tex_color = tex_color.abs().powf(0.2); // bias toward white + tex_color = Vec3::splat(0.25).max(tex_color); // not too saturated color. + tex_color.z = tex_color.y.min(tex_color.z); // no purple cars. just not realistic. :) + tex_color *= hash11(dist_and_mat.y * 0.789) * 0.15; + window_mask = saturate(((pos.y - 0.0175).abs() * 3800.0).max(0.0) - 10.0); + let dir_norm: Vec2 = normal.xz().normalize().abs(); + let mut pillars: f32 = saturate(1.0 - dir_norm.x.max(dir_norm.y)); + pillars = (pillars - 0.15).max(0.0).powf(0.125); + window_mask = window_mask.max(pillars); + tex_color *= window_mask; + } + + // ------ Calculate lighting color ------ + // Start with sun color, standard lighting equation, and shadow + let mut light_color: Vec3 = + Vec3::splat(100.0) * self.sun_col * saturate(self.sun_dir.dot(normal)) * sun_shadow; + // weighted average the near ambient occlusion with the far for just the right look + let ambient_avg: f32 = (ambient * 3.0 + ambient_s) * 0.25; + // Add sky color with ambient acclusion + light_color += (self.sky_col * saturate(normal.y * 0.5 + 0.5)) * ambient_avg.powf(0.35) * 2.5; + light_color *= 4.0; + + // finally, apply the light to the texture. + final_color = tex_color * light_color; + // Reflections for cars + if dist_and_mat.y >= 100.0 { + let mut yfade: f32 = 0.01_f32.max(1.0_f32.min(ref_.y * 100.0)); + // low-res way of making lines at the edges of car windows. Not sure I like it. + yfade *= saturate(1.0 - (window_mask.dfdx() * window_mask.dfdy()).abs() * 250.995); + final_color += self.get_env_map_skyline(ref_, self.sun_dir, pos.y - 1.5) + * 0.3 + * yfade + * sun_shadow.max(0.4); + final_color += + saturate_vec3(self.inputs.channel0.sample_cube(ref_).xyz() - Vec3::splat(0.35)) + * 0.15 + * sun_shadow.max(0.2); + } + // reflections for building windows + if window_ref != 0.0 { + final_color *= mix(1.0, 0.6, window_ref); + let yfade: f32 = 0.01_f32.max(1.0_f32.min(ref_.y * 100.0)); + final_color += self.get_env_map_skyline(ref_, self.sun_dir, pos.y - 0.5) + * 0.6 + * yfade + * sun_shadow.max(0.6) + * window_ref; //*(windowMask*0.5+0.5); + final_color += + saturate_vec3(self.inputs.channel0.sample_cube(ref_).xyz() - Vec3::splat(0.35)) + * 0.15 + * sun_shadow.max(0.25) + * window_ref; + } + final_color *= 0.9; + // fog that fades to reddish plus the sun color so that fog is brightest towards sun + let mut rv2: Vec3 = ray_vec; + rv2.y *= saturate(rv2.y.sign_gl()); + let mut fog_color: Vec3 = self.get_env_map(rv2, self.sun_dir); + fog_color = Vec3::splat(9.0).min(fog_color); + final_color = mix(fog_color, final_color, (-t * 0.02).exp()); + + // visualize length of gradient of distance field to check distance field correctness + //final_color = Vec3::splat(0.5) * (normalU.length() / smallVec.x); + //final_color = Vec3::splat(marchCount)/255.0; + } else { + // Our ray trace hit nothing, so draw sky. + final_color = self.get_env_map(ray_vec, self.sun_dir); + } + + // vignette? + final_color *= Vec3::ONE * saturate(1.0 - (uv / 2.5).length()); + final_color *= 1.3 * self.exposure; + + // output the final color without gamma correction - will do gamma later. + final_color.clamp(Vec3::ZERO, Vec3::ONE) * saturate(self.fade + 0.2) + } +} + +impl State { + // This function breaks the image down into blocks and scans + // through them, rendering 1 block at a time. It's for non- + // realtime things that take a long time to render. + + // This is the frame rate to render at. Too fast and you will + // miss some blocks. + const BLOCK_RATE: f32 = 20.0; + + fn block_render(&self, frag_coord: Vec2) { + // blockSize is how much it will try to render in 1 frame. + // adjust this smaller for more complex scenes, bigger for + // faster render times. + let block_size: f32 = 64.0; + // Make the block repeatedly scan across the image based on time. + let frame: f32 = (self.inputs.time * Self::BLOCK_RATE).floor(); + let block_res: Vec2 = (self.inputs.resolution.xy() / block_size).floor() + Vec2::ONE; + // ugly bug with mod. + //float blockX = mod(frame, blockRes.x); + let block_x: f32 = (frame / block_res.x).fract_gl() * block_res.x; + //float blockY = mod(floor(frame / blockRes.x), blockRes.y); + let block_y: f32 = ((frame / block_res.x).floor() / block_res.y).fract_gl() * block_res.y; + // Don't draw anything outside the current block. + if (frag_coord.x - block_x * block_size >= block_size) + || (frag_coord.x - (block_x - 1.0) * block_size < block_size) + || (frag_coord.y - block_y * block_size >= block_size) + || (frag_coord.y - (block_y - 1.0) * block_size < block_size) + { + discard(); + } + } + + fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { + if NON_REALTIME_HQ_RENDER { + // Optionally render a non-realtime scene with high quality + self.block_render(frag_coord); + } + + // Do a multi-pass render + let mut final_color: Vec3 = Vec3::ZERO; + if NON_REALTIME_HQ_RENDER { + for _ in 0..ANTIALIASING_SAMPLES { + let motion_blur_length_in_seconds: f32 = 1.0 / 60.0; + // Set this to the time in seconds of the frame to render. + self.local_time = FRAME_TO_RENDER_HQ; + // This line will motion-blur the renders + self.local_time += + hash11(v21(frag_coord + Vec2::splat(self.seed))) * motion_blur_length_in_seconds; + // Jitter the pixel position so we get antialiasing when we do multiple passes. + let mut jittered: Vec2 = frag_coord + + vec2( + hash21(frag_coord + Vec2::splat(self.seed)), + hash21(frag_coord * 7.234567 + Vec2::splat(self.seed)), + ); + // don't antialias if only 1 sample. + if ANTIALIASING_SAMPLES == 1 { + jittered = frag_coord; + }; + // Accumulate one pass of raytracing into our pixel value + final_color += self.ray_trace(jittered); + // Change the random seed for each pass. + self.seed *= 1.01234567; + } + // Average all accumulated pixel intensities + final_color /= ANTIALIASING_SAMPLES as f32; + } else { + // Regular real-time rendering + self.local_time = self.inputs.time; + final_color = self.ray_trace(frag_coord); + } + + *frag_color = final_color.clamp(Vec3::ZERO, Vec3::ONE).sqrt().extend(1.0); + } +} diff --git a/shaders/src/shaders/soft_shadow_variation.rs b/shaders/src/shaders/soft_shadow_variation.rs new file mode 100644 index 0000000..b084995 --- /dev/null +++ b/shaders/src/shaders/soft_shadow_variation.rs @@ -0,0 +1,228 @@ +//! Ported to Rust from +//! +//! Original comment: +//! ```glsl +//! // +//! // Testing Sebastian Aaltonen's soft shadow improvement +//! // +//! // The technique is based on estimating a better closest point in ray +//! // at each step by triangulating from the previous march step. +//! // +//! // More info about the technique at slide 39 of this presentation: +//! // https://www.dropbox.com/s/s9tzmyj0wqkymmz/Claybook_Simulation_Raytracing_GDC18.pptx?dl=0 +//! // +//! // Traditional technique: http://iquilezles.org/www/articles/rmshadows/rmshadows.htm +//! // +//! // Go to lines 54 to compare both. +//! ``` + +use crate::shader_prelude::*; + +pub const SHADER_DEFINITION: ShaderDefinition = ShaderDefinition { + name: "Soft Shadow Variation", +}; + +pub fn shader_fn(render_instruction: &ShaderInput, render_result: &mut ShaderResult) { + let color = &mut render_result.color; + let &ShaderInput { + resolution, + time, + frag_coord, + .. + } = render_instruction; + Inputs { resolution, time }.main_image(color, frag_coord); +} + +struct Inputs { + resolution: Vec3, + time: f32, +} + +// make this 1 is your machine is too slow +const AA: usize = 2; + +//------------------------------------------------------------------ + +fn sd_plane(p: Vec3) -> f32 { + p.y +} + +fn sd_box(p: Vec3, b: Vec3) -> f32 { + let d: Vec3 = p.abs() - b; + d.x.max(d.y.max(d.z)).min(0.0) + d.max(Vec3::ZERO).length() +} + +//------------------------------------------------------------------ + +fn map(pos: Vec3) -> f32 { + let qos: Vec3 = vec3((pos.x + 0.5).fract_gl() - 0.5, pos.y, pos.z); + sd_plane(pos - vec3(0.0, 0.00, 0.0)).min(sd_box(qos - vec3(0.0, 0.25, 0.0), vec3(0.2, 0.5, 0.2))) +} + +//------------------------------------------------------------------ + +fn calc_softshadow(ro: Vec3, rd: Vec3, mint: f32, tmax: f32, technique: bool) -> f32 { + let mut res: f32 = 1.0; + let mut t: f32 = mint; + let mut ph: f32 = 1e10; // big, such that y = 0 on the first iteration + for _ in 0..32 { + let h: f32 = map(ro + rd * t); + + // traditional technique + if !technique { + res = res.min(10.0 * h / t); + } + // improved technique + else { + // use this if you are getting artifact on the first iteration, or unroll the + // first iteration out of the loop + //float y = (i==0) ? 0.0 : h*h/(2.0*ph); + + let y: f32 = h * h / (2.0 * ph); + let d: f32 = (h * h - y * y).sqrt(); + res = res.min(10.0 * d / (t - y).max(0.0)); + ph = h; + } + t += h; + + if res < 0.0001 || t > tmax { + break; + } + } + res.clamp(0.0, 1.0) +} + +fn calc_normal(pos: Vec3) -> Vec3 { + let e: Vec2 = vec2(1.0, -1.0) * 0.5773 * 0.0005; + (e.xyy() * map(pos + e.xyy()) + + e.yyx() * map(pos + e.yyx()) + + e.yxy() * map(pos + e.yxy()) + + e.xxx() * map(pos + e.xxx())) + .normalize() +} + +fn cast_ray(ro: Vec3, rd: Vec3) -> f32 { + let mut tmin: f32 = 1.0; + let mut tmax: f32 = 20.0; + + if true { + // bounding volume + let tp1: f32 = (0.0 - ro.y) / rd.y; + if tp1 > 0.0 { + tmax = tmax.min(tp1); + } + let tp2: f32 = (1.0 - ro.y) / rd.y; + if tp2 > 0.0 { + if ro.y > 1.0 { + tmin = tmin.max(tp2); + } else { + tmax = tmax.min(tp2); + } + } + } + let mut t: f32 = tmin; + for _ in 0..64 { + let precis: f32 = 0.0005 * t; + let res: f32 = map(ro + rd * t); + if res < precis || t > tmax { + break; + } + t += res; + } + + if t > tmax { + t = -1.0; + } + t +} + +fn calc_ao(pos: Vec3, nor: Vec3) -> f32 { + let mut occ: f32 = 0.0; + let mut sca: f32 = 1.0; + for i in 0..5 { + let h: f32 = 0.001 + 0.15 * i as f32 / 4.0; + let d: f32 = map(pos + h * nor); + occ += (h - d) * sca; + sca *= 0.95; + } + (1.0 - 1.5 * occ).clamp(0.0, 1.0) +} + +fn render(ro: Vec3, rd: Vec3, technique: bool) -> Vec3 { + let mut col: Vec3 = Vec3::ZERO; + let t: f32 = cast_ray(ro, rd); + + if t > -0.5 { + let pos: Vec3 = ro + t * rd; + let nor: Vec3 = calc_normal(pos); + + // material + let mate: Vec3 = Vec3::splat(0.3); + // key light + let lig: Vec3 = vec3(-0.1, 0.3, 0.6).normalize(); + let hal: Vec3 = (lig - rd).normalize(); + let dif: f32 = nor.dot(lig).clamp(0.0, 1.0) * calc_softshadow(pos, lig, 0.01, 3.0, technique); + + let spe: f32 = nor.dot(hal).clamp(0.0, 1.0).powf(16.0) + * dif + * (0.04 + 0.96 * (1.0 + hal.dot(rd)).clamp(0.0, 1.0).powf(5.0)); + + col = mate * 4.0 * dif * vec3(1.00, 0.70, 0.5); + col += 12.0 * spe * vec3(1.00, 0.70, 0.5); + + // ambient light + let occ: f32 = calc_ao(pos, nor); + let amb: f32 = (0.5 + 0.5 * nor.y).clamp(0.0, 1.0); + col += mate * amb * occ * vec3(0.0, 0.08, 0.1); + + // fog + col *= (-0.0005 * t * t * t).exp(); + } + + col +} + +fn set_camera(ro: Vec3, ta: Vec3, cr: f32) -> Mat3 { + let cw: Vec3 = (ta - ro).normalize(); + let cp: Vec3 = vec3(cr.sin(), cr.cos(), 0.0); + let cu: Vec3 = cw.cross(cp).normalize(); + let cv: Vec3 = cu.cross(cw).normalize(); + Mat3::from_cols(cu, cv, cw) +} + +impl Inputs { + fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { + // camera + let an: f32 = 12.0 - (0.1 * self.time).sin(); + let ro: Vec3 = vec3(3.0 * (0.1 * an).cos(), 1.0, -3.0 * (0.1 * an).sin()); + let ta: Vec3 = vec3(0.0, -0.4, 0.0); + // camera-to-world transformation + let ca: Mat3 = set_camera(ro, ta, 0.0); + + let technique = (self.time / 2.0).fract_gl() > 0.5; + + let mut tot: Vec3 = Vec3::ZERO; + + for m in 0..AA { + for n in 0..AA { + // pixel coordinates + let o: Vec2 = vec2(m as f32, n as f32) / AA as f32 - Vec2::splat(0.5); + let p: Vec2 = (-self.resolution.xy() + 2.0 * (frag_coord + o)) / self.resolution.y; + + // ray direction + let rd: Vec3 = ca * p.extend(2.0).normalize(); + + // render + let mut col: Vec3 = render(ro, rd, technique); + + // gamma + col = col.powf(0.4545); + + tot += col; + } + } + tot /= (AA * AA) as f32; + + *frag_color = tot.extend(1.0); + } +} diff --git a/shaders/src/shaders/tileable_water_caustic.rs b/shaders/src/shaders/tileable_water_caustic.rs new file mode 100644 index 0000000..4903155 --- /dev/null +++ b/shaders/src/shaders/tileable_water_caustic.rs @@ -0,0 +1,91 @@ +//! Ported to Rust from +//! +//! Original comment: +//! ```glsl +//! // Found this on GLSL sandbox. I really liked it, changed a few things and made it tileable. +//! // :) +//! // by David Hoskins. +//! +//! +//! // Water turbulence effect by joltz0r 2013-07-04, improved 2013-07-07 +//! ``` + +use crate::shader_prelude::*; + +pub const SHADER_DEFINITION: ShaderDefinition = ShaderDefinition { + name: "Tileable Water Caustic", +}; + +pub fn shader_fn(render_instruction: &ShaderInput, render_result: &mut ShaderResult) { + let color = &mut render_result.color; + let &ShaderInput { + resolution, + time, + frag_coord, + .. + } = render_instruction; + Inputs { resolution, time }.main_image(color, frag_coord); +} + +struct Inputs { + resolution: Vec3, + time: f32, +} + +// Redefine below to see the tiling... +const SHOW_TILING: bool = false; + +use core::f32::consts::TAU; +const MAX_ITER: usize = 5; + +impl Inputs { + fn main_image(&self, frag_color: &mut Vec4, frag_coord: Vec2) { + let time: f32 = self.time * 0.5 + 23.0; + // uv should be the 0-1 uv of texture... + let mut uv: Vec2 = frag_coord / self.resolution.xy(); + + let p: Vec2 = if SHOW_TILING { + (uv * TAU * 2.0).rem_euclid(Vec2::splat(TAU)) - Vec2::splat(250.0) + } else { + (uv * TAU).rem_euclid(Vec2::splat(TAU)) - Vec2::splat(250.0) + }; + let mut i: Vec2 = p; + let mut c: f32 = 1.0; + let inten: f32 = 0.005; + + for n in 0..MAX_ITER { + let t: f32 = time * (1.1 - (3.5 / (n + 1) as f32)); + i = p + + vec2( + (t - i.x).cos() + (t + i.y).sin(), + (t - i.y).sin() + (t + i.x).cos(), + ); + c += 1.0 + / vec2( + p.x / ((i.x + t).sin() / inten), + p.y / ((i.y + t).cos() / inten), + ) + .length(); + } + c /= MAX_ITER as f32; + c = 1.17 - c.powf(1.4); + let mut colour: Vec3 = Vec3::splat(c.abs().powf(8.0)); + colour = (colour + vec3(0.0, 0.35, 0.5)).clamp(Vec3::ZERO, Vec3::ONE); + + if SHOW_TILING { + let pixel: Vec2 = 2.0 / self.resolution.xy(); + uv *= 2.0; + + let f: f32 = (self.time * 0.5).rem_euclid(2.0).floor(); // Flash value. + let first: Vec2 = pixel.step(uv) * f; // Rule out first screen pixels and flash. + uv = uv.fract_gl().step(pixel); // Add one line of pixels per tile. + colour = mix( + colour, + vec3(1.0, 1.0, 0.0), + (uv.x + uv.y) * first.x * first.y, + ); // Yellow line + } + + *frag_color = colour.extend(1.0); + } +} diff --git a/shaders/src/shaders/tokyo.rs b/shaders/src/shaders/tokyo.rs new file mode 100644 index 0000000..a077077 --- /dev/null +++ b/shaders/src/shaders/tokyo.rs @@ -0,0 +1,544 @@ +//! Ported to Rust from +//! +//! Original comment: +//! ```glsl +//! // Created by Reinder Nijhoff 2014 +//! // Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. +//! // @reindernijhoff +//! // +//! // https://www.shadertoy.com/view/Xtf3zn +//! // +//! // Tokyo by night in the rain. The car model is made by Eiffie +//! // (Shiny Toy': https://www.shadertoy.com/view/ldsGWB). +//! // I have never been in Tokyo btw. +//! ``` + +use crate::shader_prelude::*; + +pub const SHADER_DEFINITION: ShaderDefinition = ShaderDefinition { + name: "Tokyo by Night", +}; + +pub fn shader_fn(render_instruction: &ShaderInput, render_result: &mut ShaderResult) { + let color = &mut render_result.color; + let &ShaderInput { + resolution, + time, + frag_coord, + .. + } = render_instruction; + State::new(Inputs { resolution, time }).main_image(color, frag_coord); +} + +#[derive(Clone, Copy)] +struct Inputs { + resolution: Vec3, + time: f32, +} + +struct State { + inputs: Inputs, + + d_l: f32, // minimal distance to light + + int1: Vec3, + int2: Vec3, + nor1: Vec3, + lint1: Vec4, + lint2: Vec4, +} + +impl State { + #[must_use] + fn new(inputs: Inputs) -> Self { + Self { + inputs, + + d_l: 0.0, + + int1: Vec3::ZERO, + int2: Vec3::ZERO, + nor1: Vec3::ZERO, + lint1: Vec4::ZERO, + lint2: Vec4::ZERO, + } + } +} + +const BUMPMAP: bool = false; +const MARCHSTEPS: i32 = 128; +const MARCHSTEPSREFLECTION: i32 = 48; +const LIGHTINTENSITY: f32 = 5.0; + +//---------------------------------------------------------------------- + +//const backgroundColor: Vec3 = const_vec3!(0.2,0.4,0.6) * 0.09; +const BACKGROUND_COLOR: Vec3 = vec3(0.2 * 0.09, 0.4 * 0.09, 0.6 * 0.09); +impl State { + fn time(&self) -> f32 { + self.inputs.time + 90.0 + } +} + +//---------------------------------------------------------------------- +// noises + +fn hash(n: f32) -> f32 { + (n.sin() * 687.3123).fract_gl() +} + +fn noise(x: Vec2) -> f32 { + let p: Vec2 = x.floor(); + let mut f: Vec2 = x.fract_gl(); + f = f * f * (Vec2::splat(3.0) - 2.0 * f); + let n: f32 = p.x + p.y * 157.0; + mix( + mix(hash(n + 0.0), hash(n + 1.0), f.x), + mix(hash(n + 157.0), hash(n + 158.0), f.x), + f.y, + ) +} + +const M2: Mat2 = mat2(vec2(0.80, -0.60), vec2(0.60, 0.80)); + +fn fbm(mut p: Vec2) -> f32 { + let mut f: f32 = 0.0; + f += 0.5000 * noise(p); + p = M2 * p * 2.02; + f += 0.2500 * noise(p); + p = M2 * p * 2.03; + f += 0.1250 * noise(p); + // p = M2 * p * 2.01; + // f += 0.0625*noise( p ); + + f / 0.9375 +} + +//---------------------------------------------------------------------- +// distance primitives + +fn ud_round_box(p: Vec3, b: Vec3, r: f32) -> f32 { + (p.abs() - b).max(Vec3::ZERO).length() - r +} + +fn sd_box(p: Vec3, b: Vec3) -> f32 { + let d: Vec3 = p.abs() - b; + d.x.max(d.y.max(d.z)).min(0.0) + d.max(Vec3::ZERO).length() +} + +fn sd_sphere(p: Vec3, s: f32) -> f32 { + p.length() - s +} + +fn sd_cylinder(p: Vec3, h: Vec2) -> f32 { + let d: Vec2 = vec2(p.xz().length(), p.y).abs() - h; + d.x.max(d.y).min(0.0) + d.max(Vec2::ZERO).length() +} + +//---------------------------------------------------------------------- +// distance operators + +fn op_u(d2: f32, d1: f32) -> f32 { + d1.min(d2) +} +fn op_s(d2: f32, d1: f32) -> f32 { + (-d1).max(d2) +} +fn smin(a: f32, b: f32, k: f32) -> f32 { + //from iq + -((-k * a).exp() + (-k * b).exp()).ln() / k +} + +//---------------------------------------------------------------------- +// Map functions + +// car model is made by Eiffie +// shader 'Shiny Toy': https://www.shadertoy.com/view/ldsGWB + +fn map_car(p0: Vec3) -> f32 { + let mut p: Vec3 = p0 + vec3(0.0, 1.24, 0.0); + let mut r: f32 = p.yz().length(); + let mut d: f32 = vec3(p.x.abs() - 0.35, r - 1.92, -p.y + 1.4) + .max(Vec3::ZERO) + .length() + - 0.05; + d = d.max(p.z - 1.0); + p = p0 + vec3(0.0, -0.22, 0.39); + p = (p.xz().abs() - vec2(0.5300, 0.9600)).extend(p.y).xzy(); + p.x = p.x.abs(); + r = p.yz().length(); + d = smin( + d, + vec3(p.x - 0.08, r - 0.25, -p.y - 0.08) + .max(Vec3::ZERO) + .length() + - 0.04, + 8.0, + ); + d = d.max(-(p.x - 0.165).max(r - 0.24)); + let d2: f32 = vec2((p.x - 0.13).max(0.0), r - 0.2).length() - 0.02; + d = d.min(d2); + + d +} + +impl State { + fn map(&mut self, p: Vec3) -> f32 { + let mut pd: Vec3 = p; + let mut d: f32; + + pd.x = pd.x.abs(); + pd.z *= -p.x.sign_gl(); + + let ch: f32 = hash(((pd.z + 18.0 * self.time()) / 40.0).floor()); + let lh: f32 = hash((pd.z / 13.0).floor()); + + let pdm: Vec3 = vec3(pd.x, pd.y, pd.z.rem_euclid(10.0) - 5.0); + self.d_l = sd_sphere(vec3(pdm.x - 8.1, pdm.y - 4.5, pdm.z), 0.1); + + self.d_l = op_u( + self.d_l, + sd_box( + vec3(pdm.x - 12.0, pdm.y - 9.5 - lh, pd.z.rem_euclid(91.0) - 45.5), + vec3(0.2, 4.5, 0.2), + ), + ); + self.d_l = op_u( + self.d_l, + sd_box( + vec3( + pdm.x - 12.0, + pdm.y - 11.5 + lh, + pd.z.rem_euclid(31.0) - 15.5, + ), + vec3(0.22, 5.5, 0.2), + ), + ); + self.d_l = op_u( + self.d_l, + sd_box( + vec3(pdm.x - 12.0, pdm.y - 8.5 - lh, pd.z.rem_euclid(41.0) - 20.5), + vec3(0.24, 3.5, 0.2), + ), + ); + + if lh > 0.5 { + self.d_l = op_u( + self.d_l, + sd_box( + vec3(pdm.x - 12.5, pdm.y - 2.75 - lh, pd.z.rem_euclid(13.) - 6.5), + vec3(0.1, 0.25, 3.2), + ), + ); + } + + let pm: Vec3 = vec3( + (pd.x + (pd.z * 4.0).floor() * 0.25).rem_euclid(0.5) - 0.25, + pd.y, + pd.z.rem_euclid(0.25) - 0.125, + ); + d = ud_round_box(pm, vec3(0.245, 0.1, 0.12), 0.005); + + d = op_s(d, -(p.x + 8.)); + d = op_u(d, pd.y); + + let mut pdc: Vec3 = vec3( + pd.x, + pd.y, + (pd.z + 18.0 * self.time()).rem_euclid(40.0) - 20.0, + ); + + // car + if ch > 0.75 { + pdc.x += (ch - 0.75) * 4.0; + self.d_l = op_u( + self.d_l, + sd_sphere(vec3((pdc.x - 5.0).abs() - 1.05, pdc.y - 0.55, pdc.z), 0.025), + ); + self.d_l = op_u( + self.d_l, + sd_sphere( + vec3((pdc.x - 5.0).abs() - 1.2, pdc.y - 0.65, pdc.z + 6.05), + 0.025, + ), + ); + + d = op_u(d, map_car((pdc - vec3(5.0, -0.025, -2.3)) * 0.45)); + } + + d = op_u(d, 13. - pd.x); + d = op_u( + d, + sd_cylinder(vec3(pdm.x - 8.5, pdm.y, pdm.z), vec2(0.075, 4.5)), + ); + d = op_u(d, self.d_l); + + d + } + + //---------------------------------------------------------------------- + + fn calc_normal_simple(&mut self, pos: Vec3) -> Vec3 { + let e: Vec2 = vec2(1.0, -1.0) * 0.005; + + let n: Vec3 = (e.xyy() * self.map(pos + e.xyy()) + + e.yyx() * self.map(pos + e.yyx()) + + e.yxy() * self.map(pos + e.yxy()) + + e.xxx() * self.map(pos + e.xxx())) + .normalize(); + n + } + + fn calc_normal(&mut self, pos: Vec3) -> Vec3 { + let mut n: Vec3 = self.calc_normal_simple(pos); + if pos.y > 0.12 { + return n; + } + + if BUMPMAP { + let mut oc: Vec2 = + (vec2(pos.x + (pos.z * 4.0).floor() * 0.25, pos.z) * vec2(2.0, 4.0)).floor(); + + if pos.x.abs() < 8. { + oc = pos.xz(); + } + + let p: Vec3 = pos * 250.; + let mut xn: Vec3 = 0.05 * vec3(noise(p.xz()) - 0.5, 0., noise(p.zx()) - 0.5); + xn += 0.1 * vec3(fbm(oc) - 0.5, 0., fbm(oc.yx()) - 0.5); + + n = (xn + n).normalize(); + } + + n + } + + fn intersect(&mut self, mut ro: Vec3, mut rd: Vec3) -> f32 { + let precis: f32 = 0.001; + let mut h: f32; + let mut t: f32 = 0.0; + self.int1 = Vec3::splat(-500.0); + self.int2 = Vec3::splat(-500.0); + self.lint1 = Vec4::splat(-500.0); + self.lint2 = Vec4::splat(-500.0); + let mut mld: f32 = 100.0; + + for _ in 0..MARCHSTEPS { + h = self.map(ro + rd * t); + if self.d_l < mld { + mld = self.d_l; + self.lint1 = (ro + rd * t).extend(self.lint1.w); + self.lint1.w = self.d_l.abs(); + } + if h < precis { + self.int1 = ro + rd * t; + break; + } + t += h.max(precis * 2.); + } + + if self.int1.z < -400.0 || t > 300.0 { + // check intersection with plane y = -0.1; + let d: f32 = -(ro.y + 0.1) / rd.y; + if d > 0.0 { + self.int1 = ro + rd * d; + } else { + return -1.0; + } + } + + ro *= rd * t; + self.nor1 = self.calc_normal(ro); + ro += 0.01 * self.nor1; + rd = rd.reflect(self.nor1); + t = 0.0; + mld = 100.; + + for _ in 0..MARCHSTEPSREFLECTION { + h = self.map(ro + rd * t); + if self.d_l < mld { + mld = self.d_l; + self.lint2 = (ro + rd * t).extend(self.lint2.w); + self.lint2.w = self.d_l.abs(); + } + if h < precis { + self.int2 = ro + rd * t; + return 1.0; + } + t += h.max(precis * 2.0); + } + + 0.0 + } + + //---------------------------------------------------------------------- + // shade + + fn shade(&self, ro: Vec3, pos: Vec3, nor: Vec3) -> Vec3 { + let mut col: Vec3 = Vec3::splat(0.5); + + if pos.x.abs() > 15.0 || pos.x.abs() < 8.0 { + col = Vec3::splat(0.02); + } + if pos.y < 0.01 { + if self.int1.x.abs() < 0.1 { + col = Vec3::splat(0.9); + } + if (self.int1.x.abs() - 7.4).abs() < 0.1 { + col = Vec3::splat(0.9); + } + } + + let sh: f32 = nor.dot(vec3(-0.3, 0.3, -0.5).normalize()).clamp(0.0, 1.0); + col *= sh * BACKGROUND_COLOR; + + if pos.x.abs() > 12.9 && pos.y > 9. { + // windows + let ha: f32 = hash(133.1234 * (pos.y / 3.0).floor() + ((pos.z) / 3.0).floor()); + if ha > 0.95 { + col = ((ha - 0.95) * 10.) * vec3(1., 0.7, 0.4); + } + } + + col = mix( + BACKGROUND_COLOR, + col, + ((0.1 * pos.y).max(0.25) - 0.065 * pos.distance(ro)) + .min(0.0) + .exp(), + ); + + col + } + + fn get_light_color(&self, pos: Vec3) -> Vec3 { + let mut lcol: Vec3 = vec3(1.0, 0.7, 0.5); + + let mut pd: Vec3 = pos; + pd.x = pd.x.abs(); + pd.z *= -pos.x.sign_gl(); + + let ch: f32 = hash(((pd.z + 18. * self.time()) / 40.0).floor()); + let mut pdc: Vec3 = vec3( + pd.x, + pd.y, + (pd.z + 18.0 * self.time()).rem_euclid(40.0) - 20.0, + ); + + if ch > 0.75 { + // car + pdc.x += (ch - 0.75) * 4.; + if sd_sphere(vec3((pdc.x - 5.0).abs() - 1.05, pdc.y - 0.55, pdc.z), 0.25) < 2. { + lcol = vec3(1., 0.05, 0.01); + } + } + if pd.y > 2.0 && pd.x.abs() > 10.0 && pd.y < 5.0 { + let fl: f32 = (pd.z / 13.0).floor(); + lcol = 0.4 * lcol + 0.5 * vec3(hash(0.1562 + fl), hash(0.423134 + fl), 0.0); + } + if pd.x.abs() > 10. && pd.y > 5.0 { + let fl: f32 = (pd.z / 2.0).floor(); + lcol = 0.5 * lcol + 0.5 * vec3(hash(0.1562 + fl), hash(0.923134 + fl), hash(0.423134 + fl)); + } + + lcol + } +} + +fn random_start(co: Vec2) -> f32 { + 0.8 + 0.2 * hash(co.dot(vec2(123.42, 117.853)) * 412.453) +} + +impl State { + //---------------------------------------------------------------------- + // main + + fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { + let mut q: Vec2 = frag_coord / self.inputs.resolution.xy(); + let mut p: Vec2 = -Vec2::ONE + 2.0 * q; + p.x *= self.inputs.resolution.x / self.inputs.resolution.y; + + if q.y < 0.12 || q.y >= 0.88 { + *frag_color = vec4(0.0, 0.0, 0.0, 1.0); + return; + } + + // camera + let z: f32 = self.time(); + let x: f32 = -10.9 + 1. * (self.time() * 0.2).sin(); + let ro: Vec3 = vec3(x, 1.3 + 0.3 * (self.time() * 0.26).cos(), z - 1.); + let ta: Vec3 = vec3( + -8.0, + 1.3 + 0.4 * (self.time() * 0.26).cos(), + z + 4. + (self.time() * 0.04).cos(), + ); + + let ww: Vec3 = (ta - ro).normalize(); + let uu: Vec3 = ww.cross(vec3(0.0, 1.0, 0.0)).normalize(); + let vv: Vec3 = uu.cross(ww).normalize(); + let rd: Vec3 = (-p.x * uu + p.y * vv + 2.2 * ww).normalize(); + + let mut col: Vec3 = BACKGROUND_COLOR; + + // raymarch + let ints: f32 = self.intersect(ro + random_start(p) * rd, rd); + if ints > -0.5 { + // calculate reflectance + let mut r: f32 = 0.09; + if self.int1.y > 0.129 { + r = 0.025 * hash(133.1234 * (self.int1.y / 3.0).floor() + (self.int1.z / 3.0).floor()); + } + if self.int1.x.abs() < 8.0 { + if self.int1.y < 0.01 { + // road + r = 0.007 * fbm(self.int1.xz()); + } else { + // car + r = 0.02; + } + } + if self.int1.x.abs() < 0.1 { + r *= 4.0; + } + if (self.int1.x.abs() - 7.4).abs() < 0.1 { + r *= 4.0; + } + + r *= 2.0; + + col = self.shade(ro, self.int1, self.nor1); + + if ints > 0.5 { + let tmp = self.calc_normal_simple(self.int2); + col += r * self.shade(self.int1, self.int2, tmp); + } + if self.lint2.w > 0. { + col += (r * LIGHTINTENSITY * (-self.lint2.w * 7.0).exp()) + * self.get_light_color(self.lint2.xyz()); + } + } + + // Rain (by Dave Hoskins) + let st: Vec2 = + 256. * (p * vec2(0.5, 0.01) + vec2(self.time() * 0.13 - q.y * 0.6, self.time() * 0.13)); + let mut f: f32 = noise(st) * noise(st * 0.773) * 1.55; + f = 0.25 + (f.abs().powf(13.0) * 13.0).clamp(0.0, q.y * 0.14); + + if self.lint1.w > 0.0 { + col += + (f * LIGHTINTENSITY * (-self.lint1.w * 7.0).exp()) * self.get_light_color(self.lint1.xyz()); + } + + col += 0.25 * f * (Vec3::splat(0.2) + BACKGROUND_COLOR); + + // post processing + col = col.clamp(Vec3::ZERO, Vec3::ONE).powf(0.4545); + col *= 1.2 * vec3(1.0, 0.99, 0.95); + col = (1.06 * col - Vec3::splat(0.03)).clamp(Vec3::ZERO, Vec3::ONE); + q.y = (q.y - 0.12) * (1. / 0.76); + col *= Vec3::splat(0.5) + + Vec3::splat(0.5) * (16.0 * q.x * q.y * (1.0 - q.x) * (1.0 - q.y)).powf(0.1); + + *frag_color = col.extend(1.0); + } +} diff --git a/shaders/src/shaders/two_tweets.rs b/shaders/src/shaders/two_tweets.rs new file mode 100644 index 0000000..de90256 --- /dev/null +++ b/shaders/src/shaders/two_tweets.rs @@ -0,0 +1,50 @@ +//! Ported to Rust from +//! +//! Original comment: +//! ```glsl +//! // Created by inigo quilez - iq/2013 +//! // License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. +//! ``` + +use crate::shader_prelude::*; + +pub const SHADER_DEFINITION: ShaderDefinition = ShaderDefinition { name: "Two Tweets" }; + +pub fn shader_fn(render_instruction: &ShaderInput, render_result: &mut ShaderResult) { + let color = &mut render_result.color; + let &ShaderInput { + resolution, + time, + frag_coord, + .. + } = render_instruction; + Inputs { resolution, time }.main_image(color, frag_coord); +} + +struct Inputs { + resolution: Vec3, + time: f32, +} + +impl Inputs { + fn f(&self, mut p: Vec3) -> f32 { + p.z += self.time; + (Vec3::splat(0.05 * (9. * p.y * p.x).cos()) + vec3(p.x.cos(), p.y.cos(), p.z.cos()) + - Vec3::splat(0.1 * (9. * (p.z + 0.3 * p.x - p.y)).cos())) + .length() + - 1. + } + + fn main_image(&self, c: &mut Vec4, p: Vec2) { + let d: Vec3 = Vec3::splat(0.5) - p.extend(1.0) / self.resolution.x; + let mut o: Vec3 = d; + for _ in 0..128 { + o += self.f(o) * d; + } + *c = ((self.f(o - d) * vec3(0.0, 1.0, 2.0) + + Vec3::splat(self.f(o - Vec3::splat(0.6))) * vec3(2.0, 1.0, 0.0)) + .abs() + * (1. - 0.1 * o.z)) + .extend(1.0); + } +} diff --git a/shaders/src/shaders/voxel_pac_man.rs b/shaders/src/shaders/voxel_pac_man.rs new file mode 100644 index 0000000..d756af7 --- /dev/null +++ b/shaders/src/shaders/voxel_pac_man.rs @@ -0,0 +1,394 @@ +//! Ported to Rust from +//! +//! Original comment: +//! ```glsl +//! /////////////////////////////////////////////////////////////////////////////// +//! // // +//! // GGGG IIIII AAA N N TTTTT PPPP AAA CCCC M M AAA N N // +//! // G I A A NN N T P P A A C MM MM A A NN N // +//! // G GG I AAAAA N N N T PPPP AAAAA C --- M M M AAAAA N N N // +//! // G G I A A N NN T P A A C M M A A N NN // +//! // GGGG IIIII A A N N T P A A CCCC M M A A N N // +//! // // +//! /////////////////////////////////////////////////////////////////////////////// +//! */ +//! ``` + +use crate::shader_prelude::*; + +pub const SHADER_DEFINITION: ShaderDefinition = ShaderDefinition { + name: "Voxel PacMan", +}; + +pub fn shader_fn(render_instruction: &ShaderInput, render_result: &mut ShaderResult) { + let color = &mut render_result.color; + let &ShaderInput { + resolution, + time, + frag_coord, + mouse, + .. + } = render_instruction; + State::new(Inputs { + resolution, + time, + mouse, + }) + .main_image(color, frag_coord); +} + +struct Inputs { + resolution: Vec3, + time: f32, + mouse: Vec4, +} + +struct State { + inputs: Inputs, + + // Global variable to handle the glow effect + glow_counter: f32, +} + +impl State { + #[must_use] + fn new(inputs: Inputs) -> Self { + Self { + inputs, + + glow_counter: 0.0, + } + } +} + +// Parameters +const VOXEL_RESOLUTION: f32 = 1.5; +const VOXEL_LIGHTING: bool = true; +const SHADOW: bool = true; +const GROUND: bool = true; +const GHOST: bool = true; +const MOUSE: bool = true; +const HSV2RGB_FAST: bool = true; +const HSV2RGB_SAFE: bool = false; + +const CAMERA_FOCAL_LENGTH: f32 = 8.0; +const DELTA: f32 = 0.01; +const RAY_LENGTH_MAX: f32 = 500.0; +const RAY_STEP_MAX: u32 = 100; +const AMBIENT: f32 = 0.2; +const SPECULAR_POWER: f32 = 2.0; +const SPECULAR_INTENSITY: f32 = 0.3; +const SHADOW_LENGTH: f32 = 150.0; +const SHADOW_POWER: f32 = 3.0; +const FADE_POWER: f32 = 1.0; +const BACKGROUND: f32 = 0.7; +const GLOW: f32 = 0.4; +const GAMMA: f32 = 0.8; + +// PRNG (from https://www.shadertoy.com/view/4djSRW) +fn rand(mut seed: Vec3) -> f32 { + seed = (seed * vec3(5.3983, 5.4427, 6.9371)).fract_gl(); + seed += Vec3::splat(seed.yzx().dot(seed + vec3(21.5351, 14.3137, 15.3219))); + (seed.x * seed.y * seed.z * 95.4337).fract_gl() +} + +impl State { + // Distance to the voxel + fn dist_voxel(&mut self, p: Vec3) -> f32 { + // Update the glow counter + self.glow_counter += 1.0; + + // Rounded box + let voxel_radius: f32 = 0.25; + (p.abs() + Vec3::splat(-0.5 + voxel_radius)) + .max(Vec3::ZERO) + .length() + - voxel_radius + } + + // Distance to the scene and color of the closest point + fn dist_scene(&mut self, mut p: Vec3, p2: &mut Vec3) -> Vec2 { + // Update the glow counter + self.glow_counter += 1.0; + + // Scaling + p *= VOXEL_RESOLUTION; + + // Velocity, period of the waves, spacing of the gums + let mut v: f32 = VOXEL_RESOLUTION * (self.inputs.time * 100.0 / VOXEL_RESOLUTION).floor(); + let k1: f32 = 0.05; + let k2: f32 = 60.0; + + // Giant Pac-Man + let mut body: f32 = p.length(); + body = (body - 32.0).max(27.0 - body); + let mut eyes: f32 = 6.0 - vec3(p.x.abs() - 12.5, p.y - 19.5, p.z - 20.0).length(); + let mut mouth_angle: f32 = PI * (0.07 + 0.07 * (2.0 * v * PI / k2).cos()); + let mouth_top: f32 = p.dot(vec3(0.0, -mouth_angle.cos(), mouth_angle.sin())) - 2.0; + mouth_angle *= 2.5; + let mouth_bottom: f32 = p.dot(vec3(0.0, mouth_angle.cos(), mouth_angle.sin())); + let pac_man: f32 = body.max(eyes).max(mouth_top.min(mouth_bottom)); + let mut d: Vec2 = vec2(pac_man, 0.13); + *p2 = p; + + // Gums + let mut q: Vec3 = p.xy().extend((p.z + v).rem_euclid(k2) - k2 * 0.5); + let gum: f32 = (q.length() - 6.0).max(-p.z); + if gum < d.x { + d = vec2(gum, 0.35); + *p2 = q; + } + + // Ground + if GROUND { + q = p.xy().extend(p.z + v); + let ground: f32 = (q.y + 50.0 + 14.0 * (q.x * k1).cos() * (q.z * k1).cos()) * 0.7; + if ground < d.x { + d = vec2(ground, 0.55); + *p2 = q; + } + } + + // Ghost + if GHOST { + v = VOXEL_RESOLUTION + * ((130.0 + 60.0 * (self.inputs.time * 3.0).cos()) / VOXEL_RESOLUTION).floor(); + q = p.xy().extend(p.z + v); + body = vec3(q.x, (q.y - 4.0).max(0.0), q.z).length(); + body = (body - 28.0).max(22.0 - body); + eyes = 8.0 - vec3(q.x.abs() - 12.0, q.y - 10.0, q.z - 22.0).length(); + let bottom: f32 = (q.y + 28.0 + 4.0 * (p.x * 0.4).cos() * (p.z * 0.4).cos()) * 0.7; + let ghost: f32 = body.max(eyes).max(-bottom); + if ghost < d.x { + d = vec2(ghost, 0.76); + *p2 = q; + } + } + + // Scaling + d.x /= VOXEL_RESOLUTION; + d + } + + // Distance to the (voxelized?) scene + fn dist(&mut self, p: &mut Vec3, ray: Vec3, voxelized: f32, ray_length_max: f32) -> Vec4 { + let mut p2: Vec3 = *p; + let mut d: Vec2 = vec2(1.0 / 0.0, 0.0); + let mut ray_length: f32 = 0.0; + let mut ray_length_in_voxel: f32 = 0.0; + let mut ray_length_check_voxel: f32 = 0.0; + let ray_sign: Vec3 = ray.sign_gl(); + let ray_delta_voxel: Vec3 = ray_sign / ray; + for _ in 0..RAY_STEP_MAX { + if ray_length < ray_length_in_voxel { + d.x = self.dist_voxel((*p + Vec3::splat(0.5)).fract_gl() - Vec3::splat(0.5)); + if d.x < DELTA { + break; + } + } else if ray_length < ray_length_check_voxel { + let mut ray_delta: Vec3 = (Vec3::splat(0.5) + - ray_sign * ((*p + Vec3::splat(0.5)).fract_gl() - Vec3::splat(0.5))) + * ray_delta_voxel; + let d_next: f32 = ray_delta.x.min(ray_delta.y.min(ray_delta.z)); + d = self.dist_scene((*p + Vec3::splat(0.5)).floor(), &mut p2); + if d.x < 0.0 { + ray_delta = ray_delta_voxel - ray_delta; + d.x = (ray_length_in_voxel - ray_length) + .max(DELTA - ray_delta.x.min(ray_delta.y.min(ray_delta.z))); + ray_length_in_voxel = ray_length + d_next; + } else { + d.x = DELTA + d_next; + } + } else { + d = self.dist_scene(*p, &mut p2); + if voxelized > 0.5 { + if d.x < SQRT_3 * 0.5 { + ray_length_check_voxel = ray_length + d.x.abs() + SQRT_3 * 0.5; + d.x = (ray_length_in_voxel - ray_length + DELTA).max(d.x - SQRT_3 * 0.5); + } + } else if d.x < DELTA { + break; + } + } + ray_length += d.x; + if ray_length > ray_length_max { + break; + } + *p += d.x * ray; + } + d.extend(ray_length).extend(rand(p2)) + } + + // Normal at a given point + fn normal(&mut self, mut p: Vec3, voxelized: f32) -> Vec3 { + let h: Vec2 = vec2(DELTA, -DELTA); + let mut n: Vec3 = Vec3::ZERO; + if voxelized > 0.5 { + p = (p + Vec3::splat(0.5)).fract_gl() - Vec3::splat(0.5); + n = h.xxx() * self.dist_voxel(p + h.xxx()) + + h.xyy() * self.dist_voxel(p + h.xyy()) + + h.yxy() * self.dist_voxel(p + h.yxy()) + + h.yyx() * self.dist_voxel(p + h.yyx()); + } else { + n = h.xxx() * self.dist_scene(p + h.xxx(), &mut n).x + + h.xyy() * self.dist_scene(p + h.xyy(), &mut n).x + + h.yxy() * self.dist_scene(p + h.yxy(), &mut n).x + + h.yyx() * self.dist_scene(p + h.yyx(), &mut n).x; + } + n.normalize() + } +} + +// HSV to RGB +fn hsv2rgb(mut hsv: Vec3) -> Vec3 { + if HSV2RGB_SAFE { + hsv = hsv.yz().clamp(Vec2::ZERO, Vec2::ONE).extend(hsv.x).zxy(); + } + if HSV2RGB_FAST { + hsv.z + * (Vec3::ONE + + 0.5 + * hsv.y + * ((2.0 * PI * (Vec3::splat(hsv.x) + vec3(0.0, 2.0 / 3.0, 1.0 / 3.0))).cos() - Vec3::ONE)) + } else { + hsv.z + * (Vec3::ONE + + Vec3::splat(hsv.y) + * (((Vec3::splat(hsv.x) + vec3(0.0, 2.0 / 3.0, 1.0 / 3.0)).fract_gl() * 6.0 + - Vec3::splat(3.0)) + .abs() + - Vec3::splat(2.0)) + .clamp(-Vec3::ONE, Vec3::ZERO)) + } +} + +impl State { + // Main function + fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { + // Get the fragment + let frag: Vec2 = (2.0 * frag_coord - self.inputs.resolution.xy()) / self.inputs.resolution.y; + + // Define the rendering mode + let mut mode_timing: f32 = self.inputs.time * 0.234; + let mut mode_angle: f32 = PI * (self.inputs.time * 0.2).cos(); + mode_angle = (frag - vec2((self.inputs.time * 2.0).cos(), 0.0)) + .dot(vec2(mode_angle.cos(), mode_angle.sin())); + let mut mode_voxel: f32 = 0.5.step((mode_timing / (4.0 * PI)).fract_gl()); + mode_timing = mode_timing.cos(); + let mode_3d: f32 = smoothstep(0.8, 0.5, mode_timing); + let mode_switch: f32 = + smoothstep(0.995, 1.0, mode_timing) + smoothstep(0.02, 0.0, mode_angle.abs()) * mode_voxel; + mode_voxel = 1.0 + (0.0.step(mode_angle) - 1.0) * mode_voxel; + + // Define the ray corresponding to this fragment + let mut ray: Vec3 = frag + .extend(mix(8.0, CAMERA_FOCAL_LENGTH, mode_3d)) + .normalize(); + + // Compute the orientation of the camera + let mut yaw_angle: f32 = PI * (1.2 + 0.2 * (self.inputs.time * 0.5).cos()); + let mut pitch_angle: f32 = PI * (0.1 * (self.inputs.time * 0.3).cos() - 0.05); + if MOUSE { + yaw_angle += 4.0 * PI * self.inputs.mouse.x / self.inputs.resolution.x; + pitch_angle += PI * 0.3 * (1.0 - self.inputs.mouse.y / self.inputs.resolution.y); + } + yaw_angle = mix(PI * 1.5, yaw_angle, mode_3d); + pitch_angle *= mode_3d; + + let cos_yaw: f32 = yaw_angle.cos(); + let sin_yaw: f32 = yaw_angle.sin(); + let cos_pitch: f32 = pitch_angle.cos(); + let sin_pitch: f32 = pitch_angle.sin(); + + let mut camera_orientation: Mat3 = Mat3::ZERO; + camera_orientation.x_axis = vec3(cos_yaw, 0.0, -sin_yaw); + camera_orientation.y_axis = vec3(sin_yaw * sin_pitch, cos_pitch, cos_yaw * sin_pitch); + camera_orientation.z_axis = vec3(sin_yaw * cos_pitch, -sin_pitch, cos_yaw * cos_pitch); + + ray = camera_orientation * ray; + + // Compute the origin of the ray + let camera_dist: f32 = mix( + 300.0, + 195.0 + 150.0 * (self.inputs.time * 0.8).cos(), + mode_3d, + ); + let mut origin: Vec3 = (vec3(0.0, 0.0, 40.0 * (self.inputs.time * 0.2).sin()) + - camera_orientation.z_axis * camera_dist) + / VOXEL_RESOLUTION; + + // Compute the distance to the scene + self.glow_counter = 0.0; + let d: Vec4 = self.dist( + &mut origin, + ray, + mode_voxel, + RAY_LENGTH_MAX / VOXEL_RESOLUTION, + ); + + // Set the background color + let mut final_color: Vec3 = hsv2rgb(vec3( + 0.2 * ray.y + 0.4 * mode_voxel - 0.37, + 1.0, + mode_3d * BACKGROUND, + )); + let glow_color: Vec3 = GLOW * vec3(1.0, 0.3, 0.0) * self.glow_counter / RAY_STEP_MAX as f32; + if d.x < DELTA { + // Set the object color + let mut color: Vec3 = hsv2rgb(vec3( + d.y + 0.1 * d.w * mode_voxel, + 0.5 + 0.5 * mode_voxel, + 1.0, + )); + + // Lighting + let l: Vec3 = mix( + vec3(1.0, 0.0, 0.0), + vec3(1.25 + (self.inputs.time * 0.2).cos(), 1.0, 1.0), + mode_3d, + ) + .normalize(); + if VOXEL_LIGHTING { + if mode_voxel > 0.5 { + let n: Vec3 = self.normal((origin + Vec3::splat(0.5)).floor(), 0.0); + let diffuse: f32 = n.dot(l).max(0.0); + let specular: f32 = + ray.reflect(n).dot(l).max(0.0).powf(SPECULAR_POWER) * SPECULAR_INTENSITY; + color = (AMBIENT + diffuse) * color + Vec3::splat(specular); + } + } + let n: Vec3 = self.normal(origin, mode_voxel); + let mut diffuse: f32 = n.dot(l); + let mut specular: f32; + if diffuse < 0.0 { + diffuse = 0.0; + specular = 0.0; + } else { + specular = ray.reflect(n).dot(l).max(0.0).powf(SPECULAR_POWER) * SPECULAR_INTENSITY; + if SHADOW { + origin += n * DELTA * 2.0; + let mut shadow: Vec4 = + self.dist(&mut origin, l, mode_voxel, SHADOW_LENGTH / VOXEL_RESOLUTION); + if shadow.x < DELTA { + shadow.z = (shadow.z * VOXEL_RESOLUTION / SHADOW_LENGTH) + .min(1.0) + .powf(SHADOW_POWER); + diffuse *= shadow.z; + specular *= shadow.z; + } + } + } + color = (AMBIENT + diffuse) * color + Vec3::splat(specular); + + // Fading + let fade: f32 = (1.0 - d.z * VOXEL_RESOLUTION / RAY_LENGTH_MAX) + .max(0.0) + .powf(FADE_POWER); + final_color = mix(final_color, color, fade); + } + + // Set the fragment color + final_color = mix(final_color.powf(GAMMA) + glow_color, Vec3::ONE, mode_switch); + *frag_color = final_color.extend(1.0); + } +} diff --git a/shaders/src/shared_data.rs b/shaders/src/shared_data.rs new file mode 100644 index 0000000..5f0fc8b --- /dev/null +++ b/shaders/src/shared_data.rs @@ -0,0 +1,25 @@ +use bytemuck::{Pod, Zeroable}; + +#[repr(C)] +#[derive(Copy, Clone, Pod, Zeroable)] +#[allow(unused_attributes)] +pub struct ShaderConstants { + pub width: u32, + pub height: u32, + pub time: f32, + + // UI state + /// Boolean value indicating whether all shaders are rendered in a grid layout. + pub grid_mode: u32, + pub shader_to_show: u32, + + // Mouse state. + pub cursor_x: f32, + pub cursor_y: f32, + pub drag_start_x: f32, + pub drag_start_y: f32, + pub drag_end_x: f32, + pub drag_end_y: f32, + pub mouse_left_pressed: u32, + pub mouse_left_clicked: u32, +} diff --git a/shaders/src/skyline.rs b/shaders/src/skyline.rs deleted file mode 100644 index dbb55e5..0000000 --- a/shaders/src/skyline.rs +++ /dev/null @@ -1,1053 +0,0 @@ -//! Ported to Rust from -//! -//! Original comment: -//! ```glsl -//! /*-------------------------------------------------------------------------------------- -//! License CC0 - http://creativecommons.org/publicdomain/zero/1.0/ -//! To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. This software is distributed without any warranty. -//! ---------------------------------------------------------------------------------------- -//! ^ This means do ANYTHING YOU WANT with this code. Because we are programmers, not lawyers. -//! -Otavio Good -//! */ -//! ``` - -use crate::SampleCube; -use shared::*; -use spirv_std::arch::Derivative; -use spirv_std::glam::{vec2, vec3, Vec2, Vec2Swizzles, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles}; - -// Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but -// we tie #[no_std] above to the same condition, so it's fine. -#[cfg(target_arch = "spirv")] -use spirv_std::num_traits::Float; - -pub struct Inputs { - pub resolution: Vec3, - pub time: f32, - pub mouse: Vec4, - pub channel0: C0, -} - -pub struct State { - inputs: Inputs, - - // -------------------------------------------------------- - // These variables are for the non-realtime block renderer. - local_time: f32, - seed: f32, - - // Animation variables - fade: f32, - sun_dir: Vec3, - sun_col: Vec3, - exposure: f32, - sky_col: Vec3, - horizon_col: Vec3, - - // other - march_count: f32, -} - -impl State { - pub fn new(inputs: Inputs) -> Self { - State { - inputs, - - local_time: 0.0, - seed: 1.0, - - fade: 1.0, - sun_dir: Vec3::ZERO, - sun_col: Vec3::ZERO, - exposure: 1.0, - sky_col: Vec3::ZERO, - horizon_col: Vec3::ZERO, - - march_count: 0.0, - } - } -} - -// ---------------- Config ---------------- -// This is an option that lets you render high quality frames for screenshots. It enables -// stochastic antialiasing and motion blur automatically for any shader. -const NON_REALTIME_HQ_RENDER: bool = false; -const FRAME_TO_RENDER_HQ: f32 = 50.0; // Time in seconds of frame to render -const ANTIALIASING_SAMPLES: u32 = 16; // 16x antialiasing - too much might make the shader compiler angry. - -const MANUAL_CAMERA: bool = false; - -// ---- noise functions ---- -fn _v31(a: Vec3) -> f32 { - a.x + a.y * 37.0 + a.z * 521.0 -} -fn v21(a: Vec2) -> f32 { - a.x + a.y * 37.0 -} -fn hash11(a: f32) -> f32 { - (a.sin() * 10403.9).fract_gl() -} -fn hash21(uv: Vec2) -> f32 { - let f: f32 = uv.x + uv.y * 37.0; - (f.sin() * 104003.9).fract_gl() -} -fn hash22(uv: Vec2) -> Vec2 { - let f: f32 = uv.x + uv.y * 37.0; - (f.cos() * vec2(10003.579, 37049.7)).fract_gl() -} -fn _hash12(f: f32) -> Vec2 { - (f.cos() * vec2(10003.579, 37049.7)).fract_gl() -} -fn _hash1d(u: f32) -> f32 { - (u.sin() * 143.9).fract_gl() // scale this down to kill the jitters -} -fn hash2d(uv: Vec2) -> f32 { - let f: f32 = uv.x + uv.y * 37.0; - (f.sin() * 104003.9).fract_gl() -} -fn hash3d(uv: Vec3) -> f32 { - let f: f32 = uv.x + uv.y * 37.0 + uv.z * 521.0; - (f.sin() * 110003.9).fract_gl() -} -fn mix_p(f0: f32, f1: f32, a: f32) -> f32 { - mix(f0, f1, a * a * (3.0 - 2.0 * a)) -} -const ZERO_ONE: Vec2 = vec2(0.0, 1.0); -fn noise2d(uv: Vec2) -> f32 { - let fr: Vec2 = uv.fract_gl(); - let fl: Vec2 = uv.floor(); - let h00: f32 = hash2d(fl); - let h10: f32 = hash2d(fl + ZERO_ONE.yx()); - let h01: f32 = hash2d(fl + ZERO_ONE); - let h11: f32 = hash2d(fl + ZERO_ONE.yy()); - mix_p(mix_p(h00, h10, fr.x), mix_p(h01, h11, fr.x), fr.y) -} -fn noise(uv: Vec3) -> f32 { - let fr: Vec3 = uv.fract_gl(); - let fl: Vec3 = uv.floor(); - let h000: f32 = hash3d(fl); - let h100: f32 = hash3d(fl + ZERO_ONE.yxx()); - let h010: f32 = hash3d(fl + ZERO_ONE.xyx()); - let h110: f32 = hash3d(fl + ZERO_ONE.yyx()); - let h001: f32 = hash3d(fl + ZERO_ONE.xxy()); - let h101: f32 = hash3d(fl + ZERO_ONE.yxy()); - let h011: f32 = hash3d(fl + ZERO_ONE.xyy()); - let h111: f32 = hash3d(fl + ZERO_ONE.yyy()); - mix_p( - mix_p(mix_p(h000, h100, fr.x), mix_p(h010, h110, fr.x), fr.y), - mix_p(mix_p(h001, h101, fr.x), mix_p(h011, h111, fr.x), fr.y), - fr.z, - ) -} - -const PI: f32 = 3.14159265; - -fn saturate_vec3(a: Vec3) -> Vec3 { - a.clamp(Vec3::ZERO, Vec3::ONE) -} -fn _saturate_vec2(a: Vec2) -> Vec2 { - a.clamp(Vec2::ZERO, Vec2::ONE) -} -fn saturate(a: f32) -> f32 { - a.clamp(0.0, 1.0) -} - -impl State { - // This function basically is a procedural environment map that makes the sun - fn get_sun_color_small(&self, ray_dir: Vec3, sun_dir: Vec3) -> Vec3 { - let local_ray: Vec3 = ray_dir.normalize(); - let dist: f32 = 1.0 - (local_ray.dot(sun_dir) * 0.5 + 0.5); - let mut sun_intensity: f32 = 0.05 / dist; - sun_intensity += (-dist * 150.0).exp() * 7000.0; - sun_intensity = sun_intensity.min(40000.0); - self.sun_col * sun_intensity * 0.025 - } - - fn get_env_map(&self, ray_dir: Vec3, sun_dir: Vec3) -> Vec3 { - // fade the sky color, multiply sunset dimming - let mut final_color: Vec3 = mix( - self.horizon_col, - self.sky_col, - saturate(ray_dir.y).powf(0.47), - ) * 0.95; - // make clouds - just a horizontal plane with noise - let mut n: f32 = noise2d(ray_dir.xz() / ray_dir.y * 1.0); - n += noise2d(ray_dir.xz() / ray_dir.y * 2.0) * 0.5; - n += noise2d(ray_dir.xz() / ray_dir.y * 4.0) * 0.25; - n += noise2d(ray_dir.xz() / ray_dir.y * 8.0) * 0.125; - n = n.abs().powf(3.0); - n = mix(n * 0.2, n, saturate((ray_dir.y * 8.0).abs())); // fade clouds in distance - final_color = mix( - final_color, - (Vec3::ONE + self.sun_col * 10.0) * 0.75 * saturate((ray_dir.y + 0.2) * 5.0), - saturate(n * 0.125), - ); - - // add the sun - final_color += self.get_sun_color_small(ray_dir, sun_dir); - final_color - } - - fn get_env_map_skyline(&self, ray_dir: Vec3, sun_dir: Vec3, height: f32) -> Vec3 { - let mut final_color: Vec3 = self.get_env_map(ray_dir, sun_dir); - - // Make a skyscraper skyline reflection. - let mut radial: f32 = ray_dir.z.atan2(ray_dir.x) * 4.0; - let mut skyline: f32 = - (((5.3456 * radial).sin() + (1.234 * radial).sin() + (2.177 * radial).sin()) * 0.6) - .floor(); - radial *= 4.0; - skyline += (((5.0 * radial).sin() + (1.234 * radial).sin() + (2.177 * radial).sin()) * 0.6) - .floor() - * 0.1; - let mut mask: f32 = saturate((ray_dir.y * 8.0 - skyline - 2.5 + height) * 24.0); - let vert: f32 = (radial * 32.0).sin().sign_gl() * 0.5 + 0.5; - let hor: f32 = (ray_dir.y * 256.0).sin().sign_gl() * 0.5 + 0.5; - mask = saturate(mask + (1.0 - hor * vert) * 0.05); - final_color = mix(final_color * vec3(0.1, 0.07, 0.05), final_color, mask); - - final_color - } -} - -// min function that supports materials in the y component -fn matmin(a: Vec2, b: Vec2) -> Vec2 { - if a.x < b.x { - a - } else { - b - } -} - -// ---- shapes defined by distance fields ---- -// See this site for a reference to more distance functions... -// http://iquilezles.org/www/articles/distfunctions/distfunctions.htm - -// signed box distance field -fn sd_box(p: Vec3, radius: Vec3) -> f32 { - let dist: Vec3 = p.abs() - radius; - dist.x.max(dist.y.max(dist.z)).min(0.0) + dist.max(Vec3::ZERO).length() -} - -// capped cylinder distance field -fn cyl_cap(p: Vec3, r: f32, len_rad: f32) -> f32 { - let mut a: f32 = p.xy().length() - r; - a = a.max(p.z.abs() - len_rad); - a -} - -// k should be negative. -4.0 works nicely. -// smooth blending function -fn smin(a: f32, b: f32, k: f32) -> f32 { - ((k * a).exp2() + (k * b).exp2()).log2() / k -} - -fn repeat(a: f32, len: f32) -> f32 { - a.rem_euclid(len) - 0.5 * len -} - -// Distance function that defines the car. -// Basically it's 2 boxes smooth-blended together and a mirrored cylinder for the wheels. -fn car(base_center: Vec3, unique: f32) -> Vec2 { - // bottom box - let mut car: f32 = sd_box( - base_center + vec3(0.0, -0.008, 0.001), - vec3(0.01, 0.00225, 0.0275), - ); - // top box smooth blended - car = smin( - car, - sd_box( - base_center + vec3(0.0, -0.016, 0.008), - vec3(0.005, 0.0005, 0.01), - ), - -160.0, - ); - // mirror the z axis to duplicate the cylinders for wheels - let mut w_mirror: Vec3 = base_center + vec3(0.0, -0.005, 0.0); - w_mirror.z = w_mirror.z.abs() - 0.02; - let wheels: f32 = cyl_cap((w_mirror).zyx(), 0.004, 0.0135); - // Set materials - let mut dist_and_mat: Vec2 = vec2(wheels, 3.0); // car wheels - // Car material is some big number that's unique to each car - // so I can have each car be a different color - dist_and_mat = matmin(dist_and_mat, vec2(car, 100000.0 + unique)); // car - dist_and_mat -} - -// How much space between voxel borders and geometry for voxel ray march optimization -const VOXEL_PAD: f32 = 0.2; -// p should be in [0..1] range on xz plane -// pint is an integer pair saying which city block you are on -fn city_block(p: Vec3, pint: Vec2) -> Vec2 { - // Get random numbers for this block by hashing the city block variable - let mut rand: Vec4 = Vec4::ZERO; - rand = hash22(pint).extend(rand.z).extend(rand.w); - rand = hash22(rand.xy()).extend(rand.x).extend(rand.y).zwxy(); - let mut rand2: Vec2 = hash22(rand.zw()); - - // Radius of the building - let mut base_rad: f32 = 0.2 + (rand.x) * 0.1; - base_rad = (base_rad * 20.0 + 0.5).floor() / 20.0; // try to snap this for window texture - - // make position relative to the middle of the block - let base_center: Vec3 = p - vec3(0.5, 0.0, 0.5); - let mut height: f32 = rand.w * rand.z + 0.1; // height of first building block - // Make the city skyline higher in the middle of the city. - let downtown: f32 = saturate(4.0 / pint.length()); - height *= downtown; - height *= 1.5 + (base_rad - 0.15) * 20.0; - height += 0.1; // minimum building height - //height += sin(iTime + pint.x); // animate the building heights if you're feeling silly - height = (height * 20.0).floor() * 0.05; // height is in floor units - each floor is 0.05 high. - let mut d: f32 = sd_box(base_center, vec3(base_rad, height, base_rad)); // large building piece - - // road - d = d.min(p.y); - - //if (length(pint.xy) > 8.0) return vec2(d, mat); // Hack to LOD in the distance - - // height of second building section - let mut height2: f32 = (rand.y * 2.0 - 1.0).max(0.0) * downtown; - height2 = (height2 * 20.0).floor() * 0.05; // floor units - rand2 = (rand2 * 20.0).floor() * 0.05; // floor units - // size pieces of building - d = d.min(sd_box( - base_center - vec3(0.0, height, 0.0), - vec3(base_rad, height2 - rand2.y, base_rad * 0.4), - )); - d = d.min(sd_box( - base_center - vec3(0.0, height, 0.0), - vec3(base_rad * 0.4, height2 - rand2.x, base_rad), - )); - // second building section - if rand2.y > 0.25 { - d = d.min(sd_box( - base_center - vec3(0.0, height, 0.0), - vec3(base_rad * 0.8, height2, base_rad * 0.8), - )); - // subtract off piece from top so it looks like there's a wall around the roof. - let mut top_width: f32 = base_rad; - if height2 > 0.0 { - top_width = base_rad * 0.8; - } - d = d.max(-sd_box( - base_center - vec3(0.0, height + height2, 0.0), - vec3(top_width - 0.0125, 0.015, top_width - 0.0125), - )); - } else { - // Cylinder top section of building - if height2 > 0.0 { - d = d.min(cyl_cap( - (base_center - vec3(0.0, height, 0.0)).xzy(), - base_rad * 0.8, - height2, - )); - } - } - // mini elevator shaft boxes on top of building - d = d.min(sd_box( - base_center - - vec3( - (rand.x - 0.5) * base_rad, - height + height2, - (rand.y - 0.5) * base_rad, - ), - vec3( - base_rad * 0.3 * rand.z, - 0.1 * rand2.y, - base_rad * 0.3 * rand2.x + 0.025, - ), - )); - // mirror another box (and scale it) so we get 2 boxes for the price of 1. - let mut box_pos: Vec3 = base_center - - vec3( - (rand2.x - 0.5) * base_rad, - height + height2, - (rand2.y - 0.5) * base_rad, - ); - let big: f32 = box_pos.x.sign_gl(); - box_pos.x = box_pos.x.abs() - 0.02 - base_rad * 0.3 * rand.w; - d = d.min(sd_box( - box_pos, - vec3( - base_rad * 0.3 * rand.w, - 0.07 * rand.y, - base_rad * 0.2 * rand.x + big * 0.025, - ), - )); - - // Put domes on some building tops for variety - if rand.y < 0.04 { - d = d.min((base_center - vec3(0.0, height, 0.0)).length() - base_rad * 0.8); - } - - //d = max(d, p.y); // flatten the city for debugging cars - - // Need to make a material variable. - let mut dist_and_mat: Vec2 = vec2(d, 0.0); - // sidewalk box with material - dist_and_mat = matmin( - dist_and_mat, - vec2(sd_box(base_center, vec3(0.35, 0.005, 0.35)), 1.0), - ); - - dist_and_mat -} - -impl State { - // This is the distance function that defines all the scene's geometry. - // The input is a position in space. - // The output is the distance to the nearest surface and a material index. - fn distance_to_object(&self, p: Vec3) -> Vec2 { - //p.y += noise2d((p.xz)*0.0625)*8.0; // Hills - let mut rep: Vec3 = p; - rep = (p.xz().fract_gl()).extend(rep.y).xzy(); // [0..1] for representing the position in the city block - let mut dist_and_mat: Vec2 = city_block(rep, p.xz().floor()); - - // Set up the cars. This is doing a lot of mirroring and repeating because I - // only want to do a single call to the car distance function for all the - // cars in the scene. And there's a lot of traffic! - let mut p2: Vec3 = p; - rep = p2; - let car_time: f32 = self.local_time * 0.2; // Speed of car driving - let mut cross_street: f32 = 1.0; // whether we are north/south or east/west - let mut repeat_dist: f32 = 0.25; // Car density bumper to bumper - // If we are going north/south instead of east/west (?) make cars that are - // stopped in the street so we don't have collisions. - if (rep.x.fract_gl() - 0.5).abs() < 0.35 { - p2.x += 0.05; - p2 = (p2.zx() * vec2(-1.0, 1.0)).extend(p2.y).xzy(); // Rotate 90 degrees - rep = p2.xz().extend(rep.y).xzy(); - cross_street = 0.0; - repeat_dist = 0.1; // Denser traffic on cross streets - } - - rep.z += p2.x.floor(); // shift so less repitition between parallel blocks - rep.x = repeat(p2.x - 0.5, 1.0); // repeat every block - rep.z = rep.z * rep.x.sign_gl(); // mirror but keep cars facing the right way - rep.x = (rep.x * rep.x.sign_gl()) - 0.09; - rep.z -= car_time * cross_street; // make cars move - let unique_id: f32 = (rep.z / repeat_dist).floor(); // each car gets a unique ID that we can use for colors - rep.z = repeat(rep.z, repeat_dist); // repeat the line of cars every quarter block - rep.x += hash11(unique_id) * 0.075 - 0.01; // nudge cars left and right to take both lanes - let mut front_back: f32 = hash11(unique_id * 0.987) * 0.18 - 0.09; - front_back *= (self.local_time * 2.0 + unique_id).sin(); - rep.z += front_back * cross_street; // nudge cars forward back for variation - let car_dist: Vec2 = car(rep, unique_id); // car distance function - - // Drop the cars in the scene with materials - dist_and_mat = matmin(dist_and_mat, car_dist); - - dist_and_mat - } -} - -// This basically makes a procedural texture map for the sides of the buildings. -// It makes a texture, a normal for normal mapping, and a mask for window reflection. -fn calc_windows( - block: Vec2, - pos: Vec3, - tex_color: &mut Vec3, - window_ref: &mut f32, - normal: &mut Vec3, -) { - let hue: Vec3 = vec3( - hash21(block) * 0.8, - hash21(block * 7.89) * 0.4, - hash21(block * 37.89) * 0.5, - ); - *tex_color += hue * 0.4; - *tex_color *= 0.75; - let mut window: f32 = 0.0; - window = window.max(mix( - 0.2, - 1.0, - ((pos.y * 20.0 - 0.35).fract_gl() * 2.0 + 0.1).floor(), - )); - if pos.y < 0.05 { - window = 1.0; - } - let mut win_width: f32 = hash21(block * 4.321) * 2.0; - if (win_width < 1.3) && (win_width >= 1.0) { - win_width = 1.3; - } - window = window.max(mix( - 0.2, - 1.0, - ((pos.x * 40.0 + 0.05).fract_gl() * win_width).floor(), - )); - window = window.max(mix( - 0.2, - 1.0, - ((pos.z * 40.0 + 0.05).fract_gl() * win_width).floor(), - )); - if window < 0.5 { - *window_ref += 1.0; - } - window *= hash21(block * 1.123); - *tex_color *= window; - - let wave: f32 = (((pos.y * 40.0 - 0.1) * PI).sin() * 0.505 - 0.5).floor() + 1.0; - normal.y -= (-1.0_f32).max(1.0_f32.min(-wave * 0.5)); - let mut pits: f32 = 1.0_f32.min(((pos.z * 80.0) * PI).sin().abs() * 4.0) - 1.0; - normal.z += pits * 0.25; - pits = 1.0_f32.min(((pos.x * 80.0) * PI).sin().abs() * 4.0) - 1.0; - normal.x += pits * 0.25; -} - -impl State { - // Input is UV coordinate of pixel to render. - // Output is RGB color. - fn ray_trace(&mut self, frag_coord: Vec2) -> Vec3 { - self.march_count = 0.0; - // -------------------------------- animate --------------------------------------- - self.sun_col = vec3(258.0, 248.0, 200.0) / 3555.0; - self.sun_dir = vec3(0.93, 1.0, 1.0).normalize(); - self.horizon_col = vec3(1.0, 0.95, 0.85) * 0.9; - self.sky_col = vec3(0.3, 0.5, 0.95); - self.exposure = 1.0; - self.fade = 1.0; - - let mut cam_pos: Vec3 = Vec3::ZERO; - let mut cam_up: Vec3 = Vec3::ZERO; - let mut cam_lookat: Vec3 = Vec3::ZERO; - // ------------------- Set up the camera rays for ray marching -------------------- - // Map uv to [-1.0..1.0] - let mut uv: Vec2 = frag_coord / self.inputs.resolution.xy() * 2.0 - Vec2::ONE; - uv /= 2.0; // zoom in - - if MANUAL_CAMERA { - // Camera up vector. - cam_up = vec3(0.0, 1.0, 0.0); - - // Camera lookat. - cam_lookat = vec3(0.0, 0.0, 0.0); - - // debugging camera - let mx: f32 = -self.inputs.mouse.x / self.inputs.resolution.x * PI * 2.0; // + localTime * 0.05; - let my: f32 = self.inputs.mouse.y / self.inputs.resolution.y * 3.14 * 0.5 + PI / 2.0; // + sin(localTime * 0.3)*0.8+0.1;//*PI/2.01; - cam_pos = vec3(my.cos() * mx.cos(), my.sin(), my.cos() * mx.sin()) * 7.35; - //7.35 - } else { - // Do the camera fly-by animation and different scenes. - // Time variables for start and end of each scene - let t0: f32 = 0.0; - let t1: f32 = 8.0; - let t2: f32 = 14.0; - let t3: f32 = 24.0; - let t4: f32 = 38.0; - let t5: f32 = 56.0; - let t6: f32 = 58.0; - /*let t0: f32 = 0.0; - let t1: f32 = 0.0; - let t2: f32 = 0.0; - let t3: f32 = 0.0; - let t4: f32 = 0.0; - let t5: f32 = 16.0; - let t6: f32 = 18.0;*/ - // Repeat the animation after time t6 - self.local_time = (self.local_time / t6).fract_gl() * t6; - if self.local_time < t1 { - let time: f32 = self.local_time - t0; - let alpha: f32 = time / (t1 - t0); - self.fade = saturate(time); - self.fade *= saturate(t1 - self.local_time); - cam_pos = vec3(13.0, 3.3, -3.5); - cam_pos.x -= smoothstep(0.0, 1.0, alpha) * 4.8; - cam_up = vec3(0.0, 1.0, 0.0); - cam_lookat = vec3(0.0, 1.5, 1.5); - } else if self.local_time < t2 { - let time: f32 = self.local_time - t1; - let alpha: f32 = time / (t2 - t1); - self.fade = saturate(time); - self.fade *= saturate(t2 - self.local_time); - cam_pos = vec3(26.0, 0.05 + smoothstep(0.0, 1.0, alpha) * 0.4, 2.0); - cam_pos.z -= alpha * 2.8; - cam_up = vec3(0.0, 1.0, 0.0); - cam_lookat = vec3(cam_pos.x - 0.3, -8.15, -40.0); - - self.sun_dir = vec3(0.95, 0.6, 1.0).normalize(); - self.sun_col = vec3(258.0, 248.0, 160.0) / 3555.0; - self.exposure *= 0.7; - self.sky_col *= 1.5; - } else if self.local_time < t3 { - let time: f32 = self.local_time - t2; - let alpha: f32 = time / (t3 - t2); - self.fade = saturate(time); - self.fade *= saturate(t3 - self.local_time); - cam_pos = vec3(12.0, 6.3, -0.5); - cam_pos.y -= alpha * 5.5; - cam_pos.x = (alpha * 1.0).cos() * 5.2; - cam_pos.z = (alpha * 1.0).sin() * 5.2; - cam_up = vec3(0.0, 1.0, -0.5 + alpha * 0.5).normalize(); - cam_lookat = vec3(0.0, 1.0, -0.5); - } else if self.local_time < t4 { - let time: f32 = self.local_time - t3; - let alpha: f32 = time / (t4 - t3); - self.fade = saturate(time); - self.fade *= saturate(t4 - self.local_time); - cam_pos = vec3(2.15 - alpha * 0.5, 0.02, -1.0 - alpha * 0.2); - cam_pos.y += smoothstep(0.0, 1.0, alpha * alpha) * 3.4; - cam_up = vec3(0.0, 1.0, 0.0).normalize(); - cam_lookat = vec3(0.0, 0.5 + alpha, alpha * 5.0); - } else if self.local_time < t5 { - let time: f32 = self.local_time - t4; - let alpha: f32 = time / (t5 - t4); - self.fade = saturate(time); - self.fade *= saturate(t5 - self.local_time); - cam_pos = vec3(-2.0, 1.3 - alpha * 1.2, -10.5 - alpha * 0.5); - cam_up = vec3(0.0, 1.0, 0.0).normalize(); - cam_lookat = vec3(-2.0, 0.3 + alpha, -0.0); - self.sun_dir = vec3(0.5 - alpha * 0.6, 0.3 - alpha * 0.3, 1.0).normalize(); - self.sun_col = vec3(258.0, 148.0, 60.0) / 3555.0; - self.local_time *= 16.0; - self.exposure *= 0.4; - self.horizon_col = vec3(1.0, 0.5, 0.35) * 2.0; - self.sky_col = vec3(0.75, 0.5, 0.95); - } else if self.local_time < t6 { - self.fade = 0.0; - cam_pos = vec3(26.0, 100.0, 2.0); - cam_up = vec3(0.0, 1.0, 0.0); - cam_lookat = vec3(0.3, 0.15, 0.0); - } - } - - // Camera setup for ray tracing / marching - let cam_vec: Vec3 = (cam_lookat - cam_pos).normalize(); - let side_norm: Vec3 = cam_up.cross(cam_vec).normalize(); - let up_norm: Vec3 = cam_vec.cross(side_norm); - let world_facing: Vec3 = cam_pos + cam_vec; - let world_pix: Vec3 = world_facing - + uv.x * side_norm * (self.inputs.resolution.x / self.inputs.resolution.y) - + uv.y * up_norm; - let ray_vec: Vec3 = (world_pix - cam_pos).normalize(); - - // ----------------------------- Ray march the scene ------------------------------ - let mut dist_and_mat: Vec2 = Vec2::ZERO; // Distance and material - let mut t: f32 = 0.05; // + Hash2d(uv)*0.1; // random dither-fade things close to the camera - let max_depth: f32 = 45.0; // farthest distance rays will travel - let mut pos: Vec3 = Vec3::ZERO; - let small_val: f32 = 0.000625; - - // ray marching time - for _ in 0..250 { - // This is the count of the max times the ray actually marches. - self.march_count += 1.0; - // Step along the ray. - pos = cam_pos + ray_vec * t; - // This is _the_ function that defines the "distance field". - // It's really what makes the scene geometry. The idea is that the - // distance field returns the distance to the closest object, and then - // we know we are safe to "march" along the ray by that much distance - // without hitting anything. We repeat this until we get really close - // and then break because we have effectively hit the object. - dist_and_mat = self.distance_to_object(pos); - - // 2d voxel walk through the city blocks. - // The distance function is not continuous at city block boundaries, - // so we have to pause our ray march at each voxel boundary. - let mut walk: f32 = dist_and_mat.x; - let mut dx: f32 = -pos.x.fract_gl(); - if ray_vec.x > 0.0 { - dx = (-pos.x).fract_gl(); - } - let mut dz: f32 = -pos.z.fract_gl(); - if ray_vec.z > 0.0 { - dz = (-pos.z).fract_gl(); - } - let mut nearest_voxel: f32 = - (dx / ray_vec.x).fract_gl().min((dz / ray_vec.z).fract_gl()) + VOXEL_PAD; - nearest_voxel = VOXEL_PAD.max(nearest_voxel); // hack that assumes streets and sidewalks are this wide. - //nearestVoxel = nearestVoxel.max(t * 0.02); // hack to stop voxel walking in the distance. - walk = walk.min(nearest_voxel); - - // move down the ray a safe amount - t += walk; - // If we are very close to the object, let's call it a hit and exit this loop. - if (t > max_depth) || (dist_and_mat.x.abs() < small_val) { - break; - } - } - - // Ray trace a ground plane to infinity - let alpha: f32 = -cam_pos.y / ray_vec.y; - if (t > max_depth) && (ray_vec.y < -0.0) { - pos = (cam_pos.xz() + ray_vec.xz() * alpha).extend(pos.y).xzy(); - pos.y = -0.0; - t = alpha; - dist_and_mat.y = 0.0; - dist_and_mat.x = 0.0; - } - // -------------------------------------------------------------------------------- - // Now that we have done our ray marching, let's put some color on this geometry. - let mut final_color: Vec3; - - // If a ray actually hit the object, let's light it. - if (t <= max_depth) || (t == alpha) { - let dist: f32 = dist_and_mat.x; - // calculate the normal from the distance field. The distance field is a volume, so if you - // sample the current point and neighboring points, you can use the difference to get - // the normal. - let small_vec: Vec3 = vec3(small_val, 0.0, 0.0); - let normal_u: Vec3 = vec3( - dist - self.distance_to_object(pos - small_vec.xyy()).x, - dist - self.distance_to_object(pos - small_vec.yxy()).x, - dist - self.distance_to_object(pos - small_vec.yyx()).x, - ); - let mut normal: Vec3 = normal_u.normalize(); - - // calculate 2 ambient occlusion values. One for global stuff and one - // for local stuff - let mut ambient_s: f32 = 1.0; - ambient_s *= saturate(self.distance_to_object(pos + normal * 0.0125).x * 80.0); - ambient_s *= saturate(self.distance_to_object(pos + normal * 0.025).x * 40.0); - ambient_s *= saturate(self.distance_to_object(pos + normal * 0.05).x * 20.0); - ambient_s *= saturate(self.distance_to_object(pos + normal * 0.1).x * 10.0); - ambient_s *= saturate(self.distance_to_object(pos + normal * 0.2).x * 5.0); - ambient_s *= saturate(self.distance_to_object(pos + normal * 0.4).x * 2.5); - //ambientS *= saturate_f32(DistanceToObject(pos + normal * 0.8).x*1.25); - let mut ambient: f32 = ambient_s; // * saturate_f32(DistanceToObject(pos + normal * 1.6).x*1.25*0.5); - //ambient *= saturate_f32(DistanceToObject(pos + normal * 3.2)*1.25*0.25); - //ambient *= saturate_f32(DistanceToObject(pos + normal * 6.4)*1.25*0.125); - ambient = ambient.powf(0.5).max(0.025); // tone down ambient with a pow and min clamp it. - ambient = saturate(ambient); - - // calculate the reflection vector for highlights - let ref_: Vec3 = ray_vec.reflect(normal); - - // Trace a ray toward the sun for sun shadows - let mut sun_shadow: f32 = 1.0; - let mut iter: f32 = 0.01; - let nudge_pos: Vec3 = pos + normal * 0.002; // don't start tracing too close or inside the object - for _ in 0..40 { - let shadow_pos: Vec3 = nudge_pos + self.sun_dir * iter; - let temp_dist: f32 = self.distance_to_object(shadow_pos).x; - sun_shadow *= saturate(temp_dist * 150.0); // Shadow hardness - if temp_dist <= 0.0 { - break; - } - - let mut walk: f32 = temp_dist; - let mut dx: f32 = -shadow_pos.x.fract_gl(); - if self.sun_dir.x > 0.0 { - dx = (-shadow_pos.x).fract_gl(); - } - let mut dz: f32 = -shadow_pos.z.fract_gl(); - if self.sun_dir.z > 0.0 { - dz = (-shadow_pos.z).fract_gl(); - } - let mut nearest_voxel: f32 = (dx / self.sun_dir.x) - .fract_gl() - .min((dz / self.sun_dir.z).fract_gl()) - + small_val; - nearest_voxel = nearest_voxel.max(0.2); // hack that assumes streets and sidewalks are this wide. - walk = walk.min(nearest_voxel); - - iter += walk.max(0.01); - if iter > 4.5 { - break; - } - } - sun_shadow = saturate(sun_shadow); - - // make a few frequencies of noise to give it some texture - let mut n: f32 = 0.0; - n += noise(pos * 32.0); - n += noise(pos * 64.0); - n += noise(pos * 128.0); - n += noise(pos * 256.0); - n += noise(pos * 512.0); - n = mix(0.7, 0.95, n); - - // ------ Calculate texture color ------ - let block: Vec2 = pos.xz().floor(); - let mut tex_color: Vec3 = vec3(0.95, 1.0, 1.0); - tex_color *= 0.8; - let mut window_ref: f32 = 0.0; - // texture map the sides of buildings - if (normal.y < 0.1) && (dist_and_mat.y == 0.0) { - let posdx: Vec3 = pos.dfdx(); - let posdy: Vec3 = pos.dfdy(); - let _pos_grad: Vec3 = posdx * hash21(uv) + posdy * hash21(uv * 7.6543); - - // Quincunx antialias the building texture and normal map. - // I guess procedural textures are hard to mipmap. - let mut col_total: Vec3; - let mut col_temp: Vec3 = tex_color; - let mut n_temp: Vec3 = Vec3::ZERO; - calc_windows(block, pos, &mut col_temp, &mut window_ref, &mut n_temp); - col_total = col_temp; - - col_temp = tex_color; - calc_windows( - block, - pos + posdx * 0.666, - &mut col_temp, - &mut window_ref, - &mut n_temp, - ); - col_total += col_temp; - - col_temp = tex_color; - calc_windows( - block, - pos + posdx * 0.666 + posdy * 0.666, - &mut col_temp, - &mut window_ref, - &mut n_temp, - ); - col_total += col_temp; - - col_temp = tex_color; - calc_windows( - block, - pos + posdy * 0.666, - &mut col_temp, - &mut window_ref, - &mut n_temp, - ); - col_total += col_temp; - - col_temp = tex_color; - calc_windows( - block, - pos + posdx * 0.333 + posdy * 0.333, - &mut col_temp, - &mut window_ref, - &mut n_temp, - ); - col_total += col_temp; - - tex_color = col_total * 0.2; - window_ref *= 0.2; - - normal = (normal + n_temp * 0.2).normalize(); - } else { - // Draw the road - let xroad: f32 = ((pos.x + 0.5).fract_gl() - 0.5).abs(); - let zroad: f32 = ((pos.z + 0.5).fract_gl() - 0.5).abs(); - let road: f32 = saturate((xroad.min(zroad) - 0.143) * 480.0); - tex_color *= 1.0 - normal.y * 0.95 * hash21(block * 9.87) * road; // change rooftop color - tex_color *= mix(0.1, 1.0, road); - - // double yellow line in middle of road - let mut yellow_line: f32 = saturate(1.0 - (xroad.min(zroad) - 0.002) * 480.0); - yellow_line *= saturate((xroad.min(zroad) - 0.0005) * 480.0); - yellow_line *= saturate((xroad * xroad + zroad * zroad - 0.05) * 880.0); - tex_color = mix(tex_color, vec3(1.0, 0.8, 0.3), yellow_line); - - // white dashed lines on road - let mut white_line: f32 = saturate(1.0 - (xroad.min(zroad) - 0.06) * 480.0); - white_line *= saturate((xroad.min(zroad) - 0.056) * 480.0); - white_line *= saturate((xroad * xroad + zroad * zroad - 0.05) * 880.0); - white_line *= saturate(1.0 - ((zroad * 8.0).fract_gl() - 0.5) * 280.0); // dotted line - white_line *= saturate(1.0 - ((xroad * 8.0).fract_gl() - 0.5) * 280.0); - tex_color = mix(tex_color, Vec3::splat(0.5), white_line); - - white_line = saturate(1.0 - (xroad.min(zroad) - 0.11) * 480.0); - white_line *= saturate((xroad.min(zroad) - 0.106) * 480.0); - white_line *= saturate((xroad * xroad + zroad * zroad - 0.06) * 880.0); - tex_color = mix(tex_color, Vec3::splat(0.5), white_line); - - // crosswalk - let mut cross_walk: f32 = saturate(1.0 - ((xroad * 40.0).fract_gl() - 0.5) * 280.0); - cross_walk *= saturate((zroad - 0.15) * 880.0); - cross_walk *= saturate((-zroad + 0.21) * 880.0) * (1.0 - road); - cross_walk *= n * n; - tex_color = mix(tex_color, Vec3::splat(0.25), cross_walk); - cross_walk = saturate(1.0 - ((zroad * 40.0).fract_gl() - 0.5) * 280.0); - cross_walk *= saturate((xroad - 0.15) * 880.0); - cross_walk *= saturate((-xroad + 0.21) * 880.0) * (1.0 - road); - cross_walk *= n * n; - tex_color = mix(tex_color, Vec3::splat(0.25), cross_walk); - - { - // sidewalk cracks - let mut sidewalk: f32 = 1.0; - let mut block_size: Vec2 = Vec2::splat(100.0); - if pos.y > 0.1 { - block_size = vec2(10.0, 50.0); - } - //sidewalk *= (pos.x*block_size).sin()abs().pow(0.025); - //sidewalk *= (pos.z*block_size).sin()abs().pow(0.025); - sidewalk *= - saturate(((pos.z * block_size.x).sin() * 800.0 / block_size.x).abs()); - sidewalk *= - saturate(((pos.x * block_size.y).sin() * 800.0 / block_size.y).abs()); - sidewalk = saturate(mix(0.7, 1.0, sidewalk)); - sidewalk = saturate((1.0 - road) + sidewalk); - tex_color *= sidewalk; - } - } - // Car tires are almost black to not call attention to their ugly. - if dist_and_mat.y == 3.0 { - tex_color = Vec3::splat(0.05); - } - - // apply noise - tex_color *= Vec3::ONE * n * 0.05; - tex_color *= 0.7; - tex_color = saturate_vec3(tex_color); - - let mut window_mask: f32 = 0.0; - if dist_and_mat.y >= 100.0 { - // car texture and windows - tex_color = vec3( - hash11(dist_and_mat.y) * 1.0, - hash11(dist_and_mat.y * 8.765), - hash11(dist_and_mat.y * 17.731), - ) * 0.1; - tex_color = tex_color.abs().powf(0.2); // bias toward white - tex_color = Vec3::splat(0.25).max(tex_color); // not too saturated color. - tex_color.z = tex_color.y.min(tex_color.z); // no purple cars. just not realistic. :) - tex_color *= hash11(dist_and_mat.y * 0.789) * 0.15; - window_mask = saturate(((pos.y - 0.0175).abs() * 3800.0).max(0.0) - 10.0); - let dir_norm: Vec2 = normal.xz().normalize().abs(); - let mut pillars: f32 = saturate(1.0 - dir_norm.x.max(dir_norm.y)); - pillars = (pillars - 0.15).max(0.0).powf(0.125); - window_mask = window_mask.max(pillars); - tex_color *= window_mask; - } - - // ------ Calculate lighting color ------ - // Start with sun color, standard lighting equation, and shadow - let mut light_color: Vec3 = - Vec3::splat(100.0) * self.sun_col * saturate(self.sun_dir.dot(normal)) * sun_shadow; - // weighted average the near ambient occlusion with the far for just the right look - let ambient_avg: f32 = (ambient * 3.0 + ambient_s) * 0.25; - // Add sky color with ambient acclusion - light_color += - (self.sky_col * saturate(normal.y * 0.5 + 0.5)) * ambient_avg.powf(0.35) * 2.5; - light_color *= 4.0; - - // finally, apply the light to the texture. - final_color = tex_color * light_color; - // Reflections for cars - if dist_and_mat.y >= 100.0 { - let mut yfade: f32 = 0.01_f32.max(1.0_f32.min(ref_.y * 100.0)); - // low-res way of making lines at the edges of car windows. Not sure I like it. - yfade *= saturate(1.0 - (window_mask.dfdx() * window_mask.dfdy()).abs() * 250.995); - final_color += self.get_env_map_skyline(ref_, self.sun_dir, pos.y - 1.5) - * 0.3 - * yfade - * sun_shadow.max(0.4); - final_color += - saturate_vec3(self.inputs.channel0.sample_cube(ref_).xyz() - Vec3::splat(0.35)) - * 0.15 - * sun_shadow.max(0.2); - } - // reflections for building windows - if window_ref != 0.0 { - final_color *= mix(1.0, 0.6, window_ref); - let yfade: f32 = 0.01_f32.max(1.0_f32.min(ref_.y * 100.0)); - final_color += self.get_env_map_skyline(ref_, self.sun_dir, pos.y - 0.5) - * 0.6 - * yfade - * sun_shadow.max(0.6) - * window_ref; //*(windowMask*0.5+0.5); - final_color += - saturate_vec3(self.inputs.channel0.sample_cube(ref_).xyz() - Vec3::splat(0.35)) - * 0.15 - * sun_shadow.max(0.25) - * window_ref; - } - final_color *= 0.9; - // fog that fades to reddish plus the sun color so that fog is brightest towards sun - let mut rv2: Vec3 = ray_vec; - rv2.y *= saturate(rv2.y.sign_gl()); - let mut fog_color: Vec3 = self.get_env_map(rv2, self.sun_dir); - fog_color = Vec3::splat(9.0).min(fog_color); - final_color = mix(fog_color, final_color, (-t * 0.02).exp()); - - // visualize length of gradient of distance field to check distance field correctness - //final_color = Vec3::splat(0.5) * (normalU.length() / smallVec.x); - //final_color = Vec3::splat(marchCount)/255.0; - } else { - // Our ray trace hit nothing, so draw sky. - final_color = self.get_env_map(ray_vec, self.sun_dir); - } - - // vignette? - final_color *= Vec3::ONE * saturate(1.0 - (uv / 2.5).length()); - final_color *= 1.3 * self.exposure; - - // output the final color without gamma correction - will do gamma later. - final_color.clamp(Vec3::ZERO, Vec3::ONE) * saturate(self.fade + 0.2) - } -} - -impl State { - // This function breaks the image down into blocks and scans - // through them, rendering 1 block at a time. It's for non- - // realtime things that take a long time to render. - - // This is the frame rate to render at. Too fast and you will - // miss some blocks. - const BLOCK_RATE: f32 = 20.0; - - fn block_render(&self, frag_coord: Vec2) { - // blockSize is how much it will try to render in 1 frame. - // adjust this smaller for more complex scenes, bigger for - // faster render times. - let block_size: f32 = 64.0; - // Make the block repeatedly scan across the image based on time. - let frame: f32 = (self.inputs.time * Self::BLOCK_RATE).floor(); - let block_res: Vec2 = (self.inputs.resolution.xy() / block_size).floor() + Vec2::ONE; - // ugly bug with mod. - //float blockX = mod(frame, blockRes.x); - let block_x: f32 = (frame / block_res.x).fract_gl() * block_res.x; - //float blockY = mod(floor(frame / blockRes.x), blockRes.y); - let block_y: f32 = ((frame / block_res.x).floor() / block_res.y).fract_gl() * block_res.y; - // Don't draw anything outside the current block. - if (frag_coord.x - block_x * block_size >= block_size) - || (frag_coord.x - (block_x - 1.0) * block_size < block_size) - || (frag_coord.y - block_y * block_size >= block_size) - || (frag_coord.y - (block_y - 1.0) * block_size < block_size) - { - discard(); - } - } - - pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { - if NON_REALTIME_HQ_RENDER { - // Optionally render a non-realtime scene with high quality - self.block_render(frag_coord); - } - - // Do a multi-pass render - let mut final_color: Vec3 = Vec3::ZERO; - if NON_REALTIME_HQ_RENDER { - for _ in 0..ANTIALIASING_SAMPLES { - let motion_blur_length_in_seconds: f32 = 1.0 / 60.0; - // Set this to the time in seconds of the frame to render. - self.local_time = FRAME_TO_RENDER_HQ; - // This line will motion-blur the renders - self.local_time += hash11(v21(frag_coord + Vec2::splat(self.seed))) - * motion_blur_length_in_seconds; - // Jitter the pixel position so we get antialiasing when we do multiple passes. - let mut jittered: Vec2 = frag_coord - + vec2( - hash21(frag_coord + Vec2::splat(self.seed)), - hash21(frag_coord * 7.234567 + Vec2::splat(self.seed)), - ); - // don't antialias if only 1 sample. - if ANTIALIASING_SAMPLES == 1 { - jittered = frag_coord - }; - // Accumulate one pass of raytracing into our pixel value - final_color += self.ray_trace(jittered); - // Change the random seed for each pass. - self.seed *= 1.01234567; - } - // Average all accumulated pixel intensities - final_color /= ANTIALIASING_SAMPLES as f32; - } else { - // Regular real-time rendering - self.local_time = self.inputs.time; - final_color = self.ray_trace(frag_coord); - } - - *frag_color = final_color.clamp(Vec3::ZERO, Vec3::ONE).sqrt().extend(1.0); - } -} diff --git a/shaders/src/soft_shadow_variation.rs b/shaders/src/soft_shadow_variation.rs deleted file mode 100644 index 1617a4e..0000000 --- a/shaders/src/soft_shadow_variation.rs +++ /dev/null @@ -1,225 +0,0 @@ -//! Ported to Rust from -//! -//! Original comment: -//! ```glsl -//! // -//! // Testing Sebastian Aaltonen's soft shadow improvement -//! // -//! // The technique is based on estimating a better closest point in ray -//! // at each step by triangulating from the previous march step. -//! // -//! // More info about the technique at slide 39 of this presentation: -//! // https://www.dropbox.com/s/s9tzmyj0wqkymmz/Claybook_Simulation_Raytracing_GDC18.pptx?dl=0 -//! // -//! // Traditional technique: http://iquilezles.org/www/articles/rmshadows/rmshadows.htm -//! // -//! // Go to lines 54 to compare both. -//! ``` - -use shared::*; -use spirv_std::glam::{vec2, vec3, Mat3, Vec2, Vec2Swizzles, Vec3, Vec3Swizzles, Vec4}; - -// Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but -// we tie #[no_std] above to the same condition, so it's fine. -#[cfg(target_arch = "spirv")] -use spirv_std::num_traits::Float; - -pub struct Inputs { - pub resolution: Vec3, - pub time: f32, -} - -// make this 1 is your machine is too slow -const AA: usize = 2; - -//------------------------------------------------------------------ - -fn sd_plane(p: Vec3) -> f32 { - p.y -} - -fn sd_box(p: Vec3, b: Vec3) -> f32 { - let d: Vec3 = p.abs() - b; - d.x.max(d.y.max(d.z)).min(0.0) + d.max(Vec3::ZERO).length() -} - -//------------------------------------------------------------------ - -fn map(pos: Vec3) -> f32 { - let qos: Vec3 = vec3((pos.x + 0.5).fract_gl() - 0.5, pos.y, pos.z); - return sd_plane(pos - vec3(0.0, 0.00, 0.0)) - .min(sd_box(qos - vec3(0.0, 0.25, 0.0), vec3(0.2, 0.5, 0.2))); -} - -//------------------------------------------------------------------ - -fn calc_softshadow(ro: Vec3, rd: Vec3, mint: f32, tmax: f32, technique: i32) -> f32 { - let mut res: f32 = 1.0; - let mut t: f32 = mint; - let mut ph: f32 = 1e10; // big, such that y = 0 on the first iteration - for _ in 0..32 { - let h: f32 = map(ro + rd * t); - - // traditional technique - if technique == 0 { - res = res.min(10.0 * h / t); - } - // improved technique - else { - // use this if you are getting artifact on the first iteration, or unroll the - // first iteration out of the loop - //float y = (i==0) ? 0.0 : h*h/(2.0*ph); - - let y: f32 = h * h / (2.0 * ph); - let d: f32 = (h * h - y * y).sqrt(); - res = res.min(10.0 * d / (t - y).max(0.0)); - ph = h; - } - t += h; - - if res < 0.0001 || t > tmax { - break; - } - } - res.clamp(0.0, 1.0) -} - -fn calc_normal(pos: Vec3) -> Vec3 { - let e: Vec2 = vec2(1.0, -1.0) * 0.5773 * 0.0005; - (e.xyy() * map(pos + e.xyy()) - + e.yyx() * map(pos + e.yyx()) - + e.yxy() * map(pos + e.yxy()) - + e.xxx() * map(pos + e.xxx())) - .normalize() -} - -fn cast_ray(ro: Vec3, rd: Vec3) -> f32 { - let mut tmin: f32 = 1.0; - let mut tmax: f32 = 20.0; - - if true { - // bounding volume - let tp1: f32 = (0.0 - ro.y) / rd.y; - if tp1 > 0.0 { - tmax = tmax.min(tp1); - } - let tp2: f32 = (1.0 - ro.y) / rd.y; - if tp2 > 0.0 { - if ro.y > 1.0 { - tmin = tmin.max(tp2); - } else { - tmax = tmax.min(tp2); - } - } - } - let mut t: f32 = tmin; - for _ in 0..64 { - let precis: f32 = 0.0005 * t; - let res: f32 = map(ro + rd * t); - if res < precis || t > tmax { - break; - } - t += res; - } - - if t > tmax { - t = -1.0; - } - t -} - -fn calc_ao(pos: Vec3, nor: Vec3) -> f32 { - let mut occ: f32 = 0.0; - let mut sca: f32 = 1.0; - for i in 0..5 { - let h: f32 = 0.001 + 0.15 * i as f32 / 4.0; - let d: f32 = map(pos + h * nor); - occ += (h - d) * sca; - sca *= 0.95; - } - (1.0 - 1.5 * occ).clamp(0.0, 1.0) -} - -fn render(ro: Vec3, rd: Vec3, technique: i32) -> Vec3 { - let mut col: Vec3 = Vec3::ZERO; - let t: f32 = cast_ray(ro, rd); - - if t > -0.5 { - let pos: Vec3 = ro + t * rd; - let nor: Vec3 = calc_normal(pos); - - // material - let mate: Vec3 = Vec3::splat(0.3); - // key light - let lig: Vec3 = vec3(-0.1, 0.3, 0.6).normalize(); - let hal: Vec3 = (lig - rd).normalize(); - let dif: f32 = - nor.dot(lig).clamp(0.0, 1.0) * calc_softshadow(pos, lig, 0.01, 3.0, technique); - - let spe: f32 = nor.dot(hal).clamp(0.0, 1.0).powf(16.0) - * dif - * (0.04 + 0.96 * (1.0 + hal.dot(rd)).clamp(0.0, 1.0).powf(5.0)); - - col = mate * 4.0 * dif * vec3(1.00, 0.70, 0.5); - col += 12.0 * spe * vec3(1.00, 0.70, 0.5); - - // ambient light - let occ: f32 = calc_ao(pos, nor); - let amb: f32 = (0.5 + 0.5 * nor.y).clamp(0.0, 1.0); - col += mate * amb * occ * vec3(0.0, 0.08, 0.1); - - // fog - col *= (-0.0005 * t * t * t).exp(); - } - - col -} - -fn set_camera(ro: Vec3, ta: Vec3, cr: f32) -> Mat3 { - let cw: Vec3 = (ta - ro).normalize(); - let cp: Vec3 = vec3(cr.sin(), cr.cos(), 0.0); - let cu: Vec3 = cw.cross(cp).normalize(); - let cv: Vec3 = cu.cross(cw).normalize(); - Mat3::from_cols(cu, cv, cw) -} - -impl Inputs { - pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { - // camera - let an: f32 = 12.0 - (0.1 * self.time).sin(); - let ro: Vec3 = vec3(3.0 * (0.1 * an).cos(), 1.0, -3.0 * (0.1 * an).sin()); - let ta: Vec3 = vec3(0.0, -0.4, 0.0); - // camera-to-world transformation - let ca: Mat3 = set_camera(ro, ta, 0.0); - - let technique: i32 = if (self.time / 2.0).fract_gl() > 0.5 { - 1 - } else { - 0 - }; - - let mut tot: Vec3 = Vec3::ZERO; - - for m in 0..AA { - for n in 0..AA { - // pixel coordinates - let o: Vec2 = vec2(m as f32, n as f32) / AA as f32 - Vec2::splat(0.5); - let p: Vec2 = (-self.resolution.xy() + 2.0 * (frag_coord + o)) / self.resolution.y; - - // ray direction - let rd: Vec3 = ca * p.extend(2.0).normalize(); - - // render - let mut col: Vec3 = render(ro, rd, technique); - - // gamma - col = col.powf(0.4545); - - tot += col; - } - } - tot /= (AA * AA) as f32; - - *frag_color = tot.extend(1.0); - } -} diff --git a/shaders/src/tileable_water_caustic.rs b/shaders/src/tileable_water_caustic.rs deleted file mode 100644 index e2a7afc..0000000 --- a/shaders/src/tileable_water_caustic.rs +++ /dev/null @@ -1,81 +0,0 @@ -//! Ported to Rust from -//! -//! Original comment: -//! ```glsl -//! // Found this on GLSL sandbox. I really liked it, changed a few things and made it tileable. -//! // :) -//! // by David Hoskins. -//! -//! -//! // Water turbulence effect by joltz0r 2013-07-04, improved 2013-07-07 -//! ``` - -use shared::*; -use spirv_std::glam::{vec2, vec3, Vec2, Vec3, Vec3Swizzles, Vec4}; - -// Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but -// we tie #[no_std] above to the same condition, so it's fine. -#[cfg(target_arch = "spirv")] -use spirv_std::num_traits::Float; - -pub struct Inputs { - pub resolution: Vec3, - pub time: f32, -} - -// Redefine below to see the tiling... -const SHOW_TILING: bool = false; - -use core::f32::consts::TAU; -const MAX_ITER: usize = 5; - -impl Inputs { - pub fn main_image(&self, frag_color: &mut Vec4, frag_coord: Vec2) { - let time: f32 = self.time * 0.5 + 23.0; - // uv should be the 0-1 uv of texture... - let mut uv: Vec2 = frag_coord / self.resolution.xy(); - - let p: Vec2 = if SHOW_TILING { - (uv * TAU * 2.0).rem_euclid(Vec2::splat(TAU)) - Vec2::splat(250.0) - } else { - (uv * TAU).rem_euclid(Vec2::splat(TAU)) - Vec2::splat(250.0) - }; - let mut i: Vec2 = p; - let mut c: f32 = 1.0; - let inten: f32 = 0.005; - - for n in 0..MAX_ITER { - let t: f32 = time * (1.1 - (3.5 / (n + 1) as f32)); - i = p + vec2( - (t - i.x).cos() + (t + i.y).sin(), - (t - i.y).sin() + (t + i.x).cos(), - ); - c += 1.0 - / vec2( - p.x / ((i.x + t).sin() / inten), - p.y / ((i.y + t).cos() / inten), - ) - .length(); - } - c /= MAX_ITER as f32; - c = 1.17 - c.powf(1.4); - let mut colour: Vec3 = Vec3::splat(c.abs().powf(8.0)); - colour = (colour + vec3(0.0, 0.35, 0.5)).clamp(Vec3::ZERO, Vec3::ONE); - - if SHOW_TILING { - let pixel: Vec2 = 2.0 / self.resolution.xy(); - uv *= 2.0; - - let f: f32 = (self.time * 0.5).rem_euclid(2.0).floor(); // Flash value. - let first: Vec2 = pixel.step(uv) * f; // Rule out first screen pixels and flash. - uv = uv.fract_gl().step(pixel); // Add one line of pixels per tile. - colour = mix( - colour, - vec3(1.0, 1.0, 0.0), - (uv.x + uv.y) * first.x * first.y, - ); // Yellow line - } - - *frag_color = colour.extend(1.0); - } -} diff --git a/shaders/src/tokyo.rs b/shaders/src/tokyo.rs deleted file mode 100644 index 6b15d2b..0000000 --- a/shaders/src/tokyo.rs +++ /dev/null @@ -1,540 +0,0 @@ -//! Ported to Rust from -//! -//! Original comment: -//! ```glsl -//! // Created by Reinder Nijhoff 2014 -//! // Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. -//! // @reindernijhoff -//! // -//! // https://www.shadertoy.com/view/Xtf3zn -//! // -//! // Tokyo by night in the rain. The car model is made by Eiffie -//! // (Shiny Toy': https://www.shadertoy.com/view/ldsGWB). -//! // I have never been in Tokyo btw. -//! ``` - -use shared::*; -use spirv_std::glam::{ - mat2, vec2, vec3, vec4, Mat2, Vec2, Vec2Swizzles, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles, -}; - -// Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but -// we tie #[no_std] above to the same condition, so it's fine. -#[cfg(target_arch = "spirv")] -use spirv_std::num_traits::Float; - -#[derive(Clone, Copy)] -pub struct Inputs { - pub resolution: Vec3, - pub time: f32, -} - -pub struct State { - inputs: Inputs, - - d_l: f32, // minimal distance to light - - int1: Vec3, - int2: Vec3, - nor1: Vec3, - lint1: Vec4, - lint2: Vec4, -} - -impl State { - pub fn new(inputs: Inputs) -> State { - State { - inputs, - - d_l: 0.0, - - int1: Vec3::ZERO, - int2: Vec3::ZERO, - nor1: Vec3::ZERO, - lint1: Vec4::ZERO, - lint2: Vec4::ZERO, - } - } -} - -const BUMPMAP: bool = false; -const MARCHSTEPS: i32 = 128; -const MARCHSTEPSREFLECTION: i32 = 48; -const LIGHTINTENSITY: f32 = 5.0; - -//---------------------------------------------------------------------- - -//const backgroundColor: Vec3 = const_vec3!(0.2,0.4,0.6) * 0.09; -const BACKGROUND_COLOR: Vec3 = vec3(0.2 * 0.09, 0.4 * 0.09, 0.6 * 0.09); -impl State { - fn time(&self) -> f32 { - self.inputs.time + 90.0 - } -} - -//---------------------------------------------------------------------- -// noises - -fn hash(n: f32) -> f32 { - (n.sin() * 687.3123).fract_gl() -} - -fn noise(x: Vec2) -> f32 { - let p: Vec2 = x.floor(); - let mut f: Vec2 = x.fract_gl(); - f = f * f * (Vec2::splat(3.0) - 2.0 * f); - let n: f32 = p.x + p.y * 157.0; - mix( - mix(hash(n + 0.0), hash(n + 1.0), f.x), - mix(hash(n + 157.0), hash(n + 158.0), f.x), - f.y, - ) -} - -const M2: Mat2 = mat2(vec2(0.80, -0.60), vec2(0.60, 0.80)); - -fn fbm(mut p: Vec2) -> f32 { - let mut f: f32 = 0.0; - f += 0.5000 * noise(p); - p = M2 * p * 2.02; - f += 0.2500 * noise(p); - p = M2 * p * 2.03; - f += 0.1250 * noise(p); - // p = M2 * p * 2.01; - // f += 0.0625*noise( p ); - - f / 0.9375 -} - -//---------------------------------------------------------------------- -// distance primitives - -fn ud_round_box(p: Vec3, b: Vec3, r: f32) -> f32 { - (p.abs() - b).max(Vec3::ZERO).length() - r -} - -fn sd_box(p: Vec3, b: Vec3) -> f32 { - let d: Vec3 = p.abs() - b; - d.x.max(d.y.max(d.z)).min(0.0) + d.max(Vec3::ZERO).length() -} - -fn sd_sphere(p: Vec3, s: f32) -> f32 { - p.length() - s -} - -fn sd_cylinder(p: Vec3, h: Vec2) -> f32 { - let d: Vec2 = vec2(p.xz().length(), p.y).abs() - h; - d.x.max(d.y).min(0.0) + d.max(Vec2::ZERO).length() -} - -//---------------------------------------------------------------------- -// distance operators - -fn op_u(d2: f32, d1: f32) -> f32 { - d1.min(d2) -} -fn op_s(d2: f32, d1: f32) -> f32 { - (-d1).max(d2) -} -fn smin(a: f32, b: f32, k: f32) -> f32 { - //from iq - -((-k * a).exp() + (-k * b).exp()).ln() / k -} - -//---------------------------------------------------------------------- -// Map functions - -// car model is made by Eiffie -// shader 'Shiny Toy': https://www.shadertoy.com/view/ldsGWB - -fn map_car(p0: Vec3) -> f32 { - let mut p: Vec3 = p0 + vec3(0.0, 1.24, 0.0); - let mut r: f32 = p.yz().length(); - let mut d: f32 = vec3(p.x.abs() - 0.35, r - 1.92, -p.y + 1.4) - .max(Vec3::ZERO) - .length() - - 0.05; - d = d.max(p.z - 1.0); - p = p0 + vec3(0.0, -0.22, 0.39); - p = (p.xz().abs() - vec2(0.5300, 0.9600)).extend(p.y).xzy(); - p.x = p.x.abs(); - r = p.yz().length(); - d = smin( - d, - vec3(p.x - 0.08, r - 0.25, -p.y - 0.08) - .max(Vec3::ZERO) - .length() - - 0.04, - 8.0, - ); - d = d.max(-(p.x - 0.165).max(r - 0.24)); - let d2: f32 = vec2((p.x - 0.13).max(0.0), r - 0.2).length() - 0.02; - d = d.min(d2); - - d -} - -impl State { - fn map(&mut self, p: Vec3) -> f32 { - let mut pd: Vec3 = p; - let mut d: f32; - - pd.x = pd.x.abs(); - pd.z *= -p.x.sign_gl(); - - let ch: f32 = hash(((pd.z + 18.0 * self.time()) / 40.0).floor()); - let lh: f32 = hash((pd.z / 13.0).floor()); - - let pdm: Vec3 = vec3(pd.x, pd.y, pd.z.rem_euclid(10.0) - 5.0); - self.d_l = sd_sphere(vec3(pdm.x - 8.1, pdm.y - 4.5, pdm.z), 0.1); - - self.d_l = op_u( - self.d_l, - sd_box( - vec3(pdm.x - 12.0, pdm.y - 9.5 - lh, pd.z.rem_euclid(91.0) - 45.5), - vec3(0.2, 4.5, 0.2), - ), - ); - self.d_l = op_u( - self.d_l, - sd_box( - vec3( - pdm.x - 12.0, - pdm.y - 11.5 + lh, - pd.z.rem_euclid(31.0) - 15.5, - ), - vec3(0.22, 5.5, 0.2), - ), - ); - self.d_l = op_u( - self.d_l, - sd_box( - vec3(pdm.x - 12.0, pdm.y - 8.5 - lh, pd.z.rem_euclid(41.0) - 20.5), - vec3(0.24, 3.5, 0.2), - ), - ); - - if lh > 0.5 { - self.d_l = op_u( - self.d_l, - sd_box( - vec3(pdm.x - 12.5, pdm.y - 2.75 - lh, pd.z.rem_euclid(13.) - 6.5), - vec3(0.1, 0.25, 3.2), - ), - ); - } - - let pm: Vec3 = vec3( - (pd.x + (pd.z * 4.0).floor() * 0.25).rem_euclid(0.5) - 0.25, - pd.y, - pd.z.rem_euclid(0.25) - 0.125, - ); - d = ud_round_box(pm, vec3(0.245, 0.1, 0.12), 0.005); - - d = op_s(d, -(p.x + 8.)); - d = op_u(d, pd.y); - - let mut pdc: Vec3 = vec3( - pd.x, - pd.y, - (pd.z + 18.0 * self.time()).rem_euclid(40.0) - 20.0, - ); - - // car - if ch > 0.75 { - pdc.x += (ch - 0.75) * 4.0; - self.d_l = op_u( - self.d_l, - sd_sphere(vec3((pdc.x - 5.0).abs() - 1.05, pdc.y - 0.55, pdc.z), 0.025), - ); - self.d_l = op_u( - self.d_l, - sd_sphere( - vec3((pdc.x - 5.0).abs() - 1.2, pdc.y - 0.65, pdc.z + 6.05), - 0.025, - ), - ); - - d = op_u(d, map_car((pdc - vec3(5.0, -0.025, -2.3)) * 0.45)); - } - - d = op_u(d, 13. - pd.x); - d = op_u( - d, - sd_cylinder(vec3(pdm.x - 8.5, pdm.y, pdm.z), vec2(0.075, 4.5)), - ); - d = op_u(d, self.d_l); - - d - } - - //---------------------------------------------------------------------- - - fn calc_normal_simple(&mut self, pos: Vec3) -> Vec3 { - let e: Vec2 = vec2(1.0, -1.0) * 0.005; - - let n: Vec3 = (e.xyy() * self.map(pos + e.xyy()) - + e.yyx() * self.map(pos + e.yyx()) - + e.yxy() * self.map(pos + e.yxy()) - + e.xxx() * self.map(pos + e.xxx())) - .normalize(); - n - } - - fn calc_normal(&mut self, pos: Vec3) -> Vec3 { - let mut n: Vec3 = self.calc_normal_simple(pos); - if pos.y > 0.12 { - return n; - } - - if BUMPMAP { - let mut oc: Vec2 = - (vec2(pos.x + (pos.z * 4.0).floor() * 0.25, pos.z) * vec2(2.0, 4.0)).floor(); - - if pos.x.abs() < 8. { - oc = pos.xz(); - } - - let p: Vec3 = pos * 250.; - let mut xn: Vec3 = 0.05 * vec3(noise(p.xz()) - 0.5, 0., noise(p.zx()) - 0.5); - xn += 0.1 * vec3(fbm(oc) - 0.5, 0., fbm(oc.yx()) - 0.5); - - n = (xn + n).normalize(); - } - - n - } - - fn intersect(&mut self, mut ro: Vec3, mut rd: Vec3) -> f32 { - let precis: f32 = 0.001; - let mut h: f32; - let mut t: f32 = 0.0; - self.int1 = Vec3::splat(-500.0); - self.int2 = Vec3::splat(-500.0); - self.lint1 = Vec4::splat(-500.0); - self.lint2 = Vec4::splat(-500.0); - let mut mld: f32 = 100.0; - - for _ in 0..MARCHSTEPS { - h = self.map(ro + rd * t); - if self.d_l < mld { - mld = self.d_l; - self.lint1 = (ro + rd * t).extend(self.lint1.w); - self.lint1.w = self.d_l.abs(); - } - if h < precis { - self.int1 = ro + rd * t; - break; - } - t += h.max(precis * 2.); - } - - if self.int1.z < -400.0 || t > 300.0 { - // check intersection with plane y = -0.1; - let d: f32 = -(ro.y + 0.1) / rd.y; - if d > 0.0 { - self.int1 = ro + rd * d; - } else { - return -1.0; - } - } - - ro = ro + rd * t; - self.nor1 = self.calc_normal(ro); - ro += 0.01 * self.nor1; - rd = rd.reflect(self.nor1); - t = 0.0; - mld = 100.; - - for _ in 0..MARCHSTEPSREFLECTION { - h = self.map(ro + rd * t); - if self.d_l < mld { - mld = self.d_l; - self.lint2 = (ro + rd * t).extend(self.lint2.w); - self.lint2.w = self.d_l.abs(); - } - if h < precis { - self.int2 = ro + rd * t; - return 1.0; - } - t += h.max(precis * 2.0); - } - - 0.0 - } - - //---------------------------------------------------------------------- - // shade - - fn shade(&self, ro: Vec3, pos: Vec3, nor: Vec3) -> Vec3 { - let mut col: Vec3 = Vec3::splat(0.5); - - if pos.x.abs() > 15.0 || pos.x.abs() < 8.0 { - col = Vec3::splat(0.02); - } - if pos.y < 0.01 { - if self.int1.x.abs() < 0.1 { - col = Vec3::splat(0.9); - } - if (self.int1.x.abs() - 7.4).abs() < 0.1 { - col = Vec3::splat(0.9); - } - } - - let sh: f32 = nor.dot(vec3(-0.3, 0.3, -0.5).normalize()).clamp(0.0, 1.0); - col *= sh * BACKGROUND_COLOR; - - if pos.x.abs() > 12.9 && pos.y > 9. { - // windows - let ha: f32 = hash(133.1234 * (pos.y / 3.0).floor() + ((pos.z) / 3.0).floor()); - if ha > 0.95 { - col = ((ha - 0.95) * 10.) * vec3(1., 0.7, 0.4); - } - } - - col = mix( - BACKGROUND_COLOR, - col, - ((0.1 * pos.y).max(0.25) - 0.065 * pos.distance(ro)) - .min(0.0) - .exp(), - ); - - col - } - - fn get_light_color(&self, pos: Vec3) -> Vec3 { - let mut lcol: Vec3 = vec3(1.0, 0.7, 0.5); - - let mut pd: Vec3 = pos; - pd.x = pd.x.abs(); - pd.z *= -pos.x.sign_gl(); - - let ch: f32 = hash(((pd.z + 18. * self.time()) / 40.0).floor()); - let mut pdc: Vec3 = vec3( - pd.x, - pd.y, - (pd.z + 18.0 * self.time()).rem_euclid(40.0) - 20.0, - ); - - if ch > 0.75 { - // car - pdc.x += (ch - 0.75) * 4.; - if sd_sphere(vec3((pdc.x - 5.0).abs() - 1.05, pdc.y - 0.55, pdc.z), 0.25) < 2. { - lcol = vec3(1., 0.05, 0.01); - } - } - if pd.y > 2.0 && pd.x.abs() > 10.0 && pd.y < 5.0 { - let fl: f32 = (pd.z / 13.0).floor(); - lcol = 0.4 * lcol + 0.5 * vec3(hash(0.1562 + fl), hash(0.423134 + fl), 0.0); - } - if pd.x.abs() > 10. && pd.y > 5.0 { - let fl: f32 = (pd.z / 2.0).floor(); - lcol = 0.5 * lcol - + 0.5 * vec3(hash(0.1562 + fl), hash(0.923134 + fl), hash(0.423134 + fl)); - } - - lcol - } -} - -fn random_start(co: Vec2) -> f32 { - 0.8 + 0.2 * hash(co.dot(vec2(123.42, 117.853)) * 412.453) -} - -impl State { - //---------------------------------------------------------------------- - // main - - pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { - let mut q: Vec2 = frag_coord / self.inputs.resolution.xy(); - let mut p: Vec2 = -Vec2::ONE + 2.0 * q; - p.x *= self.inputs.resolution.x / self.inputs.resolution.y; - - if q.y < 0.12 || q.y >= 0.88 { - *frag_color = vec4(0.0, 0.0, 0.0, 1.0); - return; - } else { - // camera - let z: f32 = self.time(); - let x: f32 = -10.9 + 1. * (self.time() * 0.2).sin(); - let ro: Vec3 = vec3(x, 1.3 + 0.3 * (self.time() * 0.26).cos(), z - 1.); - let ta: Vec3 = vec3( - -8.0, - 1.3 + 0.4 * (self.time() * 0.26).cos(), - z + 4. + (self.time() * 0.04).cos(), - ); - - let ww: Vec3 = (ta - ro).normalize(); - let uu: Vec3 = ww.cross(vec3(0.0, 1.0, 0.0)).normalize(); - let vv: Vec3 = uu.cross(ww).normalize(); - let rd: Vec3 = (-p.x * uu + p.y * vv + 2.2 * ww).normalize(); - - let mut col: Vec3 = BACKGROUND_COLOR; - - // raymarch - let ints: f32 = self.intersect(ro + random_start(p) * rd, rd); - if ints > -0.5 { - // calculate reflectance - let mut r: f32 = 0.09; - if self.int1.y > 0.129 { - r = 0.025 - * hash( - 133.1234 * (self.int1.y / 3.0).floor() + (self.int1.z / 3.0).floor(), - ); - } - if self.int1.x.abs() < 8.0 { - if self.int1.y < 0.01 { - // road - r = 0.007 * fbm(self.int1.xz()); - } else { - // car - r = 0.02; - } - } - if self.int1.x.abs() < 0.1 { - r *= 4.0; - } - if (self.int1.x.abs() - 7.4).abs() < 0.1 { - r *= 4.0; - } - - r *= 2.0; - - col = self.shade(ro, self.int1, self.nor1); - - if ints > 0.5 { - let tmp = self.calc_normal_simple(self.int2); - col += r * self.shade(self.int1, self.int2, tmp); - } - if self.lint2.w > 0. { - col += (r * LIGHTINTENSITY * (-self.lint2.w * 7.0).exp()) - * self.get_light_color(self.lint2.xyz()); - } - } - - // Rain (by Dave Hoskins) - let st: Vec2 = 256. - * (p * vec2(0.5, 0.01) + vec2(self.time() * 0.13 - q.y * 0.6, self.time() * 0.13)); - let mut f: f32 = noise(st) * noise(st * 0.773) * 1.55; - f = 0.25 + (f.abs().powf(13.0) * 13.0).clamp(0.0, q.y * 0.14); - - if self.lint1.w > 0.0 { - col += (f * LIGHTINTENSITY * (-self.lint1.w * 7.0).exp()) - * self.get_light_color(self.lint1.xyz()); - } - - col += 0.25 * f * (Vec3::splat(0.2) + BACKGROUND_COLOR); - - // post processing - col = col.clamp(Vec3::ZERO, Vec3::ONE).powf(0.4545); - col *= 1.2 * vec3(1.0, 0.99, 0.95); - col = (1.06 * col - Vec3::splat(0.03)).clamp(Vec3::ZERO, Vec3::ONE); - q.y = (q.y - 0.12) * (1. / 0.76); - col *= Vec3::splat(0.5) - + Vec3::splat(0.5) * (16.0 * q.x * q.y * (1.0 - q.x) * (1.0 - q.y)).powf(0.1); - - *frag_color = col.extend(1.0); - } - } -} diff --git a/shaders/src/two_tweets.rs b/shaders/src/two_tweets.rs deleted file mode 100644 index e482020..0000000 --- a/shaders/src/two_tweets.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! Ported to Rust from -//! -//! Original comment: -//! ```glsl -//! // Created by inigo quilez - iq/2013 -//! // License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. -//! ``` - -use spirv_std::glam::{vec3, Vec2, Vec3, Vec4}; - -// Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but -// we tie #[no_std] above to the same condition, so it's fine. -#[cfg(target_arch = "spirv")] -use spirv_std::num_traits::Float; - -pub struct Inputs { - pub resolution: Vec3, - pub time: f32, -} - -impl Inputs { - fn f(&self, mut p: Vec3) -> f32 { - p.z += self.time; - (Vec3::splat(0.05 * (9. * p.y * p.x).cos()) + vec3(p.x.cos(), p.y.cos(), p.z.cos()) - - Vec3::splat(0.1 * (9. * (p.z + 0.3 * p.x - p.y)).cos())) - .length() - - 1. - } - - pub fn main_image(&self, c: &mut Vec4, p: Vec2) { - let d: Vec3 = Vec3::splat(0.5) - p.extend(1.0) / self.resolution.x; - let mut o: Vec3 = d; - for _ in 0..128 { - o += self.f(o) * d; - } - *c = ((self.f(o - d) * vec3(0.0, 1.0, 2.0) - + Vec3::splat(self.f(o - Vec3::splat(0.6))) * vec3(2.0, 1.0, 0.0)) - .abs() - * (1. - 0.1 * o.z)) - .extend(1.0); - } -} diff --git a/shaders/src/voxel_pac_man.rs b/shaders/src/voxel_pac_man.rs deleted file mode 100644 index 7f2efd6..0000000 --- a/shaders/src/voxel_pac_man.rs +++ /dev/null @@ -1,384 +0,0 @@ -//! Ported to Rust from -//! -//! Original comment: -//! ```glsl -//! /////////////////////////////////////////////////////////////////////////////// -//! // // -//! // GGGG IIIII AAA N N TTTTT PPPP AAA CCCC M M AAA N N // -//! // G I A A NN N T P P A A C MM MM A A NN N // -//! // G GG I AAAAA N N N T PPPP AAAAA C --- M M M AAAAA N N N // -//! // G G I A A N NN T P A A C M M A A N NN // -//! // GGGG IIIII A A N N T P A A CCCC M M A A N N // -//! // // -//! /////////////////////////////////////////////////////////////////////////////// -//! */ -//! ``` - -use shared::*; -use spirv_std::glam::{vec2, vec3, Mat3, Vec2, Vec2Swizzles, Vec3, Vec3Swizzles, Vec4}; - -// Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but -// we tie #[no_std] above to the same condition, so it's fine. -#[cfg(target_arch = "spirv")] -use spirv_std::num_traits::Float; - -pub struct Inputs { - pub resolution: Vec3, - pub time: f32, - pub mouse: Vec4, -} - -pub struct State { - inputs: Inputs, - - // Global variable to handle the glow effect - glow_counter: f32, -} - -impl State { - pub fn new(inputs: Inputs) -> State { - State { - inputs, - - glow_counter: 0.0, - } - } -} - -// Parameters -const VOXEL_RESOLUTION: f32 = 1.5; -const VOXEL_LIGHTING: bool = true; -const SHADOW: bool = true; -const GROUND: bool = true; -const GHOST: bool = true; -const MOUSE: bool = true; -const HSV2RGB_FAST: bool = true; -const HSV2RGB_SAFE: bool = false; - -const CAMERA_FOCAL_LENGTH: f32 = 8.0; -const DELTA: f32 = 0.01; -const RAY_LENGTH_MAX: f32 = 500.0; -const RAY_STEP_MAX: u32 = 100; -const AMBIENT: f32 = 0.2; -const SPECULAR_POWER: f32 = 2.0; -const SPECULAR_INTENSITY: f32 = 0.3; -const SHADOW_LENGTH: f32 = 150.0; -const SHADOW_POWER: f32 = 3.0; -const FADE_POWER: f32 = 1.0; -const BACKGROUND: f32 = 0.7; -const GLOW: f32 = 0.4; -const GAMMA: f32 = 0.8; - -// Math constants -const PI: f32 = 3.14159265359; -const SQRT3: f32 = 1.73205080757; - -// PRNG (from https://www.shadertoy.com/view/4djSRW) -fn rand(mut seed: Vec3) -> f32 { - seed = (seed * vec3(5.3983, 5.4427, 6.9371)).fract_gl(); - seed += Vec3::splat(seed.yzx().dot(seed + vec3(21.5351, 14.3137, 15.3219))); - (seed.x * seed.y * seed.z * 95.4337).fract_gl() -} - -impl State { - // Distance to the voxel - fn dist_voxel(&mut self, p: Vec3) -> f32 { - // Update the glow counter - self.glow_counter += 1.0; - - // Rounded box - let voxel_radius: f32 = 0.25; - (p.abs() + Vec3::splat(-0.5 + voxel_radius)) - .max(Vec3::ZERO) - .length() - - voxel_radius - } - - // Distance to the scene and color of the closest point - fn dist_scene(&mut self, mut p: Vec3, p2: &mut Vec3) -> Vec2 { - // Update the glow counter - self.glow_counter += 1.0; - - // Scaling - p *= VOXEL_RESOLUTION; - - // Velocity, period of the waves, spacing of the gums - let mut v: f32 = VOXEL_RESOLUTION * (self.inputs.time * 100.0 / VOXEL_RESOLUTION).floor(); - let k1: f32 = 0.05; - let k2: f32 = 60.0; - - // Giant Pac-Man - let mut body: f32 = p.length(); - body = (body - 32.0).max(27.0 - body); - let mut eyes: f32 = 6.0 - vec3(p.x.abs() - 12.5, p.y - 19.5, p.z - 20.0).length(); - let mut mouth_angle: f32 = PI * (0.07 + 0.07 * (2.0 * v * PI / k2).cos()); - let mouth_top: f32 = p.dot(vec3(0.0, -mouth_angle.cos(), mouth_angle.sin())) - 2.0; - mouth_angle *= 2.5; - let mouth_bottom: f32 = p.dot(vec3(0.0, mouth_angle.cos(), mouth_angle.sin())); - let pac_man: f32 = body.max(eyes).max(mouth_top.min(mouth_bottom)); - let mut d: Vec2 = vec2(pac_man, 0.13); - *p2 = p; - - // Gums - let mut q: Vec3 = p.xy().extend((p.z + v).rem_euclid(k2) - k2 * 0.5); - let gum: f32 = (q.length() - 6.0).max(-p.z); - if gum < d.x { - d = vec2(gum, 0.35); - *p2 = q; - } - - // Ground - if GROUND { - q = p.xy().extend(p.z + v); - let ground: f32 = (q.y + 50.0 + 14.0 * (q.x * k1).cos() * (q.z * k1).cos()) * 0.7; - if ground < d.x { - d = vec2(ground, 0.55); - *p2 = q; - } - } - - // Ghost - if GHOST { - v = VOXEL_RESOLUTION - * ((130.0 + 60.0 * (self.inputs.time * 3.0).cos()) / VOXEL_RESOLUTION).floor(); - q = p.xy().extend(p.z + v); - body = vec3(q.x, (q.y - 4.0).max(0.0), q.z).length(); - body = (body - 28.0).max(22.0 - body); - eyes = 8.0 - vec3(q.x.abs() - 12.0, q.y - 10.0, q.z - 22.0).length(); - let bottom: f32 = (q.y + 28.0 + 4.0 * (p.x * 0.4).cos() * (p.z * 0.4).cos()) * 0.7; - let ghost: f32 = body.max(eyes).max(-bottom); - if ghost < d.x { - d = vec2(ghost, 0.76); - *p2 = q; - } - } - - // Scaling - d.x /= VOXEL_RESOLUTION; - d - } - - // Distance to the (voxelized?) scene - fn dist(&mut self, p: &mut Vec3, ray: Vec3, voxelized: f32, ray_length_max: f32) -> Vec4 { - let mut p2: Vec3 = *p; - let mut d: Vec2 = vec2(1.0 / 0.0, 0.0); - let mut ray_length: f32 = 0.0; - let mut ray_length_in_voxel: f32 = 0.0; - let mut ray_length_check_voxel: f32 = 0.0; - let ray_sign: Vec3 = ray.sign_gl(); - let ray_delta_voxel: Vec3 = ray_sign / ray; - for _ in 0..RAY_STEP_MAX { - if ray_length < ray_length_in_voxel { - d.x = self.dist_voxel((*p + Vec3::splat(0.5)).fract_gl() - Vec3::splat(0.5)); - if d.x < DELTA { - break; - } - } else if ray_length < ray_length_check_voxel { - let mut ray_delta: Vec3 = (Vec3::splat(0.5) - - ray_sign * ((*p + Vec3::splat(0.5)).fract_gl() - Vec3::splat(0.5))) - * ray_delta_voxel; - let d_next: f32 = ray_delta.x.min(ray_delta.y.min(ray_delta.z)); - d = self.dist_scene((*p + Vec3::splat(0.5)).floor(), &mut p2); - if d.x < 0.0 { - ray_delta = ray_delta_voxel - ray_delta; - d.x = (ray_length_in_voxel - ray_length) - .max(DELTA - ray_delta.x.min(ray_delta.y.min(ray_delta.z))); - ray_length_in_voxel = ray_length + d_next; - } else { - d.x = DELTA + d_next; - } - } else { - d = self.dist_scene(*p, &mut p2); - if voxelized > 0.5 { - if d.x < SQRT3 * 0.5 { - ray_length_check_voxel = ray_length + d.x.abs() + SQRT3 * 0.5; - d.x = (ray_length_in_voxel - ray_length + DELTA).max(d.x - SQRT3 * 0.5); - } - } else if d.x < DELTA { - break; - } - } - ray_length += d.x; - if ray_length > ray_length_max { - break; - } - *p += d.x * ray; - } - d.extend(ray_length).extend(rand(p2)) - } - - // Normal at a given point - fn normal(&mut self, mut p: Vec3, voxelized: f32) -> Vec3 { - let h: Vec2 = vec2(DELTA, -DELTA); - let mut n: Vec3 = Vec3::ZERO; - if voxelized > 0.5 { - p = (p + Vec3::splat(0.5)).fract_gl() - Vec3::splat(0.5); - n = h.xxx() * self.dist_voxel(p + h.xxx()) - + h.xyy() * self.dist_voxel(p + h.xyy()) - + h.yxy() * self.dist_voxel(p + h.yxy()) - + h.yyx() * self.dist_voxel(p + h.yyx()); - } else { - n = h.xxx() * self.dist_scene(p + h.xxx(), &mut n).x - + h.xyy() * self.dist_scene(p + h.xyy(), &mut n).x - + h.yxy() * self.dist_scene(p + h.yxy(), &mut n).x - + h.yyx() * self.dist_scene(p + h.yyx(), &mut n).x; - } - n.normalize() - } -} - -// HSV to RGB -fn hsv2rgb(mut hsv: Vec3) -> Vec3 { - if HSV2RGB_SAFE { - hsv = hsv.yz().clamp(Vec2::ZERO, Vec2::ONE).extend(hsv.x).zxy(); - } - if HSV2RGB_FAST { - hsv.z - * (Vec3::ONE - + 0.5 - * hsv.y - * ((2.0 * PI * (Vec3::splat(hsv.x) + vec3(0.0, 2.0 / 3.0, 1.0 / 3.0))).cos() - - Vec3::ONE)) - } else { - hsv.z - * (Vec3::ONE - + Vec3::splat(hsv.y) - * (((Vec3::splat(hsv.x) + vec3(0.0, 2.0 / 3.0, 1.0 / 3.0)).fract_gl() * 6.0 - - Vec3::splat(3.0)) - .abs() - - Vec3::splat(2.0)) - .clamp(-Vec3::ONE, Vec3::ZERO)) - } -} - -impl State { - // Main function - pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) { - // Get the fragment - let frag: Vec2 = - (2.0 * frag_coord - self.inputs.resolution.xy()) / self.inputs.resolution.y; - - // Define the rendering mode - let mut mode_timing: f32 = self.inputs.time * 0.234; - let mut mode_angle: f32 = PI * (self.inputs.time * 0.2).cos(); - mode_angle = (frag - vec2((self.inputs.time * 2.0).cos(), 0.0)) - .dot(vec2(mode_angle.cos(), mode_angle.sin())); - let mut mode_voxel: f32 = 0.5.step((mode_timing / (4.0 * PI)).fract_gl()); - mode_timing = mode_timing.cos(); - let mode_3d: f32 = smoothstep(0.8, 0.5, mode_timing); - let mode_switch: f32 = smoothstep(0.995, 1.0, mode_timing) - + smoothstep(0.02, 0.0, mode_angle.abs()) * mode_voxel; - mode_voxel = 1.0 + (0.0.step(mode_angle) - 1.0) * mode_voxel; - - // Define the ray corresponding to this fragment - let mut ray: Vec3 = frag - .extend(mix(8.0, CAMERA_FOCAL_LENGTH, mode_3d)) - .normalize(); - - // Compute the orientation of the camera - let mut yaw_angle: f32 = PI * (1.2 + 0.2 * (self.inputs.time * 0.5).cos()); - let mut pitch_angle: f32 = PI * (0.1 * (self.inputs.time * 0.3).cos() - 0.05); - if MOUSE { - yaw_angle += 4.0 * PI * self.inputs.mouse.x / self.inputs.resolution.x; - pitch_angle += PI * 0.3 * (1.0 - self.inputs.mouse.y / self.inputs.resolution.y); - } - yaw_angle = mix(PI * 1.5, yaw_angle, mode_3d); - pitch_angle *= mode_3d; - - let cos_yaw: f32 = yaw_angle.cos(); - let sin_yaw: f32 = yaw_angle.sin(); - let cos_pitch: f32 = pitch_angle.cos(); - let sin_pitch: f32 = pitch_angle.sin(); - - let mut camera_orientation: Mat3 = Mat3::ZERO; - camera_orientation.x_axis = vec3(cos_yaw, 0.0, -sin_yaw); - camera_orientation.y_axis = vec3(sin_yaw * sin_pitch, cos_pitch, cos_yaw * sin_pitch); - camera_orientation.z_axis = vec3(sin_yaw * cos_pitch, -sin_pitch, cos_yaw * cos_pitch); - - ray = camera_orientation * ray; - - // Compute the origin of the ray - let camera_dist: f32 = mix( - 300.0, - 195.0 + 150.0 * (self.inputs.time * 0.8).cos(), - mode_3d, - ); - let mut origin: Vec3 = (vec3(0.0, 0.0, 40.0 * (self.inputs.time * 0.2).sin()) - - camera_orientation.z_axis * camera_dist) - / VOXEL_RESOLUTION; - - // Compute the distance to the scene - self.glow_counter = 0.0; - let d: Vec4 = self.dist( - &mut origin, - ray, - mode_voxel, - RAY_LENGTH_MAX / VOXEL_RESOLUTION, - ); - - // Set the background color - let mut final_color: Vec3 = hsv2rgb(vec3( - 0.2 * ray.y + 0.4 * mode_voxel - 0.37, - 1.0, - mode_3d * BACKGROUND, - )); - let glow_color: Vec3 = GLOW * vec3(1.0, 0.3, 0.0) * self.glow_counter / RAY_STEP_MAX as f32; - if d.x < DELTA { - // Set the object color - let mut color: Vec3 = hsv2rgb(vec3( - d.y + 0.1 * d.w * mode_voxel, - 0.5 + 0.5 * mode_voxel, - 1.0, - )); - - // Lighting - let l: Vec3 = mix( - vec3(1.0, 0.0, 0.0), - vec3(1.25 + (self.inputs.time * 0.2).cos(), 1.0, 1.0), - mode_3d, - ) - .normalize(); - if VOXEL_LIGHTING { - if mode_voxel > 0.5 { - let n: Vec3 = self.normal((origin + Vec3::splat(0.5)).floor(), 0.0); - let diffuse: f32 = n.dot(l).max(0.0); - let specular: f32 = - ray.reflect(n).dot(l).max(0.0).powf(SPECULAR_POWER) * SPECULAR_INTENSITY; - color = (AMBIENT + diffuse) * color + Vec3::splat(specular); - } - } - let n: Vec3 = self.normal(origin, mode_voxel); - let mut diffuse: f32 = n.dot(l); - let mut specular: f32; - if diffuse < 0.0 { - diffuse = 0.0; - specular = 0.0; - } else { - specular = ray.reflect(n).dot(l).max(0.0).powf(SPECULAR_POWER) * SPECULAR_INTENSITY; - if SHADOW { - origin += n * DELTA * 2.0; - let mut shadow: Vec4 = - self.dist(&mut origin, l, mode_voxel, SHADOW_LENGTH / VOXEL_RESOLUTION); - if shadow.x < DELTA { - shadow.z = (shadow.z * VOXEL_RESOLUTION / SHADOW_LENGTH) - .min(1.0) - .powf(SHADOW_POWER); - diffuse *= shadow.z; - specular *= shadow.z; - } - } - } - color = (AMBIENT + diffuse) * color + Vec3::splat(specular); - - // Fading - let fade: f32 = (1.0 - d.z * VOXEL_RESOLUTION / RAY_LENGTH_MAX) - .max(0.0) - .powf(FADE_POWER); - final_color = mix(final_color, color, fade); - } - - // Set the fragment color - final_color = mix(final_color.powf(GAMMA) + glow_color, Vec3::ONE, mode_switch); - *frag_color = final_color.extend(1.0); - } -} diff --git a/shared/Cargo.toml b/shared/Cargo.toml deleted file mode 100644 index 7659ff9..0000000 --- a/shared/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "shared" -version = "0.0.0" -authors = [] -edition = "2021" - -[dependencies] -spirv-std.workspace = true -bytemuck = { version = "1.20.0", features = ["derive"] } - -[lints] -workspace = true diff --git a/shared/src/lib.rs b/shared/src/lib.rs deleted file mode 100644 index f36ffe2..0000000 --- a/shared/src/lib.rs +++ /dev/null @@ -1,236 +0,0 @@ -//! Ported to Rust from https://github.com/Tw1ddle/Sky-Shader/blob/master/src/shaders/glsl/sky.fragment - -#![cfg_attr(target_arch = "spirv", no_std)] -#![feature(asm_experimental_arch)] - -use bytemuck::{Pod, Zeroable}; -use core::f32::consts::PI; -use core::ops::{Add, Mul, Sub}; -use spirv_std::glam::{vec2, vec3, vec4, Vec2, Vec3, Vec4}; - -// Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but -// we tie #[no_std] above to the same condition, so it's fine. -#[cfg(target_arch = "spirv")] -use spirv_std::num_traits::Float; - -#[repr(C)] -#[derive(Copy, Clone, Pod, Zeroable)] -#[allow(unused_attributes)] -pub struct ShaderConstants { - pub width: u32, - pub height: u32, - pub time: f32, - pub cursor_x: f32, - pub cursor_y: f32, - pub drag_start_x: f32, - pub drag_start_y: f32, - pub drag_end_x: f32, - pub drag_end_y: f32, - pub mouse_left_pressed: u32, - pub mouse_left_clicked: u32, -} - -pub fn saturate(x: f32) -> f32 { - x.max(0.0).min(1.0) -} - -/// Based on: https://seblagarde.wordpress.com/2014/12/01/inverse-trigonometric-functions-gpu-optimization-for-amd-gcn-architecture/ -pub fn acos_approx(v: f32) -> f32 { - let x = v.abs(); - let mut res = -0.155972 * x + 1.56467; // p(x) - res *= (1.0f32 - x).sqrt(); - - if v >= 0.0 { - res - } else { - PI - res - } -} - -pub fn smoothstep(edge0: f32, edge1: f32, x: f32) -> f32 { - // Scale, bias and saturate x to 0..1 range - let x = saturate((x - edge0) / (edge1 - edge0)); - // Evaluate polynomial - x * x * (3.0 - 2.0 * x) -} - -pub fn mix + Add + Sub, A: Copy>( - x: X, - y: X, - a: A, -) -> X { - x - x * a + y * a -} - -pub trait Clamp { - fn clamp(self, min: Self, max: Self) -> Self; -} - -impl Clamp for f32 { - fn clamp(self, min: Self, max: Self) -> Self { - self.max(min).min(max) - } -} - -pub trait FloatExt { - fn fract_gl(self) -> Self; - fn rem_euclid(self, rhs: Self) -> Self; - fn sign_gl(self) -> Self; - fn step(self, x: Self) -> Self; -} - -impl FloatExt for f32 { - fn fract_gl(self) -> f32 { - self - self.floor() - } - - fn rem_euclid(self, rhs: f32) -> f32 { - let r = self % rhs; - if r < 0.0 { - r + rhs.abs() - } else { - r - } - } - - fn sign_gl(self) -> f32 { - if self < 0.0 { - -1.0 - } else if self == 0.0 { - 0.0 - } else { - 1.0 - } - } - - fn step(self, x: f32) -> f32 { - if x < self { - 0.0 - } else { - 1.0 - } - } -} - -pub trait VecExt { - fn sin(self) -> Self; - fn cos(self) -> Self; - fn powf_vec(self, p: Self) -> Self; - fn sqrt(self) -> Self; - fn ln(self) -> Self; - fn step(self, other: Self) -> Self; - fn sign_gl(self) -> Self; -} - -impl VecExt for Vec2 { - fn sin(self) -> Vec2 { - vec2(self.x.sin(), self.y.sin()) - } - - fn cos(self) -> Vec2 { - vec2(self.x.cos(), self.y.cos()) - } - - fn powf_vec(self, p: Vec2) -> Vec2 { - vec2(self.x.powf(p.x), self.y.powf(p.y)) - } - - fn sqrt(self) -> Vec2 { - vec2(self.x.sqrt(), self.y.sqrt()) - } - - fn ln(self) -> Vec2 { - vec2(self.x.ln(), self.y.ln()) - } - - fn step(self, other: Vec2) -> Vec2 { - vec2(self.x.step(other.x), self.y.step(other.y)) - } - - fn sign_gl(self) -> Vec2 { - vec2(self.x.sign_gl(), self.y.sign_gl()) - } -} - -impl VecExt for Vec3 { - fn sin(self) -> Vec3 { - vec3(self.x.sin(), self.y.sin(), self.z.sin()) - } - - fn cos(self) -> Vec3 { - vec3(self.x.cos(), self.y.cos(), self.z.cos()) - } - - fn powf_vec(self, p: Vec3) -> Vec3 { - vec3(self.x.powf(p.x), self.y.powf(p.y), self.z.powf(p.z)) - } - - fn sqrt(self) -> Vec3 { - vec3(self.x.sqrt(), self.y.sqrt(), self.z.sqrt()) - } - - fn ln(self) -> Vec3 { - vec3(self.x.ln(), self.y.ln(), self.z.ln()) - } - - fn step(self, other: Vec3) -> Vec3 { - vec3( - self.x.step(other.x), - self.y.step(other.y), - self.z.step(other.z), - ) - } - - fn sign_gl(self) -> Vec3 { - vec3(self.x.sign_gl(), self.y.sign_gl(), self.z.sign_gl()) - } -} - -impl VecExt for Vec4 { - fn sin(self) -> Vec4 { - vec4(self.x.sin(), self.y.sin(), self.z.sin(), self.w.sin()) - } - - fn cos(self) -> Vec4 { - vec4(self.x.cos(), self.y.cos(), self.z.cos(), self.w.cos()) - } - - fn powf_vec(self, p: Vec4) -> Vec4 { - vec4( - self.x.powf(p.x), - self.y.powf(p.y), - self.z.powf(p.z), - self.w.powf(p.w), - ) - } - - fn sqrt(self) -> Vec4 { - vec4(self.x.sqrt(), self.y.sqrt(), self.z.sqrt(), self.w.sqrt()) - } - - fn ln(self) -> Vec4 { - vec4(self.x.ln(), self.y.ln(), self.z.ln(), self.w.ln()) - } - - fn step(self, other: Vec4) -> Vec4 { - vec4( - self.x.step(other.x), - self.y.step(other.y), - self.z.step(other.z), - self.w.step(other.w), - ) - } - - fn sign_gl(self) -> Vec4 { - vec4( - self.x.sign_gl(), - self.y.sign_gl(), - self.z.sign_gl(), - self.w.sign_gl(), - ) - } -} - -pub fn discard() { - unsafe { spirv_std::arch::demote_to_helper_invocation() } -} diff --git a/src/main.rs b/src/main.rs index cb11317..ab74555 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,346 +1,393 @@ use futures::executor::block_on; use ouroboros::self_referencing; -use std::error::Error; -use std::time::Instant; -use wgpu::{self, InstanceDescriptor}; -use wgpu::{include_spirv, include_spirv_raw}; -use winit::application::ApplicationHandler; -use winit::dpi::LogicalSize; -use winit::event::{ElementState, MouseButton, WindowEvent}; -use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; -use winit::keyboard::NamedKey; -use winit::window::{Window, WindowAttributes, WindowId}; +use shadertoys_shaders::{shaders::SHADER_DEFINITIONS, shared_data::ShaderConstants}; +use std::{error::Error, time::Instant}; +use wgpu::{self, include_spirv, include_spirv_raw, InstanceDescriptor}; +use winit::{ + application::ApplicationHandler, + dpi::LogicalSize, + event::{ElementState, KeyEvent, MouseButton, WindowEvent}, + event_loop::{ActiveEventLoop, ControlFlow, EventLoop}, + keyboard::{KeyCode, NamedKey, PhysicalKey}, + window::{Window, WindowAttributes, WindowId}, +}; #[self_referencing] struct WindowSurface { - window: Box, - #[borrows(window)] - #[covariant] - surface: wgpu::Surface<'this>, + window: Box, + #[borrows(window)] + #[covariant] + surface: wgpu::Surface<'this>, } struct ShaderToyApp { - device: Option, - queue: Option, - window_surface: Option, - config: Option, - render_pipeline: Option, - shader_module: Option, - close_requested: bool, - start: Instant, - // Mouse state. - cursor_x: f32, - cursor_y: f32, - drag_start_x: f32, - drag_start_y: f32, - drag_end_x: f32, - drag_end_y: f32, - mouse_left_pressed: bool, - mouse_left_clicked: bool, + device: Option, + queue: Option, + window_surface: Option, + config: Option, + render_pipeline: Option, + shader_module: Option, + close_requested: bool, + start: Instant, + + // UI state + grid_mode: bool, + shader_to_show: u32, + + // Mouse state. + cursor_x: f32, + cursor_y: f32, + drag_start_x: f32, + drag_start_y: f32, + drag_end_x: f32, + drag_end_y: f32, + mouse_left_pressed: bool, + mouse_left_clicked: bool, } impl Default for ShaderToyApp { - fn default() -> Self { - Self { - device: None, - queue: None, - window_surface: None, - config: None, - render_pipeline: None, - shader_module: None, - close_requested: false, - start: Instant::now(), - cursor_x: 0.0, - cursor_y: 0.0, - drag_start_x: 0.0, - drag_start_y: 0.0, - drag_end_x: 0.0, - drag_end_y: 0.0, - mouse_left_pressed: false, - mouse_left_clicked: false, - } + fn default() -> Self { + Self { + device: None, + queue: None, + window_surface: None, + config: None, + render_pipeline: None, + shader_module: None, + close_requested: false, + start: Instant::now(), + cursor_x: 0.0, + cursor_y: 0.0, + drag_start_x: 0.0, + drag_start_y: 0.0, + drag_end_x: 0.0, + drag_end_y: 0.0, + mouse_left_pressed: false, + mouse_left_clicked: false, + shader_to_show: 0, + grid_mode: false, } + } } impl ShaderToyApp { - async fn init(&mut self, event_loop: &dyn ActiveEventLoop) -> Result<(), Box> { - let window_attributes = WindowAttributes::default() - .with_title("Rust GPU - wgpu") - .with_surface_size(LogicalSize::new(1280.0, 720.0)); - let window_box = event_loop.create_window(window_attributes)?; - let mut instance_flags = wgpu::InstanceFlags::default(); - // Turn off validation as the shaders are trusted. - instance_flags.remove(wgpu::InstanceFlags::VALIDATION); - // Disable debugging info to speed things up. - instance_flags.remove(wgpu::InstanceFlags::DEBUG); - let instance = wgpu::Instance::new(&InstanceDescriptor { - flags: instance_flags, - ..Default::default() - }); - - let window_surface = WindowSurfaceBuilder { - window: window_box, - surface_builder: |window| { - instance - .create_surface(window) - .expect("Failed to create surface") - }, - } - .build(); + async fn init(&mut self, event_loop: &dyn ActiveEventLoop) -> Result<(), Box> { + let window_attributes = WindowAttributes::default() + .with_title("Rust GPU - wgpu") + .with_surface_size(LogicalSize::new(1280.0, 720.0)); + let window_box = event_loop.create_window(window_attributes)?; + let mut instance_flags = wgpu::InstanceFlags::default(); + // Turn off validation as the shaders are trusted. + instance_flags.remove(wgpu::InstanceFlags::VALIDATION); + // Disable debugging info to speed things up. + instance_flags.remove(wgpu::InstanceFlags::DEBUG); + let instance = wgpu::Instance::new(&InstanceDescriptor { + flags: instance_flags, + ..Default::default() + }); - let window_size = window_surface.borrow_window().surface_size(); - let surface = window_surface.borrow_surface(); + let window_surface = WindowSurfaceBuilder { + window: window_box, + surface_builder: |window| { + instance + .create_surface(window) + .expect("Failed to create surface") + }, + } + .build(); - let adapter = instance - .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::HighPerformance, - compatible_surface: Some(surface), - force_fallback_adapter: false, - }) - .await?; - let mut required_features = wgpu::Features::PUSH_CONSTANTS; - if adapter - .features() - .contains(wgpu::Features::SPIRV_SHADER_PASSTHROUGH) - { - required_features |= wgpu::Features::SPIRV_SHADER_PASSTHROUGH; - } - let required_limits = wgpu::Limits { - max_push_constant_size: 256, - ..Default::default() - }; - let (device, queue) = adapter - .request_device(&wgpu::DeviceDescriptor { - label: None, - required_features, - required_limits, - ..Default::default() - }) - .await?; - let shader_module = if device - .features() - .contains(wgpu::Features::SPIRV_SHADER_PASSTHROUGH) - { - let x = include_spirv_raw!(env!("shadertoys_shaders.spv")); - unsafe { device.create_shader_module_passthrough(x) } - } else { - device.create_shader_module(include_spirv!(env!("shadertoys_shaders.spv"))) - }; - let swapchain_format = surface.get_capabilities(&adapter).formats[0]; - let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: None, - bind_group_layouts: &[], - push_constant_ranges: &[wgpu::PushConstantRange { - stages: wgpu::ShaderStages::VERTEX_FRAGMENT, - range: 0..std::mem::size_of::() as u32, - }], - }); - let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: None, - layout: Some(&pipeline_layout), - vertex: wgpu::VertexState { - module: &shader_module, - entry_point: Some("main_vs"), - buffers: &[], - compilation_options: Default::default(), - }, - fragment: Some(wgpu::FragmentState { - module: &shader_module, - entry_point: Some("main_fs"), - targets: &[Some(wgpu::ColorTargetState { - format: swapchain_format, - blend: Some(wgpu::BlendState::REPLACE), - write_mask: wgpu::ColorWrites::ALL, - })], - compilation_options: Default::default(), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - ..Default::default() - }, - depth_stencil: None, - multisample: wgpu::MultisampleState::default(), - multiview: None, - cache: None, - }); - let config = wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: swapchain_format, - width: window_size.width, - height: window_size.height, - present_mode: wgpu::PresentMode::Fifo, - alpha_mode: wgpu::CompositeAlphaMode::Auto, - view_formats: vec![], - desired_maximum_frame_latency: Default::default(), - }; - surface.configure(&device, &config); + let window_size = window_surface.borrow_window().surface_size(); + let surface = window_surface.borrow_surface(); - self.device = Some(device); - self.queue = Some(queue); - self.window_surface = Some(window_surface); - self.config = Some(config); - self.render_pipeline = Some(render_pipeline); - self.shader_module = Some(shader_module); - self.start = Instant::now(); - Ok(()) + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::HighPerformance, + compatible_surface: Some(surface), + force_fallback_adapter: false, + }) + .await?; + let mut required_features = wgpu::Features::PUSH_CONSTANTS; + if adapter + .features() + .contains(wgpu::Features::SPIRV_SHADER_PASSTHROUGH) + { + required_features |= wgpu::Features::SPIRV_SHADER_PASSTHROUGH; } + let required_limits = wgpu::Limits { + max_push_constant_size: 256, + ..Default::default() + }; + let (device, queue) = adapter + .request_device(&wgpu::DeviceDescriptor { + label: None, + required_features, + required_limits, + ..Default::default() + }) + .await?; + let shader_module = if device + .features() + .contains(wgpu::Features::SPIRV_SHADER_PASSTHROUGH) + { + let x = include_spirv_raw!(env!("shadertoys_shaders.spv")); + unsafe { device.create_shader_module_passthrough(x) } + } else { + device.create_shader_module(include_spirv!(env!("shadertoys_shaders.spv"))) + }; + let swapchain_format = surface.get_capabilities(&adapter).formats[0]; + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[], + push_constant_ranges: &[wgpu::PushConstantRange { + stages: wgpu::ShaderStages::VERTEX_FRAGMENT, + range: 0..std::mem::size_of::() as u32, + }], + }); + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: None, + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &shader_module, + entry_point: Some("main_vs"), + buffers: &[], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader_module, + entry_point: Some("main_fs"), + targets: &[Some(wgpu::ColorTargetState { + format: swapchain_format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: Default::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + cache: None, + }); + let config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: swapchain_format, + width: window_size.width, + height: window_size.height, + present_mode: wgpu::PresentMode::Fifo, + alpha_mode: wgpu::CompositeAlphaMode::Auto, + view_formats: vec![], + desired_maximum_frame_latency: Default::default(), + }; + surface.configure(&device, &config); - fn render(&mut self) { - let window_surface = match &self.window_surface { - Some(ws) => ws, - None => return, - }; + self.device = Some(device); + self.queue = Some(queue); + self.window_surface = Some(window_surface); + self.config = Some(config); + self.render_pipeline = Some(render_pipeline); + self.shader_module = Some(shader_module); + self.start = Instant::now(); + Ok(()) + } - let window = window_surface.borrow_window(); - let current_size = window.surface_size(); - let surface = window_surface.borrow_surface(); - let device = self.device.as_ref().unwrap(); - let queue = self.queue.as_ref().unwrap(); - let frame = match surface.get_current_texture() { - Ok(frame) => frame, - Err(e) => { - eprintln!("Failed to acquire texture: {:?}", e); - return; - } - }; - let view = frame - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); - let mut encoder = - device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); - { - let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: None, - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - rpass.set_viewport( - 0.0, - 0.0, - current_size.width as f32, - current_size.height as f32, - 0.0, - 1.0, - ); - let push_constants = shared::ShaderConstants { - width: current_size.width, - height: current_size.height, - time: self.start.elapsed().as_secs_f32(), - cursor_x: self.cursor_x, - cursor_y: self.cursor_y, - drag_start_x: self.drag_start_x, - drag_start_y: self.drag_start_y, - drag_end_x: self.drag_end_x, - drag_end_y: self.drag_end_y, - mouse_left_pressed: self.mouse_left_pressed as u32, - mouse_left_clicked: self.mouse_left_clicked as u32, - }; - self.mouse_left_clicked = false; - rpass.set_pipeline(self.render_pipeline.as_ref().unwrap()); - rpass.set_push_constants( - wgpu::ShaderStages::VERTEX_FRAGMENT, - 0, - bytemuck::bytes_of(&push_constants), - ); - rpass.draw(0..3, 0..1); - } - queue.submit(Some(encoder.finish())); - frame.present(); + fn render(&mut self) { + let window_surface = match &self.window_surface { + Some(ws) => ws, + None => return, + }; + + let window = window_surface.borrow_window(); + let current_size = window.surface_size(); + let surface = window_surface.borrow_surface(); + let device = self.device.as_ref().unwrap(); + let queue = self.queue.as_ref().unwrap(); + let frame = match surface.get_current_texture() { + Ok(frame) => frame, + Err(e) => { + eprintln!("Failed to acquire texture: {:?}", e); + return; + }, + }; + let view = frame + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + let mut encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + { + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + rpass.set_viewport( + 0.0, + 0.0, + current_size.width as f32, + current_size.height as f32, + 0.0, + 1.0, + ); + let push_constants = ShaderConstants { + width: current_size.width, + height: current_size.height, + time: self.start.elapsed().as_secs_f32(), + cursor_x: self.cursor_x, + cursor_y: self.cursor_y, + drag_start_x: self.drag_start_x, + drag_start_y: self.drag_start_y, + drag_end_x: self.drag_end_x, + drag_end_y: self.drag_end_y, + mouse_left_pressed: self.mouse_left_pressed as u32, + mouse_left_clicked: self.mouse_left_clicked as u32, + shader_to_show: self.shader_to_show, + grid_mode: self.grid_mode as u32, + }; + self.mouse_left_clicked = false; + rpass.set_pipeline(self.render_pipeline.as_ref().unwrap()); + rpass.set_push_constants( + wgpu::ShaderStages::VERTEX_FRAGMENT, + 0, + bytemuck::bytes_of(&push_constants), + ); + rpass.draw(0..3, 0..1); } + queue.submit(Some(encoder.finish())); + frame.present(); + } } impl ApplicationHandler for ShaderToyApp { - fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) { - if let Err(e) = block_on(self.init(event_loop)) { - eprintln!("Initialization error: {e}"); - event_loop.exit(); - } + fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) { + if let Err(e) = block_on(self.init(event_loop)) { + eprintln!("Initialization error: {e}"); + event_loop.exit(); } + } - fn window_event( - &mut self, - event_loop: &dyn ActiveEventLoop, - _window_id: WindowId, - event: WindowEvent, - ) { - match event { - WindowEvent::CloseRequested => self.close_requested = true, - WindowEvent::SurfaceResized(new_size) => { - if let Some(config) = self.config.as_mut() { - config.width = new_size.width; - config.height = new_size.height; - if let Some(ws) = &self.window_surface { - let surface = ws.borrow_surface(); - if let Some(device) = self.device.as_ref() { - surface.configure(device, config); - } - } - } - } - WindowEvent::PointerMoved { position, .. } => { - self.cursor_x = position.x as f32; - self.cursor_y = position.y as f32; - if self.mouse_left_pressed { - self.drag_end_x = self.cursor_x; - self.drag_end_y = self.cursor_y; - } - } - WindowEvent::PointerButton { state, button, .. } => { - if button.mouse_button() == MouseButton::Left { - self.mouse_left_pressed = state == ElementState::Pressed; - if self.mouse_left_pressed { - self.drag_start_x = self.cursor_x; - self.drag_start_y = self.cursor_y; - self.drag_end_x = self.cursor_x; - self.drag_end_y = self.cursor_y; - self.mouse_left_clicked = true; - } - } - } - WindowEvent::MouseWheel { delta, .. } => { - if let winit::event::MouseScrollDelta::LineDelta(x, y) = delta { - self.drag_end_x = x * 0.1; - self.drag_end_y = y * 0.1; - } - } - WindowEvent::KeyboardInput { event, .. } => { - if event.logical_key == NamedKey::Escape && event.state == ElementState::Pressed { - self.close_requested = true; - } + fn window_event( + &mut self, + event_loop: &dyn ActiveEventLoop, + _window_id: WindowId, + event: WindowEvent, + ) { + match event { + WindowEvent::CloseRequested => self.close_requested = true, + WindowEvent::SurfaceResized(new_size) => { + if let Some(config) = self.config.as_mut() { + config.width = new_size.width; + config.height = new_size.height; + if let Some(ws) = &self.window_surface { + let surface = ws.borrow_surface(); + if let Some(device) = self.device.as_ref() { + surface.configure(device, config); } - WindowEvent::RedrawRequested => self.render(), - _ => {} + } } - if self.close_requested { - event_loop.exit(); - } else if let Some(ws) = &self.window_surface { - ws.borrow_window().request_redraw(); + }, + WindowEvent::PointerMoved { position, .. } => { + self.cursor_x = position.x as f32; + self.cursor_y = position.y as f32; + if self.mouse_left_pressed { + self.drag_end_x = self.cursor_x; + self.drag_end_y = self.cursor_y; } - event_loop.set_control_flow(ControlFlow::Poll); + }, + WindowEvent::PointerButton { state, button, .. } => { + if button.mouse_button() == MouseButton::Left { + self.mouse_left_pressed = state == ElementState::Pressed; + if self.mouse_left_pressed { + self.drag_start_x = self.cursor_x; + self.drag_start_y = self.cursor_y; + self.drag_end_x = self.cursor_x; + self.drag_end_y = self.cursor_y; + self.mouse_left_clicked = true; + } + } + }, + WindowEvent::MouseWheel { delta, .. } => { + if let winit::event::MouseScrollDelta::LineDelta(delta_x, delta_y) = delta { + self.drag_end_x = delta_x * 0.1; + self.drag_end_y = delta_y * 0.1; + } + }, + WindowEvent::KeyboardInput { event, .. } => match event { + KeyEvent { + state: ElementState::Pressed, + .. + } if event.logical_key == NamedKey::Escape => { + self.close_requested = true; + }, + KeyEvent { + state: ElementState::Pressed, + physical_key: PhysicalKey::Code(KeyCode::KeyE), + .. + } => { + self.grid_mode = false; + self.shader_to_show = (self.shader_to_show + 1) % SHADER_DEFINITIONS.len() as u32; + println!( + "Shader to show: {}", + SHADER_DEFINITIONS[self.shader_to_show as usize].name + ); + }, + KeyEvent { + state: ElementState::Pressed, + physical_key: PhysicalKey::Code(KeyCode::KeyQ), + .. + } => { + self.grid_mode = false; + self.shader_to_show = (self.shader_to_show + SHADER_DEFINITIONS.len() as u32 - 1) + % SHADER_DEFINITIONS.len() as u32; + println!( + "Shader to show: {}", + SHADER_DEFINITIONS[self.shader_to_show as usize].name + ); + }, + KeyEvent { + state: ElementState::Pressed, + physical_key: PhysicalKey::Code(KeyCode::KeyG), + .. + } => { + self.grid_mode = !self.grid_mode; + println!("Grid mode: {}", self.grid_mode); + }, + _ => {}, + }, + WindowEvent::RedrawRequested => self.render(), + _ => {}, } + if self.close_requested { + event_loop.exit(); + } else if let Some(ws) = &self.window_surface { + ws.borrow_window().request_redraw(); + } + event_loop.set_control_flow(ControlFlow::Poll); + } - fn about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) { - if self.close_requested { - event_loop.exit(); - } else if let Some(ws) = &self.window_surface { - ws.borrow_window().request_redraw(); - } - event_loop.set_control_flow(ControlFlow::Poll); + fn about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) { + if self.close_requested { + event_loop.exit(); + } else if let Some(ws) = &self.window_surface { + ws.borrow_window().request_redraw(); } + event_loop.set_control_flow(ControlFlow::Poll); + } } fn main() -> Result<(), Box> { - env_logger::init(); - let event_loop = EventLoop::new()?; - let mut app = ShaderToyApp::default(); - event_loop.run_app(&mut app).map_err(Into::into) + env_logger::init(); + let event_loop = EventLoop::new()?; + let mut app = ShaderToyApp::default(); + event_loop.run_app(&mut app).map_err(Into::into) }