Improved integration in Godot

Exporting a material will now create a SpatialMaterial. The Material graph node
now generates different textures when used as a Godot addon (metallic, roughness
and ambient occlusion are merged into a single texture).

Rendering code (that was in graph_edit and node_base scenes) is now in a single "renderer scene" attached to the main window.
This commit is contained in:
RodZill4 2018-09-04 21:45:14 +02:00
parent 1c56fc60d8
commit 2621ff4b46
15 changed files with 338 additions and 226 deletions

View File

@ -1 +1 @@
{"connections":[{"from":"perlin_0","from_port":0,"to":"warp_0","to_port":0},{"from":"perlin_1","from_port":0,"to":"warp_0","to_port":1},{"from":"perlin_2","from_port":0,"to":"blend_0","to_port":0},{"from":"blend_0","from_port":0,"to":"normal_map_0","to_port":0},{"from":"normal_map_0","from_port":0,"to":"Material","to_port":4},{"from":"blend_0","from_port":0,"to":"colorize_0","to_port":0},{"from":"colorize_0","from_port":0,"to":"Material","to_port":2},{"from":"blend_0","from_port":0,"to":"Material","to_port":1},{"from":"voronoi_0","from_port":0,"to":"colorize_1","to_port":0},{"from":"warp_0","from_port":0,"to":"warp_1","to_port":0},{"from":"colorize_1","from_port":0,"to":"warp_1","to_port":1},{"from":"warp_1","from_port":0,"to":"blend_0","to_port":1},{"from":"combine_0","from_port":0,"to":"export_0","to_port":0},{"from":"colorize_0","from_port":0,"to":"combine_0","to_port":1},{"from":"blend_0","from_port":0,"to":"colorize_2","to_port":0},{"from":"blend_0","from_port":0,"to":"combine_0","to_port":0},{"from":"colorize_2","from_port":0,"to":"Material","to_port":0}],"nodes":[{"amount":0.1,"name":"normal_map_0","node_position":{"x":319,"y":265.5},"type":"normal_map"},{"gradient":[{"b":0.53125,"g":0.53125,"pos":0,"r":0.53125},{"b":0.708333,"g":0.708333,"pos":1,"r":0.708333}],"name":"colorize_0","node_position":{"x":313,"y":176.5},"type":"colorize"},{"iterations":6,"name":"perlin_2","node_position":{"x":-312,"y":2.5},"persistence":1,"scale_x":32,"scale_y":4,"type":"perlin"},{"iterations":3,"name":"perlin_1","node_position":{"x":-400,"y":359.5},"persistence":0.5,"scale_x":4,"scale_y":4,"type":"perlin"},{"iterations":3,"name":"perlin_0","node_position":{"x":-393,"y":203.5},"persistence":0.5,"scale_x":32,"scale_y":4,"type":"perlin"},{"amount":0.3,"name":"warp_0","node_position":{"x":-180,"y":317.5},"type":"warp"},{"gradient":[{"b":0.432292,"g":0.432292,"pos":0,"r":0.432292},{"b":0,"g":0,"pos":0.345455,"r":0}],"name":"colorize_1","node_position":{"x":-194,"y":466.5},"type":"colorize"},{"amount":0.1,"name":"warp_1","node_position":{"x":-31,"y":336.5},"type":"warp"},{"intensity":1,"name":"voronoi_0","node_position":{"x":-381,"y":505.5},"scale_x":5,"scale_y":4,"type":"voronoi"},{"amount":1,"blend_type":2,"name":"blend_0","node_position":{"x":83,"y":245.5},"type":"blend"},{"name":"combine_0","node_position":{"x":515.35144,"y":-15.818176},"type":"combine"},{"name":"export_0","node_position":{"x":656.35144,"y":-20.818176},"suffix":"mr","type":"export"},{"name":"Material","node_position":{"x":544,"y":79},"type":"material"},{"gradient":[{"b":0.071126,"g":0.34877,"pos":0,"r":0.59375},{"b":0.013021,"g":0.144043,"pos":1,"r":0.3125}],"name":"colorize_2","node_position":{"x":305.35144,"y":76.181824},"type":"colorize"}]} {"connections":[{"from":"perlin_0","from_port":0,"to":"warp_0","to_port":0},{"from":"perlin_1","from_port":0,"to":"warp_0","to_port":1},{"from":"perlin_2","from_port":0,"to":"blend_0","to_port":0},{"from":"blend_0","from_port":0,"to":"normal_map_0","to_port":0},{"from":"normal_map_0","from_port":0,"to":"Material","to_port":4},{"from":"blend_0","from_port":0,"to":"colorize_0","to_port":0},{"from":"colorize_0","from_port":0,"to":"Material","to_port":2},{"from":"blend_0","from_port":0,"to":"Material","to_port":1},{"from":"voronoi_0","from_port":0,"to":"colorize_1","to_port":0},{"from":"warp_0","from_port":0,"to":"warp_1","to_port":0},{"from":"colorize_1","from_port":0,"to":"warp_1","to_port":1},{"from":"warp_1","from_port":0,"to":"blend_0","to_port":1},{"from":"combine_0","from_port":0,"to":"export_0","to_port":0},{"from":"colorize_0","from_port":0,"to":"combine_0","to_port":1},{"from":"blend_0","from_port":0,"to":"colorize_2","to_port":0},{"from":"blend_0","from_port":0,"to":"combine_0","to_port":0},{"from":"colorize_2","from_port":0,"to":"Material","to_port":0}],"nodes":[{"iterations":6,"name":"perlin_2","node_position":{"x":-312,"y":2.5},"persistence":1,"scale_x":32,"scale_y":4,"type":"perlin"},{"iterations":3,"name":"perlin_1","node_position":{"x":-400,"y":359.5},"persistence":0.5,"scale_x":4,"scale_y":4,"type":"perlin"},{"iterations":3,"name":"perlin_0","node_position":{"x":-393,"y":203.5},"persistence":0.5,"scale_x":32,"scale_y":4,"type":"perlin"},{"amount":0.3,"name":"warp_0","node_position":{"x":-180,"y":317.5},"type":"warp"},{"gradient":[{"b":0.432292,"g":0.432292,"pos":0,"r":0.432292},{"b":0,"g":0,"pos":0.345455,"r":0}],"name":"colorize_1","node_position":{"x":-194,"y":466.5},"type":"colorize"},{"amount":0.1,"name":"warp_1","node_position":{"x":-31,"y":336.5},"type":"warp"},{"intensity":1,"name":"voronoi_0","node_position":{"x":-381,"y":505.5},"scale_x":5,"scale_y":4,"type":"voronoi"},{"amount":1,"blend_type":2,"name":"blend_0","node_position":{"x":83,"y":245.5},"type":"blend"},{"name":"combine_0","node_position":{"x":515.35144,"y":-15.818176},"type":"combine"},{"name":"export_0","node_position":{"x":656.35144,"y":-20.818176},"resolution":1,"suffix":"mr","type":"export"},{"albedo_color":{"a":1,"b":1,"g":1,"r":1,"type":"Color"},"ao_light_affect":1,"depth_scale":1,"emission_energy":1,"metallic":1,"name":"Material","node_position":{"x":544,"y":79},"normal_scale":1,"resolution":1,"roughness":1,"type":"material"},{"gradient":[{"b":0.071126,"g":0.34877,"pos":0,"r":0.59375},{"b":0.013021,"g":0.144043,"pos":1,"r":0.3125}],"name":"colorize_2","node_position":{"x":305.35144,"y":76.181824},"type":"colorize"},{"amount":0.1,"name":"normal_map_0","node_position":{"x":319,"y":265.5},"size":5,"type":"normal_map"},{"gradient":[{"b":0.53125,"g":0.53125,"pos":0,"r":0.53125},{"b":0.708333,"g":0.708333,"pos":1,"r":0.708333}],"name":"colorize_0","node_position":{"x":313,"y":176.5},"type":"colorize"}]}

