Release/v6.1 #32

Merged
ChaoticByte merged 6 commits from release/v6.1 into main 2025-01-06 22:00:36 +00:00
9 changed files with 100 additions and 61 deletions

View file

@ -22,7 +22,7 @@ Just load an image using `//!load`, edit the shader code and hit `F5` to see the
//!load <filepath> //!load <filepath>
``` ```
The image file will be read and available as the `TEXTURE` variable. The main image file will be read and available as the sampler2D `TEXTURE`.
#### Load additional images #### Load additional images
@ -34,6 +34,28 @@ uniform sampler2D <name>;
Have a look at the `place_texture.gdshader` example. Have a look at the `place_texture.gdshader` example.
### Have multiple steps with `//!steps n`
You can apply your shaderfile multiple times. At every additional step, `TEXTURE` is the result of the previous step. This can be used to chain effects that cannot be easily chained otherwise.
To query the current step index, a `STEP` uniform is automatically injected. If `steps` is set to `0`, your shader won't be applied at all.
Example:
```glsl
//!load ...
//!steps 5
void fragment() {
if (STEP == 0) {
...
} else if (STEP == 1) {
...
}
// ... and so on
}
```
## Shaderlib ## Shaderlib
This repo comes with a (still small) shader library including pre-written functions and more. This repo comes with a (still small) shader library including pre-written functions and more.

View file

@ -0,0 +1,20 @@
shader_type canvas_item;
//!steps 9
//!load ./swamp.jpg
const float strength = 0.01;
void fragment() {
float v;
if (STEP % 3 == 0) {
v = COLOR.r; // 3 times
} else if (STEP % 3 == 0) {
v = COLOR.g; // 3 times
} else {
v = COLOR.b; // 3 times
}
vec2 uv = UV;
uv.y -= v * strength;
COLOR = texture(TEXTURE, uv);
}

View file

@ -1,13 +0,0 @@
shader_type canvas_item;
//!load ./swamp.jpg
const float strength = 0.1;
void fragment() {
vec2 uv = UV;
float b = (COLOR.r + COLOR.g + COLOR.b) / 3.0;
uv.x = mix(uv.x, b, strength);
uv.y = mix(uv.y, b, strength);
COLOR = texture(TEXTURE, uv);
}

View file

@ -11,7 +11,7 @@ config_version=5
[application] [application]
config/name="Fragmented" config/name="Fragmented"
config/version="v6.0" config/version="v6.1"
run/main_scene="res://scenes/main.tscn" run/main_scene="res://scenes/main.tscn"
config/features=PackedStringArray("4.3", "Mobile") config/features=PackedStringArray("4.3", "Mobile")
run/low_processor_mode=true run/low_processor_mode=true

View file

@ -42,7 +42,7 @@ script = ExtResource("8_mls06")
[node name="EditorWindow" type="Window" parent="."] [node name="EditorWindow" type="Window" parent="."]
unique_name_in_owner = true unique_name_in_owner = true
disable_3d = true disable_3d = true
position = Vector2i(48, 80) position = Vector2i(48, 36)
size = Vector2i(704, 704) size = Vector2i(704, 704)
script = ExtResource("6_8k0ha") script = ExtResource("6_8k0ha")

View file

