Skip to content

New shadertoys features #40

New issue

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

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

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/target
rustc-ice-*.txt
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"rust-analyzer.cargo.targetDir": true,
//"rust-analyzer.check.command": "clippy",
}
16 changes: 4 additions & 12 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

82 changes: 73 additions & 9 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -11,25 +11,22 @@ 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"

[build-dependencies]
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"))',
] }
12 changes: 6 additions & 6 deletions build.rs
Original file line number Diff line number Diff line change
@@ -2,14 +2,14 @@ use spirv_builder::{MetadataPrintout, SpirvBuilder};
use std::error::Error;

fn build_shader(path_to_crate: &str) -> Result<(), Box<dyn Error>> {
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<dyn Error>> {
build_shader("shaders")?;
Ok(())
build_shader("shaders")?;
Ok(())
}
1 change: 1 addition & 0 deletions clippy.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
check-private-items = true
13 changes: 13 additions & 0 deletions rustfmt.toml
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion shaders/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
310 changes: 0 additions & 310 deletions shaders/src/a_lot_of_spheres.rs

This file was deleted.

316 changes: 0 additions & 316 deletions shaders/src/a_question_of_time.rs

This file was deleted.

185 changes: 0 additions & 185 deletions shaders/src/apollonian.rs

This file was deleted.

286 changes: 0 additions & 286 deletions shaders/src/atmosphere_system_test.rs

This file was deleted.

628 changes: 0 additions & 628 deletions shaders/src/bubble_buckey_balls.rs

This file was deleted.

142 changes: 0 additions & 142 deletions shaders/src/clouds.rs

This file was deleted.

425 changes: 0 additions & 425 deletions shaders/src/filtering_procedurals.rs

This file was deleted.

1,243 changes: 0 additions & 1,243 deletions shaders/src/flappy_bird.rs

This file was deleted.

71 changes: 0 additions & 71 deletions shaders/src/galaxy_of_universes.rs

This file was deleted.

798 changes: 0 additions & 798 deletions shaders/src/geodesic_tiling.rs

This file was deleted.

64 changes: 0 additions & 64 deletions shaders/src/heart.rs

This file was deleted.

345 changes: 121 additions & 224 deletions shaders/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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);
}
604 changes: 0 additions & 604 deletions shaders/src/luminescence.rs

This file was deleted.

93 changes: 0 additions & 93 deletions shaders/src/mandelbrot_smooth.rs

This file was deleted.

372 changes: 0 additions & 372 deletions shaders/src/miracle_snowflakes.rs

This file was deleted.

293 changes: 0 additions & 293 deletions shaders/src/morphing.rs

This file was deleted.

39 changes: 0 additions & 39 deletions shaders/src/moving_square.rs

This file was deleted.

436 changes: 0 additions & 436 deletions shaders/src/on_off_spikes.rs

This file was deleted.

98 changes: 0 additions & 98 deletions shaders/src/phantom_star.rs

This file was deleted.

128 changes: 0 additions & 128 deletions shaders/src/playing_marble.rs

This file was deleted.

225 changes: 0 additions & 225 deletions shaders/src/protean_clouds.rs

This file was deleted.

844 changes: 0 additions & 844 deletions shaders/src/raymarching_primitives.rs

This file was deleted.

251 changes: 0 additions & 251 deletions shaders/src/seascape.rs

This file was deleted.

324 changes: 324 additions & 0 deletions shaders/src/shader_prelude.rs
Original file line number Diff line number Diff line change
@@ -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<X: Copy + Mul<A, Output = X> + Add<Output = X> + Sub<Output = X>, 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() }
}
318 changes: 318 additions & 0 deletions shaders/src/shaders/a_lot_of_spheres.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,318 @@
//! Ported to Rust from <https://www.shadertoy.com/view/lsX3WH>
//!
//! 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);
}
}
329 changes: 329 additions & 0 deletions shaders/src/shaders/a_question_of_time.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@
//! Ported to Rust from <https://www.shadertoy.com/view/lljfRD>
//!
//! 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);
}
}
201 changes: 201 additions & 0 deletions shaders/src/shaders/apollonian.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
//! Ported to Rust from <https://www.shadertoy.com/view/4ds3zn>
//!
//! 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);
}
}
303 changes: 303 additions & 0 deletions shaders/src/shaders/atmosphere_system_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
//! Ported to Rust from <https://www.shadertoy.com/view/XtBXDz>
//!
//! 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);
}
}
645 changes: 645 additions & 0 deletions shaders/src/shaders/bubble_buckey_balls.rs

Large diffs are not rendered by default.

153 changes: 153 additions & 0 deletions shaders/src/shaders/clouds.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
//! Ported to Rust from <https://www.shadertoy.com/view/4tdSWr>
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);
}
}
439 changes: 439 additions & 0 deletions shaders/src/shaders/filtering_procedurals.rs

Large diffs are not rendered by default.

1,253 changes: 1,253 additions & 0 deletions shaders/src/shaders/flappy_bird.rs

Large diffs are not rendered by default.

80 changes: 80 additions & 0 deletions shaders/src/shaders/galaxy_of_universes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//! Ported to Rust from <https://www.shadertoy.com/view/MdXSzS>
//!
//! 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);
}
}
810 changes: 810 additions & 0 deletions shaders/src/shaders/geodesic_tiling.rs

Large diffs are not rendered by default.

71 changes: 71 additions & 0 deletions shaders/src/shaders/heart.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//! Ported to Rust from <https://www.shadertoy.com/view/XsfGRn>
//!
//! 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);
}
}
610 changes: 610 additions & 0 deletions shaders/src/shaders/luminescence.rs

Large diffs are not rendered by default.

102 changes: 102 additions & 0 deletions shaders/src/shaders/mandelbrot_smooth.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
//! Ported to Rust from <https://www.shadertoy.com/view/4df3Rn>
//!
//! 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);
}
}
385 changes: 385 additions & 0 deletions shaders/src/shaders/miracle_snowflakes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,385 @@
//! Ported to Rust from <https://www.shadertoy.com/view/Xsd3zf>
//!
//! 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<a {a=a1;}
} else {
a = noise2(rad * 10.3);
}
a - 0.5
}
}
impl State {
fn dist_obj(&self, pos: Vec3, mut ray: Vec3, mut r: f32, seed: Vec2) -> 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);
}
}
89 changes: 89 additions & 0 deletions shaders/src/shaders/mod.rs
Original file line number Diff line number Diff line change
@@ -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,
);
Loading