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>
|
||||
```
|
||||
|
||||
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.
|
||||
|
|
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]
|
||||
|
||||
config/name="Fragmented"
|
||||
config/version="v6.0"
|
||||
config/version="v6.1"
|
||||
run/main_scene="res://scenes/main.tscn"
|
||||
config/features=PackedStringArray("4.3", "Mobile")
|
||||
run/low_processor_mode=true
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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 steps: int = ShaderDirectiveParser.parse_steps_directive(shader.code)
|
||||
# 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.code)
|
||||
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.code):
|
||||
err = Filesystem.load_additional_image(n[1], Filesystem.get_absolute_path(n[2]))
|
||||
if err != "":
|
||||
errors.append(err)
|
||||
|
@ -60,20 +65,25 @@ 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)
|
||||
# iterate n times
|
||||
for i in range(steps):
|
||||
# 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()
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,22 +1,24 @@
|
|||
extends Node
|
||||
|
||||
var _load_regex: RegEx
|
||||
var _load_additional_regex: RegEx
|
||||
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 _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(.*)')
|
||||
|
||||
func parse_load_directive(shader: Shader) -> PackedStringArray:
|
||||
var regex_match = self._load_regex.search(Filesystem.shader.code)
|
||||
func parse_load_directive(shader_code: String) -> PackedStringArray:
|
||||
var regex_match = self._load_regex.search(shader_code)
|
||||
if regex_match == null:
|
||||
return []
|
||||
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] = []
|
||||
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)
|
||||
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