@ -4,15 +4,10 @@ extends SubViewport
@onready var image_sprite = %ImageSprite @onready var image_sprite = %ImageSprite
@onready var image_viewport_display = %ImageViewportDisplay @onready var image_viewport_display = %ImageViewportDisplay
var _fragment_function_regex: RegEx var _fragment_function_regex: RegEx = RegEx.create_from_string(r'\s*void\s+fragment\s*\(\s*\)\s*{\s*')
func _init(): func validate_shader_compilation(shader: Shader) -> bool:
_fragment_function_regex = RegEx.new()
_fragment_function_regex.compile(r'\s*void\s+fragment\s*\(\s*\)\s*{\s*')
func validate_shader_compilation() -> bool:
# Inject code to validate shader compilation # Inject code to validate shader compilation
var shader: Shader = Filesystem.shader.duplicate()
var shader_code = shader.code; var shader_code = shader.code;
# -> get position of fragment shader # -> get position of fragment shader
var fragment_function_match = _fragment_function_regex.search(shader.code) var fragment_function_match = _fragment_function_regex.search(shader.code)
@ -29,19 +24,28 @@ func validate_shader_compilation() -> bool:
# test if uniform list is empty -> if it is empty, the shader compilation failed # test if uniform list is empty -> if it is empty, the shader compilation failed
return len(shader.get_shader_uniform_list()) > 0 return len(shader.get_shader_uniform_list()) > 0
func inject_step_uniform(shader_code: String) -> Shader:
var shader = Shader.new()
# this should run after validate_shader_compilation()
var fragment_function_match = _fragment_function_regex.search(shader_code)
shader.code = shader_code.insert(fragment_function_match.get_start(), "\nuniform int STEP;")
return shader
func update() -> Array: # returns error messages (strings) func update() -> Array: # returns error messages (strings)
if not validate_shader_compilation(): # inject STEP uniform & get number of steps
var shader: Shader = inject_step_uniform(Filesystem.shader_code)
var steps: int = ShaderDirectiveParser.parse_steps_directive(shader.code)
# validate shader
if not validate_shader_compilation(shader):
return ["Shader compilation failed!"] return ["Shader compilation failed!"]
var errors = [] var errors = []
var fit_image = false # load texture(s) from //!load directive -> TEXTURE
# load texture(s) var m = ShaderDirectiveParser.parse_load_directive(shader.code)
Filesystem.clear_additional_images()
# ... from //!load directive -> TEXTURE
var m = ShaderDirectiveParser.parse_load_directive(Filesystem.shader)
if len(m) < 1: if len(m) < 1:
errors.append("Didn't find a load directive!") errors.append("Didn't find a load directive!")
return errors return errors
var original_image_path = Filesystem.get_absolute_path(m[1]) var original_image_path = Filesystem.get_absolute_path(m[1])
var fit_image = false
if original_image_path != Filesystem.last_original_image_path: if original_image_path != Filesystem.last_original_image_path:
fit_image = true fit_image = true
var err = Filesystem.load_original_image(original_image_path) var err = Filesystem.load_original_image(original_image_path)
@ -50,7 +54,8 @@ func update() -> Array: # returns error messages (strings)
image_viewport_display.hide() image_viewport_display.hide()
return errors return errors
# ... from //!load+ directives # ... from //!load+ directives
for n in ShaderDirectiveParser.parse_load_additional_directive(Filesystem.shader): Filesystem.clear_additional_images()
for n in ShaderDirectiveParser.parse_load_additional_directive(shader.code):
err = Filesystem.load_additional_image(n[1], Filesystem.get_absolute_path(n[2])) err = Filesystem.load_additional_image(n[1], Filesystem.get_absolute_path(n[2]))
if err != "": if err != "":
errors.append(err) errors.append(err)
@ -60,20 +65,25 @@ func update() -> Array: # returns error messages (strings)
image_sprite.texture = Filesystem.original_image image_sprite.texture = Filesystem.original_image
image_sprite.offset = Filesystem.original_image.get_size() / 2 image_sprite.offset = Filesystem.original_image.get_size() / 2
self.size = Filesystem.original_image.get_size() self.size = Filesystem.original_image.get_size()
# create shader material
var mat = ShaderMaterial.new() var mat = ShaderMaterial.new()
mat.shader = Filesystem.shader mat.shader = shader
# ... as shader parameters # add images as shader parameters
for key in Filesystem.additional_images: for key in Filesystem.additional_images:
mat.set_shader_parameter( mat.set_shader_parameter(
key, # uniform param name key, # uniform param name
Filesystem.additional_images[key]) Filesystem.additional_images[key])
# assign material # iterate n times
image_sprite.material = mat for i in range(steps):
# Get viewport texture # set STEP param
await RenderingServer.frame_post_draw # for good measure mat.set_shader_parameter("STEP", i)
Filesystem.result = get_texture().get_image() # assign material
image_sprite.material = null image_sprite.material = mat
image_sprite.texture = ImageTexture.create_from_image(Filesystem.result) # Get viewport texture
await RenderingServer.frame_post_draw # for good measure
Filesystem.result = get_texture().get_image()
image_sprite.material = null
image_sprite.texture = ImageTexture.create_from_image(Filesystem.result)
if fit_image: if fit_image:
camera.fit_image() camera.fit_image()
camera.update_vd_zoomlevel() camera.update_vd_zoomlevel()

View file

@ -208,7 +208,7 @@ func _input(event):
_on_save_shader_button_pressed() _on_save_shader_button_pressed()
func update_code_edit(): func update_code_edit():
code_editor.text = Filesystem.shader.code code_editor.text = Filesystem.shader_code
enum Status {OKAY, ERROR, UNKNOWN = -1} enum Status {OKAY, ERROR, UNKNOWN = -1}
@ -239,14 +239,14 @@ func _on_open_shader_button_pressed():
open_shader_dialog.show() open_shader_dialog.show()
func _on_save_shader_button_pressed(): func _on_save_shader_button_pressed():
Filesystem.shader.code = code_editor.text Filesystem.shader_code = code_editor.text
if Filesystem.last_shader_savepath == "": if Filesystem.last_shader_savepath == "":
_on_save_shader_as_button_pressed() _on_save_shader_as_button_pressed()
else: else:
_on_save_shader_dialog_file_selected(Filesystem.last_shader_savepath) _on_save_shader_dialog_file_selected(Filesystem.last_shader_savepath)
func _on_save_shader_as_button_pressed() -> void: func _on_save_shader_as_button_pressed() -> void:
Filesystem.shader.code = code_editor.text Filesystem.shader_code = code_editor.text
if Filesystem.last_shader_savepath == "": if Filesystem.last_shader_savepath == "":
save_shader_dialog.current_file = "filter.gdshader" save_shader_dialog.current_file = "filter.gdshader"
else: else:
@ -257,7 +257,7 @@ func _on_fit_image_button_pressed():
camera.fit_image() camera.fit_image()
func _on_apply_shader_button_pressed(): func _on_apply_shader_button_pressed():
Filesystem.shader.code = code_editor.text Filesystem.shader_code = code_editor.text
var errors = await compositor.update() var errors = await compositor.update()
if len(errors) > 0: if len(errors) > 0:
update_status(Status.ERROR, "\n".join(errors)) update_status(Status.ERROR, "\n".join(errors))

