diff --git a/addons/material_maker/engine/gen_base.gd b/addons/material_maker/engine/gen_base.gd index 05e0beb..40b008e 100644 --- a/addons/material_maker/engine/gen_base.gd +++ b/addons/material_maker/engine/gen_base.gd @@ -1,4 +1,68 @@ +tool extends Node class_name MMGenBase +class OutputPort: + var generator : MMGenBase = null + var output_index : int = 0 + + func _init(g : MMGenBase, o : int): + generator = g + output_index = o + + func get_shader(): + return generator.get_shader(output_index) + + func get_globals(): + return generator.get_globals() +var parameters = null + +func get_seed(): + return 0 + +func get_source(input_index : int): + return get_parent().get_port_source(name, input_index) + +func get_input_shader(input_index : int): + var source = get_source(input_index) + if source != null: + return source.get_shader() + +func get_shader(output_index : int): + return get_shader_code("UV", output_index); + +# this will need an output index for switch +func get_globals(): + var list = [] + for i in range(10): + var source = get_source(i) + if source != null: + var source_list = source.get_globals() + for g in source_list: + if list.find(g) == -1: + list.append(g) + return list + +func get_shader_code(uv, slot = 0): + var rv + rv = _get_shader_code(uv, slot) + if !rv.has("f"): + if rv.has("rgb"): + rv.f = "(dot("+rv.rgb+", vec3(1.0))/3.0)" + elif rv.has("rgba"): + rv.f = "(dot("+rv.rgba+".rgb, vec3(1.0))/3.0)" + else: + rv.f = "0.0" + if !rv.has("rgb"): + if rv.has("rgba"): + rv.rgb = rv.rgba+".rgb" + else: + rv.rgb = "vec3("+rv.f+")" + if !rv.has("rgba"): + rv.rgba = "vec4("+rv.rgb+", 1.0)" + rv.globals = get_globals() + return rv + +func _get_shader_code(uv : String, output_index : int): + return null diff --git a/addons/material_maker/engine/gen_graph.gd b/addons/material_maker/engine/gen_graph.gd index a5caacc..af73b8d 100644 --- a/addons/material_maker/engine/gen_graph.gd +++ b/addons/material_maker/engine/gen_graph.gd @@ -1,2 +1,11 @@ +tool extends MMGenBase class_name MMGenGraph + +var connections = null + +func get_port_source(gen_name: String, input_index: int) -> OutputPort: + for c in connections: + if c.to == gen_name and c.to_port == input_index: + return OutputPort.new(get_node(c.from), c.from_port) + return null diff --git a/addons/material_maker/engine/gen_material.gd b/addons/material_maker/engine/gen_material.gd index 37f2e00..2853e2f 100644 --- a/addons/material_maker/engine/gen_material.gd +++ b/addons/material_maker/engine/gen_material.gd @@ -1,11 +1,122 @@ +tool extends MMGenBase class_name MMGenMaterial +var texture_list + +var material +var generated_textures = {} + +const TEXTURE_LIST = [ + { port=0, texture="albedo" }, + { port=1, texture="metallic" }, + { port=2, texture="roughness" }, + { port=3, texture="emission" }, + { port=4, texture="normal_map" }, + { port=5, texture="ambient_occlusion" }, + { port=6, texture="depth_map" } +] + +const ADDON_TEXTURE_LIST = [ + { port=0, texture="albedo" }, + { port=3, texture="emission" }, + { port=4, texture="normal_map" }, + { ports=[1, 2, 5], default_values=["0.0", "1.0", "1.0"], texture="mrao" }, + { port=6, texture="depth_map" } +] + +func _ready(): + 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 } + material = SpatialMaterial.new() + func generate_material(): print("Generating material") - var material = SpatialMaterial.new() + print(get_source(0).get_shader()) return material func initialize(data: Dictionary): if data.has("name"): name = data.name + +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 = parameters.albedo_color + m.albedo_texture = get_generated_texture("albedo", file_prefix) + m.metallic = parameters.metallic + m.roughness = parameters.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 = parameters.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 = parameters.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 = parameters.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 = parameters.depth_scale + m.depth_texture = texture + else: + m.depth_enabled = false + +func export_textures(prefix, size = null): + if size == null: + size = int(pow(2, 8+parameters.resolution)) + for t in texture_list: + var texture = generated_textures[t.texture].texture + if texture != null: + var image = texture.get_data() + image.save_png("%s_%s.png" % [ prefix, t.texture ]) + if Engine.editor_hint: + 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() diff --git a/addons/material_maker/engine/gen_shader.gd b/addons/material_maker/engine/gen_shader.gd index 6607849..8c6c916 100644 --- a/addons/material_maker/engine/gen_shader.gd +++ b/addons/material_maker/engine/gen_shader.gd @@ -1,14 +1,136 @@ +tool extends MMGenBase class_name MMGenShader -var config -var parameters +var model_data = null +var generated_variants = [] -func configure(c: Dictionary): - config = c +func set_model_data(data: Dictionary): + model_data = data func initialize(data: Dictionary): if data.has("name"): name = data.name if data.has("parameters"): parameters = data.parameters + +func find_keyword_call(string, keyword): + var search_string = "$%s(" % keyword + var position = string.find(search_string) + if position == -1: + return null + var parenthesis_level = 0 + var parameter_begin = position+search_string.length() + var parameter_end = -1 + for i in range(parameter_begin, string.length()): + if string[i] == '(': + parenthesis_level += 1 + elif string[i] == ')': + if parenthesis_level == 0: + return string.substr(parameter_begin, i-parameter_begin) + parenthesis_level -= 1 + return "" + +func replace_input(string, input, type, src, default): + var required_defs = "" + var required_code = "" + while true: + var uv = find_keyword_call(string, input) + if uv == null: + break + elif uv == "": + print("syntax error") + break + var src_code + if src == null: + src_code = subst(default, "(%s)" % uv) + else: + src_code = src.get_shader_code(uv) + src_code.string = src_code[type] + required_defs += src_code.defs + required_code += src_code.code + string = string.replace("$%s(%s)" % [ input, uv ], src_code.string) + return { string=string, defs=required_defs, code=required_code } + +func is_word_letter(l): + return "azertyuiopqsdfghjklmwxcvbnAZERTYUIOPQSDFGHJKLMWXCVBN1234567890_".find(l) != -1 + +func replace_variable(string, variable, value): + string = string.replace("$(%s)" % variable, value) + var keyword_size = variable.length()+1 + var new_string = "" + while !string.empty(): + var pos = string.find("$"+variable) + if pos == -1: + new_string += string + break + new_string += string.left(pos) + string = string.right(pos) + if string.empty() or !is_word_letter(string[0]): + new_string += value + else: + new_string += "$"+variable + string = string.right(keyword_size) + return new_string + +func subst(string, uv = ""): + var required_defs = "" + var required_code = "" + string = replace_variable(string, "name", name) + string = replace_variable(string, "seed", str(get_seed())) + if uv != "": + string = replace_variable(string, "uv", "("+uv+")") + if model_data.has("parameters") and typeof(model_data.parameters) == TYPE_ARRAY: + for p in model_data.parameters: + if !p.has("name") or !p.has("type"): + continue + var value = parameters[p.name] + var value_string = null + if p.type == "float": + value_string = "%.9f" % value + elif p.type == "size": + value_string = "%.9f" % pow(2, value+p.first) + elif p.type == "enum": + value_string = p.values[value].value + elif p.type == "color": + value_string = "vec4(%.9f, %.9f, %.9f, %.9f)" % [ value.r, value.g, value.b, value.a ] + if value_string != null: + string = replace_variable(string, p.name, value_string) + if model_data.has("inputs") and typeof(model_data.inputs) == TYPE_ARRAY: + for i in range(model_data.inputs.size()): + var input = model_data.inputs[i] + var source = get_source(i) + var result = replace_input(string, input.name, input.type, source, input.default) + string = result.string + required_defs += result.defs + required_code += result.code + return { string=string, defs=required_defs, code=required_code } + +func _get_shader_code(uv, slot = 0): + var output_info = [ { field="rgba", type="vec4" }, { field="rgb", type="vec3" }, { field="f", type="float" } ] + var rv = { defs="", code="" } + var variant_string = uv+","+str(slot) + if model_data != null and model_data.has("outputs") and model_data.outputs.size() > slot: + var output = model_data.outputs[slot] + if model_data.has("instance") && generated_variants.empty(): + rv.defs = subst(model_data.instance).string + var variant_index = generated_variants.find(variant_string) + if variant_index == -1: + variant_index = generated_variants.size() + generated_variants.append(variant_string) + for t in output_info: + if output.has(t.field): + var subst_output = subst(output[t.field], uv) + rv.defs += subst_output.defs + rv.code += subst_output.code + rv.code += "%s %s_%d_%d_%s = %s;\n" % [ t.type, name, slot, variant_index, t.field, subst_output.string ] + for t in output_info: + if output.has(t.field): + rv[t.field] = "%s_%d_%d_%s" % [ name, slot, variant_index, t.field ] + return rv + +func get_globals(): + var list = .get_globals() + if typeof(model_data) == TYPE_DICTIONARY and model_data.has("global") and list.find(model_data.global) == -1: + list.append(model_data.global) + return list diff --git a/addons/material_maker/engine/loader.gd b/addons/material_maker/engine/loader.gd index b99f823..357c5cc 100644 --- a/addons/material_maker/engine/loader.gd +++ b/addons/material_maker/engine/loader.gd @@ -1,3 +1,4 @@ +tool extends Object class_name MMGenLoader @@ -16,18 +17,22 @@ func create_gen(data) -> MMGenBase: var g = create_gen(n) if g != null: generator.add_child(g) + generator.connections = data.connections elif data.has("type"): if data.type == "material": generator = MMGenMaterial.new() else: generator = MMGenShader.new() - var file = File.new() - if file.open("res://addons/material_maker/nodes/"+data.type+".mmn", File.READ) == OK: - var config = parse_json(file.get_as_text()) - print("loaded description "+data.type+".mmn") - generator.configure(config) + if data.type == "custom": + pass else: - print("Cannot find description for "+data.type) + var file = File.new() + if file.open("res://addons/material_maker/nodes/"+data.type+".mmn", File.READ) == OK: + var model_data = parse_json(file.get_as_text()) + print("loaded description "+data.type+".mmn") + generator.set_model_data(model_data) + else: + print("Cannot find description for "+data.type) if generator != null and data.has("parameters"): generator.initialize(data) return generator diff --git a/addons/material_maker/nodes/blur/blur.gd b/addons/material_maker/nodes/blur/blur.gd index c2a8b49..e713b28 100644 --- a/addons/material_maker/nodes/blur/blur.gd +++ b/addons/material_maker/nodes/blur/blur.gd @@ -63,7 +63,7 @@ func get_textures(): list[name] = saved_texture return list -func _get_shader_code(uv): +func _get_shader_code(uv, slot = 0): var rv = { defs="", code="" } var src = get_source() if src == null: diff --git a/addons/material_maker/plugin.gd b/addons/material_maker/plugin.gd index 7af04c7..5260dc2 100644 --- a/addons/material_maker/plugin.gd +++ b/addons/material_maker/plugin.gd @@ -47,4 +47,5 @@ func generate_material(ptex_filename: String) -> Material: var loader = MMGenLoader.new() var generator = loader.load_gen(ptex_filename) add_child(generator) - return generator.get_node("Material").generate_material() + var material = generator.get_node("Material") + return material.generate_material()