|
| 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