View file

@ -1,7 +1,7 @@
extends Node extends Node
@onready var template_shader: Shader = load("res://src/shader/template.gdshader") @onready var template_shader: Shader = load("res://src/shader/template.gdshader")
@onready var shader: Shader = template_shader.duplicate() @onready var shader_code: String = template_shader.code
var original_image: ImageTexture var original_image: ImageTexture
var additional_images: Dictionary var additional_images: Dictionary
@ -13,7 +13,7 @@ var last_shader_savepath = ""
var last_original_image_path = "" var last_original_image_path = ""
func reset(): func reset():
self.shader = self.template_shader.duplicate() self.shader_code = self.template_shader.code
self.last_image_savepath = "" self.last_image_savepath = ""
self.last_shader_savepath = "" self.last_shader_savepath = ""
self.last_original_image_path = "" self.last_original_image_path = ""
@ -61,9 +61,7 @@ func save_result(path: String):
func load_shader(path: String): func load_shader(path: String):
print("Load ", path) print("Load ", path)
var file = FileAccess.open(path, FileAccess.READ) var file = FileAccess.open(path, FileAccess.READ)
var shader_code = file.get_as_text() self.shader_code = file.get_as_text()
self.shader = Shader.new()
shader.code = shader_code
if "/" in path: # update current working directory if "/" in path: # update current working directory
self.cwd = path.substr(0, path.rfind("/")) self.cwd = path.substr(0, path.rfind("/"))
self.last_shader_savepath = path self.last_shader_savepath = path
@ -71,7 +69,7 @@ func load_shader(path: String):
func save_shader(path: String): func save_shader(path: String):
print("Save ", path) print("Save ", path)
var file = FileAccess.open(path, FileAccess.WRITE) var file = FileAccess.open(path, FileAccess.WRITE)
file.store_string(self.shader.code) file.store_string(self.shader_code)
if "/" in path: # update current working directory if "/" in path: # update current working directory
self.cwd = path.substr(0, path.rfind("/")) self.cwd = path.substr(0, path.rfind("/"))
self.last_shader_savepath = path self.last_shader_savepath = path

View file

@ -1,22 +1,24 @@
extends Node extends Node
var _load_regex: RegEx var _load_regex: RegEx = RegEx.create_from_string(r'\/\/!load\s(.*)')
var _load_additional_regex: RegEx var _load_additional_regex: RegEx = RegEx.create_from_string(r'\/\/!load\+\s(\w*)\s(.*)')
var _iterate_regex: RegEx = RegEx.create_from_string(r'\/\/!steps\s([0-9]+)\s*')
func _ready(): func parse_load_directive(shader_code: String) -> PackedStringArray:
self._load_regex = RegEx.new() var regex_match = self._load_regex.search(shader_code)
self._load_additional_regex = RegEx.new()
self._load_regex.compile(r'\/\/!load\s(.*)')
self._load_additional_regex.compile(r'\/\/!load\+\s(\w*)\s(.*)')
func parse_load_directive(shader: Shader) -> PackedStringArray:
var regex_match = self._load_regex.search(Filesystem.shader.code)
if regex_match == null: if regex_match == null:
return [] return []
return regex_match.strings return regex_match.strings
func parse_load_additional_directive(shader: Shader) -> Array[PackedStringArray]: func parse_load_additional_directive(shader_code: String) -> Array[PackedStringArray]:
var results : Array[PackedStringArray] = [] var results : Array[PackedStringArray] = []
for m in self._load_additional_regex.search_all(shader.code): for m in self._load_additional_regex.search_all(shader_code):
results.append(m.strings) results.append(m.strings)
return results return results
func parse_steps_directive(shader_code: String) -> int:
var regex_match = self._iterate_regex.search(shader_code)
if regex_match == null:
return 1
else:
return max(0, int(regex_match.strings[1]))