diff --git a/assets/shaders/post-processing.wgsl b/assets/shaders/post-processing.wgsl index e3bbcea..d5ab6c5 100644 --- a/assets/shaders/post-processing.wgsl +++ b/assets/shaders/post-processing.wgsl @@ -1,3 +1,6 @@ +// https://github.com/Rudranil-Sarkar/Ordered-Dithering-Shader-GLSL/blob/master/Dither8x8.frag +// http://alex-charlton.com/posts/Dithering_on_the_GPU/ + #import bevy_pbr::mesh_view_bindings @group(1) @binding(0) @@ -10,15 +13,173 @@ var noise: texture_2d; @group(1) @binding(3) var noise_sampler: sampler; +@group(1) @binding(4) +var palette: array, 16>; + +let dither_table = array( + 0, 48, 12, 60, 3, 51, 15, 63, + 32, 16, 44, 28, 35, 19, 47, 31, + 8, 56, 4, 52, 11, 59, 7, 55, + 40, 24, 36, 20, 43, 27, 39, 23, + 2, 50, 14, 62, 1, 49, 13, 61, + 34, 18, 46, 30, 33, 17, 45, 29, + 10, 58, 6, 54, 9, 57, 5, 53, + 42, 26, 38, 22, 41, 25, 37, 21 +); + + +fn hsl2rgb(c: vec3) -> vec3 { + let rgb = clamp( + abs( + ((c.x * 6.0 + vec3(0.0,4.0,2.0)) % 6.0) + - 3.0 + ) - 1.0, + vec3(0.0), + vec3(1.0) + ); + + return c.z + c.y * (rgb - .5) * (1. - abs(2. * c.z - 1.)); +} + +fn rgb2hsl(c: vec3) -> vec3 { + var h = 0.; + var s = 0.; + var l = 0.; + var r = c.r; + var g = c.g; + var b = c.b; + var cMin = min(r, min(g, b)); + var cMax = max(r, max(g, b)); + + l = (cMax + cMin) / 2.; + if (cMax > cMin) { + var cDelta = cMax - cMin; + + if (l < 0.0) { + s = cDelta / (cMax+cMin); + } else { + s = cDelta / (2. - (cMax + cMin)); + } + + if(r == cMax) { + h = (g - b) / cDelta; + } else if (g == cMax) { + h = 2. + (b - r) / cDelta; + } else { + h = 4. + (r - g) / cDelta; + } + + if(h < 0.) { + h += 6.; + } + h = h / 6.; + } + return vec3(h, s, l); +} + +fn hueDistance(h1: f32, h2: f32) -> f32 { + let diff = abs(h1 - h2); + + return min(abs(1. - diff), diff); +} + +let lightnessSteps: f32 = 4.; + +// SOURCE: http://alex-charlton.com/posts/Dithering_on_the_GPU/ +fn lightnessStep(l: f32) -> f32 { + return floor((.5 + l * lightnessSteps)) / lightnessSteps; +} + +let SaturationSteps: f32 = 4.; + +fn SaturationStep(s: f32) -> f32 { + /* Quantize the saturation to one of SaturationSteps values */ + return floor((.5 + s * SaturationSteps)) / SaturationSteps; +} + +fn Closest_color(hue: f32) -> array, 2> { + var closest = vec3(-2., 0., 0.); + var secondClosest = vec3(-2., 0., 0.); + + var temp: vec3; + + var p: array, 16> = palette; + for(var i: i32 = 0; i < 16; i++) { + temp = p[i]; + + let tempDistance = hueDistance(temp.x, hue); + + if(tempDistance < hueDistance(closest.x, hue)) { + secondClosest = closest; + closest = temp; + } else if(tempDistance < hueDistance(secondClosest.x, hue)){ + secondClosest = temp; + } + } + + var result: array, 2>; + result[0] = closest; + result[1] = secondClosest; + return result; +} + +fn dither(pos: vec2, color: vec3) -> vec3 { + let x = floor(pos.x % 8.); + let y = floor(pos.y % 8.); + + let index = i32(x + y * 8.); + + // This variable can be adjusted in range of -1 to 1 for more desirable result + let bias = 0.0; + + var p: array = dither_table; + let limit = (f32(p[index]) + 1.) / 64. + bias; + + let Colors: array, 2> = Closest_color(color.x); + + let hueDiff = hueDistance(color.x, Colors[0].x) / hueDistance(Colors[1].x, Colors[0].x); + + let l1 = lightnessStep(max((color.z - .125), 0.)); + let l2 = lightnessStep(min((color.z + .124), 1.)); + let lightnessDiff = (color.z - l1) / (l2 - l1); + + var resultColor: vec3; + if (hueDiff < limit) { + resultColor = Colors[0]; + } else { + resultColor = Colors[1]; + } + if (lightnessDiff < limit) { + resultColor.z = l1; + } else { + resultColor.z = l2; + } + + let s1 = SaturationStep(max((color.y - .125), 0.)); + let s2 = SaturationStep(min((color.y + .124), 1.)); + let SaturationDiff = (color.y - s1) / (s2 - s1); + + if (SaturationDiff < limit) { + resultColor.y = s1; + } else { + resultColor.y = s2; + } + + return hsl2rgb(resultColor); +} + @fragment fn fragment( @builtin(position) position: vec4, #import bevy_sprite::mesh2d_vertex_output ) -> @location(0) vec4 { let uv = position.xy / vec2(view.width, view.height); - let uv = floor(uv * 400.0) / 400.0; + // let uv = floor(uv * 400.0) / 400.0; let col = textureSample(texture, our_sampler, uv); - return round(col * 50.0) / 50.0; + return vec4(dither(position.xy / 2.0, rgb2hsl(col.rgb)), col.a); + + // return round(col * 50.0) / 50.0; + // return col; } diff --git a/assets/urbex-16.hex b/assets/urbex-16.hex new file mode 100644 index 0000000..1c9e63d --- /dev/null +++ b/assets/urbex-16.hex @@ -0,0 +1,16 @@ +cbd1be +8f9389 +52534c +26201d +e0a46e +91a47a +5d7643 +4d533a +a93130 +7a1338 +834664 +917692 +160712 +593084 +3870be +579fb4 diff --git a/src/camera.rs b/src/camera.rs index a6766da..a8dd660 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -14,7 +14,7 @@ use bevy::{ }; use bevy_mod_raycast::{RayCastMethod, RayCastSource}; -use crate::{pillar::UnlitPillar, player::*}; +use crate::{pillar::UnlitPillar, player::*, PALETTE}; pub struct MyRaycastSet; @@ -87,6 +87,7 @@ pub fn spawn_camera( let material_handle = post_processing_materials.add(PostProcessingMaterial { source_image: image_handle, noise, + palette: PALETTE, }); // Post processing 2d quad, with material using the render texture done by the main camera, with a custom shader. @@ -125,6 +126,8 @@ pub struct PostProcessingMaterial { #[texture(2)] #[sampler(3)] noise: Handle, + #[uniform(4)] + palette: [Vec3; 16], } impl Material2d for PostProcessingMaterial { diff --git a/src/main.rs b/src/main.rs index 50e5da6..2464c6d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,8 @@ #![allow(clippy::type_complexity)] -use bevy::{pbr::AmbientLight, prelude::*, sprite::Material2dPlugin, window::close_on_esc}; +use bevy::{ + math::vec3, pbr::AmbientLight, prelude::*, sprite::Material2dPlugin, window::close_on_esc, +}; use bevy_mod_raycast::{DefaultRaycastingPlugin, RayCastMesh, RaycastSystem}; mod camera; @@ -24,6 +26,33 @@ pub enum AppState { Game, } +/// color palette in hsl +/// from https://lospec.com/palette-list/urbex-16 +pub const PALETTE: [Vec3; 16] = [ + vec3(0.2193, 0.1712, 0.7824), + vec3(0.2333, 0.0442, 0.5569), + vec3(0.1905, 0.0440, 0.3118), + vec3(0.0556, 0.1343, 0.1314), + vec3(0.0789, 0.6477, 0.6549), + vec3(0.2421, 0.1875, 0.5608), + vec3(0.2484, 0.2757, 0.3627), + vec3(0.2067, 0.1773, 0.2765), + vec3(0.0014, 0.5576, 0.4255), + vec3(0.9401, 0.7305, 0.2765), + vec3(0.9180, 0.3035, 0.3941), + vec3(0.8274, 0.1138, 0.5176), + vec3(0.8778, 0.5172, 0.0569), + vec3(0.7480, 0.4667, 0.3529), + vec3(0.5970, 0.5447, 0.4824), + vec3(0.5376, 0.3827, 0.523), +]; +pub const PALETTE_HEX: [&str; 16] = [ + "cbd1be", "8f9389", "52534c", "26201d", "e0a46e", "91a47a", "5d7643", "4d533a", "a93130", + "7a1338", "834664", "917692", "160712", "593084", "3870be", "579fb4", +]; + +pub const LIGHT_FRIEND_COLOR_INDICES: [usize; 6] = [0, 4, 7, 8, 11, 14]; + fn main() { App::new() .insert_resource(Msaa { samples: 1 }) diff --git a/src/player.rs b/src/player.rs index da0cc9b..0211518 100644 --- a/src/player.rs +++ b/src/player.rs @@ -1,6 +1,9 @@ use bevy::{prelude::*, render::view::NoFrustumCulling}; -use crate::{camera::MouseCoords, illumination::Illumination, pillar::PillarActivationProgress}; +use crate::{ + camera::MouseCoords, illumination::Illumination, pillar::PillarActivationProgress, + LIGHT_FRIEND_COLOR_INDICES, PALETTE_HEX, +}; #[derive(Component)] pub struct Player; @@ -47,11 +50,25 @@ pub fn spawn_player( .with_children(|children| { let count = 6; for i in 0..count { - let color = Color::hsl(360.0 * i as f32 / count as f32, 0.5, 0.5); + // let color = Color::hsl(360.0 * i as f32 / count as f32, 0.5, 0.5); + let color = PALETTE_HEX[LIGHT_FRIEND_COLOR_INDICES[i]]; + + let color = Color::hex(color).unwrap(); + + // let c = color.as_rgba_f32(); + // println!( + // "\x1b[38;2;{};{};{}2m {} {} {} \x1b[m", + // (c[0] * 255.0) as u8, + // (c[1] * 255.0) as u8, + // (c[2] * 255.0) as u8, + // (c[0] * 255.0) as u8, + // (c[1] * 255.0) as u8, + // (c[2] * 255.0) as u8, + // ); let mut light_material: StandardMaterial = color.into(); - light_material.metallic = 0.5; - light_material.reflectance = 0.5; + light_material.metallic = 0.0; + light_material.reflectance = 0.0; light_material.emissive = color.as_rgba() * 10.0; let light_material = materials.add(light_material);