View File

@ -1,6 +1,9 @@
tool tool
extends GraphEdit extends GraphEdit
var editor_interface = null
var renderer = null
var save_path = null var save_path = null
var need_save = false var need_save = false
@ -8,7 +11,6 @@ signal save_path_changed
signal graph_changed signal graph_changed
func _ready(): func _ready():
$SaveViewport/ColorRect.material = $SaveViewport/ColorRect.material.duplicate(true)
OS.low_processor_usage_mode = true OS.low_processor_usage_mode = true
center_view() center_view()
@ -284,59 +286,3 @@ func drop_data(position, data):
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
create_nodes(data, offset_from_global_position(get_global_transform().xform(position))) create_nodes(data, offset_from_global_position(get_global_transform().xform(position)))
return true return true
# Save shader to image, create image texture
func setup_material(shader_material, textures, shader_code):
for k in textures.keys():
shader_material.set_shader_param(k+"_tex", textures[k])
shader_material.shader.code = shader_code
var render_queue = []
func render_shader_to_viewport(shader, textures, size, method, args):
render_queue.append( { shader=shader, textures=textures, size=size, method=method, args=args } )
if render_queue.size() == 1:
while !render_queue.empty():
var job = render_queue.front()
$SaveViewport.size = Vector2(job.size, job.size)
$SaveViewport/ColorRect.rect_position = Vector2(0, 0)
$SaveViewport/ColorRect.rect_size = Vector2(job.size, job.size)
var shader_material = $SaveViewport/ColorRect.material
shader_material.shader.code = job.shader
if job.textures != null:
for k in job.textures.keys():
shader_material.set_shader_param(k+"_tex", job.textures[k])
$SaveViewport.render_target_update_mode = Viewport.UPDATE_ONCE
$SaveViewport.update_worlds()
yield(get_tree(), "idle_frame")
yield(get_tree(), "idle_frame")
callv(job.method, job.args)
render_queue.pop_front()
func render_to_viewport(node, size, method, args):
render_shader_to_viewport(node.generate_shader(), node.get_textures(), size, method, args)
func export_texture(node, filename, size = 256):
if node == null:
return null
render_to_viewport(node, size, "do_export_texture", [ filename ])
func do_export_texture(filename):
var viewport_texture = $SaveViewport.get_texture()
var viewport_image = viewport_texture.get_data()
viewport_image.save_png(filename)
func precalculate_node(node, size, target_texture, object, method, args):
if node == null:
return null
render_to_viewport(node, size, "do_precalculate_texture", [ object, method, args ])
func precalculate_shader(shader, textures, size, target_texture, object, method, args):
render_shader_to_viewport(shader, textures, size, "do_precalculate_texture", [ target_texture, object, method, args ])
func do_precalculate_texture(target_texture, object, method, args):
var viewport_texture = $SaveViewport.get_texture()
target_texture.create_from_image(viewport_texture.get_data())
object.callv(method, args)

View File

