Skip to content

Commit 0381382

Browse files
committed
Ported "Atmosphere system test" (https://www.shadertoy.com/view/XtBXDz).
1 parent e5b1770 commit 0381382

File tree

2 files changed

+298
-0
lines changed

2 files changed

+298
-0
lines changed

shaders/src/atmosphere_system_test.rs

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
//! Ported to Rust from <https://www.shadertoy.com/view/XtBXDz>
2+
//!
3+
//! Original comment:
4+
//! ```glsl
5+
//! // ----------------------------------------------------------------------------
6+
//! // Rayleigh and Mie scattering atmosphere system
7+
//! //
8+
//! // implementation of the techniques described here:
9+
//! // http://www.scratchapixel.com/old/lessons/3d-advanced-lessons/simulating-the-colors-of-the-sky/atmospheric-scattering/
10+
//! // ----------------------------------------------------------------------------
11+
//! ```
12+
13+
use shared::*;
14+
use spirv_std::glam::{const_vec3, vec2, vec3, Mat3, Vec2, Vec3, Vec3Swizzles, Vec4};
15+
16+
// Note: This cfg is incorrect on its surface, it really should be "are we compiling with std", but
17+
// we tie #[no_std] above to the same condition, so it's fine.
18+
#[cfg(target_arch = "spirv")]
19+
use spirv_std::num_traits::Float;
20+
21+
pub struct Inputs {
22+
pub resolution: Vec3,
23+
pub time: f32,
24+
pub mouse: Vec4,
25+
}
26+
27+
pub struct State {
28+
inputs: Inputs,
29+
sun_dir: Vec3,
30+
}
31+
32+
impl State {
33+
pub fn new(inputs: Inputs) -> Self {
34+
State {
35+
inputs,
36+
sun_dir: vec3(0.0, 1.0, 0.0),
37+
}
38+
}
39+
}
40+
41+
const PI: f32 = 3.14159265359;
42+
43+
#[derive(Copy, Clone)]
44+
struct Ray {
45+
origin: Vec3,
46+
direction: Vec3,
47+
}
48+
const _BIAS: f32 = 1e-4; // small offset to avoid self-intersections
49+
50+
struct Sphere {
51+
origin: Vec3,
52+
radius: f32,
53+
_material: i32,
54+
}
55+
56+
struct _Plane {
57+
direction: Vec3,
58+
distance: f32,
59+
material: i32,
60+
}
61+
62+
fn rotate_around_x(angle_degrees: f32) -> Mat3 {
63+
let angle: f32 = angle_degrees.deg_to_radians();
64+
let _sin: f32 = angle.sin();
65+
let _cos: f32 = angle.cos();
66+
Mat3::from_cols_array(&[1.0, 0.0, 0.0, 0.0, _cos, -_sin, 0.0, _sin, _cos])
67+
}
68+
69+
fn get_primary_ray(cam_local_point: Vec3, cam_origin: &mut Vec3, cam_look_at: &mut Vec3) -> Ray {
70+
let fwd: Vec3 = (*cam_look_at - *cam_origin).normalize();
71+
let mut up: Vec3 = vec3(0.0, 1.0, 0.0);
72+
let right: Vec3 = up.cross(fwd);
73+
up = fwd.cross(right);
74+
75+
Ray {
76+
origin: *cam_origin,
77+
direction: (fwd + up * cam_local_point.y + right * cam_local_point.x).normalize(),
78+
}
79+
}
80+
81+
fn isect_sphere(ray: Ray, sphere: Sphere, t0: &mut f32, t1: &mut f32) -> bool {
82+
let rc: Vec3 = sphere.origin - ray.origin;
83+
let radius2: f32 = sphere.radius * sphere.radius;
84+
let tca: f32 = rc.dot(ray.direction);
85+
let d2: f32 = rc.dot(rc) - tca * tca;
86+
if d2 > radius2 {
87+
return false;
88+
}
89+
let thc: f32 = (radius2 - d2).sqrt();
90+
*t0 = tca - thc;
91+
*t1 = tca + thc;
92+
true
93+
}
94+
95+
// scattering coefficients at sea level (m)
96+
const BETA_R: Vec3 = const_vec3!([5.5e-6, 13.0e-6, 22.4e-6]); // Rayleigh
97+
const BETA_M: Vec3 = const_vec3!([21e-6, 21e-6, 21e-6]); // Mie
98+
99+
// scale height (m)
100+
// thickness of the atmosphere if its density were uniform
101+
const H_R: f32 = 7994.0; // Rayleigh
102+
const H_M: f32 = 1200.0; // Mie
103+
104+
fn rayleigh_phase_func(mu: f32) -> f32 {
105+
3.0 * (1.0 + mu*mu)
106+
/ //------------------------
107+
(16.0 * PI)
108+
}
109+
110+
// Henyey-Greenstein phase function factor [-1, 1]
111+
// represents the average cosine of the scattered directions
112+
// 0 is isotropic scattering
113+
// > 1 is forward scattering, < 1 is backwards
114+
const G: f32 = 0.76;
115+
fn henyey_greenstein_phase_func(mu: f32) -> f32 {
116+
(1. - G*G)
117+
/ //---------------------------------------------
118+
((4. * PI) * (1. + G*G - 2.*G*mu).powf(1.5))
119+
}
120+
121+
// Schlick Phase Function factor
122+
// Pharr and Humphreys [2004] equivalence to g above
123+
const K: f32 = 1.55 * G - 0.55 * (G * G * G);
124+
fn schlick_phase_func(mu: f32) -> f32 {
125+
(1. - K*K)
126+
/ //-------------------------------------------
127+
(4. * PI * (1. + K*mu) * (1. + K*mu))
128+
}
129+
130+
const EARTH_RADIUS: f32 = 6360e3; // (m)
131+
const ATMOSPHERE_RADIUS: f32 = 6420e3; // (m)
132+
133+
const SUN_POWER: f32 = 20.0;
134+
135+
const ATMOSPHERE: Sphere = Sphere {
136+
origin: Vec3::zero(),
137+
radius: ATMOSPHERE_RADIUS,
138+
_material: 0,
139+
};
140+
141+
const NUM_SAMPLES: i32 = 16;
142+
const NUM_SAMPLES_LIGHT: i32 = 8;
143+
144+
fn get_sun_light(ray: Ray, optical_depth_r: &mut f32, optical_depth_m: &mut f32) -> bool {
145+
let mut t0: f32 = 0.0;
146+
let mut t1: f32 = 0.0;
147+
isect_sphere(ray, ATMOSPHERE, &mut t0, &mut t1);
148+
149+
let mut march_pos: f32 = 0.0;
150+
let march_step: f32 = t1 / NUM_SAMPLES_LIGHT as f32;
151+
152+
let mut i = 0;
153+
while i < NUM_SAMPLES_LIGHT {
154+
let s: Vec3 = ray.origin + ray.direction * (march_pos + 0.5 * march_step);
155+
let height: f32 = s.length() - EARTH_RADIUS;
156+
if height < 0.0 {
157+
return false;
158+
}
159+
160+
*optical_depth_r += (-height / H_R).exp() * march_step;
161+
*optical_depth_m += (-height / H_M).exp() * march_step;
162+
163+
march_pos += march_step;
164+
i += 1;
165+
}
166+
true
167+
}
168+
169+
impl State {
170+
fn get_incident_light(&self, ray: Ray) -> Vec3 {
171+
// "pierce" the atmosphere with the viewing ray
172+
let mut t0: f32 = 0.0;
173+
let mut t1: f32 = 0.0;
174+
if !isect_sphere(ray, ATMOSPHERE, &mut t0, &mut t1) {
175+
return Vec3::zero();
176+
}
177+
178+
let march_step: f32 = t1 / NUM_SAMPLES as f32;
179+
180+
// cosine of angle between view and light directions
181+
let mu: f32 = ray.direction.dot(self.sun_dir);
182+
183+
// Rayleigh and Mie phase functions
184+
// A black box indicating how light is interacting with the material
185+
// Similar to BRDF except
186+
// * it usually considers a single angle
187+
// (the phase angle between 2 directions)
188+
// * integrates to 1 over the entire sphere of directions
189+
let phase_r: f32 = rayleigh_phase_func(mu);
190+
let phase_m: f32 = if true {
191+
henyey_greenstein_phase_func(mu)
192+
} else {
193+
schlick_phase_func(mu)
194+
};
195+
196+
// optical depth (or "average density")
197+
// represents the accumulated extinction coefficients
198+
// along the path, multiplied by the length of that path
199+
let mut optical_depth_r: f32 = 0.0;
200+
let mut optical_depth_m: f32 = 0.0;
201+
202+
let mut sum_r: Vec3 = Vec3::zero();
203+
let mut sum_m: Vec3 = Vec3::zero();
204+
let mut march_pos: f32 = 0.0;
205+
206+
let mut i = 0;
207+
while i < NUM_SAMPLES {
208+
let s: Vec3 = ray.origin + ray.direction * (march_pos + 0.5 * march_step);
209+
let height: f32 = s.length() - EARTH_RADIUS;
210+
211+
// integrate the height scale
212+
let hr: f32 = (-height / H_R).exp() * march_step;
213+
let hm: f32 = (-height / H_M).exp() * march_step;
214+
optical_depth_r += hr;
215+
optical_depth_m += hm;
216+
217+
// gather the sunlight
218+
let light_ray: Ray = Ray {
219+
origin: s,
220+
direction: self.sun_dir,
221+
};
222+
let mut optical_depth_light_r: f32 = 0.0;
223+
let mut optical_depth_light_m: f32 = 0.0;
224+
let overground: bool = get_sun_light(
225+
light_ray,
226+
&mut optical_depth_light_r,
227+
&mut optical_depth_light_m,
228+
);
229+
230+
if overground {
231+
let tau: Vec3 = BETA_R * (optical_depth_r + optical_depth_light_r)
232+
+ BETA_M * 1.1 * (optical_depth_m + optical_depth_light_m);
233+
let attenuation: Vec3 = exp(-tau);
234+
235+
sum_r += hr * attenuation;
236+
sum_m += hm * attenuation;
237+
}
238+
239+
march_pos += march_step;
240+
i += 1;
241+
}
242+
243+
SUN_POWER * (sum_r * phase_r * BETA_R + sum_m * phase_m * BETA_M)
244+
}
245+
246+
pub fn main_image(&mut self, frag_color: &mut Vec4, frag_coord: Vec2) {
247+
let aspect_ratio: Vec2 = vec2(self.inputs.resolution.x / self.inputs.resolution.y, 1.0);
248+
let fov: f32 = 45.0.deg_to_radians().tan();
249+
let point_ndc: Vec2 = frag_coord / self.inputs.resolution.xy();
250+
let point_cam: Vec3 = ((2.0 * point_ndc - Vec2::one()) * aspect_ratio * fov).extend(-1.0);
251+
252+
let col: Vec3;
253+
254+
// sun
255+
let rot: Mat3 = rotate_around_x(-(self.inputs.time / 2.0).sin().abs() * 90.0);
256+
self.sun_dir = rot.transpose() * self.sun_dir;
257+
258+
if self.inputs.mouse.z < 0.1 {
259+
// sky dome angles
260+
let p: Vec3 = point_cam;
261+
let z2: f32 = p.x * p.x + p.y * p.y;
262+
let phi: f32 = p.y.atan2(p.x);
263+
let theta: f32 = (1.0 - z2).acos();
264+
let dir: Vec3 = vec3(
265+
theta.sin() * phi.cos(),
266+
theta.cos(),
267+
theta.sin() * phi.sin(),
268+
);
269+
270+
let ray: Ray = Ray {
271+
origin: vec3(0.0, EARTH_RADIUS + 1.0, 0.0),
272+
direction: dir,
273+
};
274+
275+
col = self.get_incident_light(ray);
276+
} else {
277+
let mut eye: Vec3 = vec3(0.0, EARTH_RADIUS + 1.0, 0.0);
278+
let mut look_at: Vec3 = vec3(0.0, EARTH_RADIUS + 1.5, -1.0);
279+
280+
let ray: Ray = get_primary_ray(point_cam, &mut eye, &mut look_at);
281+
282+
if ray.direction.dot(vec3(0.0, 1.0, 0.0)) > 0.0 {
283+
col = self.get_incident_light(ray);
284+
} else {
285+
col = Vec3::splat(0.333);
286+
}
287+
}
288+
289+
*frag_color = col.extend(1.0);
290+
}
291+
}

shaders/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use spirv_std::storage_class::{Input, Output, PushConstant};
1010
pub mod a_lot_of_spheres;
1111
pub mod a_question_of_time;
1212
pub mod apollonian;
13+
pub mod atmosphere_system_test;
1314
pub mod clouds;
1415
pub mod galaxy_of_universes;
1516
pub mod heart;
@@ -107,6 +108,12 @@ pub fn fs(constants: &ShaderConstants, mut frag_coord: Vec2) -> Vec4 {
107108
}
108109
.main_image(&mut color, frag_coord),
109110
12 => galaxy_of_universes::Inputs { resolution, time }.main_image(&mut color, frag_coord),
111+
13 => atmosphere_system_test::State::new(atmosphere_system_test::Inputs {
112+
resolution,
113+
time,
114+
mouse,
115+
})
116+
.main_image(&mut color, frag_coord),
110117
_ => {}
111118
}
112119
pow(color.truncate(), 2.2).extend(color.w)

0 commit comments

Comments
 (0)