Release/v6.1 #32
9 changed files with 100 additions and 61 deletions
24
README.md
24
README.md
|
@ -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.
|
||||||
|
|
20
examples/multistep_distort.gdshader
Normal file
20
examples/multistep_distort.gdshader
Normal 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);
|
||||||
|
}
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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]))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue