moria/assets/shaders/post-processing.wgsl

186 lines
4.5 KiB
Plaintext

// 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)
var texture: texture_2d<f32>;
@group(1) @binding(1)
var our_sampler: sampler;
@group(1) @binding(2)
var noise: texture_2d<f32>;
@group(1) @binding(3)
var noise_sampler: sampler;
@group(1) @binding(4)
var<uniform> palette: array<vec3<f32>, 16>;
let dither_table = array<i32, 64>(
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<f32>) -> vec3<f32> {
let rgb = clamp(
abs(
((c.x * 6.0 + vec3<f32>(0.0,4.0,2.0)) % 6.0)
- 3.0
) - 1.0,
vec3<f32>(0.0),
vec3<f32>(1.0)
);
return c.z + c.y * (rgb - .5) * (1. - abs(2. * c.z - 1.));
}
fn rgb2hsl(c: vec3<f32>) -> vec3<f32> {
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<f32>(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<vec3<f32>, 2> {
var closest = vec3<f32>(-2., 0., 0.);
var secondClosest = vec3<f32>(-2., 0., 0.);
var temp: vec3<f32>;
var p: array<vec3<f32>, 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<vec3<f32>, 2>;
result[0] = closest;
result[1] = secondClosest;
return result;
}
fn dither(pos: vec2<f32>, color: vec3<f32>) -> vec3<f32> {
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<i32, 64> = dither_table;
let limit = (f32(p[index]) + 1.) / 64. + bias;
let Colors: array<vec3<f32>, 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<f32>;
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<f32>,
#import bevy_sprite::mesh2d_vertex_output
) -> @location(0) vec4<f32> {
let uv = position.xy / vec2<f32>(view.width, view.height);
// let uv = floor(uv * 400.0) / 400.0;
let col = textureSample(texture, our_sampler, uv);
return vec4<f32>(dither(position.xy / 2.0, rgb2hsl(col.rgb)), col.a);
// return round(col * 50.0) / 50.0;
// return col;
}