@ -1,4 +1,4 @@
[gd_scene load_steps=6 format=2] [gd_scene load_steps=4 format=2]
[ext_resource path="res://addons/material_maker/graph_edit.gd" type="Script" id=1] [ext_resource path="res://addons/material_maker/graph_edit.gd" type="Script" id=1]
[ext_resource path="res://addons/material_maker/nodes/material.tscn" type="PackedScene" id=2] [ext_resource path="res://addons/material_maker/nodes/material.tscn" type="PackedScene" id=2]
@ -6,20 +6,6 @@
[sub_resource type="Theme" id=1] [sub_resource type="Theme" id=1]
[sub_resource type="Shader" id=2]
code = "shader_type canvas_item;
void fragment() {
COLOR = vec4(1.0);
}
"
[sub_resource type="ShaderMaterial" id=3]
render_priority = 0
shader = SubResource( 2 )
[node name="GraphEdit" type="GraphEdit" index="0"] [node name="GraphEdit" type="GraphEdit" index="0"]
self_modulate = Color( 1, 1, 1, 0 ) self_modulate = Color( 1, 1, 1, 0 )
@ -35,7 +21,7 @@ mouse_default_cursor_shape = 0
size_flags_horizontal = 1 size_flags_horizontal = 1
size_flags_vertical = 1 size_flags_vertical = 1
right_disconnects = true right_disconnects = true
scroll_offset = Vector2( 0, 0 ) scroll_offset = Vector2( -525, -250 )
snap_distance = 20 snap_distance = 20
use_snap = false use_snap = false
zoom = 1.0 zoom = 1.0
@ -44,8 +30,12 @@ _sections_unfolded = [ "Material", "Mouse", "Visibility" ]
[node name="Material" parent="." index="0" instance=ExtResource( 2 )] [node name="Material" parent="." index="0" instance=ExtResource( 2 )]
margin_left = 525.0
margin_top = 250.0
margin_right = 755.0
margin_bottom = 470.0
theme = SubResource( 1 ) theme = SubResource( 1 )
_sections_unfolded = [ "Anchor", "Margin", "Mouse", "Theme", "slot", "slot/2", "slot/3", "slot/4", "slot/5" ] _sections_unfolded = [ "Anchor", "Margin", "Mouse", "Theme", "slot", "slot/0", "slot/1", "slot/2", "slot/3", "slot/4", "slot/5" ]
[node name="Timer" type="Timer" parent="." index="1"] [node name="Timer" type="Timer" parent="." index="1"]
@ -54,58 +44,6 @@ wait_time = 0.2
one_shot = true one_shot = true
autostart = false autostart = false
[node name="SaveViewport" type="Viewport" parent="." index="2"]
arvr = false
size = Vector2( 0, 0 )
own_world = true
world = null
transparent_bg = false
msaa = 2
hdr = false
disable_3d = false
usage = 2
debug_draw = 0
render_target_v_flip = true
render_target_clear_mode = 0
render_target_update_mode = 1
audio_listener_enable_2d = false
audio_listener_enable_3d = false
physics_object_picking = false
gui_disable_input = true
gui_snap_controls_to_pixels = true
shadow_atlas_size = 0
shadow_atlas_quad_0 = 2
shadow_atlas_quad_1 = 2
shadow_atlas_quad_2 = 3
shadow_atlas_quad_3 = 4
_sections_unfolded = [ "GUI", "Render Target", "Rendering" ]
[node name="ColorRect" type="ColorRect" parent="SaveViewport" index="0"]
material = SubResource( 3 )
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_right = 40.0
margin_bottom = 40.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 1
color = Color( 1, 1, 1, 1 )
_sections_unfolded = [ "Material", "Rect" ]
[node name="Timer" type="Timer" parent="SaveViewport" index="1"]
process_mode = 1
wait_time = 0.1
one_shot = true
autostart = false
[connection signal="connection_request" from="." to="." method="connect_node"] [connection signal="connection_request" from="." to="." method="connect_node"]
[connection signal="disconnection_request" from="." to="." method="disconnect_node"] [connection signal="disconnection_request" from="." to="." method="disconnect_node"]

View File

