Skip to content

Commit

Permalink
Add Ocean example, "Fix" color issues
Browse files Browse the repository at this point in the history
  • Loading branch information
thedocruby committed Oct 25, 2023
1 parent 6247ca6 commit e6def67
Show file tree
Hide file tree
Showing 8 changed files with 292 additions and 17 deletions.
2 changes: 0 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,3 @@ spirv-std = { version = "=0.9.0" }
spirv-std-types = { version = "=0.9.0" }
spirv-std-macros = { version = "=0.9.0" }
spirv-builder = { version = "=0.9.0", default-features = false }
rustc_codegen_spirv = { version = "=0.9.0", default-features = false }
rustc_codegen_spirv-types = { version = "=0.9.0" }
8 changes: 4 additions & 4 deletions examples/example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@
// Import gravylib
use gravylib::*;
// Import shaders from the custom shader crate (with gravy-styled lib.rs)
use shaders::{ CIRCLE, RAINBOW };
#[allow(unused_imports)]
use shaders::{ CIRCLE, RAINBOW, OCEAN };

fn main() {

// Build shader from raw shader
let shader = Shader::from(
// Tip: Try changing the shader!
// `CIRCLE` points to the shader in `shaders/src/circle.rs`
// `RAINBOW` points to the shader in `shaders/src/rainbow.rs`
CIRCLE
// Options: CIRCLE, RAINBOW, OCEAN
OCEAN
);

// Execute shader
Expand Down
11 changes: 7 additions & 4 deletions examples/shaders/src/circle.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
// Ported to Rust from <https://www.shadertoy.com/view/3ltSW2>

// Imports
// ** Imports

use crate::*;
use core::f32::consts::PI;

// Helpers
// ** Helpers

fn circle_sdf(p: Vec2, r: f32) -> f32 {
p.length()-r
}

// "Entry point" (effectively)
// ** "Entry point" (effectively)

pub fn circle( constants: &CircleConstants, frag_coord: Vec2) -> Vec4 {
let p: Vec2 = (2.0 * frag_coord
- vec2(constants.width as f32,
Expand All @@ -29,7 +32,7 @@ pub fn circle( constants: &CircleConstants, frag_coord: Vec2) -> Vec4 {
col *= 0.8 + 0.2*(150.0*(d.abs() + (constants.time * PI).cos() * 0.1)).cos();
col = mix3(
col,
Vec3::splat(1.0),
Vec3::ONE,
1.0-smoothstep(0.0,0.01,d.abs())
);

Expand Down
4 changes: 4 additions & 0 deletions examples/shaders/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,8 @@ pub fn cos3(v: Vec3) -> Vec3 {

pub fn sin3(v: Vec3) -> Vec3 {
vec3(v.x.sin(), v.y.sin(), v.z.sin())
}

pub fn reflect(ray: Vec3, normal: Vec3) -> Vec3 {
ray - normal * 2.0 * ray.dot(normal)
}
44 changes: 43 additions & 1 deletion examples/shaders/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,46 @@ use common::*;
entry_point: "circle",
phantom: std::marker::PhantomData,
};
// ** CIRCLE
// ** CIRCLE

// ** OCEAN
mod ocean;

#[derive(Copy, Clone, Pod, Zeroable)]
#[repr(C)]
pub struct OceanConstants {
pub width: u32,
pub height: u32,
pub time: f32,
}

impl From<Constants> for OceanConstants {
fn from(constants: Constants) -> Self {
Self {
width: constants.width,
height: constants.height,
time: constants.time,
}
}
}

#[spirv(fragment)]
pub fn ocean(
#[spirv(frag_coord)] in_frag_coord: Vec4,
#[spirv(push_constant)] constants: &OceanConstants,
output: &mut Vec4,
) {
let frag_coord = vec2(in_frag_coord.x, in_frag_coord.y);
*output = ocean::ocean(constants, frag_coord);
}

#[cfg(not(target_arch = "spirv"))]
#[allow(dead_code)]
pub const OCEAN: &RawShader<OceanConstants> = &RawShader {
shader_type: ShaderType::Pixel,
crate_name: env!("CARGO_CRATE_NAME"),
entry_point: "ocean",
phantom: std::marker::PhantomData,
};

// ** OCEAN
224 changes: 224 additions & 0 deletions examples/shaders/src/ocean.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
// Ported to Rust from <https://www.shadertoy.com/view/MdXyzX>
// NOTE: Mouse input has been removed from the original

// ** Imports

use crate::*;
// use core::f32::consts::TAU;

// ** Constants

const DRAG_MULT: f32 = 0.28; // changes how much waves pull on the water
const WATER_DEPTH: f32 = 1.0; // how deep is the water
const CAMERA_HEIGHT: f32 = 1.5; // how high the camera should be
const ITERATIONS_RAYMARCH: u32 = 12; // waves iterations of raymarching
const ITERATIONS_NORMAL: u32 = 40; // waves iterations when calculating normals

// ** Helpers

// Calculates wave value and its derivative,
// for the wave direction, position in space, wave frequency and time
fn wavedx(position: Vec2, direction: Vec2, frequency: f32, timeshift: f32) -> Vec2 {
let x = direction.dot(position) * frequency + timeshift;
let wave = (x.sin() - 1.0).exp();
let dx = wave * x.cos();
vec2(wave, -dx)
}

// Calculates waves by summing octaves of various waves with various parameters
fn getwaves(mut position: Vec2, iterations: u32, time: f32) -> f32 {
let mut iter: f32 = 0.0; // this will help generating well distributed wave directions
let mut frequency= 1.0; // frequency of the wave, this will change every iteration
let mut time_multiplier = 2.0; // time multiplier for the wave, this will change every iteration
let mut weight = 1.0;// weight in final sum for the wave, this will change every iteration
let mut sum_of_values = 0.0; // will store final sum of values
let mut sum_of_weights = 0.0; // will store final sum of weights
for _i in 0..iterations {
// generate some wave direction that looks kind of random
let p = vec2(iter.sin(), iter.cos());
// calculate wave data
let res = wavedx(position, p, frequency, time * time_multiplier);

// shift position around according to wave drag and derivative of the wave
position += p * res.y * weight * DRAG_MULT;

// add the results to sums
sum_of_values += res.x * weight;
sum_of_weights += weight;

// modify next octave parameters
weight *= 0.82;
frequency *= 1.18;
time_multiplier *= 1.07;

// add some kind of random value to make next wave look random too
iter += 1232.399963;
}
// calculate and return
sum_of_values / sum_of_weights
}

// Raymarches the ray from top water layer boundary to low water layer boundary
fn raymarchwater(camera: Vec3, start: Vec3, end: Vec3, depth: f32, time: f32) -> f32 {
let mut pos = start;
let dir = (end - start).normalize();
for _i in 0..64 {
// the height is from 0 to -depth
let height = getwaves(pos.xz(), ITERATIONS_RAYMARCH, time) * depth - depth;
// if the waves height almost nearly matches the ray height, assume its a hit and return the hit distance
if height + 0.01 > pos.y {
return pos.distance(camera);
}
// iterate forwards according to the height mismatch
pos += dir * (pos.y - height);
}
// if hit was not registered, just assume hit the top layer,
// this makes the raymarching faster and looks better at higher distances
start.distance(camera)
}

// Calculate normal at point by calculating the height at the pos and 2 additional points very close to pos
fn normal(pos: Vec2, e: f32, depth: f32, time: f32) -> Vec3 {
let ex = vec2(e, 0.0);
let h = getwaves(pos.xy(), ITERATIONS_NORMAL, time) * depth;
let a = vec3(pos.x, h, pos.y);

(a - vec3(pos.x - e, getwaves(pos.xy() - ex.xy(), ITERATIONS_NORMAL, time) * depth, pos.y))
.cross(a - vec3(pos.x, getwaves(pos.xy() + ex.yx(), ITERATIONS_NORMAL, time) * depth, pos.y + e))
.normalize()
}

// Helper function generating a rotation matrix around the axis by the angle
fn create_rotation_matrix_axis_angle(axis: Vec3, angle: f32) -> Mat3{
let s = angle.sin();
let c = angle.cos();
let oc = 1.0 - c;
return mat3(
vec3(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s),
vec3(oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s),
vec3(oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c )
);
}

// Helper function that generates camera ray based on UV and mouse
fn get_ray(frag_coord: Vec2, resf: Vec2, mouse_norm: Vec2) -> Vec3 {
let mut uv = ((frag_coord.xy() / resf.xy()) * 2.0 - 1.0) * vec2(resf.x / resf.y, 1.0);
uv.y = -uv.y;
// for fisheye, uncomment following line and comment the next one
// let proj: Vec3 = (vec3(uv.x, uv.y, 1.0) + vec3(uv.x, uv.y, -1.0) * uv.length().pow(2.0) * 0.05).normalize();
let proj = vec3(uv.x, uv.y, 1.5).normalize();
if resf.x < 600.0 {
return proj;
}
return create_rotation_matrix_axis_angle(vec3(0.0, -1.0, 0.0), 3.0 * ((mouse_norm.x + 0.5) * 2.0 - 1.0))
* create_rotation_matrix_axis_angle(vec3(1.0, 0.0, 0.0), 0.5 + 1.5 * ((mouse_norm.y * 1.5) * 2.0 - 1.0))
* proj;
}

// Ray-Plane intersection checker
fn intersect_plane(origin: Vec3, direction: Vec3, point: Vec3, normal: Vec3) -> f32 {
(normal.dot(point - origin) / normal.dot(direction)).clamp(-1.0, 9991999.0)
}

// Some very barebones but fast atmosphere approximation
fn extra_cheap_atmosphere(raydir: Vec3, mut sundir: Vec3) -> Vec3 {
sundir.y = sundir.y.max(-0.07);
let special_trick = 1.0 / (raydir.y * 1.0 + 0.1);
let special_trick2 = 1.0 / (sundir.y * 11.0 + 1.0);
let raysundt = sundir.dot(raydir).abs().powf(2.0);
let sundt = sundir.dot(raydir).max(0.0).powf(8.0);
let mymie = sundt * special_trick * 0.2;
let suncolor = mix3(Vec3::ONE, (Vec3::ONE - vec3(5.5, 13.0, 22.4) / 22.4).max(Vec3::ZERO), special_trick2);
let bluesky= vec3(5.5, 13.0, 22.4) / 22.4 * suncolor;
let mut bluesky2 = (bluesky - vec3(5.5, 13.0, 22.4) * 0.002 * (special_trick + -6.0 * sundir.y * sundir.y)).max(Vec3::ZERO);
bluesky2 *= special_trick * (0.24 + raysundt * 0.24);
bluesky2 * (1.0 + 1.0 * (1.0 - raydir.y).powf(3.0)) + mymie * suncolor
}

// Calculate where the sun should be, it will be moving around the sky
fn get_sun_direction(time: f32) -> Vec3 {
vec3((time * 0.1).sin(), 1.0, (time * 0.1).cos()).normalize()
}

// Get atmosphere color for given direction
fn get_atmosphere(dir: Vec3, time: f32) -> Vec3 {
extra_cheap_atmosphere(dir, get_sun_direction(time)) * 0.5
}

// Get sun color for given direction
fn get_sun(dir: Vec3, time: f32) -> f32 {
get_sun_direction(time).dot(dir).max(0.0).powf(720.0) * 210.0
}

// Great tonemapping function from my other shader: https://www.shadertoy.com/view/XsGfWV
fn aces_tonemap(color: Vec3) -> Vec3 {
let m1 = mat3(
vec3(0.59719, 0.07600, 0.02840),
vec3(0.35458, 0.90834, 0.13383),
vec3(0.04823, 0.01566, 0.83777),
);
let m2 = mat3(
vec3( 1.60475, -0.10208, -0.00327),
vec3(-0.53108, 1.10813, -0.07276),
vec3(-0.07367, -0.00605, 1.07602)
);
let v = m1 * color;
let a = v * (v + 0.0245786) - 0.000090537;
let b = v * (0.983729 * v + 0.4329510) + 0.238081;
(m2 * (a / b)).clamp(Vec3::ZERO, Vec3::ONE).powf(1.0 / 2.2)
}

// ** "Entry point" (effectively)

// Main
pub fn ocean(constants: &OceanConstants, frag_coord: Vec2) -> Vec4 {
// get the ray
let resf = vec2(constants.width as f32, constants.height as f32);
// let mousef = vec2(constants.mousex as f32, constants.mousey as f32);
// let mouse_norm = (constants.mousef.xy() / resf.xy()) // normalize mouse coords
let ray = get_ray(frag_coord, resf, /*mouse_norm*/vec2(0.0, 0.0));
if ray.y >= 0.0 {
// if ray.y is positive, render the sky
let c = get_atmosphere(ray, constants.time) + get_sun(ray, constants.time);
return aces_tonemap(c * 2.0).extend(1.0);
}

// now ray.y must be negative, water must be hit
// define water planes
let water_plane_high = Vec3::ZERO;
let water_plane_low = vec3(0.0, -WATER_DEPTH, 0.0);

// define ray origin, moving around
let origin = vec3(constants.time, CAMERA_HEIGHT, constants.time);

// calculate intersections and reconstruct positions
let high_plane_hit = intersect_plane(origin, ray, water_plane_high, vec3(0.0, 1.0, 0.0));
let low_plane_hit = intersect_plane(origin, ray, water_plane_low, vec3(0.0, 1.0, 0.0));
let high_hit_pos = origin + ray * high_plane_hit;
let low_hit_pos = origin + ray * low_plane_hit;

// raymatch water and reconstruct the hit pos
let dist = raymarchwater(origin, high_hit_pos, low_hit_pos, WATER_DEPTH, constants.time);
let water_hit_pos = origin + ray * dist;

// calculate normal at the hit position
let mut n = normal(water_hit_pos.xz(), 0.01, WATER_DEPTH, constants.time);

// smooth the normal with distance to avoid disturbing high frequency noise
n = mix3(n, vec3(0.0, 1.0, 0.0), 0.8 * ((dist*0.01).sqrt() * 1.1).min(1.0));

// calculate fresnel coefficient
let fresnel = 0.04 + (1.0-0.04)*((1.0 - ray.dot(-n).max(0.0)).powf(5.0));

// reflect the ray and make sure it bounces up
let mut r = reflect(ray, n).normalize();
r.y = r.y.abs();

// calculate the reflection and approximate subsurface scattering
let reflection = get_atmosphere(r, constants.time) + get_sun(r, constants.time);
let scattering = vec3(0.0293, 0.0698, 0.1717) * (0.2 + (water_hit_pos.y + WATER_DEPTH) / WATER_DEPTH);

// return the combined result
let c = fresnel * reflection + (1.0 - fresnel) * scattering;
aces_tonemap(c * 2.0).extend(1.0)
}
13 changes: 8 additions & 5 deletions examples/shaders/src/rainbow.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// Adapted from <https://www.shadertoy.com/view/mtyGWy>

// Imports
// ** Imports

use crate::*;
use core::f32::consts::TAU;

// Helpers
// ** Helpers

pub fn palette(t: f32) -> Vec3 {
let a = vec3(0.5, 0.5, 0.5);
let b = vec3(0.5, 0.5, 0.5);
Expand All @@ -14,7 +16,8 @@ pub fn palette(t: f32) -> Vec3 {
cos3(TAU * (c * t + d)).mul_add(b, a)
}

// "Entry point" (effectively)
// ** "Entry point" (effectively)

pub fn rainbow(
constants: &RainbowConstants,
frag_coord: Vec2,
Expand All @@ -23,15 +26,15 @@ pub fn rainbow(
/ constants.height as f32;

let uv0 = uv;
let mut final_color = Vec3::splat(0.0);
let mut final_color = Vec3::ZERO;

for i in 0..4 {
uv = (uv * 1.5).fract() - 0.5;

let mut d = uv.length() * (-1.0 * uv0.length()).exp();
d = (d * 8.0 + constants.time).sin() / 8.0;
d = d.abs();
d = (0.01 / d).powf(2.0);
d = (0.01 / d).powf(1.2);

let col = palette(
uv0.length() +
Expand Down
3 changes: 2 additions & 1 deletion src/graphics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ impl<T: From<Constants> + Copy + Clone + Pod + Zeroable> State<T> {

let surface_format = surface_caps.formats.iter()
.copied()
.find(|f| f.is_srgb())
// HACK (thedocruby) This should be using sRGB, but it's not working properly for some reason
.find(|f| !f.is_srgb())
.unwrap_or(surface_caps.formats[0]);

let mut config = wgpu::SurfaceConfiguration {
Expand Down

0 comments on commit e6def67

Please sign in to comment.