Skip to content

Commit f06db4d

Browse files
authored
Merge pull request #14 from LykenSol/MslSDN
Ported "Morphing" (https://www.shadertoy.com/view/MslSDN).
2 parents d49d332 + 5153c4a commit f06db4d

File tree

2 files changed

+316
-1
lines changed

2 files changed

+316
-1
lines changed

shaders/src/lib.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ pub mod galaxy_of_universes;
1616
pub mod heart;
1717
pub mod mandelbrot_smooth;
1818
pub mod miracle_snowflakes;
19+
pub mod morphing;
1920
pub mod phantom_star;
2021
pub mod playing_marble;
2122
pub mod protean_clouds;
@@ -52,7 +53,7 @@ impl Channel for RgbCube {
5253

5354
pub fn fs(constants: &ShaderConstants, mut frag_coord: Vec2) -> Vec4 {
5455
const COLS: usize = 4;
55-
const ROWS: usize = 4;
56+
const ROWS: usize = 5;
5657

5758
let resolution = vec3(
5859
constants.width as f32 / COLS as f32,
@@ -138,6 +139,12 @@ pub fn fs(constants: &ShaderConstants, mut frag_coord: Vec2) -> Vec4 {
138139
mouse,
139140
})
140141
.main_image(&mut color, frag_coord),
142+
16 => morphing::State::new(morphing::Inputs {
143+
resolution,
144+
time,
145+
mouse,
146+
})
147+
.main_image(&mut color, frag_coord),
141148
_ => {}
142149
}
143150
pow(color.truncate(), 2.2).extend(color.w)

shaders/src/morphing.rs

Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
//! Ported to Rust from <https://www.shadertoy.com/view/MslSDN>
2+
//!
3+
//! Original comment:
4+
//! ```glsl
5+
//! // Created by Sebastien Durand - 2014
6+
//! // License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
7+
//! ```
8+
9+
use shared::*;
10+
use spirv_std::glam::{
11+
const_vec3, vec2, vec3, Mat2, Vec2, Vec2Swizzles, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles,
12+
};
13+
14+
// Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but
15+
// we tie #[no_std] above to the same condition, so it's fine.
16+
#[cfg(target_arch = "spirv")]
17+
use spirv_std::num_traits::Float;
18+
19+
pub struct Inputs {
20+
pub resolution: Vec3,
21+
pub time: f32,
22+
pub mouse: Vec4,
23+
}
24+
25+
pub struct State {
26+
inputs: Inputs,
27+
28+
a: [Vec2; 15],
29+
t1: [Vec2; 5],
30+
t2: [Vec2; 5],
31+
32+
l: Vec3,
33+
34+
t_morph: f32,
35+
mat2_rot: Mat2,
36+
}
37+
38+
impl State {
39+
pub fn new(inputs: Inputs) -> Self {
40+
State {
41+
inputs,
42+
43+
a: [
44+
Vec2::zero(),
45+
Vec2::zero(),
46+
Vec2::zero(),
47+
Vec2::zero(),
48+
Vec2::zero(),
49+
Vec2::zero(),
50+
Vec2::zero(),
51+
Vec2::zero(),
52+
Vec2::zero(),
53+
Vec2::zero(),
54+
Vec2::zero(),
55+
Vec2::zero(),
56+
Vec2::zero(),
57+
Vec2::zero(),
58+
Vec2::zero(),
59+
],
60+
t1: [
61+
Vec2::zero(),
62+
Vec2::zero(),
63+
Vec2::zero(),
64+
Vec2::zero(),
65+
Vec2::zero(),
66+
],
67+
t2: [
68+
Vec2::zero(),
69+
Vec2::zero(),
70+
Vec2::zero(),
71+
Vec2::zero(),
72+
Vec2::zero(),
73+
],
74+
75+
l: vec3(1.0, 0.72, 1.0).normalize(),
76+
77+
t_morph: 0.0,
78+
mat2_rot: Mat2::zero(),
79+
}
80+
}
81+
}
82+
83+
fn u(a: Vec2, b: Vec2) -> f32 {
84+
a.x * b.y - b.x * a.y
85+
}
86+
87+
const Y: Vec3 = const_vec3!([0.0, 1.0, 0.0]);
88+
// const E: Vec3 = Y * 0.01;
89+
const _E: Vec3 = const_vec3!([0.0, 0.01, 0.0]);
90+
91+
// Distance to Bezier
92+
// inspired by [iq:https://www.shadertoy.com/view/ldj3Wh]
93+
// calculate distance to 2D bezier curve on xy but without forgeting the z component of p
94+
// total distance is corrected using pytagore just before return
95+
fn bezier(mut m: Vec2, mut n: Vec2, mut o: Vec2, p: Vec3) -> Vec2 {
96+
let q: Vec2 = p.xy();
97+
m -= q;
98+
n -= q;
99+
o -= q;
100+
let x: f32 = u(m, o);
101+
let y: f32 = 2.0 * u(n, m);
102+
let z: f32 = 2.0 * u(o, n);
103+
let i: Vec2 = o - m;
104+
let j: Vec2 = o - n;
105+
let k: Vec2 = n - m;
106+
let s: Vec2 = 2. * (x * i + y * j + z * k);
107+
let mut r: Vec2 = m + (y * z - x * x) * vec2(s.y, -s.x) / s.dot(s);
108+
let t: f32 = ((u(r, i) + 2.0 * u(k, r)) / (x + x + y + z)).clamp(0.0, 1.0); // parametric position on curve
109+
r = m + t * (k + k + t * (j - k)); // distance on 2D xy space
110+
vec2((r.dot(r) + p.z * p.z).sqrt(), t) // distance on 3D space
111+
}
112+
113+
fn smin(a: f32, b: f32, k: f32) -> f32 {
114+
let h: f32 = (0.5 + 0.5 * (b - a) / k).clamp(0.0, 1.0);
115+
mix(b, a, h) - k * h * (1. - h)
116+
}
117+
118+
impl State {
119+
// Distance to scene
120+
fn m(&self, mut p: Vec3) -> f32 {
121+
// Distance to Teapot ---------------------------------------------------
122+
// precalcul first part of teapot spout
123+
let h: Vec2 = bezier(self.t1[2], self.t1[3], self.t1[4], p);
124+
let mut a: f32 = 99.0;
125+
// distance to teapot handle (-.06 => make the thickness)
126+
let b: f32 = (bezier(self.t2[0], self.t2[1], self.t2[2], p)
127+
.x
128+
.min(bezier(self.t2[2], self.t2[3], self.t2[4], p).x)
129+
- 0.06)
130+
// max p.y-.9 => cut the end of the spout
131+
.min(
132+
(p.y - 0.9).max(
133+
// distance to second part of teapot spout (abs(dist,r1)-dr) => enable to make the spout hole
134+
((bezier(self.t1[0], self.t1[1], self.t1[2], p).x - 0.07).abs() - 0.01)
135+
// distance to first part of teapot spout (tickness incrase with pos on curve)
136+
.min(h.x * (1. - 0.75 * h.y) - 0.08),
137+
),
138+
);
139+
140+
// distance to teapot body => use rotation symetry to simplify calculation to a distance to 2D bezier curve
141+
let qq: Vec3 = vec3((p.dot(p) - p.y * p.y).sqrt(), p.y, 0.0);
142+
// the substraction of .015 enable to generate a small thickness arround bezier to help convergance
143+
// the .8 factor help convergance
144+
let mut i = 0;
145+
while i < 13 {
146+
a = a.min((bezier(self.a[i], self.a[i + 1], self.a[i + 2], qq).x - 0.015) * 0.7);
147+
i += 2;
148+
}
149+
// smooth minimum to improve quality at junction of handle and spout to the body
150+
let d_teapot: f32 = smin(a, b, 0.02);
151+
152+
// Distance to other shapes ---------------------------------------------
153+
let mut d_shape: f32;
154+
let id_morph: i32 = ((0.5 + (self.inputs.time) / (2.0 * 3.141592658)).floor() % 3.0) as i32;
155+
156+
if id_morph == 1 {
157+
p = (self.mat2_rot.transpose() * p.xz()).extend(p.y).xzy();
158+
let d: Vec3 = (p - vec3(0.0, 0.5, 0.0)).abs() - vec3(0.8, 0.7, 0.8);
159+
d_shape = d.x.max(d.y.max(d.z)).min(0.0) + d.max(Vec3::zero()).length();
160+
} else if id_morph == 2 {
161+
p -= vec3(0.0, 0.55, 0.0);
162+
let d1: Vec3 = p.abs() - vec3(0.67, 0.67, 0.67 * 1.618);
163+
let d3: Vec3 = p.abs() - vec3(0.67 * 1.618, 0.67, 0.67);
164+
d_shape = d1.x.max(d1.y.max(d1.z)).min(0.0) + d1.max(Vec3::zero()).length();
165+
d_shape =
166+
d_shape.min(d3.x.max(d3.y.max(d3.z)).min(0.0) + d3.max(Vec3::zero()).length());
167+
} else {
168+
d_shape = (p - vec3(0.0, 0.45, 0.0)).length() - 1.1;
169+
}
170+
171+
// !!! The morphing is here !!!
172+
mix(d_teapot, d_shape, self.t_morph.abs())
173+
}
174+
}
175+
176+
// HSV to RGB conversion
177+
// [iq: https://www.shadertoy.com/view/MsS3Wc]
178+
fn hsv2rgb_smooth(x: f32, y: f32, z: f32) -> Vec3 {
179+
let mut rgb: Vec3 =
180+
(((x * Vec3::splat(6.0) + vec3(0.0, 4.0, 2.0)).rem_euclid(6.0) - Vec3::splat(3.0)).abs()
181+
- Vec3::one())
182+
.clamp(Vec3::zero(), Vec3::one());
183+
rgb = rgb * rgb * (Vec3::splat(3.0) - 2.0 * rgb); // cubic smoothing
184+
z * mix(Vec3::one(), rgb, y)
185+
}
186+
187+
impl State {
188+
fn normal(&self, p: Vec3, ray: Vec3, t: f32) -> Vec3 {
189+
let pitch: f32 = 0.4 * t / self.inputs.resolution.x;
190+
let d: Vec2 = vec2(-1.0, 1.0) * pitch;
191+
// tetrahedral offsets
192+
let p0: Vec3 = p + d.xxx();
193+
let p1: Vec3 = p + d.xyy();
194+
let p2: Vec3 = p + d.yxy();
195+
let p3: Vec3 = p + d.yyx();
196+
let f0: f32 = self.m(p0);
197+
let f1: f32 = self.m(p1);
198+
let f2: f32 = self.m(p2);
199+
let f3: f32 = self.m(p3);
200+
let grad: Vec3 = p0 * f0 + p1 * f1 + p2 * f2 + p3 * f3 - p * (f0 + f1 + f2 + f3);
201+
// prevent normals pointing away from camera (caused by precision errors)
202+
(grad - (grad.dot(ray).max(0.0)) * ray).normalize()
203+
}
204+
205+
pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) {
206+
let aa: f32 = 3.14159 / 4.0;
207+
self.mat2_rot = Mat2::from_cols_array(&[aa.cos(), aa.sin(), -aa.sin(), aa.cos()]);
208+
209+
// Morphing step
210+
self.t_morph = (self.inputs.time * 0.5).cos();
211+
self.t_morph *= self.t_morph * self.t_morph * self.t_morph * self.t_morph;
212+
213+
// Teapot body profil (8 quadratic curves)
214+
self.a[0] = vec2(0.0, 0.0);
215+
self.a[1] = vec2(0.64, 0.0);
216+
self.a[2] = vec2(0.64, 0.03);
217+
self.a[3] = vec2(0.8, 0.12);
218+
self.a[4] = vec2(0.8, 0.3);
219+
self.a[5] = vec2(0.8, 0.48);
220+
self.a[6] = vec2(0.64, 0.9);
221+
self.a[7] = vec2(0.6, 0.93);
222+
self.a[8] = vec2(0.56, 0.9);
223+
self.a[9] = vec2(0.56, 0.96);
224+
self.a[10] = vec2(0.12, 1.02);
225+
self.a[11] = vec2(0.0, 1.05);
226+
self.a[12] = vec2(0.16, 1.14);
227+
self.a[13] = vec2(0.2, 1.2);
228+
self.a[14] = vec2(0.0, 1.2);
229+
// Teapot spout (2 quadratic curves)
230+
self.t1[0] = vec2(1.16, 0.96);
231+
self.t1[1] = vec2(1.04, 0.9);
232+
self.t1[2] = vec2(1.0, 0.72);
233+
self.t1[3] = vec2(0.92, 0.48);
234+
self.t1[4] = vec2(0.72, 0.42);
235+
// Teapot handle (2 quadratic curves)
236+
self.t2[0] = vec2(-0.6, 0.78);
237+
self.t2[1] = vec2(-1.16, 0.84);
238+
self.t2[2] = vec2(-1.16, 0.63);
239+
self.t2[3] = vec2(-1.2, 0.42);
240+
self.t2[4] = vec2(-0.72, 0.24);
241+
242+
// Configure camera
243+
let r: Vec2 = self.inputs.resolution.xy();
244+
let m: Vec2 = self.inputs.mouse.xy() / r;
245+
let q: Vec2 = frag_coord / r;
246+
let mut p: Vec2 = q + q - Vec2::one();
247+
p.x *= r.x / r.y;
248+
let mut j: f32 = 0.0;
249+
let mut s: f32 = 1.0;
250+
let mut h: f32 = 0.1;
251+
let mut t: f32 = 5.0 + 0.2 * self.inputs.time + 4.0 * m.x;
252+
let o: Vec3 = 2.9 * vec3(t.cos(), 0.7 - m.y, t.sin());
253+
let w: Vec3 = (Y * 0.4 - o).normalize();
254+
let u: Vec3 = w.cross(Y).normalize();
255+
let v: Vec3 = u.cross(w);
256+
let d: Vec3 = (p.x * u + p.y * v + w + w).normalize();
257+
let n: Vec3;
258+
let x: Vec3;
259+
260+
// Ray marching
261+
t = 0.0;
262+
let mut i = 0;
263+
while i < 48 {
264+
if h < 0.0001 || t > 4.7 {
265+
break;
266+
}
267+
h = self.m(o + d * t);
268+
t += h;
269+
i += 1;
270+
}
271+
272+
// Background colour change as teapot complementaries colours (using HSV)
273+
let mut c: Vec3 = mix(
274+
hsv2rgb_smooth(0.5 + self.inputs.time * 0.02, 0.35, 0.4),
275+
hsv2rgb_smooth(-0.5 + self.inputs.time * 0.02, 0.35, 0.7),
276+
q.y,
277+
);
278+
279+
// Calculate color on point
280+
if h < 0.001 {
281+
x = o + t * d;
282+
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)));
283+
284+
// Calculate Shadows
285+
let mut i = 0;
286+
while i < 20 {
287+
j += 0.02;
288+
s = s.min(self.m(x + self.l * j) / j);
289+
i += 1;
290+
}
291+
// Teapot color rotation in HSV color space
292+
let c1: Vec3 = hsv2rgb_smooth(0.9 + self.inputs.time * 0.02, 1.0, 1.0);
293+
// Shading
294+
c = mix(
295+
c,
296+
mix(
297+
(((3.0 * s).clamp(0.0, 1.0) + 0.3) * c1).sqrt(),
298+
Vec3::splat(self.l.reflect(n).dot(d).max(0.0).powf(99.0)),
299+
0.4,
300+
),
301+
2.0 * n.dot(-d),
302+
);
303+
}
304+
305+
c *= (16.0 * q.x * q.y * (1.0 - q.x) * (1.0 - q.y)).powf(0.16); // Vigneting
306+
*frag_color = c.extend(1.0);
307+
}
308+
}

0 commit comments

Comments
 (0)