@ -1,6 +1,7 @@
tool tool
extends Panel extends Panel
var editor_interface = null
var current_tab = null var current_tab = null
const MENU = [ const MENU = [
@ -26,6 +27,8 @@ const MENU = [
{ menu="Help", command="about", description="About" } { menu="Help", command="about", description="About" }
] ]
signal quit
func _ready(): func _ready():
OS.set_window_title(ProjectSettings.get_setting("application/config/name")+" v"+ProjectSettings.get_setting("application/config/release")) OS.set_window_title(ProjectSettings.get_setting("application/config/name")+" v"+ProjectSettings.get_setting("application/config/release"))
for m in $VBoxContainer/Menu.get_children(): for m in $VBoxContainer/Menu.get_children():
@ -76,6 +79,8 @@ func menu_about_to_show(name, menu):
func new_pane(): func new_pane():
var graph_edit = preload("res://addons/material_maker/graph_edit.tscn").instance() var graph_edit = preload("res://addons/material_maker/graph_edit.tscn").instance()
graph_edit.renderer = $Renderer
graph_edit.editor_interface = editor_interface
$VBoxContainer/HBoxContainer/Projects.add_child(graph_edit) $VBoxContainer/HBoxContainer/Projects.add_child(graph_edit)
$VBoxContainer/HBoxContainer/Projects.current_tab = graph_edit.get_index() $VBoxContainer/HBoxContainer/Projects.current_tab = graph_edit.get_index()
return graph_edit return graph_edit
@ -140,8 +145,7 @@ func export_material_is_disabled():
func quit(): func quit():
if Engine.editor_hint: if Engine.editor_hint:
get_parent().hide() emit_signal("quit")
get_parent().queue_free()
else: else:
get_tree().quit() get_tree().quit()
@ -245,7 +249,7 @@ func update_preview_2d(node = null):
node = n node = n
break break
if node != null: if node != null:
graph_edit.setup_material(preview.get_2d_material(), node.get_textures(), node.generate_shader()) graph_edit.renderer.setup_material(preview.get_2d_material(), node.get_textures(), node.generate_shader())
func _on_Projects_tab_changed(tab): func _on_Projects_tab_changed(tab):
var new_tab = $VBoxContainer/HBoxContainer/Projects.get_current_tab_control() var new_tab = $VBoxContainer/HBoxContainer/Projects.get_current_tab_control()

View File

@ -1,9 +1,10 @@
[gd_scene load_steps=5 format=2] [gd_scene load_steps=6 format=2]
[ext_resource path="res://addons/material_maker/main_window.gd" type="Script" id=1] [ext_resource path="res://addons/material_maker/main_window.gd" type="Script" id=1]
[ext_resource path="res://addons/material_maker/library.gd" type="Script" id=2] [ext_resource path="res://addons/material_maker/library.gd" type="Script" id=2]
[ext_resource path="res://addons/material_maker/preview.tscn" type="PackedScene" id=3] [ext_resource path="res://addons/material_maker/preview.tscn" type="PackedScene" id=3]
[ext_resource path="res://addons/material_maker/widgets/tabs.gd" type="Script" id=4] [ext_resource path="res://addons/material_maker/widgets/tabs.gd" type="Script" id=4]
[ext_resource path="res://addons/material_maker/renderer.tscn" type="PackedScene" id=5]
[node name="MainWindow" type="Panel" index="0"] [node name="MainWindow" type="Panel" index="0"]
@ -260,6 +261,8 @@ tab_align = 0
tab_close_display_policy = 1 tab_close_display_policy = 1
scrolling_enabled = true scrolling_enabled = true
[node name="Renderer" parent="." index="1" instance=ExtResource( 5 )]
[connection signal="no_more_tabs" from="VBoxContainer/HBoxContainer/Projects" to="." method="new_material"] [connection signal="no_more_tabs" from="VBoxContainer/HBoxContainer/Projects" to="." method="new_material"]
[connection signal="resized" from="VBoxContainer/HBoxContainer/Projects" to="VBoxContainer/HBoxContainer/Projects" method="_on_Projects_resized"] [connection signal="resized" from="VBoxContainer/HBoxContainer/Projects" to="VBoxContainer/HBoxContainer/Projects" method="_on_Projects_resized"]

View File

@ -155,28 +155,12 @@ func deserialize_element(e):
return Color(e.r, e.g, e.b, e.a) return Color(e.r, e.g, e.b, e.a)
return e return e
func do_generate_shader(src_code):
var code
code = "shader_type canvas_item;\n"
var file = File.new()
file.open("res://addons/material_maker/common.shader", File.READ)
code += file.get_as_text()
code += "\n"
var shader_code = src_code.defs
shader_code += "void fragment() {\n"
shader_code += src_code.code
shader_code += "COLOR = vec4("+src_code.rgb+", 1.0);\n"
shader_code += "}\n"
#print("GENERATED SHADER:\n"+shader_code)
code += shader_code
return code
func generate_shader(slot = 0): func generate_shader(slot = 0):
# Reset all nodes # Reset all nodes
for c in get_parent().get_children(): for c in get_parent().get_children():
if c is GraphNode: if c is GraphNode:
c.reset() c.reset()
return do_generate_shader(get_shader_code("UV", slot)) return get_parent().renderer.generate_shader(get_shader_code("UV", slot))
func serialize(): func serialize():
var type = get_script().resource_path var type = get_script().resource_path

View File

@ -6,9 +6,7 @@ var direction = 0
var sigma = 1.0 var sigma = 1.0
var input_shader = "" var input_shader = ""
var input_texture var saved_texture
var mid_texture
var final_texture
const DIRECTION_H = 1 const DIRECTION_H = 1
const DIRECTION_V = 2 const DIRECTION_V = 2
@ -30,9 +28,7 @@ func _ready():
$HBoxContainer2/direction.add_item(d.name) $HBoxContainer2/direction.add_item(d.name)
$HBoxContainer2/direction.selected = direction $HBoxContainer2/direction.selected = direction
initialize_properties([ $HBoxContainer1/size, $HBoxContainer2/direction, $HBoxContainer3/sigma ]) initialize_properties([ $HBoxContainer1/size, $HBoxContainer2/direction, $HBoxContainer3/sigma ])
input_texture = ImageTexture.new() saved_texture = ImageTexture.new()
mid_texture = ImageTexture.new()
final_texture = ImageTexture.new()
func get_gaussian_blur_shader(horizontal): func get_gaussian_blur_shader(horizontal):
var convolution = { x=0, y=0, kernel=[], epsilon=1.0/pow(2, 5+size) } var convolution = { x=0, y=0, kernel=[], epsilon=1.0/pow(2, 5+size) }
@ -53,23 +49,23 @@ func get_gaussian_blur_shader(horizontal):
func _rerender(): func _rerender():
if DIRECTIONS[direction].mask & DIRECTION_H != 0: if DIRECTIONS[direction].mask & DIRECTION_H != 0:
get_parent().precalculate_shader(input_shader, get_source().get_textures(), int(pow(2, 5+size)), input_texture, self, "pass_1", []) get_parent().renderer.precalculate_shader(input_shader, get_source().get_textures(), int(pow(2, 5+size)), saved_texture, self, "pass_1", [])
else: else:
get_parent().precalculate_shader(input_shader, get_source().get_textures(), int(pow(2, 5+size)), mid_texture, self, "pass_2", []) get_parent().renderer.precalculate_shader(input_shader, get_source().get_textures(), int(pow(2, 5+size)), saved_texture, self, "pass_2", [])
func pass_1(): func pass_1():
if DIRECTIONS[direction].mask & DIRECTION_V != 0: if DIRECTIONS[direction].mask & DIRECTION_V != 0:
get_parent().precalculate_shader(get_gaussian_blur_shader(true), { input=input_texture}, int(pow(2, 5+size)), mid_texture, self, "pass_2", []) get_parent().renderer.precalculate_shader(get_gaussian_blur_shader(true), { input=saved_texture }, int(pow(2, 5+size)), saved_texture, self, "pass_2", [])
else: else:
get_parent().precalculate_shader(get_gaussian_blur_shader(true), { input=input_texture}, int(pow(2, 5+size)), final_texture, self, "rerender_targets", []) get_parent().renderer.precalculate_shader(get_gaussian_blur_shader(true), { input=saved_texture }, int(pow(2, 5+size)), saved_texture, self, "rerender_targets", [])
func pass_2(): func pass_2():
get_parent().precalculate_shader(get_gaussian_blur_shader(false), { input=mid_texture}, int(pow(2, 5+size)), final_texture, self, "rerender_targets", []) get_parent().renderer.precalculate_shader(get_gaussian_blur_shader(false), { input=saved_texture }, int(pow(2, 5+size)), saved_texture, self, "rerender_targets", [])
func get_textures(): func get_textures():
var list = {} var list = {}
list[name] = final_texture list[name] = saved_texture
return list return list
func _get_shader_code(uv): func _get_shader_code(uv):
@ -77,7 +73,7 @@ func _get_shader_code(uv):
var src = get_source() var src = get_source()
if src == null: if src == null:
return rv return rv
input_shader = do_generate_shader(src.get_shader_code("UV")) input_shader = get_parent().renderer.generate_shader(src.get_shader_code("UV"))
_rerender() _rerender()
if generated_variants.empty(): if generated_variants.empty():
rv.defs = "uniform sampler2D "+name+"_tex;\n" rv.defs = "uniform sampler2D "+name+"_tex;\n"

View File

@ -34,14 +34,14 @@ func _ready():
initialize_properties([ $HBoxContainer1/size, $HBoxContainer2/direction ]) initialize_properties([ $HBoxContainer1/size, $HBoxContainer2/direction ])
func _rerender(): func _rerender():
get_parent().precalculate_shader(input_shader, get_source().get_textures(), int(pow(2, 5+size)), input_texture, self, "pass_1", []) get_parent().renderer.precalculate_shader(input_shader, get_source().get_textures(), int(pow(2, 5+size)), input_texture, self, "pass_1", [])
func pass_1(): func pass_1():
var convolution = CONVOLUTION var convolution = CONVOLUTION
convolution.epsilon=1.0/pow(2, 5+size) convolution.epsilon=1.0/pow(2, 5+size)
for i in range(8): for i in range(8):
convolution.kernel[INDICES[i]] = COEFS[(i+8-int(direction))%8] convolution.kernel[INDICES[i]] = COEFS[(i+8-int(direction))%8]
get_parent().precalculate_shader(get_convolution_shader(convolution), {input=input_texture}, int(pow(2, 5+size)), final_texture, self, "rerender_targets", []) get_parent().renderer.precalculate_shader(get_convolution_shader(convolution), {input=input_texture}, int(pow(2, 5+size)), final_texture, self, "rerender_targets", [])
func get_textures(): func get_textures():
var list = {} var list = {}
@ -53,7 +53,7 @@ func _get_shader_code(uv):
var src = get_source() var src = get_source()
if src == null: if src == null:
return rv return rv
input_shader = do_generate_shader(src.get_shader_code("UV")) input_shader = get_parent().generate_shader(src.get_shader_code("UV"))
_rerender() _rerender()
if generated_variants.empty(): if generated_variants.empty():
rv.defs = "uniform sampler2D %s_tex;\n" % [ name ] rv.defs = "uniform sampler2D %s_tex;\n" % [ name ]

View File

@ -19,7 +19,7 @@ func export_textures(prefix, size = null):
if suffix != "": if suffix != "":
if size == null: if size == null:
size = int(pow(2, 8+resolution)) size = int(pow(2, 8+resolution))
get_parent().export_texture(get_source(), "%s_%s.png" % [ prefix, suffix ], size) get_parent().renderer.export_texture(get_source(), "%s_%s.png" % [ prefix, suffix ], size)
func serialize(): func serialize():
var data = .serialize() var data = .serialize()

View File

@ -10,6 +10,8 @@ var normal_scale
var ao_light_affect var ao_light_affect
var depth_scale var depth_scale
var texture_list
var current_material_list = [] var current_material_list = []
var generated_textures = {} var generated_textures = {}
@ -27,24 +29,30 @@ const ADDON_TEXTURE_LIST = [
{ port=0, texture="albedo" }, { port=0, texture="albedo" },
{ port=3, texture="emission" }, { port=3, texture="emission" },
{ port=4, texture="normal_map" }, { port=4, texture="normal_map" },
{ ports=[1, 2, 5], texture="mrao" }, { ports=[1, 2, 5], default_values=["0.0", "1.0", "1.0"], texture="mrao" },
{ port=6, texture="depth_map" } { port=6, texture="depth_map" }
] ]
func _ready(): func _ready():
for t in TEXTURE_LIST: texture_list = TEXTURE_LIST
if Engine.editor_hint:
texture_list = ADDON_TEXTURE_LIST
for t in texture_list:
generated_textures[t.texture] = { shader=null, source=null, texture=null } generated_textures[t.texture] = { shader=null, source=null, texture=null }
initialize_properties([ $resolution, $Albedo/albedo_color, $Metallic/metallic, $Roughness/roughness, $Emission/emission_energy, $NormalMap/normal_scale, $AmbientOcclusion/ao_light_affect, $DepthMap/depth_scale ]) initialize_properties([ $resolution, $Albedo/albedo_color, $Metallic/metallic, $Roughness/roughness, $Emission/emission_energy, $NormalMap/normal_scale, $AmbientOcclusion/ao_light_affect, $DepthMap/depth_scale ])
func _rerender(): func _rerender():
var size = int(pow(2, 8+resolution))
var has_textures = false var has_textures = false
for t in TEXTURE_LIST: for t in texture_list:
var shader = generated_textures[t.texture].shader var shader = generated_textures[t.texture].shader
if shader != null: if shader != null:
var input_textures = null var input_textures = {}
if generated_textures[t.texture].source != null: for s in generated_textures[t.texture].sources:
input_textures = generated_textures[t.texture].source.get_textures() var source_textures = s.get_textures()
get_parent().precalculate_shader(shader, input_textures, 1024, generated_textures[t.texture].texture, self, "do_update_materials", [ current_material_list ]) for st in source_textures.keys():
input_textures[st] = source_textures[st]
get_parent().renderer.precalculate_shader(shader, input_textures, size, generated_textures[t.texture].texture, self, "do_update_materials", [ current_material_list ])
has_textures = true has_textures = true
if !has_textures: if !has_textures:
do_update_materials(current_material_list) do_update_materials(current_material_list)
@ -59,63 +67,123 @@ func _get_shader_code(uv):
func update_materials(material_list): func update_materials(material_list):
current_material_list = material_list current_material_list = material_list
var has_textures = false var has_textures = false
for t in TEXTURE_LIST: for t in texture_list:
var source = get_source(t.port) var shader = null
if source == null: var sources = []
generated_textures[t.texture].shader = null if t.has("port"):
generated_textures[t.texture].source = null var source = get_source(t.port)
if source != null:
shader = source.generate_shader()
sources.append(source)
elif t.has("ports"):
var source = [ null, null, null ]
generated_textures[t.texture].mask = 0
for i in range(3):
source[i] = get_source(t.ports[i])
if source[i] != null:
sources.append(source[i])
generated_textures[t.texture].mask |= 1 << i
if !sources.empty():
for c in get_parent().get_children():
if c is GraphNode:
c.reset()
var source_code = [ null, null, null ]
for i in range(3):
if source[i] != null:
source_code[i] = source[i].get_shader_code("UV")
else:
source_code[i] = { defs="", code="", f=t.default_values[i] }
shader = get_parent().renderer.generate_combined_shader(source_code[0], source_code[1], source_code[2])
generated_textures[t.texture].shader = shader
generated_textures[t.texture].sources = sources
if shader == null:
if generated_textures[t.texture].texture != null: if generated_textures[t.texture].texture != null:
generated_textures[t.texture].texture = null generated_textures[t.texture].texture = null
else: else:
generated_textures[t.texture].shader = source.generate_shader()
generated_textures[t.texture].source = source
if generated_textures[t.texture].texture == null: if generated_textures[t.texture].texture == null:
generated_textures[t.texture].texture = ImageTexture.new() generated_textures[t.texture].texture = ImageTexture.new()
_rerender() _rerender()
func get_generated_texture(slot, file_prefix = null):
if file_prefix != null:
var file_name = "%s_%s.png" % [ file_prefix, slot ]
if File.new().file_exists(file_name):
return load(file_name)
else:
return null
else:
return generated_textures[slot].texture
func update_spatial_material(m, file_prefix = null):
var texture
m.albedo_color = albedo_color
m.albedo_texture = get_generated_texture("albedo", file_prefix)
m.metallic = metallic
m.roughness = roughness
if Engine.editor_hint:
texture = get_generated_texture("mrao", file_prefix)
m.metallic_texture = texture
m.metallic_texture_channel = SpatialMaterial.TEXTURE_CHANNEL_RED
m.roughness_texture = texture
m.roughness_texture_channel = SpatialMaterial.TEXTURE_CHANNEL_GREEN
else:
m.metallic_texture = get_generated_texture("metallic", file_prefix)
m.roughness_texture = get_generated_texture("roughness", file_prefix)
texture = get_generated_texture("emission", file_prefix)
if texture != null:
m.emission_enabled = true
m.emission_energy = emission_energy
m.emission_texture = texture
else:
m.emission_enabled = false
texture = get_generated_texture("normal_map", file_prefix)
if texture != null:
m.normal_enabled = true
m.normal_texture = texture
else:
m.normal_enabled = false
if Engine.editor_hint:
if (generated_textures.mrao.mask & (1 << 2)) != 0:
m.ao_enabled = true
m.ao_light_affect = ao_light_affect
m.ao_texture = m.metallic_texture
m.ao_texture_channel = SpatialMaterial.TEXTURE_CHANNEL_BLUE
else:
m.ao_enabled = false
else:
texture = get_generated_texture("ambient_occlusion", file_prefix)
if texture != null:
m.ao_enabled = true
m.ao_light_affect = ao_light_affect
m.ao_texture = texture
else:
m.ao_enabled = false
texture = get_generated_texture("depth_map", file_prefix)
if texture != null:
m.depth_enabled = true
m.depth_scale = depth_scale
m.depth_texture = texture
else:
m.depth_enabled = false
func do_update_materials(material_list): func do_update_materials(material_list):
for m in material_list: for m in material_list:
if m is SpatialMaterial: if m is SpatialMaterial:
m.albedo_color = albedo_color update_spatial_material(m)
m.albedo_texture = generated_textures.albedo.texture
m.metallic = metallic
m.metallic_texture = generated_textures.metallic.texture
m.roughness = roughness
m.roughness_texture = generated_textures.roughness.texture
if generated_textures.emission.texture != null:
m.emission_enabled = true
m.emission_energy = emission_energy
m.emission_texture = generated_textures.emission.texture
else:
m.emission_enabled = false
if generated_textures.normal_map.texture != null:
m.normal_enabled = true
m.normal_texture = generated_textures.normal_map.texture
else:
m.normal_enabled = false
if generated_textures.ambient_occlusion.texture != null:
m.ao_enabled = true
m.ao_light_affect = ao_light_affect
m.ao_texture = generated_textures.ambient_occlusion.texture
else:
m.ao_enabled = false
if generated_textures.depth_map.texture != null:
m.depth_enabled = true
m.depth_scale = depth_scale
m.depth_texture = generated_textures.depth_map.texture
else:
m.depth_enabled = false
func export_textures(prefix, size = null): func export_textures(prefix, size = null):
if size == null: if size == null:
size = int(pow(2, 8+resolution)) size = int(pow(2, 8+resolution))
var texture_list = TEXTURE_LIST
if Engine.editor_hint:
texture_list = ADDON_TEXTURE_LIST
for t in texture_list: for t in texture_list:
if t.has("port"): var texture = generated_textures[t.texture].texture
get_parent().export_texture(get_source(t.port), "%s_%s.png" % [ prefix, t.texture ], size) if texture != null:
elif t.has("ports"): var image = texture.get_data()
get_parent().export_combined_texture(get_source(t.port[0]), get_source(t.port[1]), get_source(t.port[2]), "%s_%s.png" % [ prefix, t.texture ], size) image.save_png("%s_%s.png" % [ prefix, t.texture ])
if Engine.editor_hint: if Engine.editor_hint:
print("Exporting material") var resource_filesystem = get_parent().editor_interface.get_resource_filesystem()
resource_filesystem.scan()
yield(resource_filesystem, "filesystem_changed")
var new_material = SpatialMaterial.new()
update_spatial_material(new_material, prefix)
ResourceSaver.save("%s.tres" % [ prefix ], new_material)
resource_filesystem.scan()

