From 93dc0d248b6e7c4524ff3dd02ec58376b81c7230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Tue, 29 Dec 2020 17:39:18 +0100 Subject: [PATCH 01/74] Show warning when scene is missing a world and rp background is set to world --- blender/arm/exporter.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/blender/arm/exporter.py b/blender/arm/exporter.py index 5ee3a07f..1b43d619 100755 --- a/blender/arm/exporter.py +++ b/blender/arm/exporter.py @@ -1950,8 +1950,9 @@ class ArmoryExporter: self.output['tilesheet_datas'].append(o) def export_world(self): - """Exports the world of the scene.""" + """Exports the world of the current scene.""" world = self.scene.world + if world is not None: world_name = arm.utils.safestr(world.name) @@ -1962,6 +1963,9 @@ class ArmoryExporter: self.post_export_world(world, out_world) self.output['world_datas'].append(out_world) + elif arm.utils.get_rp().rp_background == 'World': + log.warn(f'Scene "{self.scene.name}" is missing a world, some render targets will not be cleared!') + def export_objects(self, scene): """Exports all supported blender objects. From 96dfc0629503846a04003e0b0a2f7b69dbfd75a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Wed, 30 Dec 2020 00:43:05 +0100 Subject: [PATCH 02/74] Show warnings for invalid instancing configurations --- blender/arm/material/make.py | 5 ++- blender/arm/material/make_shader.py | 63 ++++++++++++++++++++--------- 2 files changed, 49 insertions(+), 19 deletions(-) diff --git a/blender/arm/material/make.py b/blender/arm/material/make.py index c1ece5e5..59eebd81 100755 --- a/blender/arm/material/make.py +++ b/blender/arm/material/make.py @@ -1,6 +1,7 @@ import bpy import arm.utils import arm.node_utils +from typing import Dict import arm.material.make_shader as make_shader import arm.material.mat_batch as mat_batch import arm.material.mat_state as mat_state @@ -21,7 +22,8 @@ def glsl_value(val): else: return val -def parse(material, mat_data, mat_users, mat_armusers): + +def parse(material: bpy.types.Material, mat_data, mat_users: Dict[bpy.types.Material, bpy.types.Object], mat_armusers): wrd = bpy.data.worlds['Arm'] rpdat = arm.utils.get_rp() @@ -54,6 +56,7 @@ def parse(material, mat_data, mat_users, mat_armusers): bind_constants['mesh'] = [] bind_textures = {} bind_textures['mesh'] = [] + make_shader.make_instancing_and_skinning(material, mat_users) elif not wrd.arm_batch_materials or material.name.startswith('armdefault'): rpasses, shader_data, shader_data_name, bind_constants, bind_textures = make_shader.build(material, mat_users, mat_armusers) sd = shader_data.sd diff --git a/blender/arm/material/make_shader.py b/blender/arm/material/make_shader.py index b27f246b..ac4b42fc 100644 --- a/blender/arm/material/make_shader.py +++ b/blender/arm/material/make_shader.py @@ -1,6 +1,8 @@ import os import bpy import subprocess +from typing import Dict +import arm.log as log import arm.utils import arm.assets as assets import arm.material.mat_utils as mat_utils @@ -18,14 +20,14 @@ import arm.exporter rpass_hook = None -def build(material, mat_users, mat_armusers): +def build(material: bpy.types.Material, mat_users: Dict[bpy.types.Material, bpy.types.Object], mat_armusers): mat_state.mat_users = mat_users mat_state.mat_armusers = mat_armusers mat_state.material = material mat_state.nodes = material.node_tree.nodes mat_state.data = ShaderData(material) mat_state.output_node = cycles.node_by_type(mat_state.nodes, 'OUTPUT_MATERIAL') - if mat_state.output_node == None: + if mat_state.output_node is None: # Place empty material output to keep compiler happy.. mat_state.output_node = mat_state.nodes.new('ShaderNodeOutputMaterial') @@ -41,22 +43,7 @@ def build(material, mat_users, mat_armusers): if not os.path.exists(full_path): os.makedirs(full_path) - global_elems = [] - if mat_users != None and material in mat_users: - for bo in mat_users[material]: - # GPU Skinning - if arm.utils.export_bone_data(bo): - global_elems.append({'name': 'bone', 'data': 'short4norm'}) - global_elems.append({'name': 'weight', 'data': 'short4norm'}) - # Instancing - if bo.arm_instanced != 'Off' or material.arm_particle_flag: - global_elems.append({'name': 'ipos', 'data': 'float3'}) - if bo.arm_instanced == 'Loc + Rot' or bo.arm_instanced == 'Loc + Rot + Scale': - global_elems.append({'name': 'irot', 'data': 'float3'}) - if bo.arm_instanced == 'Loc + Scale' or bo.arm_instanced == 'Loc + Rot + Scale': - global_elems.append({'name': 'iscl', 'data': 'float3'}) - - mat_state.data.global_elems = global_elems + make_instancing_and_skinning(material, mat_users) bind_constants = dict() bind_textures = dict() @@ -161,3 +148,43 @@ def write_shader(rel_path, shader, ext, rpass, matname, keep_cache=True): args.append('pos') proc = subprocess.call(args) os.chdir(cwd) + + +def make_instancing_and_skinning(mat: bpy.types.Material, mat_users: Dict[bpy.types.Material, bpy.types.Object]) -> None: + """Build material with instancing or skinning if enabled. + If the material is a custom material, only validation checks for instancing are performed.""" + global_elems = [] + if mat_users is not None and mat in mat_users: + # Whether there are both an instanced object and a not instanced object with this material + instancing_usage = [False, False] + + for bo in mat_users[mat]: + if mat.arm_custom_material == '': + # GPU Skinning + if arm.utils.export_bone_data(bo): + global_elems.append({'name': 'bone', 'data': 'short4norm'}) + global_elems.append({'name': 'weight', 'data': 'short4norm'}) + + # Instancing + inst = bo.arm_instanced + if inst != 'Off' or mat.arm_particle_flag: + instancing_usage[0] = True + + if mat.arm_custom_material == '': + global_elems.append({'name': 'ipos', 'data': 'float3'}) + if 'Rot' in inst: + global_elems.append({'name': 'irot', 'data': 'float3'}) + if 'Scale' in inst: + global_elems.append({'name': 'iscl', 'data': 'float3'}) + + elif inst == 'Off': + # Ignore children of instanced objects, they are instanced even when set to 'Off' + instancing_usage[1] = bo.parent is None or bo.parent.arm_instanced == 'Off' + + if instancing_usage[0] and instancing_usage[1]: + # Display a warning for invalid instancing configurations + # See https://github.com/armory3d/armory/issues/2072 + log.warn(f'Material "{mat.name}" has both instanced and not instanced objects, objects might flicker!') + + if mat.arm_custom_material == '': + mat_state.data.global_elems = global_elems From 11591c79a47cacaa54fee548b8e345c4556b68d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Wed, 30 Dec 2020 01:06:54 +0100 Subject: [PATCH 03/74] Cleanup --- blender/arm/material/make.py | 107 +++++++++++----------------- blender/arm/material/make_shader.py | 55 +++++++------- 2 files changed, 71 insertions(+), 91 deletions(-) diff --git a/blender/arm/material/make.py b/blender/arm/material/make.py index 59eebd81..53ed1416 100755 --- a/blender/arm/material/make.py +++ b/blender/arm/material/make.py @@ -1,17 +1,15 @@ +from typing import Dict, List + import bpy -import arm.utils -import arm.node_utils -from typing import Dict +from bpy.types import Material +from bpy.types import Object + +import arm.material.cycles as cycles import arm.material.make_shader as make_shader import arm.material.mat_batch as mat_batch -import arm.material.mat_state as mat_state -import arm.material.cycles as cycles +import arm.node_utils +import arm.utils -def glsl_type(t): # Merge with cycles - if t == 'RGB' or t == 'RGBA' or t == 'VECTOR': - return 'vec3' - else: - return 'float' def glsl_value(val): if str(type(val)) == "": @@ -23,39 +21,25 @@ def glsl_value(val): return val -def parse(material: bpy.types.Material, mat_data, mat_users: Dict[bpy.types.Material, bpy.types.Object], mat_armusers): +def parse(material: Material, mat_data, mat_users: Dict[Material, List[Object]], mat_armusers): wrd = bpy.data.worlds['Arm'] rpdat = arm.utils.get_rp() # No batch - shader data per material if material.arm_custom_material != '': rpasses = ['mesh'] - sd = {} - sd['contexts'] = [] - con = {} - con['vertex_elements'] = [] - elem = {} - elem['name'] = 'pos' - elem['data'] = 'short4norm' - con['vertex_elements'].append(elem) - elem = {} - elem['name'] = 'nor' - elem['data'] = 'short2norm' - con['vertex_elements'].append(elem) - elem = {} - elem['name'] = 'tex' - elem['data'] = 'short2norm' - con['vertex_elements'].append(elem) - elem = {} - elem['name'] = 'tex1' - elem['data'] = 'short2norm' - con['vertex_elements'].append(elem) - sd['contexts'].append(con) + + con = {'vertex_elements': []} + con['vertex_elements'].append({'name': 'pos', 'data': 'short4norm'}) + con['vertex_elements'].append({'name': 'nor', 'data': 'short2norm'}) + con['vertex_elements'].append({'name': 'tex', 'data': 'short2norm'}) + con['vertex_elements'].append({'name': 'tex1', 'data': 'short2norm'}) + + sd = {'contexts': [con]} shader_data_name = material.arm_custom_material - bind_constants = {} - bind_constants['mesh'] = [] - bind_textures = {} - bind_textures['mesh'] = [] + bind_constants = {'mesh': []} + bind_textures = {'mesh': []} + make_shader.make_instancing_and_skinning(material, mat_users) elif not wrd.arm_batch_materials or material.name.startswith('armdefault'): rpasses, shader_data, shader_data_name, bind_constants, bind_textures = make_shader.build(material, mat_users, mat_armusers) @@ -66,39 +50,35 @@ def parse(material: bpy.types.Material, mat_data, mat_users: Dict[bpy.types.Mate # Material for rp in rpasses: - - c = {} - c['name'] = rp - c['bind_constants'] = [] + bind_constants[rp] - c['bind_textures'] = [] + bind_textures[rp] + c = { + 'name': rp, + 'bind_constants': [] + bind_constants[rp], + 'bind_textures': [] + bind_textures[rp], + } mat_data['contexts'].append(c) if rp == 'mesh': - const = {} - const['name'] = 'receiveShadow' - const['bool'] = material.arm_receive_shadow - c['bind_constants'].append(const) + c['bind_constants'].append({'name': 'receiveShadow', 'bool': material.arm_receive_shadow}) if material.arm_material_id != 0: - const = {} - const['name'] = 'materialID' - const['int'] = material.arm_material_id - c['bind_constants'].append(const) + c['bind_constants'].append({'name': 'materialID', 'int': material.arm_material_id}) + if material.arm_material_id == 2: wrd.world_defs += '_Hair' + elif rpdat.rp_sss_state == 'On': sss = False sss_node = arm.node_utils.get_node_by_type(material.node_tree, 'SUBSURFACE_SCATTERING') - if sss_node != None and sss_node.outputs[0].is_linked: # Check linked node + if sss_node is not None and sss_node.outputs[0].is_linked: # Check linked node sss = True sss_node = arm.node_utils.get_node_by_type(material.node_tree, 'BSDF_PRINCIPLED') - if sss_node != None and sss_node.outputs[0].is_linked and (sss_node.inputs[1].is_linked or sss_node.inputs[1].default_value != 0.0): + if sss_node is not None and sss_node.outputs[0].is_linked and (sss_node.inputs[1].is_linked or sss_node.inputs[1].default_value != 0.0): sss = True sss_node = arm.node_utils.get_node_armorypbr(material.node_tree) - if sss_node != None and sss_node.outputs[0].is_linked and (sss_node.inputs[8].is_linked or sss_node.inputs[8].default_value != 0.0): + if sss_node is not None and sss_node.outputs[0].is_linked and (sss_node.inputs[8].is_linked or sss_node.inputs[8].default_value != 0.0): sss = True - const = {} - const['name'] = 'materialID' + + const = {'name': 'materialID'} if sss: const['int'] = 2 else: @@ -114,27 +94,20 @@ def parse(material: bpy.types.Material, mat_data, mat_users: Dict[bpy.types.Mate if node.type == 'TEX_IMAGE': tex_name = arm.utils.safesrc(node.name) tex = cycles.make_texture(node, tex_name) - if tex == None: # Empty texture - tex = {} - tex['name'] = tex_name - tex['file'] = '' + # Empty texture + if tex is None: + tex = {'name': tex_name, 'file': ''} c['bind_textures'].append(tex) # Set marked inputs as uniforms for node in material.node_tree.nodes: for inp in node.inputs: if inp.is_uniform: - uname = arm.utils.safesrc(inp.node.name) + arm.utils.safesrc(inp.name) # Merge with cycles - const = {} - const['name'] = uname - const[glsl_type(inp.type)] = glsl_value(inp.default_value) - c['bind_constants'].append(const) + uname = arm.utils.safesrc(inp.node.name) + arm.utils.safesrc(inp.name) # Merge with cycles module + c['bind_constants'].append({'name': uname, cycles.glsl_type(inp.type): glsl_value(inp.default_value)}) elif rp == 'translucent': - const = {} - const['name'] = 'receiveShadow' - const['bool'] = material.arm_receive_shadow - c['bind_constants'].append(const) + c['bind_constants'].append({'name': 'receiveShadow', 'bool': material.arm_receive_shadow}) if wrd.arm_single_data_file: mat_data['shader'] = shader_data_name diff --git a/blender/arm/material/make_shader.py b/blender/arm/material/make_shader.py index ac4b42fc..93b40391 100644 --- a/blender/arm/material/make_shader.py +++ b/blender/arm/material/make_shader.py @@ -1,26 +1,31 @@ import os -import bpy import subprocess -from typing import Dict -import arm.log as log -import arm.utils -import arm.assets as assets -import arm.material.mat_utils as mat_utils -import arm.material.mat_state as mat_state -from arm.material.shader import ShaderData -import arm.material.cycles as cycles -import arm.material.make_mesh as make_mesh -import arm.material.make_transluc as make_transluc -import arm.material.make_overlay as make_overlay -import arm.material.make_depth as make_depth -import arm.material.make_decal as make_decal -import arm.material.make_voxel as make_voxel +from typing import Dict, List, Tuple + +import bpy +from bpy.types import Material +from bpy.types import Object + import arm.api +import arm.assets as assets import arm.exporter +import arm.log as log +import arm.material.cycles as cycles +import arm.material.make_decal as make_decal +import arm.material.make_depth as make_depth +import arm.material.make_mesh as make_mesh +import arm.material.make_overlay as make_overlay +import arm.material.make_transluc as make_transluc +import arm.material.make_voxel as make_voxel +import arm.material.mat_state as mat_state +import arm.material.mat_utils as mat_utils +from arm.material.shader import Shader, ShaderContext, ShaderData +import arm.utils rpass_hook = None -def build(material: bpy.types.Material, mat_users: Dict[bpy.types.Material, bpy.types.Object], mat_armusers): + +def build(material: Material, mat_users: Dict[Material, List[Object]], mat_armusers) -> Tuple: mat_state.mat_users = mat_users mat_state.mat_armusers = mat_armusers mat_state.material = material @@ -59,10 +64,10 @@ def build(material: bpy.types.Material, mat_users: Dict[bpy.types.Material, bpy. con = None - if rpdat.rp_driver != 'Armory' and arm.api.drivers[rpdat.rp_driver]['make_rpass'] != None: + if rpdat.rp_driver != 'Armory' and arm.api.drivers[rpdat.rp_driver]['make_rpass'] is not None: con = arm.api.drivers[rpdat.rp_driver]['make_rpass'](rp) - if con != None: + if con is not None: pass elif rp == 'mesh': @@ -86,7 +91,7 @@ def build(material: bpy.types.Material, mat_users: Dict[bpy.types.Material, bpy. elif rp == 'voxel': con = make_voxel.make(rp) - elif rpass_hook != None: + elif rpass_hook is not None: con = rpass_hook(rp) write_shaders(rel_path, con, rp, matname) @@ -94,7 +99,7 @@ def build(material: bpy.types.Material, mat_users: Dict[bpy.types.Material, bpy. shader_data_name = matname + '_data' if wrd.arm_single_data_file: - if not 'shader_datas' in arm.exporter.current_output: + if 'shader_datas' not in arm.exporter.current_output: arm.exporter.current_output['shader_datas'] = [] arm.exporter.current_output['shader_datas'].append(mat_state.data.get()['shader_datas'][0]) else: @@ -104,7 +109,8 @@ def build(material: bpy.types.Material, mat_users: Dict[bpy.types.Material, bpy. return rpasses, mat_state.data, shader_data_name, bind_constants, bind_textures -def write_shaders(rel_path, con, rpass, matname): + +def write_shaders(rel_path: str, con: ShaderContext, rpass: str, matname: str) -> None: keep_cache = mat_state.material.arm_cached write_shader(rel_path, con.vert, 'vert', rpass, matname, keep_cache=keep_cache) write_shader(rel_path, con.frag, 'frag', rpass, matname, keep_cache=keep_cache) @@ -112,8 +118,9 @@ def write_shaders(rel_path, con, rpass, matname): write_shader(rel_path, con.tesc, 'tesc', rpass, matname, keep_cache=keep_cache) write_shader(rel_path, con.tese, 'tese', rpass, matname, keep_cache=keep_cache) -def write_shader(rel_path, shader, ext, rpass, matname, keep_cache=True): - if shader == None or shader.is_linked: + +def write_shader(rel_path: str, shader: Shader, ext: str, rpass: str, matname: str, keep_cache=True) -> None: + if shader is None or shader.is_linked: return # TODO: blend context @@ -150,7 +157,7 @@ def write_shader(rel_path, shader, ext, rpass, matname, keep_cache=True): os.chdir(cwd) -def make_instancing_and_skinning(mat: bpy.types.Material, mat_users: Dict[bpy.types.Material, bpy.types.Object]) -> None: +def make_instancing_and_skinning(mat: Material, mat_users: Dict[Material, List[Object]]) -> None: """Build material with instancing or skinning if enabled. If the material is a custom material, only validation checks for instancing are performed.""" global_elems = [] From 27521265df35a40687e56e15e93d35219953157e Mon Sep 17 00:00:00 2001 From: luboslenco Date: Sun, 3 Jan 2021 12:11:59 +0100 Subject: [PATCH 04/74] Bump version --- blender/arm/props.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blender/arm/props.py b/blender/arm/props.py index ae6aa0a9..beed316f 100755 --- a/blender/arm/props.py +++ b/blender/arm/props.py @@ -11,7 +11,7 @@ import arm.proxy import arm.utils # Armory version -arm_version = '2020.12' +arm_version = '2021.1' arm_commit = '$Id$' def get_project_html5_copy(self): From 24f62623adf067727a4563c7c4d502841900e22f Mon Sep 17 00:00:00 2001 From: knowledgenude Date: Mon, 4 Jan 2021 11:28:59 -0300 Subject: [PATCH 05/74] add-inputmapper --- Sources/armory/system/InputMap.hx | 567 ++++++++++++++++++++++++++++++ 1 file changed, 567 insertions(+) create mode 100644 Sources/armory/system/InputMap.hx diff --git a/Sources/armory/system/InputMap.hx b/Sources/armory/system/InputMap.hx new file mode 100644 index 00000000..bcb20781 --- /dev/null +++ b/Sources/armory/system/InputMap.hx @@ -0,0 +1,567 @@ +package armory.system; + +import kha.FastFloat; +import iron.math.Vec4; +import iron.system.Input; + +class InputMap { + static var axes = new Map(); + static var actions = new Map(); + + public var index: Int; + public var currentTag: String; + + public function new(index: Int): Void { + this.index = index; + } + + /** + * Set the tag that this input map will look for + * @param tag The tag name + * @return Void + **/ + public function setCurrentTag(tag: String): Void { + currentTag = tag; + } + + /** + * Create input axis in the input map + * @param name The name of the input axis + * @param pressure The pressure required to activate pressure sensitivity buttons + * @param deadzone The displacement required to activate gamepad sticks and catch mouse movement + * @return Void + **/ + public function createAxis(name: String, ?pressure: FastFloat, ?deadzone: FastFloat): Void { + var p = pressure == null ? 0.0 : pressure; + var d = deadzone == null ? 0.0 : deadzone; + axes[name] = new InputAxis(this, index, p, d); + } + + /** + * Create input action in the input map + * @param name The name of the input action + * @param pressure The pressure required to activate pressure sensitivity buttons + * @param deadzone The displacement required to activate gamepad sticks and catch mouse movement + * @return Void + **/ + public function createAction(name: String, ?pressure: FastFloat, ?deadzone: FastFloat): Void { + var p = pressure == null ? 0.0 : pressure; + var d = deadzone == null ? 0.0 : deadzone; + actions[name] = new InputAction(this, index, p, d); + } + + /** + * Get the input axis present in the input map by its name + * @param name The name of the input axis + * @return InputAxis + **/ + public inline function getAxis(name: String): InputAxis { + return axes[name]; + } + + /** + * Get the input action present in the input map by its name + * @param name The name of the input action + * @return InputAction + **/ + public inline function getAction(name: String): InputAction { + return actions[name]; + } + + /** + * Get the vector of the given input axis + * @param name The name of the input axis + * @return Vec4 + **/ + public inline function getVec(name: String): Vec4 { + return axes[name].get(); + } + + /** + * Check if the given input action is started + * @param name The name of the input action + * @return Bool + **/ + public inline function isActionPressed(name: String): Bool { + return actions[name].pressed(); + } + + /** + * Check if the given input action is released + * @param name The name of the target input action + * @return Bool + **/ + public inline function isActionReleased(name: String): Bool { + return actions[name].released(); + } +} + +class InputAction { + static var components = new Map(); + + final parent: InputMap; + public final index: Int; + public final deadzone: FastFloat; + public final pressure: FastFloat; + + public function new(parent: InputMap, index: Int, pressure: FastFloat, deadzone: FastFloat) { + this.parent = parent; + this.index = index; + this.pressure = pressure; + this.deadzone = deadzone; + } + + /** + * Add a keyboard input component + * @param tag The input component tag + * @param key The key that should be started or released + * @param modifiers The keys that should be down before activate the main key + * @return Void + **/ + public function addKeyboardComponent(tag: String, key: String, ?modifiers): Void { + var mod = modifiers == null ? new Array() : modifiers; + addCustomComponent(tag, new KeyboardActionComponent(this, key, mod)); + } + + /** + * Add a mouse input component + * @param tag The input component tag + * @param button The button that should be started or released + * @param modifiers The buttons that should be down before activate the main key + * @return Void + **/ + public function addMouseComponent(tag: String, button: String, ?modifiers): Void { + var mod = modifiers == null ? new Array() : modifiers; + addCustomComponent(tag, new MouseActionComponent(this, button, mod)); + } + + /** + * Add a gamepad input component + * @param tag The input component tag + * @param button The button that should be started or released + * @param modifiers The buttons that should be down before activate the main key + * @return Void + **/ + public function addGamepadComponent(tag: String, button: String, ?modifiers): Void { + var mod = modifiers == null ? new Array() : modifiers; + addCustomComponent(tag, new GamepadActionComponent(this, button, mod)); + } + + /** + * Add a custom input component + * @param tag The input component tag + * @param component The constructed input component + * @return Void + **/ + public function addCustomComponent(tag: String, component: InputActionComponent): Void { + components[component] = tag; + } + + /** + * Remove an input component + * @param component The component to be removed + * @return Void + **/ + public function removeComponent(component: InputActionComponent): Void { + components.remove(component); + } + + public function pressed(): Bool { + for (component => tag in components) { + if (tag == parent.currentTag) { + if (component.started()) return true; + } + } + return false; + } + + public function released(): Bool { + for (component => tag in components) { + if (tag == parent.currentTag) { + if (component.released()) return true; + } + } + return false; + } +} + +class InputActionComponent { + var parent: InputAction; + var key: String; + var modifiers: Array; + + public function new(parent: InputAction, key: String, modifiers: Array): Void { + this.key = key; + this.modifiers = modifiers; + this.parent = parent; + } + + public function started(): Bool { + return false; + } + + public function released(): Bool { + return false; + } +} + +class KeyboardActionComponent extends InputActionComponent { + + final keyboard = Input.getKeyboard(); + + public function new(parent: InputAction, key: String, modifiers: Array): Void { + super(parent, key, modifiers); + this.parent = parent; + this.key = key; + this.modifiers = modifiers; + } + + public inline override function started(): Bool { + if (keyboard.started(key)) { + for (m in modifiers) { + if (!keyboard.down(m)) return false; + } + return true; + } + return false; + } + + public inline override function released(): Bool { + if (keyboard.released(key)) { + for (m in modifiers) { + if (!keyboard.down(m)) return false; + } + return true; + } + return false; + } +} + +class MouseActionComponent extends InputActionComponent { + + final mouse = Input.getMouse(); + + public function new(parent: InputAction, key: String, modifiers: Array): Void { + super(parent, key, modifiers); + this.parent = parent; + this.key = key; + this.modifiers = modifiers; + } + + public override function started(): Bool { + if (mouse.started(key)) { + for (m in modifiers) { + if (!mouse.down(m)) return false; + } + return true; + } + return false; + } + + public override function released(): Bool { + if (mouse.released(key)) { + for (m in modifiers) { + if (!mouse.down(m)) return false; + } + return true; + } + return false; + } +} + +class GamepadActionComponent extends InputActionComponent { + + final gamepad: Gamepad; + + public function new(parent: InputAction, key: String, modifiers: Array) { + super(parent, key, modifiers); + this.parent = parent; + this.key = key; + this.modifiers = modifiers; + gamepad = Input.getGamepad(parent.index); + } + + public inline override function started(): Bool { + if (gamepad.started(key)) { + for (m in modifiers) { + if (gamepad.down(m) <= parent.pressure) return false; + } + return true; + } + return false; + } + + public inline override function released(): Bool { + if (gamepad.released(key)) { + for (m in modifiers) { + if (gamepad.down(m) <= parent.pressure) return false; + } + return true; + } + return false; + } +} + +class InputAxis { + static var componentsX = new Map(); + static var scaleX = 1.0; + + static var componentsY = new Map(); + static var scaleY = 1.0; + + static var normalize = false; + static var vec = new Vec4(); + + final parent: InputMap; + public final index: Int; + public final deadzone: FastFloat; + public final pressure: FastFloat; + + public function new(parent: InputMap, index: Int, pressure: FastFloat, deadzone: FastFloat) { + this.parent = parent; + this.index = index; + this.pressure = pressure; + this.deadzone = deadzone; + } + + /** + * Add a keyboard input component + * @param position The position that the added input component will be in the returned vector ("x" or "y") + * @param tag The input component tag + * @param positiveKey The key that when pressed will sum +1 + * @param negativeKey The key that when pressed will sum -1 + * @return Void + **/ + public function addKeyboardComponent(position: String, tag: String, positiveKey: String, ?negativeKey: String): Void { + var n = negativeKey == null ? "" : negativeKey; + addCustomComponent(position, tag, new KeyboardAxisComponent(this, positiveKey, negativeKey)); + } + + /** + * Add a mouse input component + * @param position The position that the added input component will be in the returned vector ("x" or "y") + * @param tag The input component tag + * @param positiveButton The key that when pressed will sum +1 + * @param negativeButton The key that when pressed will sum -1 + * @return Void + **/ + public function addMouseComponent(position: String, tag: String, positiveButton: String, ?negativeButton: String): Void { + var n = negativeButton == null ? "" : negativeButton; + addCustomComponent(position, tag, new MouseAxisComponent(this, positiveButton, negativeButton)); + } + + /** + * Add a gamepad input component + * @param position The position that the added input component will be in the returned vector ("x" or "y") + * @param tag The input component tag + * @param positiveButton The key that when pressed will sum +1 + * @param negativeButton The key that when pressed will sum -1 + * @return Void + **/ + public function addGamepadComponent(position: String, tag: String, positiveButton: String, ?negativeButton: String): Void { + var n = negativeButton == null ? "" : negativeButton; + addCustomComponent(position, tag, new GamepadAxisComponent(this, positiveButton, negativeButton)); + } + + /** + * Add a custom input component + * @param tag The input component tag + * @param component The constructed input component + * @return Void + **/ + public function addCustomComponent(position: String, tag: String, component: InputAxisComponent): Void { + switch (position) { + case "x": componentsX[component] = tag; + case "y": componentsY[component] = tag; + } + } + + /** + * Remove an input component + * @param component The component to be removed + * @return Void + **/ + public function removeComponent(position: String, component: InputAxisComponent): Void { + switch (position) { + case "x": componentsX.remove(component); + case "y": componentsY.remove(component); + } + } + + /** + * Set the scale of the returned vector + * @param x The scale of the components in the position x + * @param y The scale of the components in the positon y + * @return Void + **/ + public function setScale(x: FastFloat, y: FastFloat): Void { + scaleX = x; + scaleY = y; + } + + /** + * Enable the returned vector normalization + * @return Void + **/ + public function enableNormalize(): Void { + normalize = true; + } + + /** + * Disable the returned vector normalization + * @return Void + **/ + public function disableNormalize(): Void { + normalize = false; + } + + /** + * Get the input axis vector + * @return Void + **/ + public inline function get(): Vec4 { + vec.set(0, 0, 0); + + for (component => tag in componentsX) { + if (tag == parent.currentTag) if (component.get() != 0.0) vec.x += component.get(); + } + + for (component => tag in componentsY) { + if (tag == parent.currentTag) if (component.get() != 0.0) vec.y += component.get(); + } + + if (normalize) vec.normalize(); + + vec.x *= scaleX; + vec.y *= scaleY; + + return vec; + } +} + +class InputAxisComponent { + var parent: InputAxis; + var positiveKey: String; + var negativeKey: String; + var scale: FastFloat; + + public function new(parent: InputAxis, positiveKey: String, negativeKey: String): Void { + this.parent = parent; + this.positiveKey = positiveKey; + this.negativeKey = negativeKey; + } + + public function get(): FastFloat { + return 0.0; + } +} + +class KeyboardAxisComponent extends InputAxisComponent { + final keyboard = Input.getKeyboard(); + + public function new(parent: InputAxis, positiveKey: String, negativeKey: String): Void { + super(parent, positiveKey, negativeKey); + this.parent = parent; + this.positiveKey = positiveKey; + this.negativeKey = negativeKey; + } + + public inline override function get(): FastFloat { + scale = 0.0; + if (keyboard.down(positiveKey)) scale++; + if (keyboard.down(negativeKey)) scale--; + return scale; + } +} + +class MouseAxisComponent extends InputAxisComponent { + final mouse = Input.getMouse(); + + public function new(parent: InputAxis, positiveKey: String, negativeKey: String): Void { + super(parent, positiveKey, negativeKey); + this.parent = parent; + this.positiveKey = positiveKey; + this.negativeKey = negativeKey; + } + + public inline override function get(): FastFloat { + scale = 0.0; + var movX = mouse.movementX; + var movY = mouse.movementY; + var wheelMov = mouse.wheelDelta; + + switch (positiveKey) { + case "moved x": if (movX > parent.deadzone) scale++; + case "movement x": if (movX > parent.deadzone) return movX - parent.deadzone; + case "moved y": if (movY > parent.deadzone) scale++; + case "movement y": if (movY > parent.deadzone) return movY - parent.deadzone; + case "wheel moved": if (wheelMov > parent.deadzone) scale ++; + case "wheel movement": if (wheelMov > parent.deadzone) return wheelMov - parent.deadzone; + default: if (mouse.down(positiveKey)) scale++; + } + switch (negativeKey) { + case "moved x": if (movX < -parent.deadzone) scale--; + case "movement x": if (movX < -parent.deadzone) return movX + parent.deadzone; + case "moved y": if (movY < -parent.deadzone) scale--; + case "movement y": if (movY < -parent.deadzone) return movY + parent.deadzone; + case "wheel moved": if (wheelMov > parent.deadzone) scale --; + case "wheel movement": if (wheelMov > parent.deadzone) return wheelMov + parent.deadzone; + default: if (mouse.down(negativeKey)) scale--; + } + return scale; + } +} + +class GamepadAxisComponent extends InputAxisComponent { + final gamepad: Gamepad; + + public function new(parent: InputAxis, positiveKey: String, negativeKey: String): Void { + super(parent, positiveKey, negativeKey); + this.parent = parent; + this.positiveKey = positiveKey; + this.negativeKey = negativeKey; + gamepad = Input.getGamepad(parent.index); + } + + public inline override function get(): FastFloat { + scale = 0.0; + var rightMovX = gamepad.rightStick.movementX; + var rightMovY = gamepad.rightStick.movementY; + var leftMovX = gamepad.leftStick.movementX; + var leftMovY = gamepad.leftStick.movementY; + + // Avoid division by zero + var rightTrigger = gamepad.down("r2") > 0.0 ? (gamepad.down("r2") - parent.pressure) / (1 - parent.pressure) : 0.0; + var leftTrigger = gamepad.down("l2") > 0.0 ? (gamepad.down("r2") - parent.pressure) / (1 - parent.pressure) : 0.0; + + switch (positiveKey) { + case "right stick moved x": if (rightMovX > parent.deadzone) scale++; + case "right stick movement x": if (rightMovX > parent.deadzone) return rightMovX - parent.deadzone; + case "right stick moved y": if (rightMovY > parent.deadzone) scale++; + case "right stick movement y": if (rightMovY > parent.deadzone) return rightMovY - parent.deadzone; + case "left stick moved x": if (leftMovX > parent.deadzone) scale++; + case "left stick movement x": if (leftMovX > parent.deadzone) return leftMovX - parent.deadzone; + case "left stick moved y": if (leftMovY > parent.deadzone) scale++; + case "left stick movement y": if (leftMovY > parent.deadzone) return leftMovY - parent.deadzone; + case "right trigger": scale += rightTrigger; + case "left trigger": scale += leftTrigger; + default: if (gamepad.down(positiveKey) > parent.pressure) scale++; + } + + switch (negativeKey) { + case "right stick moved x": if (rightMovX < -parent.deadzone) scale--; + case "right stick movement x": if (rightMovX < -parent.deadzone) return rightMovX + parent.deadzone; + case "right stick moved y": if (rightMovY < -parent.deadzone) scale--; + case "right stick movement y": if (rightMovY < -parent.deadzone) return rightMovY + parent.deadzone; + case "left stick moved x": if (leftMovX < -parent.deadzone) scale--; + case "left stick movement x": if (leftMovX < -parent.deadzone) return leftMovX + parent.deadzone; + case "left stick moved y": if (leftMovY < -parent.deadzone) scale--; + case "left stick movement y": if (leftMovY < -parent.deadzone) return leftMovY + parent.deadzone; + case "right trigger": scale -= rightTrigger; + case "left trigger": scale -= leftTrigger; + default: if (gamepad.down(negativeKey) < -parent.pressure) scale--; + } + + return scale; + } +} From ae057508cab5509bb47158e2aec93b493dfaca45 Mon Sep 17 00:00:00 2001 From: knowledgenude <63247726+knowledgenude@users.noreply.github.com> Date: Mon, 4 Jan 2021 12:09:30 -0300 Subject: [PATCH 06/74] Fix mouse wheel key verification --- Sources/armory/system/InputMap.hx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/armory/system/InputMap.hx b/Sources/armory/system/InputMap.hx index bcb20781..1e432c39 100644 --- a/Sources/armory/system/InputMap.hx +++ b/Sources/armory/system/InputMap.hx @@ -504,8 +504,8 @@ class MouseAxisComponent extends InputAxisComponent { case "movement x": if (movX < -parent.deadzone) return movX + parent.deadzone; case "moved y": if (movY < -parent.deadzone) scale--; case "movement y": if (movY < -parent.deadzone) return movY + parent.deadzone; - case "wheel moved": if (wheelMov > parent.deadzone) scale --; - case "wheel movement": if (wheelMov > parent.deadzone) return wheelMov + parent.deadzone; + case "wheel moved": if (wheelMov < -parent.deadzone) scale --; + case "wheel movement": if (wheelMov < -parent.deadzone) return wheelMov + parent.deadzone; default: if (mouse.down(negativeKey)) scale--; } return scale; From e742ca76c0fbdf49566005976ad8e5652879e558 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Fri, 8 Jan 2021 22:40:14 +0100 Subject: [PATCH 07/74] Fix editing bundled traits if the project was never compiled --- blender/arm/props_traits.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/blender/arm/props_traits.py b/blender/arm/props_traits.py index 377ed796..a11beade 100755 --- a/blender/arm/props_traits.py +++ b/blender/arm/props_traits.py @@ -308,20 +308,24 @@ class ArmEditBundledScriptButton(bpy.types.Operator): obj = bpy.context.scene sdk_path = arm.utils.get_sdk_path() project_path = arm.utils.get_fp() - pkg = arm.utils.safestr(bpy.data.worlds['Arm'].arm_project_package) item = obj.arm_traitlist[obj.arm_traitlist_index] - source_hx_path = os.path.join(sdk_path , 'armory', 'Sources', 'armory', 'trait', item.class_name_prop + '.hx') - target_hx_path = os.path.join(project_path, 'Sources', pkg, item.class_name_prop + '.hx') + + pkg = arm.utils.safestr(bpy.data.worlds['Arm'].arm_project_package) + source_hx_path = os.path.join(sdk_path, 'armory', 'Sources', 'armory', 'trait', item.class_name_prop + '.hx') + target_dir = os.path.join(project_path, 'Sources', pkg) + target_hx_path = os.path.join(target_dir, item.class_name_prop + '.hx') if not os.path.isfile(target_hx_path): + if not os.path.exists(target_dir): + os.makedirs(target_dir) + # Rewrite package and copy - sf = open(source_hx_path, encoding="utf-8") - sf.readline() - tf = open(target_hx_path, 'w', encoding="utf-8") - tf.write('package ' + pkg + ';\n') - shutil.copyfileobj(sf, tf) - sf.close() - tf.close() + with open(source_hx_path, encoding="utf-8") as sf: + sf.readline() + with open(target_hx_path, 'w', encoding="utf-8") as tf: + tf.write('package ' + pkg + ';\n') + shutil.copyfileobj(sf, tf) + arm.utils.fetch_script_names() # From bundled to script From 081fcb2870975ae19320d6fc2c17e830cc463700 Mon Sep 17 00:00:00 2001 From: knowledgenude Date: Mon, 11 Jan 2021 18:14:21 -0300 Subject: [PATCH 08/74] update-inputmap --- Sources/armory/system/InputMap.hx | 501 +++++++++++++++--------------- 1 file changed, 255 insertions(+), 246 deletions(-) diff --git a/Sources/armory/system/InputMap.hx b/Sources/armory/system/InputMap.hx index 1e432c39..5f07ac63 100644 --- a/Sources/armory/system/InputMap.hx +++ b/Sources/armory/system/InputMap.hx @@ -7,34 +7,31 @@ import iron.system.Input; class InputMap { static var axes = new Map(); static var actions = new Map(); + var config = ""; - public var index: Int; - public var currentTag: String; - - public function new(index: Int): Void { - this.index = index; - } + public function new() {} /** - * Set the tag that this input map will look for - * @param tag The tag name + * Set the config that the input map will look for commands + * @param config The config name * @return Void **/ - public function setCurrentTag(tag: String): Void { - currentTag = tag; + public function setConfig(config: String) { + this.config = config; } /** - * Create input axis in the input map + * Add input axis in the input map * @param name The name of the input axis * @param pressure The pressure required to activate pressure sensitivity buttons - * @param deadzone The displacement required to activate gamepad sticks and catch mouse movement - * @return Void + * @param deadzone The displacement required to activate gamepad sticks or catch mouse movement + * @param index The index used to indentify gamepads + * @return InputAxis **/ - public function createAxis(name: String, ?pressure: FastFloat, ?deadzone: FastFloat): Void { - var p = pressure == null ? 0.0 : pressure; - var d = deadzone == null ? 0.0 : deadzone; - axes[name] = new InputAxis(this, index, p, d); + public function addAxis(name: String, ?pressure = 0.0, ?deadzone = 0.0, ?index = 0) { + var axis = new InputAxis(pressure, deadzone, index); + axes[name] = axis; + return axis; } /** @@ -42,12 +39,13 @@ class InputMap { * @param name The name of the input action * @param pressure The pressure required to activate pressure sensitivity buttons * @param deadzone The displacement required to activate gamepad sticks and catch mouse movement - * @return Void + * @param index The index used to indentify gamepads + * @return InputAction **/ - public function createAction(name: String, ?pressure: FastFloat, ?deadzone: FastFloat): Void { - var p = pressure == null ? 0.0 : pressure; - var d = deadzone == null ? 0.0 : deadzone; - actions[name] = new InputAction(this, index, p, d); + public function addAction(name: String, ?pressure = 0.0, ?index = 0) { + var action = new InputAction(pressure, index); + actions[name] = action; + return action; } /** @@ -55,7 +53,7 @@ class InputMap { * @param name The name of the input axis * @return InputAxis **/ - public inline function getAxis(name: String): InputAxis { + public inline function getAxis(name: String) { return axes[name]; } @@ -64,7 +62,7 @@ class InputMap { * @param name The name of the input action * @return InputAction **/ - public inline function getAction(name: String): InputAction { + public inline function getAction(name: String) { return actions[name]; } @@ -73,8 +71,8 @@ class InputMap { * @param name The name of the input axis * @return Vec4 **/ - public inline function getVec(name: String): Vec4 { - return axes[name].get(); + public inline function getVec(name: String) { + return axes[name].getVec(config); } /** @@ -82,8 +80,8 @@ class InputMap { * @param name The name of the input action * @return Bool **/ - public inline function isActionPressed(name: String): Bool { - return actions[name].pressed(); + public inline function started(name: String) { + return actions[name].started(config); } /** @@ -91,132 +89,139 @@ class InputMap { * @param name The name of the target input action * @return Bool **/ - public inline function isActionReleased(name: String): Bool { - return actions[name].released(); + public inline function released(name: String) { + return actions[name].released(config); } } class InputAction { - static var components = new Map(); - - final parent: InputMap; - public final index: Int; - public final deadzone: FastFloat; public final pressure: FastFloat; + public final index: Int; - public function new(parent: InputMap, index: Int, pressure: FastFloat, deadzone: FastFloat) { - this.parent = parent; + static var commands = new Map>(); + var display = new Map(); + + public function new(pressure: FastFloat, index: Int) { this.index = index; this.pressure = pressure; - this.deadzone = deadzone; } /** - * Add a keyboard input component - * @param tag The input component tag + * Get the display form of all commands that activates this action according to the active config + * @param config + */ + public function getDisplay(config: String) { + var s = ""; + for (c in commands[config]) { + s += display[c] + " OR "; + } + return s; + } + + /** + * Add a keyboard input command * @param key The key that should be started or released * @param modifiers The keys that should be down before activate the main key - * @return Void + * @param config The input command config + * @param display The form this command will be displayed + * @return InputActionCommand **/ - public function addKeyboardComponent(tag: String, key: String, ?modifiers): Void { + public function addKeyboard(key: String, ?modifiers: Array, ?config = "", ?display = "") { var mod = modifiers == null ? new Array() : modifiers; - addCustomComponent(tag, new KeyboardActionComponent(this, key, mod)); + var command = new KeyboardActionCommand(this, key, mod); + addCommand(command, config, display); + return command; } /** - * Add a mouse input component - * @param tag The input component tag + * Add a mouse input command * @param button The button that should be started or released * @param modifiers The buttons that should be down before activate the main key - * @return Void + * @param config The input command config + * @param display The form this command will be displayed + * @return InputActionCommand **/ - public function addMouseComponent(tag: String, button: String, ?modifiers): Void { + public function addMouse(button: String, ?modifiers: Array, ?config = "", ?display = "") { var mod = modifiers == null ? new Array() : modifiers; - addCustomComponent(tag, new MouseActionComponent(this, button, mod)); + var command = new MouseActionCommand(this, button, mod); + addCommand(command, config, display); + return command; } /** - * Add a gamepad input component - * @param tag The input component tag + * Add a gamepad input command * @param button The button that should be started or released * @param modifiers The buttons that should be down before activate the main key - * @return Void + * @param config The input command config + * @param display The form this command will be displayed + * @return InputActionCommand **/ - public function addGamepadComponent(tag: String, button: String, ?modifiers): Void { + public function addGamepad(button: String, ?modifiers: Array, ?config = "", ?display = "") { var mod = modifiers == null ? new Array() : modifiers; - addCustomComponent(tag, new GamepadActionComponent(this, button, mod)); + var command = new GamepadActionCommand(this, button, mod); + addCommand(command, config, display); + return command; } /** - * Add a custom input component - * @param tag The input component tag - * @param component The constructed input component - * @return Void + * Add a custom input command + * @param command The constructed input command + * @param config The input command config + * @return InputActionCommand **/ - public function addCustomComponent(tag: String, component: InputActionComponent): Void { - components[component] = tag; + public function addCommand(command: InputActionCommand, config: String, display: String) { + if (commands[config] == null) commands[config] = new Array(); + commands[config].push(command); + this.display[command] = display; + return command; } - /** - * Remove an input component - * @param component The component to be removed - * @return Void - **/ - public function removeComponent(component: InputActionComponent): Void { - components.remove(component); - } - - public function pressed(): Bool { - for (component => tag in components) { - if (tag == parent.currentTag) { - if (component.started()) return true; - } + public function started(config: String) { + for (c in commands[config]) { + if (c.started()) return true; } return false; } - public function released(): Bool { - for (component => tag in components) { - if (tag == parent.currentTag) { - if (component.released()) return true; - } + public function released(config: String) { + for (c in commands[config]) { + if (c.released()) return true; } return false; } } -class InputActionComponent { - var parent: InputAction; - var key: String; - var modifiers: Array; +class InputActionCommand { + final key: String; + final modifiers: Array; + final pressure: FastFloat; + final index: Int; - public function new(parent: InputAction, key: String, modifiers: Array): Void { + public function new(parent: InputAction, key: String, modifiers: Array) { this.key = key; this.modifiers = modifiers; - this.parent = parent; + + pressure = parent.pressure; + index = parent.index; } - public function started(): Bool { + public function started() { return false; } - public function released(): Bool { + public function released() { return false; } } -class KeyboardActionComponent extends InputActionComponent { - +class KeyboardActionCommand extends InputActionCommand { final keyboard = Input.getKeyboard(); - public function new(parent: InputAction, key: String, modifiers: Array): Void { + public function new(parent: InputAction, key: String, modifiers: Array) { super(parent, key, modifiers); - this.parent = parent; - this.key = key; - this.modifiers = modifiers; } - public inline override function started(): Bool { + public inline override function started() { if (keyboard.started(key)) { for (m in modifiers) { if (!keyboard.down(m)) return false; @@ -226,7 +231,7 @@ class KeyboardActionComponent extends InputActionComponent { return false; } - public inline override function released(): Bool { + public inline override function released() { if (keyboard.released(key)) { for (m in modifiers) { if (!keyboard.down(m)) return false; @@ -237,18 +242,14 @@ class KeyboardActionComponent extends InputActionComponent { } } -class MouseActionComponent extends InputActionComponent { - +class MouseActionCommand extends InputActionCommand { final mouse = Input.getMouse(); - public function new(parent: InputAction, key: String, modifiers: Array): Void { + public function new(parent: InputAction, key: String, modifiers: Array) { super(parent, key, modifiers); - this.parent = parent; - this.key = key; - this.modifiers = modifiers; } - public override function started(): Bool { + public override function started() { if (mouse.started(key)) { for (m in modifiers) { if (!mouse.down(m)) return false; @@ -258,7 +259,7 @@ class MouseActionComponent extends InputActionComponent { return false; } - public override function released(): Bool { + public override function released() { if (mouse.released(key)) { for (m in modifiers) { if (!mouse.down(m)) return false; @@ -269,32 +270,28 @@ class MouseActionComponent extends InputActionComponent { } } -class GamepadActionComponent extends InputActionComponent { - +class GamepadActionCommand extends InputActionCommand { final gamepad: Gamepad; public function new(parent: InputAction, key: String, modifiers: Array) { super(parent, key, modifiers); - this.parent = parent; - this.key = key; - this.modifiers = modifiers; - gamepad = Input.getGamepad(parent.index); + gamepad = Input.getGamepad(index); } - public inline override function started(): Bool { + public inline override function started() { if (gamepad.started(key)) { for (m in modifiers) { - if (gamepad.down(m) <= parent.pressure) return false; + if (gamepad.down(m) <= pressure) return false; } return true; } return false; } - public inline override function released(): Bool { + public inline override function released() { if (gamepad.released(key)) { for (m in modifiers) { - if (gamepad.down(m) <= parent.pressure) return false; + if (gamepad.down(m) <= pressure) return false; } return true; } @@ -303,98 +300,114 @@ class GamepadActionComponent extends InputActionComponent { } class InputAxis { - static var componentsX = new Map(); + public final pressure: FastFloat; + public final deadzone: FastFloat; + public final index: Int; + static var commandsX = new Map>(); static var scaleX = 1.0; - - static var componentsY = new Map(); + static var commandsY = new Map>(); static var scaleY = 1.0; - static var normalize = false; static var vec = new Vec4(); + var display = new Map(); - final parent: InputMap; - public final index: Int; - public final deadzone: FastFloat; - public final pressure: FastFloat; - - public function new(parent: InputMap, index: Int, pressure: FastFloat, deadzone: FastFloat) { - this.parent = parent; + public function new(pressure: FastFloat, deadzone: FastFloat, index: Int) { this.index = index; this.pressure = pressure; this.deadzone = deadzone; } /** - * Add a keyboard input component - * @param position The position that the added input component will be in the returned vector ("x" or "y") - * @param tag The input component tag + * Get the display form of all commands that activates this axis according to the active config + * @param config + */ + public function getDisplay(config: String) { + var s = ""; + for (c in commandsX[config]) { + s += display[c] + " OR "; + } + + for (c in commandsY[config]) { + s += display[c] + " OR "; + } + + return s; + } + + /** + * Add a keyboard input command + * @param position The position that the added input command will be in the returned vector ("x" or "y") * @param positiveKey The key that when pressed will sum +1 * @param negativeKey The key that when pressed will sum -1 - * @return Void + * @param config The input command config + * @param display The form this command will be displayed + * @return InputAxisCommand **/ - public function addKeyboardComponent(position: String, tag: String, positiveKey: String, ?negativeKey: String): Void { - var n = negativeKey == null ? "" : negativeKey; - addCustomComponent(position, tag, new KeyboardAxisComponent(this, positiveKey, negativeKey)); + public function addKeyboard(position: String, positiveKey: String, ?negativeKey = "", ?config = "", ?display = "") { + var command = new KeyboardAxisCommand(this, positiveKey, negativeKey); + addCommand(position, command, config, display); + return command; } /** - * Add a mouse input component - * @param position The position that the added input component will be in the returned vector ("x" or "y") - * @param tag The input component tag + * Add a mouse input command + * @param position The position that the added input command will be in the returned vector ("x" or "y") * @param positiveButton The key that when pressed will sum +1 * @param negativeButton The key that when pressed will sum -1 - * @return Void + * @param config The input command config + * @param display The form this command will be displayed + * @return InputAxisCommand **/ - public function addMouseComponent(position: String, tag: String, positiveButton: String, ?negativeButton: String): Void { - var n = negativeButton == null ? "" : negativeButton; - addCustomComponent(position, tag, new MouseAxisComponent(this, positiveButton, negativeButton)); + public function addMouse(position: String, positiveButton: String, ?negativeButton = "", ?config = "", ?display = "") { + var command = new MouseAxisCommand(this, positiveButton, negativeButton); + addCommand(position, command, config, display); + return command; } /** - * Add a gamepad input component - * @param position The position that the added input component will be in the returned vector ("x" or "y") - * @param tag The input component tag + * Add a gamepad input command + * @param position The position that the added input command will be in the returned vector ("x" or "y") * @param positiveButton The key that when pressed will sum +1 * @param negativeButton The key that when pressed will sum -1 - * @return Void + * @param config The input command config + * @param display The form this command will be displayed + * @return InputAxisCommand **/ - public function addGamepadComponent(position: String, tag: String, positiveButton: String, ?negativeButton: String): Void { - var n = negativeButton == null ? "" : negativeButton; - addCustomComponent(position, tag, new GamepadAxisComponent(this, positiveButton, negativeButton)); + public function addGamepad(position: String, positiveButton: String, ?negativeButton = "", ?config = "", ?display = "") { + var command = new GamepadAxisCommand(this, positiveButton, negativeButton); + addCommand(position, command, config, display); + return command; } /** - * Add a custom input component - * @param tag The input component tag - * @param component The constructed input component - * @return Void + * Add a custom input command + * @param command The constructed input command + * @param config The input command config + * @param display The form this command will be displayed + * @return InputAxisCommand **/ - public function addCustomComponent(position: String, tag: String, component: InputAxisComponent): Void { + public function addCommand(position: String, command: InputAxisCommand, config: String, display: String) { switch (position) { - case "x": componentsX[component] = tag; - case "y": componentsY[component] = tag; - } - } - - /** - * Remove an input component - * @param component The component to be removed - * @return Void - **/ - public function removeComponent(position: String, component: InputAxisComponent): Void { - switch (position) { - case "x": componentsX.remove(component); - case "y": componentsY.remove(component); + case "x": { + if (commandsX[config] == null) commandsX[config] = new Array(); + commandsX[config].push(command); + } + case "y": { + if (commandsY[config] == null) commandsY[config] = new Array(); + commandsY[config].push(command); + } } + this.display[command] = display; + return command; } /** * Set the scale of the returned vector - * @param x The scale of the components in the position x - * @param y The scale of the components in the positon y + * @param x The scale of the commands in the position x + * @param y The scale of the commands in the positon y * @return Void **/ - public function setScale(x: FastFloat, y: FastFloat): Void { + public function setScale(x: FastFloat, y: FastFloat) { scaleX = x; scaleY = y; } @@ -403,7 +416,7 @@ class InputAxis { * Enable the returned vector normalization * @return Void **/ - public function enableNormalize(): Void { + public function enableNormalize() { normalize = true; } @@ -411,7 +424,7 @@ class InputAxis { * Disable the returned vector normalization * @return Void **/ - public function disableNormalize(): Void { + public function disableNormalize() { normalize = false; } @@ -419,15 +432,15 @@ class InputAxis { * Get the input axis vector * @return Void **/ - public inline function get(): Vec4 { + public inline function getVec(config: String) { vec.set(0, 0, 0); - for (component => tag in componentsX) { - if (tag == parent.currentTag) if (component.get() != 0.0) vec.x += component.get(); + for (c in commandsX[config]) { + if (c.getScale() != 0.0) vec.x += c.getScale(); } - for (component => tag in componentsY) { - if (tag == parent.currentTag) if (component.get() != 0.0) vec.y += component.get(); + for (c in commandsY[config]) { + if (c.getScale() != 0.0) vec.y += c.getScale(); } if (normalize) vec.normalize(); @@ -439,129 +452,125 @@ class InputAxis { } } -class InputAxisComponent { - var parent: InputAxis; - var positiveKey: String; - var negativeKey: String; +class InputAxisCommand { + final positiveKey: String; + final negativeKey: String; + final pressure: FastFloat; + final minusPressure: FastFloat; + final deadzone: FastFloat; + final minusDeadzone: FastFloat; var scale: FastFloat; - public function new(parent: InputAxis, positiveKey: String, negativeKey: String): Void { - this.parent = parent; + public function new(parent: InputAxis, positiveKey: String, negativeKey: String) { this.positiveKey = positiveKey; this.negativeKey = negativeKey; + + pressure = parent.pressure; + minusPressure = -pressure; + deadzone = parent.deadzone; + minusDeadzone = -deadzone; } - public function get(): FastFloat { - return 0.0; + public function getScale() { + return scale; } } -class KeyboardAxisComponent extends InputAxisComponent { +class KeyboardAxisCommand extends InputAxisCommand { final keyboard = Input.getKeyboard(); - public function new(parent: InputAxis, positiveKey: String, negativeKey: String): Void { + public function new(parent: InputAxis, positiveKey: String, negativeKey: String) { super(parent, positiveKey, negativeKey); - this.parent = parent; - this.positiveKey = positiveKey; - this.negativeKey = negativeKey; } - public inline override function get(): FastFloat { - scale = 0.0; + public inline override function getScale() { + var scale = 0.0; if (keyboard.down(positiveKey)) scale++; if (keyboard.down(negativeKey)) scale--; return scale; } } -class MouseAxisComponent extends InputAxisComponent { +class MouseAxisCommand extends InputAxisCommand { final mouse = Input.getMouse(); - public function new(parent: InputAxis, positiveKey: String, negativeKey: String): Void { + public function new(parent: InputAxis, positiveKey: String, negativeKey: String) { super(parent, positiveKey, negativeKey); - this.parent = parent; - this.positiveKey = positiveKey; - this.negativeKey = negativeKey; } - public inline override function get(): FastFloat { + public inline override function getScale() { scale = 0.0; - var movX = mouse.movementX; - var movY = mouse.movementY; - var wheelMov = mouse.wheelDelta; + var movementX = mouse.movementX; + var movementY = mouse.movementY; + var wheelDelta = mouse.wheelDelta; switch (positiveKey) { - case "moved x": if (movX > parent.deadzone) scale++; - case "movement x": if (movX > parent.deadzone) return movX - parent.deadzone; - case "moved y": if (movY > parent.deadzone) scale++; - case "movement y": if (movY > parent.deadzone) return movY - parent.deadzone; - case "wheel moved": if (wheelMov > parent.deadzone) scale ++; - case "wheel movement": if (wheelMov > parent.deadzone) return wheelMov - parent.deadzone; + case "moved x": if (movementX > deadzone) scale++; + case "movement x": if (movementX > deadzone) return movementX - deadzone; + case "moved y": if (movementY > deadzone) scale++; + case "movement y": if (movementY > deadzone) return movementY - deadzone; + case "wheel moved": if (wheelDelta > deadzone) scale++; + case "wheel movement": if (wheelDelta > deadzone) return wheelDelta - deadzone; default: if (mouse.down(positiveKey)) scale++; } switch (negativeKey) { - case "moved x": if (movX < -parent.deadzone) scale--; - case "movement x": if (movX < -parent.deadzone) return movX + parent.deadzone; - case "moved y": if (movY < -parent.deadzone) scale--; - case "movement y": if (movY < -parent.deadzone) return movY + parent.deadzone; - case "wheel moved": if (wheelMov < -parent.deadzone) scale --; - case "wheel movement": if (wheelMov < -parent.deadzone) return wheelMov + parent.deadzone; + case "moved x": if (movementX < minusDeadzone) scale--; + case "movement x": if (movementX < minusDeadzone) return movementX + deadzone; + case "moved y": if (movementY < minusDeadzone) scale--; + case "movement y": if (movementY < minusDeadzone) return movementY + deadzone; + case "wheel moved": if (wheelDelta < minusDeadzone) scale--; + case "wheel movement": if (wheelDelta < minusDeadzone) return wheelDelta + deadzone; default: if (mouse.down(negativeKey)) scale--; } return scale; } } -class GamepadAxisComponent extends InputAxisComponent { +class GamepadAxisCommand extends InputAxisCommand { final gamepad: Gamepad; - public function new(parent: InputAxis, positiveKey: String, negativeKey: String): Void { + public function new(parent: InputAxis, positiveKey: String, negativeKey: String) { super(parent, positiveKey, negativeKey); - this.parent = parent; - this.positiveKey = positiveKey; - this.negativeKey = negativeKey; gamepad = Input.getGamepad(parent.index); } - public inline override function get(): FastFloat { + public inline override function getScale() { scale = 0.0; - var rightMovX = gamepad.rightStick.movementX; - var rightMovY = gamepad.rightStick.movementY; - var leftMovX = gamepad.leftStick.movementX; - var leftMovY = gamepad.leftStick.movementY; - - // Avoid division by zero - var rightTrigger = gamepad.down("r2") > 0.0 ? (gamepad.down("r2") - parent.pressure) / (1 - parent.pressure) : 0.0; - var leftTrigger = gamepad.down("l2") > 0.0 ? (gamepad.down("r2") - parent.pressure) / (1 - parent.pressure) : 0.0; + var rx = gamepad.rightStick.movementX; + var ry = gamepad.rightStick.movementY; + var lx = gamepad.leftStick.movementX; + var ly = gamepad.leftStick.movementY; + var rt = gamepad.down("r2") > 0.0 ? (gamepad.down("r2") - pressure) / (1 - pressure) : 0.0; + var lt = gamepad.down("l2") > 0.0 ? (gamepad.down("r2") - pressure) / (1 - pressure) : 0.0; switch (positiveKey) { - case "right stick moved x": if (rightMovX > parent.deadzone) scale++; - case "right stick movement x": if (rightMovX > parent.deadzone) return rightMovX - parent.deadzone; - case "right stick moved y": if (rightMovY > parent.deadzone) scale++; - case "right stick movement y": if (rightMovY > parent.deadzone) return rightMovY - parent.deadzone; - case "left stick moved x": if (leftMovX > parent.deadzone) scale++; - case "left stick movement x": if (leftMovX > parent.deadzone) return leftMovX - parent.deadzone; - case "left stick moved y": if (leftMovY > parent.deadzone) scale++; - case "left stick movement y": if (leftMovY > parent.deadzone) return leftMovY - parent.deadzone; - case "right trigger": scale += rightTrigger; - case "left trigger": scale += leftTrigger; - default: if (gamepad.down(positiveKey) > parent.pressure) scale++; + case "right stick moved x": if (rx > deadzone) scale++; + case "right stick movement x": if (rx > deadzone) return rx - deadzone; + case "right stick moved y": if (ry > deadzone) scale++; + case "right stick movement y": if (ry > deadzone) return ry - deadzone; + case "left stick moved x": if (lx > deadzone) scale++; + case "left stick movement x": if (lx > deadzone) return lx - deadzone; + case "left stick moved y": if (ly > deadzone) scale++; + case "left stick movement y": if (ly > deadzone) return ly - deadzone; + case "right trigger": scale += rt; + case "left trigger": scale += lt; + default: if (gamepad.down(positiveKey) > pressure) scale++; } switch (negativeKey) { - case "right stick moved x": if (rightMovX < -parent.deadzone) scale--; - case "right stick movement x": if (rightMovX < -parent.deadzone) return rightMovX + parent.deadzone; - case "right stick moved y": if (rightMovY < -parent.deadzone) scale--; - case "right stick movement y": if (rightMovY < -parent.deadzone) return rightMovY + parent.deadzone; - case "left stick moved x": if (leftMovX < -parent.deadzone) scale--; - case "left stick movement x": if (leftMovX < -parent.deadzone) return leftMovX + parent.deadzone; - case "left stick moved y": if (leftMovY < -parent.deadzone) scale--; - case "left stick movement y": if (leftMovY < -parent.deadzone) return leftMovY + parent.deadzone; - case "right trigger": scale -= rightTrigger; - case "left trigger": scale -= leftTrigger; - default: if (gamepad.down(negativeKey) < -parent.pressure) scale--; + case "right stick moved x": if (rx < minusDeadzone) scale--; + case "right stick movement x": if (rx < minusDeadzone) return rx + deadzone; + case "right stick moved y": if (ry < minusDeadzone) scale--; + case "right stick movement y": if (ry < minusDeadzone) return ry + deadzone; + case "left stick moved x": if (lx < minusDeadzone) scale--; + case "left stick movement x": if (lx < minusDeadzone) return lx + deadzone; + case "left stick moved y": if (ly < minusDeadzone) scale--; + case "left stick movement y": if (ly < minusDeadzone) return ly + deadzone; + case "right trigger": scale -= rt; + case "left trigger": scale -= lt; + default: if (gamepad.down(negativeKey) < minusPressure) scale--; } return scale; } -} +} \ No newline at end of file From fb8a05639448fac791e741dc25703660d926b132 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sun, 17 Jan 2021 00:23:02 +0100 Subject: [PATCH 09/74] Fix reroute socket indices --- blender/arm/make_logic.py | 41 +++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/blender/arm/make_logic.py b/blender/arm/make_logic.py index 0ebab8e2..bc0c95a5 100755 --- a/blender/arm/make_logic.py +++ b/blender/arm/make_logic.py @@ -1,4 +1,6 @@ import os +from typing import Optional, TextIO + import bpy import arm.utils import arm.log @@ -167,26 +169,45 @@ def build_node(node: bpy.types.Node, f): # Create inputs for inp in node.inputs: - # Is linked - find node + # True if the input is connected to a unlinked reroute + # somewhere down the reroute line + unconnected = False + + # Is linked -> find the connected node if inp.is_linked: n = inp.links[0].from_node socket = inp.links[0].from_socket - if (inp.bl_idname == 'ArmNodeSocketAction' and socket.bl_idname != 'ArmNodeSocketAction') or \ - (socket.bl_idname == 'ArmNodeSocketAction' and inp.bl_idname != 'ArmNodeSocketAction'): - print('Armory Error: Sockets do not match in logic node tree "{0}" - node "{1}" - socket "{2}"'.format(group_name, node.name, inp.name)) - inp_name = build_node(n, f) - for i in range(0, len(n.outputs)): - if n.outputs[i] == socket: - inp_from = i + + # Follow reroutes first + while n.type == "REROUTE": + if len(n.inputs) == 0 or not n.inputs[0].is_linked: + unconnected = True break - # Not linked - create node with default values + + socket = n.inputs[0].links[0].from_socket + n = n.inputs[0].links[0].from_node + + if not unconnected: + if (inp.bl_idname == 'ArmNodeSocketAction' and socket.bl_idname != 'ArmNodeSocketAction') or \ + (socket.bl_idname == 'ArmNodeSocketAction' and inp.bl_idname != 'ArmNodeSocketAction'): + arm.log.warn(f'Sockets do not match in logic node tree "{group_name}": node "{node.name}", socket "{inp.name}"') + + inp_name = build_node(n, f) + for i in range(0, len(n.outputs)): + if n.outputs[i] == socket: + inp_from = i + break + + # Not linked -> create node with default values else: inp_name = build_default_node(inp) inp_from = 0 + # The input is linked to a reroute, but the reroute is unlinked - if inp_name == None: + if unconnected: inp_name = build_default_node(inp) inp_from = 0 + # Add input f.write('\t\t' + name + '.addInput(' + inp_name + ', ' + str(inp_from) + ');\n') From 41657adb64c4ced070903a370d7a22569f254eb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sun, 17 Jan 2021 00:23:19 +0100 Subject: [PATCH 10/74] Cleanup --- blender/arm/make_logic.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/blender/arm/make_logic.py b/blender/arm/make_logic.py index bc0c95a5..6ee7aac8 100755 --- a/blender/arm/make_logic.py +++ b/blender/arm/make_logic.py @@ -2,9 +2,10 @@ import os from typing import Optional, TextIO import bpy -import arm.utils -import arm.log + from arm.exporter import ArmoryExporter +import arm.log +import arm.utils parsed_nodes = [] parsed_ids = dict() # Sharing node data @@ -102,7 +103,9 @@ def build_node_tree(node_group): f.write('}') node_group.arm_cached = True -def build_node(node: bpy.types.Node, f): + +def build_node(node: bpy.types.Node, f: TextIO) -> Optional[str]: + """Builds the given node and returns its name. f is an opened file object.""" global parsed_nodes global parsed_ids @@ -128,7 +131,7 @@ def build_node(node: bpy.types.Node, f): parsed_nodes.append(name) # Create node - node_type = node.bl_idname[2:] # Discard 'LN'TimeNode prefix + node_type = node.bl_idname[2:] # Discard 'LN' prefix f.write('\t\tvar ' + name + ' = new armory.logicnode.' + node_type + '(this);\n') # Handle Function Nodes From 8619006757518627376c178718c249220ec29880 Mon Sep 17 00:00:00 2001 From: tong Date: Sun, 24 Jan 2021 18:36:58 +0100 Subject: [PATCH 11/74] Print rounded build time to console --- blender/arm/make.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/blender/arm/make.py b/blender/arm/make.py index 0700afed..73a17cc3 100755 --- a/blender/arm/make.py +++ b/blender/arm/make.py @@ -9,6 +9,7 @@ import threading import webbrowser import shlex import errno +import math import bpy @@ -431,7 +432,7 @@ def compilation_server_done(): log.error('Build failed, check console') def build_done(): - print('Finished in ' + str(time.time() - profile_time)) + print('Finished in ' + str( math.ceil((time.time() - profile_time) * 100) / 100) + 's') if log.num_warnings > 0: log.print_warn(f'{log.num_warnings} warnings occurred during compilation') if state.proc_build is None: From 6ef11958165413b3b91af8ccca7b5ee3f989c1a1 Mon Sep 17 00:00:00 2001 From: knowledgenude Date: Mon, 25 Jan 2021 20:19:24 -0300 Subject: [PATCH 12/74] Update inputmap and add FSM --- Sources/armory/system/FSM.hx | 95 ++++ Sources/armory/system/InputMap.hx | 770 +++++++++++------------------- 2 files changed, 367 insertions(+), 498 deletions(-) create mode 100644 Sources/armory/system/FSM.hx diff --git a/Sources/armory/system/FSM.hx b/Sources/armory/system/FSM.hx new file mode 100644 index 00000000..53a66007 --- /dev/null +++ b/Sources/armory/system/FSM.hx @@ -0,0 +1,95 @@ +package armory.system; + +class FSM { + var state: Null; + var nextState: Null; + var transitions = new Array(); + var tempTransitions = new Array(); + var entered = false; + + public function new() {} + + /** + * Set the initial / current state of the FSM and return it + * @param state The state to be set + * @return State + **/ + public function setState(state: State) { + this.state = state; + syncTransitions(); + return state; + } + + /** + * Bind multiple transitions to the specified state + * @param canEnter The function that returns true or false to enter the transition + * @param toState The next state the transiiton will return + * @param fromStates The states that are allowed to be changed by the next state + * @return Void + **/ + public function bindTransitions(canEnter: Void -> Bool, toState: State, fromStates: Array) { + for (s in fromStates) { + transitions.push(new Transition(canEnter, s, toState)); + } + + syncTransitions(); + } + + function syncTransitions() { + tempTransitions = []; + + for (t in transitions) { + if (t.isConnected(state)) tempTransitions.push(t); + } + } + + public function run() { + if (!entered) { + state.onEnter(); + entered = true; + } + + state.onUpdate(); + + for (t in tempTransitions) { + if (t.canEnter()) { + nextState = t.getNextState(); + state.onExit(); + state = nextState; + entered = false; + syncTransitions(); + break; + } + } + } +} + +class Transition { + final func: Void -> Bool; + final from: State; + final to: State; + + public function new(func: Void -> Bool, from: State, to: State) { + this.func = func; + this.from = from; + this.to = to; + } + + public function canEnter() { + return func(); + } + + public function isConnected(state: State) { + return from == state; + } + + public function getNextState() { + return to; + } +} + +interface State { + function onEnter(): Void; + function onUpdate(): Void; + function onExit(): Void; +} \ No newline at end of file diff --git a/Sources/armory/system/InputMap.hx b/Sources/armory/system/InputMap.hx index 5f07ac63..79b998e1 100644 --- a/Sources/armory/system/InputMap.hx +++ b/Sources/armory/system/InputMap.hx @@ -1,208 +1,118 @@ package armory.system; import kha.FastFloat; -import iron.math.Vec4; import iron.system.Input; class InputMap { - static var axes = new Map(); - static var actions = new Map(); - var config = ""; + var commands = new Map>>(); public function new() {} - /** - * Set the config that the input map will look for commands - * @param config The config name - * @return Void - **/ - public function setConfig(config: String) { - this.config = config; + public function addKeyboard(config: String) { + var command = new KeyboardCommand(); + return addCustomCommand(command, config); } - /** - * Add input axis in the input map - * @param name The name of the input axis - * @param pressure The pressure required to activate pressure sensitivity buttons - * @param deadzone The displacement required to activate gamepad sticks or catch mouse movement - * @param index The index used to indentify gamepads - * @return InputAxis - **/ - public function addAxis(name: String, ?pressure = 0.0, ?deadzone = 0.0, ?index = 0) { - var axis = new InputAxis(pressure, deadzone, index); - axes[name] = axis; + public function addGamepad(config: String) { + var command = new GamepadCommand(); + return addCustomCommand(command, config); + } + + public function addCustomCommand(command: InputCommand, config: String) { + if (commands[config] == null) commands[config] = new Array(); + commands[config].push(command); + return command; + } +} + +class ActionMap extends InputMap { + + public inline function started(config: String) { + var started = false; + + for (c in commands[config]) { + if (c.started()) { + started = true; + break; + } + } + + return started; + } + + public inline function released(config: String) { + var released = false; + + for (c in commands[config]) { + if (c.released()) { + released = true; + break; + } + } + + return released; + } +} + +class AxisMap extends InputMap { + var scale: FastFloat = 1.0; + + public inline function getAxis(config: String) { + var axis = 0.0; + + for (c in commands[config]) { + var tempAxis = c.getAxis(); + + if (tempAxis != 0.0 && tempAxis != axis) { + axis += tempAxis; + scale = c.getScale(); + } + } + return axis; } - /** - * Create input action in the input map - * @param name The name of the input action - * @param pressure The pressure required to activate pressure sensitivity buttons - * @param deadzone The displacement required to activate gamepad sticks and catch mouse movement - * @param index The index used to indentify gamepads - * @return InputAction - **/ - public function addAction(name: String, ?pressure = 0.0, ?index = 0) { - var action = new InputAction(pressure, index); - actions[name] = action; - return action; - } - - /** - * Get the input axis present in the input map by its name - * @param name The name of the input axis - * @return InputAxis - **/ - public inline function getAxis(name: String) { - return axes[name]; - } - - /** - * Get the input action present in the input map by its name - * @param name The name of the input action - * @return InputAction - **/ - public inline function getAction(name: String) { - return actions[name]; - } - - /** - * Get the vector of the given input axis - * @param name The name of the input axis - * @return Vec4 - **/ - public inline function getVec(name: String) { - return axes[name].getVec(config); - } - - /** - * Check if the given input action is started - * @param name The name of the input action - * @return Bool - **/ - public inline function started(name: String) { - return actions[name].started(config); - } - - /** - * Check if the given input action is released - * @param name The name of the target input action - * @return Bool - **/ - public inline function released(name: String) { - return actions[name].released(config); + public inline function getScale() { + return scale; } } -class InputAction { - public final pressure: FastFloat; - public final index: Int; +class InputCommand { + var keys = new Array(); + var modifiers = new Array(); + var displacementKeys = new Array(); + var displacementModifiers = new Array(); + var deadzone: FastFloat = 0.0; + var scale: FastFloat = 1.0; - static var commands = new Map>(); - var display = new Map(); + public function new() {} - public function new(pressure: FastFloat, index: Int) { - this.index = index; - this.pressure = pressure; + public function setKeys(keys: Array) { + return this.keys = keys; } - /** - * Get the display form of all commands that activates this action according to the active config - * @param config - */ - public function getDisplay(config: String) { - var s = ""; - for (c in commands[config]) { - s += display[c] + " OR "; - } - return s; + public function setMods(modifiers: Array) { + return this.modifiers = modifiers; } - /** - * Add a keyboard input command - * @param key The key that should be started or released - * @param modifiers The keys that should be down before activate the main key - * @param config The input command config - * @param display The form this command will be displayed - * @return InputActionCommand - **/ - public function addKeyboard(key: String, ?modifiers: Array, ?config = "", ?display = "") { - var mod = modifiers == null ? new Array() : modifiers; - var command = new KeyboardActionCommand(this, key, mod); - addCommand(command, config, display); - return command; + public function setDisplacementKeys(keys: Array) { + return displacementKeys = keys; } - /** - * Add a mouse input command - * @param button The button that should be started or released - * @param modifiers The buttons that should be down before activate the main key - * @param config The input command config - * @param display The form this command will be displayed - * @return InputActionCommand - **/ - public function addMouse(button: String, ?modifiers: Array, ?config = "", ?display = "") { - var mod = modifiers == null ? new Array() : modifiers; - var command = new MouseActionCommand(this, button, mod); - addCommand(command, config, display); - return command; + public function setDisplacementMods(modifiers: Array) { + return displacementModifiers = modifiers; } - /** - * Add a gamepad input command - * @param button The button that should be started or released - * @param modifiers The buttons that should be down before activate the main key - * @param config The input command config - * @param display The form this command will be displayed - * @return InputActionCommand - **/ - public function addGamepad(button: String, ?modifiers: Array, ?config = "", ?display = "") { - var mod = modifiers == null ? new Array() : modifiers; - var command = new GamepadActionCommand(this, button, mod); - addCommand(command, config, display); - return command; + public function setDeadzone(deadzone: FastFloat) { + return this.deadzone = deadzone; } - /** - * Add a custom input command - * @param command The constructed input command - * @param config The input command config - * @return InputActionCommand - **/ - public function addCommand(command: InputActionCommand, config: String, display: String) { - if (commands[config] == null) commands[config] = new Array(); - commands[config].push(command); - this.display[command] = display; - return command; + public function setScale(scale: FastFloat) { + return this.scale = scale; } - public function started(config: String) { - for (c in commands[config]) { - if (c.started()) return true; - } - return false; - } - - public function released(config: String) { - for (c in commands[config]) { - if (c.released()) return true; - } - return false; - } -} - -class InputActionCommand { - final key: String; - final modifiers: Array; - final pressure: FastFloat; - final index: Int; - - public function new(parent: InputAction, key: String, modifiers: Array) { - this.key = key; - this.modifiers = modifiers; - - pressure = parent.pressure; - index = parent.index; + public function getScale() { + return scale; } public function started() { @@ -212,365 +122,229 @@ class InputActionCommand { public function released() { return false; } + + public function getAxis(): FastFloat { + return 0.0; + } } -class KeyboardActionCommand extends InputActionCommand { - final keyboard = Input.getKeyboard(); - - public function new(parent: InputAction, key: String, modifiers: Array) { - super(parent, key, modifiers); - } +class KeyboardCommand extends InputCommand { + var keyboard = Input.getKeyboard(); + var mouse = Input.getMouse(); public inline override function started() { - if (keyboard.started(key)) { - for (m in modifiers) { - if (!keyboard.down(m)) return false; + for (k in keys) { + if (keyboard.started(k)) { + for (m in modifiers) { + if (!keyboard.down(m)) return false; + } + + for (m in displacementModifiers) { + if (!mouse.down(m)) return false; + } + + return true; } - return true; } + + for (k in displacementKeys) { + if (mouse.started(k)) { + for (m in modifiers) { + if (!keyboard.down(m)) return false; + } + + for (m in displacementModifiers) { + if (!mouse.down(m)) return false; + } + + return true; + } + } + return false; } public inline override function released() { - if (keyboard.released(key)) { - for (m in modifiers) { - if (!keyboard.down(m)) return false; + for (k in keys) { + if (keyboard.released(k)) { + for (m in modifiers) { + if (!keyboard.down(m)) return false; + } + + for (m in displacementModifiers) { + if (!mouse.down(m)) return false; + } + + return true; } - return true; } - return false; - } -} -class MouseActionCommand extends InputActionCommand { - final mouse = Input.getMouse(); + for (k in displacementKeys) { + if (mouse.released(k)) { + for (m in modifiers) { + if (!keyboard.down(m)) return false; + } + + for (m in displacementModifiers) { + if (!mouse.down(m)) return false; + } - public function new(parent: InputAction, key: String, modifiers: Array) { - super(parent, key, modifiers); - } - - public override function started() { - if (mouse.started(key)) { - for (m in modifiers) { - if (!mouse.down(m)) return false; + return true; } - return true; } + return false; } - public override function released() { - if (mouse.released(key)) { - for (m in modifiers) { - if (!mouse.down(m)) return false; - } - return true; - } - return false; - } -} - -class GamepadActionCommand extends InputActionCommand { - final gamepad: Gamepad; - - public function new(parent: InputAction, key: String, modifiers: Array) { - super(parent, key, modifiers); - gamepad = Input.getGamepad(index); - } - - public inline override function started() { - if (gamepad.started(key)) { - for (m in modifiers) { - if (gamepad.down(m) <= pressure) return false; - } - return true; - } - return false; - } - - public inline override function released() { - if (gamepad.released(key)) { - for (m in modifiers) { - if (gamepad.down(m) <= pressure) return false; - } - return true; - } - return false; - } -} - -class InputAxis { - public final pressure: FastFloat; - public final deadzone: FastFloat; - public final index: Int; - static var commandsX = new Map>(); - static var scaleX = 1.0; - static var commandsY = new Map>(); - static var scaleY = 1.0; - static var normalize = false; - static var vec = new Vec4(); - var display = new Map(); - - public function new(pressure: FastFloat, deadzone: FastFloat, index: Int) { - this.index = index; - this.pressure = pressure; - this.deadzone = deadzone; - } - - /** - * Get the display form of all commands that activates this axis according to the active config - * @param config - */ - public function getDisplay(config: String) { - var s = ""; - for (c in commandsX[config]) { - s += display[c] + " OR "; - } - - for (c in commandsY[config]) { - s += display[c] + " OR "; - } - - return s; - } - - /** - * Add a keyboard input command - * @param position The position that the added input command will be in the returned vector ("x" or "y") - * @param positiveKey The key that when pressed will sum +1 - * @param negativeKey The key that when pressed will sum -1 - * @param config The input command config - * @param display The form this command will be displayed - * @return InputAxisCommand - **/ - public function addKeyboard(position: String, positiveKey: String, ?negativeKey = "", ?config = "", ?display = "") { - var command = new KeyboardAxisCommand(this, positiveKey, negativeKey); - addCommand(position, command, config, display); - return command; - } - - /** - * Add a mouse input command - * @param position The position that the added input command will be in the returned vector ("x" or "y") - * @param positiveButton The key that when pressed will sum +1 - * @param negativeButton The key that when pressed will sum -1 - * @param config The input command config - * @param display The form this command will be displayed - * @return InputAxisCommand - **/ - public function addMouse(position: String, positiveButton: String, ?negativeButton = "", ?config = "", ?display = "") { - var command = new MouseAxisCommand(this, positiveButton, negativeButton); - addCommand(position, command, config, display); - return command; - } - - /** - * Add a gamepad input command - * @param position The position that the added input command will be in the returned vector ("x" or "y") - * @param positiveButton The key that when pressed will sum +1 - * @param negativeButton The key that when pressed will sum -1 - * @param config The input command config - * @param display The form this command will be displayed - * @return InputAxisCommand - **/ - public function addGamepad(position: String, positiveButton: String, ?negativeButton = "", ?config = "", ?display = "") { - var command = new GamepadAxisCommand(this, positiveButton, negativeButton); - addCommand(position, command, config, display); - return command; - } - - /** - * Add a custom input command - * @param command The constructed input command - * @param config The input command config - * @param display The form this command will be displayed - * @return InputAxisCommand - **/ - public function addCommand(position: String, command: InputAxisCommand, config: String, display: String) { - switch (position) { - case "x": { - if (commandsX[config] == null) commandsX[config] = new Array(); - commandsX[config].push(command); - } - case "y": { - if (commandsY[config] == null) commandsY[config] = new Array(); - commandsY[config].push(command); - } - } - this.display[command] = display; - return command; - } - - /** - * Set the scale of the returned vector - * @param x The scale of the commands in the position x - * @param y The scale of the commands in the positon y - * @return Void - **/ - public function setScale(x: FastFloat, y: FastFloat) { - scaleX = x; - scaleY = y; - } - - /** - * Enable the returned vector normalization - * @return Void - **/ - public function enableNormalize() { - normalize = true; - } - - /** - * Disable the returned vector normalization - * @return Void - **/ - public function disableNormalize() { - normalize = false; - } - - /** - * Get the input axis vector - * @return Void - **/ - public inline function getVec(config: String) { - vec.set(0, 0, 0); - - for (c in commandsX[config]) { - if (c.getScale() != 0.0) vec.x += c.getScale(); - } - - for (c in commandsY[config]) { - if (c.getScale() != 0.0) vec.y += c.getScale(); - } - - if (normalize) vec.normalize(); - - vec.x *= scaleX; - vec.y *= scaleY; - - return vec; - } -} - -class InputAxisCommand { - final positiveKey: String; - final negativeKey: String; - final pressure: FastFloat; - final minusPressure: FastFloat; - final deadzone: FastFloat; - final minusDeadzone: FastFloat; - var scale: FastFloat; - - public function new(parent: InputAxis, positiveKey: String, negativeKey: String) { - this.positiveKey = positiveKey; - this.negativeKey = negativeKey; - - pressure = parent.pressure; - minusPressure = -pressure; - deadzone = parent.deadzone; - minusDeadzone = -deadzone; - } - - public function getScale() { - return scale; - } -} - -class KeyboardAxisCommand extends InputAxisCommand { - final keyboard = Input.getKeyboard(); - - public function new(parent: InputAxis, positiveKey: String, negativeKey: String) { - super(parent, positiveKey, negativeKey); - } - - public inline override function getScale() { - var scale = 0.0; - if (keyboard.down(positiveKey)) scale++; - if (keyboard.down(negativeKey)) scale--; - return scale; - } -} - -class MouseAxisCommand extends InputAxisCommand { - final mouse = Input.getMouse(); - - public function new(parent: InputAxis, positiveKey: String, negativeKey: String) { - super(parent, positiveKey, negativeKey); - } - - public inline override function getScale() { - scale = 0.0; + public inline override function getAxis() { + var axis = 0.0; var movementX = mouse.movementX; var movementY = mouse.movementY; var wheelDelta = mouse.wheelDelta; - switch (positiveKey) { - case "moved x": if (movementX > deadzone) scale++; - case "movement x": if (movementX > deadzone) return movementX - deadzone; - case "moved y": if (movementY > deadzone) scale++; - case "movement y": if (movementY > deadzone) return movementY - deadzone; - case "wheel moved": if (wheelDelta > deadzone) scale++; - case "wheel movement": if (wheelDelta > deadzone) return wheelDelta - deadzone; - default: if (mouse.down(positiveKey)) scale++; + for (k in keys) { + if (keyboard.down(k)) { + axis++; + break; + } } - switch (negativeKey) { - case "moved x": if (movementX < minusDeadzone) scale--; - case "movement x": if (movementX < minusDeadzone) return movementX + deadzone; - case "moved y": if (movementY < minusDeadzone) scale--; - case "movement y": if (movementY < minusDeadzone) return movementY + deadzone; - case "wheel moved": if (wheelDelta < minusDeadzone) scale--; - case "wheel movement": if (wheelDelta < minusDeadzone) return wheelDelta + deadzone; - default: if (mouse.down(negativeKey)) scale--; + + for (m in modifiers) { + if (keyboard.down(m)) { + axis --; + break; + } } - return scale; + + for (k in displacementKeys) { + switch (k) { + case "moved x": if (movementX > deadzone) axis++; + case "moved y": if (movementY > deadzone) axis--; + case "wheel": if (wheelDelta < -deadzone) axis++; + case "movement x": if (movementX > deadzone) return movementX - deadzone; + case "movement y": if (movementY > deadzone) return movementY - deadzone; + default: { + if (mouse.down(k)) { + axis ++; + break; + } + } + } + } + + for (m in displacementModifiers) { + switch (m) { + case "moved x": if (movementX < -deadzone) axis--; + case "moved y": if (movementY < -deadzone) axis++; + case "wheel": if (wheelDelta > deadzone) axis--; + case "movement x": if (movementX < -deadzone) return movementX + deadzone; + case "movement y": if (movementY < -deadzone) return movementY + deadzone; + default: { + if (mouse.down(m)) { + axis --; + break; + } + } + } + } + + return axis > 1 ? 1 : axis < -1 ? -1 : axis; } } -class GamepadAxisCommand extends InputAxisCommand { - final gamepad: Gamepad; +class GamepadCommand extends InputCommand { + var gamepad = Input.getGamepad(0); - public function new(parent: InputAxis, positiveKey: String, negativeKey: String) { - super(parent, positiveKey, negativeKey); - gamepad = Input.getGamepad(parent.index); + public inline override function started() { + for (k in keys) { + if (gamepad.started(k)) { + for (m in modifiers) { + if (gamepad.down(m) < deadzone) return false; + } + + return true; + } + } + + return false; } - public inline override function getScale() { - scale = 0.0; - var rx = gamepad.rightStick.movementX; - var ry = gamepad.rightStick.movementY; - var lx = gamepad.leftStick.movementX; - var ly = gamepad.leftStick.movementY; - var rt = gamepad.down("r2") > 0.0 ? (gamepad.down("r2") - pressure) / (1 - pressure) : 0.0; - var lt = gamepad.down("l2") > 0.0 ? (gamepad.down("r2") - pressure) / (1 - pressure) : 0.0; + public inline override function released() { + for (k in keys) { + if (gamepad.released(k)) { + for (m in modifiers) { + if (gamepad.down(m) < deadzone) return false; + } - switch (positiveKey) { - case "right stick moved x": if (rx > deadzone) scale++; - case "right stick movement x": if (rx > deadzone) return rx - deadzone; - case "right stick moved y": if (ry > deadzone) scale++; - case "right stick movement y": if (ry > deadzone) return ry - deadzone; - case "left stick moved x": if (lx > deadzone) scale++; - case "left stick movement x": if (lx > deadzone) return lx - deadzone; - case "left stick moved y": if (ly > deadzone) scale++; - case "left stick movement y": if (ly > deadzone) return ly - deadzone; - case "right trigger": scale += rt; - case "left trigger": scale += lt; - default: if (gamepad.down(positiveKey) > pressure) scale++; + return true; + } } - switch (negativeKey) { - case "right stick moved x": if (rx < minusDeadzone) scale--; - case "right stick movement x": if (rx < minusDeadzone) return rx + deadzone; - case "right stick moved y": if (ry < minusDeadzone) scale--; - case "right stick movement y": if (ry < minusDeadzone) return ry + deadzone; - case "left stick moved x": if (lx < minusDeadzone) scale--; - case "left stick movement x": if (lx < minusDeadzone) return lx + deadzone; - case "left stick moved y": if (ly < minusDeadzone) scale--; - case "left stick movement y": if (ly < minusDeadzone) return ly + deadzone; - case "right trigger": scale -= rt; - case "left trigger": scale -= lt; - default: if (gamepad.down(negativeKey) < minusPressure) scale--; + return false; + } + + public inline override function getAxis() { + var axis = 0.0; + var rsMovementX = gamepad.rightStick.movementX; + var rsMovementY = gamepad.rightStick.movementY; + var lsMovementX = gamepad.leftStick.movementX; + var lsMovementY = gamepad.leftStick.movementY; + var rtPressure = gamepad.down("r2") > 0.0 ? (gamepad.down("r2") - deadzone) / (1 - deadzone) : 0.0; + var ltPressure = gamepad.down("l2") > 0.0 ? (gamepad.down("r2") - deadzone) / (1 - deadzone) : 0.0; + + for (k in keys) { + switch(k) { + case "rtPressure": axis += rtPressure; + case "ltPressure": axis += ltPressure; + default: { + if (gamepad.down(k) > deadzone) { + axis++; + break; + } + } + } } - return scale; + for (m in modifiers) { + switch (m) { + case "rtPressure": axis -= rtPressure; + case "ltPressure": axis -= ltPressure; + default: { + if (gamepad.down(m) > deadzone) { + axis--; + break; + } + } + } + } + + for (k in displacementKeys) { + switch(k) { + case "rs moved x": if (rsMovementX > deadzone) axis++; + case "rs moved y": if (rsMovementY > deadzone) axis++; + case "ls moved x": if (lsMovementX > deadzone) axis++; + case "ls moved y": if (lsMovementY > deadzone) axis++; + case "rs movement x": if (rsMovementX > deadzone) return rsMovementX - deadzone; + case "rs movement y": if (rsMovementY > deadzone) return rsMovementY - deadzone; + case "ls movement x": if (lsMovementX > deadzone) return lsMovementX - deadzone; + case "ls movement y": if (lsMovementY > deadzone) return lsMovementY - deadzone; + } + } + + for (m in displacementModifiers) { + switch (m) { + case "rs moved x": if (rsMovementX < -deadzone) axis--; + case "rs moved y": if (rsMovementY < -deadzone) axis--; + case "ls moved x": if (lsMovementX < -deadzone) axis--; + case "ls moved y": if (lsMovementY < -deadzone) axis--; + case "rs movement x": if (rsMovementX < -deadzone) return rsMovementX + deadzone; + case "rs movement y": if (rsMovementY < -deadzone) return rsMovementY + deadzone; + case "ls movement x": if (lsMovementX < -deadzone) return lsMovementX + deadzone; + case "ls movement y": if (lsMovementY < -deadzone) return lsMovementY + deadzone; + + } + } + + return axis > 1 ? 1 : axis < -1 ? -1 : axis; } } \ No newline at end of file From 725520081a1e7bea9e505f2a65db28efa32c1d9a Mon Sep 17 00:00:00 2001 From: tong Date: Sun, 24 Jan 2021 16:53:34 +0100 Subject: [PATCH 13/74] Normalize sdk path --- blender/arm/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/blender/arm/utils.py b/blender/arm/utils.py index e6875612..82ff4f9f 100755 --- a/blender/arm/utils.py +++ b/blender/arm/utils.py @@ -156,11 +156,11 @@ def get_sdk_path(): addon_prefs = get_arm_preferences() p = bundled_sdk_path() if use_local_sdk: - return get_fp() + '/armsdk/' + return os.path.normpath(get_fp() + '/armsdk/') elif os.path.exists(p) and addon_prefs.sdk_bundled: - return p + return os.path.normpath(p) else: - return addon_prefs.sdk_path + return os.path.normpath(addon_prefs.sdk_path) def get_last_commit(): p = get_sdk_path() + 'armory/.git/refs/heads/master' From 4ee1131f6e9a64b270d16cdac01895b5aa908f0c Mon Sep 17 00:00:00 2001 From: Kevin Leung Date: Fri, 29 Jan 2021 20:54:34 +0800 Subject: [PATCH 14/74] [PickLocation] Actually return null when input invalid --- Sources/armory/logicnode/PickLocationNode.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/armory/logicnode/PickLocationNode.hx b/Sources/armory/logicnode/PickLocationNode.hx index 4c258b76..5cb9eeb5 100644 --- a/Sources/armory/logicnode/PickLocationNode.hx +++ b/Sources/armory/logicnode/PickLocationNode.hx @@ -15,7 +15,7 @@ class PickLocationNode extends LogicNode { var object: Object = inputs[0].get(); var coords: Vec4 = inputs[1].get(); - if (object == null || coords == null) null; + if (object == null || coords == null) return null; #if arm_physics var physics = armory.trait.physics.PhysicsWorld.active; From 6ffa58fb6b55b34c3c057aa8bedaf385cd7a4062 Mon Sep 17 00:00:00 2001 From: luboslenco Date: Mon, 1 Feb 2021 10:00:43 +0100 Subject: [PATCH 15/74] Bump version --- blender/arm/props.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blender/arm/props.py b/blender/arm/props.py index beed316f..a2d1f23d 100755 --- a/blender/arm/props.py +++ b/blender/arm/props.py @@ -11,7 +11,7 @@ import arm.proxy import arm.utils # Armory version -arm_version = '2021.1' +arm_version = '2021.2' arm_commit = '$Id$' def get_project_html5_copy(self): From afe133381cf11efdb33b86a73914b6865bcce7ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Wed, 3 Feb 2021 18:23:22 +0100 Subject: [PATCH 16/74] Fix normals export of skinned materials --- blender/arm/material/make_attrib.py | 2 -- blender/arm/material/make_skin.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/blender/arm/material/make_attrib.py b/blender/arm/material/make_attrib.py index 7aded764..1d01e99b 100644 --- a/blender/arm/material/make_attrib.py +++ b/blender/arm/material/make_attrib.py @@ -37,7 +37,6 @@ def write_norpos(con_mesh: shader.ShaderContext, vert: shader.Shader, declare=Fa prep = '' if declare: prep = 'vec3 ' - vert.write_pre = True is_bone = con_mesh.is_elem('bone') if is_bone: make_skin.skin_pos(vert) @@ -48,4 +47,3 @@ def write_norpos(con_mesh: shader.ShaderContext, vert: shader.Shader, declare=Fa vert.write_attrib(prep + 'wnormal = normalize(N * vec3(nor.xy, pos.w));') if con_mesh.is_elem('ipos'): make_inst.inst_pos(con_mesh, vert) - vert.write_pre = False diff --git a/blender/arm/material/make_skin.py b/blender/arm/material/make_skin.py index 2feb6895..d6ea4177 100644 --- a/blender/arm/material/make_skin.py +++ b/blender/arm/material/make_skin.py @@ -17,4 +17,4 @@ def skin_pos(vert): def skin_nor(vert, prep): rpdat = arm.utils.get_rp() - vert.write(prep + 'wnormal = normalize(N * (vec3(nor.xy, pos.w) + 2.0 * cross(skinA.xyz, cross(skinA.xyz, vec3(nor.xy, pos.w)) + skinA.w * vec3(nor.xy, pos.w))));') + vert.write_attrib(prep + 'wnormal = normalize(N * (vec3(nor.xy, pos.w) + 2.0 * cross(skinA.xyz, cross(skinA.xyz, vec3(nor.xy, pos.w)) + skinA.w * vec3(nor.xy, pos.w))));') From 1c3e24a8fd17999df98a39c9e64aa347be3d5cf0 Mon Sep 17 00:00:00 2001 From: N8n5h Date: Tue, 26 Jan 2021 22:01:06 -0300 Subject: [PATCH 17/74] Add support for shadow map atlasing With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this limitation was solved. The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a modified version of drawShadowMap(). Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial 4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth levels is added or not when compiling. the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature. --- .../deferred_light/deferred_light.frag.glsl | 86 ++- Shaders/deferred_light/deferred_light.json | 48 +- .../deferred_light.frag.glsl | 66 +- .../deferred_light_mobile.json | 30 +- Shaders/std/clusters.glsl | 3 - Shaders/std/light.glsl | 126 ++-- Shaders/std/light_mobile.glsl | 79 ++- Shaders/std/shadows.glsl | 179 ++++- .../volumetric_light.frag.glsl | 9 +- .../volumetric_light/volumetric_light.json | 17 +- Sources/armory/renderpath/Inc.hx | 671 +++++++++++++++++- .../armory/renderpath/RenderPathCreator.hx | 17 +- .../armory/renderpath/RenderPathDeferred.hx | 13 + .../armory/renderpath/RenderPathForward.hx | 12 + Sources/armory/trait/internal/DebugConsole.hx | 3 + blender/arm/make_renderpath.py | 27 +- blender/arm/material/make_cluster.py | 24 +- blender/arm/material/make_mesh.py | 39 +- blender/arm/material/shader.py | 17 +- blender/arm/props_renderpath.py | 56 ++ blender/arm/props_ui.py | 58 +- blender/arm/write_data.py | 17 + 22 files changed, 1403 insertions(+), 194 deletions(-) diff --git a/Shaders/deferred_light/deferred_light.frag.glsl b/Shaders/deferred_light/deferred_light.frag.glsl index c9ac4da2..31c342ae 100644 --- a/Shaders/deferred_light/deferred_light.frag.glsl +++ b/Shaders/deferred_light/deferred_light.frag.glsl @@ -2,7 +2,6 @@ #include "compiled.inc" #include "std/gbuffer.glsl" -#include "std/light.glsl" #ifdef _Clusters #include "std/clusters.glsl" #endif @@ -80,14 +79,11 @@ uniform mat4 invVP; #ifdef _ShadowMap #ifdef _SinglePoint //!uniform sampler2DShadow shadowMapSpot[1]; - //!uniform mat4 LWVPSpot0; + //!uniform mat4 LWVPSpot[1]; #endif #ifdef _Clusters //!uniform sampler2DShadow shadowMapSpot[4]; - //!uniform mat4 LWVPSpot0; - //!uniform mat4 LWVPSpot1; - //!uniform mat4 LWVPSpot2; - //!uniform mat4 LWVPSpot3; + //!uniform mat4 LWVPSpotArray[4]; #endif #endif #endif @@ -109,21 +105,36 @@ uniform vec2 cameraPlane; #ifdef _SinglePoint #ifdef _Spot //!uniform sampler2DShadow shadowMapSpot[1]; - //!uniform mat4 LWVPSpot0; + //!uniform mat4 LWVPSpot[1]; #else //!uniform samplerCubeShadow shadowMapPoint[1]; //!uniform vec2 lightProj; #endif #endif #ifdef _Clusters - //!uniform samplerCubeShadow shadowMapPoint[4]; + #ifdef _ShadowMapAtlas + #ifdef _SingleAtlas + uniform sampler2DShadow shadowMapAtlas; + #endif + #endif + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + //!uniform sampler2DShadow shadowMapAtlasPoint; + #endif + //!uniform vec4 pointLightDataArray[4]; + #else + //!uniform samplerCubeShadow shadowMapPoint[4]; + #endif //!uniform vec2 lightProj; #ifdef _Spot - //!uniform sampler2DShadow shadowMapSpot[4]; - //!uniform mat4 LWVPSpot0; - //!uniform mat4 LWVPSpot1; - //!uniform mat4 LWVPSpot2; - //!uniform mat4 LWVPSpot3; + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + //!uniform sampler2DShadow shadowMapAtlasSpot; + #endif + #else + //!uniform sampler2DShadow shadowMapSpot[4]; + #endif + //!uniform mat4 LWVPSpotArray[4]; #endif #endif #endif @@ -132,7 +143,13 @@ uniform vec2 cameraPlane; uniform vec3 sunDir; uniform vec3 sunCol; #ifdef _ShadowMap + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + uniform sampler2DShadow shadowMapAtlasSun; + #endif + #else uniform sampler2DShadow shadowMap; + #endif uniform float shadowsBias; #ifdef _CSM //!uniform vec4 casData[shadowmapCascades * 4 + 4]; @@ -159,6 +176,8 @@ uniform sampler2D texClouds; uniform float time; #endif +#include "std/light.glsl" + in vec2 texCoord; in vec3 viewRay; out vec4 fragColor; @@ -289,10 +308,32 @@ void main() { #ifdef _ShadowMap #ifdef _CSM - svisibility = shadowTestCascade(shadowMap, eye, p + n * shadowsBias * 10, shadowsBias); + svisibility = shadowTestCascade( + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + shadowMapAtlasSun + #else + shadowMapAtlas + #endif + #else + shadowMap + #endif + , eye, p + n * shadowsBias * 10, shadowsBias + ); #else - vec4 lPos = LWVP * vec4(p + n * shadowsBias * 100, 1.0); - if (lPos.w > 0.0) svisibility = shadowTest(shadowMap, lPos.xyz / lPos.w, shadowsBias); + vec4 lPos = LWVP * vec4(p + n * shadowsBias * 100, 1.0); + if (lPos.w > 0.0) svisibility = shadowTest( + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + shadowMapAtlasSun + #else + shadowMapAtlas + #endif + #else + shadowMap + #endif + , lPos.xyz / lPos.w, shadowsBias + ); #endif #endif @@ -335,7 +376,18 @@ void main() { int casi, casindex; mat4 LWVP = getCascadeMat(distance(eye, p), casi, casindex); #endif - fragColor.rgb += fragColor.rgb * SSSSTransmittance(LWVP, p, n, sunDir, lightPlane.y, shadowMap); + fragColor.rgb += fragColor.rgb * SSSSTransmittance( + LWVP, p, n, sunDir, lightPlane.y, + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + shadowMapAtlasSun + #else + shadowMapAtlas + #endif + #else + shadowMap + #endif + ); } #endif diff --git a/Shaders/deferred_light/deferred_light.json b/Shaders/deferred_light/deferred_light.json index 06d5a80f..d0d02f1f 100755 --- a/Shaders/deferred_light/deferred_light.json +++ b/Shaders/deferred_light/deferred_light.json @@ -199,43 +199,37 @@ "ifdef": ["_SinglePoint", "_Spot"] }, { - "name": "LWVPSpot0", + "name": "LWVPSpotArray", + "link": "_biasLightWorldViewProjectionMatrixSpotArray", + "ifdef": ["_Clusters", "_ShadowMap", "_Spot"] + }, + { + "name": "pointLightDataArray", + "link": "_pointLightsAtlasArray", + "ifdef": ["_Clusters", "_ShadowMap", "_ShadowMapAtlas"] + }, + { + "name": "LWVPSpot[0]", "link": "_biasLightWorldViewProjectionMatrixSpot0", - "ifdef": ["_Spot", "_ShadowMap"] + "ifndef": ["_ShadowMapAtlas"], + "ifdef": ["_LTC", "_ShadowMap"] }, { - "name": "LWVPSpot1", + "name": "LWVPSpot[1]", "link": "_biasLightWorldViewProjectionMatrixSpot1", - "ifdef": ["_Spot", "_ShadowMap"] + "ifndef": ["_ShadowMapAtlas"], + "ifdef": ["_LTC", "_ShadowMap"] }, { - "name": "LWVPSpot2", + "name": "LWVPSpot[2]", "link": "_biasLightWorldViewProjectionMatrixSpot2", - "ifdef": ["_Spot", "_ShadowMap"] + "ifndef": ["_ShadowMapAtlas"], + "ifdef": ["_LTC", "_ShadowMap"] }, { - "name": "LWVPSpot3", - "link": "_biasLightWorldViewProjectionMatrixSpot3", - "ifdef": ["_Spot", "_ShadowMap"] - }, - { - "name": "LWVPSpot0", - "link": "_biasLightWorldViewProjectionMatrixSpot0", - "ifdef": ["_LTC", "_ShadowMap"] - }, - { - "name": "LWVPSpot1", - "link": "_biasLightWorldViewProjectionMatrixSpot1", - "ifdef": ["_LTC", "_ShadowMap"] - }, - { - "name": "LWVPSpot2", - "link": "_biasLightWorldViewProjectionMatrixSpot2", - "ifdef": ["_LTC", "_ShadowMap"] - }, - { - "name": "LWVPSpot3", + "name": "LWVPSpot[3]", "link": "_biasLightWorldViewProjectionMatrixSpot3", + "ifndef": ["_ShadowMapAtlas"], "ifdef": ["_LTC", "_ShadowMap"] } ], diff --git a/Shaders/deferred_light_mobile/deferred_light.frag.glsl b/Shaders/deferred_light_mobile/deferred_light.frag.glsl index c549d1ff..cc8ce302 100644 --- a/Shaders/deferred_light_mobile/deferred_light.frag.glsl +++ b/Shaders/deferred_light_mobile/deferred_light.frag.glsl @@ -3,7 +3,6 @@ #include "compiled.inc" #include "std/gbuffer.glsl" #include "std/math.glsl" -#include "std/light_mobile.glsl" #ifdef _Clusters #include "std/clusters.glsl" #endif @@ -47,21 +46,36 @@ uniform vec2 cameraPlane; #ifdef _SinglePoint #ifdef _Spot //!uniform sampler2DShadow shadowMapSpot[1]; - //!uniform mat4 LWVPSpot0; + //!uniform mat4 LWVPSpot[1]; #else //!uniform samplerCubeShadow shadowMapPoint[1]; //!uniform vec2 lightProj; #endif #endif #ifdef _Clusters - //!uniform samplerCubeShadow shadowMapPoint[4]; + #ifdef _ShadowMapAtlas + #ifdef _SingleAtlas + uniform sampler2DShadow shadowMapAtlas; + #endif + #endif + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + //!uniform sampler2DShadow shadowMapAtlasPoint; + #endif + //!uniform vec4 pointLightDataArray[4]; + #else + //!uniform samplerCubeShadow shadowMapPoint[4]; + #endif //!uniform vec2 lightProj; #ifdef _Spot - //!uniform sampler2DShadow shadowMapSpot[4]; - //!uniform mat4 LWVPSpot0; - //!uniform mat4 LWVPSpot1; - //!uniform mat4 LWVPSpot2; - //!uniform mat4 LWVPSpot3; + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + //!uniform sampler2DShadow shadowMapAtlasSpot; + #endif + #else + //!uniform sampler2DShadow shadowMapSpot[4]; + #endif + //!uniform mat4 LWVPSpotArray[4]; #endif #endif #endif @@ -70,7 +84,13 @@ uniform vec2 cameraPlane; uniform vec3 sunDir; uniform vec3 sunCol; #ifdef _ShadowMap + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + uniform sampler2DShadow shadowMapAtlasSun; + #endif + #else uniform sampler2DShadow shadowMap; + #endif uniform float shadowsBias; #ifdef _CSM //!uniform vec4 casData[shadowmapCascades * 4 + 4]; @@ -90,6 +110,8 @@ uniform float pointBias; #endif #endif +#include "std/light_mobile.glsl" + in vec2 texCoord; in vec3 viewRay; out vec4 fragColor; @@ -168,10 +190,32 @@ void main() { #ifdef _ShadowMap #ifdef _CSM - svisibility = shadowTestCascade(shadowMap, eye, p + n * shadowsBias * 10, shadowsBias, shadowmapSize * vec2(shadowmapCascades, 1.0)); + svisibility = shadowTestCascade( + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + shadowMapAtlasSun + #else + shadowMapAtlas + #endif + #else + shadowMap + #endif + , eye, p + n * shadowsBias * 10, shadowsBias + ); #else - vec4 lPos = LWVP * vec4(p + n * shadowsBias * 100, 1.0); - if (lPos.w > 0.0) svisibility = shadowTest(shadowMap, lPos.xyz / lPos.w, shadowsBias, shadowmapSize); + vec4 lPos = LWVP * vec4(p + n * shadowsBias * 100, 1.0); + if (lPos.w > 0.0) svisibility = shadowTest( + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + shadowMapAtlasSun + #else + shadowMapAtlas + #endif + #else + shadowMap + #endif + , lPos.xyz / lPos.w, shadowsBias + ); #endif #endif diff --git a/Shaders/deferred_light_mobile/deferred_light_mobile.json b/Shaders/deferred_light_mobile/deferred_light_mobile.json index 118bcdf3..620e27e1 100644 --- a/Shaders/deferred_light_mobile/deferred_light_mobile.json +++ b/Shaders/deferred_light_mobile/deferred_light_mobile.json @@ -138,24 +138,38 @@ "ifdef": ["_SinglePoint", "_Spot"] }, { - "name": "LWVPSpot0", + "name": "LWVPSpotArray", + "link": "_biasLightWorldViewProjectionMatrixSpotArray", + "ifdef": ["_Clusters", "_ShadowMap", "_Spot"] + }, + { + "name": "pointLightDataArray", + "link": "_pointLightsAtlasArray", + "ifdef": ["_Clusters", "_ShadowMap", "_ShadowMapAtlas"] + }, + { + "name": "LWVPSpot[0]", "link": "_biasLightWorldViewProjectionMatrixSpot0", - "ifdef": ["_Spot", "_ShadowMap"] + "ifndef": ["_ShadowMapAtlas"], + "ifdef": ["_LTC", "_ShadowMap"] }, { - "name": "LWVPSpot1", + "name": "LWVPSpot[1]", "link": "_biasLightWorldViewProjectionMatrixSpot1", - "ifdef": ["_Spot", "_ShadowMap"] + "ifndef": ["_ShadowMapAtlas"], + "ifdef": ["_LTC", "_ShadowMap"] }, { - "name": "LWVPSpot2", + "name": "LWVPSpot[2]", "link": "_biasLightWorldViewProjectionMatrixSpot2", - "ifdef": ["_Spot", "_ShadowMap"] + "ifndef": ["_ShadowMapAtlas"], + "ifdef": ["_LTC", "_ShadowMap"] }, { - "name": "LWVPSpot3", + "name": "LWVPSpot[3]", "link": "_biasLightWorldViewProjectionMatrixSpot3", - "ifdef": ["_Spot", "_ShadowMap"] + "ifndef": ["_ShadowMapAtlas"], + "ifdef": ["_LTC", "_ShadowMap"] } ], "vertex_shader": "../include/pass_viewray.vert.glsl", diff --git a/Shaders/std/clusters.glsl b/Shaders/std/clusters.glsl index c9af62bf..04570ef6 100644 --- a/Shaders/std/clusters.glsl +++ b/Shaders/std/clusters.glsl @@ -1,7 +1,4 @@ -const int maxLights = 16; -const int maxLightsCluster = 4; // Ensure fast loop unroll before going higher -const float clusterNear = 3.0; const vec3 clusterSlices = vec3(16, 16, 16); int getClusterI(vec2 tc, float viewz, vec2 cameraPlane) { diff --git a/Shaders/std/light.glsl b/Shaders/std/light.glsl index 293dad04..61c26771 100644 --- a/Shaders/std/light.glsl +++ b/Shaders/std/light.glsl @@ -21,27 +21,41 @@ #endif #ifdef _ShadowMap -#ifdef _SinglePoint - #ifdef _Spot - uniform sampler2DShadow shadowMapSpot[1]; - uniform mat4 LWVPSpot0; - #else - uniform samplerCubeShadow shadowMapPoint[1]; - uniform vec2 lightProj; + #ifdef _SinglePoint + #ifdef _Spot + #ifndef _LTC + uniform sampler2DShadow shadowMapSpot[1]; + uniform mat4 LWVPSpot[1]; + #endif + #else + uniform samplerCubeShadow shadowMapPoint[1]; + uniform vec2 lightProj; + #endif #endif -#endif -#ifdef _Clusters - uniform samplerCubeShadow shadowMapPoint[4]; - uniform vec2 lightProj; - #ifdef _Spot - uniform sampler2DShadow shadowMapSpot[4]; - uniform mat4 LWVPSpot0; - uniform mat4 LWVPSpot1; - uniform mat4 LWVPSpot2; - uniform mat4 LWVPSpot3; + #ifdef _Clusters + #ifdef _SingleAtlas + //!uniform sampler2DShadow shadowMapAtlas; + #endif + uniform vec2 lightProj; + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + uniform sampler2DShadow shadowMapAtlasPoint; + #endif + #else + uniform samplerCubeShadow shadowMapPoint[4]; + #endif + #ifdef _Spot + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + uniform sampler2DShadow shadowMapAtlasSpot; + #endif + #else + uniform sampler2DShadow shadowMapSpot[maxLightsCluster]; + #endif + uniform mat4 LWVPSpotArray[maxLightsCluster]; + #endif #endif #endif -#endif #ifdef _LTC uniform vec3 lightArea0; @@ -51,17 +65,14 @@ uniform vec3 lightArea3; uniform sampler2D sltcMat; uniform sampler2D sltcMag; #ifdef _ShadowMap - #ifndef _Spot +#ifndef _Spot #ifdef _SinglePoint - uniform sampler2DShadow shadowMapSpot[1]; - uniform mat4 LWVPSpot0; + uniform sampler2DShadow shadowMapSpot[1]; + uniform mat4 LWVPSpot[1]; #endif #ifdef _Clusters - uniform sampler2DShadow shadowMapSpot[4]; - uniform mat4 LWVPSpot0; - uniform mat4 LWVPSpot1; - uniform mat4 LWVPSpot2; - uniform mat4 LWVPSpot3; + uniform sampler2DShadow shadowMapSpot[maxLightsCluster]; + uniform mat4 LWVPSpotArray[maxLightsCluster]; #endif #endif #endif @@ -132,24 +143,24 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co #ifdef _ShadowMap if (receiveShadow) { #ifdef _SinglePoint - vec4 lPos = LWVPSpot0 * vec4(p + n * bias * 10, 1.0); + vec4 lPos = LWVPSpot[0] * vec4(p + n * bias * 10, 1.0); direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias); #endif #ifdef _Clusters if (index == 0) { - vec4 lPos = LWVPSpot0 * vec4(p + n * bias * 10, 1.0); + vec4 lPos = LWVPSpot[0] * vec4(p + n * bias * 10, 1.0); direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias); } else if (index == 1) { - vec4 lPos = LWVPSpot1 * vec4(p + n * bias * 10, 1.0); + vec4 lPos = LWVPSpot[1] * vec4(p + n * bias * 10, 1.0); direct *= shadowTest(shadowMapSpot[1], lPos.xyz / lPos.w, bias); } else if (index == 2) { - vec4 lPos = LWVPSpot2 * vec4(p + n * bias * 10, 1.0); + vec4 lPos = LWVPSpot[2] * vec4(p + n * bias * 10, 1.0); direct *= shadowTest(shadowMapSpot[2], lPos.xyz / lPos.w, bias); } else if (index == 3) { - vec4 lPos = LWVPSpot3 * vec4(p + n * bias * 10, 1.0); + vec4 lPos = LWVPSpot[3] * vec4(p + n * bias * 10, 1.0); direct *= shadowTest(shadowMapSpot[3], lPos.xyz / lPos.w, bias); } #endif @@ -168,26 +179,26 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co #ifdef _ShadowMap if (receiveShadow) { #ifdef _SinglePoint - vec4 lPos = LWVPSpot0 * vec4(p + n * bias * 10, 1.0); + vec4 lPos = LWVPSpot[0] * vec4(p + n * bias * 10, 1.0); direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias); #endif #ifdef _Clusters - if (index == 0) { - vec4 lPos = LWVPSpot0 * vec4(p + n * bias * 10, 1.0); - direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias); - } - else if (index == 1) { - vec4 lPos = LWVPSpot1 * vec4(p + n * bias * 10, 1.0); - direct *= shadowTest(shadowMapSpot[1], lPos.xyz / lPos.w, bias); - } - else if (index == 2) { - vec4 lPos = LWVPSpot2 * vec4(p + n * bias * 10, 1.0); - direct *= shadowTest(shadowMapSpot[2], lPos.xyz / lPos.w, bias); - } - else if (index == 3) { - vec4 lPos = LWVPSpot3 * vec4(p + n * bias * 10, 1.0); - direct *= shadowTest(shadowMapSpot[3], lPos.xyz / lPos.w, bias); - } + vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0); + #ifdef _ShadowMapAtlas + direct *= shadowTest( + #ifndef _SingleAtlas + shadowMapAtlasSpot + #else + shadowMapAtlas + #endif + , lPos.xyz / lPos.w, bias + ); + #else + if (index == 0) direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias); + else if (index == 1) direct *= shadowTest(shadowMapSpot[1], lPos.xyz / lPos.w, bias); + else if (index == 2) direct *= shadowTest(shadowMapSpot[2], lPos.xyz / lPos.w, bias); + else if (index == 3) direct *= shadowTest(shadowMapSpot[3], lPos.xyz / lPos.w, bias); + #endif #endif } #endif @@ -207,10 +218,21 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co #endif #endif #ifdef _Clusters - if (index == 0) direct *= PCFCube(shadowMapPoint[0], ld, -l, bias, lightProj, n); - else if (index == 1) direct *= PCFCube(shadowMapPoint[1], ld, -l, bias, lightProj, n); - else if (index == 2) direct *= PCFCube(shadowMapPoint[2], ld, -l, bias, lightProj, n); - else if (index == 3) direct *= PCFCube(shadowMapPoint[3], ld, -l, bias, lightProj, n); + #ifdef _ShadowMapAtlas + direct *= PCFFakeCube( + #ifndef _SingleAtlas + shadowMapAtlasPoint + #else + shadowMapAtlas + #endif + , ld, -l, bias, lightProj, n, index + ); + #else + if (index == 0) direct *= PCFCube(shadowMapPoint[0], ld, -l, bias, lightProj, n); + else if (index == 1) direct *= PCFCube(shadowMapPoint[1], ld, -l, bias, lightProj, n); + else if (index == 2) direct *= PCFCube(shadowMapPoint[2], ld, -l, bias, lightProj, n); + else if (index == 3) direct *= PCFCube(shadowMapPoint[3], ld, -l, bias, lightProj, n); + #endif #endif } #endif diff --git a/Shaders/std/light_mobile.glsl b/Shaders/std/light_mobile.glsl index 86558dd7..f6a966c9 100644 --- a/Shaders/std/light_mobile.glsl +++ b/Shaders/std/light_mobile.glsl @@ -11,21 +11,33 @@ #ifdef _SinglePoint #ifdef _Spot uniform sampler2DShadow shadowMapSpot[1]; - uniform mat4 LWVPSpot0; + uniform mat4 LWVPSpot[1]; #else uniform samplerCubeShadow shadowMapPoint[1]; uniform vec2 lightProj; #endif #endif #ifdef _Clusters - uniform samplerCubeShadow shadowMapPoint[4]; + #ifdef _SingleAtlas + //!uniform sampler2DShadow shadowMapAtlas; + #endif uniform vec2 lightProj; + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + uniform sampler2DShadow shadowMapAtlasPoint; + #endif + #else + uniform samplerCubeShadow shadowMapPoint[4]; + #endif #ifdef _Spot - uniform sampler2DShadow shadowMapSpot[4]; - uniform mat4 LWVPSpot0; - uniform mat4 LWVPSpot1; - uniform mat4 LWVPSpot2; - uniform mat4 LWVPSpot3; + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + uniform sampler2DShadow shadowMapAtlasSpot; + #endif + #else + uniform sampler2DShadow shadowMapSpot[maxLightsCluster]; + #endif + uniform mat4 LWVPSpotArray[maxLightsCluster]; #endif #endif #endif @@ -62,26 +74,26 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co #ifdef _ShadowMap if (receiveShadow) { #ifdef _SinglePoint - vec4 lPos = LWVPSpot0 * vec4(p + n * bias * 10, 1.0); + vec4 lPos = LWVPSpot[0] * vec4(p + n * bias * 10, 1.0); direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias); #endif #ifdef _Clusters - if (index == 0) { - vec4 lPos = LWVPSpot0 * vec4(p + n * bias * 10, 1.0); - direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias); - } - else if (index == 1) { - vec4 lPos = LWVPSpot1 * vec4(p + n * bias * 10, 1.0); - direct *= shadowTest(shadowMapSpot[1], lPos.xyz / lPos.w, bias); - } - else if (index == 2) { - vec4 lPos = LWVPSpot2 * vec4(p + n * bias * 10, 1.0); - direct *= shadowTest(shadowMapSpot[2], lPos.xyz / lPos.w, bias); - } - else if (index == 3) { - vec4 lPos = LWVPSpot3 * vec4(p + n * bias * 10, 1.0); - direct *= shadowTest(shadowMapSpot[3], lPos.xyz / lPos.w, bias); - } + vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0); + #ifdef _ShadowMapAtlas + direct *= shadowTest( + #ifndef _SingleAtlas + shadowMapAtlasSpot + #else + shadowMapAtlas + #endif + , lPos.xyz / lPos.w, bias + ); + #else + if (index == 0) direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias); + else if (index == 1) direct *= shadowTest(shadowMapSpot[1], lPos.xyz / lPos.w, bias); + else if (index == 2) direct *= shadowTest(shadowMapSpot[2], lPos.xyz / lPos.w, bias); + else if (index == 3) direct *= shadowTest(shadowMapSpot[3], lPos.xyz / lPos.w, bias); + #endif #endif } #endif @@ -96,10 +108,21 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co direct *= PCFCube(shadowMapPoint[0], ld, -l, bias, lightProj, n); #endif #ifdef _Clusters - if (index == 0) direct *= PCFCube(shadowMapPoint[0], ld, -l, bias, lightProj, n); - else if (index == 1) direct *= PCFCube(shadowMapPoint[1], ld, -l, bias, lightProj, n); - else if (index == 2) direct *= PCFCube(shadowMapPoint[2], ld, -l, bias, lightProj, n); - else if (index == 3) direct *= PCFCube(shadowMapPoint[3], ld, -l, bias, lightProj, n); + #ifdef _ShadowMapAtlas + direct *= PCFFakeCube( + #ifndef _SingleAtlas + shadowMapAtlasPoint + #else + shadowMapAtlas + #endif + , ld, -l, bias, lightProj, n, index + ); + #else + if (index == 0) direct *= PCFCube(shadowMapPoint[0], ld, -l, bias, lightProj, n); + else if (index == 1) direct *= PCFCube(shadowMapPoint[1], ld, -l, bias, lightProj, n); + else if (index == 2) direct *= PCFCube(shadowMapPoint[2], ld, -l, bias, lightProj, n); + else if (index == 3) direct *= PCFCube(shadowMapPoint[3], ld, -l, bias, lightProj, n); + #endif #endif } #endif diff --git a/Shaders/std/shadows.glsl b/Shaders/std/shadows.glsl index d610676a..81a12cc4 100755 --- a/Shaders/std/shadows.glsl +++ b/Shaders/std/shadows.glsl @@ -11,6 +11,41 @@ uniform vec4 casData[shadowmapCascades * 4 + 4]; uniform vec2 smSizeUniform; #endif +#ifdef _ShadowMap + #ifdef _Clusters + #ifdef _ShadowMapAtlas + uniform vec4 pointLightDataArray[maxLightsCluster * 6]; + #endif + #endif +#endif + +#ifdef _ShadowMapAtlas +// https://www.khronos.org/registry/OpenGL/specs/gl/glspec20.pdf // p:168 +// https://www.gamedev.net/forums/topic/687535-implementing-a-cube-map-lookup-function/5337472/ +vec2 sampleCube(vec3 dir, out int faceIndex) { + vec3 dirAbs = abs(dir); + float ma; + vec2 uv; + if(dirAbs.z >= dirAbs.x && dirAbs.z >= dirAbs.y) { + faceIndex = dir.z < 0.0 ? 5 : 4; + ma = 0.5 / dirAbs.z; + uv = vec2(dir.z < 0.0 ? -dir.x : dir.x, -dir.y); + } + else if(dirAbs.y >= dirAbs.x) { + faceIndex = dir.y < 0.0 ? 3 : 2; + ma = 0.5 / dirAbs.y; + uv = vec2(dir.x, dir.y < 0.0 ? -dir.z : dir.z); + } + else { + faceIndex = dir.x < 0.0 ? 1 : 0; + ma = 0.5 / dirAbs.x; + uv = vec2(dir.x < 0.0 ? dir.z : -dir.z, -dir.y); + } + // downscale uv a little to hide seams + return uv * 0.9976 * ma + 0.5; +} +#endif + float PCF(sampler2DShadow shadowMap, const vec2 uv, const float compare, const vec2 smSize) { float result = texture(shadowMap, vec3(uv + (vec2(-1.0, -1.0) / smSize), compare)); result += texture(shadowMap, vec3(uv + (vec2(-1.0, 0.0) / smSize), compare)); @@ -50,6 +85,148 @@ float PCFCube(samplerCubeShadow shadowMapCube, const vec3 lp, vec3 ml, const flo return result / 9.0; } +#ifdef _ShadowMapAtlas +// transform "out-of-bounds" coordinates to the correct face/coordinate system +vec2 transformOffsetedUV(const int faceIndex, out int newFaceIndex, vec2 uv) { + if (uv.x < 0.0) { + if (faceIndex == 0) { // X+ + newFaceIndex = 4; // Z+ + } + else if (faceIndex == 1) { // X- + newFaceIndex = 5; // Z- + } + else if (faceIndex == 2) { // Y+ + newFaceIndex = 1; // X- + } + else if (faceIndex == 3) { // Y- + newFaceIndex = 1; // X- + } + else if (faceIndex == 4) { // Z+ + newFaceIndex = 1; // X- + } + else { // Z- + newFaceIndex = 0; // X+ + } + uv = vec2(1.0 + uv.x, uv.y); + } + else if (uv.x > 1.0) { + if (faceIndex == 0) { // X+ + newFaceIndex = 5; // Z- + } + else if (faceIndex == 1) { // X- + newFaceIndex = 4; // Z+ + } + else if (faceIndex == 2) { // Y+ + newFaceIndex = 0; // X+ + } + else if (faceIndex == 3) { // Y- + newFaceIndex = 0; // X+ + } + else if (faceIndex == 4) { // Z+ + newFaceIndex = 0; // X+ + } + else { // Z- + newFaceIndex = 1; // X- + } + uv = vec2(1.0 - uv.x, uv.y); + } + else if (uv.y < 0.0) { + if (faceIndex == 0) { // X+ + newFaceIndex = 2; // Y+ + } + else if (faceIndex == 1) { // X- + newFaceIndex = 2; // Y+ + } + else if (faceIndex == 2) { // Y+ + newFaceIndex = 5; // Z- + } + else if (faceIndex == 3) { // Y- + newFaceIndex = 4; // Z+ + } + else if (faceIndex == 4) { // Z+ + newFaceIndex = 2; // Y+ + } + else { // Z- + newFaceIndex = 2; // Y+ + } + uv = vec2(uv.x, 1.0 + uv.y); + } + else if (uv.y > 1.0) { + if (faceIndex == 0) { // X+ + newFaceIndex = 3; // Y- + } + else if (faceIndex == 1) { // X- + newFaceIndex = 3; // Y- + } + else if (faceIndex == 2) { // Y+ + newFaceIndex = 4; // Z+ + } + else if (faceIndex == 3) { // Y- + newFaceIndex = 5; // Z- + } + else if (faceIndex == 4) { // Z+ + newFaceIndex = 3; // Y- + } + else { // Z- + newFaceIndex = 3; // Y- + } + uv = vec2(uv.x, 1.0 - uv.y); + } else { + newFaceIndex = faceIndex; + } + // cover corner cases too + return uv; +} + +float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float bias, const vec2 lightProj, const vec3 n, const int index) { + const vec2 smSize = smSizeUniform; // TODO: incorrect... + const float compare = lpToDepth(lp, lightProj) - bias * 1.5; + ml = ml + n * bias * 20; + + int faceIndex = 0; + const int lightIndex = index * 6; + const vec2 uv = sampleCube(ml, faceIndex); + + vec4 pointLightTile = pointLightDataArray[lightIndex + faceIndex]; // x: tile X offset, y: tile Y offset, z: tile size relative to atlas + float result = texture(shadowMap, vec3(pointLightTile.z * uv + pointLightTile.xy, compare)); + // soft shadowing + int newFaceIndex = 0; + vec2 uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(-1.0, 0.0) / smSize))); + pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; + result += texture(shadowMap, vec3(pointLightTile.z * uvtiled + pointLightTile.xy, compare)); + + uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(-1.0, 1.0) / smSize))); + pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; + result += texture(shadowMap, vec3(pointLightTile.z * uvtiled + pointLightTile.xy, compare)); + + uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(0.0, -1.0) / smSize))); + pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; + result += texture(shadowMap, vec3(pointLightTile.z * uvtiled + pointLightTile.xy, compare)); + + uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(-1.0, -1.0) / smSize))); + pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; + result += texture(shadowMap, vec3(pointLightTile.z * uvtiled + pointLightTile.xy, compare)); + + uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(0.0, 1.0) / smSize))); + pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; + result += texture(shadowMap, vec3(pointLightTile.z * uvtiled + pointLightTile.xy, compare)); + + uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(1.0, -1.0) / smSize))); + pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; + result += texture(shadowMap, vec3(pointLightTile.z * uvtiled + pointLightTile.xy, compare)); + + uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(1.0, 0.0) / smSize))); + pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; + result += texture(shadowMap, vec3(pointLightTile.z * uvtiled + pointLightTile.xy, compare)); + + uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(1.0, 1.0) / smSize))); + pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; + result += texture(shadowMap, vec3(pointLightTile.z * uvtiled + pointLightTile.xy, compare)); + + return result / 9.0; +} +#endif + float shadowTest(sampler2DShadow shadowMap, const vec3 lPos, const float shadowsBias) { #ifdef _SMSizeUniform vec2 smSize = smSizeUniform; @@ -95,7 +272,7 @@ mat4 getCascadeMat(const float d, out int casi, out int casIndex) { float shadowTestCascade(sampler2DShadow shadowMap, const vec3 eye, const vec3 p, const float shadowsBias) { #ifdef _SMSizeUniform - vec2 smSize = smSizeUniform * vec2(shadowmapCascades, 1.0); + vec2 smSize = smSizeUniform; #else const vec2 smSize = shadowmapSize * vec2(shadowmapCascades, 1.0); #endif diff --git a/Shaders/volumetric_light/volumetric_light.frag.glsl b/Shaders/volumetric_light/volumetric_light.frag.glsl index 84ca0a90..e1306b06 100644 --- a/Shaders/volumetric_light/volumetric_light.frag.glsl +++ b/Shaders/volumetric_light/volumetric_light.frag.glsl @@ -24,7 +24,7 @@ uniform vec2 cameraPlane; #ifdef _SinglePoint #ifdef _Spot uniform sampler2DShadow shadowMapSpot[1]; - uniform mat4 LWVPSpot0; + uniform mat4 LWVPSpot[1]; #else uniform samplerCubeShadow shadowMapPoint[1]; uniform vec2 lightProj; @@ -35,10 +35,7 @@ uniform vec2 cameraPlane; uniform vec2 lightProj; #ifdef _Spot uniform sampler2DShadow shadowMapSpot[4]; - uniform mat4 LWVPSpot0; - uniform mat4 LWVPSpot1; - uniform mat4 LWVPSpot2; - uniform mat4 LWVPSpot3; + uniform mat4 LWVPSpot[maxLightsCluster]; #endif #endif #endif @@ -103,7 +100,7 @@ void rayStep(inout vec3 curPos, inout float curOpticalDepth, inout float scatter #ifdef _SinglePoint #ifdef _Spot - vec4 lPos = LWVPSpot0 * vec4(curPos, 1.0); + vec4 lPos = LWVPSpot[0] * vec4(curPos, 1.0); visibility = shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, pointBias); float spotEffect = dot(spotDir, normalize(pointPos - curPos)); // lightDir if (spotEffect < spotData.x) { // x - cutoff, y - cutoff - exponent diff --git a/Shaders/volumetric_light/volumetric_light.json b/Shaders/volumetric_light/volumetric_light.json index 06724e62..a8e96a56 100755 --- a/Shaders/volumetric_light/volumetric_light.json +++ b/Shaders/volumetric_light/volumetric_light.json @@ -108,23 +108,32 @@ "ifdef": ["_SinglePoint", "_Spot"] }, { - "name": "LWVPSpot0", + "name": "LWVPSpotArray", + "link": "_biasLightWorldViewProjectionMatrixSpotArray", + "ifdef": ["_Clusters", "_ShadowMap", "_Spot"] + }, + { + "name": "LWVPSpot[0]", "link": "_biasLightWorldViewProjectionMatrixSpot0", + "ifndef": ["_ShadowMapAtlas"], "ifdef": ["_Spot", "_ShadowMap"] }, { - "name": "LWVPSpot1", + "name": "LWVPSpot[1]", "link": "_biasLightWorldViewProjectionMatrixSpot1", + "ifndef": ["_ShadowMapAtlas"], "ifdef": ["_Spot", "_ShadowMap"] }, { - "name": "LWVPSpot2", + "name": "LWVPSpot[2]", "link": "_biasLightWorldViewProjectionMatrixSpot2", + "ifndef": ["_ShadowMapAtlas"], "ifdef": ["_Spot", "_ShadowMap"] }, { - "name": "LWVPSpot3", + "name": "LWVPSpot[3]", "link": "_biasLightWorldViewProjectionMatrixSpot3", + "ifndef": ["_ShadowMapAtlas"], "ifdef": ["_Spot", "_ShadowMap"] } ], diff --git a/Sources/armory/renderpath/Inc.hx b/Sources/armory/renderpath/Inc.hx index 9c57929a..940732a3 100644 --- a/Sources/armory/renderpath/Inc.hx +++ b/Sources/armory/renderpath/Inc.hx @@ -1,6 +1,7 @@ package armory.renderpath; import iron.RenderPath; +import iron.object.LightObject; class Inc { @@ -39,6 +40,171 @@ class Inc { #end } + #if arm_shadowmap_atlas + public static function updatePointLightAtlasData(): Void { + var atlas = ShadowMapAtlas.shadowMapAtlases.get(ShadowMapAtlas.shadowMapAtlasName("point")); + if (atlas != null) { + if(LightObject.pointLightsData == null) { + LightObject.pointLightsData = new kha.arrays.Float32Array( + LightObject.maxLightsCluster * ShadowMapTile.tilesLightType("point") * 4 ); // max possible visible lights * 6 or 2 (faces) * 4 (xyzw) + } + + var n = iron.Scene.active.lights.length > LightObject.maxLightsCluster ? LightObject.maxLightsCluster : iron.Scene.active.lights.length; + var i = 0; + var j = 0; + for (light in iron.Scene.active.lights) { + if (i >= n) + break; + if (LightObject.discardLightCulled(light)) continue; + if (light.data.raw.type == "point") { + for(k in 0...light.tileOffsetX.length) { + LightObject.pointLightsData[j ] = light.tileOffsetX[k]; // posx + LightObject.pointLightsData[j + 1] = light.tileOffsetY[k]; // posy + LightObject.pointLightsData[j + 2] = light.tileScale[k]; // tile scale factor relative to atlas + LightObject.pointLightsData[j + 3] = 0; // padding + j += 4; + } + } + i++; + } + } + } + + public static function bindShadowMapAtlas() { + for (atlas in ShadowMapAtlas.shadowMapAtlases) { + path.bindTarget(atlas.target, atlas.target); + } + } + + static function getShadowMapAtlas(atlas:ShadowMapAtlas):String { + inline function createDepthTarget(name: String, size: Int) { + var t = new RenderTargetRaw(); + t.name = name; + t.width = t.height = size; + t.format = "DEPTH16"; + return path.createRenderTarget(t); + } + + var rt = path.renderTargets.get(atlas.target); + // Create shadowmap atlas texture on the fly and replace existing on size change + if (rt == null) { + rt = createDepthTarget(atlas.target, atlas.sizew); + } + else if (atlas.updateRenderTarget) { + atlas.updateRenderTarget = false; + // Resize shadow map + rt.unload(); + rt = createDepthTarget(atlas.target, atlas.sizew); + } + return atlas.target; + } + + public static function drawShadowMapAtlas() { + #if rp_shadowmap + #if rp_probes + // Share shadow map with probe + if (lastFrame == RenderPath.active.frame) + return; + lastFrame = RenderPath.active.frame; + #end + // add new lights to the atlases + for (light in iron.Scene.active.lights) { + if (!light.lightInAtlas && !light.culledLight && light.visible && light.shadowMapScale > 0.0 + && light.data.raw.strength > 0.0 && light.data.raw.cast_shadow) { + light.lightInAtlas = ShadowMapAtlas.addLight(light); + } + } + // update point light data before rendering + updatePointLightAtlasData(); + + for (atlas in ShadowMapAtlas.shadowMapAtlases) { + var tilesToRemove = []; + #if arm_shadowmap_atlas_lod + var tilesToChangeSize = []; + #end + + var shadowmap = getShadowMapAtlas(atlas); + path.setTargetStream(shadowmap); + path.clearTarget(null, 1.0); + + for (tile in atlas.activeTiles) { + if (tile.light == null || !tile.light.visible || tile.light.culledLight + || !tile.light.data.raw.cast_shadow || tile.light.data.raw.strength == 0) { + tile.unlockLight = true; + tilesToRemove.push(tile); + continue; + } + + #if arm_shadowmap_atlas_lod + var newTileSize = atlas.getTileSize(tile.light.shadowMapScale); + if (newTileSize != tile.size) { + if (newTileSize == 0) { + tile.unlockLight = true; + tilesToRemove.push(tile); + continue; + } + // queue for size change + tile.newTileSize = newTileSize; + tilesToChangeSize.push(tile); + } + #end + // set the tile offset for this tile and every linked tile to this one + var j = 0; + tile.forEachTileLinked(function (lTile) { + tile.light.tileOffsetX[j] = (lTile.coordsX == 0) ? 0.0 : lTile.coordsX / atlas.sizew; + tile.light.tileOffsetY[j] = (lTile.coordsY == 0) ? 0.0 : lTile.coordsY / atlas.sizew; + tile.light.tileScale[j] = lTile.size / atlas.sizew; + j++; + }); + // set shadowmap size for uniform + tile.light.data.raw.shadowmap_size = atlas.sizew; + + path.light = tile.light; + + var face = 0; + var faces = ShadowMapTile.tilesLightType(tile.light.data.raw.type); + + tile.forEachTileLinked(function (lTile) { + if (faces > 1) { + #if arm_csm + switch (tile.light.data.raw.type) { + case "sun": tile.light.setCascade(iron.Scene.active.camera, face); + case "point": path.currentFace = face; + } + #else + path.currentFace = face; + #end + face++; + } + path.setCurrentViewportWithOffset(lTile.size, lTile.size, lTile.coordsX, lTile.coordsY); + + path.drawMeshesStream("shadowmap"); + }); + + path.currentFace = -1; + } + path.endStream(); + + #if arm_shadowmap_atlas_lod + for (tile in tilesToChangeSize) { + tilesToRemove.push(tile); + + var newTile = ShadowMapTile.assignTiles(tile.light, atlas, tile); + if (newTile != null) + atlas.activeTiles.push(newTile); + } + // update point light data after changing size of tiles to avoid render issues + updatePointLightAtlasData(); + #end + + for (tile in tilesToRemove) { + atlas.activeTiles.remove(tile); + tile.freeTile(); + } + } + #end // rp_shadowmap + } + #else public static function bindShadowMap() { for (l in iron.Scene.active.lights) { if (!l.visible || l.data.raw.type != "sun") continue; @@ -56,10 +222,15 @@ class Inc { } } - static function shadowMapName(l: iron.object.LightObject): String { - if (l.data.raw.type == "sun") return "shadowMap"; - if (l.data.raw.type == "point") return "shadowMapPoint[" + pointIndex + "]"; - else return "shadowMapSpot[" + spotIndex + "]"; + static function shadowMapName(light: LightObject): String { + switch (light.data.raw.type) { + case "sun": + return "shadowMap"; + case "point": + return "shadowMapPoint[" + pointIndex + "]"; + default: + return "shadowMapSpot[" + spotIndex + "]"; + } } static function getShadowMap(l: iron.object.LightObject): String { @@ -130,6 +301,7 @@ class Inc { #end // rp_shadowmap } + #end public static function applyConfig() { #if arm_config @@ -203,7 +375,11 @@ class Inc { path.setTarget("accum", ["revealage"]); #if rp_shadowmap { + #if arm_shadowmap_atlas + bindShadowMapAtlas(); + #else bindShadowMap(); + #end } #end path.drawMeshes("translucent"); @@ -342,3 +518,490 @@ class Inc { #end } } + +#if arm_shadowmap_atlas +class ShadowMapAtlas { + + public var target: String; + public var baseTileSizeConst: Int; + public var maxAtlasSizeConst: Int; + + public var sizew: Int; + public var sizeh: Int; + + public var currTileOffset = 0; + public var tiles: Array = []; + public var activeTiles: Array = []; + public var depth = 1; + #if arm_shadowmap_atlas_lod + static var tileSizes: Array = []; + static var tileSizeFactor: Array = []; + #end + public var updateRenderTarget = false; + public static var shadowMapAtlases:Map = new Map(); // map a shadowmap atlas to their light type + + function new(light: LightObject) { + + var maxTileSize = shadowMapAtlasSize(light); + this.target = shadowMapAtlasName(light.data.raw.type); + this.sizew = this.sizeh = this.baseTileSizeConst = maxTileSize; + this.depth = getSubdivisions(); + this.maxAtlasSizeConst = getMaxAtlasSize(light.data.raw.type); + + #if arm_shadowmap_atlas_lod + if (tileSizes.length == 0) + computeTileSizes(maxTileSize, depth); + #end + + } + + /** + * Adds a light to an atlas. The atlas is decided based on the type of the light + * @param light of type LightObject to be added to an yatlas + * @return if the light was added succesfully + */ + public static function addLight(light: LightObject): Bool { + // check if light can be added based on culling + if (light.culledLight || light.shadowMapScale == 0.0) + return false; + + var atlasName = shadowMapAtlasName(light.data.raw.type); + var atlas = shadowMapAtlases.get(atlasName); + if (atlas == null) { + // create a new atlas + atlas = new ShadowMapAtlas(light); + shadowMapAtlases.set(atlasName, atlas); + } + + // find a free tile for this light + var mainTile = ShadowMapTile.assignTiles(light, atlas, null); + if (mainTile == null) + return false; + // push main tile to active tiles + atlas.activeTiles.push(mainTile); + return true; + } + + static inline function shadowMapAtlasSize(light:LightObject):Int { + // TODO: this can break because we are changing shadowmap_size elsewhere. + return light.data.raw.shadowmap_size; + } + + public function getTileSize(shadowMapScale: Float): Int { + #if arm_shadowmap_atlas_lod + // find the first scale factor that is smaller to the shadowmap scale, and then return the previous one. + var i = 0; + for (sizeFactor in tileSizeFactor) { + if (sizeFactor < shadowMapScale) break; + i++; + } + return tileSizes[i - 1]; + #else + return this.baseTileSizeConst; + #end + } + + #if arm_shadowmap_atlas_lod + static function computeTileSizes(maxTileSize: Int, depth: Int): Void { + // find the highest value based on the calculation done in the cluster code + var base = LightObject.zToShadowMapScale(0, 16); + var subdiv = base / depth; + for(i in 0...depth){ + tileSizes.push(Std.int(maxTileSize / Math.pow(2, i))); + tileSizeFactor.push(base); + base -= subdiv; + } + tileSizes.push(0); + tileSizeFactor.push(0.0); + } + #end + + public inline function atlasLimitReached() { + // asume square atlas + return (currTileOffset + 1) * baseTileSizeConst > maxAtlasSizeConst; + } + + public static inline function shadowMapAtlasName(type: String): String { + #if arm_shadowmap_atlas_single_map + return "shadowMapAtlas"; + #else + switch (type) { + case "point": + return "shadowMapAtlasPoint"; + case "sun": + return "shadowMapAtlasSun"; + default: + return "shadowMapAtlasSpot"; + } + #end + } + + public static inline function getSubdivisions(): Int { + #if (rp_shadowmap_atlas_lod_subdivisions == 2) + return 2; + #elseif (rp_shadowmap_atlas_lod_subdivisions == 3) + return 3; + #elseif (rp_shadowmap_atlas_lod_subdivisions == 4) + return 4; + #elseif (rp_shadowmap_atlas_lod_subdivisions == 5) + return 5; + #elseif (rp_shadowmap_atlas_lod_subdivisions == 6) + return 6; + #elseif (rp_shadowmap_atlas_lod_subdivisions == 7) + return 7; + #elseif (rp_shadowmap_atlas_lod_subdivisions == 8) + return 8; + #elseif (!arm_shadowmap_atlas_lod) + return 1; + #end + } + + public static inline function getMaxAtlasSize(type: String): Int { + #if arm_shadowmap_atlas_single_map + #if (rp_shadowmap_atlas_max_size == 1024) + return 1024; + #elseif (rp_shadowmap_atlas_max_size == 2048) + return 2048; + #elseif (rp_shadowmap_atlas_max_size == 4096) + return 4096; + #elseif (rp_shadowmap_atlas_max_size == 8192) + return 8192; + #elseif (rp_shadowmap_atlas_max_size == 16384) + return 16384; + #end + #else + switch (type) { + case "point": { + #if (rp_shadowmap_atlas_max_size_point == 1024) + return 1024; + #elseif (rp_shadowmap_atlas_max_size_point == 2048) + return 2048; + #elseif (rp_shadowmap_atlas_max_size_point == 4096) + return 4096; + #elseif (rp_shadowmap_atlas_max_size_point == 8192) + return 8192; + #elseif (rp_shadowmap_atlas_max_size_point == 16384) + return 16384; + #end + } + case "spot": { + #if (rp_shadowmap_atlas_max_size_spot == 1024) + return 1024; + #elseif (rp_shadowmap_atlas_max_size_spot == 2048) + return 2048; + #elseif (rp_shadowmap_atlas_max_size_spot == 4096) + return 4096; + #elseif (rp_shadowmap_atlas_max_size_spot == 8192) + return 8192; + #elseif (rp_shadowmap_atlas_max_size_spot == 16384) + return 16384; + #end + } + case "sun": { + #if (rp_shadowmap_atlas_max_size_sun == 1024) + return 1024; + #elseif (rp_shadowmap_atlas_max_size_sun == 2048) + return 2048; + #elseif (rp_shadowmap_atlas_max_size_sun == 4096) + return 4096; + #elseif (rp_shadowmap_atlas_max_size_sun == 8192) + return 8192; + #elseif (rp_shadowmap_atlas_max_size_sun == 16384) + return 16384; + #end + } + default: { + #if (rp_shadowmap_atlas_max_size == 1024) + return 1024; + #elseif (rp_shadowmap_atlas_max_size == 2048) + return 2048; + #elseif (rp_shadowmap_atlas_max_size == 4096) + return 4096; + #elseif (rp_shadowmap_atlas_max_size == 8192) + return 8192; + #elseif (rp_shadowmap_atlas_max_size == 16384) + return 16384; + #end + } + + } + #end + } +} + +class ShadowMapTile { + + public var light:Null = null; + public var coordsX:Int; + public var coordsY:Int; + public var size:Int; + public var tiles:Array = []; + public var linkedTile:ShadowMapTile = null; + + #if arm_shadowmap_atlas_lod + public var parentTile: ShadowMapTile = null; + public var activeSubTiles: Int = 0; + public var newTileSize: Int = -1; + + static var tilePattern = [[0, 0], [1, 0], [0, 1], [1, 1]]; + #end + + function new(coordsX: Int, coordsY: Int, size: Int) { + this.coordsX = coordsX; + this.coordsY = coordsY; + this.size = size; + } + + public static function assignTiles(light: LightObject, atlas: ShadowMapAtlas, oldTile: ShadowMapTile): ShadowMapTile { + var tileSize = 0; + + #if arm_shadowmap_atlas_lod + if (oldTile != null && oldTile.newTileSize != -1) { + // reuse tilesize instead of computing it again + tileSize = oldTile.newTileSize; + oldTile.newTileSize = -1; + } + else + #end + tileSize = atlas.getTileSize(light.shadowMapScale); + + if (tileSize == 0) + return null; + + var tiles = []; + tiles = findCreateTiles(light, oldTile, atlas, tilesLightType(light.data.raw.type), tileSize); + + // lock new tiles with light + for (tile in tiles) + tile.lockTile(light); + + return linkTiles(tiles); + } + + static inline function linkTiles(tiles: Array): ShadowMapTile { + if (tiles.length > 1) { + var linkedTile = tiles[0]; + for (i in 1...tiles.length) { + linkedTile.linkedTile = tiles[i]; + linkedTile = tiles[i]; + } + } + return tiles[0]; + } + + static inline function findCreateTiles(light: LightObject, oldTile: ShadowMapTile, atlas: ShadowMapAtlas, tilesPerLightType: Int, tileSize: Int): Array { + var tilesFound: Array = []; + + var updateAtlas = false; + while (tilesFound.length < tilesPerLightType) { + findTiles(light, oldTile, atlas.tiles, tileSize, tilesPerLightType, tilesFound); + + if (tilesFound.length < tilesPerLightType) { + tilesFound = []; // empty tilesFound + // skip creating more tiles if limit has been reached + if (atlas.atlasLimitReached()) + break; + + createTiles(atlas.tiles, atlas.baseTileSizeConst, atlas.depth, atlas.currTileOffset, atlas.currTileOffset); + atlas.currTileOffset++; + // update texture to accomodate new size + atlas.updateRenderTarget = true; + atlas.sizew = atlas.sizeh = atlas.currTileOffset * atlas.baseTileSizeConst; + } + } + return tilesFound; + } + + inline static function findTiles(light:LightObject, oldTile: ShadowMapTile, + tiles: Array, size: Int, tilesCount: Int, tilesFound: Array): Void { + #if arm_shadowmap_atlas_lod + if (oldTile != null) { + // reuse children tiles + if (size < oldTile.size) { + oldTile.forEachTileLinked(function(lTile) { + var childTile = findFreeChildTile(lTile, size); + tilesFound.push(childTile); + }); + } + // reuse parent tiles + else { + oldTile.forEachTileLinked(function(lTile) { + // find out if parents tiles are not occupied + var parentTile = findFreeParentTile(lTile, size); + // if parent is free, add it to found tiles + if (parentTile != null) + tilesFound.push(parentTile); + }); + if (tilesFound.length < tilesCount) { + // find naively the rest of the tiles that couldn't be reused + findTilesNaive(light, tiles, size, tilesCount, tilesFound); + } + } + } + else + #end + findTilesNaive(light, tiles, size, tilesCount, tilesFound); + } + + #if arm_shadowmap_atlas_lod + static inline function findFreeChildTile(tile: ShadowMapTile, size: Int): ShadowMapTile { + var childrenTile = tile; + while (size < childrenTile.size) { + childrenTile = childrenTile.tiles[0]; + } + return childrenTile; + } + + static inline function findFreeParentTile(tile: ShadowMapTile, size: Int): ShadowMapTile { + var parentTile = tile; + while (size > parentTile.size) { + parentTile = parentTile.parentTile; + // stop if parent tile is occupied + if (parentTile.activeSubTiles > 1) { + parentTile = null; + break; + } + } + return parentTile; + } + #end + + static function findTilesNaive(light:LightObject, tiles: Array, size: Int, tilesCount: Int, tilesFound: Array): Void { + for (tile in tiles) { + if (tile.size == size) { + if (tile.light == null #if arm_shadowmap_atlas_lod && tile.activeSubTiles == 0 #end) { + tilesFound.push(tile); + // stop after finding enough tiles + if (tilesFound.length == tilesCount) + return; + } + } + else { + // skip over if end of the tree or tile is occupied + if (tile.tiles.length == 0 || tile.light != null) + continue; + findTilesNaive(light, tile.tiles, size, tilesCount, tilesFound); + // skip iterating over the rest of the tiles if found enough + if (tilesFound.length == tilesCount) + return; + } + } + } + + // create a basic tile and subdivide it if needed + public static function createTiles(tiles:Array, size:Int, depth: Int, baseX:Int, baseY:Int) { + var i = baseX; + var j = 0; + var lastTile = tiles.length; + // assume occupied tiles start from 1 line before the base x + var occupiedTiles = baseX - 1; + + while (i >= 0) { + if (i <= occupiedTiles) { // avoid overriding tiles + j = baseY; + } + while (j <= baseY) { + // create base tile of max-size + tiles.push(new ShadowMapTile(size * i, size * j, size)); + #if arm_shadowmap_atlas_lod + tiles[lastTile].tiles = subDivTile(tiles[lastTile], size, size * i, size * j, depth - 1); + #end + lastTile++; + j++; + } + i--; + } + } + + #if arm_shadowmap_atlas_lod + static function subDivTile(parent: ShadowMapTile, size: Int, baseCoordsX: Int, baseCoordsY: Int, depth: Int): Array { + var tileSize = Std.int(size / 2); + + var tiles = []; + + for (i in 0...4) { + var coordsX = baseCoordsX + tilePattern[i][0] * tileSize; + var coordsY = baseCoordsY + tilePattern[i][1] * tileSize; + + var tile = new ShadowMapTile(coordsX, coordsY, tileSize); + tile.parentTile = parent; + + if (depth > 1) + tile.tiles = subDivTile(tile, tileSize, coordsX, coordsY, depth - 1); + tiles.push(tile); + } + + return tiles; + } + #end + + public static inline function tilesLightType(type: String): Int { + switch (type) { + case "sun": + return LightObject.cascadeCount; + case "point": + return 6; + default: + return 1; + } + } + + inline function lockTile(light: LightObject): Void { + if (this.light != null) + return; + this.light = light; + #if arm_shadowmap_atlas_lod + // update the count of used tiles for parents + this.forEachParentTile(function (pTile) { + pTile.activeSubTiles++; + }); + #end + } + + public var unlockLight: Bool = false; + + public function freeTile(): Void { + // prevent duplicates + if (light != null && unlockLight) { + light.lightInAtlas = false; + unlockLight = false; + } + + var linkedTile = this; + var tempTile = this; + while (linkedTile != null) { + linkedTile.light = null; + #if arm_shadowmap_atlas_lod + // update the count of used tiles for parents + linkedTile.forEachParentTile(function (pTile) { + if (pTile.activeSubTiles > 0) + pTile.activeSubTiles--; + }); + #end + + linkedTile = linkedTile.linkedTile; + // unlink linked tiles + tempTile.linkedTile = null; + tempTile = linkedTile; + } + } + + public inline function forEachTileLinked(action: ShadowMapTile->Void): Void { + var linkedTile = this; + while (linkedTile != null) { + action(linkedTile); + linkedTile = linkedTile.linkedTile; + } + } + + #if arm_shadowmap_atlas_lod + public inline function forEachParentTile(action: ShadowMapTile->Void): Void { + var parentTile = this.parentTile; + while (parentTile != null) { + action(parentTile); + parentTile = parentTile.parentTile; + } + } + #end +} +#end diff --git a/Sources/armory/renderpath/RenderPathCreator.hx b/Sources/armory/renderpath/RenderPathCreator.hx index cc676369..5b97a69b 100644 --- a/Sources/armory/renderpath/RenderPathCreator.hx +++ b/Sources/armory/renderpath/RenderPathCreator.hx @@ -7,6 +7,8 @@ class RenderPathCreator { public static var path: RenderPath; + public static var commands: Void->Void = function() {}; + #if (rp_renderer == "Forward") public static var setTargetMeshes: Void->Void = RenderPathForward.setTargetMeshes; public static var drawMeshes: Void->Void = RenderPathForward.drawMeshes; @@ -33,13 +35,22 @@ class RenderPathCreator { #if (rp_renderer == "Forward") RenderPathForward.init(path); - path.commands = RenderPathForward.commands; + path.commands = function() { + RenderPathForward.commands(); + commands(); + } #elseif (rp_renderer == "Deferred") RenderPathDeferred.init(path); - path.commands = RenderPathDeferred.commands; + path.commands = function() { + RenderPathDeferred.commands(); + commands(); + } #elseif (rp_renderer == "Raytracer") RenderPathRaytracer.init(path); - path.commands = RenderPathRaytracer.commands; + path.commands = function() { + RenderPathRaytracer.commands(); + commands(); + } #end return path; } diff --git a/Sources/armory/renderpath/RenderPathDeferred.hx b/Sources/armory/renderpath/RenderPathDeferred.hx index 8f263349..302cbace 100644 --- a/Sources/armory/renderpath/RenderPathDeferred.hx +++ b/Sources/armory/renderpath/RenderPathDeferred.hx @@ -502,8 +502,13 @@ class RenderPathDeferred { #end #if (rp_shadowmap) + // atlasing is exclusive for now + #if arm_shadowmap_atlas + Inc.drawShadowMapAtlas(); + #else Inc.drawShadowMap(); #end + #end // Voxels #if rp_voxelao @@ -572,7 +577,11 @@ class RenderPathDeferred { #if rp_shadowmap { + #if arm_shadowmap_atlas + Inc.bindShadowMapAtlas(); + #else Inc.bindShadowMap(); + #end } #end @@ -624,7 +633,11 @@ class RenderPathDeferred { { path.setTarget("singlea"); path.bindTarget("_main", "gbufferD"); + #if arm_shadowmap_atlas + Inc.bindShadowMapAtlas(); + #else Inc.bindShadowMap(); + #end path.drawShader("shader_datas/volumetric_light/volumetric_light"); path.setTarget("singleb"); diff --git a/Sources/armory/renderpath/RenderPathForward.hx b/Sources/armory/renderpath/RenderPathForward.hx index 4b2724ab..a72fbfd8 100644 --- a/Sources/armory/renderpath/RenderPathForward.hx +++ b/Sources/armory/renderpath/RenderPathForward.hx @@ -300,7 +300,11 @@ class RenderPathForward { #if rp_shadowmap { + #if arm_shadowmap_atlas + Inc.drawShadowMapAtlas(); + #else Inc.drawShadowMap(); + #end } #end @@ -352,7 +356,11 @@ class RenderPathForward { #if rp_shadowmap { + #if arm_shadowmap_atlas + Inc.bindShadowMapAtlas(); + #else Inc.bindShadowMap(); + #end } #end @@ -466,7 +474,11 @@ class RenderPathForward { { path.setTarget("singlea"); path.bindTarget("_main", "gbufferD"); + #if arm_shadowmap_atlas + Inc.bindShadowMapAtlas(); + #else Inc.bindShadowMap(); + #end path.drawShader("shader_datas/volumetric_light/volumetric_light"); path.setTarget("singleb"); diff --git a/Sources/armory/trait/internal/DebugConsole.hx b/Sources/armory/trait/internal/DebugConsole.hx index 2bb6bfa0..03096bb3 100755 --- a/Sources/armory/trait/internal/DebugConsole.hx +++ b/Sources/armory/trait/internal/DebugConsole.hx @@ -421,6 +421,9 @@ class DebugConsole extends Trait { var lightHandle = Id.handle(); lightHandle.value = light.data.raw.strength / 10; light.data.raw.strength = ui.slider(lightHandle, "Strength", 0.0, 5.0, true) * 10; + #if arm_shadowmap_atlas + ui.text("status: " + (light.culledLight ? "culled" : "rendered")); + #end } else if (Std.is(selectedObject, iron.object.CameraObject)) { selectedType = "(Camera)"; diff --git a/blender/arm/make_renderpath.py b/blender/arm/make_renderpath.py index bcd1716b..81c00161 100755 --- a/blender/arm/make_renderpath.py +++ b/blender/arm/make_renderpath.py @@ -43,6 +43,22 @@ def add_world_defs(): if rpdat.rp_shadowmap_cascades != '1': wrd.world_defs += '_CSM' assets.add_khafile_def('arm_csm') + if rpdat.rp_shadowmap_atlas: + assets.add_khafile_def('arm_shadowmap_atlas') + wrd.world_defs += '_ShadowMapAtlas' + if rpdat.rp_shadowmap_atlas_single_map: + assets.add_khafile_def('arm_shadowmap_atlas_single_map') + wrd.world_defs += '_SingleAtlas' + assets.add_khafile_def('rp_shadowmap_atlas_max_size_point={0}'.format(int(rpdat.rp_shadowmap_atlas_max_size_point))) + assets.add_khafile_def('rp_shadowmap_atlas_max_size_spot={0}'.format(int(rpdat.rp_shadowmap_atlas_max_size_spot))) + assets.add_khafile_def('rp_shadowmap_atlas_max_size_sun={0}'.format(int(rpdat.rp_shadowmap_atlas_max_size_sun))) + assets.add_khafile_def('rp_shadowmap_atlas_max_size={0}'.format(int(rpdat.rp_shadowmap_atlas_max_size))) + + assets.add_khafile_def('rp_max_lights_cluster={0}'.format(int(rpdat.rp_max_lights_cluster))) + assets.add_khafile_def('rp_max_lights={0}'.format(int(rpdat.rp_max_lights))) + if rpdat.rp_shadowmap_atlas_lod: + assets.add_khafile_def('arm_shadowmap_atlas_lod') + assets.add_khafile_def('rp_shadowmap_atlas_lod_subdivisions={0}'.format(int(rpdat.rp_shadowmap_atlas_lod_subdivisions))) # SS if rpdat.rp_ssgi == 'RTGI' or rpdat.rp_ssgi == 'RTAO': if rpdat.rp_ssgi == 'RTGI': @@ -89,9 +105,14 @@ def add_world_defs(): wrd.world_defs += '_Spot' assets.add_khafile_def('arm_spot') - if point_lights == 1: - wrd.world_defs += '_SinglePoint' - elif point_lights > 1: + if not rpdat.rp_shadowmap_atlas: + if point_lights == 1: + wrd.world_defs += '_SinglePoint' + elif point_lights > 1: + wrd.world_defs += '_Clusters' + assets.add_khafile_def('arm_clusters') + else: + wrd.world_defs += '_SMSizeUniform' wrd.world_defs += '_Clusters' assets.add_khafile_def('arm_clusters') diff --git a/blender/arm/material/make_cluster.py b/blender/arm/material/make_cluster.py index 6864386d..34589d42 100644 --- a/blender/arm/material/make_cluster.py +++ b/blender/arm/material/make_cluster.py @@ -3,8 +3,10 @@ import bpy def write(vert, frag): wrd = bpy.data.worlds['Arm'] is_shadows = '_ShadowMap' in wrd.world_defs + is_shadows_atlas = '_ShadowMapAtlas' in wrd.world_defs + is_single_atlas = is_shadows_atlas and '_SingleAtlas' in wrd.world_defs - frag.add_include('std/clusters.glsl') + frag.add_include_front('std/clusters.glsl') frag.add_uniform('vec2 cameraProj', link='_cameraPlaneProj') frag.add_uniform('vec2 cameraPlane', link='_cameraPlane') frag.add_uniform('vec4 lightsArray[maxLights * 2]', link='_lightsArray') @@ -12,7 +14,12 @@ def write(vert, frag): if is_shadows: frag.add_uniform('bool receiveShadow') frag.add_uniform('vec2 lightProj', link='_lightPlaneProj', included=True) - frag.add_uniform('samplerCubeShadow shadowMapPoint[4]', included=True) + if is_shadows_atlas: + if not is_single_atlas: + frag.add_uniform('sampler2DShadow shadowMapAtlasPoint', included=True) + frag.add_uniform('vec4 pointLightDataArray[maxLightsCluster]', link='_pointLightsAtlasArray', included=True) + else: + frag.add_uniform('samplerCubeShadow shadowMapPoint[4]', included=True) vert.add_out('vec4 wvpposition') vert.write('wvpposition = gl_Position;') # wvpposition.z / wvpposition.w @@ -29,11 +36,12 @@ def write(vert, frag): frag.write('int numSpots = int(texelFetch(clustersData, ivec2(clusterI, 1 + maxLightsCluster), 0).r * 255);') frag.write('int numPoints = numLights - numSpots;') if is_shadows: - frag.add_uniform('mat4 LWVPSpot0', link='_biasLightWorldViewProjectionMatrixSpot0', included=True) - frag.add_uniform('mat4 LWVPSpot1', link='_biasLightWorldViewProjectionMatrixSpot1', included=True) - frag.add_uniform('mat4 LWVPSpot2', link='_biasLightWorldViewProjectionMatrixSpot2', included=True) - frag.add_uniform('mat4 LWVPSpot3', link='_biasLightWorldViewProjectionMatrixSpot3', included=True) - frag.add_uniform('sampler2DShadow shadowMapSpot[4]', included=True) + if is_shadows_atlas and not is_single_atlas: + frag.add_uniform(f'sampler2DShadow shadowMapAtlasSpot', included=True) + elif not is_shadows_atlas: + frag.add_uniform('sampler2DShadow shadowMapSpot[4]', included=True) + # FIXME: type is actually mat4, but otherwise it will not be set as floats when writing the shaders' json files + frag.add_uniform('vec4 LWVPSpotArray[maxLightsCluster]', link='_biasLightWorldViewProjectionMatrixSpotArray', included=True) frag.write('for (int i = 0; i < min(numLights, maxLightsCluster); i++) {') frag.write('int li = int(texelFetch(clustersData, ivec2(clusterI, i + 1), 0).r * 255);') @@ -52,7 +60,7 @@ def write(vert, frag): if is_shadows: frag.write(' , li, lightsArray[li * 2].w, receiveShadow') # bias if '_Spot' in wrd.world_defs: - frag.write(' , li > numPoints - 1') + frag.write(' , lightsArray[li * 2 + 1].w != 0.0') frag.write(' , lightsArray[li * 2 + 1].w') # cutoff frag.write(' , lightsArraySpot[li].w') # cutoff - exponent frag.write(' , lightsArraySpot[li].xyz') # spotDir diff --git a/blender/arm/material/make_mesh.py b/blender/arm/material/make_mesh.py index 1d9d1010..18608347 100644 --- a/blender/arm/material/make_mesh.py +++ b/blender/arm/material/make_mesh.py @@ -354,6 +354,12 @@ def make_forward_mobile(con_mesh): return is_shadows = '_ShadowMap' in wrd.world_defs + is_shadows_atlas = '_ShadowMapAtlas' in wrd.world_defs + is_single_atlas = is_shadows_atlas and '_SingleAtlas' in wrd.world_defs + shadowmap_sun = 'shadowMap' + if is_shadows_atlas: + shadowmap_sun = 'shadowMapAtlasSun' if not is_single_atlas else 'shadowMapAtlas' + frag.add_uniform('vec2 smSizeUniform', '_shadowMapSize', included=True) frag.write('vec3 direct = vec3(0.0);') if '_Sun' in wrd.world_defs: @@ -366,7 +372,7 @@ def make_forward_mobile(con_mesh): vert.add_uniform('mat4 LWVP', '_biasLightWorldViewProjectionMatrix') vert.write('lightPosition = LWVP * spos;') frag.add_uniform('bool receiveShadow') - frag.add_uniform('sampler2DShadow shadowMap') + frag.add_uniform(f'sampler2DShadow {shadowmap_sun}') frag.add_uniform('float shadowsBias', '_sunShadowsBias') frag.write('if (receiveShadow) {') @@ -374,14 +380,14 @@ def make_forward_mobile(con_mesh): frag.add_include('std/shadows.glsl') frag.add_uniform('vec4 casData[shadowmapCascades * 4 + 4]', '_cascadeData', included=True) frag.add_uniform('vec3 eye', '_cameraPosition') - frag.write('svisibility = shadowTestCascade(shadowMap, eye, wposition + n * shadowsBias * 10, shadowsBias);') + frag.write(f'svisibility = shadowTestCascade({shadowmap_sun}, eye, wposition + n * shadowsBias * 10, shadowsBias);') else: frag.write('if (lightPosition.w > 0.0) {') frag.write(' vec3 lPos = lightPosition.xyz / lightPosition.w;') if '_Legacy' in wrd.world_defs: - frag.write(' svisibility = float(texture(shadowMap, vec2(lPos.xy)).r > lPos.z - shadowsBias);') + frag.write(f' svisibility = float(texture({shadowmap_sun}, vec2(lPos.xy)).r > lPos.z - shadowsBias);') else: - frag.write(' svisibility = texture(shadowMap, vec3(lPos.xy, lPos.z - shadowsBias)).r;') + frag.write(f' svisibility = texture({shadowmap_sun}, vec3(lPos.xy, lPos.z - shadowsBias)).r;') frag.write('}') frag.write('}') # receiveShadow frag.write('direct += basecol * sdotNL * sunCol * svisibility;') @@ -404,8 +410,8 @@ def make_forward_mobile(con_mesh): frag.write('if (receiveShadow) {') if '_Spot' in wrd.world_defs: vert.add_out('vec4 spotPosition') - vert.add_uniform('mat4 LWVPSpot0', link='_biasLightWorldViewProjectionMatrixSpot0') - vert.write('spotPosition = LWVPSpot0 * spos;') + vert.add_uniform('mat4 LWVPSpotArray[1]', link='_biasLightWorldViewProjectionMatrixSpotArray') + vert.write('spotPosition = LWVPSpotArray[0] * spos;') frag.add_uniform('sampler2DShadow shadowMapSpot[1]') frag.write('if (spotPosition.w > 0.0) {') frag.write(' vec3 lPos = spotPosition.xyz / spotPosition.w;') @@ -532,13 +538,10 @@ def make_forward(con_mesh): frag.add_uniform('sampler2D sltcMag', '_ltcMag', included=True) if '_ShadowMap' in wrd.world_defs: if '_SinglePoint' in wrd.world_defs: - frag.add_uniform('mat4 LWVPSpot0', link='_biasLightViewProjectionMatrixSpot0', included=True) + frag.add_uniform('mat4 LWVPSpot[0]', link='_biasLightViewProjectionMatrixSpot0', included=True) frag.add_uniform('sampler2DShadow shadowMapSpot[1]', included=True) if '_Clusters' in wrd.world_defs: - frag.add_uniform('mat4 LWVPSpot0', link='_biasLightWorldViewProjectionMatrixSpot0', included=True) - frag.add_uniform('mat4 LWVPSpot1', link='_biasLightWorldViewProjectionMatrixSpot1', included=True) - frag.add_uniform('mat4 LWVPSpot2', link='_biasLightWorldViewProjectionMatrixSpot2', included=True) - frag.add_uniform('mat4 LWVPSpot3', link='_biasLightWorldViewProjectionMatrixSpot3', included=True) + frag.add_uniform('mat4 LWVPSpotArray[4]', link='_biasLightWorldViewProjectionMatrixSpotArray', included=True) frag.add_uniform('sampler2DShadow shadowMapSpot[4]', included=True) if not blend: @@ -605,6 +608,12 @@ def make_forward_base(con_mesh, parse_opacity=False, transluc_pass=False): frag.add_include('std/light.glsl') is_shadows = '_ShadowMap' in wrd.world_defs + is_shadows_atlas = '_ShadowMapAtlas' in wrd.world_defs + is_single_atlas = is_shadows_atlas and '_SingleAtlas' in wrd.world_defs + shadowmap_sun = 'shadowMap' + if is_shadows_atlas: + shadowmap_sun = 'shadowMapAtlasSun' if not is_single_atlas else 'shadowMapAtlas' + frag.add_uniform('vec2 smSizeUniform', '_shadowMapSize', included=True) frag.write('vec3 albedo = surfaceAlbedo(basecol, metallic);') frag.write('vec3 f0 = surfaceF0(basecol, metallic);') @@ -661,14 +670,14 @@ def make_forward_base(con_mesh, parse_opacity=False, transluc_pass=False): frag.write('float sdotVH = dot(vVec, sh);') if is_shadows: frag.add_uniform('bool receiveShadow') - frag.add_uniform('sampler2DShadow shadowMap') + frag.add_uniform(f'sampler2DShadow {shadowmap_sun}', top=True) frag.add_uniform('float shadowsBias', '_sunShadowsBias') frag.write('if (receiveShadow) {') if '_CSM' in wrd.world_defs: frag.add_include('std/shadows.glsl') frag.add_uniform('vec4 casData[shadowmapCascades * 4 + 4]', '_cascadeData', included=True) frag.add_uniform('vec3 eye', '_cameraPosition') - frag.write('svisibility = shadowTestCascade(shadowMap, eye, wposition + n * shadowsBias * 10, shadowsBias);') + frag.write(f'svisibility = shadowTestCascade({shadowmap_sun}, eye, wposition + n * shadowsBias * 10, shadowsBias);') else: if tese is not None: tese.add_out('vec4 lightPosition') @@ -685,7 +694,7 @@ def make_forward_base(con_mesh, parse_opacity=False, transluc_pass=False): vert.write('lightPosition = LWVP * spos;') frag.write('vec3 lPos = lightPosition.xyz / lightPosition.w;') frag.write('const vec2 smSize = shadowmapSize;') - frag.write('svisibility = PCF(shadowMap, lPos.xy, lPos.z - shadowsBias, smSize);') + frag.write(f'svisibility = PCF({shadowmap_sun}, lPos.xy, lPos.z - shadowsBias, smSize);') frag.write('}') # receiveShadow if '_VoxelShadow' in wrd.world_defs and '_VoxelAOvar' in wrd.world_defs: frag.write('svisibility *= 1.0 - traceShadow(voxels, voxpos, sunDir);') @@ -703,7 +712,7 @@ def make_forward_base(con_mesh, parse_opacity=False, transluc_pass=False): frag.add_uniform('float pointBias', link='_pointShadowsBias') if '_Spot' in wrd.world_defs: # Skip world matrix, already in world-space - frag.add_uniform('mat4 LWVPSpot0', link='_biasLightViewProjectionMatrixSpot0', included=True) + frag.add_uniform('mat4 LWVPSpot[1]', link='_biasLightViewProjectionMatrixSpotArray', included=True) frag.add_uniform('sampler2DShadow shadowMapSpot[1]', included=True) else: frag.add_uniform('vec2 lightProj', link='_lightPlaneProj', included=True) diff --git a/blender/arm/material/shader.py b/blender/arm/material/shader.py index 7b00ed6d..cecd6ebc 100644 --- a/blender/arm/material/shader.py +++ b/blender/arm/material/shader.py @@ -179,6 +179,7 @@ class Shader: self.includes = [] self.ins = [] self.outs = [] + self.uniforms_top = [] self.uniforms = [] self.constants = [] self.functions = {} @@ -205,6 +206,10 @@ class Shader: if not self.has_include(s): self.includes.append(s) + def add_include_front(self, s): + if not self.has_include(s): + self.includes.insert(0, s) + def add_in(self, s): if s not in self.ins: self.ins.append(s) @@ -213,7 +218,7 @@ class Shader: if s not in self.outs: self.outs.append(s) - def add_uniform(self, s, link=None, included=False): + def add_uniform(self, s, link=None, included=False, top=False): ar = s.split(' ') # layout(RGBA8) image3D voxels utype = ar[-2] @@ -236,8 +241,12 @@ class Shader: ar[0] = 'floats' ar[1] = ar[1].split('[', 1)[0] self.context.add_constant(ar[0], ar[1], link=link) - if not included and s not in self.uniforms: - self.uniforms.append(s) + if top: + if not included and s not in self.uniforms_top: + self.uniforms_top.append(s) + else: + if not included and s not in self.uniforms: + self.uniforms.append(s) def add_const(self, type_str: str, name: str, value_str: str, array_size: int = 0): """ @@ -375,6 +384,8 @@ class Shader: s += 'layout(triangle_strip) out;\n' s += 'layout(max_vertices=3) out;\n' + for a in self.uniforms_top: + s += 'uniform ' + a + ';\n' for a in self.includes: s += '#include "' + a + '"\n' if self.geom_passthrough: diff --git a/blender/arm/props_renderpath.py b/blender/arm/props_renderpath.py index e5ba673b..246c505c 100644 --- a/blender/arm/props_renderpath.py +++ b/blender/arm/props_renderpath.py @@ -242,6 +242,62 @@ class ArmRPListItem(bpy.types.PropertyGroup): rp_autoexposure: BoolProperty(name="Auto Exposure", description="Adjust exposure based on luminance", default=False, update=update_renderpath) rp_compositornodes: BoolProperty(name="Compositor", description="Draw compositor nodes", default=True, update=update_renderpath) rp_shadows: BoolProperty(name="Shadows", description="Enable shadow casting", default=True, update=update_renderpath) + rp_max_lights: EnumProperty( + items=[('4', '4', '4'), + ('8', '8', '8'), + ('16', '16', '16'), + ('24', '24', '24'), + ('32', '32', '32'), + ('64', '64', '64'),], + name="Max Lights", description="Max number of lights that can be visible in the screen", default='16') + rp_max_lights_cluster: EnumProperty( + items=[('4', '4', '4'), + ('8', '8', '8'), + ('16', '16', '16'), + ('24', '24', '24'), + ('32', '32', '32'), + ('64', '64', '64'),], + name="Max Lights Shadows", description="Max number of rendered shadow maps that can be visible in the screen. Always equal or lower than Max Lights", default='16') + rp_shadowmap_atlas: BoolProperty(name="Shadow Map Atlasing", description="Group shadow maps of lights of the same type in the same texture", default=False, update=update_renderpath) + rp_shadowmap_atlas_single_map: BoolProperty(name="Shadow Map Atlas single map", description="Use a single texture for all different light types.", default=False, update=update_renderpath) + rp_shadowmap_atlas_lod: BoolProperty(name="Shadow Map Atlas LOD (Experimental)", description="When enabled, the size of the shadow map will be determined on runtime based on the distance of the light to the camera", default=False, update=update_renderpath) + rp_shadowmap_atlas_lod_subdivisions: EnumProperty( + items=[('2', '2', '2'), + ('3', '3', '3'), + ('4', '4', '4'), + ('5', '5', '5'), + ('6', '6', '6'), + ('7', '7', '7'), + ('8', '8', '8'),], + name="LOD Subdivisions", description="Number of subdivisions of the default tile size for LOD", default='2', update=update_renderpath) + rp_shadowmap_atlas_max_size_point: EnumProperty( + items=[('1024', '1024', '1024'), + ('2048', '2048', '2048'), + ('4096', '4096', '4096'), + ('8192', '8192', '8192'), + ('16384', '16384', '16384')], + name="Max Atlas Texture Size Points", description="Sets the limit of the size of the texture.", default='8192', update=update_renderpath) + rp_shadowmap_atlas_max_size_spot: EnumProperty( + items=[('1024', '1024', '1024'), + ('2048', '2048', '2048'), + ('4096', '4096', '4096'), + ('8192', '8192', '8192'), + ('16384', '16384', '16384')], + name="Max Atlas Texture Size Spots", description="Sets the limit of the size of the texture.", default='8192', update=update_renderpath) + rp_shadowmap_atlas_max_size_sun: EnumProperty( + items=[('1024', '1024', '1024'), + ('2048', '2048', '2048'), + ('4096', '4096', '4096'), + ('8192', '8192', '8192'), + ('16384', '16384', '16384')], + name="Max Atlas Texture Size Sun", description="Sets the limit of the size of the texture.", default='8192', update=update_renderpath) + rp_shadowmap_atlas_max_size: EnumProperty( + items=[('1024', '1024', '1024'), + ('2048', '2048', '2048'), + ('4096', '4096', '4096'), + ('8192', '8192', '8192'), + ('16384', '16384', '16384')], + name="Max Atlas Texture Size", description="Sets the limit of the size of the texture.", default='8192', update=update_renderpath) rp_shadowmap_cube: EnumProperty( items=[('256', '256', '256'), ('512', '512', '512'), diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index d58b3012..504486f7 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -1125,6 +1125,13 @@ class ARM_PT_RenderPathShadowsPanel(bpy.types.Panel): rpdat = wrd.arm_rplist[wrd.arm_rplist_index] self.layout.prop(rpdat, "rp_shadows", text="") + def compute_subdivs(self, max, subdivs): + l = [max] + for i in range(subdivs - 1): + l.append(int(max / 2)) + max = max / 2 + return l + def draw(self, context): layout = self.layout layout.use_property_split = True @@ -1144,6 +1151,55 @@ class ARM_PT_RenderPathShadowsPanel(bpy.types.Panel): col2.prop(rpdat, 'arm_shadowmap_split') col.prop(rpdat, 'arm_shadowmap_bounds') col.prop(rpdat, 'arm_pcfsize') + layout.separator() + + layout.prop(rpdat, 'rp_shadowmap_atlas') + colatlas = layout.column() + colatlas.enabled = rpdat.rp_shadowmap_atlas + colatlas.prop(rpdat, 'rp_max_lights') + colatlas.prop(rpdat, 'rp_max_lights_cluster') + colatlas.prop(rpdat, 'rp_shadowmap_atlas_lod') + + colatlas_lod = colatlas.column() + colatlas_lod.enabled = rpdat.rp_shadowmap_atlas_lod + colatlas_lod.prop(rpdat, 'rp_shadowmap_atlas_lod_subdivisions') + + colatlas_lod_info = colatlas_lod.row() + colatlas_lod_info.alignment = 'RIGHT' + subdivs_list = self.compute_subdivs(int(rpdat.rp_shadowmap_cascade), int(rpdat.rp_shadowmap_atlas_lod_subdivisions)) + subdiv_text = "Subdivisions: " + ', '.join(map(str, subdivs_list)) + colatlas_lod_info.label(text=subdiv_text, icon="IMAGE_ZDEPTH") + + colatlas.prop(rpdat, 'rp_shadowmap_atlas_single_map') + # show size for single texture + if rpdat.rp_shadowmap_atlas_single_map: + colatlas_single = colatlas.column() + colatlas_single.prop(rpdat, 'rp_shadowmap_atlas_max_size') + if int(rpdat.rp_shadowmap_cascade) >= int(rpdat.rp_shadowmap_atlas_max_size): + print(rpdat.rp_shadowmap_atlas_max_size) + colatlas_warning = colatlas_single.row() + colatlas_warning.alignment = 'RIGHT' + colatlas_warning.label(text=f'Warning: {rpdat.rp_shadowmap_atlas_max_size} is too small for the shadowmap size: {rpdat.rp_shadowmap_cascade}', icon="ERROR") + else: + # show size for all types + colatlas_mixed = colatlas.column() + colatlas_mixed.prop(rpdat, 'rp_shadowmap_atlas_max_size_spot') + if int(rpdat.rp_shadowmap_cascade) > int(rpdat.rp_shadowmap_atlas_max_size_spot): + colatlas_warning = colatlas_mixed.row() + colatlas_warning.alignment = 'RIGHT' + colatlas_warning.label(text=f'Warning: {rpdat.rp_shadowmap_atlas_max_size_spot} is too small for the shadowmap size: {rpdat.rp_shadowmap_cascade}', icon="ERROR") + + colatlas_mixed.prop(rpdat, 'rp_shadowmap_atlas_max_size_point') + if int(rpdat.rp_shadowmap_cascade) >= int(rpdat.rp_shadowmap_atlas_max_size_point): + colatlas_warning = colatlas_mixed.row() + colatlas_warning.alignment = 'RIGHT' + colatlas_warning.label(text=f'Warning: {rpdat.rp_shadowmap_atlas_max_size_point} is too small for the shadowmap size: {rpdat.rp_shadowmap_cube}', icon="ERROR") + + colatlas_mixed.prop(rpdat, 'rp_shadowmap_atlas_max_size_sun') + if int(rpdat.rp_shadowmap_cascade) >= int(rpdat.rp_shadowmap_atlas_max_size_sun): + colatlas_warning = colatlas_mixed.row() + colatlas_warning.alignment = 'RIGHT' + colatlas_warning.label(text=f'Warning: {rpdat.rp_shadowmap_atlas_max_size_sun} is too small for the shadowmap size: {rpdat.rp_shadowmap_cascade}', icon="ERROR") class ARM_PT_RenderPathVoxelsPanel(bpy.types.Panel): bl_label = "Voxel AO" @@ -2671,4 +2727,4 @@ def unregister(): bpy.utils.unregister_class(ArmProxyApplyAllButton) bpy.utils.unregister_class(ArmSyncProxyButton) bpy.utils.unregister_class(ArmPrintTraitsButton) - bpy.utils.unregister_class(ARM_PT_MaterialNodePanel) \ No newline at end of file + bpy.utils.unregister_class(ARM_PT_MaterialNodePanel) diff --git a/blender/arm/write_data.py b/blender/arm/write_data.py index dcb5a57b..0803f273 100755 --- a/blender/arm/write_data.py +++ b/blender/arm/write_data.py @@ -517,6 +517,7 @@ def write_indexhtml(w, h, is_publish): add_compiledglsl = '' def write_compiledglsl(defs, make_variants): rpdat = arm.utils.get_rp() + wrd = bpy.data.worlds['Arm'] shadowmap_size = arm.utils.get_cascade_size(rpdat) if rpdat.rp_shadows else 0 with open(arm.utils.build_dir() + '/compiled/Shaders/compiled.inc', 'w') as f: f.write( @@ -689,6 +690,22 @@ const float voxelgiAperture = """ + str(round(rpdat.arm_voxelgi_aperture * 100) if rpdat.arm_skin == 'On': f.write( """const int skinMaxBones = """ + str(rpdat.arm_skin_max_bones) + """; +""") + + if '_Clusters' in wrd.world_defs: + max_lights = "4" + max_lights_clusters = "4" + if rpdat.rp_shadowmap_atlas: + max_lights = str(rpdat.rp_max_lights) + max_lights_clusters = str(rpdat.rp_max_lights_cluster) + # prevent max lights cluster being higher than max lights + if (int(max_lights_clusters) > int(max_lights)): + max_lights_clusters = max_lights + + f.write( +"""const int maxLights = """ + max_lights + """; +const int maxLightsCluster = """ + max_lights_clusters + """; +const float clusterNear = 4.0; """) f.write(add_compiledglsl + '\n') # External defined constants From c64b47548ef28717e7f3933cd1baf4af5ce0f3c3 Mon Sep 17 00:00:00 2001 From: N8n5h Date: Wed, 10 Feb 2021 20:57:50 -0300 Subject: [PATCH 18/74] Added support for direct3d-like texture coords uv for shadow atlases See http://thedev-log.blogspot.com/2012/07/texture-coordinates-tutorial-opengl-and.html, the "opengl coordinates" where inverted for proper support of direct3d texture coordinate system. --- Shaders/std/light.glsl | 6 +++- Shaders/std/light_mobile.glsl | 6 +++- Shaders/std/shadows.glsl | 55 +++++++++++++++++++++++++++++------ 3 files changed, 56 insertions(+), 11 deletions(-) diff --git a/Shaders/std/light.glsl b/Shaders/std/light.glsl index 61c26771..aa3024ae 100644 --- a/Shaders/std/light.glsl +++ b/Shaders/std/light.glsl @@ -185,13 +185,17 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co #ifdef _Clusters vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0); #ifdef _ShadowMapAtlas + vec3 uv = lPos.xyz / lPos.w; + #ifdef _InvY + uv.y = 1.0 - uv.y; // invert Y coordinates for direct3d coordinate system + #endif direct *= shadowTest( #ifndef _SingleAtlas shadowMapAtlasSpot #else shadowMapAtlas #endif - , lPos.xyz / lPos.w, bias + , uv, bias ); #else if (index == 0) direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias); diff --git a/Shaders/std/light_mobile.glsl b/Shaders/std/light_mobile.glsl index f6a966c9..e547129c 100644 --- a/Shaders/std/light_mobile.glsl +++ b/Shaders/std/light_mobile.glsl @@ -80,13 +80,17 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co #ifdef _Clusters vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0); #ifdef _ShadowMapAtlas + vec3 uv = lPos.xyz / lPos.w; + #ifdef _InvY + uv.y = 1.0 - uv.y; // invert Y coordinates for direct3d coordinate system + #endif direct *= shadowTest( #ifndef _SingleAtlas shadowMapAtlasSpot #else shadowMapAtlas #endif - , lPos.xyz / lPos.w, bias + , uv, bias ); #else if (index == 0) direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias); diff --git a/Shaders/std/shadows.glsl b/Shaders/std/shadows.glsl index 81a12cc4..baf940a4 100755 --- a/Shaders/std/shadows.glsl +++ b/Shaders/std/shadows.glsl @@ -188,40 +188,77 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float const vec2 uv = sampleCube(ml, faceIndex); vec4 pointLightTile = pointLightDataArray[lightIndex + faceIndex]; // x: tile X offset, y: tile Y offset, z: tile size relative to atlas - float result = texture(shadowMap, vec3(pointLightTile.z * uv + pointLightTile.xy, compare)); + vec2 uvtiled = pointLightTile.z * uv + pointLightTile.xy; + #ifdef _InvY + uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system + #endif + + float result = texture(shadowMap, vec3(uvtiled, compare)); // soft shadowing int newFaceIndex = 0; - vec2 uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(-1.0, 0.0) / smSize))); + uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(-1.0, 0.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; + uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; + #ifdef _InvY + uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system + #endif result += texture(shadowMap, vec3(pointLightTile.z * uvtiled + pointLightTile.xy, compare)); uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(-1.0, 1.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; - result += texture(shadowMap, vec3(pointLightTile.z * uvtiled + pointLightTile.xy, compare)); + uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; + #ifdef _InvY + uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system + #endif + result += texture(shadowMap, vec3(uvtiled, compare)); uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(0.0, -1.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; - result += texture(shadowMap, vec3(pointLightTile.z * uvtiled + pointLightTile.xy, compare)); + uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; + #ifdef _InvY + uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system + #endif + result += texture(shadowMap, vec3(uvtiled, compare)); uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(-1.0, -1.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; - result += texture(shadowMap, vec3(pointLightTile.z * uvtiled + pointLightTile.xy, compare)); + uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; + #ifdef _InvY + uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system + #endif + result += texture(shadowMap, vec3(uvtiled, compare)); uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(0.0, 1.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; - result += texture(shadowMap, vec3(pointLightTile.z * uvtiled + pointLightTile.xy, compare)); + uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; + #ifdef _InvY + uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system + #endif + result += texture(shadowMap, vec3(uvtiled, compare)); uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(1.0, -1.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; - result += texture(shadowMap, vec3(pointLightTile.z * uvtiled + pointLightTile.xy, compare)); + uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; + #ifdef _InvY + uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system + #endif + result += texture(shadowMap, vec3(uvtiled, compare)); uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(1.0, 0.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; - result += texture(shadowMap, vec3(pointLightTile.z * uvtiled + pointLightTile.xy, compare)); + uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; + #ifdef _InvY + uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system + #endif + result += texture(shadowMap, vec3(uvtiled, compare)); uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(1.0, 1.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; - result += texture(shadowMap, vec3(pointLightTile.z * uvtiled + pointLightTile.xy, compare)); + uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; + #ifdef _InvY + uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system + #endif + result += texture(shadowMap, vec3(uvtiled, compare)); return result / 9.0; } From 3260e627ce0269a753d0e5381446682a31b2dce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Fri, 12 Feb 2021 00:31:19 +0100 Subject: [PATCH 19/74] Fix custom material export when scene is exported in arm format See https://github.com/armory3d/armory/commit/af247f1876b978648fedf996d1bde19c45df6bd2 for reference --- blender/arm/material/make.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/blender/arm/material/make.py b/blender/arm/material/make.py index 53ed1416..13a79d33 100755 --- a/blender/arm/material/make.py +++ b/blender/arm/material/make.py @@ -112,7 +112,8 @@ def parse(material: Material, mat_data, mat_users: Dict[Material, List[Object]], if wrd.arm_single_data_file: mat_data['shader'] = shader_data_name else: - ext = '' if wrd.arm_minimize else '.json' + # Make sure that custom materials are not expected to be in .arm format + ext = '' if wrd.arm_minimize and material.arm_custom_material == "" else '.json' mat_data['shader'] = shader_data_name + ext + '/' + shader_data_name return sd, rpasses From 52e4aaa21c179bccbfce671722742410ae7bdd58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Fri, 12 Feb 2021 20:12:55 +0100 Subject: [PATCH 20/74] Add button to create a new custom material --- .../custom_mat_deferred.frag.glsl | 31 ++++ .../custom_mat_deferred.vert.glsl | 20 +++ .../custom_mat_forward.frag.glsl | 12 ++ .../custom_mat_forward.vert.glsl | 11 ++ blender/arm/props_ui.py | 151 +++++++++++++++++- 5 files changed, 222 insertions(+), 3 deletions(-) create mode 100644 Shaders/custom_mat_presets/custom_mat_deferred.frag.glsl create mode 100644 Shaders/custom_mat_presets/custom_mat_deferred.vert.glsl create mode 100644 Shaders/custom_mat_presets/custom_mat_forward.frag.glsl create mode 100644 Shaders/custom_mat_presets/custom_mat_forward.vert.glsl diff --git a/Shaders/custom_mat_presets/custom_mat_deferred.frag.glsl b/Shaders/custom_mat_presets/custom_mat_deferred.frag.glsl new file mode 100644 index 00000000..9b1deadd --- /dev/null +++ b/Shaders/custom_mat_presets/custom_mat_deferred.frag.glsl @@ -0,0 +1,31 @@ +#version 450 + +// Include functions for gbuffer operations (packFloat2() etc.) +#include "../std/gbuffer.glsl" + +// World-space normal from the vertex shader stage +in vec3 wnormal; + +// Gbuffer output. Deferred rendering uses the following layout: +// [0]: normal x normal y roughness metallic/matID +// [1]: base color r base color g base color b occlusion/specular +out vec4 fragColor[2]; + +void main() { + // Pack normals into 2 components to fit into the gbuffer + vec3 n = normalize(wnormal); + n /= (abs(n.x) + abs(n.y) + abs(n.z)); + n.xy = n.z >= 0.0 ? n.xy : octahedronWrap(n.xy); + + // Define PBR material values + vec3 basecol = vec3(1.0); + float roughness = 0.0; + float metallic = 0.0; + float occlusion = 1.0; + float specular = 1.0; + uint materialId = 0; + + // Store in gbuffer (see layout table above) + fragColor[0] = vec4(n.xy, roughness, packFloatInt16(metallic, materialId)); + fragColor[1] = vec4(basecol.rgb, packFloat2(occlusion, specular)); +} diff --git a/Shaders/custom_mat_presets/custom_mat_deferred.vert.glsl b/Shaders/custom_mat_presets/custom_mat_deferred.vert.glsl new file mode 100644 index 00000000..a5d66425 --- /dev/null +++ b/Shaders/custom_mat_presets/custom_mat_deferred.vert.glsl @@ -0,0 +1,20 @@ +#version 450 + +// World to view projection matrix to correctly position the vertex on screen +uniform mat4 WVP; +// Matrix to transform normals from local into world space +uniform mat3 N; + +// Position and normal vector in local space for the current vertex +// Armory packs the vertex data to preserve memory, so nor.z values are +// saved in pos.w +in vec4 pos; // pos.xyz, nor.w +in vec2 nor; // nor.xy + +// Normal vector in world space +out vec3 wnormal; + +void main() { + wnormal = normalize(N * vec3(nor.xy, pos.w)); + gl_Position = WVP * vec4(pos.xyz, 1.0); +} diff --git a/Shaders/custom_mat_presets/custom_mat_forward.frag.glsl b/Shaders/custom_mat_presets/custom_mat_forward.frag.glsl new file mode 100644 index 00000000..a38e1ea0 --- /dev/null +++ b/Shaders/custom_mat_presets/custom_mat_forward.frag.glsl @@ -0,0 +1,12 @@ +#version 450 + +// World-space normal from the vertex shader stage +in vec3 wnormal; + +// Color of each fragment on the screen +out vec4 fragColor; + +void main() { + // Shadeless white color + fragColor = vec4(1.0); +} diff --git a/Shaders/custom_mat_presets/custom_mat_forward.vert.glsl b/Shaders/custom_mat_presets/custom_mat_forward.vert.glsl new file mode 100644 index 00000000..12f10a5d --- /dev/null +++ b/Shaders/custom_mat_presets/custom_mat_forward.vert.glsl @@ -0,0 +1,11 @@ +#version 450 + +// World to view projection matrix to correctly position the vertex on screen +uniform mat4 WVP; + +// Position and normal vector in local space for the current vertex +in vec3 pos; + +void main() { + gl_Position = WVP * vec4(pos, 1.0); +} diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index d58b3012..4a364b95 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -1,5 +1,6 @@ +import json import os -import time +import shutil import bpy from bpy.props import * @@ -286,6 +287,146 @@ class InvalidateMaterialCacheButton(bpy.types.Operator): context.material.signature = '' return{'FINISHED'} + +class ARM_OT_NewCustomMaterial(bpy.types.Operator): + bl_idname = "arm.new_custom_material" + bl_label = "New Custom Material" + bl_description = "Add a new custom material. This will create all the necessary files and folders" + + def poll_mat_name(self, context): + project_dir = arm.utils.get_fp() + shader_dir_dst = os.path.join(project_dir, 'Shaders') + mat_name = arm.utils.safestr(self.mat_name) + + self.mat_exists = os.path.isdir(os.path.join(project_dir, 'Bundled', mat_name)) + + vert_exists = os.path.isfile(os.path.join(shader_dir_dst, f'{mat_name}.vert.glsl')) + frag_exists = os.path.isfile(os.path.join(shader_dir_dst, f'{mat_name}.frag.glsl')) + self.shader_exists = vert_exists or frag_exists + + mat_name: StringProperty( + name='Material Name', description='The name of the new material', + default='MyCustomMaterial', + update=poll_mat_name) + mode: EnumProperty( + name='Target RP', description='Choose for which render path mode the new material is created', + default='deferred', + items=[('deferred', 'Deferred', 'Create the material for a deferred render path'), + ('forward', 'Forward', 'Create the material for a forward render path')]) + mat_exists: BoolProperty( + name='Material Already Exists', + default=False, + options={'HIDDEN', 'SKIP_SAVE'}) + shader_exists: BoolProperty( + name='Shaders Already Exist', + default=False, + options={'HIDDEN', 'SKIP_SAVE'}) + + def invoke(self, context, event): + if not bpy.data.is_saved: + self.report({'INFO'}, "Please save your file first") + return {"CANCELLED"} + + # Try to set deferred/forward based on the selected render path + try: + self.mode = 'forward' if arm.utils.get_rp().rp_renderer == 'Forward' else 'deferred' + except IndexError: + # No render path, use default (deferred) + pass + + self.poll_mat_name(context) + wm = context.window_manager + return wm.invoke_props_dialog(self, width=300) + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + layout.prop(self, 'mat_name') + layout.prop(self, 'mode', expand=True) + + if self.mat_exists: + box = layout.box() + box.alert = True + col = box.column(align=True) + col.label(text='A custom material with that name already exists,', icon='ERROR') + col.label(text='clicking on \'OK\' will override the material!', icon='BLANK1') + + if self.shader_exists: + box = layout.box() + box.alert = True + col = box.column(align=True) + col.label(text='Shader file(s) with that name already exists,', icon='ERROR') + col.label(text='clicking on \'OK\' will override the shader(s)!', icon='BLANK1') + + def execute(self, context): + if self.mat_name == '': + return {'CANCELLED'} + + project_dir = arm.utils.get_fp() + shader_dir_src = os.path.join(arm.utils.get_sdk_path(), 'armory', 'Shaders', 'custom_mat_presets') + shader_dir_dst = os.path.join(project_dir, 'Shaders') + mat_name = arm.utils.safestr(self.mat_name) + mat_dir = os.path.join(project_dir, 'Bundled', mat_name) + + os.makedirs(mat_dir, exist_ok=True) + os.makedirs(shader_dir_dst, exist_ok=True) + + # Shader data + if self.mode == 'forward': + col_attachments = ['RGBA64'] + constants = [{'link': '_worldViewProjectionMatrix', 'name': 'WVP', 'type': 'mat4'}] + vertex_elems = [{'name': 'pos', 'data': 'short4norm'}] + else: + col_attachments = ['RGBA64', 'RGBA64'] + constants = [ + {'link': '_worldViewProjectionMatrix', 'name': 'WVP', 'type': 'mat4'}, + {'link': '_normalMatrix', 'name': 'N', 'type': 'mat3'} + ] + vertex_elems = [ + {'name': 'pos', 'data': 'short4norm'}, + {'name': 'nor', 'data': 'short2norm'} + ] + + con = { + 'color_attachments': col_attachments, + 'compare_mode': 'less', + 'constants': constants, + 'cull_mode': 'clockwise', + 'depth_write': True, + 'fragment_shader': f'{mat_name}.frag', + 'name': 'mesh', + 'texture_units': [], + 'vertex_shader': f'{mat_name}.vert', + 'vertex_elements': vertex_elems + } + data = { + 'shader_datas': [{ + 'contexts': [con], + 'name': f'{mat_name}' + }] + } + + # Save shader data file + with open(os.path.join(mat_dir, f'{mat_name}.json'), 'w') as datafile: + json.dump(data, datafile, indent=4, sort_keys=True) + + # Copy preset shaders to project + if self.mode == 'forward': + shutil.copy(os.path.join(shader_dir_src, 'custom_mat_forward.frag.glsl'), os.path.join(shader_dir_dst, f'{mat_name}.frag.glsl')) + shutil.copy(os.path.join(shader_dir_src, 'custom_mat_forward.vert.glsl'), os.path.join(shader_dir_dst, f'{mat_name}.vert.glsl')) + else: + shutil.copy(os.path.join(shader_dir_src, 'custom_mat_deferred.frag.glsl'), os.path.join(shader_dir_dst, f'{mat_name}.frag.glsl')) + shutil.copy(os.path.join(shader_dir_src, 'custom_mat_deferred.vert.glsl'), os.path.join(shader_dir_dst, f'{mat_name}.vert.glsl')) + + # True if called from the material properties tab, else it was called from the search menu + if hasattr(context, 'material') and context.material is not None: + context.material.arm_custom_material = mat_name + + return{'FINISHED'} + + class ARM_PT_MaterialPropsPanel(bpy.types.Panel): bl_label = "Armory Props" bl_space_type = "PROPERTIES" @@ -317,7 +458,9 @@ class ARM_PT_MaterialPropsPanel(bpy.types.Panel): columnb.enabled = mat.arm_discard columnb.prop(mat, 'arm_discard_opacity') columnb.prop(mat, 'arm_discard_opacity_shadows') - layout.prop(mat, 'arm_custom_material') + row = layout.row(align=True) + row.prop(mat, 'arm_custom_material') + row.operator('arm.new_custom_material', text='', icon='ADD') layout.prop(mat, 'arm_skip_context') layout.prop(mat, 'arm_particle_fade') layout.prop(mat, 'arm_billboard') @@ -2546,6 +2689,7 @@ def register(): bpy.utils.register_class(ARM_PT_WorldPropsPanel) bpy.utils.register_class(InvalidateCacheButton) bpy.utils.register_class(InvalidateMaterialCacheButton) + bpy.utils.register_class(ARM_OT_NewCustomMaterial) bpy.utils.register_class(ARM_PT_MaterialPropsPanel) bpy.utils.register_class(ARM_PT_MaterialBlendingPropsPanel) bpy.utils.register_class(ARM_PT_MaterialDriverPropsPanel) @@ -2626,6 +2770,7 @@ def unregister(): bpy.utils.unregister_class(ARM_PT_ScenePropsPanel) bpy.utils.unregister_class(InvalidateCacheButton) bpy.utils.unregister_class(InvalidateMaterialCacheButton) + bpy.utils.unregister_class(ARM_OT_NewCustomMaterial) bpy.utils.unregister_class(ARM_PT_MaterialDriverPropsPanel) bpy.utils.unregister_class(ARM_PT_MaterialBlendingPropsPanel) bpy.utils.unregister_class(ARM_PT_MaterialPropsPanel) @@ -2671,4 +2816,4 @@ def unregister(): bpy.utils.unregister_class(ArmProxyApplyAllButton) bpy.utils.unregister_class(ArmSyncProxyButton) bpy.utils.unregister_class(ArmPrintTraitsButton) - bpy.utils.unregister_class(ARM_PT_MaterialNodePanel) \ No newline at end of file + bpy.utils.unregister_class(ARM_PT_MaterialNodePanel) From b70a60078b54bddd55d680d7c55e1aaab0b641e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Fri, 12 Feb 2021 20:24:42 +0100 Subject: [PATCH 21/74] Fix and improve comments --- Shaders/custom_mat_presets/custom_mat_deferred.vert.glsl | 2 +- Shaders/custom_mat_presets/custom_mat_forward.vert.glsl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Shaders/custom_mat_presets/custom_mat_deferred.vert.glsl b/Shaders/custom_mat_presets/custom_mat_deferred.vert.glsl index a5d66425..3a48c29a 100644 --- a/Shaders/custom_mat_presets/custom_mat_deferred.vert.glsl +++ b/Shaders/custom_mat_presets/custom_mat_deferred.vert.glsl @@ -5,7 +5,7 @@ uniform mat4 WVP; // Matrix to transform normals from local into world space uniform mat3 N; -// Position and normal vector in local space for the current vertex +// Position and normal vectors of the current vertex in local space // Armory packs the vertex data to preserve memory, so nor.z values are // saved in pos.w in vec4 pos; // pos.xyz, nor.w diff --git a/Shaders/custom_mat_presets/custom_mat_forward.vert.glsl b/Shaders/custom_mat_presets/custom_mat_forward.vert.glsl index 12f10a5d..a1d12fd7 100644 --- a/Shaders/custom_mat_presets/custom_mat_forward.vert.glsl +++ b/Shaders/custom_mat_presets/custom_mat_forward.vert.glsl @@ -3,7 +3,7 @@ // World to view projection matrix to correctly position the vertex on screen uniform mat4 WVP; -// Position and normal vector in local space for the current vertex +// Position vector of the current vertex in local space in vec3 pos; void main() { From b4d5afd56197ce4f4d8e242ade29f638ffb37c37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Fri, 12 Feb 2021 20:54:02 +0100 Subject: [PATCH 22/74] Remove unused fragment shader input --- Shaders/custom_mat_presets/custom_mat_forward.frag.glsl | 3 --- 1 file changed, 3 deletions(-) diff --git a/Shaders/custom_mat_presets/custom_mat_forward.frag.glsl b/Shaders/custom_mat_presets/custom_mat_forward.frag.glsl index a38e1ea0..2baccba7 100644 --- a/Shaders/custom_mat_presets/custom_mat_forward.frag.glsl +++ b/Shaders/custom_mat_presets/custom_mat_forward.frag.glsl @@ -1,8 +1,5 @@ #version 450 -// World-space normal from the vertex shader stage -in vec3 wnormal; - // Color of each fragment on the screen out vec4 fragColor; From a2e4850b12593b68c0a935da92c7645a1899a673 Mon Sep 17 00:00:00 2001 From: SunDaw Date: Sat, 13 Feb 2021 16:56:21 +0100 Subject: [PATCH 23/74] Remove array allocation from SendEventNode Remove entires member variable from SendEvent nodes --- Sources/armory/logicnode/SendEventNode.hx | 10 ++-------- Sources/armory/logicnode/SendGlobalEventNode.hx | 4 +--- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/Sources/armory/logicnode/SendEventNode.hx b/Sources/armory/logicnode/SendEventNode.hx index 2fff9bea..6dfe0974 100644 --- a/Sources/armory/logicnode/SendEventNode.hx +++ b/Sources/armory/logicnode/SendEventNode.hx @@ -5,8 +5,6 @@ import armory.system.Event; class SendEventNode extends LogicNode { - var entries: Array = null; - public function new(tree: LogicTree) { super(tree); } @@ -17,13 +15,9 @@ class SendEventNode extends LogicNode { if (object == null) return; - var all = Event.get(name); - if (all != null) { - entries = []; - for (e in all) if (e.mask == object.uid) entries.push(e); - } + var entries = Event.get(name); if (entries == null) return; - for (e in entries) e.onEvent(); + for (e in entries) if (e.mask == object.uid) e.onEvent(); runOutput(0); } diff --git a/Sources/armory/logicnode/SendGlobalEventNode.hx b/Sources/armory/logicnode/SendGlobalEventNode.hx index 798847f6..c5a8eb34 100644 --- a/Sources/armory/logicnode/SendGlobalEventNode.hx +++ b/Sources/armory/logicnode/SendGlobalEventNode.hx @@ -4,8 +4,6 @@ import armory.system.Event; class SendGlobalEventNode extends LogicNode { - var entries: Array = null; - public function new(tree: LogicTree) { super(tree); } @@ -13,7 +11,7 @@ class SendGlobalEventNode extends LogicNode { override function run(from: Int) { var name: String = inputs[1].get(); - entries = Event.get(name); + var entries = Event.get(name); if (entries == null) return; // Event does not exist for (e in entries) e.onEvent(); From 6cbe5390984636a0442f80f6ca774b3a91b47fed Mon Sep 17 00:00:00 2001 From: SunDaw Date: Sat, 13 Feb 2021 17:23:16 +0100 Subject: [PATCH 24/74] Reuse contacts array instead of creating new Remove unused import from PhysicsWorld --- Sources/armory/trait/physics/bullet/PhysicsWorld.hx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Sources/armory/trait/physics/bullet/PhysicsWorld.hx b/Sources/armory/trait/physics/bullet/PhysicsWorld.hx index 22cf1cc5..ebfe2f78 100644 --- a/Sources/armory/trait/physics/bullet/PhysicsWorld.hx +++ b/Sources/armory/trait/physics/bullet/PhysicsWorld.hx @@ -6,7 +6,6 @@ import iron.Trait; import iron.system.Time; import iron.math.Vec4; import iron.math.RayCaster; -import iron.data.SceneFormat; class Hit { @@ -281,7 +280,7 @@ class PhysicsWorld extends Trait { } function updateContacts() { - contacts = []; + contacts.resize(0); var disp: bullet.Bt.Dispatcher = dispatcher; var numManifolds = disp.getNumManifolds(); From 0ee35adc813cb86a972d537fb783bcad82ff4e99 Mon Sep 17 00:00:00 2001 From: knowledgenude Date: Sun, 14 Feb 2021 19:21:26 -0300 Subject: [PATCH 25/74] Improved FSM --- Sources/armory/system/FSM.hx | 109 ++++++++++++++--------------------- 1 file changed, 44 insertions(+), 65 deletions(-) diff --git a/Sources/armory/system/FSM.hx b/Sources/armory/system/FSM.hx index 53a66007..f703aea7 100644 --- a/Sources/armory/system/FSM.hx +++ b/Sources/armory/system/FSM.hx @@ -1,49 +1,25 @@ package armory.system; -class FSM { - var state: Null; - var nextState: Null; - var transitions = new Array(); - var tempTransitions = new Array(); +class FSM { + final transitions = new Array>(); + final tempTransitions = new Array>(); + var state: Null>; var entered = false; public function new() {} - /** - * Set the initial / current state of the FSM and return it - * @param state The state to be set - * @return State - **/ - public function setState(state: State) { + public function bindTransition(canEnter: Void -> Bool, fromState: State, toState: State) { + final transition = new Transition(canEnter, fromState, toState); + transitions.push(transition); + syncTransitions(); + } + + public function setInitState(state: State) { this.state = state; syncTransitions(); - return state; } - /** - * Bind multiple transitions to the specified state - * @param canEnter The function that returns true or false to enter the transition - * @param toState The next state the transiiton will return - * @param fromStates The states that are allowed to be changed by the next state - * @return Void - **/ - public function bindTransitions(canEnter: Void -> Bool, toState: State, fromStates: Array) { - for (s in fromStates) { - transitions.push(new Transition(canEnter, s, toState)); - } - - syncTransitions(); - } - - function syncTransitions() { - tempTransitions = []; - - for (t in transitions) { - if (t.isConnected(state)) tempTransitions.push(t); - } - } - - public function run() { + public function update() { if (!entered) { state.onEnter(); entered = true; @@ -51,45 +27,48 @@ class FSM { state.onUpdate(); - for (t in tempTransitions) { - if (t.canEnter()) { - nextState = t.getNextState(); + for (transition in tempTransitions) { + if (transition.canEnter()) { state.onExit(); - state = nextState; + state = transition.toState; entered = false; syncTransitions(); break; } } } -} -class Transition { - final func: Void -> Bool; - final from: State; - final to: State; + public function syncTransitions() { + tempTransitions.resize(0); - public function new(func: Void -> Bool, from: State, to: State) { - this.func = func; - this.from = from; - this.to = to; - } - - public function canEnter() { - return func(); - } - - public function isConnected(state: State) { - return from == state; - } - - public function getNextState() { - return to; + for (transition in transitions) { + if (transition.fromState == state) tempTransitions.push(transition); + } } } -interface State { - function onEnter(): Void; - function onUpdate(): Void; - function onExit(): Void; +class Transition { + public final canEnter: Void -> Bool; + public final fromState: State; + public final toState: State; + + public function new(canEnter: Void -> Bool, fromState: State, toState: State) { + this.canEnter = canEnter; + this.fromState = fromState; + this.toState = toState; + } +} + +class State { + final owner: T; + + public function new(owner: T) { + this.owner = owner; + } + + public function onEnter() {} + + public function onUpdate() {} + + public function onExit() {} } \ No newline at end of file From 3ef380978efb9fba4b2dcbe2a9de58c4415605f0 Mon Sep 17 00:00:00 2001 From: N8n5h Date: Sun, 14 Feb 2021 20:21:51 -0300 Subject: [PATCH 26/74] Make sure compiled.inc is always first when parsing shaders for clusters --- blender/arm/material/shader.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/blender/arm/material/shader.py b/blender/arm/material/shader.py index cecd6ebc..48539e96 100644 --- a/blender/arm/material/shader.py +++ b/blender/arm/material/shader.py @@ -208,7 +208,11 @@ class Shader: def add_include_front(self, s): if not self.has_include(s): - self.includes.insert(0, s) + pos = 0 + # make sure compiled.inc is always on top + if len(self.includes) > 0 and self.includes[0] == 'compiled.inc': + pos = 1 + self.includes.insert(pos, s) def add_in(self, s): if s not in self.ins: From 8bc831d7e24f58e20f57ecb97a09056b81170a67 Mon Sep 17 00:00:00 2001 From: tong Date: Fri, 19 Feb 2021 16:50:29 +0100 Subject: [PATCH 27/74] Show scene name in missing camera warning --- blender/arm/exporter.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/blender/arm/exporter.py b/blender/arm/exporter.py index 1b43d619..4f706e77 100755 --- a/blender/arm/exporter.py +++ b/blender/arm/exporter.py @@ -1964,7 +1964,7 @@ class ArmoryExporter: self.output['world_datas'].append(out_world) elif arm.utils.get_rp().rp_background == 'World': - log.warn(f'Scene "{self.scene.name}" is missing a world, some render targets will not be cleared!') + log.warn(f'Scene "{self.scene.name}" is missing a world, some render targets will not be cleared') def export_objects(self, scene): """Exports all supported blender objects. @@ -2109,7 +2109,7 @@ class ArmoryExporter: self.output['camera_ref'] = self.scene.camera.name else: if self.scene.name == arm.utils.get_project_scene_name(): - log.warn('No camera found in active scene') + log.warn('Scene "'+self.scene.name+'" is missing a camera') self.output['material_datas'] = [] @@ -2152,7 +2152,7 @@ class ArmoryExporter: # No camera found if not self.camera_spawned: - log.warn('No camera found in active scene layers') + log.warn('Scene "'+self.scene.name+'" is missing a camera') # No camera found, create a default one if (len(self.output['camera_datas']) == 0 or len(bpy.data.cameras) == 0) or not self.camera_spawned: From d4ee144577acff2ae70f36a9de747a573ecda7c5 Mon Sep 17 00:00:00 2001 From: tong Date: Fri, 19 Feb 2021 16:53:01 +0100 Subject: [PATCH 28/74] Improve console warning --- blender/arm/log.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/blender/arm/log.py b/blender/arm/log.py index 4f7b5256..482ced28 100644 --- a/blender/arm/log.py +++ b/blender/arm/log.py @@ -2,8 +2,8 @@ import platform DEBUG = 36 INFO = 37 -WARN = 35 -ERROR = 31 +WARN = 93 +ERROR = 91 if platform.system() == "Windows": HAS_COLOR_SUPPORT = platform.release() == "10" @@ -54,7 +54,7 @@ def info(text): info_text = format_text(text) def print_warn(text): - log('Warning: ' + text, WARN) + log('WARNING: ' + text, WARN) def warn(text): global num_warnings From b7c4a8bb3f540e68f53ebda983204995090591b8 Mon Sep 17 00:00:00 2001 From: tong Date: Fri, 19 Feb 2021 16:57:12 +0100 Subject: [PATCH 29/74] Improve console build messages --- blender/arm/make.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/blender/arm/make.py b/blender/arm/make.py index 73a17cc3..5c904936 100755 --- a/blender/arm/make.py +++ b/blender/arm/make.py @@ -62,10 +62,9 @@ def remove_readonly(func, path, excinfo): def export_data(fp, sdk_path): wrd = bpy.data.worlds['Arm'] - print('\n' + '_' * 10 + ' [Armory] Compiling ' + '_' * 10) + print('Armory v{0} ({1})'.format(wrd.arm_version, wrd.arm_commit)) if wrd.arm_verbose_output: - print('\nArmory v{0} ({1})'.format(wrd.arm_version, wrd.arm_commit)) - print('OS: ' + arm.utils.get_os() + ', Target: ' + state.target + ', GAPI: ' + arm.utils.get_gapi() + ', Blender: ' + bpy.app.version_string) + print('Blender: ' + bpy.app.version_string + ', Target: ' + state.target + ', GAPI: ' + arm.utils.get_gapi()) # Clean compiled variants if cache is disabled build_dir = arm.utils.get_fp_build() @@ -158,10 +157,10 @@ def export_data(fp, sdk_path): cdefs = arm.utils.def_strings_to_array(wrd.compo_defs) if wrd.arm_verbose_output: - print('Exported modules:', modules) - print('Shader flags:', defs) - print('Compositor flags:', cdefs) - print('Khafile flags:', assets.khafile_defs) + print('Exported modules:', ', '.join(modules)) + print('Shader flags:' ', '.join(defs)) + print('Compositor flags:' ', '.join(cdefs)) + print('Khafile flags:', ' '.join(assets.khafile_defs)) # Render path is configurable at runtime has_config = wrd.arm_write_config or os.path.exists(arm.utils.get_fp() + '/Bundled/config.arm') @@ -432,7 +431,7 @@ def compilation_server_done(): log.error('Build failed, check console') def build_done(): - print('Finished in ' + str( math.ceil((time.time() - profile_time) * 100) / 100) + 's') + print('Finished in {:0.3f}'.format(time.time() - profile_time) + 's') if log.num_warnings > 0: log.print_warn(f'{log.num_warnings} warnings occurred during compilation') if state.proc_build is None: From 325f7471c0b0d3c350e00624b09d3d4d2f5185d6 Mon Sep 17 00:00:00 2001 From: tong Date: Fri, 19 Feb 2021 17:01:09 +0100 Subject: [PATCH 30/74] Fix printing build flags --- blender/arm/make.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blender/arm/make.py b/blender/arm/make.py index 5c904936..4eee1ae7 100755 --- a/blender/arm/make.py +++ b/blender/arm/make.py @@ -158,8 +158,8 @@ def export_data(fp, sdk_path): if wrd.arm_verbose_output: print('Exported modules:', ', '.join(modules)) - print('Shader flags:' ', '.join(defs)) - print('Compositor flags:' ', '.join(cdefs)) + print('Shader flags:', ' '.join(defs)) + print('Compositor flags:', ' '.join(cdefs)) print('Khafile flags:', ' '.join(assets.khafile_defs)) # Render path is configurable at runtime From d93de122528bba130dda9422bce927b2f93e303b Mon Sep 17 00:00:00 2001 From: tong Date: Fri, 19 Feb 2021 17:04:18 +0100 Subject: [PATCH 31/74] Print human readable time --- blender/arm/exporter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blender/arm/exporter.py b/blender/arm/exporter.py index 4f706e77..54b05c7e 100755 --- a/blender/arm/exporter.py +++ b/blender/arm/exporter.py @@ -2186,7 +2186,7 @@ class ArmoryExporter: if self.scene.frame_current != current_frame: self.scene.frame_set(current_frame, subframe=current_subframe) - print('Scene exported in ' + str(time.time() - profile_time)) + print('Scene exported in {:0.3f}'.format(time.time() - profile_time) + 's') def create_default_camera(self, is_viewport_camera=False): """Creates the default camera and adds a WalkNavigation trait to it.""" From 0abb43f2498a774f215047034646ce527dc0f04e Mon Sep 17 00:00:00 2001 From: tong Date: Fri, 19 Feb 2021 17:25:42 +0100 Subject: [PATCH 32/74] Revert terminal color change --- blender/arm/log.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blender/arm/log.py b/blender/arm/log.py index 482ced28..f28a1524 100644 --- a/blender/arm/log.py +++ b/blender/arm/log.py @@ -2,8 +2,8 @@ import platform DEBUG = 36 INFO = 37 -WARN = 93 -ERROR = 91 +WARN = 35 +ERROR = 31 if platform.system() == "Windows": HAS_COLOR_SUPPORT = platform.release() == "10" From c5855ad96f5a534efec6998a1b2ef515b981a7d0 Mon Sep 17 00:00:00 2001 From: SunDaw Date: Fri, 19 Feb 2021 19:42:18 +0100 Subject: [PATCH 33/74] Add mask option to pick RB node and physicsworld --- Sources/armory/logicnode/PickObjectNode.hx | 3 ++- .../armory/trait/physics/bullet/PhysicsWorld.hx | 4 ++-- blender/arm/logicnode/physics/LN_pick_rb.py | 16 ++++++++++++++-- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/Sources/armory/logicnode/PickObjectNode.hx b/Sources/armory/logicnode/PickObjectNode.hx index a135f245..d021808f 100644 --- a/Sources/armory/logicnode/PickObjectNode.hx +++ b/Sources/armory/logicnode/PickObjectNode.hx @@ -12,12 +12,13 @@ class PickObjectNode extends LogicNode { override function get(from: Int): Dynamic { var coords: Vec4 = inputs[0].get(); + var mask: Int = inputs[1].get(); if (coords == null) return null; #if arm_physics var physics = armory.trait.physics.PhysicsWorld.active; - var rb = physics.pickClosest(coords.x, coords.y); + var rb = physics.pickClosest(coords.x, coords.y, mask); if (rb == null) return null; if (from == 0) { // Object diff --git a/Sources/armory/trait/physics/bullet/PhysicsWorld.hx b/Sources/armory/trait/physics/bullet/PhysicsWorld.hx index ebfe2f78..2260ac70 100644 --- a/Sources/armory/trait/physics/bullet/PhysicsWorld.hx +++ b/Sources/armory/trait/physics/bullet/PhysicsWorld.hx @@ -324,12 +324,12 @@ class PhysicsWorld extends Trait { } } - public function pickClosest(inputX: Float, inputY: Float): RigidBody { + public function pickClosest(inputX: Float, inputY: Float, group: Int = 0x00000001, mask = 0xFFFFFFFF): RigidBody { var camera = iron.Scene.active.camera; var start = new Vec4(); var end = new Vec4(); RayCaster.getDirection(start, end, inputX, inputY, camera); - var hit = rayCast(camera.transform.world.getLoc(), end); + var hit = rayCast(camera.transform.world.getLoc(), end, group, mask); var rb = (hit != null) ? hit.rb : null; return rb; } diff --git a/blender/arm/logicnode/physics/LN_pick_rb.py b/blender/arm/logicnode/physics/LN_pick_rb.py index 71e3286d..8497e1f9 100644 --- a/blender/arm/logicnode/physics/LN_pick_rb.py +++ b/blender/arm/logicnode/physics/LN_pick_rb.py @@ -1,8 +1,19 @@ from arm.logicnode.arm_nodes import * class PickObjectNode(ArmLogicTreeNode): - """Pickes the rigid body in the given location using the screen - coordinates (2D).""" + """Picks the rigid body in the given location using the screen + coordinates (2D). + + @seeNode Mask + + @input Screen Coords: the location at which to pick, in screen + coordinates + @input Mask: a bit mask value to specify which + objects are considered + + @output RB: the object that was hit + @output Hit: the hit position in world coordinates + """ bl_idname = 'LNPickObjectNode' bl_label = 'Pick RB' arm_section = 'ray' @@ -11,6 +22,7 @@ class PickObjectNode(ArmLogicTreeNode): def init(self, context): super(PickObjectNode, self).init(context) self.add_input('NodeSocketVector', 'Screen Coords') + self.add_input('NodeSocketInt', 'Mask', default_value=1) self.add_output('ArmNodeSocketObject', 'RB') self.add_output('NodeSocketVector', 'Hit') From 98481929df34a430d7ae7d9cfe2dcdd9449afb16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Fri, 19 Feb 2021 20:59:54 +0100 Subject: [PATCH 34/74] Fix drawing of bool properties in UI (#1689) --- blender/arm/props_properties.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/blender/arm/props_properties.py b/blender/arm/props_properties.py index 1379813a..510e10eb 100644 --- a/blender/arm/props_properties.py +++ b/blender/arm/props_properties.py @@ -18,11 +18,11 @@ class ArmPropertyListItem(bpy.types.PropertyGroup): class ARM_UL_PropertyList(bpy.types.UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): + layout.use_property_split = False # Make sure your code supports all 3 layout types if self.layout_type in {'DEFAULT', 'COMPACT'}: - row = layout.row() - row.prop(item, "name_prop", text="", emboss=False, icon="OBJECT_DATAMODE") - row.prop(item, item.type_prop + "_prop", text="", emboss=(item.type_prop == 'boolean')) + layout.prop(item, "name_prop", text="", emboss=False, icon="OBJECT_DATAMODE") + layout.prop(item, item.type_prop + "_prop", text="", emboss=(item.type_prop == 'boolean')) elif self.layout_type in {'GRID'}: layout.alignment = 'CENTER' layout.label(text="", icon="OBJECT_DATAMODE") @@ -155,4 +155,4 @@ def unregister(): bpy.utils.unregister_class(ARM_UL_PropertyList) bpy.utils.unregister_class(ArmPropertyListNewItem) bpy.utils.unregister_class(ArmPropertyListDeleteItem) - bpy.utils.unregister_class(ArmPropertyListMoveItem) \ No newline at end of file + bpy.utils.unregister_class(ArmPropertyListMoveItem) From a1fce84f59f1d102c7c1a9dd1bd14eaf48b65dcb Mon Sep 17 00:00:00 2001 From: tong Date: Sat, 20 Feb 2021 09:36:42 +0100 Subject: [PATCH 35/74] Use f string to format console messages --- blender/arm/exporter.py | 4 ++-- blender/arm/make.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/blender/arm/exporter.py b/blender/arm/exporter.py index 54b05c7e..dbc299b7 100755 --- a/blender/arm/exporter.py +++ b/blender/arm/exporter.py @@ -2152,7 +2152,7 @@ class ArmoryExporter: # No camera found if not self.camera_spawned: - log.warn('Scene "'+self.scene.name+'" is missing a camera') + log.warn( f'Scene "{self.scene.name}" is missing a camera') # No camera found, create a default one if (len(self.output['camera_datas']) == 0 or len(bpy.data.cameras) == 0) or not self.camera_spawned: @@ -2186,7 +2186,7 @@ class ArmoryExporter: if self.scene.frame_current != current_frame: self.scene.frame_set(current_frame, subframe=current_subframe) - print('Scene exported in {:0.3f}'.format(time.time() - profile_time) + 's') + print('Scene exported in {:0.3f}s'.format(time.time() - profile_time)) def create_default_camera(self, is_viewport_camera=False): """Creates the default camera and adds a WalkNavigation trait to it.""" diff --git a/blender/arm/make.py b/blender/arm/make.py index 4eee1ae7..c00fcc52 100755 --- a/blender/arm/make.py +++ b/blender/arm/make.py @@ -64,7 +64,7 @@ def export_data(fp, sdk_path): print('Armory v{0} ({1})'.format(wrd.arm_version, wrd.arm_commit)) if wrd.arm_verbose_output: - print('Blender: ' + bpy.app.version_string + ', Target: ' + state.target + ', GAPI: ' + arm.utils.get_gapi()) + print(f'Blender: {bpy.app.version_string}, Target: {state.target}, GAPI: {arm.utils.get_gapi()}') # Clean compiled variants if cache is disabled build_dir = arm.utils.get_fp_build() @@ -431,7 +431,7 @@ def compilation_server_done(): log.error('Build failed, check console') def build_done(): - print('Finished in {:0.3f}'.format(time.time() - profile_time) + 's') + print('Finished in {:0.3f}s'.format(time.time() - profile_time)) if log.num_warnings > 0: log.print_warn(f'{log.num_warnings} warnings occurred during compilation') if state.proc_build is None: From 352f37db0c1e49547f51a7b72d97e1ecbccc243e Mon Sep 17 00:00:00 2001 From: tong Date: Sat, 20 Feb 2021 09:53:48 +0100 Subject: [PATCH 36/74] More f string --- blender/arm/exporter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blender/arm/exporter.py b/blender/arm/exporter.py index dbc299b7..30154525 100755 --- a/blender/arm/exporter.py +++ b/blender/arm/exporter.py @@ -2109,7 +2109,7 @@ class ArmoryExporter: self.output['camera_ref'] = self.scene.camera.name else: if self.scene.name == arm.utils.get_project_scene_name(): - log.warn('Scene "'+self.scene.name+'" is missing a camera') + log.warn(f'Scene "{self.scene.name}" is missing a camera') self.output['material_datas'] = [] From f1858550d0edb0a30b4740e64f16cba25f77d1bd Mon Sep 17 00:00:00 2001 From: Alexander Kleemann Date: Sat, 20 Feb 2021 13:51:25 +0100 Subject: [PATCH 37/74] Blending fix Fix for https://github.com/armory3d/armory/issues/1615 --- Sources/armory/renderpath/RenderPathDeferred.hx | 1 + blender/arm/material/make_mesh.py | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Sources/armory/renderpath/RenderPathDeferred.hx b/Sources/armory/renderpath/RenderPathDeferred.hx index 302cbace..19cac54c 100644 --- a/Sources/armory/renderpath/RenderPathDeferred.hx +++ b/Sources/armory/renderpath/RenderPathDeferred.hx @@ -665,6 +665,7 @@ class RenderPathDeferred { #if rp_blending { + path.setTarget("tex"); path.drawMeshes("blend"); } #end diff --git a/blender/arm/material/make_mesh.py b/blender/arm/material/make_mesh.py index 18608347..0b3d7b56 100644 --- a/blender/arm/material/make_mesh.py +++ b/blender/arm/material/make_mesh.py @@ -572,13 +572,15 @@ def make_forward_base(con_mesh, parse_opacity=False, transluc_pass=False): arm_discard = mat_state.material.arm_discard make_base(con_mesh, parse_opacity=(parse_opacity or arm_discard)) + + blend = mat_state.material.arm_blending vert = con_mesh.vert frag = con_mesh.frag tese = con_mesh.tese if parse_opacity or arm_discard: - if arm_discard: + if arm_discard or blend: opac = mat_state.material.arm_discard_opacity frag.write('if (opacity < {0}) discard;'.format(opac)) elif transluc_pass: @@ -587,7 +589,6 @@ def make_forward_base(con_mesh, parse_opacity=False, transluc_pass=False): opac = '0.9999' # 1.0 - eps frag.write('if (opacity < {0}) discard;'.format(opac)) - blend = mat_state.material.arm_blending if blend: frag.add_out('vec4 fragColor[1]') if parse_opacity: From aec10274f4acb0394bf89eb635b5edef26dba9fc Mon Sep 17 00:00:00 2001 From: Alexander Kleemann Date: Sat, 20 Feb 2021 15:30:18 +0100 Subject: [PATCH 38/74] Add option to ignore irradiance for baked environments --- Shaders/deferred_light/deferred_light.frag.glsl | 11 +++++++++++ Sources/armory/renderpath/RenderPathDeferred.hx | 7 +++++++ blender/arm/make_renderpath.py | 13 ++++++++++++- blender/arm/material/make_mesh.py | 3 +++ blender/arm/props.py | 1 + blender/arm/props_ui.py | 1 + 6 files changed, 35 insertions(+), 1 deletion(-) diff --git a/Shaders/deferred_light/deferred_light.frag.glsl b/Shaders/deferred_light/deferred_light.frag.glsl index 31c342ae..e119349e 100644 --- a/Shaders/deferred_light/deferred_light.frag.glsl +++ b/Shaders/deferred_light/deferred_light.frag.glsl @@ -21,6 +21,7 @@ uniform sampler2D gbufferD; uniform sampler2D gbuffer0; uniform sampler2D gbuffer1; +uniform sampler2D gbuffer2; #ifdef _VoxelAOvar uniform sampler3D voxels; @@ -205,6 +206,8 @@ void main() { vec3 v = normalize(eye - p); float dotNV = max(dot(n, v), 0.0); + vec4 g2 = textureLod(gbuffer2, texCoord, 0.0); + #ifdef _MicroShadowing occspec.x = mix(1.0, occspec.x, dotNV); // AO Fresnel #endif @@ -215,7 +218,15 @@ void main() { // Envmap #ifdef _Irr + vec3 envl = shIrradiance(n, shirr); + + if (g2.b < 0.5) { + envl = envl; + } else { + envl = vec3(1.0); + } + #ifdef _EnvTex envl /= PI; #endif diff --git a/Sources/armory/renderpath/RenderPathDeferred.hx b/Sources/armory/renderpath/RenderPathDeferred.hx index 19cac54c..e2d4760d 100644 --- a/Sources/armory/renderpath/RenderPathDeferred.hx +++ b/Sources/armory/renderpath/RenderPathDeferred.hx @@ -549,6 +549,13 @@ class RenderPathDeferred { path.bindTarget("_main", "gbufferD"); path.bindTarget("gbuffer0", "gbuffer0"); path.bindTarget("gbuffer1", "gbuffer1"); + + #if rp_gbuffer2 + { + path.bindTarget("gbuffer2", "gbuffer2"); + } + #end + #if (rp_ssgi != "Off") { if (armory.data.Config.raw.rp_ssgi != false) { diff --git a/blender/arm/make_renderpath.py b/blender/arm/make_renderpath.py index 81c00161..051ec4dc 100755 --- a/blender/arm/make_renderpath.py +++ b/blender/arm/make_renderpath.py @@ -367,7 +367,18 @@ def build(): assets.add_khafile_def('rp_chromatic_aberration') assets.add_shader_pass('chromatic_aberration_pass') - gbuffer2 = '_Veloc' in wrd.world_defs + ignoreIrr = False + + for obj in bpy.data.objects: + if obj.type == "MESH": + for slot in obj.material_slots: + mat = slot.material + if mat.arm_ignore_irradiance: + ignoreIrr = True + + if ignoreIrr: + wrd.world_defs += '_IgnoreIrr' + gbuffer2 = '_Veloc' in wrd.world_defs or '_IgnoreIrr' in wrd.world_defs if gbuffer2: assets.add_khafile_def('rp_gbuffer2') wrd.world_defs += '_gbuffer2' diff --git a/blender/arm/material/make_mesh.py b/blender/arm/material/make_mesh.py index 0b3d7b56..c64ec408 100644 --- a/blender/arm/material/make_mesh.py +++ b/blender/arm/material/make_mesh.py @@ -268,6 +268,9 @@ def make_deferred(con_mesh, rpasses): frag.write('vec2 posb = (prevwvpposition.xy / prevwvpposition.w) * 0.5 + 0.5;') frag.write('fragColor[2].rg = vec2(posa - posb);') + if mat_state.material.arm_ignore_irradiance: + frag.write('fragColor[2].b = 1.0;') + return con_mesh def make_raytracer(con_mesh): diff --git a/blender/arm/props.py b/blender/arm/props.py index a2d1f23d..47398aa3 100755 --- a/blender/arm/props.py +++ b/blender/arm/props.py @@ -331,6 +331,7 @@ def init_properties(): bpy.types.Material.arm_overlay = BoolProperty(name="Overlay", default=False) bpy.types.Material.arm_decal = BoolProperty(name="Decal", default=False) bpy.types.Material.arm_two_sided = BoolProperty(name="Two-Sided", description="Flip normal when drawing back-face", default=False) + bpy.types.Material.arm_ignore_irradiance = BoolProperty(name="Ignore Irradiance", description="Ignore irradiance for material", default=False) bpy.types.Material.arm_cull_mode = EnumProperty( items=[('none', 'Both', 'None'), ('clockwise', 'Front', 'Clockwise'), diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index 77ee693a..582ee734 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -446,6 +446,7 @@ class ARM_PT_MaterialPropsPanel(bpy.types.Panel): wrd = bpy.data.worlds['Arm'] columnb.enabled = len(wrd.arm_rplist) > 0 and arm.utils.get_rp().rp_renderer == 'Forward' columnb.prop(mat, 'arm_receive_shadow') + layout.prop(mat, 'arm_ignore_irradiance') layout.prop(mat, 'arm_two_sided') columnb = layout.column() columnb.enabled = not mat.arm_two_sided From d20656339b9d7aa9225c595af31a86cb330f2bb6 Mon Sep 17 00:00:00 2001 From: tong Date: Mon, 22 Feb 2021 15:50:19 +0100 Subject: [PATCH 39/74] Update custom icons --- blender/arm/custom_icons/bundle.png | Bin 245 -> 14010 bytes blender/arm/custom_icons/haxe.png | Bin 5127 -> 4631 bytes blender/arm/custom_icons/wasm.png | Bin 5019 -> 2886 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/blender/arm/custom_icons/bundle.png b/blender/arm/custom_icons/bundle.png index 832f0a8d8fc3ad9f91bfd39d8340dba7c4c53368..3947d5b14dd3ef8a5c5e35c75c1f69cd6f17e522 100644 GIT binary patch literal 14010 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 literal 245 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfv^#Gp`S0MfW|Noa@@aD~%*RNl{ zdi4rO0s&AA$cBKem-)5>mGYDX`2{nuitCv=#Fy4L?0=qZf-R!es<<1mq6o*na1fB9gMEsGcJloEY`l!yzT+dt|sFb zT;~>OTQonCS-=$1Xe-bdYH*++O@niegLsC6FoULpAc)d-5Cc*T8V*7;9Ab_zGdRed VRdm~QSr2F*gQu&X%Q~loCIGwQVEX_7 diff --git a/blender/arm/custom_icons/haxe.png b/blender/arm/custom_icons/haxe.png index 67702b2aa27c5336fd84414d5041e524758e61e1..7e2497a5fb903752395a42487d718c2a23b7826b 100644 GIT binary patch literal 4631 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 literal 5127 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 literal 5019 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 From dbee605e3f48c988167a2a09bdf25a14a355e7fb Mon Sep 17 00:00:00 2001 From: tong Date: Mon, 22 Feb 2021 18:05:28 +0100 Subject: [PATCH 40/74] Downscale icons to 32px --- blender/arm/custom_icons/bundle.png | Bin 14010 -> 6498 bytes blender/arm/custom_icons/haxe.png | Bin 4631 -> 1054 bytes blender/arm/custom_icons/wasm.png | Bin 2886 -> 725 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/blender/arm/custom_icons/bundle.png b/blender/arm/custom_icons/bundle.png index 3947d5b14dd3ef8a5c5e35c75c1f69cd6f17e522..7bad686512a3f71355ffce9ba51af2c333163853 100644 GIT binary patch delta 2662 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 U;9>-qTmS$707*qoM6N<$f@)99f&c&j delta 10127 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 diff --git a/blender/arm/custom_icons/haxe.png b/blender/arm/custom_icons/haxe.png index 7e2497a5fb903752395a42487d718c2a23b7826b..5f5a88cab4f60b791c2c0f7c2616346f5bb597a5 100644 GIT binary patch delta 1010 zcmV#Y zL_t(oh2@vuYg|PX$G>ML$u>XfX0b&iB%9EtDt)kdG{vAIsRTr65g)aGglH=WqT-8x zg5ZOQzfh@K-(#NiO~fBzvxy-Ei`O;1`(tkIo%#CE-F32i_gD9^^K|FTIp_Pihdbxa z0QdWl#zPo}Ka&itwK2&DNS^c7f44;By=*r7jfk*U(ps&yO7cyTPc>{L_nmWp8e?1( zMN@Nga~E0xeo#t1?gfM-x8pbtNzMXT2JkD%_j9@2M>{r5+yo%`P>;$^Dy7aVr5f|64-XGF!Z3uj zcAeyj9`QavQ%iaFJjuTYNV1zG$rG7O=1CDL^cx2tAem1C5NmB;iA3bLYPEWrWL89G z#>dC!lv2O+39t?C+w%e7f1ERehR_18kB!HK_PKK%4@wnFjC<$+G~;gNpYH zQQz8U0KDCd@g0I7_;GA(Oq5b9gNnBZ-Ko`T`_8#XTk`xwQZFnltTY;$Y6*PTS{n`{ zUXMww1j(|9yybV(V*t5a?n~#~zr%?4fVeY1KmWDwr&@vt;4=Vbe;Dx&5RumUW4{Zw z0RTWYoBcG7H11ObBFKVqXO-xMu zB_gGvRqH!~kQ4w{0GuFsZdizx?U=FFzC`kv=CMf-1kWp_PI?LM8KOfk znAzLgd#)pZh`gq?f1YkhG%!S$Ml?n8je4bz0|Y^^4&X7R)N)F~z9Bk^1AudGndJH1 z-Q8!}006u$B7Xpw>X5i6L^pAudCJ-ba4iksqKMq?{BGSLdWn1EHh_z108mQxJXWPa z3?L2w8GqsfP-~rfi0lS2pf~`Wf!CBqyoI8OEcTfMBp0;Sf8UqOWsqF&Hx58fM0VN$ zMC1TKH-JhMMZX(k!X!!lan5~FEEYdmTU&EGJ3E)O)>ld90E_~77{JL+iAWx_0gR50 z9{V1wwZYNR(T*{u8pmJdcYQd%lUl17W#;O7Bf- z1O*LMz(A-13WS#`<>uah-}+{)Gqcv2nP<+~YwtbJOg3XG@}Fo^V_jBeUS#hb2XJTf8IpbpNe?0mUKon$xiZdKsU798nXX1&SczwJ^w@T<~8OU z0Yv0 zrA1Mk+uz@}L71H*oX6b(lpBc~3H&&2mad6<$F9ie_=<043B(1-f{}5|PbP;4mgC>7 zi&(obUj_#)8*MAVzBmDOzJiU%yp|F>et>$3@ghc$n^1;lNtkBL9lGDRtN_e{U)$Q+ zrfwIHY+J91-PF~+4e9Zc;Nalk&L|AD&^bC0zMoiwiQ|iulS83qGKQ9As-@g{I%ENS zzs(mXRb?FhQNm~u4DIY*AbgJScI@z>elZJi5mA-4V3yiW!X84Vi<1tI%_VoB@Fh zE^-1EbHAP(@(|OxOCp$NRf#5$e_!^y0NhRe_GgW#!Z`2Pn3$G%g}dVUq+de`EvXCA z2+BTt8JiACQA_*+bEEAQmH44$EvQ2@X%J~5dOmtcNq>F*P|>r&aD8f&O5Z1AmlprIX*xhhwqkD>JK9LwHdgV->$SAr2E*b!zzOC zRUl^20MF1b4sjm`D?*3{jYkQFq94xqjSDA_@Q4fwvuIGJZ>TtNG zqs}&4;u39p=N7^#;EJ(lWU^V;ouNJjq)FqttL?km&~M70(v{rV@c2^(FAf?jY7taf zTmwmCRQliI;^LIYWr(fu+idQ%@?{G{V|U2JI6FEAq^i<2bdZU3~|{w)4i(OC7D zE583R{9s$jeXK}n|1l{d4ejpkp7ab4o7UYjO*p2ph7ItCHst$O z+&vrO{-!cJ!S+Pl7gSF<5#~zA7ZlU;z`!0%ijwOauitJDXOan^Wj<* z6ch|ftygC7+6jm%dPfa9;9u8rGABX$=jZWfUw0IpDkr?UWJ9IX41HZ(mYSY{+)|9_ zg%BXF_LiWG{O?QlMcUM?U%!3{45Z4CCrPAj``=egt~Gq`O@x@9SdWjIcem3m?lD(( zc6OF>M>AdVNt`H!DCbR|S<_#N_O9kJbZ{$Q_P5}pi~`4iCiP8y`3Rg`IAyd+z}Mu% z0Gvt4g6CPI?)qOY99W8Sgb)bb7g+yh#{bfrR4(dM9xVhdV;D~Je!k>rH8w>ek!)tY z`mO8JaPJl5-@`*$WbxGC-MHSWvij=D^}9f;V3mI?6DFJ9hcJZ=WdVSqkR^7`9Lm&_ z7fk4G(u%7+O!$}U%I(XH7%bluy@1?N8C!PE<^!N$L{00U3*oQ@dOAf!L|8oi$aJ&S znQ8+qyBURoT|ZAQ6fwF+Rn%bnxHAOTb^p2^cO*R@hFc>{Lz3*ngE}}lPCX{;WaGgb?IiG(*}Ka+4B71Mh8D&tkr#n z%D^DgL-~A08Dn~!Oz{1KN88AVY}xNgY&3v9Mc?~vJY?+(_^uaf0kdok+CdWAOSj&A z6pI*}s|O=aSm4u&6O@Oih${YF-=c{zaKsb<19$PnRiju3gHJmT$*o_g0}Cd?9G ztGrQDkaQSF;;CSjRaFfzL!aaw+CETywgN?~*t z8MMa=k1&bcL$nzg7@FRvV>BWi)n)Kxv&hQJQ`>=)r(cta!=gq{()N|hWjix#Bhd@? zyMJcDzu6f;w*49Ra-kceHrL>j;)u9HWOJTDPd(EYdgQ~))dfS2=}VgRk2U@Wj~HNA zp*9*tRgjQvqJ4^~VQ$i4uKZsHKp4TWw?4UDx2CFjb_=s3M=TCILywe(r5`5i0cggr%PQ+==s*eKJn8zxg zGDZJ@j^oZ6r+9(J@eUNi<3MvKEzC)S@5E~=1)XiVK$+TC4#Q&~nE-IYyXI%V5A((? z+gJerg+eX!W4`Y!(9gEO$-SvGj{ZF?sSWKOdTSZRR4GwZ| zHPiZ-7V$K?Z~pJ`1;jZho>9lyzQB}k)+uQ?P_iwSn`79{CcoN#r34S2;EzG!=nB2hd9a}T@K0}6HPK$SXne@__z+2}ymx^y%}rCV8h zCV5ydjxNAyAVoSX<`eS>K!Y4KG9%b-^5DDu(H=fNKJEe>`N?J>P$+bW;b~FN;2!HfOw6$nIU*uM+u*~_F zkjTi$Dx^IGu@xfUM~?&px>!}{o6z>+u=kr9L<^Cv5Dq?m{^=j%<9i|lRd$SO4P=z( z>L+=*C;ii%S9GDnm6lSQo`=^H0e#v?HCv=z^E%FL)CYpHFg{SulP=7{1 z;{piHyz1xDIHm>x5WWwb>4uWz(+1^8iXUJAyzyi>?*^+%n6H62Ox9@IUBB2*ec?cBiCX8y&&d`ZQ+3ku zfjnX30J7cI>O*K}=FWRjAGFaHAGMxt&ULks=jmN=Pt-!r;$p=&tkxcVY3ip3Z1LkJT-^uG&w9-69#l+f$5fpJ15}MK?t>OZ>!r3v7c=o{= z5gvfHhF5**^MDFSk{Ns6=WEH!;4GiMf1uI1lzm{hC@H-fX(Gkd+-^q+3Xs1hncT$R z!M&8l=BYJzMM?V&1^T`bIpp1PL>;hbh@Pv31vrG-)vMoaDi|<4*R;=hxGZ2`VlssC zqHFMiI?JaSY=Z|!>ImP8X5H^*`ka_6{(M8y_l8BqvrxgiMJW&NaqhmrETNq*E>&>3 z^*B|QW>s*lHo<5~<=&lF_HzN3yBXBlL?5N?)3gY+Y>4t3tSa+Xi=F!nBqk=7>MR&2 zG5hu!@wY#D&txsxW|S3ts?NYJ$p0*XoD7i~Xo^}02ajD#n-S%2y9|f9wl0q}?HQy< zSy5TV(gf-uw5(HxhE&uGcgXogIiR9mnl+s55l78!Y%X@g!YME1OoC1NZ(d3R%v68o z#gDVvaqo;MP|pucKcqEHLA~tI1aLyCIz#tb>(+`VS+&YYbcKG))6I=$*>2S_UMxP2 z<%wXZZa%%vE2CjO3UudbS#eroOdOm5Z2ZM9rGGMV zIeHjA^VWp^4N$dZ|2X>GIoz0&+)Fy?4E1N(j%7REXTUB7@$&$a0W8E^)3j_eP$U_MHa{Hy>)ZIx*Dfa$KuY6u3_ zcEj>bgQ9kQwD-I<#}MosA>|J@KRuh#a5)9qQL?1BM9Xr{0DKxto|labhrdvui^Rr? zU7uYy+#Z~A-p}!NAN|Y~O`sdyJu3Lsj`iHCW@ouWHWOFGzH&v)mNNindIA<#J^Ke? z!N$VUa#cxbrxyViLRu)B?3@M^_p+M*wi@?*Q+S}KnWL39uvmp8(=z^{^g=6Zn&vm$ z%J*LOno1uNglLiXa}=j1{5t=^c{s9_vXaWTO=h!XBWSE;RcCKqNfAm(P_~ zTgTGHGqk+TG3ilPZ!UQc=D^eO5s#HZnArJro*5dk@oul${Ym<}w*l6*x1SmysJg&S z$x1(juPwR?$#6MxWhC!c+@8;~BT<=TeWQ2NE~?B98By2Iu<3!-mQsSW&=08xSg_dq z2cyG5i=vp$f@{LirSs7jRkmL9U;R!y_j&(w0DPXJ+`WxH_+nG&`{fJ3#q!3J0r~o6 zzcLWhH_yuOqCpgctJ(N|{)zux+mmZpenv@~*!j$zI_wk{fKKoE+=B7ENjefSk!Y**p8rXw0G#XPa3&bjaYUb4%UoJ2c&kz~OzWIQC*?}gI zFy$b(n&Bu&!*TDXDAAjSo<+EnjRIwBpAYc5irwE-LilRoGKLojB}u$D8*eegwfj-5 zpzh4IK2Myp{MAbc(xxgyuJPHBVFRh8R0Gue+R%l=wGXF{Nh8v;v^^w;gU}3_e%Y9) zByc-z{i3;v2-HDGL(qGI?eTrweXkNJG3h7lkAPD{s>%82Zt!mmC-eRL_xU7Wx%`Bk z246Z@b&b~`V354^X_+vCA`*k1-PEiT=w-d)B?lbk*4OWS%To6boltzr;IlHE_q4UO z)gRIT?Wn?iYNAcWDiAQ!!{WIaUb6$hvx&Z^)+^c@Yyy=DoIdL*o2#WC z*wP9QIB8cBa}IAK4Wtt0cY=5k)UiJ^&3lT>vlD=rNj4UhI>1>qS!N}f9C?psgVRQ# zD(2ZQWN1oz7`*o<%Om^U_En$R4xNyY5HL5XiDQwN=|~U_PsT!K5m(oOkc}CHEyE41z$BEw)|P zo&MEA5Ck6qoXTP%8DRi0<@^5Bf6rnVhGzgwW;MwJq=?)Lf}nB$=Besk0L*NXqou>x z1+Zx#O90056k9oA0B{Vz#Y5Bq$k)=p5+mRUI0BA<`$6(f9LEKcD@M{JNv15za!3x0 zc0Z2ef~wv$H7ju(7fju?TtHQw@jNd|)AXg0wkIYgTE$}Vld8TqInO=Me~U=YnViR- z=S809MM;vZ0{D^{2;f{8hGPI;o2!3uU~LNkMC4Vw-98Fn0f36gdbiu1^nL$wrBe9{ z;7J~!1z>h(XQv5Z55PvZ+dbK8wZ>Jo0U!~PhHcvm0Biss9LMRa>J<^WVLGsy2Z%IH zYt?FX4?t5yHqta*wrzV>e?+zbymK697eL*ZKQ-e80OvcM&T*0tasfr8W~OV!VzH^J zHInmD6uklP%mC^pcirSJe?OJF>$*P4x0!*ex@gR9kxWT00+_2-t9vAGtLhPw%O-bC zRY~$H$$87NHjVx;GZ28+@AsWjsnk?eA-OjoA3iV#_iz7hL@tp`06ZFMhxu#!qxgTmrT;3v0oQd2-#GyrR{#J2 M07*qoM6N<$f>{?W(EtDd literal 2886 zcmd5;Yc$k*7ykW+Va6?&M0DJ9Kf<7ra%s>^E=8py8N#?Wim8#~GKp!t5fx?Jl7@1P za0)RKqKiUwl;bk#%ot?ICB|juo%7{g>;3S4fA?B@KWptzd+lfK{XFT%JX~d^)T96a zvX~=iFAn=;9(KBco}RyBuA!pW5+#mxSj(BLc3jyoyZ<&Tp|bq=S*5+L ztGt$5n6SFKZ)5vk___Y%^^Qhv)F!r}7>|PcdTI4r6R{r>zWX!3Uf8!w?t15cpwT|3 zF;HH7^ldsFLTRoh3e{_N=^|2aAg(kwi0)W_+HLuna!v8;9Sw|6V)6qHRXv}vBUpg8 z8k(`lYwn-1_~Z5;ihx{SQBiTWCgu@Ut3oR%t z?3LC|qFypBAW6*`0D0U40%k=d0&6Ufwr>e#RRRuB*5x4YiG&YDBLoWyW_zV+ODY#Jlp4x+` z8!^qBx$$iRHS!-->+0cE4P^0D^zs?dchu+!XNhmDFtE6|D34-XqiRSl7=f+7?E2jA zESoZIpwriCt~=hmG$|gi-viwJ_J0P=bH&VvJU&vpE2Dc;4DYdsu2Vy4Dmj^&Y^p$; z`fvQGTxT$G>I*$5OMYOG!c(DgBQf3SoE1~@5Bl7oo;OV1iMU-VE!OyqD&-NRr@(Zc zSQXYdx?AY1x`}#CR0BV3;Uu$-M&pUlxl&Ai}OD}9l zEB6PgoKzKzuR)w#YfnZpn^u-FH{6h9s@y$nnMZC7gCR6l*fO}f?I8eq$oGTo^LD}ICSST4@&y`s~C3(?iVbDmqt?~qE{>n2OjsfPvN z&#y@a$OwTzZ3+ihIDb^_bq)Xyh`>s*41NJsuw!asb!!+J&7gJK6K(Jw*1{*OE907w zDS6RGTTO<(P0I`_c15YVxGu+E=ZS;zD( zC2dE!R0dnXO8lRgJY;gYTtyV4a@uCUt!+zASF)RSTzCI`r#=HsHw8~{j3gD5Fo%|f zTDoXdwrKRXuC6W$)aG>`DR7f=KwLZ#ilSfbik$!Y^=mS8%>_lXJXiLX+ILZQ!SMy9av2Ho#=tbxRZQz7?O= zZR_w1vVsC|qM7=`R8>{;;B|Ng?wQ!W0|ySE(Ij<__e!GBI`=B=lDcbFWhxRh`|kjOBCLCnd?$$VKY zcoz3=>*2$Pr$8#Dy&^jt;!3^b5}0i6#9LTh;8KX_^+y;_mbQuwLtDszL-&(aITM57 zV;>dPHyIu6<}%x=WR7PpCb$XR?}S5I?ec%K+3fPR4X=tJ3_5bWsd#F-_F|EwmT0rN zK1`byK^9dB? z^j3R&`!lOp^I|ZwnEdxfzWk(5xuCcDEbkJL_@b_{k$zyyEv;G_H2KaMk|&JZR3O7W zI4fS8tDz6fiHgCSJ&qbM?jl0OKmx(z_vWtEEd$xapy}D!-%SuH-k|aLxrX3_@UU5O z!QN<(>7NrP6RBKkIm=ekfdr3&G0i%c{na!zqt^j8-#qr{paTb9at4`^nK|Zv`t({; zx#!?TOVm6vg;kXK#ML0K5+SBzNfBm_3(stGqTVf*!~2Vrtb|ZOZvmE~ErGqGlW*7f+q%N-V@1ZkO@_FUF09G!=lFPrtmPSRXlT=FP6D7PI=s0gofn zmG4G0Y?nLG?Fo26kycLD@nKMzoP8vtqaXw2sIl5PpizXd`XG_ks=%)ACHFC zl2WE?IpSo`Dj8{M>15u_Obfh0YcHDP4dgd|yZUL)ZtSHe+VdmvDNR=Ey`|O7p3=Tw zY!p7(CXbN&PG3$1;KeiFnr_OF7J_$}xH94prAGZAdnh=}>)q5+xu>>)VwBjyqN1Xa zqmODvlt+zVuQ5is2G?Y}+Lvw*LC15r!(V9p4#Cz&bVXF4ss=cgp?(;ykmwd#^(mt7 z!B=BHweQ;@D~)+$%;E>#QaxOkNEnvn%oJZ3EjzqaTs+8$Lx6MDU>9)YyD0LIw^UF#H(@^21^N__wT From 3e15ed9b5c80564564f6b5dcba947099aeb171cd Mon Sep 17 00:00:00 2001 From: tong Date: Thu, 25 Feb 2021 14:06:31 +0100 Subject: [PATCH 41/74] Write debug param by default --- blender/arm/write_data.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/blender/arm/write_data.py b/blender/arm/write_data.py index 0803f273..9f88e335 100755 --- a/blender/arm/write_data.py +++ b/blender/arm/write_data.py @@ -253,6 +253,9 @@ project.addSources('Sources'); if wrd.arm_debug_console: assets.add_khafile_def('arm_debug') khafile.write(add_shaders(sdk_path + "/armory/Shaders/debug_draw/**", rel_path=do_relpath_sdk)) + + if not is_publish and state.target == 'html5': + khafile.write("project.addParameter('--debug');\n") if wrd.arm_verbose_output: khafile.write("project.addParameter('--times');\n") From 4dbf8a3b2a6768d2f691c24500b925aac0df65a9 Mon Sep 17 00:00:00 2001 From: luboslenco Date: Mon, 1 Mar 2021 09:07:33 +0100 Subject: [PATCH 42/74] Bump version --- blender/arm/props.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blender/arm/props.py b/blender/arm/props.py index 47398aa3..72df10b8 100755 --- a/blender/arm/props.py +++ b/blender/arm/props.py @@ -11,7 +11,7 @@ import arm.proxy import arm.utils # Armory version -arm_version = '2021.2' +arm_version = '2021.3' arm_commit = '$Id$' def get_project_html5_copy(self): From c07de1cc25ab1816d1a0d57c72a3ec4db2d1a5aa Mon Sep 17 00:00:00 2001 From: N8n5h Date: Thu, 25 Feb 2021 15:09:23 -0300 Subject: [PATCH 43/74] Remove the inversion in shader of spot light uv for atlas With the change in iron, there is no longer a need to do this in the shader. --- Shaders/std/light.glsl | 6 +----- Shaders/std/light_mobile.glsl | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/Shaders/std/light.glsl b/Shaders/std/light.glsl index aa3024ae..61c26771 100644 --- a/Shaders/std/light.glsl +++ b/Shaders/std/light.glsl @@ -185,17 +185,13 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co #ifdef _Clusters vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0); #ifdef _ShadowMapAtlas - vec3 uv = lPos.xyz / lPos.w; - #ifdef _InvY - uv.y = 1.0 - uv.y; // invert Y coordinates for direct3d coordinate system - #endif direct *= shadowTest( #ifndef _SingleAtlas shadowMapAtlasSpot #else shadowMapAtlas #endif - , uv, bias + , lPos.xyz / lPos.w, bias ); #else if (index == 0) direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias); diff --git a/Shaders/std/light_mobile.glsl b/Shaders/std/light_mobile.glsl index e547129c..f6a966c9 100644 --- a/Shaders/std/light_mobile.glsl +++ b/Shaders/std/light_mobile.glsl @@ -80,17 +80,13 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co #ifdef _Clusters vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0); #ifdef _ShadowMapAtlas - vec3 uv = lPos.xyz / lPos.w; - #ifdef _InvY - uv.y = 1.0 - uv.y; // invert Y coordinates for direct3d coordinate system - #endif direct *= shadowTest( #ifndef _SingleAtlas shadowMapAtlasSpot #else shadowMapAtlas #endif - , uv, bias + , lPos.xyz / lPos.w, bias ); #else if (index == 0) direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias); From 5f8b92f9c3bb2f0d03207a462f14b15db0104cd4 Mon Sep 17 00:00:00 2001 From: N8n5h Date: Mon, 1 Mar 2021 01:16:17 -0300 Subject: [PATCH 44/74] Applied flip workaround for point light too for html5 and atlas The same concept for spot lights was applied for point lights; the difference is that because point lights require to do the projection in the shader, the first inversion was applied in sampleCube() when returning the uv coordinates. "_FlipY" was added to replace "_InvY" in "PCFFakeCube()" because the inversion is only necessary for shaders and not anything else, otherwise it would break rendering of other parts. --- Shaders/std/shadows.glsl | 23 ++++++++++++++--------- blender/arm/make.py | 20 ++++++++++---------- blender/arm/write_data.py | 3 +++ 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/Shaders/std/shadows.glsl b/Shaders/std/shadows.glsl index baf940a4..8aa053f3 100755 --- a/Shaders/std/shadows.glsl +++ b/Shaders/std/shadows.glsl @@ -42,7 +42,12 @@ vec2 sampleCube(vec3 dir, out int faceIndex) { uv = vec2(dir.x < 0.0 ? dir.z : -dir.z, -dir.y); } // downscale uv a little to hide seams + // transform coordinates from clip space to texture space + #ifndef _FlipY return uv * 0.9976 * ma + 0.5; + #else + return vec2(uv.x * ma, uv.y * -ma) * 0.9976 + 0.5; + #endif } #endif @@ -189,7 +194,7 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float vec4 pointLightTile = pointLightDataArray[lightIndex + faceIndex]; // x: tile X offset, y: tile Y offset, z: tile size relative to atlas vec2 uvtiled = pointLightTile.z * uv + pointLightTile.xy; - #ifdef _InvY + #ifdef _FlipY uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system #endif @@ -199,7 +204,7 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(-1.0, 0.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; - #ifdef _InvY + #ifdef _FlipY uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system #endif result += texture(shadowMap, vec3(pointLightTile.z * uvtiled + pointLightTile.xy, compare)); @@ -207,7 +212,7 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(-1.0, 1.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; - #ifdef _InvY + #ifdef _FlipY uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system #endif result += texture(shadowMap, vec3(uvtiled, compare)); @@ -215,7 +220,7 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(0.0, -1.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; - #ifdef _InvY + #ifdef _FlipY uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system #endif result += texture(shadowMap, vec3(uvtiled, compare)); @@ -223,7 +228,7 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(-1.0, -1.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; - #ifdef _InvY + #ifdef _FlipY uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system #endif result += texture(shadowMap, vec3(uvtiled, compare)); @@ -231,7 +236,7 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(0.0, 1.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; - #ifdef _InvY + #ifdef _FlipY uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system #endif result += texture(shadowMap, vec3(uvtiled, compare)); @@ -239,7 +244,7 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(1.0, -1.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; - #ifdef _InvY + #ifdef _FlipY uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system #endif result += texture(shadowMap, vec3(uvtiled, compare)); @@ -247,7 +252,7 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(1.0, 0.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; - #ifdef _InvY + #ifdef _FlipY uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system #endif result += texture(shadowMap, vec3(uvtiled, compare)); @@ -255,7 +260,7 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(1.0, 1.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; - #ifdef _InvY + #ifdef _FlipY uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system #endif result += texture(shadowMap, vec3(uvtiled, compare)); diff --git a/blender/arm/make.py b/blender/arm/make.py index c00fcc52..4538a9e6 100755 --- a/blender/arm/make.py +++ b/blender/arm/make.py @@ -211,7 +211,7 @@ def export_data(fp, sdk_path): resx, resy = arm.utils.get_render_resolution(arm.utils.get_active_scene()) if wrd.arm_write_config: write_data.write_config(resx, resy) - + # Change project version (Build, Publish) if (not state.is_play) and (wrd.arm_project_version_autoinc): wrd.arm_project_version = arm.utils.arm.utils.change_version_project(wrd.arm_project_version) @@ -654,12 +654,12 @@ def build_success(): state.proc_publish_build = run_proc(cmd, done_gradlew_build) else: print('\nBuilding APK Warning: ANDROID_SDK_ROOT is not specified in environment variables and "Android SDK Path" setting is not specified in preferences: \n- If you specify an environment variable ANDROID_SDK_ROOT, then you need to restart Blender;\n- If you specify the setting "Android SDK Path" in the preferences, then repeat operation "Publish"') - + # HTML5 After Publish if target_name.startswith('html5'): if len(arm.utils.get_html5_copy_path()) > 0 and (wrd.arm_project_html5_copy): project_name = arm.utils.safesrc(wrd.arm_project_name +'-'+ wrd.arm_project_version) - dst = os.path.join(arm.utils.get_html5_copy_path(), project_name) + dst = os.path.join(arm.utils.get_html5_copy_path(), project_name) if os.path.exists(dst): shutil.rmtree(dst) try: @@ -673,10 +673,10 @@ def build_success(): link_html5_app = arm.utils.get_link_web_server() +'/'+ project_name print("Running a browser with a link " + link_html5_app) webbrowser.open(link_html5_app) - + # Windows After Publish if target_name.startswith('windows'): - list_vs = [] + list_vs = [] err = '' # Print message project_name = arm.utils.safesrc(wrd.arm_project_name +'-'+ wrd.arm_project_version) @@ -707,18 +707,18 @@ def build_success(): for vs in list_vs: print('- ' + vs[1] + ' (version ' + vs[3] +')') return - # Current VS + # Current VS vs_path = '' for vs in list_vs: if vs[0] == wrd.arm_project_win_list_vs: vs_path = vs[2] break - # Open in Visual Studio + # Open in Visual Studio if int(wrd.arm_project_win_build) == 1: cmd = os.path.join('start "' + vs_path, 'Common7', 'IDE', 'devenv.exe" "' + os.path.join(project_path, project_name + '.sln"')) subprocess.Popen(cmd, shell=True) # Compile - if int(wrd.arm_project_win_build) > 1: + if int(wrd.arm_project_win_build) > 1: bits = '64' if wrd.arm_project_win_build_arch == 'x64' else '32' # vcvars cmd = os.path.join(vs_path, 'VC', 'Auxiliary', 'Build', 'vcvars' + bits + '.bat') @@ -792,7 +792,7 @@ def done_vs_vars(): # MSBuild wrd = bpy.data.worlds['Arm'] list_vs, err = arm.utils.get_list_installed_vs(True, True, True) - # Current VS + # Current VS vs_path = '' vs_name = '' for vs in list_vs: @@ -851,7 +851,7 @@ def done_vs_build(): os.chdir(res_path) # set work folder subprocess.Popen(cmd, shell=True) # Open Build Directory - if wrd.arm_project_win_build_open: + if wrd.arm_project_win_build_open: arm.utils.open_folder(path) state.redraw_ui = True else: diff --git a/blender/arm/write_data.py b/blender/arm/write_data.py index 9f88e335..439e2984 100755 --- a/blender/arm/write_data.py +++ b/blender/arm/write_data.py @@ -537,6 +537,9 @@ def write_compiledglsl(defs, make_variants): #endif """) + if state.target == 'html5': + f.write("#define _FlipY\n") + f.write("""const float PI = 3.1415926535; const float PI2 = PI * 2.0; const vec2 shadowmapSize = vec2(""" + str(shadowmap_size) + """, """ + str(shadowmap_size) + """); From cf99e5a38288bf5672abe1c830ad3767b06bae89 Mon Sep 17 00:00:00 2001 From: N8n5h Date: Mon, 1 Mar 2021 18:37:54 -0300 Subject: [PATCH 45/74] Fixed point lights breaking for Krom Windows with shadow map atlas https://github.com/armory3d/armory/issues/2110#issuecomment-787913813 --- Shaders/std/shadows.glsl | 8 ++++++-- blender/arm/write_data.py | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Shaders/std/shadows.glsl b/Shaders/std/shadows.glsl index 8aa053f3..cafde49e 100755 --- a/Shaders/std/shadows.glsl +++ b/Shaders/std/shadows.glsl @@ -44,9 +44,13 @@ vec2 sampleCube(vec3 dir, out int faceIndex) { // downscale uv a little to hide seams // transform coordinates from clip space to texture space #ifndef _FlipY - return uv * 0.9976 * ma + 0.5; + return uv * 0.9976 * ma + 0.5; #else - return vec2(uv.x * ma, uv.y * -ma) * 0.9976 + 0.5; + #ifdef HLSL + return uv * 0.9976 * ma + 0.5; + #else + return vec2(uv.x * ma, uv.y * -ma) * 0.9976 + 0.5; + #endif #endif } #endif diff --git a/blender/arm/write_data.py b/blender/arm/write_data.py index 439e2984..c2d1c54d 100755 --- a/blender/arm/write_data.py +++ b/blender/arm/write_data.py @@ -537,7 +537,7 @@ def write_compiledglsl(defs, make_variants): #endif """) - if state.target == 'html5': + if state.target == 'html5' or arm.utils.get_gapi() == 'direct3d11': f.write("#define _FlipY\n") f.write("""const float PI = 3.1415926535; From 72dde10fb0ed3837bbfe0c090e3f66813a9547ac Mon Sep 17 00:00:00 2001 From: N8n5h Date: Tue, 2 Mar 2021 19:57:40 -0300 Subject: [PATCH 46/74] Fix forward+single atlas failing to compile with just 1 light This should fix an issue where the uniform for shadowMapAtlas used with the single atlas option was not added when having just 1 light. --- blender/arm/material/make_cluster.py | 13 +++++++++---- blender/arm/material/make_mesh.py | 4 ++-- blender/arm/material/shader.py | 3 +++ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/blender/arm/material/make_cluster.py b/blender/arm/material/make_cluster.py index 34589d42..1ac91d5f 100644 --- a/blender/arm/material/make_cluster.py +++ b/blender/arm/material/make_cluster.py @@ -4,7 +4,7 @@ def write(vert, frag): wrd = bpy.data.worlds['Arm'] is_shadows = '_ShadowMap' in wrd.world_defs is_shadows_atlas = '_ShadowMapAtlas' in wrd.world_defs - is_single_atlas = is_shadows_atlas and '_SingleAtlas' in wrd.world_defs + is_single_atlas = '_SingleAtlas' in wrd.world_defs frag.add_include_front('std/clusters.glsl') frag.add_uniform('vec2 cameraProj', link='_cameraPlaneProj') @@ -17,6 +17,8 @@ def write(vert, frag): if is_shadows_atlas: if not is_single_atlas: frag.add_uniform('sampler2DShadow shadowMapAtlasPoint', included=True) + else: + frag.add_uniform('sampler2DShadow shadowMapAtlas', top=True) frag.add_uniform('vec4 pointLightDataArray[maxLightsCluster]', link='_pointLightsAtlasArray', included=True) else: frag.add_uniform('samplerCubeShadow shadowMapPoint[4]', included=True) @@ -36,9 +38,12 @@ def write(vert, frag): frag.write('int numSpots = int(texelFetch(clustersData, ivec2(clusterI, 1 + maxLightsCluster), 0).r * 255);') frag.write('int numPoints = numLights - numSpots;') if is_shadows: - if is_shadows_atlas and not is_single_atlas: - frag.add_uniform(f'sampler2DShadow shadowMapAtlasSpot', included=True) - elif not is_shadows_atlas: + if is_shadows_atlas: + if not is_single_atlas: + frag.add_uniform('sampler2DShadow shadowMapAtlasSpot', included=True) + else: + frag.add_uniform('sampler2DShadow shadowMapAtlas', top=True) + else: frag.add_uniform('sampler2DShadow shadowMapSpot[4]', included=True) # FIXME: type is actually mat4, but otherwise it will not be set as floats when writing the shaders' json files frag.add_uniform('vec4 LWVPSpotArray[maxLightsCluster]', link='_biasLightWorldViewProjectionMatrixSpotArray', included=True) diff --git a/blender/arm/material/make_mesh.py b/blender/arm/material/make_mesh.py index c64ec408..9ec18bbe 100644 --- a/blender/arm/material/make_mesh.py +++ b/blender/arm/material/make_mesh.py @@ -358,9 +358,9 @@ def make_forward_mobile(con_mesh): is_shadows = '_ShadowMap' in wrd.world_defs is_shadows_atlas = '_ShadowMapAtlas' in wrd.world_defs - is_single_atlas = is_shadows_atlas and '_SingleAtlas' in wrd.world_defs shadowmap_sun = 'shadowMap' if is_shadows_atlas: + is_single_atlas = '_SingleAtlas' in wrd.world_defs shadowmap_sun = 'shadowMapAtlasSun' if not is_single_atlas else 'shadowMapAtlas' frag.add_uniform('vec2 smSizeUniform', '_shadowMapSize', included=True) frag.write('vec3 direct = vec3(0.0);') @@ -575,7 +575,7 @@ def make_forward_base(con_mesh, parse_opacity=False, transluc_pass=False): arm_discard = mat_state.material.arm_discard make_base(con_mesh, parse_opacity=(parse_opacity or arm_discard)) - + blend = mat_state.material.arm_blending vert = con_mesh.vert diff --git a/blender/arm/material/shader.py b/blender/arm/material/shader.py index 48539e96..999ea293 100644 --- a/blender/arm/material/shader.py +++ b/blender/arm/material/shader.py @@ -223,6 +223,9 @@ class Shader: self.outs.append(s) def add_uniform(self, s, link=None, included=False, top=False): + # prevent duplicates + if s in self.uniforms or s in self.uniforms_top: + return ar = s.split(' ') # layout(RGBA8) image3D voxels utype = ar[-2] From 4009d9d3fe0e7191fd6cfc20774b61fc20200417 Mon Sep 17 00:00:00 2001 From: Alexander Kleemann Date: Wed, 10 Mar 2021 20:48:46 +0100 Subject: [PATCH 47/74] Fix for an error with NoneType materials Solves an error where an unallocated material slot caused a bug --- blender/arm/make_renderpath.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/blender/arm/make_renderpath.py b/blender/arm/make_renderpath.py index 051ec4dc..ef296d21 100755 --- a/blender/arm/make_renderpath.py +++ b/blender/arm/make_renderpath.py @@ -373,8 +373,11 @@ def build(): if obj.type == "MESH": for slot in obj.material_slots: mat = slot.material - if mat.arm_ignore_irradiance: - ignoreIrr = True + + if mat: #Check if not NoneType + + if mat.arm_ignore_irradiance: + ignoreIrr = True if ignoreIrr: wrd.world_defs += '_IgnoreIrr' From e5d6c47e3e10ffd66a9ae5024fb444cc0de5da6c Mon Sep 17 00:00:00 2001 From: Alexander Kleemann Date: Wed, 10 Mar 2021 20:49:51 +0100 Subject: [PATCH 48/74] Fix Blender exit error (Keymap winman hangs) --- blender/arm/keymap.py | 2 ++ blender/arm/lightmapper/keymap/__init__.py | 7 ------- blender/arm/props_bake.py | 6 ++---- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/blender/arm/keymap.py b/blender/arm/keymap.py index b4a019ea..77e09183 100644 --- a/blender/arm/keymap.py +++ b/blender/arm/keymap.py @@ -17,6 +17,8 @@ def register(): km = addon_keyconfig.keymaps.new(name='Window', space_type='EMPTY', region_type="WINDOW") km.keymap_items.new(props_ui.ArmoryPlayButton.bl_idname, type='F5', value='PRESS') + km.keymap_items.new("tlm.build_lightmaps", type='F6', value='PRESS') + km.keymap_items.new("tlm.clean_lightmaps", type='F7', value='PRESS') arm_keymaps.append(km) def unregister(): diff --git a/blender/arm/lightmapper/keymap/__init__.py b/blender/arm/lightmapper/keymap/__init__.py index 079b3c5d..e69de29b 100644 --- a/blender/arm/lightmapper/keymap/__init__.py +++ b/blender/arm/lightmapper/keymap/__init__.py @@ -1,7 +0,0 @@ -from . import keymap - -def register(): - keymap.register() - -def unregister(): - keymap.unregister() \ No newline at end of file diff --git a/blender/arm/props_bake.py b/blender/arm/props_bake.py index 25bff93a..e433e682 100644 --- a/blender/arm/props_bake.py +++ b/blender/arm/props_bake.py @@ -3,7 +3,7 @@ import arm.assets import bpy from bpy.types import Menu, Panel, UIList from bpy.props import * -from arm.lightmapper import operators, properties, utility, keymap +from arm.lightmapper import operators, properties, utility class ArmBakeListItem(bpy.types.PropertyGroup): obj: PointerProperty(type=bpy.types.Object, description="The object to bake") @@ -361,7 +361,6 @@ def register(): operators.register() properties.register() - keymap.register() def unregister(): bpy.utils.unregister_class(ArmBakeListItem) @@ -380,5 +379,4 @@ def unregister(): #Unregister lightmapper operators.unregister() - properties.unregister() - keymap.unregister() \ No newline at end of file + properties.unregister() \ No newline at end of file From 8e972f75fd25933f5a6ece83e1137573b2023383 Mon Sep 17 00:00:00 2001 From: N8n5h Date: Sat, 13 Mar 2021 13:24:53 -0300 Subject: [PATCH 49/74] Fix light visual artifact when close to the camera This fixes a current problem with lights that occurs when getting too close to the camera. The issue is related to the clustering algorithm and cluster near. Cluster near explanation: Because of the logarithmic nature of how the Z slice is calculated, clusters slices get "burned" too quickly at the start of the frustum, which is not ideal. To aid this, and offset is applied to the start of the cluster frustum, and that is clusterNear. Cluster near solves that problem but creates another one: There is a gap between the near plane and the cluster near offset that gets completely ignored when calculating. And this results in the visual artifact of slices not being affected by light whatsoever. The proposed solution is make the gap resolve to the first slice of Z clusters. --- Shaders/std/clusters.glsl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Shaders/std/clusters.glsl b/Shaders/std/clusters.glsl index 04570ef6..9a8b66e6 100644 --- a/Shaders/std/clusters.glsl +++ b/Shaders/std/clusters.glsl @@ -8,6 +8,10 @@ int getClusterI(vec2 tc, float viewz, vec2 cameraPlane) { float z = log(viewz - cnear + 1.0) / log(cameraPlane.y - cnear + 1.0); sliceZ = int(z * (clusterSlices.z - 1)) + 1; } + // address gap between near plane and cluster near offset + else if (viewz >= cameraPlane.x) { + sliceZ = 1; + } return int(tc.x * clusterSlices.x) + int(int(tc.y * clusterSlices.y) * clusterSlices.x) + int(sliceZ * clusterSlices.x * clusterSlices.y); From 404a1248a1f371c3f29b1a4f6846ff7c3346338b Mon Sep 17 00:00:00 2001 From: N8n5h Date: Sat, 13 Mar 2021 13:25:34 -0300 Subject: [PATCH 50/74] Make clusterNear be the same as in LightObject --- blender/arm/write_data.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blender/arm/write_data.py b/blender/arm/write_data.py index c2d1c54d..4d2aadda 100755 --- a/blender/arm/write_data.py +++ b/blender/arm/write_data.py @@ -253,7 +253,7 @@ project.addSources('Sources'); if wrd.arm_debug_console: assets.add_khafile_def('arm_debug') khafile.write(add_shaders(sdk_path + "/armory/Shaders/debug_draw/**", rel_path=do_relpath_sdk)) - + if not is_publish and state.target == 'html5': khafile.write("project.addParameter('--debug');\n") @@ -711,7 +711,7 @@ const float voxelgiAperture = """ + str(round(rpdat.arm_voxelgi_aperture * 100) f.write( """const int maxLights = """ + max_lights + """; const int maxLightsCluster = """ + max_lights_clusters + """; -const float clusterNear = 4.0; +const float clusterNear = 3.0; """) f.write(add_compiledglsl + '\n') # External defined constants From 7d9c6ce7c7bfe17b4b610ccd279dd99943269aa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 13 Mar 2021 21:12:12 +0100 Subject: [PATCH 51/74] Make @prop recognition much more robust --- blender/arm/props_traits.py | 3 +- blender/arm/props_traits_props.py | 1 + blender/arm/utils.py | 178 ++++++++++++++---------------- 3 files changed, 83 insertions(+), 99 deletions(-) diff --git a/blender/arm/props_traits.py b/blender/arm/props_traits.py index a11beade..7f69612e 100755 --- a/blender/arm/props_traits.py +++ b/blender/arm/props_traits.py @@ -882,9 +882,10 @@ def draw_traits(layout, obj, is_object): if item.arm_traitpropswarnings: box = layout.box() box.label(text=f"Warnings ({len(item.arm_traitpropswarnings)}):", icon="ERROR") + col = box.column(align=True) for warning in item.arm_traitpropswarnings: - box.label(text=warning.warning) + col.label(text=f'"{warning.propName}": {warning.warning}') propsrow = layout.row() propsrows = max(len(item.arm_traitpropslist), 6) diff --git a/blender/arm/props_traits_props.py b/blender/arm/props_traits_props.py index ea9dc23d..24931612 100644 --- a/blender/arm/props_traits_props.py +++ b/blender/arm/props_traits_props.py @@ -32,6 +32,7 @@ def filter_objects(item, b_object): class ArmTraitPropWarning(bpy.types.PropertyGroup): + propName: StringProperty(name="Property Name") warning: StringProperty(name="Warning") diff --git a/blender/arm/utils.py b/blender/arm/utils.py index 82ff4f9f..305f79da 100755 --- a/blender/arm/utils.py +++ b/blender/arm/utils.py @@ -4,7 +4,7 @@ import os import platform import re import subprocess -from typing import Any +from typing import Any, Dict, List, Optional, Tuple import webbrowser import shlex import locale @@ -292,116 +292,97 @@ def fetch_bundled_script_names(): for file in glob.glob('*.hx'): wrd.arm_bundled_scripts_list.add().name = file.rsplit('.', 1)[0] + script_props = {} script_props_defaults = {} -script_warnings = {} -def fetch_script_props(file): - with open(file, encoding="utf-8") as f: - name = file.rsplit('.', 1)[0] - if 'Sources' in name: - name = name[name.index('Sources') + 8:] - if '/' in name: - name = name.replace('/', '.') - if '\\' in file: - name = name.replace('\\', '.') +script_warnings: Dict[str, List[Tuple[str, str]]] = {} # Script name -> List of (identifier, warning message) - script_props[name] = [] - script_props_defaults[name] = [] - script_warnings[name] = [] +# See https://regex101.com/r/bbrCzN/7 +RX_MODIFIERS = r'(?P(?:public\s+|private\s+|static\s+|inline\s+|final\s+)*)?' # Optional modifiers +RX_IDENTIFIER = r'(?P[_$a-z]+[_a-z0-9]*)' # Variable name, follow Haxe rules +RX_TYPE = r'(?::\s+(?P[_a-z]+[\._a-z0-9]*))?' # Optional type annotation +RX_VALUE = r'(?:\s*=\s*(?P(?:\".*\")|(?:[^;]+)|))?' # Optional default value - lines = f.read().splitlines() +PROP_REGEX_RAW = fr'@prop\s+{RX_MODIFIERS}var\s+{RX_IDENTIFIER}{RX_TYPE}{RX_VALUE};' +PROP_REGEX = re.compile(PROP_REGEX_RAW, re.IGNORECASE) +def fetch_script_props(filename: str): + """Parses @prop declarations from the given Haxe script.""" + with open(filename, 'r', encoding='utf-8') as sourcefile: + source = sourcefile.read() - # Read next line - read_prop = False - for lineno, line in enumerate(lines): - # enumerate() starts with 0 - lineno += 1 + if source == '': + return - if not read_prop: - read_prop = line.lstrip().startswith('@prop') + name = filename.rsplit('.', 1)[0] + + # Convert the name into a package path relative to the "Sources" dir + if 'Sources' in name: + name = name[name.index('Sources') + 8:] + if '/' in name: + name = name.replace('/', '.') + if '\\' in filename: + name = name.replace('\\', '.') + + script_props[name] = [] + script_props_defaults[name] = [] + script_warnings[name] = [] + + for match in re.finditer(PROP_REGEX, source): + + p_modifiers: Optional[str] = match.group('modifiers') + p_identifier: str = match.group('identifier') + p_type: Optional[str] = match.group('type') + p_default_val: Optional[str] = match.group('value') + + if p_modifiers is not None: + if 'static' in p_modifiers: + script_warnings[name].append((p_identifier, 'static properties may result in unwanted behaviour!')) + if 'inline' in p_modifiers: + script_warnings[name].append((p_identifier, 'inline properties are not supported!')) + continue + if 'final' in p_modifiers: + script_warnings[name].append((p_identifier, 'final properties are not supported!')) continue - if read_prop: - if 'var ' in line: - # Line of code - code_ref = line.split('var ')[1].split(';')[0] - else: - script_warnings[name].append(f"Line {lineno - 1}: Unused @prop") - read_prop = line.lstrip().startswith('@prop') - continue + # Property type is annotated + if p_type is not None: + if p_type.startswith("iron.object."): + p_type = p_type[12:] + elif p_type.startswith("iron.math."): + p_type = p_type[10:] - valid_prop = False + type_default_val = get_type_default_value(p_type) + if type_default_val is None: + script_warnings[name].append((p_identifier, f'unsupported type "{p_type}"!')) + continue - # Declaration = Assignment; - var_sides = code_ref.split('=') - # DeclarationName: DeclarationType - decl_sides = var_sides[0].split(':') + # Default value exists + if p_default_val is not None: + # Remove string quotes + p_default_val = p_default_val.replace('\'', '').replace('"', '') + else: + p_default_val = type_default_val - prop_name = decl_sides[0].strip() + # Default value is given instead, try to infer the properties type from it + elif p_default_val is not None: + p_type = get_prop_type_from_value(p_default_val) - if 'static ' in line: - # Static properties can be overwritten multiple times - # from multiple property lists - script_warnings[name].append(f"Line {lineno} (\"{prop_name}\"): Static properties may result in undefined behaviours!") + # Type is not recognized + if p_type is None: + script_warnings[name].append((p_identifier, 'could not infer property type from given value!')) + continue + if p_type == "String": + p_default_val = p_default_val.replace('\'', '').replace('"', '') - # If the prop type is annotated in the code - # (= declaration has two parts) - if len(decl_sides) > 1: - prop_type = decl_sides[1].strip() - if prop_type.startswith("iron.object."): - prop_type = prop_type[12:] - elif prop_type.startswith("iron.math."): - prop_type = prop_type[10:] + else: + script_warnings[name].append((p_identifier, 'missing type or default value!')) + continue - # Default value exists - if len(var_sides) > 1 and var_sides[1].strip() != "": - # Type is not supported - if get_type_default_value(prop_type) is None: - script_warnings[name].append(f"Line {lineno} (\"{prop_name}\"): Type {prop_type} is not supported!") - read_prop = False - continue + # Register prop + prop = (p_identifier, p_type) + script_props[name].append(prop) + script_props_defaults[name].append(p_default_val) - prop_value = var_sides[1].replace('\'', '').replace('"', '').strip() - - else: - prop_value = get_type_default_value(prop_type) - - # Type is not supported - if prop_value is None: - script_warnings[name].append(f"Line {lineno} (\"{prop_name}\"): Type {prop_type} is not supported!") - read_prop = False - continue - - valid_prop = True - - # Default value exists - elif len(var_sides) > 1 and var_sides[1].strip() != "": - prop_value = var_sides[1].strip() - prop_type = get_prop_type_from_value(prop_value) - - # Type is not recognized - if prop_type is None: - script_warnings[name].append(f"Line {lineno} (\"{prop_name}\"): Property type not recognized!") - read_prop = False - continue - if prop_type == "String": - prop_value = prop_value.replace('\'', '').replace('"', '') - - valid_prop = True - - else: - script_warnings[name].append(f"Line {lineno} (\"{prop_name}\"): Not a valid property!") - read_prop = False - continue - - prop = (prop_name, prop_type) - - # Register prop - if valid_prop: - script_props[name].append(prop) - script_props_defaults[name].append(prop_value) - - read_prop = False def get_prop_type_from_value(value: str): """ @@ -555,7 +536,8 @@ def fetch_prop(o): item.arm_traitpropswarnings.clear() for warning in warnings: entry = item.arm_traitpropswarnings.add() - entry.warning = warning + entry.propName = warning[0] + entry.warning = warning[1] def fetch_bundled_trait_props(): # Bundled script props @@ -1096,4 +1078,4 @@ def register(local_sdk=False): use_local_sdk = local_sdk def unregister(): - pass \ No newline at end of file + pass From 4ad68304e52f9dd21627cc4a44fd918fbba93d1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 13 Mar 2021 21:22:42 +0100 Subject: [PATCH 52/74] Improve `@prop` warning messages --- blender/arm/utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/blender/arm/utils.py b/blender/arm/utils.py index 305f79da..fe8128f0 100755 --- a/blender/arm/utils.py +++ b/blender/arm/utils.py @@ -336,12 +336,12 @@ def fetch_script_props(filename: str): if p_modifiers is not None: if 'static' in p_modifiers: - script_warnings[name].append((p_identifier, 'static properties may result in unwanted behaviour!')) + script_warnings[name].append((p_identifier, '`static` modifier might cause unwanted behaviour!')) if 'inline' in p_modifiers: - script_warnings[name].append((p_identifier, 'inline properties are not supported!')) + script_warnings[name].append((p_identifier, '`inline` modifier is not supported!')) continue if 'final' in p_modifiers: - script_warnings[name].append((p_identifier, 'final properties are not supported!')) + script_warnings[name].append((p_identifier, '`final` modifier is not supported!')) continue # Property type is annotated @@ -353,7 +353,7 @@ def fetch_script_props(filename: str): type_default_val = get_type_default_value(p_type) if type_default_val is None: - script_warnings[name].append((p_identifier, f'unsupported type "{p_type}"!')) + script_warnings[name].append((p_identifier, f'unsupported type `{p_type}`!')) continue # Default value exists From b7fef30b700a9fddaecf903ffd5286900ef8eecc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sun, 14 Mar 2021 15:50:27 +0100 Subject: [PATCH 53/74] Further improve handling of final trait props --- blender/arm/utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/blender/arm/utils.py b/blender/arm/utils.py index fe8128f0..0c0cdc43 100755 --- a/blender/arm/utils.py +++ b/blender/arm/utils.py @@ -297,13 +297,13 @@ script_props = {} script_props_defaults = {} script_warnings: Dict[str, List[Tuple[str, str]]] = {} # Script name -> List of (identifier, warning message) -# See https://regex101.com/r/bbrCzN/7 +# See https://regex101.com/r/bbrCzN/8 RX_MODIFIERS = r'(?P(?:public\s+|private\s+|static\s+|inline\s+|final\s+)*)?' # Optional modifiers RX_IDENTIFIER = r'(?P[_$a-z]+[_a-z0-9]*)' # Variable name, follow Haxe rules RX_TYPE = r'(?::\s+(?P[_a-z]+[\._a-z0-9]*))?' # Optional type annotation RX_VALUE = r'(?:\s*=\s*(?P(?:\".*\")|(?:[^;]+)|))?' # Optional default value -PROP_REGEX_RAW = fr'@prop\s+{RX_MODIFIERS}var\s+{RX_IDENTIFIER}{RX_TYPE}{RX_VALUE};' +PROP_REGEX_RAW = fr'@prop\s+{RX_MODIFIERS}(?Pvar|final)\s+{RX_IDENTIFIER}{RX_TYPE}{RX_VALUE};' PROP_REGEX = re.compile(PROP_REGEX_RAW, re.IGNORECASE) def fetch_script_props(filename: str): """Parses @prop declarations from the given Haxe script.""" @@ -340,8 +340,8 @@ def fetch_script_props(filename: str): if 'inline' in p_modifiers: script_warnings[name].append((p_identifier, '`inline` modifier is not supported!')) continue - if 'final' in p_modifiers: - script_warnings[name].append((p_identifier, '`final` modifier is not supported!')) + if 'final' in p_modifiers or match.group('attr_type') == 'final': + script_warnings[name].append((p_identifier, '`final` properties are not supported!')) continue # Property type is annotated From efcef7dd2514f1d5f95e0d20c520409456c7e3ad Mon Sep 17 00:00:00 2001 From: N8n5h Date: Sun, 14 Mar 2021 21:51:36 -0300 Subject: [PATCH 54/74] Fix shadow rendering for deferred and forward mobile --- .../deferred_light.frag.glsl | 3 + .../deferred_light_mobile.json | 5 ++ Shaders/std/light_mobile.glsl | 55 ++++++++++--------- 3 files changed, 36 insertions(+), 27 deletions(-) diff --git a/Shaders/deferred_light_mobile/deferred_light.frag.glsl b/Shaders/deferred_light_mobile/deferred_light.frag.glsl index cc8ce302..f1b32985 100644 --- a/Shaders/deferred_light_mobile/deferred_light.frag.glsl +++ b/Shaders/deferred_light_mobile/deferred_light.frag.glsl @@ -29,6 +29,9 @@ uniform int envmapNumMipmaps; uniform vec3 backgroundCol; #endif +#ifdef _SMSizeUniform +//!uniform vec2 smSizeUniform; +#endif uniform vec2 cameraProj; uniform vec3 eye; uniform vec3 eyeLook; diff --git a/Shaders/deferred_light_mobile/deferred_light_mobile.json b/Shaders/deferred_light_mobile/deferred_light_mobile.json index 620e27e1..48774740 100644 --- a/Shaders/deferred_light_mobile/deferred_light_mobile.json +++ b/Shaders/deferred_light_mobile/deferred_light_mobile.json @@ -107,6 +107,11 @@ "link": "_viewProjectionMatrix", "ifdef": ["_SSRS"] }, + { + "name": "smSizeUniform", + "link": "_shadowMapSize", + "ifdef": ["_SMSizeUniform"] + }, { "name": "lightProj", "link": "_lightPlaneProj", diff --git a/Shaders/std/light_mobile.glsl b/Shaders/std/light_mobile.glsl index f6a966c9..9686f844 100644 --- a/Shaders/std/light_mobile.glsl +++ b/Shaders/std/light_mobile.glsl @@ -8,39 +8,39 @@ #endif #ifdef _ShadowMap -#ifdef _SinglePoint - #ifdef _Spot - uniform sampler2DShadow shadowMapSpot[1]; - uniform mat4 LWVPSpot[1]; - #else - uniform samplerCubeShadow shadowMapPoint[1]; - uniform vec2 lightProj; + #ifdef _SinglePoint + #ifdef _Spot + uniform sampler2DShadow shadowMapSpot[1]; + uniform mat4 LWVPSpot[1]; + #else + uniform samplerCubeShadow shadowMapPoint[1]; + uniform vec2 lightProj; + #endif #endif -#endif -#ifdef _Clusters - #ifdef _SingleAtlas - //!uniform sampler2DShadow shadowMapAtlas; - #endif - uniform vec2 lightProj; - #ifdef _ShadowMapAtlas - #ifndef _SingleAtlas - uniform sampler2DShadow shadowMapAtlasPoint; - #endif - #else - uniform samplerCubeShadow shadowMapPoint[4]; - #endif - #ifdef _Spot + #ifdef _Clusters + #ifdef _SingleAtlas + //!uniform sampler2DShadow shadowMapAtlas; + #endif + uniform vec2 lightProj; #ifdef _ShadowMapAtlas #ifndef _SingleAtlas - uniform sampler2DShadow shadowMapAtlasSpot; + uniform sampler2DShadow shadowMapAtlasPoint; #endif #else - uniform sampler2DShadow shadowMapSpot[maxLightsCluster]; + uniform samplerCubeShadow shadowMapPoint[4]; + #endif + #ifdef _Spot + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + uniform sampler2DShadow shadowMapAtlasSpot; + #endif + #else + uniform sampler2DShadow shadowMapSpot[maxLightsCluster]; + #endif + uniform mat4 LWVPSpotArray[maxLightsCluster]; #endif - uniform mat4 LWVPSpotArray[maxLightsCluster]; #endif #endif -#endif vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, const vec3 lp, const vec3 lightCol, const vec3 albedo, const float rough, const float spec, const vec3 f0 @@ -102,11 +102,13 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co #endif #ifdef _ShadowMap - #ifndef _Spot + if (receiveShadow) { #ifdef _SinglePoint + #ifndef _Spot direct *= PCFCube(shadowMapPoint[0], ld, -l, bias, lightProj, n); #endif + #endif #ifdef _Clusters #ifdef _ShadowMapAtlas direct *= PCFFakeCube( @@ -126,7 +128,6 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co #endif } #endif - #endif return direct; } From 7463de61407c9cea2cc972c619c255c8e969145c Mon Sep 17 00:00:00 2001 From: N8n5h Date: Sun, 14 Mar 2021 10:35:25 -0300 Subject: [PATCH 55/74] Add cast_shadow to lightArray data This was done to undo the hardcoded nature of "receiveShadows", which doesn't play nice when you have multiple lights of the same type and one or several have cast_shadow set to false. By simply "passing" the cast_shadow parameter within the lightArray it was possible to detect lights that don't cast shadows before actually wasting gpu cycles to render shadows with garbage data. Support for deferred desktop and mobile, and forward was added. --- Shaders/deferred_light/deferred_light.frag.glsl | 13 +++++++------ .../deferred_light.frag.glsl | 13 +++++++------ .../volumetric_light/volumetric_light.frag.glsl | 2 +- blender/arm/material/make_cluster.py | 16 ++++++++-------- 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/Shaders/deferred_light/deferred_light.frag.glsl b/Shaders/deferred_light/deferred_light.frag.glsl index e119349e..fe81fad3 100644 --- a/Shaders/deferred_light/deferred_light.frag.glsl +++ b/Shaders/deferred_light/deferred_light.frag.glsl @@ -94,7 +94,7 @@ uniform vec3 eye; uniform vec3 eyeLook; #ifdef _Clusters -uniform vec4 lightsArray[maxLights * 2]; +uniform vec4 lightsArray[maxLights * 3]; #ifdef _Spot uniform vec4 lightsArraySpot[maxLights]; #endif @@ -456,18 +456,19 @@ void main() { n, v, dotNV, - lightsArray[li * 2].xyz, // lp - lightsArray[li * 2 + 1].xyz, // lightCol + lightsArray[li * 3].xyz, // lp + lightsArray[li * 3 + 1].xyz, // lightCol albedo, roughness, occspec.y, f0 #ifdef _ShadowMap - , li, lightsArray[li * 2].w, true // bias + // light index, shadow bias, cast_shadows + , li, lightsArray[li * 3 + 2].x, lightsArray[li * 3 + 2].z != 0.0 #endif #ifdef _Spot - , lightsArray[li * 2 + 1].w != 0.0 - , lightsArray[li * 2 + 1].w // cutoff + , lightsArray[li * 3 + 2].y != 0.0 + , lightsArray[li * 3 + 2].y // cutoff , lightsArraySpot[li].w // cutoff - exponent , lightsArraySpot[li].xyz // spotDir #endif diff --git a/Shaders/deferred_light_mobile/deferred_light.frag.glsl b/Shaders/deferred_light_mobile/deferred_light.frag.glsl index cc8ce302..de7dceee 100644 --- a/Shaders/deferred_light_mobile/deferred_light.frag.glsl +++ b/Shaders/deferred_light_mobile/deferred_light.frag.glsl @@ -34,7 +34,7 @@ uniform vec3 eye; uniform vec3 eyeLook; #ifdef _Clusters -uniform vec4 lightsArray[maxLights * 2]; +uniform vec4 lightsArray[maxLights * 3]; #ifdef _Spot uniform vec4 lightsArraySpot[maxLights]; #endif @@ -255,18 +255,19 @@ void main() { n, v, dotNV, - lightsArray[li * 2].xyz, // lp - lightsArray[li * 2 + 1].xyz, // lightCol + lightsArray[li * 3].xyz, // lp + lightsArray[li * 3 + 1].xyz, // lightCol albedo, roughness, occspec.y, f0 #ifdef _ShadowMap - , li, lightsArray[li * 2].w, true // bias + // light index, shadow bias, cast_shadows + , li, lightsArray[li * 3 + 2].x, lightsArray[li * 3 + 2].z != 0.0 #endif #ifdef _Spot - , lightsArray[li * 2 + 1].w != 0.0 - , lightsArray[li * 2 + 1].w // cutoff + , lightsArray[li * 3 + 2].y != 0.0 + , lightsArray[li * 3 + 2].y // cutoff , lightsArraySpot[li].w // cutoff - exponent , lightsArraySpot[li].xyz // spotDir #endif diff --git a/Shaders/volumetric_light/volumetric_light.frag.glsl b/Shaders/volumetric_light/volumetric_light.frag.glsl index e1306b06..a0817b54 100644 --- a/Shaders/volumetric_light/volumetric_light.frag.glsl +++ b/Shaders/volumetric_light/volumetric_light.frag.glsl @@ -12,7 +12,7 @@ uniform sampler2D gbufferD; uniform sampler2D snoise; #ifdef _Clusters -uniform vec4 lightsArray[maxLights * 2]; +uniform vec4 lightsArray[maxLights * 3]; #ifdef _Spot uniform vec4 lightsArraySpot[maxLights]; #endif diff --git a/blender/arm/material/make_cluster.py b/blender/arm/material/make_cluster.py index 1ac91d5f..e3d104f1 100644 --- a/blender/arm/material/make_cluster.py +++ b/blender/arm/material/make_cluster.py @@ -9,7 +9,7 @@ def write(vert, frag): frag.add_include_front('std/clusters.glsl') frag.add_uniform('vec2 cameraProj', link='_cameraPlaneProj') frag.add_uniform('vec2 cameraPlane', link='_cameraPlane') - frag.add_uniform('vec4 lightsArray[maxLights * 2]', link='_lightsArray') + frag.add_uniform('vec4 lightsArray[maxLights * 3]', link='_lightsArray') frag.add_uniform('sampler2D clustersData', link='_clustersData') if is_shadows: frag.add_uniform('bool receiveShadow') @@ -56,19 +56,19 @@ def write(vert, frag): frag.write(' n,') frag.write(' vVec,') frag.write(' dotNV,') - frag.write(' lightsArray[li * 2].xyz,') # lp - frag.write(' lightsArray[li * 2 + 1].xyz,') # lightCol + frag.write(' lightsArray[li * 3].xyz,') # lp + frag.write(' lightsArray[li * 3 + 1].xyz,') # lightCol frag.write(' albedo,') frag.write(' roughness,') frag.write(' specular,') frag.write(' f0') if is_shadows: - frag.write(' , li, lightsArray[li * 2].w, receiveShadow') # bias + frag.write('\t, li, lightsArray[li * 3 + 2].x, lightsArray[li * 3 + 2].z != 0.0') # bias if '_Spot' in wrd.world_defs: - frag.write(' , lightsArray[li * 2 + 1].w != 0.0') - frag.write(' , lightsArray[li * 2 + 1].w') # cutoff - frag.write(' , lightsArraySpot[li].w') # cutoff - exponent - frag.write(' , lightsArraySpot[li].xyz') # spotDir + frag.write('\t, lightsArray[li * 3 + 2].y != 0.0') + frag.write('\t, lightsArray[li * 3 + 2].y') # cutoff + frag.write('\t, lightsArraySpot[li].w') # cutoff - exponent + frag.write('\t, lightsArraySpot[li].xyz') # spotDir if '_VoxelShadow' in wrd.world_defs and '_VoxelAOvar' in wrd.world_defs: frag.write(' , voxels, voxpos') frag.write(');') From c7211542b4ee758dbb67f1647e442612ebb81676 Mon Sep 17 00:00:00 2001 From: N8n5h Date: Sun, 14 Mar 2021 10:46:58 -0300 Subject: [PATCH 56/74] Make pointLightData work with lights with cast_shadow=false --- Sources/armory/renderpath/Inc.hx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/armory/renderpath/Inc.hx b/Sources/armory/renderpath/Inc.hx index 940732a3..62109321 100644 --- a/Sources/armory/renderpath/Inc.hx +++ b/Sources/armory/renderpath/Inc.hx @@ -57,7 +57,11 @@ class Inc { break; if (LightObject.discardLightCulled(light)) continue; if (light.data.raw.type == "point") { - for(k in 0...light.tileOffsetX.length) { + if (!light.data.raw.cast_shadow) { + j += 4 * 6; + continue; + } + for(k in 0...6) { LightObject.pointLightsData[j ] = light.tileOffsetX[k]; // posx LightObject.pointLightsData[j + 1] = light.tileOffsetY[k]; // posy LightObject.pointLightsData[j + 2] = light.tileScale[k]; // tile scale factor relative to atlas From 1ad1d564bd46b2398f6226a090f3e4ed2c4f1ed9 Mon Sep 17 00:00:00 2001 From: knowledgenude <63247726+knowledgenude@users.noreply.github.com> Date: Tue, 16 Mar 2021 18:27:42 -0300 Subject: [PATCH 57/74] Fix clamp node --- Sources/armory/logicnode/ClampNode.hx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Sources/armory/logicnode/ClampNode.hx b/Sources/armory/logicnode/ClampNode.hx index 8bd5f13d..911cf9d4 100644 --- a/Sources/armory/logicnode/ClampNode.hx +++ b/Sources/armory/logicnode/ClampNode.hx @@ -1,18 +1,18 @@ package armory.logicnode; +import kha.FastFloat; + class ClampNode extends LogicNode { public function new(tree: LogicTree) { super(tree); } - override function get(from: Int): Dynamic { - var value: kha.FastFloat = inputs[0].get(); - var min: kha.FastFloat = inputs[1].get(); - var max: kha.FastFloat = inputs[2].get(); + override function get(from: Int): FastFloat { + var value = inputs[0].get(); + var min = inputs[1].get(); + var max = inputs[2].get(); - if (value == null || min == null || max == null) return null; - - value <= min ? return min : value >= max ? return max : return value; + return value < min ? min : value > max ? max : value; } } From ca2c3e151a181a33188190da79f46b3a9386a177 Mon Sep 17 00:00:00 2001 From: N8n5h Date: Wed, 17 Mar 2021 20:28:26 -0300 Subject: [PATCH 58/74] Modified UI for collision filter props_collision_filter_mask * Moved to collision filter mask panel under armory props to keep UI more tidy. * Moved the `arm_rb_collision_filter_mask` property out of the props_collision_filter_mask to be with the rest of the properties. * Changed name and description of collision filter mask to be more consistent with the existing rigid body UI. --- blender/arm/props.py | 6 ++++++ blender/arm/props_collision_filter_mask.py | 23 +++++++++++----------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/blender/arm/props.py b/blender/arm/props.py index 72df10b8..91fe1ccf 100755 --- a/blender/arm/props.py +++ b/blender/arm/props.py @@ -280,6 +280,12 @@ def init_properties(): bpy.types.Object.arm_rb_trigger = BoolProperty(name="Trigger", description="Disable contact response", default=False) bpy.types.Object.arm_rb_deactivation_time = FloatProperty(name="Deactivation Time", description="Delay putting rigid body into sleep", default=0.0) bpy.types.Object.arm_rb_ccd = BoolProperty(name="Continuous Collision Detection", description="Improve collision for fast moving objects", default=False) + bpy.types.Object.arm_rb_collision_filter_mask = bpy.props.BoolVectorProperty( + name="Collision Collections Filter Mask", + description="Collision collections rigid body interacts with", + default=(True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False), + size=20, + subtype='LAYER') bpy.types.Object.arm_animation_enabled = BoolProperty(name="Animation", description="Enable skinning & timeline animation", default=True) bpy.types.Object.arm_tilesheet = StringProperty(name="Tilesheet", description="Set tilesheet animation", default='') bpy.types.Object.arm_tilesheet_action = StringProperty(name="Tilesheet Action", description="Set startup action", default='') diff --git a/blender/arm/props_collision_filter_mask.py b/blender/arm/props_collision_filter_mask.py index d5b52910..00086ac8 100644 --- a/blender/arm/props_collision_filter_mask.py +++ b/blender/arm/props_collision_filter_mask.py @@ -3,29 +3,28 @@ from bpy.props import * from bpy.types import Panel class ARM_PT_RbCollisionFilterMaskPanel(bpy.types.Panel): - bl_label = "Armory Collision Filter Mask" + bl_label = "Collections Filter Mask" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "physics" - + bl_parent_id = "ARM_PT_PhysicsPropsPanel" + + @classmethod + def poll(self, context): + obj = context.object + if obj is None: + return False + return obj.rigid_body is not None def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False - obj = bpy.context.object - if obj == None: - return - if obj.rigid_body != None: - layout.prop(obj, 'arm_rb_collision_filter_mask') + obj = context.object + layout.prop(obj, 'arm_rb_collision_filter_mask', text="", expand=True) def register(): bpy.utils.register_class(ARM_PT_RbCollisionFilterMaskPanel) - bpy.types.Object.arm_rb_collision_filter_mask = bpy.props.BoolVectorProperty( - name="Collision Filter Mask", - default=(True, False, False,False,False,False, False, False,False,False,False, False, False,False,False,False, False, False,False,False), - size=20, - subtype='LAYER') def unregister(): bpy.utils.unregister_class(ARM_PT_RbCollisionFilterMaskPanel) From 255921b9eeaaf45fb893cb860de5108d5fee01e5 Mon Sep 17 00:00:00 2001 From: QuantumCoderQC <55564981+QuantumCoderQC@users.noreply.github.com> Date: Thu, 18 Mar 2021 21:34:59 +0100 Subject: [PATCH 59/74] Make description of logic node more precise The OnVolumeTrigger node does not required a RigidBody. --- blender/arm/logicnode/physics/LN_on_volume_trigger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blender/arm/logicnode/physics/LN_on_volume_trigger.py b/blender/arm/logicnode/physics/LN_on_volume_trigger.py index 7b796d84..225aa301 100644 --- a/blender/arm/logicnode/physics/LN_on_volume_trigger.py +++ b/blender/arm/logicnode/physics/LN_on_volume_trigger.py @@ -1,7 +1,7 @@ from arm.logicnode.arm_nodes import * class OnVolumeTriggerNode(ArmLogicTreeNode): - """Activates the output when the given rigid body enter, overlap or leave the given trigger. + """Activates the output when the given object enters, overlaps or leaves the bounding box of the given trigger object. (Note: Works even if objects are not Rigid Bodies). @input RB: this object is taken as the entering object @input Trigger: this object is used as the volume trigger From a778460d283e5178984e224edf08cb879cf37616 Mon Sep 17 00:00:00 2001 From: N8n5h Date: Thu, 18 Mar 2021 22:49:56 -0300 Subject: [PATCH 60/74] Expose the exported rigid body type to the UI to make rb less confusing --- blender/arm/exporter.py | 6 +++++- blender/arm/props_ui.py | 16 +++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/blender/arm/exporter.py b/blender/arm/exporter.py index 30154525..19e87a6e 100755 --- a/blender/arm/exporter.py +++ b/blender/arm/exporter.py @@ -2308,6 +2308,10 @@ class ArmoryExporter: return instanced_type, instanced_data + @staticmethod + def rigid_body_static(rb): + return (not rb.enabled and not rb.kinematic) or (rb.type == 'PASSIVE' and not rb.kinematic) + def post_export_object(self, bobject: bpy.types.Object, o, type): # Export traits self.export_traits(bobject, o) @@ -2334,7 +2338,7 @@ class ArmoryExporter: elif rb.collision_shape == 'CAPSULE': shape = 6 body_mass = rb.mass - is_static = (not rb.enabled and not rb.kinematic) or (rb.type == 'PASSIVE' and not rb.kinematic) + is_static = self.rigid_body_static(rb) if is_static: body_mass = 0 x = {} diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index 582ee734..2c6d3ec5 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -22,6 +22,8 @@ from arm.lightmapper.utility import icon from arm.lightmapper.properties.denoiser import oidn, optix import importlib +from arm.exporter import ArmoryExporter + # Menu in object region class ARM_PT_ObjectPropsPanel(bpy.types.Panel): bl_label = "Armory Props" @@ -180,7 +182,19 @@ class ARM_PT_PhysicsPropsPanel(bpy.types.Panel): if obj == None: return - if obj.rigid_body != None: + rb = obj.rigid_body + if rb is not None: + col = layout.column() + row = col.row() + row.alignment = 'RIGHT' + + rb_type = 'Dynamic' + if ArmoryExporter.rigid_body_static(rb): + rb_type = 'Static' + if rb.kinematic: + rb_type = 'Kinematic' + row.label(text=(f'Rigid Body Export Type: {rb_type}'), icon='AUTO') + layout.prop(obj, 'arm_rb_linear_factor') layout.prop(obj, 'arm_rb_angular_factor') layout.prop(obj, 'arm_rb_trigger') From d4463a361153a573602dec3eef6a279fa96a280d Mon Sep 17 00:00:00 2001 From: N8n5h Date: Fri, 19 Mar 2021 10:22:01 -0300 Subject: [PATCH 61/74] Catch tangent n-gon error to properly show it to the user Instead of just letting it crash whenever the users hits play. --- blender/arm/exporter.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/blender/arm/exporter.py b/blender/arm/exporter.py index 30154525..50c525ae 100755 --- a/blender/arm/exporter.py +++ b/blender/arm/exporter.py @@ -32,6 +32,7 @@ import arm.material.mat_batch as mat_batch import arm.utils import arm.profiler +import arm.log as log @unique class NodeType(Enum): @@ -1157,7 +1158,16 @@ class ArmoryExporter: else: invscale_tex = 1 * 32767 if has_tang: - exportMesh.calc_tangents(uvmap=lay0.name) + try: + exportMesh.calc_tangents(uvmap=lay0.name) + except Exception as e: + if hasattr(e, 'message'): + log.error(e.message) + else: + # Assume it was caused because of encountering n-gons + log.error(f"""object {bobject.name} contains n-gons in its mesh, so it's impossible to compute tanget space for normal mapping. +Make sure the mesh only has tris/quads.""") + tangdata = np.empty(num_verts * 3, dtype=' Date: Fri, 19 Mar 2021 11:39:06 -0300 Subject: [PATCH 62/74] Log errors and show them in the blender UI --- blender/arm/log.py | 3 +++ blender/arm/props_ui.py | 14 +++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/blender/arm/log.py b/blender/arm/log.py index f28a1524..801555c3 100644 --- a/blender/arm/log.py +++ b/blender/arm/log.py @@ -29,6 +29,7 @@ else: info_text = '' num_warnings = 0 +num_errors = 0 def clear(clear_warnings=False): global info_text, num_warnings @@ -62,4 +63,6 @@ def warn(text): print_warn(text) def error(text): + global num_errors + num_errors += 1 log('ERROR: ' + text, ERROR) diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index 582ee734..955aa4fa 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -560,7 +560,19 @@ class ARM_PT_ArmoryPlayerPanel(bpy.types.Panel): box = layout.box() # Less spacing between lines col = box.column(align=True) - col.label(text=f'{log.num_warnings} warnings occurred during compilation!', icon='ERROR') + warnings = 'warnings' if log.num_warnings > 1 else 'warning' + col.label(text=f'{log.num_warnings} {warnings} occurred during compilation!', icon='ERROR') + # Blank icon to achieve the same indentation as the line before + # prevent showing "open console" twice: + if log.num_errors == 0: + col.label(text='Please open the console to get more information.', icon='BLANK1') + + if log.num_errors > 0: + box = layout.box() + # Less spacing between lines + col = box.column(align=True) + errors = 'errors' if log.num_errors > 1 else 'error' + col.label(text=f'{log.num_errors} {errors} occurred during compilation!', icon='CANCEL') # Blank icon to achieve the same indentation as the line before col.label(text='Please open the console to get more information.', icon='BLANK1') From 7851abc4911e80396548ad70c82fe0736ef41b81 Mon Sep 17 00:00:00 2001 From: N8n5h Date: Sat, 20 Mar 2021 21:26:05 -0300 Subject: [PATCH 63/74] Clear error count after building again --- blender/arm/log.py | 6 ++++-- blender/arm/make.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/blender/arm/log.py b/blender/arm/log.py index 801555c3..2dca68da 100644 --- a/blender/arm/log.py +++ b/blender/arm/log.py @@ -31,11 +31,13 @@ info_text = '' num_warnings = 0 num_errors = 0 -def clear(clear_warnings=False): - global info_text, num_warnings +def clear(clear_warnings=False, clear_errors=False): + global info_text, num_warnings, num_errors info_text = '' if clear_warnings: num_warnings = 0 + if clear_errors: + num_errors = 0 def format_text(text): return (text[:80] + '..') if len(text) > 80 else text # Limit str size diff --git a/blender/arm/make.py b/blender/arm/make.py index 4538a9e6..ffc91e3a 100755 --- a/blender/arm/make.py +++ b/blender/arm/make.py @@ -338,7 +338,7 @@ def build(target, is_play=False, is_publish=False, is_export=False): if arm.utils.get_save_on_build(): bpy.ops.wm.save_mainfile() - log.clear(clear_warnings=True) + log.clear(clear_warnings=True, clear_errors=True) # Set camera in active scene active_scene = arm.utils.get_active_scene() From 362fffa408582fb4775f156c320811f9f4843213 Mon Sep 17 00:00:00 2001 From: Alexander Kleemann Date: Tue, 23 Mar 2021 21:51:28 +0100 Subject: [PATCH 64/74] Reimplement blackbody node from python to GLSL Should now work with changing values --- blender/arm/material/cycles_functions.py | 71 +++++++++++++++++++ .../material/cycles_nodes/nodes_converter.py | 68 ++---------------- 2 files changed, 75 insertions(+), 64 deletions(-) diff --git a/blender/arm/material/cycles_functions.py b/blender/arm/material/cycles_functions.py index a133d2f8..8135371b 100644 --- a/blender/arm/material/cycles_functions.py +++ b/blender/arm/material/cycles_functions.py @@ -332,3 +332,74 @@ vec3 wrap(const vec3 value, const vec3 max, const vec3 min) { \t wrap(value.z, max.z, min.z)); } """ + +str_blackbody = """ +vec3 blackbody(const float temperature){ + + vec3 rgb = vec3(0.0, 0.0, 0.0); + + vec3 r = vec3(0.0, 0.0, 0.0); + vec3 g = vec3(0.0, 0.0, 0.0); + vec3 b = vec3(0.0, 0.0, 0.0); + + float t_inv = float(1.0 / temperature); + + if (temperature >= 12000.0) { + + rgb = vec3(0.826270103, 0.994478524, 1.56626022); + + } else if(temperature < 965.0) { + + rgb = vec3(4.70366907, 0.0, 0.0); + + } else { + + if (temperature >= 6365.0) { + vec3 r = vec3(3.78765709e+03, 9.36026367e-06, 3.98995841e-01); + vec3 g = vec3(-5.00279505e+02, -4.59745390e-06, 1.09090465e+00); + vec4 b = vec4(6.72595954e-13, -2.73059993e-08, 4.24068546e-04, -7.52204323e-01); + + rgb = vec3(r.r * t_inv + r.g * temperature + r.b, g.r * t_inv + g.g * temperature + g.b, ((b.r * temperature + b.g) * temperature + b.b) * temperature + b.a ); + + } else if (temperature >= 3315.0) { + vec3 r = vec3(4.60124770e+03, 2.89727618e-05, 1.48001316e-01); + vec3 g = vec3(-1.18134453e+03, -2.18913373e-05, 1.30656109e+00); + vec4 b = vec4(-2.22463426e-13, -1.55078698e-08, 3.81675160e-04, -7.30646033e-01); + + rgb = vec3(r.r * t_inv + r.g * temperature + r.b, g.r * t_inv + g.g * temperature + g.b, ((b.r * temperature + b.g) * temperature + b.b) * temperature + b.a ); + + } else if (temperature >= 1902.0) { + vec3 r = vec3(4.66849800e+03, 2.85655028e-05, 1.29075375e-01); + vec3 g = vec3(-1.42546105e+03, -4.01730887e-05, 1.44002695e+00); + vec4 b = vec4(-2.02524603e-11, 1.79435860e-07, -2.60561875e-04, -1.41761141e-02); + + rgb = vec3(r.r * t_inv + r.g * temperature + r.b, g.r * t_inv + g.g * temperature + g.b, ((b.r * temperature + b.g) * temperature + b.b) * temperature + b.a ); + + } else if (temperature >= 1449.0) { + vec3 r = vec3(4.10671449e+03, -8.61949938e-05, 6.41423749e-01); + vec3 g = vec3(-1.22075471e+03, 2.56245413e-05, 1.20753416e+00); + vec4 b = vec4(0.0, 0.0, 0.0, 0.0); + + rgb = vec3(r.r * t_inv + r.g * temperature + r.b, g.r * t_inv + g.g * temperature + g.b, ((b.r * temperature + b.g) * temperature + b.b) * temperature + b.a ); + + } else if (temperature >= 1167.0) { + vec3 r = vec3(3.37763626e+03, -4.34581697e-04, 1.64843306e+00); + vec3 g = vec3(-1.00402363e+03, 1.29189794e-04, 9.08181524e-01); + vec4 b = vec4(0.0, 0.0, 0.0, 0.0); + + rgb = vec3(r.r * t_inv + r.g * temperature + r.b, g.r * t_inv + g.g * temperature + g.b, ((b.r * temperature + b.g) * temperature + b.b) * temperature + b.a ); + + } else { + vec3 r = vec3(2.52432244e+03, -1.06185848e-03, 3.11067539e+00); + vec3 g = vec3(-7.50343014e+02, 3.15679613e-04, 4.73464526e-01); + vec4 b = vec4(0.0, 0.0, 0.0, 0.0); + + rgb = vec3(r.r * t_inv + r.g * temperature + r.b, g.r * t_inv + g.g * temperature + g.b, ((b.r * temperature + b.g) * temperature + b.b) * temperature + b.a ); + + } + } + + return rgb; + +} +""" \ No newline at end of file diff --git a/blender/arm/material/cycles_nodes/nodes_converter.py b/blender/arm/material/cycles_nodes/nodes_converter.py index b87e9f16..a70c6559 100644 --- a/blender/arm/material/cycles_nodes/nodes_converter.py +++ b/blender/arm/material/cycles_nodes/nodes_converter.py @@ -10,71 +10,11 @@ from arm.material.shader import floatstr, vec3str def parse_blackbody(node: bpy.types.ShaderNodeBlackbody, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: - t = float(c.parse_value_input(node.inputs[0])) - - rgb = [0, 0, 0] - blackbody_table_r = [ - [2.52432244e+03, -1.06185848e-03, 3.11067539e+00], - [3.37763626e+03, -4.34581697e-04, 1.64843306e+00], - [4.10671449e+03, -8.61949938e-05, 6.41423749e-01], - [4.66849800e+03, 2.85655028e-05, 1.29075375e-01], - [4.60124770e+03, 2.89727618e-05, 1.48001316e-01], - [3.78765709e+03, 9.36026367e-06, 3.98995841e-01] - ] - blackbody_table_g = [ - [-7.50343014e+02, 3.15679613e-04, 4.73464526e-01], - [-1.00402363e+03, 1.29189794e-04, 9.08181524e-01], - [-1.22075471e+03, 2.56245413e-05, 1.20753416e+00], - [-1.42546105e+03, -4.01730887e-05, 1.44002695e+00], - [-1.18134453e+03, -2.18913373e-05, 1.30656109e+00], - [-5.00279505e+02, -4.59745390e-06, 1.09090465e+00] - ] - blackbody_table_b = [ - [0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0], - [-2.02524603e-11, 1.79435860e-07, -2.60561875e-04, -1.41761141e-02], - [-2.22463426e-13, -1.55078698e-08, 3.81675160e-04, -7.30646033e-01], - [6.72595954e-13, -2.73059993e-08, 4.24068546e-04, -7.52204323e-01] - ] - - if t >= 12000: - rgb[0] = 0.826270103 - rgb[1] = 0.994478524 - rgb[2] = 1.56626022 - - elif t < 965.0: - rgb[0] = 4.70366907 - rgb[1] = 0.0 - rgb[2] = 0.0 - - else: - if t >= 6365.0: - i = 5 - elif t >= 3315.0: - i = 4 - elif t >= 1902.0: - i = 3 - elif t >= 1449.0: - i = 2 - elif t >= 1167.0: - i = 1 - else: - i = 0 - - r = blackbody_table_r[i] - g = blackbody_table_g[i] - b = blackbody_table_b[i] - - t_inv = 1.0 / t - - rgb[0] = r[0] * t_inv + r[1] * t + r[2] - rgb[1] = g[0] * t_inv + g[1] * t + g[2] - rgb[2] = ((b[0] * t + b[1]) * t + b[2]) * t + b[3] - - # Pass constant - return c.to_vec3([rgb[0], rgb[1], rgb[2]]) + + t = c.parse_value_input(node.inputs[0]) + state.curshader.add_function(c_functions.str_blackbody) + return f'blackbody({t})' def parse_clamp(node: bpy.types.ShaderNodeClamp, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr: value = c.parse_value_input(node.inputs['Value']) From 07741abc55bdba397d5884b69f36cea8289b97dc Mon Sep 17 00:00:00 2001 From: Alexander Kleemann Date: Tue, 23 Mar 2021 21:52:21 +0100 Subject: [PATCH 65/74] Implement map range node Map range node should now be fully supported --- blender/arm/material/cycles_functions.py | 63 +++++++++++++++++++ .../material/cycles_nodes/nodes_converter.py | 30 +++++++++ 2 files changed, 93 insertions(+) diff --git a/blender/arm/material/cycles_functions.py b/blender/arm/material/cycles_functions.py index 8135371b..1618c0a3 100644 --- a/blender/arm/material/cycles_functions.py +++ b/blender/arm/material/cycles_functions.py @@ -402,4 +402,67 @@ vec3 blackbody(const float temperature){ return rgb; } +""" + +# Adapted from https://github.com/blender/blender/blob/594f47ecd2d5367ca936cf6fc6ec8168c2b360d0/source/blender/gpu/shaders/material/gpu_shader_material_map_range.glsl +str_map_range_linear = """ +float map_range_linear(const float value, const float fromMin, const float fromMax, const float toMin, const float toMax) { + if (fromMax != fromMin) { + return float(toMin + ((value - fromMin) / (fromMax - fromMin)) * (toMax - toMin)); + } + else { + return float(0.0); + } +} +""" + +str_map_range_stepped = """ +float map_range_stepped(const float value, const float fromMin, const float fromMax, const float toMin, const float toMax, const float steps) { + if (fromMax != fromMin) { + float factor = (value - fromMin) / (fromMax - fromMin); + factor = (steps > 0.0) ? floor(factor * (steps + 1.0)) / steps : 0.0; + return float(toMin + factor * (toMax - toMin)); + } + else { + return float(0.0); + } +} +""" + +str_map_range_smoothstep = """ +float map_range_smoothstep(const float value, const float fromMin, const float fromMax, const float toMin, const float toMax) +{ + if (fromMax != fromMin) { + float factor = (fromMin > fromMax) ? 1.0 - smoothstep(fromMax, fromMin, value) : + smoothstep(fromMin, fromMax, value); + return float(toMin + factor * (toMax - toMin)); + } + else { + return float(0.0); + } +} +""" + +str_map_range_smootherstep = """ +float safe_divide(float a, float b) +{ + return (b != 0.0) ? a / b : 0.0; +} + +float smootherstep(float edge0, float edge1, float x) +{ + x = clamp(safe_divide((x - edge0), (edge1 - edge0)), 0.0, 1.0); + return x * x * x * (x * (x * 6.0 - 15.0) + 10.0); +} + +float map_range_smootherstep(const float value, const float fromMin, const float fromMax, const float toMin, const float toMax) { + if (fromMax != fromMin) { + float factor = (fromMin > fromMax) ? 1.0 - smootherstep(fromMax, fromMin, value) : + smootherstep(fromMin, fromMax, value); + return float(toMin + factor * (toMax - toMin)); + } + else { + return float(0.0); + } +} """ \ No newline at end of file diff --git a/blender/arm/material/cycles_nodes/nodes_converter.py b/blender/arm/material/cycles_nodes/nodes_converter.py index a70c6559..9071471a 100644 --- a/blender/arm/material/cycles_nodes/nodes_converter.py +++ b/blender/arm/material/cycles_nodes/nodes_converter.py @@ -8,6 +8,36 @@ import arm.material.cycles_functions as c_functions from arm.material.parser_state import ParserState from arm.material.shader import floatstr, vec3str +def parse_maprange(node: bpy.types.ShaderNodeMapRange, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr: + + interp = node.interpolation_type + + value: str = c.parse_value_input(node.inputs[0]) if node.inputs[0].is_linked else c.to_vec1(node.inputs[0].default_value) + fromMin = float(c.parse_value_input(node.inputs[1])) + fromMax = float(c.parse_value_input(node.inputs[2])) + toMin = float(c.parse_value_input(node.inputs[3])) + toMax = float(c.parse_value_input(node.inputs[4])) + + if interp == "LINEAR": + + state.curshader.add_function(c_functions.str_map_range_linear) + return f'map_range_linear({value}, {fromMin}, {fromMax}, {toMin}, {toMax})' + + elif interp == "STEPPED": + + steps = float(c.parse_value_input(node.inputs[5])) + state.curshader.add_function(c_functions.str_map_range_stepped) + return f'map_range_stepped({value}, {fromMin}, {fromMax}, {toMin}, {toMax}, {steps})' + + elif interp == "SMOOTHSTEP": + + state.curshader.add_function(c_functions.str_map_range_smoothstep) + return f'map_range_smoothstep({value}, {fromMin}, {fromMax}, {toMin}, {toMax})' + + elif interp == "SMOOTHERSTEP": + + state.curshader.add_function(c_functions.str_map_range_smootherstep) + return f'map_range_smootherstep({value}, {fromMin}, {fromMax}, {toMin}, {toMax})' def parse_blackbody(node: bpy.types.ShaderNodeBlackbody, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: From 5c1f8fe12827ab8d8df0a580c1f0162aa1cdb2db Mon Sep 17 00:00:00 2001 From: Alexander Kleemann Date: Tue, 23 Mar 2021 21:56:14 +0100 Subject: [PATCH 66/74] Implement vector rotate node Should now be fully supported --- blender/arm/material/cycles_functions.py | 49 +++++++++++++++++++ .../arm/material/cycles_nodes/nodes_vector.py | 30 ++++++++++++ 2 files changed, 79 insertions(+) diff --git a/blender/arm/material/cycles_functions.py b/blender/arm/material/cycles_functions.py index 1618c0a3..1bd3e340 100644 --- a/blender/arm/material/cycles_functions.py +++ b/blender/arm/material/cycles_functions.py @@ -465,4 +465,53 @@ float map_range_smootherstep(const float value, const float fromMin, const float return float(0.0); } } +""" + +str_rotate_around_axis = """ +vec3 rotate_around_axis(const vec3 p, const vec3 axis, const float angle) +{ + float costheta = cos(angle); + float sintheta = sin(angle); + vec3 r; + + r.x = ((costheta + (1.0 - costheta) * axis.x * axis.x) * p.x) + + (((1.0 - costheta) * axis.x * axis.y - axis.z * sintheta) * p.y) + + (((1.0 - costheta) * axis.x * axis.z + axis.y * sintheta) * p.z); + + r.y = (((1.0 - costheta) * axis.x * axis.y + axis.z * sintheta) * p.x) + + ((costheta + (1.0 - costheta) * axis.y * axis.y) * p.y) + + (((1.0 - costheta) * axis.y * axis.z - axis.x * sintheta) * p.z); + + r.z = (((1.0 - costheta) * axis.x * axis.z - axis.y * sintheta) * p.x) + + (((1.0 - costheta) * axis.y * axis.z + axis.x * sintheta) * p.y) + + ((costheta + (1.0 - costheta) * axis.z * axis.z) * p.z); + + return r; +} +""" + +str_euler_to_mat3 = """ +mat3 euler_to_mat3(vec3 euler) +{ + float cx = cos(euler.x); + float cy = cos(euler.y); + float cz = cos(euler.z); + float sx = sin(euler.x); + float sy = sin(euler.y); + float sz = sin(euler.z); + + mat3 mat; + mat[0][0] = cy * cz; + mat[0][1] = cy * sz; + mat[0][2] = -sy; + + mat[1][0] = sy * sx * cz - cx * sz; + mat[1][1] = sy * sx * sz + cx * cz; + mat[1][2] = cy * sx; + + mat[2][0] = sy * cx * cz + sx * sz; + mat[2][1] = sy * cx * sz - sx * cz; + mat[2][2] = cy * cx; + return mat; +} """ \ No newline at end of file diff --git a/blender/arm/material/cycles_nodes/nodes_vector.py b/blender/arm/material/cycles_nodes/nodes_vector.py index 5e35c7de..d18296b2 100644 --- a/blender/arm/material/cycles_nodes/nodes_vector.py +++ b/blender/arm/material/cycles_nodes/nodes_vector.py @@ -141,3 +141,33 @@ def parse_displacement(node: bpy.types.ShaderNodeDisplacement, out_socket: bpy.t scale = c.parse_value_input(node.inputs[2]) nor = c.parse_vector_input(node.inputs[3]) return f'(vec3({height}) * {scale})' + +def parse_vectorrotate(node: bpy.types.ShaderNodeVectorRotate, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: + + type = node.rotation_type + input_vector: bpy.types.NodeSocket = c.parse_vector_input(node.inputs[0]) + input_center: bpy.types.NodeSocket = c.parse_vector_input(node.inputs[1]) + input_axis: bpy.types.NodeSocket = c.parse_vector_input(node.inputs[2]) + input_angle: bpy.types.NodeSocket = c.parse_value_input(node.inputs[3]) + input_rotation: bpy.types.NodeSocket = c.parse_vector_input(node.inputs[4]) + + if node.invert: + input_invert = "0" + else: + input_invert = "1" + + state.curshader.add_function(c_functions.str_rotate_around_axis) + + if type == 'AXIS_ANGLE': + return f'vec3( (length({input_axis}) != 0.0) ? rotate_around_axis({input_vector} - {input_center}, normalize({input_axis}), {input_angle} * {input_invert}) + {input_center} : {input_vector} )' + elif type == 'X_AXIS': + return f'vec3( rotate_around_axis({input_vector} - {input_center}, vec3(1.0, 0.0, 0.0), {input_angle} * {input_invert}) + {input_center} )' + elif type == 'Y_AXIS': + return f'vec3( rotate_around_axis({input_vector} - {input_center}, vec3(0.0, 1.0, 0.0), {input_angle} * {input_invert}) + {input_center} )' + elif type == 'Z_AXIS': + return f'vec3( rotate_around_axis({input_vector} - {input_center}, vec3(0.0, 0.0, 1.0), {input_angle} * {input_invert}) + {input_center} )' + elif type == 'EULER_XYZ': + state.curshader.add_function(c_functions.str_euler_to_mat3) + return f'vec3( mat3(({input_invert} < 0.0) ? transpose(euler_to_mat3({input_rotation})) : euler_to_mat3({input_rotation})) * ({input_vector} - {input_center}) + {input_center})' + + return f'(vec3(1.0, 0.0, 0.0))' \ No newline at end of file From 0cc2ea4491e0d6d621842afc020bc2607b466ad2 Mon Sep 17 00:00:00 2001 From: Alexander Kleemann Date: Tue, 23 Mar 2021 21:58:46 +0100 Subject: [PATCH 67/74] Add map range and vector rotate to node parser dictionary --- blender/arm/material/cycles.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/blender/arm/material/cycles.py b/blender/arm/material/cycles.py index 71cf6f84..fc128d85 100644 --- a/blender/arm/material/cycles.py +++ b/blender/arm/material/cycles.py @@ -331,6 +331,7 @@ def parse_vector(node: bpy.types.Node, socket: bpy.types.NodeSocket) -> str: 'COMBXYZ': nodes_converter.parse_combxyz, 'VECT_MATH': nodes_converter.parse_vectormath, 'DISPLACEMENT': nodes_vector.parse_displacement, + 'VECTOR_ROTATE': nodes_vector.parse_vectorrotate, } if node.type in node_parser_funcs: @@ -433,6 +434,7 @@ def parse_value(node, socket): 'SEPRGB': nodes_converter.parse_seprgb, 'SEPXYZ': nodes_converter.parse_sepxyz, 'VECT_MATH': nodes_converter.parse_vectormath, + 'MAP_RANGE': nodes_converter.parse_maprange, } if node.type in node_parser_funcs: From dfe1a49f85f74f3760dfc8b725897e69e9464398 Mon Sep 17 00:00:00 2001 From: Alexander Kleemann Date: Tue, 23 Mar 2021 22:02:25 +0100 Subject: [PATCH 68/74] Implement the remaining 16 math node operations Math node is now fully supported --- .../material/cycles_nodes/nodes_converter.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/blender/arm/material/cycles_nodes/nodes_converter.py b/blender/arm/material/cycles_nodes/nodes_converter.py index 9071471a..046ae83e 100644 --- a/blender/arm/material/cycles_nodes/nodes_converter.py +++ b/blender/arm/material/cycles_nodes/nodes_converter.py @@ -232,14 +232,21 @@ def parse_math(node: bpy.types.ShaderNodeMath, out_socket: bpy.types.NodeSocket, out_val = '({0} * {1})'.format(val1, val2) elif op == 'DIVIDE': out_val = '({0} / {1})'.format(val1, val2) + elif op == 'MULTIPLY_ADD': + val3 = c.parse_value_input(node.inputs[2]) + out_val = '({0} * {1} + {2})'.format(val1, val2, val3) elif op == 'POWER': out_val = 'pow({0}, {1})'.format(val1, val2) elif op == 'LOGARITHM': out_val = 'log({0})'.format(val1) elif op == 'SQRT': out_val = 'sqrt({0})'.format(val1) + elif op == 'INVERSE_SQRT': + out_val = 'inversesqrt({0})'.format(val1) elif op == 'ABSOLUTE': out_val = 'abs({0})'.format(val1) + elif op == 'EXPONENT': + out_val = 'exp({0})'.format(val1) elif op == 'MINIMUM': out_val = 'min({0}, {1})'.format(val1, val2) elif op == 'MAXIMUM': @@ -248,6 +255,17 @@ def parse_math(node: bpy.types.ShaderNodeMath, out_socket: bpy.types.NodeSocket, out_val = 'float({0} < {1})'.format(val1, val2) elif op == 'GREATER_THAN': out_val = 'float({0} > {1})'.format(val1, val2) + elif op == 'SIGN': + out_val = 'sign({0})'.format(val1) + elif op == 'COMPARE': + val3 = c.parse_value_input(node.inputs[2]) + out_val = 'float((abs({0} - {1}) <= max({2}, 1e-5)) ? 1.0 : 0.0)'.format(val1, val2, val3) + elif op == 'SMOOTH_MIN': + val3 = c.parse_value_input(node.inputs[2]) + out_val = 'float(float({2} != 0.0 ? min({0},{1}) - (max({2} - abs({0} - {1}), 0.0) / {2}) * (max({2} - abs({0} - {1}), 0.0) / {2}) * (max({2} - abs({0} - {1}), 0.0) / {2}) * {2} * (1.0 / 6.0) : min({0}, {1})))'.format(val1, val2, val3) + elif op == 'SMOOTH_MAX': + val3 = c.parse_value_input(node.inputs[2]) + out_val = 'float(0-(float({2} != 0.0 ? min(-{0},-{1}) - (max({2} - abs(-{0} - (-{1})), 0.0) / {2}) * (max({2} - abs(-{0} - (-{1})), 0.0) / {2}) * (max({2} - abs(-{0} - (-{1})), 0.0) / {2}) * {2} * (1.0 / 6.0) : min(-{0}, (-{1})))))'.format(val1, val2, val3) elif op == 'ROUND': # out_val = 'round({0})'.format(val1) out_val = 'floor({0} + 0.5)'.format(val1) @@ -255,11 +273,20 @@ def parse_math(node: bpy.types.ShaderNodeMath, out_socket: bpy.types.NodeSocket, out_val = 'floor({0})'.format(val1) elif op == 'CEIL': out_val = 'ceil({0})'.format(val1) + elif op == 'TRUNC': + out_val = 'trunc({0})'.format(val1) elif op == 'FRACT': out_val = 'fract({0})'.format(val1) elif op == 'MODULO': # out_val = 'float({0} % {1})'.format(val1, val2) out_val = 'mod({0}, {1})'.format(val1, val2) + elif op == 'WRAP': + val3 = c.parse_value_input(node.inputs[2]) + out_val = 'float((({1}-{2}) != 0.0) ? {0} - (({1}-{2}) * floor(({0} - {2}) / ({1}-{2}))) : {2})'.format(val1, val2, val3) + elif op == 'SNAP': + out_val = 'floor(({1} != 0.0) ? {0} / {1} : 0.0) * {1}'.format(val1, val2) + elif op == 'PINGPONG': + out_val = 'float(({1} != 0.0) ? abs(fract(({0} - {1}) / ({1} * 2.0)) * {1} * 2.0 - {1}) : 0.0)'.format(val1, val2) elif op == 'SINE': out_val = 'sin({0})'.format(val1) elif op == 'COSINE': @@ -274,6 +301,16 @@ def parse_math(node: bpy.types.ShaderNodeMath, out_socket: bpy.types.NodeSocket, out_val = 'atan({0})'.format(val1) elif op == 'ARCTAN2': out_val = 'atan({0}, {1})'.format(val1, val2) + elif op == 'SINH': + out_val = 'sinh({0})'.format(val1) + elif op == 'COSH': + out_val = 'cosh({0})'.format(val1) + elif op == 'TANH': + out_val = 'tanh({0})'.format(val1) + elif op == 'RADIANS': + out_val = 'radians({0})'.format(val1) + elif op == 'DEGREES': + out_val = 'degrees({0})'.format(val1) if node.use_clamp: return 'clamp({0}, 0.0, 1.0)'.format(out_val) From 6e0bfa272ced1046639666faf203cd10ef99003f Mon Sep 17 00:00:00 2001 From: Alexander Kleemann Date: Wed, 24 Mar 2021 10:13:46 +0100 Subject: [PATCH 69/74] Remove float parsing --- blender/arm/material/cycles_nodes/nodes_converter.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/blender/arm/material/cycles_nodes/nodes_converter.py b/blender/arm/material/cycles_nodes/nodes_converter.py index 046ae83e..813a1d87 100644 --- a/blender/arm/material/cycles_nodes/nodes_converter.py +++ b/blender/arm/material/cycles_nodes/nodes_converter.py @@ -13,10 +13,10 @@ def parse_maprange(node: bpy.types.ShaderNodeMapRange, out_socket: bpy.types.Nod interp = node.interpolation_type value: str = c.parse_value_input(node.inputs[0]) if node.inputs[0].is_linked else c.to_vec1(node.inputs[0].default_value) - fromMin = float(c.parse_value_input(node.inputs[1])) - fromMax = float(c.parse_value_input(node.inputs[2])) - toMin = float(c.parse_value_input(node.inputs[3])) - toMax = float(c.parse_value_input(node.inputs[4])) + fromMin = c.parse_value_input(node.inputs[1]) + fromMax = c.parse_value_input(node.inputs[2]) + toMin = c.parse_value_input(node.inputs[3]) + toMax = c.parse_value_input(node.inputs[4]) if interp == "LINEAR": From 930b2968fab2f6a549b41357895420ed38d39065 Mon Sep 17 00:00:00 2001 From: Alexander Kleemann Date: Wed, 24 Mar 2021 10:20:05 +0100 Subject: [PATCH 70/74] Include cycles glsl functions for vector rotation --- blender/arm/material/cycles_nodes/nodes_vector.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/blender/arm/material/cycles_nodes/nodes_vector.py b/blender/arm/material/cycles_nodes/nodes_vector.py index d18296b2..5f78a70b 100644 --- a/blender/arm/material/cycles_nodes/nodes_vector.py +++ b/blender/arm/material/cycles_nodes/nodes_vector.py @@ -4,6 +4,7 @@ import bpy from mathutils import Euler, Vector import arm.material.cycles as c +import arm.material.cycles_functions as c_functions from arm.material.parser_state import ParserState from arm.material.shader import floatstr, vec3str @@ -170,4 +171,4 @@ def parse_vectorrotate(node: bpy.types.ShaderNodeVectorRotate, out_socket: bpy.t state.curshader.add_function(c_functions.str_euler_to_mat3) return f'vec3( mat3(({input_invert} < 0.0) ? transpose(euler_to_mat3({input_rotation})) : euler_to_mat3({input_rotation})) * ({input_vector} - {input_center}) + {input_center})' - return f'(vec3(1.0, 0.0, 0.0))' \ No newline at end of file + return f'(vec3(1.0, 0.0, 0.0))' From 24093d15df303bb29dd8c3f17ff4a9cecdb02fd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Thu, 25 Mar 2021 22:02:59 +0100 Subject: [PATCH 71/74] Fix writing to gl_Position before instancing code --- blender/arm/material/make_mesh.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/blender/arm/material/make_mesh.py b/blender/arm/material/make_mesh.py index 9ec18bbe..09519dab 100644 --- a/blender/arm/material/make_mesh.py +++ b/blender/arm/material/make_mesh.py @@ -125,6 +125,10 @@ def make_base(con_mesh, parse_opacity): if write_material_attribs_post != None: write_material_attribs_post(con_mesh, frag) + vert.add_out('vec3 wnormal') + make_attrib.write_norpos(con_mesh, vert) + frag.write_attrib('vec3 n = normalize(wnormal);') + if not is_displacement and not vattr_written: make_attrib.write_vertpos(vert) @@ -162,10 +166,6 @@ def make_base(con_mesh, parse_opacity): make_tess.interpolate(tese, 'vcolor', 3, declare_out=frag.contains('vcolor')) tese.write_pre = False - vert.add_out('vec3 wnormal') - make_attrib.write_norpos(con_mesh, vert) - frag.write_attrib('vec3 n = normalize(wnormal);') - if con_mesh.is_elem('tang'): if tese is not None: tese.add_out('mat3 TBN') From de8073732a37acdb66eb130446050d4182d71469 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 27 Mar 2021 00:30:05 +0100 Subject: [PATCH 72/74] Replace ' and " characters in asset names --- blender/arm/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blender/arm/utils.py b/blender/arm/utils.py index 0c0cdc43..88db6888 100755 --- a/blender/arm/utils.py +++ b/blender/arm/utils.py @@ -576,7 +576,7 @@ def safesrc(s): def safestr(s: str) -> str: """Outputs a string where special characters have been replaced with '_', which can be safely used in file and path names.""" - for c in r'[]/\;,><&*:%=+@!#^()|?^': + for c in r'''[]/\;,><&*:%=+@!#^()|?^'"''': s = s.replace(c, '_') return ''.join([i if ord(i) < 128 else '_' for i in s]) From ead7dc9d322a18bcec04286ecb565d09c127f243 Mon Sep 17 00:00:00 2001 From: N8n5h Date: Mon, 29 Mar 2021 10:41:46 -0300 Subject: [PATCH 73/74] Moved biasLWVP matrix uniform reference to a new one for sun This was done because the original "_biasLightWorldViewProjectionMatrix" relies on renderpath.light, which is problematic when rendering the deferred light pass and there is a sun and other lights of different type on the scene. Which would result on the wrong light being picked up for the calculation of the uniform value. --- Shaders/deferred_light/deferred_light.json | 2 +- Shaders/deferred_light_mobile/deferred_light_mobile.json | 2 +- Shaders/volumetric_light/volumetric_light.json | 2 +- blender/arm/material/make_mesh.py | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Shaders/deferred_light/deferred_light.json b/Shaders/deferred_light/deferred_light.json index d0d02f1f..efc70925 100755 --- a/Shaders/deferred_light/deferred_light.json +++ b/Shaders/deferred_light/deferred_light.json @@ -99,7 +99,7 @@ }, { "name": "LWVP", - "link": "_biasLightWorldViewProjectionMatrix", + "link": "_biasLightWorldViewProjectionMatrixSun", "ifndef": ["_CSM"], "ifdef": ["_Sun", "_ShadowMap"] }, diff --git a/Shaders/deferred_light_mobile/deferred_light_mobile.json b/Shaders/deferred_light_mobile/deferred_light_mobile.json index 48774740..4413dc02 100644 --- a/Shaders/deferred_light_mobile/deferred_light_mobile.json +++ b/Shaders/deferred_light_mobile/deferred_light_mobile.json @@ -88,7 +88,7 @@ }, { "name": "LWVP", - "link": "_biasLightWorldViewProjectionMatrix", + "link": "_biasLightWorldViewProjectionMatrixSun", "ifndef": ["_CSM"], "ifdef": ["_Sun", "_ShadowMap"] }, diff --git a/Shaders/volumetric_light/volumetric_light.json b/Shaders/volumetric_light/volumetric_light.json index a8e96a56..dfae1e2c 100755 --- a/Shaders/volumetric_light/volumetric_light.json +++ b/Shaders/volumetric_light/volumetric_light.json @@ -63,7 +63,7 @@ }, { "name": "LWVP", - "link": "_biasLightWorldViewProjectionMatrix", + "link": "_biasLightWorldViewProjectionMatrixSun", "ifndef": ["_CSM"], "ifdef": ["_Sun", "_ShadowMap"] }, diff --git a/blender/arm/material/make_mesh.py b/blender/arm/material/make_mesh.py index 09519dab..00932ef0 100644 --- a/blender/arm/material/make_mesh.py +++ b/blender/arm/material/make_mesh.py @@ -372,7 +372,7 @@ def make_forward_mobile(con_mesh): frag.write('float sdotNL = max(dot(n, sunDir), 0.0);') if is_shadows: vert.add_out('vec4 lightPosition') - vert.add_uniform('mat4 LWVP', '_biasLightWorldViewProjectionMatrix') + vert.add_uniform('mat4 LWVP', '_biasLightWorldViewProjectionMatrixSun') vert.write('lightPosition = LWVP * spos;') frag.add_uniform('bool receiveShadow') frag.add_uniform(f'sampler2DShadow {shadowmap_sun}') @@ -694,7 +694,7 @@ def make_forward_base(con_mesh, parse_opacity=False, transluc_pass=False): vert.write('lightPosition = LVP * vec4(wposition, 1.0);') else: vert.add_out('vec4 lightPosition') - vert.add_uniform('mat4 LWVP', '_biasLightWorldViewProjectionMatrix') + vert.add_uniform('mat4 LWVP', '_biasLightWorldViewProjectionMatrixSun') vert.write('lightPosition = LWVP * spos;') frag.write('vec3 lPos = lightPosition.xyz / lightPosition.w;') frag.write('const vec2 smSize = shadowmapSize;') From b930b87bc955eb20d69c7922f4c36c85055b1e19 Mon Sep 17 00:00:00 2001 From: Lubos Lenco Date: Fri, 2 Apr 2021 13:34:08 +0200 Subject: [PATCH 74/74] Bump version --- blender/arm/props.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blender/arm/props.py b/blender/arm/props.py index 91fe1ccf..5143c8c6 100755 --- a/blender/arm/props.py +++ b/blender/arm/props.py @@ -11,7 +11,7 @@ import arm.proxy import arm.utils # Armory version -arm_version = '2021.3' +arm_version = '2021.4' arm_commit = '$Id$' def get_project_html5_copy(self):