1
0
Fork 0
mirror of https://github.com/Orama-Interactive/Pixelorama.git synced 2025-01-18 09:09:47 +00:00

Add a gaussian blur layer effect

This commit is contained in:
Emmanouil Papadeas 2024-09-08 02:40:28 +03:00
parent 28e143e033
commit b3021ceb67
3 changed files with 241 additions and 66 deletions

View file

@ -15,24 +15,27 @@ static func create_ui_for_shader_uniforms(
) -> void: ) -> void:
var code := shader.code.split("\n") var code := shader.code.split("\n")
var uniforms: PackedStringArray = [] var uniforms: PackedStringArray = []
var uniform_data: PackedStringArray = []
var description: String = "" var description: String = ""
var descriprion_began := false var description_began := false
for line in code: for line in code:
## Management of "end" tags # Management of "end" tags
if line.begins_with("// (end DESCRIPTION)"): if line.begins_with("// (end DESCRIPTION)"):
descriprion_began = false description_began = false
if descriprion_began: if description_began:
description += "\n" + line.strip_edges() description += "\n" + line.strip_edges()
## Detection of uniforms # Detection of uniforms
if line.begins_with("uniform"): if line.begins_with("uniform"):
uniforms.append(line) uniforms.append(line)
if line.begins_with("// uniform_data"):
uniform_data.append(line)
## Management of "begin" tags # Management of "begin" tags
elif line.begins_with("// (begin DESCRIPTION)"): elif line.begins_with("// (begin DESCRIPTION)"):
descriprion_began = true description_began = true
## Validation of begin/end tags # Validation of begin/end tags
if descriprion_began == true: ## Description started but never ended. treat it as an error if description_began == true: # Description started but never ended. treat it as an error
print("Shader description started but never finished. Assuming empty description") print("Shader description started but never finished. Assuming empty description")
description = "" description = ""
if not description.is_empty(): if not description.is_empty():
@ -59,65 +62,102 @@ static func create_ui_for_shader_uniforms(
var u_init := u_left_side[0].split(" ") var u_init := u_left_side[0].split(" ")
var u_type := u_init[1] var u_type := u_init[1]
var u_name := u_init[2] var u_name := u_init[2]
# Find custom data of the uniform, if any exists
# Right now it only checks if a uniform should have another type of node
# Such as integers having OptionButtons
# But in the future it could be expanded to include custom names or descriptions.
var custom_data: PackedStringArray = []
var type_override := ""
for data in uniform_data:
if u_name in data:
var line_to_examine := data.split(" ")
if line_to_examine[3] == "type::":
var temp_splitter := data.split("::")
if temp_splitter.size() > 1:
type_override = temp_splitter[1].strip_edges()
custom_data.append(data)
var humanized_u_name := Keychain.humanize_snake_case(u_name) + ":" var humanized_u_name := Keychain.humanize_snake_case(u_name) + ":"
if u_type == "float" or u_type == "int": if u_type == "float" or u_type == "int":
var hbox := HBoxContainer.new()
var label := Label.new() var label := Label.new()
label.text = humanized_u_name label.text = humanized_u_name
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
var slider := ValueSlider.new()
slider.allow_greater = true
slider.allow_lesser = true
slider.size_flags_horizontal = Control.SIZE_EXPAND_FILL
var min_value := 0.0
var max_value := 255.0
var step := 1.0
var range_values_array: PackedStringArray
if "hint_range" in u_hint:
var range_values: String = u_hint.replace("hint_range(", "")
range_values = range_values.replace(")", "").strip_edges()
range_values_array = range_values.split(",")
if u_type == "float":
if range_values_array.size() >= 1:
min_value = float(range_values_array[0])
else:
min_value = 0.01
if range_values_array.size() >= 2:
max_value = float(range_values_array[1])
if range_values_array.size() >= 3:
step = float(range_values_array[2])
else:
step = 0.01
if u_value != "":
slider.value = float(u_value)
else:
if range_values_array.size() >= 1:
min_value = int(range_values_array[0])
if range_values_array.size() >= 2:
max_value = int(range_values_array[1])
if range_values_array.size() >= 3:
step = int(range_values_array[2])
if u_value != "":
slider.value = int(u_value)
if params.has(u_name):
slider.value = params[u_name]
else:
params[u_name] = slider.value
slider.min_value = min_value
slider.max_value = max_value
slider.step = step
slider.value_changed.connect(value_changed.bind(u_name))
slider.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
var hbox := HBoxContainer.new()
hbox.add_child(label) hbox.add_child(label)
hbox.add_child(slider) if type_override.begins_with("OptionButton"):
var option_button := OptionButton.new()
option_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
option_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
option_button.item_selected.connect(value_changed.bind(u_name))
var items := (
type_override
. replace("OptionButton ", "")
. replace("[", "")
. replace("]", "")
. split("||")
)
for item in items:
option_button.add_item(item)
if u_value != "":
option_button.select(int(u_value))
if params.has(u_name):
option_button.select(params[u_name])
else:
params[u_name] = option_button.selected
hbox.add_child(option_button)
else:
var slider := ValueSlider.new()
slider.allow_greater = true
slider.allow_lesser = true
slider.size_flags_horizontal = Control.SIZE_EXPAND_FILL
var min_value := 0.0
var max_value := 255.0
var step := 1.0
var range_values_array: PackedStringArray
if "hint_range" in u_hint:
var range_values: String = u_hint.replace("hint_range(", "")
range_values = range_values.replace(")", "").strip_edges()
range_values_array = range_values.split(",")
if u_type == "float":
if range_values_array.size() >= 1:
min_value = float(range_values_array[0])
else:
min_value = 0.01
if range_values_array.size() >= 2:
max_value = float(range_values_array[1])
if range_values_array.size() >= 3:
step = float(range_values_array[2])
else:
step = 0.01
if u_value != "":
slider.value = float(u_value)
else:
if range_values_array.size() >= 1:
min_value = int(range_values_array[0])
if range_values_array.size() >= 2:
max_value = int(range_values_array[1])
if range_values_array.size() >= 3:
step = int(range_values_array[2])
if u_value != "":
slider.value = int(u_value)
if params.has(u_name):
slider.value = params[u_name]
else:
params[u_name] = slider.value
slider.min_value = min_value
slider.max_value = max_value
slider.step = step
slider.value_changed.connect(value_changed.bind(u_name))
slider.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
hbox.add_child(slider)
parent_node.add_child(hbox) parent_node.add_child(hbox)
elif u_type == "vec2" or u_type == "ivec2" or u_type == "uvec2": elif u_type == "vec2" or u_type == "ivec2" or u_type == "uvec2":
var label := Label.new() var label := Label.new()
@ -352,23 +392,23 @@ static func _get_loaded_texture(params: Dictionary, parameter_name: String) -> I
if parameter_name in params: if parameter_name in params:
if params[parameter_name] is ImageTexture: if params[parameter_name] is ImageTexture:
return params[parameter_name].get_image() return params[parameter_name].get_image()
var image = Image.create_empty(64, 64, false, Image.FORMAT_RGBA8) var image := Image.create_empty(64, 64, false, Image.FORMAT_RGBA8)
return image return image
static func _shader_update_texture( static func _shader_update_texture(
resource_proj: ResourceProject, value_changed: Callable, parameter_name: String resource_proj: ResourceProject, value_changed: Callable, parameter_name: String
) -> void: ) -> void:
var warnings = "" var warnings := ""
if resource_proj.frames.size() > 1: if resource_proj.frames.size() > 1:
warnings += "This resource is intended to have 1 frame only. Extra frames will be ignored." warnings += "This resource is intended to have 1 frame only. Extra frames will be ignored."
if resource_proj.layers.size() > 1: if resource_proj.layers.size() > 1:
warnings += "\nThis resource is intended to have 1 layer only. layers will be blended." warnings += "\nThis resource is intended to have 1 layer only. layers will be blended."
var updated_image = Image.create_empty( var updated_image := Image.create_empty(
resource_proj.size.x, resource_proj.size.y, false, Image.FORMAT_RGBA8 resource_proj.size.x, resource_proj.size.y, false, Image.FORMAT_RGBA8
) )
var frame = resource_proj.frames[0] var frame := resource_proj.frames[0]
DrawingAlgos.blend_layers(updated_image, frame, Vector2i.ZERO, resource_proj) DrawingAlgos.blend_layers(updated_image, frame, Vector2i.ZERO, resource_proj)
value_changed.call(ImageTexture.create_from_image(updated_image), parameter_name) value_changed.call(ImageTexture.create_from_image(updated_image), parameter_name)
if not warnings.is_empty(): if not warnings.is_empty():
@ -378,7 +418,7 @@ static func _shader_update_texture(
static func _modify_texture_resource( static func _modify_texture_resource(
image: Image, resource_name: StringName, update_callable: Callable image: Image, resource_name: StringName, update_callable: Callable
) -> void: ) -> void:
var resource_proj = ResourceProject.new([], resource_name, image.get_size()) var resource_proj := ResourceProject.new([], resource_name, image.get_size())
resource_proj.layers.append(PixelLayer.new(resource_proj)) resource_proj.layers.append(PixelLayer.new(resource_proj))
resource_proj.frames.append(resource_proj.new_empty_frame()) resource_proj.frames.append(resource_proj.new_empty_frame())
resource_proj.frames[0].cels[0].set_content(image) resource_proj.frames[0].cels[0].set_content(image)

View file

@ -0,0 +1,134 @@
// https://godotshaders.com/shader/gaussian-blur-functions-for-gles2/
// Licensed under MIT.
shader_type canvas_item;
// uniform_data blur_type type:: OptionButton [Xor's Gaussian Blur||Monk's Multi-Pass Gaussian Blur||NoDev's Single-Pass Gaussian Blur||NoDev's Multi-Pass Gaussian Blur]
uniform int blur_type : hint_range(0, 3, 1) = 0;
uniform int blur_amount = 16;
uniform float blur_radius = 1.0;
uniform vec2 blur_direction = vec2(1, 1);
// Xor's gaussian blur function
// Link: https://xorshaders.weebly.com/tutorials/blur-shaders-5-part-2
// Defaults from: https://www.shadertoy.com/view/Xltfzj
//
// BLUR BLURRINESS (Default 8.0)
// BLUR ITERATIONS (Default 16.0 - More is better but slower)
// BLUR QUALITY (Default 4.0 - More is better but slower)
//
// Desc.: Don't have the best performance but will run on almost
// anything, although, if developing for mobile, is better to use
// 'texture_nodevgaussian(...) instead'.
vec4 texture_xorgaussian(sampler2D tex, vec2 uv, vec2 pixel_size, float blurriness, int iterations, int quality) {
vec2 radius = blurriness / (1.0 / pixel_size).xy;
vec4 blurred_tex = texture(tex, uv);
for(float d = 0.0; d < TAU; d += TAU / float(iterations)) {
for(float i = 1.0 / float(quality); i <= 1.0; i += 1.0 / float(quality)) {
vec2 directions = uv + vec2(cos(d), sin(d)) * radius * i;
blurred_tex += texture(tex, directions);
}
}
blurred_tex /= float(quality) * float(iterations) + 1.0;
return blurred_tex;
}
// Experience-Monks' fast gaussian blur function
// Link: https://github.com/Experience-Monks/glsl-fast-gaussian-blur/
//
// BLUR ITERATIONS (Default 16.0 - More is better but slower)
// BLUR DIRECTION (Direction in which the blur is applied, use vec2(1, 0) for first pass and vec2(0, 1) for second pass)
//
// Desc.: ACTUALLY PRETTY SLOW but still pretty good for custom cinematic
// bloom effects, since this needs render 2 passes
vec4 texture_monksgaussian_multipass(sampler2D tex, vec2 uv, vec2 pixel_size, int iterations, vec2 direction) {
vec4 blurred_tex = vec4(0.0);
vec2 resolution = 1.0 / pixel_size;
for (int i = 0; i < iterations; i++ ) {
float size = float(iterations - i);
vec2 off1 = vec2(1.3846153846) * (direction * size);
vec2 off2 = vec2(3.2307692308) * (direction * size);
blurred_tex += texture(tex, uv) * 0.2270270270;
blurred_tex += texture(tex, uv + (off1 / resolution)) * 0.3162162162;
blurred_tex += texture(tex, uv - (off1 / resolution)) * 0.3162162162;
blurred_tex += texture(tex, uv + (off2 / resolution)) * 0.0702702703;
blurred_tex += texture(tex, uv - (off2 / resolution)) * 0.0702702703;
}
blurred_tex /= float(iterations) + 1.0;
return blurred_tex;
}
// u/_NoDev_'s gaussian blur function
// Discussion Link: https://www.reddit.com/r/godot/comments/klgfo9/help_with_shaders_in_gles2/
// Code Link: https://postimg.cc/7JDJw80d
//
// BLUR BLURRINESS (Default 8.0 - More is better but slower)
// BLUR RADIUS (Default 1.5)
// BLUR DIRECTION (Direction in which the blur is applied, use vec2(1, 0) for first pass and vec2(0, 1) for second pass)
//
// Desc.: Really fast and GOOD FOR MOST CASES, but might NOT RUN IN THE WEB!
// use 'texture_xorgaussian' instead if you found any issues.
vec4 texture_nodevgaussian_singlepass(sampler2D tex, vec2 uv, vec2 pixel_size, float blurriness, float radius) {
float n = 0.0015;
vec4 blurred_tex = vec4(0);
float weight;
for (float i = -blurriness; i <= blurriness; i++) {
float d = i / PI;
vec2 anchor = vec2(cos(d), sin(d)) * radius * i;
vec2 directions = uv + pixel_size * anchor;
blurred_tex += texture(tex, directions) * n;
if (i <= 0.0) {n += 0.0015; }
if (i > 0.0) {n -= 0.0015; }
weight += n;
}
float norm = 1.0 / weight;
blurred_tex *= norm;
return blurred_tex;
}
vec4 texture_nodevgaussian_multipass(sampler2D tex, vec2 uv, vec2 pixel_size, float blurriness, vec2 direction) {
float n = 0.0015;
vec4 blurred_tex = vec4(0);
float weight;
for (float i = -blurriness; i <= blurriness; i++) {
vec2 directions = uv + pixel_size * (direction * i);
blurred_tex += texture(tex, directions) * n;
if (i <= 0.0) {n += 0.0015; }
if (i > 0.0) {n -= 0.0015; }
weight += n;
}
float norm = 1.0 / weight;
blurred_tex *= norm;
return blurred_tex;
}
void fragment() {
if (blur_type == 0) {
vec4 xorgaussian = texture_xorgaussian(TEXTURE, UV, TEXTURE_PIXEL_SIZE, float(blur_amount), 16, 4);
COLOR = xorgaussian;
}
else if (blur_type == 1) {
vec4 monksgaussian_multipass = texture_monksgaussian_multipass(TEXTURE, UV, TEXTURE_PIXEL_SIZE, blur_amount, blur_direction);
COLOR = monksgaussian_multipass;
}
else if (blur_type == 2) {
vec4 nodevgaussian_singlepass = texture_nodevgaussian_singlepass(TEXTURE, UV, TEXTURE_PIXEL_SIZE, float(blur_amount), blur_radius);
COLOR = nodevgaussian_singlepass;
}
else if (blur_type == 3) {
vec4 nodevgaussian_multipass = texture_nodevgaussian_multipass(TEXTURE, UV, TEXTURE_PIXEL_SIZE, float(blur_amount), blur_direction);
COLOR = nodevgaussian_multipass;
}
else {
COLOR = texture(TEXTURE, UV);
}
}

View file

@ -7,6 +7,7 @@ var effects: Array[LayerEffect] = [
LayerEffect.new( LayerEffect.new(
"Convolution Matrix", preload("res://src/Shaders/Effects/ConvolutionMatrix.gdshader") "Convolution Matrix", preload("res://src/Shaders/Effects/ConvolutionMatrix.gdshader")
), ),
LayerEffect.new("Gaussian Blur", preload("res://src/Shaders/Effects/GaussianBlur.gdshader")),
LayerEffect.new("Offset", preload("res://src/Shaders/Effects/OffsetPixels.gdshader")), LayerEffect.new("Offset", preload("res://src/Shaders/Effects/OffsetPixels.gdshader")),
LayerEffect.new("Outline", preload("res://src/Shaders/Effects/OutlineInline.gdshader")), LayerEffect.new("Outline", preload("res://src/Shaders/Effects/OutlineInline.gdshader")),
LayerEffect.new("Drop Shadow", preload("res://src/Shaders/Effects/DropShadow.gdshader")), LayerEffect.new("Drop Shadow", preload("res://src/Shaders/Effects/DropShadow.gdshader")),