View File

@ -35,13 +35,13 @@ func _ready():
initialize_properties([ $HBoxContainer1/size, $HBoxContainer2/amount ]) initialize_properties([ $HBoxContainer1/size, $HBoxContainer2/amount ])
func _rerender(): func _rerender():
get_parent().precalculate_shader(input_shader, get_source().get_textures(), int(pow(2, 5+size)), input_texture, self, "pass_1", []) get_parent().renderer.precalculate_shader(input_shader, get_source().get_textures(), int(pow(2, 5+size)), input_texture, self, "pass_1", [])
func pass_1(): func pass_1():
var convolution = CONVOLUTION var convolution = CONVOLUTION
convolution.epsilon=1.0/pow(2, 5+size) convolution.epsilon=1.0/pow(2, 5+size)
convolution.scale_before_normalize = pow(2, size-1)*amount convolution.scale_before_normalize = pow(2, size-1)*amount
get_parent().precalculate_shader(get_convolution_shader(convolution), { input=input_texture}, int(pow(2, 5+size)), final_texture, self, "rerender_targets", []) get_parent().renderer.precalculate_shader(get_convolution_shader(convolution), { input=input_texture}, int(pow(2, 5+size)), final_texture, self, "rerender_targets", [])
func get_textures(): func get_textures():
var list = {} var list = {}
@ -53,7 +53,7 @@ func _get_shader_code(uv):
var src = get_source() var src = get_source()
if src == null: if src == null:
return rv return rv
input_shader = do_generate_shader(src.get_shader_code("UV")) input_shader = get_parent().renderer.generate_shader(src.get_shader_code("UV"))
_rerender() _rerender()
if generated_variants.empty(): if generated_variants.empty():
rv.defs = "uniform sampler2D %s_tex;\n" % [ name ] rv.defs = "uniform sampler2D %s_tex;\n" % [ name ]

