Compare commits
11 commits
Author | SHA1 | Date | |
---|---|---|---|
54cbddbbba | |||
65fddc0bd8 | |||
ebc8ce2cb9 | |||
c9c5428dbc | |||
4d0f7ca538 | |||
3dcc4ca02c | |||
bf1c1327ac | |||
560e455cd2 | |||
77273f9c1b | |||
9d23efea63 | |||
4278cde44b |
14 changed files with 254 additions and 14 deletions
|
@ -62,15 +62,17 @@ Example:
|
|||
//!load ...
|
||||
//!steps 5
|
||||
|
||||
uniform int STEP; // this is mandatory!
|
||||
uniform int STEP;
|
||||
uniform int STEPS;
|
||||
|
||||
void fragment() {
|
||||
if (STEP == 0) {
|
||||
...
|
||||
} else if (STEP == 1) {
|
||||
...
|
||||
} else if (STEP == STEPS-1) {
|
||||
...
|
||||
}
|
||||
// ... and so on
|
||||
}
|
||||
```
|
||||
|
||||
|
|
20
examples/multistep_pixelsort.gdshader
Normal file
20
examples/multistep_pixelsort.gdshader
Normal file
|
@ -0,0 +1,20 @@
|
|||
shader_type canvas_item;
|
||||
|
||||
#include "./shaderlib/pixelsort.gdshaderinc"
|
||||
|
||||
//!steps 1500
|
||||
uniform int STEP;
|
||||
|
||||
//!load ./images/mountain.jpg
|
||||
|
||||
void fragment() {
|
||||
// pixel sorting works in multiple steps
|
||||
COLOR = pixelsort_step(
|
||||
TEXTURE, UV,
|
||||
DIRECTION_BOTTOM_TO_TOP,
|
||||
COLOR_MODE_OKLCH,
|
||||
{true, false, false},
|
||||
{-INF, .007, -INF},
|
||||
{INF, INF, INF},
|
||||
STEP);
|
||||
}
|
1
examples/multistep_pixelsort.gdshader.uid
Normal file
1
examples/multistep_pixelsort.gdshader.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://csk0fg4by651b
|
10
examples/sobel.gdshader
Normal file
10
examples/sobel.gdshader
Normal file
|
@ -0,0 +1,10 @@
|
|||
shader_type canvas_item;
|
||||
|
||||
//!load ./images/noisy.png
|
||||
|
||||
#include "./shaderlib/sobel.gdshaderinc"
|
||||
|
||||
void fragment() {
|
||||
// Sobel Filter
|
||||
COLOR = sobel(TEXTURE, UV);
|
||||
}
|
1
examples/sobel.gdshader.uid
Normal file
1
examples/sobel.gdshader.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://h376mk1fq4ky
|
|
@ -11,10 +11,9 @@ config_version=5
|
|||
[application]
|
||||
|
||||
config/name="Fragmented"
|
||||
config/version="v10.0"
|
||||
config/version="v10.2"
|
||||
run/main_scene="res://src/scenes/main.tscn"
|
||||
config/features=PackedStringArray("4.4", "Mobile")
|
||||
run/low_processor_mode=true
|
||||
config/icon="res://src/assets/icon.png"
|
||||
|
||||
[autoload]
|
||||
|
@ -28,7 +27,6 @@ window/size/viewport_width=640
|
|||
window/size/viewport_height=672
|
||||
window/energy_saving/keep_screen_on=false
|
||||
window/subwindows/embed_subwindows=false
|
||||
window/vsync/vsync_mode=0
|
||||
|
||||
[editor_plugins]
|
||||
|
||||
|
|
|
@ -13,3 +13,15 @@ vec4 alpha_blend(vec4 b, vec4 a) {
|
|||
vec3 col = ((a.rgb*a.a) + ((b.rgb*b.a) * (1.0 - a.a)) / alpha);
|
||||
return vec4(col.r, col.g, col.b, alpha);
|
||||
}
|
||||
|
||||
/*
|
||||
Rotate UV
|
||||
*/
|
||||
|
||||
vec2 rotateUV(vec2 uv, float rotation, vec2 center) {
|
||||
float cosRot = cos(rotation);
|
||||
float sinRot = sin(rotation);
|
||||
return vec2(
|
||||
cosRot * (uv.x - center.x) + sinRot * (uv.y - center.y) + center.x,
|
||||
cosRot * (uv.y - center.y) - sinRot * (uv.x - center.x) + center.y);
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#include "./common.gdshaderinc"
|
||||
|
||||
vec4 rgb2oklab(vec4 c) {
|
||||
// oklab.x and .y (a and b) should range from -0.5 to 0.5
|
||||
|
||||
float l = 0.4122214708f * c.r + 0.5363325363f * c.g + 0.0514459929f * c.b;
|
||||
float m = 0.2119034982f * c.r + 0.6806995451f * c.g + 0.1073969566f * c.b;
|
||||
float s = 0.0883024619f * c.r + 0.2817188376f * c.g + 0.6299787005f * c.b;
|
||||
|
@ -29,6 +31,8 @@ vec4 rgb2oklab(vec4 c) {
|
|||
}
|
||||
|
||||
vec4 oklab2rgb(vec4 c) {
|
||||
// oklab.x and .y (a and b) should range from -0.5 to 0.5
|
||||
|
||||
float l_ = c.x + 0.3963377774f * c.y + 0.2158037573f * c.z;
|
||||
float m_ = c.x - 0.1055613458f * c.y - 0.0638541728f * c.z;
|
||||
float s_ = c.x - 0.0894841775f * c.y - 1.2914855480f * c.z;
|
||||
|
@ -46,6 +50,7 @@ vec4 oklab2rgb(vec4 c) {
|
|||
}
|
||||
|
||||
vec4 oklab2oklch(vec4 c) {
|
||||
// oklch.z (hue) ranges from -3.6 to 3.6
|
||||
return vec4(
|
||||
c.x,
|
||||
sqrt((c.y * c.y) + (c.z * c.z)),
|
||||
|
@ -55,6 +60,7 @@ vec4 oklab2oklch(vec4 c) {
|
|||
}
|
||||
|
||||
vec4 oklch2oklab(vec4 c) {
|
||||
// oklch.z (hue) ranges from -3.6 to 3.6
|
||||
return vec4(
|
||||
c.x,
|
||||
c.y * cos(c.z),
|
||||
|
|
126
shaderlib/pixelsort.gdshaderinc
Normal file
126
shaderlib/pixelsort.gdshaderinc
Normal file
|
@ -0,0 +1,126 @@
|
|||
|
||||
/*
|
||||
Pixelsorting using odd-even sort
|
||||
|
||||
I roughly followed https://ciphrd.com/2020/04/08/pixel-sorting-on-shader-using-well-crafted-sorting-filters-glsl/
|
||||
- vector fields aren't implemented, diagonal sorting is not supported!
|
||||
*/
|
||||
|
||||
#include "./hsv.gdshaderinc"
|
||||
#include "./oklab.gdshaderinc"
|
||||
|
||||
#define INF (1.0/0.0)
|
||||
|
||||
#define DIRECTION_LEFT_TO_RIGHT vec2(1, 0)
|
||||
#define DIRECTION_RIGHT_TO_LEFT vec2(-1, 0)
|
||||
#define DIRECTION_TOP_TO_BOTTOM vec2(0, 1)
|
||||
#define DIRECTION_BOTTOM_TO_TOP vec2(0, -1)
|
||||
|
||||
#define COLOR_MODE_RGB 0
|
||||
#define COLOR_MODE_OKLAB 1
|
||||
#define COLOR_MODE_OKLCH 2
|
||||
#define COLOR_MODE_HSV 3
|
||||
|
||||
vec4 pixelsort_step(
|
||||
sampler2D tex, vec2 uv,
|
||||
vec2 direction, // e.g. (1, 0) for left-to-right or (0, -1) for bottom-to-top
|
||||
// see DIRECTION_LEFT_TO_RIGHT, etc.
|
||||
// note: vertical sorting doesn't work, so using e.g. (1, 1) won't work
|
||||
int color_mode, // 0 = RGB, 1 = OKLAB, 2 = OKLCH, 3 = HSV
|
||||
// see COLOR_MODE_RGB, etc.
|
||||
bool color_channel_mask[3], // which color channel(s) to take into account
|
||||
float lower_threshold[3], // lower threshold for pixels to be considered sorted
|
||||
// when in doubt, use {-INF, -INF, -INF}
|
||||
float upper_threshold[3], // upper threshold; {INF, INF, INF}
|
||||
int step_ // from STEP
|
||||
) {
|
||||
// sanitize inputs
|
||||
direction = clamp(direction, vec2(-1, -1), vec2(1, 1));
|
||||
color_mode = clamp(color_mode, 0, 3);
|
||||
// get neighbour
|
||||
vec2 texture_size = vec2(textureSize(tex, 0));
|
||||
vec2 a = (mod(floor(uv * texture_size), 2.0) * 2.0 - 1.0) * (mod(float(step_), 2.0) * 2.0 - 1.0);
|
||||
vec2 neighbour_uv = uv + (direction * a / texture_size);
|
||||
//
|
||||
vec4 x = texture(tex, uv);
|
||||
vec4 y = texture(tex, neighbour_uv);
|
||||
if ( // stop at borders
|
||||
neighbour_uv.x > 1.0 ||
|
||||
neighbour_uv.x < 0.0 ||
|
||||
neighbour_uv.y > 1.0 ||
|
||||
neighbour_uv.y < 0.0
|
||||
) {
|
||||
return x;
|
||||
} else {
|
||||
// convert color if necessary
|
||||
// get value to compare
|
||||
float vx = 0.0;
|
||||
float vy = 0.0;
|
||||
vec3 color_x;
|
||||
vec3 color_y;
|
||||
if (color_mode == COLOR_MODE_RGB) {
|
||||
color_x = x.rgb;
|
||||
color_y = y.rgb;
|
||||
} else if (color_mode == COLOR_MODE_OKLAB) {
|
||||
color_x = rgb2oklab(x).rgb;
|
||||
color_y = rgb2oklab(y).rgb;
|
||||
} else if (color_mode == COLOR_MODE_OKLCH) {
|
||||
color_x = oklab2oklch(rgb2oklab(x)).rgb;
|
||||
color_y = oklab2oklch(rgb2oklab(y)).rgb;
|
||||
} else if (color_mode == COLOR_MODE_HSV) {
|
||||
color_x = rgb2hsv(x).rgb;
|
||||
color_y = rgb2hsv(y).rgb;
|
||||
}
|
||||
float divisor = 0.0;
|
||||
if (color_channel_mask[0]) {
|
||||
vx += color_x.r;
|
||||
vy += color_y.r;
|
||||
divisor += 1.0;
|
||||
}
|
||||
if (color_channel_mask[1]) {
|
||||
vx += color_x.g;
|
||||
vy += color_y.g;
|
||||
divisor += 1.0;
|
||||
}
|
||||
if (color_channel_mask[2]) {
|
||||
vx += color_x.b;
|
||||
vy += color_y.b;
|
||||
divisor += 1.0;
|
||||
}
|
||||
divisor = max(divisor, 1.0);
|
||||
vx /= divisor;
|
||||
vy /= divisor;
|
||||
//
|
||||
if (
|
||||
(a.x < .0 && abs(direction).y == .0) ||
|
||||
(a.y < .0 && abs(direction).x == .0)
|
||||
) {
|
||||
if (
|
||||
vy > vx &&
|
||||
// threshold
|
||||
color_x.r < upper_threshold[0] &&
|
||||
color_x.g < upper_threshold[1] &&
|
||||
color_x.b < upper_threshold[2] &&
|
||||
color_x.r > lower_threshold[0] &&
|
||||
color_x.g > lower_threshold[1] &&
|
||||
color_x.b > lower_threshold[2]
|
||||
) { return y; }
|
||||
else { return x; }
|
||||
} else if (
|
||||
(a.x > .0 && abs(direction).y == .0) ||
|
||||
(a.y > .0 && abs(direction).x == .0)
|
||||
) {
|
||||
if (
|
||||
vx >= vy &&
|
||||
// threshold
|
||||
color_y.r < upper_threshold[0] &&
|
||||
color_y.g < upper_threshold[1] &&
|
||||
color_y.b < upper_threshold[2] &&
|
||||
color_y.r > lower_threshold[0] &&
|
||||
color_y.g > lower_threshold[1] &&
|
||||
color_y.b > lower_threshold[2]
|
||||
) { return y; }
|
||||
else { return x; }
|
||||
}
|
||||
}
|
||||
}
|
1
shaderlib/pixelsort.gdshaderinc.uid
Normal file
1
shaderlib/pixelsort.gdshaderinc.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://doefnwk3vyr0o
|
50
shaderlib/sobel.gdshaderinc
Normal file
50
shaderlib/sobel.gdshaderinc
Normal file
|
@ -0,0 +1,50 @@
|
|||
|
||||
/*
|
||||
Edge Detection (Sobel Filter and Gaussian Blur) by FencerDevLog, adapted
|
||||
original code: https://godotshaders.com/shader/edge-detection-sobel-filter-and-gaussian-blur/
|
||||
license of the original code: CC0
|
||||
*/
|
||||
|
||||
vec3 _convolution(sampler2D tex, vec2 uv, vec2 pixel_size) {
|
||||
vec3 conv = vec3(0.0);
|
||||
// Gaussian blur kernel
|
||||
float gauss[25] = {
|
||||
0.00390625, 0.015625, 0.0234375, 0.015625, 0.00390625,
|
||||
0.015625, 0.0625, 0.09375, 0.0625, 0.015625,
|
||||
0.0234375, 0.09375, 0.140625, 0.09375, 0.0234375,
|
||||
0.015625, 0.0625, 0.09375, 0.0625, 0.015625,
|
||||
0.00390625, 0.015625, 0.0234375, 0.015625, 0.00390625
|
||||
};
|
||||
for (int row = 0; row < 5; row++) {
|
||||
for (int col = 0; col < 5; col++) {
|
||||
conv += texture(tex, uv + vec2(float(col - 2), float(row - 2)) * pixel_size).rgb * gauss[row * 5 + col];
|
||||
}
|
||||
}
|
||||
return conv;
|
||||
}
|
||||
|
||||
vec4 sobel(sampler2D tex, vec2 uv) {
|
||||
vec2 pixel_size = 1.0/vec2(textureSize(tex, 0));
|
||||
vec3 pixels[9]; // Sobel kernel
|
||||
// [0, 1, 2]
|
||||
// [3, 4, 5]
|
||||
// [6, 7, 8]
|
||||
for (int row = 0; row < 3; row++) {
|
||||
for (int col = 0; col < 3; col++) {
|
||||
vec2 uv_ = uv + vec2(float(col - 1), float(row - 1)) * pixel_size;
|
||||
pixels[row * 3 + col] = _convolution(tex, uv_, pixel_size);
|
||||
}
|
||||
}
|
||||
|
||||
// Sobel operator
|
||||
vec3 gx = (
|
||||
pixels[0] * -1.0 + pixels[3] * -2.0 + pixels[6] * -1.0
|
||||
+ pixels[2] * 1.0 + pixels[5] * 2.0 + pixels[8] * 1.0
|
||||
);
|
||||
vec3 gy = (
|
||||
pixels[0] * -1.0 + pixels[1] * -2.0 + pixels[2] * -1.0
|
||||
+ pixels[6] * 1.0 + pixels[7] * 2.0 + pixels[8] * 1.0
|
||||
);
|
||||
vec3 sobel = sqrt(gx * gx + gy * gy);
|
||||
return vec4(sobel, 1.0);
|
||||
}
|
1
shaderlib/sobel.gdshaderinc.uid
Normal file
1
shaderlib/sobel.gdshaderinc.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://bqo1fpunnl05f
|
|
@ -18,7 +18,7 @@ func _input(event):
|
|||
|
||||
var old_zoom = self.zoom
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
func _process(_delta: float) -> void:
|
||||
if self.zoom != old_zoom:
|
||||
image_viewport_display.update_zoom_texture_filter(self.zoom)
|
||||
image_viewport_display.material.set_shader_parameter("zoom_level", self.zoom)
|
||||
|
|
|
@ -37,19 +37,26 @@ func validate_shader_compilation(shader: Shader) -> bool:
|
|||
# test if uniform list is empty -> if it is empty, the shader compilation failed
|
||||
return len(shader.get_shader_uniform_list()) > 0
|
||||
|
||||
func shader_has_step_uniform(shader: Shader) -> bool:
|
||||
func shader_has_uniform(shader: Shader, name: String, type: int) -> bool:
|
||||
for u in shader.get_shader_uniform_list():
|
||||
if u["name"] == "STEP" && u["type"] == 2:
|
||||
if u["name"] == name && u["type"] == type:
|
||||
return true
|
||||
return false
|
||||
|
||||
func set_vsync(enabled: bool):
|
||||
if enabled:
|
||||
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_ENABLED)
|
||||
else:
|
||||
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED)
|
||||
|
||||
func update(overwrite_image_path: String = "") -> Array: # returns error messages (strings)
|
||||
var shader = Filesystem.shader # read from disk
|
||||
if shader == null:
|
||||
return ["No shader opened!"]
|
||||
# get number of steps & check if code has STEP uniform
|
||||
var steps: int = ShaderDirectiveParser.parse_steps_directive(shader.code)
|
||||
var has_step_uniform: bool = shader_has_step_uniform(shader)
|
||||
var has_step_uniform: bool = shader_has_uniform(shader, "STEP", 2)
|
||||
var has_steps_uniform: bool = shader_has_uniform(shader, "STEPS", 2)
|
||||
# validate shader
|
||||
if not validate_shader_compilation(shader):
|
||||
return ["Shader compilation failed!"]
|
||||
|
@ -84,6 +91,9 @@ func update(overwrite_image_path: String = "") -> Array: # returns error message
|
|||
image_sprite.texture = Filesystem.original_image
|
||||
image_sprite.offset = Filesystem.original_image.get_size() / 2
|
||||
self.size = Filesystem.original_image.get_size()
|
||||
# already show the image viewport & fit the image
|
||||
if fit_image: camera.fit_image()
|
||||
image_viewport_display.show()
|
||||
# create shader material
|
||||
var mat = ShaderMaterial.new()
|
||||
mat.shader = shader
|
||||
|
@ -95,6 +105,10 @@ func update(overwrite_image_path: String = "") -> Array: # returns error message
|
|||
# assign material
|
||||
image_sprite.material = mat
|
||||
# iterate n times
|
||||
set_vsync(false) # speed up processing
|
||||
if has_steps_uniform:
|
||||
# set STEPS param
|
||||
mat.set_shader_parameter("STEPS", steps)
|
||||
for i in range(steps):
|
||||
if has_step_uniform:
|
||||
# set STEP param
|
||||
|
@ -103,9 +117,7 @@ func update(overwrite_image_path: String = "") -> Array: # returns error message
|
|||
await RenderingServer.frame_post_draw # wait for next frame to get drawn
|
||||
Filesystem.result = get_texture().get_image()
|
||||
image_sprite.texture = ImageTexture.create_from_image(Filesystem.result)
|
||||
set_vsync(true) # reenable vsync
|
||||
image_sprite.material = null
|
||||
if fit_image:
|
||||
camera.fit_image()
|
||||
image_viewport_display.show()
|
||||
# done
|
||||
return errors
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue