Add //!steps directive, some code cleanup, updated README & examples

This commit is contained in:
ChaoticByte 2025-01-06 22:11:53 +01:00
parent 1b87cafb13
commit 1d9f838668
No known key found for this signature in database
7 changed files with 89 additions and 50 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>
```
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
@ -34,6 +34,28 @@ uniform sampler2D <name>;
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
This repo comes with a (still small) shader library including pre-written functions and more.

View file

@ -1,13 +1,20 @@
shader_type canvas_item;
//!steps 9
//!load ./swamp.jpg
const float strength = 0.1;
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;
float b = (COLOR.r + COLOR.g + COLOR.b) / 3.0;
uv.x = mix(uv.x, b, strength);
uv.y = mix(uv.y, b, strength);
uv.y -= v * strength;
COLOR = texture(TEXTURE, uv);
}

View file

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

View file

@ -4,15 +4,10 @@ extends SubViewport
@onready var image_sprite = %ImageSprite
@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():
_fragment_function_regex = RegEx.new()
_fragment_function_regex.compile(r'\s*void\s+fragment\s*\(\s*\)\s*{\s*')
func validate_shader_compilation() -> bool:
func validate_shader_compilation(shader: Shader) -> bool:
# Inject code to validate shader compilation
var shader: Shader = Filesystem.shader.duplicate()
var shader_code = shader.code;
# -> get position of fragment shader
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
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)
if not validate_shader_compilation():
# inject STEP uniform & get number of steps
var shader: Shader = inject_step_uniform(Filesystem.shader_code)
var step: int = ShaderDirectiveParser.parse_steps_directive(shader)
# validate shader
if not validate_shader_compilation(shader):
return ["Shader compilation failed!"]
var errors = []
var fit_image = false
# load texture(s)
Filesystem.clear_additional_images()
# ... from //!load directive -> TEXTURE
var m = ShaderDirectiveParser.parse_load_directive(Filesystem.shader)
# load texture(s) from //!load directive -> TEXTURE
var m = ShaderDirectiveParser.parse_load_directive(shader)
if len(m) < 1:
errors.append("Didn't find a load directive!")
return errors
var original_image_path = Filesystem.get_absolute_path(m[1])
var fit_image = false
if original_image_path != Filesystem.last_original_image_path:
fit_image = true
var err = Filesystem.load_original_image(original_image_path)
@ -50,7 +54,8 @@ func update() -> Array: # returns error messages (strings)
image_viewport_display.hide()
return errors
# ... 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):
err = Filesystem.load_additional_image(n[1], Filesystem.get_absolute_path(n[2]))
if err != "":
errors.append(err)
@ -60,22 +65,27 @@ func update() -> Array: # returns error messages (strings)
image_sprite.texture = Filesystem.original_image
image_sprite.offset = Filesystem.original_image.get_size() / 2
self.size = Filesystem.original_image.get_size()
# create shader material
var mat = ShaderMaterial.new()
mat.shader = Filesystem.shader
# ... as shader parameters
mat.shader = shader
# add images as shader parameters
for key in Filesystem.additional_images:
mat.set_shader_parameter(
key, # uniform param name
Filesystem.additional_images[key])
# assign material
image_sprite.material = mat
# 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:
camera.fit_image()
# iterate n times
for i in range(step):
# set STEP param
mat.set_shader_parameter("STEP", i)
# assign material
image_sprite.material = mat
# 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:
camera.fit_image()
camera.update_vd_zoomlevel()
image_viewport_display.show()
# done

View file

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

View file

@ -1,7 +1,7 @@
extends Node
@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 additional_images: Dictionary
@ -13,7 +13,7 @@ var last_shader_savepath = ""
var last_original_image_path = ""
func reset():
self.shader = self.template_shader.duplicate()
self.shader_code = self.template_shader.code
self.last_image_savepath = ""
self.last_shader_savepath = ""
self.last_original_image_path = ""
@ -61,9 +61,7 @@ func save_result(path: String):
func load_shader(path: String):
print("Load ", path)
var file = FileAccess.open(path, FileAccess.READ)
var shader_code = file.get_as_text()
self.shader = Shader.new()
shader.code = shader_code
self.shader_code = file.get_as_text()
if "/" in path: # update current working directory
self.cwd = path.substr(0, path.rfind("/"))
self.last_shader_savepath = path
@ -71,7 +69,7 @@ func load_shader(path: String):
func save_shader(path: String):
print("Save ", path)
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
self.cwd = path.substr(0, path.rfind("/"))
self.last_shader_savepath = path

View file

@ -1,16 +1,11 @@
extends Node
var _load_regex: RegEx
var _load_additional_regex: RegEx
func _ready():
self._load_regex = RegEx.new()
self._load_additional_regex = RegEx.new()
self._load_regex.compile(r'\/\/!load\s(.*)')
self._load_additional_regex.compile(r'\/\/!load\+\s(\w*)\s(.*)')
var _load_regex: RegEx = RegEx.create_from_string(r'\/\/!load\s(.*)')
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 parse_load_directive(shader: Shader) -> PackedStringArray:
var regex_match = self._load_regex.search(Filesystem.shader.code)
var regex_match = self._load_regex.search(shader.code)
if regex_match == null:
return []
return regex_match.strings
@ -20,3 +15,10 @@ func parse_load_additional_directive(shader: Shader) -> Array[PackedStringArray]
for m in self._load_additional_regex.search_all(shader.code):
results.append(m.strings)
return results
func parse_steps_directive(shader: Shader) -> 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]))