View File

@ -5,17 +5,12 @@ var mm_button = null
var material_maker = null var material_maker = null
func _enter_tree(): func _enter_tree():
#material_maker = preload("res://addons/material_maker/pm_material_maker.tscn").instance()
#add_control_to_bottom_panel(material_maker, "ProceduralMaterial")
print("Adding menu item")
mm_button = Button.new() mm_button = Button.new()
mm_button.connect("pressed", self, "open_material_maker") mm_button.connect("pressed", self, "open_material_maker")
mm_button.text = "Material Maker" mm_button.text = "Material Maker"
add_control_to_container(CONTAINER_TOOLBAR, mm_button) add_control_to_container(CONTAINER_TOOLBAR, mm_button)
print("done")
func _exit_tree(): func _exit_tree():
#remove_control_from_bottom_panel(material_maker)
if mm_button != null: if mm_button != null:
remove_control_from_container(CONTAINER_TOOLBAR, mm_button) remove_control_from_container(CONTAINER_TOOLBAR, mm_button)
mm_button.queue_free() mm_button.queue_free()
@ -34,6 +29,15 @@ func _set_state(s):
func open_material_maker(): func open_material_maker():
if material_maker == null: if material_maker == null:
material_maker = load("res://addons/material_maker/window_dialog.tscn").instance() material_maker = preload("res://addons/material_maker/window_dialog.tscn").instance()
var panel = material_maker.get_node("MainWindow")
panel.editor_interface = get_editor_interface()
panel.connect("quit", self, "close_material_maker")
add_child(material_maker) add_child(material_maker)
material_maker.popup_centered() material_maker.popup_centered()
func close_material_maker():
if material_maker != null:
material_maker.hide()
material_maker.queue_free()
material_maker = null

View File

@ -0,0 +1,98 @@
tool
extends Viewport
var render_queue = []
func _ready():
$ColorRect.material = $ColorRect.material.duplicate(true)
# Save shader to image, create image texture
static func generate_shader(src_code):
var code
code = "shader_type canvas_item;\n"
var file = File.new()
file.open("res://addons/material_maker/common.shader", File.READ)
code += file.get_as_text()
code += "\n"
var shader_code = src_code.defs
shader_code += "void fragment() {\n"
shader_code += src_code.code
shader_code += "COLOR = vec4("+src_code.rgb+", 1.0);\n"
shader_code += "}\n"
#print("GENERATED SHADER:\n"+shader_code)
code += shader_code
return code
static func generate_combined_shader(red_code, green_code, blue_code):
var code
code = "shader_type canvas_item;\n"
var file = File.new()
file.open("res://addons/material_maker/common.shader", File.READ)
code += file.get_as_text()
code += "\n"
var shader_code = ""
shader_code += red_code.defs
shader_code += green_code.defs
shader_code += blue_code.defs
shader_code += "void fragment() {\n"
shader_code += red_code.code
shader_code += green_code.code
shader_code += blue_code.code
shader_code += "COLOR = vec4("+red_code.f+", "+green_code.f+", "+blue_code.f+", 1.0);\n"
shader_code += "}\n"
#print("GENERATED COMBINED SHADER:\n"+shader_code)
code += shader_code
return code
func setup_material(shader_material, textures, shader_code):
for k in textures.keys():
shader_material.set_shader_param(k+"_tex", textures[k])
shader_material.shader.code = shader_code
func render_shader_to_viewport(shader, textures, render_size, method, args):
render_queue.append( { shader=shader, textures=textures, size=render_size, method=method, args=args } )
if render_queue.size() == 1:
while !render_queue.empty():
var job = render_queue.front()
size = Vector2(job.size, job.size)
$ColorRect.rect_position = Vector2(0, 0)
$ColorRect.rect_size = Vector2(job.size, job.size)
var shader_material = $ColorRect.material
shader_material.shader.code = job.shader
if job.textures != null:
for k in job.textures.keys():
shader_material.set_shader_param(k+"_tex", job.textures[k])
render_target_update_mode = Viewport.UPDATE_ALWAYS
update_worlds()
yield(get_tree(), "idle_frame")
yield(get_tree(), "idle_frame")
render_target_update_mode = Viewport.UPDATE_DISABLED
callv(job.method, job.args)
render_queue.pop_front()
func render_to_viewport(node, render_size, method, args):
render_shader_to_viewport(node.generate_shader(), node.get_textures(), render_size, method, args)
func export_texture(node, filename, render_size = 256):
if node == null:
return null
render_to_viewport(node, render_size, "do_export_texture", [ filename ])
func do_export_texture(filename):
var viewport_texture = get_texture()
var viewport_image = viewport_texture.get_data()
viewport_image.save_png(filename)
func precalculate_node(node, render_size, target_texture, object, method, args):
if node == null:
return null
render_to_viewport(node, render_size, "do_precalculate_texture", [ object, method, args ])
func precalculate_shader(shader, textures, render_size, target_texture, object, method, args):
render_shader_to_viewport(shader, textures, render_size, "do_precalculate_texture", [ target_texture, object, method, args ])
func do_precalculate_texture(target_texture, object, method, args):
var viewport_texture = get_texture()
target_texture.create_from_image(viewport_texture.get_data())
object.callv(method, args)

View File

@ -0,0 +1,72 @@
[gd_scene load_steps=4 format=2]
[ext_resource path="res://addons/material_maker/renderer.gd" type="Script" id=1]
[sub_resource type="Shader" id=1]
code = "shader_type canvas_item;
void fragment() {
COLOR = vec4(1.0);
}
"
[sub_resource type="ShaderMaterial" id=2]
render_priority = 0
shader = SubResource( 1 )
[node name="Renderer" type="Viewport"]
arvr = false
size = Vector2( 0, 0 )
own_world = true
world = null
transparent_bg = false
msaa = 2
hdr = false
disable_3d = false
usage = 2
debug_draw = 0
render_target_v_flip = true
render_target_clear_mode = 0
render_target_update_mode = 1
audio_listener_enable_2d = false
audio_listener_enable_3d = false
physics_object_picking = false
gui_disable_input = true
gui_snap_controls_to_pixels = true
shadow_atlas_size = 0
shadow_atlas_quad_0 = 2
shadow_atlas_quad_1 = 2
shadow_atlas_quad_2 = 3
shadow_atlas_quad_3 = 4
script = ExtResource( 1 )
_sections_unfolded = [ "GUI", "Render Target", "Rendering" ]
[node name="ColorRect" type="ColorRect" parent="." index="0"]
material = SubResource( 2 )
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_right = 40.0
margin_bottom = 40.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 1
color = Color( 1, 1, 1, 1 )
_sections_unfolded = [ "Material", "Rect" ]
[node name="Timer" type="Timer" parent="." index="1"]
process_mode = 1
wait_time = 0.1
one_shot = true
autostart = false

View File

@ -1 +0,0 @@
{"connections":[{"from":"pattern_1","from_port":0,"to":"colorize_0","to_port":0},{"from":"perlin_0","from_port":0,"to":"transform_0","to_port":0},{"from":"colorize_0","from_port":0,"to":"transform_0","to_port":3},{"from":"uniform_0","from_port":0,"to":"Material","to_port":2},{"from":"normal_map_0","from_port":0,"to":"Material","to_port":4},{"from":"transform_0","from_port":0,"to":"normal_map_0","to_port":0},{"from":"transform_0","from_port":0,"to":"colorize_1","to_port":0},{"from":"colorize_1","from_port":0,"to":"Material","to_port":0}],"nodes":[{"iterations":7,"name":"perlin_0","node_position":{"x":-248,"y":-183.5},"persistence":0.5,"scale_x":4,"scale_y":4,"type":"perlin"},{"mix":0,"name":"pattern_1","node_position":{"x":-488,"y":6.5},"type":"pattern","x_scale":1,"x_wave":0,"y_scale":1,"y_wave":0},{"gradient":[{"b":0,"g":0,"pos":0,"r":0},{"b":1,"g":1,"pos":1,"r":1}],"name":"colorize_0","node_position":{"x":-214,"y":9.5},"type":"colorize"},{"albedo_color":{"a":1,"b":1,"g":1,"r":1,"type":"Color"},"ao_light_affect":1,"depth_scale":1,"emission_energy":1,"metallic":1,"name":"Material","node_position":{"x":387,"y":-79},"normal_scale":1,"resolution":1,"roughness":1,"type":"material"},{"color":{"a":1,"b":0,"g":0,"r":0,"type":"Color"},"name":"uniform_0","node_position":{"x":248.577271,"y":-53.178574},"type":"uniform"},{"amount":0.5,"name":"normal_map_0","node_position":{"x":234.577271,"y":52.821411},"type":"normal_map"},{"name":"transform_0","node_position":{"x":-8,"y":-73.5},"repeat":true,"rotate":100,"scale_x":1,"scale_y":1,"translate_x":0,"translate_y":0,"type":"transform"},{"gradient":[{"b":0,"g":0,"pos":0.3,"r":1},{"b":0,"g":1,"pos":0.409091,"r":0.96875},{"b":0,"g":0.801432,"pos":0.518182,"r":0.025045},{"b":1,"g":0.000001,"pos":0.627273,"r":0},{"b":0.708333,"g":0,"pos":0.745455,"r":0.641927}],"name":"colorize_1","node_position":{"x":213,"y":-149.5},"type":"colorize"}]}