Exemplo n.º 1
0
def parse_surface(world: bpy.types.World, node_surface: bpy.types.Node,
                  frag: Shader):
    wrd = bpy.data.worlds['Arm']
    rpdat = arm.utils.get_rp()
    solid_mat = rpdat.arm_material_model == 'Solid'

    if node_surface.type in ('BACKGROUND', 'EMISSION'):
        # Append irradiance define
        if rpdat.arm_irradiance and not solid_mat:
            wrd.world_defs += '_Irr'

        # Extract environment strength
        # Todo: follow/parse strength input
        world.arm_envtex_strength = node_surface.inputs[1].default_value

        # Color
        out = cycles.parse_vector_input(node_surface.inputs[0])
        frag.write(f'fragColor.rgb = {out};')

        if not node_surface.inputs[0].is_linked:
            solid_mat = rpdat.arm_material_model == 'Solid'
            if rpdat.arm_irradiance and not solid_mat:
                world.world_defs += '_Irr'
            world.arm_envtex_color = node_surface.inputs[0].default_value
            world.arm_envtex_strength = 1.0

    else:
        log.warn(
            f'World node type {node_surface.type} must not be connected to the world output node!'
        )

    # Invalidate the parser state for subsequent executions
    cycles.state = None
Exemplo n.º 2
0
def parse_lightpath(node: bpy.types.ShaderNodeLightPath,
                    out_socket: bpy.types.NodeSocket,
                    state: ParserState) -> floatstr:
    # https://github.com/blender/blender/blob/master/source/blender/gpu/shaders/material/gpu_shader_material_light_path.glsl
    if out_socket == node.outputs['Is Camera Ray']:
        return '1.0'
    elif out_socket == node.outputs['Is Shadow Ray']:
        return '0.0'
    elif out_socket == node.outputs['Is Diffuse Ray']:
        return '1.0'
    elif out_socket == node.outputs['Is Glossy Ray']:
        return '1.0'
    elif out_socket == node.outputs['Is Singular Ray']:
        return '0.0'
    elif out_socket == node.outputs['Is Reflection Ray']:
        return '0.0'
    elif out_socket == node.outputs['Is Transmission Ray']:
        return '0.0'
    elif out_socket == node.outputs['Ray Length']:
        return '1.0'
    elif out_socket == node.outputs['Ray Depth']:
        return '0.0'
    elif out_socket == node.outputs['Diffuse Depth']:
        return '0.0'
    elif out_socket == node.outputs['Glossy Depth']:
        return '0.0'
    elif out_socket == node.outputs['Transparent Depth']:
        return '0.0'
    elif out_socket == node.outputs['Transmission Depth']:
        return '0.0'

    log.warn(f'Light Path node: unsupported output {out_socket.identifier}.')
    return '0.0'
Exemplo n.º 3
0
def parse_shader(node: bpy.types.Node, socket: bpy.types.NodeSocket) -> Tuple[str, ...]:
    # Use switch-like lookup via dictionary
    # (better performance, better code readability)
    # 'NODE_TYPE': parser_function
    node_parser_funcs: Dict[str, Callable] = {
        'MIX_SHADER': nodes_shader.parse_mixshader,
        'ADD_SHADER': nodes_shader.parse_addshader,
        'BSDF_PRINCIPLED': nodes_shader.parse_bsdfprincipled,
        'BSDF_DIFFUSE': nodes_shader.parse_bsdfdiffuse,
        'BSDF_GLOSSY': nodes_shader.parse_bsdfglossy,
        'AMBIENT_OCCLUSION': nodes_shader.parse_ambientocclusion,
        'BSDF_ANISOTROPIC': nodes_shader.parse_bsdfanisotropic,
        'EMISSION': nodes_shader.parse_emission,
        'BSDF_GLASS': nodes_shader.parse_bsdfglass,
        'HOLDOUT': nodes_shader.parse_holdout,
        'SUBSURFACE_SCATTERING': nodes_shader.parse_subsurfacescattering,
        'BSDF_TRANSLUCENT': nodes_shader.parse_bsdftranslucent,
        'BSDF_TRANSPARENT': nodes_shader.parse_bsdftransparent,
        'BSDF_VELVET': nodes_shader.parse_bsdfvelvet,
    }

    if node.type in node_parser_funcs:
        node_parser_funcs[node.type](node, socket, state)

    elif node.type == 'GROUP':
        if node.node_tree.name.startswith('Armory PBR'):
            if state.parse_surface:
                # Normal
                if node.inputs[5].is_linked and node.inputs[5].links[0].from_node.type == 'NORMAL_MAP':
                    log.warn(mat_name() + ' - Do not use Normal Map node with Armory PBR, connect Image Texture directly')
                parse_normal_map_color_input(node.inputs[5])
                # Base color
                state.out_basecol = parse_vector_input(node.inputs[0])
                # Occlusion
                state.out_occlusion = parse_value_input(node.inputs[2])
                # Roughness
                state.out_roughness = parse_value_input(node.inputs[3])
                # Metallic
                state.out_metallic = parse_value_input(node.inputs[4])
                # Emission
                if node.inputs[6].is_linked or node.inputs[6].default_value != 0.0:
                    state.out_emission = parse_value_input(node.inputs[6])
                    state.emission_found = True
            if state.parse_opacity:
                state.out_opacity = parse_value_input(node.inputs[1])
        else:
            return parse_group(node, socket)

    elif node.type == 'GROUP_INPUT':
        return parse_group_input(node, socket)

    elif node.type == 'CUSTOM':
        if node.bl_idname == 'ArmShaderDataNode':
            return node.parse(state.frag, state.vert)

    else:
        # TODO: Print node tree name (save in ParserState)
        log.warn(f'Material node type {node.type} not supported')

    return state.get_outs()
Exemplo n.º 4
0
def parse_value_input(inp: bpy.types.NodeSocket) -> floatstr:
    # Follow input
    if inp.is_linked:
        link = inp.links[0]

        if link.from_node.type == 'REROUTE':
            return parse_value_input(link.from_node.inputs[0])

        res_var = write_result(link)
        socket_type = link.from_socket.type
        if socket_type in ('RGB', 'RGBA', 'VECTOR'):
            # RGB to BW
            return rgb_to_bw(res_var)
        elif socket_type in ('VALUE', 'INT'):
            return res_var
        else:
            log.warn(f'Node tree "{tree_name()}": socket "{link.from_socket.name}" of node "{link.from_node.name}" cannot be connected to a scalar value socket')
            return '0.0'

    # Use value from socket
    else:
        if mat_batch() and inp.is_uniform:
            return to_uniform(inp)
        else:
            return to_vec1(inp.default_value)
Exemplo n.º 5
0
def parse_vector_input(inp: bpy.types.NodeSocket) -> vec3str:
    """Return the parsed result of the given input socket."""
    # Follow input
    if inp.is_linked:
        link = inp.links[0]
        if link.from_node.type == 'REROUTE':
            return parse_vector_input(link.from_node.inputs[0])
        res_var = write_result(link)
        st = link.from_socket.type
        if st in ('RGB', 'RGBA', 'VECTOR'):
            return res_var
        elif st in ('VALUE', 'INT'):
            return f'vec3({res_var})'
        else:
            log.warn(f'Node tree "{tree_name()}": socket "{link.from_socket.name}" of node "{link.from_node.name}" cannot be connected to a vector-like socket')
            return to_vec3([0.0, 0.0, 0.0])

    # Unlinked reroute
    elif inp.type == 'VALUE':
        return to_vec3([0.0, 0.0, 0.0])

    # Use direct socket value
    else:
        if mat_batch() and inp.is_uniform:
            return to_uniform(inp)
        else:
            return to_vec3(inp.default_value)
Exemplo n.º 6
0
def parse_value(node, socket):
    node_parser_funcs: Dict[str, Callable] = {
        'ATTRIBUTE': nodes_input.parse_attribute,
        'CAMERA': nodes_input.parse_camera,
        'FRESNEL': nodes_input.parse_fresnel,
        'NEW_GEOMETRY': nodes_input.parse_geometry,
        'HAIR_INFO': nodes_input.parse_hairinfo,
        'LAYER_WEIGHT': nodes_input.parse_layerweight,
        'LIGHT_PATH': nodes_input.parse_lightpath,
        'OBJECT_INFO': nodes_input.parse_objectinfo,
        'PARTICLE_INFO': nodes_input.parse_particleinfo,
        'VALUE': nodes_input.parse_value,
        'WIREFRAME': nodes_input.parse_wireframe,
        'TEX_BRICK': nodes_texture.parse_tex_brick,
        'TEX_CHECKER': nodes_texture.parse_tex_checker,
        'TEX_GRADIENT': nodes_texture.parse_tex_gradient,
        'TEX_IMAGE': nodes_texture.parse_tex_image,
        'TEX_MAGIC': nodes_texture.parse_tex_magic,
        'TEX_MUSGRAVE': nodes_texture.parse_tex_musgrave,
        'TEX_NOISE': nodes_texture.parse_tex_noise,
        'TEX_POINTDENSITY': nodes_texture.parse_tex_pointdensity,
        'TEX_VORONOI': nodes_texture.parse_tex_voronoi,
        'TEX_WAVE': nodes_texture.parse_tex_wave,
        'LIGHT_FALLOFF': nodes_color.parse_lightfalloff,
        'NORMAL': nodes_vector.parse_normal,
        'CLAMP': nodes_converter.parse_clamp,
        'VALTORGB': nodes_converter.parse_valtorgb,
        'MATH': nodes_converter.parse_math,
        'RGBTOBW': nodes_converter.parse_rgbtobw,
        'SEPHSV': nodes_converter.parse_sephsv,
        '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:
        return node_parser_funcs[node.type](node, socket, state)

    elif node.type == 'GROUP':
        if node.node_tree.name.startswith('Armory PBR'):
            # Displacement
            if socket == node.outputs[1]:
                return parse_value_input(node.inputs[7])
            else:
                return None
        else:
            return parse_group(node, socket)

    elif node.type == 'GROUP_INPUT':
        return parse_group_input(node, socket)

    elif node.type == 'CUSTOM':
        if node.bl_idname == 'ArmShaderDataNode':
            return node.parse(state.frag, state.vert)

    log.warn(f'Material node type {node.type} not supported')
    return '0.0'
Exemplo n.º 7
0
def disp_linked(output_node):
    # Armory PBR with unlinked height socket
    linked = output_node.inputs[2].is_linked
    tess_enabled = arm.utils.tess_enabled(make_state.target)
    if linked:
        l = output_node.inputs[2].links[0]
        if l.from_node.type == 'GROUP' and l.from_node.node_tree.name.startswith(
                'Armory PBR') and l.from_node.inputs[10].is_linked == False:
            return False
    if linked and not tess_enabled:
        log.warn('Tessellation not available on ' + make_state.target)
    return tess_enabled and linked
Exemplo n.º 8
0
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 = []
    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]
        mat_state.uses_instancing = False

        for bo in mat_users[mat]:
            if mat.arm_custom_material == '':
                # Morph Targets
                if arm.utils.export_morph_targets(bo):
                    global_elems.append({
                        'name': 'morph',
                        'data': 'short2norm'
                    })
                # 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
                mat_state.uses_instancing = 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
Exemplo n.º 9
0
def disp_linked(output_node):
    # Armory PBR with unlinked height socket
    linked = output_node.inputs[2].is_linked
    disp_enabled = arm.utils.disp_enabled(make_state.target)
    if linked:
        l = output_node.inputs[2].links[0]
        if l.from_node.type == 'GROUP' and l.from_node.node_tree.name.startswith('Armory PBR') and \
            ((len(l.from_node.inputs) == 14 and l.from_node.inputs[10].is_linked == False) or (len(l.from_node.inputs) != 14 and l.from_node.inputs[7].is_linked == False)):
            return False
    rpdat = arm.utils.get_rp()
    if linked and not disp_enabled and rpdat.arm_displacement:
        log.warn('Tessellation not available on ' + make_state.target)
    return disp_enabled and linked
Exemplo n.º 10
0
def disp_linked(output_node):
    linked = output_node.inputs[2].is_linked
    if not linked:
        return False
    # Armory PBR with unlinked height socket
    l = output_node.inputs[2].links[0]
    if l.from_node.type == 'GROUP' and l.from_node.node_tree.name.startswith('Armory PBR') and \
        l.from_node.inputs[7].is_linked == False:
        return False
    disp_enabled = arm.utils.disp_enabled(make_state.target)
    rpdat = arm.utils.get_rp()
    if not disp_enabled and rpdat.arm_rp_displacement == 'Tessellation':
        log.warn('Tessellation not available on ' + make_state.target)
    return disp_enabled
Exemplo n.º 11
0
def cpu_count(*, physical_only=False) -> Optional[int]:
    """Returns the number of logical (default) or physical CPUs.
    The result can be `None` if `os.cpu_count()` was not able to get the
    correct count of logical CPUs.
    """
    if not physical_only:
        return os.cpu_count()

    err_reason = ''
    command = []

    _os = get_os()
    try:
        if _os == 'win':
            sysroot = os.environ.get("SYSTEMROOT", default="C:\\WINDOWS")
            command = [
                f'{sysroot}\\System32\\wbem\\wmic.exe', 'cpu', 'get',
                'NumberOfCores'
            ]
            result = subprocess.check_output(command)
            result = result.decode('utf-8').splitlines()
            result = int(result[2])
            if result > 0:
                return result

        elif _os == 'linux':
            command = ["grep -P '^core id' /proc/cpuinfo | sort -u | wc -l"]
            result = subprocess.check_output(command[0], shell=True)
            result = result.decode('utf-8').splitlines()
            result = int(result[0])
            if result > 0:
                return result

        # macOS
        else:
            command = ['sysctl', '-n', 'hw.physicalcpu']
            return int(subprocess.check_output(command))

    except subprocess.CalledProcessError as e:
        err_reason = f'Reason: command {command} exited with code {e.returncode}.'
    except FileNotFoundError as e:
        err_reason = f'Reason: couldn\'t open file from command {command} ({e.errno=}).'

    # Last resort even though it can be wrong
    log.warn(
        "Could not retrieve count of physical CPUs, using logical CPU count instead.\n\t"
        + err_reason)
    return os.cpu_count()
Exemplo n.º 12
0
def check_sdkpath(self):
    s = get_sdk_path()
    if not check_path(s):
        msg = f"SDK path '{s}' contains special characters. Please move SDK to different path for now."
        self.report({"ERROR"}, msg) if self is not None else log.warn(msg)
        return False
    else:
        return True
Exemplo n.º 13
0
def check_projectpath(self):
    s = get_fp()
    if not check_path(s):
        msg = f"Project path '{s}' contains special characters, build process may fail."
        self.report({"ERROR"}, msg) if self is not None else log.warn(msg)
        return False
    else:
        return True
Exemplo n.º 14
0
def parse_clamp(node: bpy.types.ShaderNodeClamp,
                out_socket: bpy.types.NodeSocket,
                state: ParserState) -> floatstr:
    value = c.parse_value_input(node.inputs['Value'])
    minVal = c.parse_value_input(node.inputs['Min'])
    maxVal = c.parse_value_input(node.inputs['Max'])

    if node.clamp_type == 'MINMAX':
        # Condition is minVal < maxVal, otherwise use 'RANGE' type
        return f'clamp({value}, {minVal}, {maxVal})'

    elif node.clamp_type == 'RANGE':
        return f'{minVal} < {maxVal} ? clamp({value}, {minVal}, {maxVal}) : clamp({value}, {maxVal}, {minVal})'

    else:
        log.warn(f'Clamp node: unsupported clamp type {node.clamp_type}.')
        return value
Exemplo n.º 15
0
def parse_shader_input(inp: bpy.types.NodeSocket) -> Tuple[str, ...]:
    # Follow input
    if inp.is_linked:
        link = inp.links[0]
        if link.from_node.type == 'REROUTE':
            return parse_shader_input(link.from_node.inputs[0])

        if link.from_socket.type != 'SHADER':
            log.warn(f'Node tree "{tree_name()}": socket "{link.from_socket.name}" of node "{link.from_node.name}" cannot be connected to a shader socket')
            state.reset_outs()
            return state.get_outs()

        return parse_shader(link.from_node, link.from_socket)

    else:
        # Return default shader values
        state.reset_outs()
        return state.get_outs()
Exemplo n.º 16
0
def register():
    wm = bpy.context.window_manager
    addon_keyconfig = wm.keyconfigs.addon

    # Keyconfigs are not available in background mode. If the keyconfig
    # was not found despite running _not_ in background mode, a warning
    # is printed
    if addon_keyconfig is None:
        if not bpy.app.background:
            log.warn("No keyconfig path found")
        return

    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)
Exemplo n.º 17
0
def add(asset_file):
    global assets

    # Asset already exists, do nothing
    if asset_file in assets:
        return

    asset_file_base = os.path.basename(asset_file)
    for f in assets:
        f_file_base = os.path.basename(f)
        if f_file_base == asset_file_base:
            log.warn(
                f'Asset name "{asset_file_base}" already exists, skipping')
            return

    assets.append(asset_file)

    # Reserved file name
    for f in reserved_names:
        if f in asset_file:
            log.warn(
                f'File "{asset_file}" contains reserved keyword, this will break C++ builds!'
            )
Exemplo n.º 18
0
def add_world_defs():
    wrd = bpy.data.worlds['Arm']
    rpdat = arm.utils.get_rp()

    # Screen-space ray-traced shadows
    if rpdat.arm_ssrs:
        wrd.world_defs += '_SSRS'

    if rpdat.arm_two_sided_area_light:
        wrd.world_defs += '_TwoSidedAreaLight'

    # Store contexts
    if rpdat.rp_hdr == False:
        wrd.world_defs += '_LDR'

    # Alternative models
    if rpdat.arm_diffuse_model == 'OrenNayar':
        wrd.world_defs += '_OrenNayar'

    # TODO: Light texture test..
    if wrd.arm_light_texture != '':
        wrd.world_defs += '_LightColTex'

    if wrd.arm_light_ies_texture != '':
        wrd.world_defs += '_LightIES'
        assets.add_embedded_data('iestexture.png')

    if wrd.arm_light_clouds_texture != '':
        wrd.world_defs += '_LightClouds'
        assets.add_embedded_data('cloudstexture.png')

    if rpdat.rp_renderer == 'Deferred':
        assets.add_khafile_def('arm_deferred')
        wrd.world_defs += '_Deferred'

    # GI
    voxelgi = False
    voxelao = False
    has_voxels = arm.utils.voxel_support()
    if has_voxels:
        if rpdat.rp_gi == 'Voxel GI':
            voxelgi = True
        elif rpdat.rp_gi == 'Voxel AO':
            voxelao = True
    # Shadows
    if rpdat.rp_shadowmap == 'Off':
        wrd.world_defs += '_NoShadows'
        assets.add_khafile_def('arm_no_shadows')
    else:
        if rpdat.rp_shadowmap_cascades != '1':
            if voxelgi:
                log.warn(
                    'Disabling shadow cascades - Voxel GI does not support cascades yet'
                )
            else:
                wrd.world_defs += '_CSM'
                assets.add_khafile_def('arm_csm')
    # SS
    # if rpdat.rp_dfrs:
    #     wrd.world_defs += '_DFRS'
    #     assets.add_khafile_def('arm_sdf')
    # if rpdat.rp_dfao:
    #     wrd.world_defs += '_DFAO'
    #     assets.add_khafile_def('arm_sdf')
    # if rpdat.rp_dfgi:
    #     wrd.world_defs += '_DFGI'
    #     assets.add_khafile_def('arm_sdf')
    if rpdat.rp_ssgi == 'RTGI' or rpdat.rp_ssgi == 'RTAO':
        if rpdat.rp_ssgi == 'RTGI':
            wrd.world_defs += '_RTGI'
        if rpdat.arm_ssgi_rays == '9':
            wrd.world_defs += '_SSGICone9'
    if rpdat.rp_autoexposure:
        wrd.world_defs += '_AutoExposure'

    if voxelgi or voxelao:
        assets.add_khafile_def('arm_voxelgi')
        wrd.world_defs += '_VoxelCones' + rpdat.arm_voxelgi_cones
        if rpdat.arm_voxelgi_revoxelize:
            assets.add_khafile_def('arm_voxelgi_revox')
            if rpdat.arm_voxelgi_camera:
                wrd.world_defs += '_VoxelGICam'
            if rpdat.arm_voxelgi_temporal:
                assets.add_khafile_def('arm_voxelgi_temporal')
                wrd.world_defs += '_VoxelGITemporal'

        if voxelgi:
            wrd.world_defs += '_VoxelGI'
            assets.add_shader_external(
                arm.utils.get_sdk_path() +
                '/armory/Shaders/voxel_light/voxel_light.comp.glsl')
            if rpdat.arm_voxelgi_bounces != "1":
                assets.add_khafile_def('rp_gi_bounces={0}'.format(
                    rpdat.arm_voxelgi_bounces))
                assets.add_shader_external(
                    arm.utils.get_sdk_path() +
                    '/armory/Shaders/voxel_bounce/voxel_bounce.comp.glsl')
            if rpdat.arm_voxelgi_shadows:
                wrd.world_defs += '_VoxelGIDirect'
                wrd.world_defs += '_VoxelGIShadow'
            if rpdat.arm_voxelgi_refraction:
                wrd.world_defs += '_VoxelGIDirect'
                wrd.world_defs += '_VoxelGIRefract'
            if rpdat.rp_voxelgi_relight:
                assets.add_khafile_def('rp_voxelgi_relight')
        elif voxelao:
            wrd.world_defs += '_VoxelAO'

    if arm.utils.get_gapi().startswith(
            'direct3d'):  # Flip Y axis in drawQuad command
        wrd.world_defs += '_InvY'

    if arm.utils.get_legacy_shaders() and not state.is_viewport:
        wrd.world_defs += '_Legacy'

    # Area lights
    lights = bpy.data.lights if bpy.app.version >= (2, 80,
                                                    1) else bpy.data.lamps
    for light in lights:
        if light.type == 'AREA':
            wrd.world_defs += '_LTC'
            assets.add_khafile_def('arm_ltc')
            break

    if '_Rad' in wrd.world_defs or '_VoxelGI' in wrd.world_defs:
        wrd.world_defs += '_Brdf'
    if '_Brdf' in wrd.world_defs or '_VoxelAO' in wrd.world_defs:
        wrd.world_defs += '_IndPos'
Exemplo n.º 19
0
def parse_shader(node, socket):
    global parsing_basecol
    out_basecol = 'vec3(0.8)'
    out_roughness = '0.0'
    out_metallic = '0.0'
    out_occlusion = '1.0'
    out_opacity = '1.0'

    if node.type == 'GROUP':
        if node.node_tree.name.startswith('Armory PBR'):

            if parse_surface:
                # Base color
                parsing_basecol = True
                out_basecol = parse_vector_input(node.inputs[0])
                parsing_basecol = False
                # Occlusion TODO: deprecated, occlussion is value instead of vector now
                if node.inputs[1].type == 'RGBA':
                    out_occlusion = '{0}.r'.format(
                        parse_vector_input(node.inputs[1]))
                else:
                    out_occlusion = parse_value_input(node.inputs[1])
                if node.inputs[
                        2].is_linked or node.inputs[2].default_value != 1.0:
                    out_occlusion = '({0} * {1})'.format(
                        out_occlusion, parse_value_input(node.inputs[2]))
                # Roughness
                out_roughness = parse_value_input(node.inputs[3])
                if node.inputs[
                        4].is_linked or node.inputs[4].default_value != 1.0:
                    out_roughness = '({0} * {1})'.format(
                        out_roughness, parse_value_input(node.inputs[4]))
                # Metallic
                out_metallic = parse_value_input(node.inputs[5])
                # Normal
                if node.inputs[6].is_linked and node.inputs[6].links[
                        0].from_node.type == 'NORMAL_MAP':
                    log.warn(
                        mat_state.material.name +
                        ' - Do not use Normal Map node with Armory PBR, connect Image Texture directly'
                    )
                parse_normal_map_color_input(node.inputs[6], node.inputs[7])
                # Emission
                if node.inputs[8].is_linked:
                    parsing_basecol = True
                    out_emission = parse_vector_input(node.inputs[8])
                    parsing_basecol = False
                    if node.inputs[9].is_linked or node.inputs[
                            9].default_value != 1.0:
                        out_emission = '({0} * {1})'.format(
                            out_emission, parse_value_input(node.inputs[9]))
                    out_basecol = '({0} + {1})'.format(out_basecol,
                                                       out_emission)

            if parse_opacity:
                out_opacity = parse_value_input(node.inputs[12])
                if node.inputs[
                        13].is_linked or node.inputs[13].default_value != 1.0:
                    out_opacity = '({0} * {1})'.format(
                        out_opacity, parse_value_input(node.inputs[13]))
        else:
            return parse_group(node, socket)

    elif node.type == 'GROUP_INPUT':
        return parse_group_input(node, socket)

    elif node.type == 'MIX_SHADER':
        prefix = '' if node.inputs[0].is_linked else 'const '
        fac = parse_value_input(node.inputs[0])
        fac_var = node_name(node.name) + '_fac'
        fac_inv_var = node_name(node.name) + '_fac_inv'
        curshader.write('{0}float {1} = {2};'.format(prefix, fac_var, fac))
        curshader.write('{0}float {1} = 1.0 - {2};'.format(
            prefix, fac_inv_var, fac_var))
        bc1, rough1, met1, occ1, opac1 = parse_shader_input(node.inputs[1])
        bc2, rough2, met2, occ2, opac2 = parse_shader_input(node.inputs[2])
        if parse_surface:
            parsing_basecol = True
            out_basecol = '({0} * {3} + {1} * {2})'.format(
                bc1, bc2, fac_var, fac_inv_var)
            parsing_basecol = False
            out_roughness = '({0} * {3} + {1} * {2})'.format(
                rough1, rough2, fac_var, fac_inv_var)
            out_metallic = '({0} * {3} + {1} * {2})'.format(
                met1, met2, fac_var, fac_inv_var)
            out_occlusion = '({0} * {3} + {1} * {2})'.format(
                occ1, occ2, fac_var, fac_inv_var)
        if parse_opacity:
            out_opacity = '({0} * {3} + {1} * {2})'.format(
                opac1, opac2, fac_var, fac_inv_var)

    elif node.type == 'ADD_SHADER':
        bc1, rough1, met1, occ1, opac1 = parse_shader_input(node.inputs[0])
        bc2, rough2, met2, occ2, opac2 = parse_shader_input(node.inputs[1])
        if parse_surface:
            parsing_basecol = True
            out_basecol = '({0} + {1})'.format(bc1, bc2)
            parsing_basecol = False
            out_roughness = '({0} * 0.5 + {1} * 0.5)'.format(rough1, rough2)
            out_metallic = '({0} * 0.5 + {1} * 0.5)'.format(met1, met2)
            out_occlusion = '({0} * 0.5 + {1} * 0.5)'.format(occ1, occ2)
        if parse_opacity:
            out_opacity = '({0} * 0.5 + {1} * 0.5)'.format(opac1, opac2)

    elif node.type == 'BSDF_DIFFUSE':
        if parse_surface:
            write_normal(node.inputs[2])
            parsing_basecol = True
            out_basecol = parse_vector_input(node.inputs[0])
            parsing_basecol = False
            out_roughness = parse_value_input(node.inputs[1])

    elif node.type == 'BSDF_GLOSSY':
        if parse_surface:
            write_normal(node.inputs[2])
            parsing_basecol = True
            out_basecol = parse_vector_input(node.inputs[0])
            parsing_basecol = False
            out_roughness = parse_value_input(node.inputs[1])
            out_metallic = '1.0'

    elif node.type == 'AMBIENT_OCCLUSION':
        if parse_surface:
            # Single channel
            out_occlusion = parse_vector_input(node.inputs[0]) + '.r'

    elif node.type == 'BSDF_ANISOTROPIC':
        if parse_surface:
            write_normal(node.inputs[4])
            # Revert to glossy
            parsing_basecol = True
            out_basecol = parse_vector_input(node.inputs[0])
            parsing_basecol = False
            out_roughness = parse_value_input(node.inputs[1])
            out_metallic = '1.0'

    elif node.type == 'EMISSION':
        if parse_surface:
            # Multiply basecol
            parsing_basecol = True
            out_basecol = parse_vector_input(node.inputs[0])
            parsing_basecol = False
            strength = parse_value_input(node.inputs[1])
            out_basecol = '({0} * {1} * 50.0)'.format(out_basecol, strength)

    elif node.type == 'BSDF_GLASS':
        if parse_surface:
            write_normal(node.inputs[3])
            out_roughness = parse_value_input(node.inputs[1])
        if parse_opacity:
            out_opacity = '(1.0 - {0}.r)'.format(
                parse_vector_input(node.inputs[0]))

    elif node.type == 'BSDF_HAIR':
        pass

    elif node.type == 'HOLDOUT':
        if parse_surface:
            # Occlude
            out_occlusion = '0.0'

    elif node.type == 'BSDF_REFRACTION':
        # write_normal(node.inputs[3])
        pass

    elif node.type == 'SUBSURFACE_SCATTERING':
        # write_normal(node.inputs[4])
        pass

    elif node.type == 'BSDF_TOON':
        # write_normal(node.inputs[3])
        pass

    elif node.type == 'BSDF_TRANSLUCENT':
        if parse_surface:
            write_normal(node.inputs[1])
        if parse_opacity:
            out_opacity = '(1.0 - {0}.r)'.format(
                parse_vector_input(node.inputs[0]))

    elif node.type == 'BSDF_TRANSPARENT':
        if parse_opacity:
            out_opacity = '(1.0 - {0}.r)'.format(
                parse_vector_input(node.inputs[0]))

    elif node.type == 'BSDF_VELVET':
        if parse_surface:
            write_normal(node.inputs[2])
            parsing_basecol = True
            out_basecol = parse_vector_input(node.inputs[0])
            parsing_basecol = False
            out_roughness = '1.0'
            out_metallic = '1.0'

    elif node.type == 'VOLUME_ABSORPTION':
        pass

    elif node.type == 'VOLUME_SCATTER':
        pass

    return out_basecol, out_roughness, out_metallic, out_occlusion, out_opacity
Exemplo n.º 20
0
def make_texture(image_node: bpy.types.ShaderNodeTexImage,
                 tex_name: str,
                 matname: str = None) -> Optional[Dict[str, Any]]:
    tex = {'name': tex_name}

    if matname is None:
        matname = mat_state.material.name

    image = image_node.image
    if image is None:
        return None

    # Get filepath
    filepath = image.filepath
    if filepath == '':
        if image.packed_file is not None:
            filepath = './' + image.name
            has_ext = filepath.endswith(('.jpg', '.png', '.hdr'))
            if not has_ext:
                # Raw bytes, write converted .jpg to /unpacked
                filepath += '.raw'

        elif image.source == "GENERATED":
            unpack_path = os.path.join(arm.utils.get_fp_build(), 'compiled',
                                       'Assets', 'unpacked')
            if not os.path.exists(unpack_path):
                os.makedirs(unpack_path)

            filepath = os.path.join(unpack_path, image.name + ".jpg")
            arm.utils.convert_image(image, filepath, "JPEG")

        else:
            log.warn(matname + '/' + image.name + ' - invalid file path')
            return None

    # Reference image name
    texpath = arm.utils.asset_path(filepath)
    texfile = arm.utils.extract_filename(filepath)
    tex['file'] = arm.utils.safestr(texfile)
    s = tex['file'].rsplit('.', 1)

    if len(s) == 1:
        log.warn(matname + '/' + image.name +
                 ' - file extension required for image name')
        return None

    ext = s[1].lower()
    do_convert = ext not in ('jpg', 'png', 'hdr', 'mp4')  # Convert image
    if do_convert:
        new_ext = 'png' if (ext in ('tga', 'dds')) else 'jpg'
        tex['file'] = tex['file'].rsplit('.', 1)[0] + '.' + new_ext

    if image.packed_file is not None or not is_ascii(texfile):
        # Extract packed data / copy non-ascii texture
        unpack_path = os.path.join(arm.utils.get_fp_build(), 'compiled',
                                   'Assets', 'unpacked')
        if not os.path.exists(unpack_path):
            os.makedirs(unpack_path)
        unpack_filepath = os.path.join(unpack_path, tex['file'])

        if do_convert:
            if not os.path.isfile(unpack_filepath):
                fmt = 'PNG' if new_ext == 'png' else 'JPEG'
                arm.utils.convert_image(image,
                                        unpack_filepath,
                                        file_format=fmt)
        else:

            # Write bytes if size is different or file does not exist yet
            if image.packed_file is not None:
                if not os.path.isfile(unpack_filepath) or os.path.getsize(
                        unpack_filepath) != image.packed_file.size:
                    with open(unpack_filepath, 'wb') as f:
                        f.write(image.packed_file.data)
            # Copy non-ascii texture
            else:
                if not os.path.isfile(unpack_filepath) or os.path.getsize(
                        unpack_filepath) != os.path.getsize(texpath):
                    shutil.copy(texpath, unpack_filepath)

        arm.assets.add(unpack_filepath)

    else:
        if not os.path.isfile(arm.utils.asset_path(filepath)):
            log.warn('Material ' + matname + '/' + image.name +
                     ' - file not found(' + filepath + ')')
            return None

        if do_convert:
            unpack_path = os.path.join(arm.utils.get_fp_build(), 'compiled',
                                       'Assets', 'unpacked')
            if not os.path.exists(unpack_path):
                os.makedirs(unpack_path)
            converted_path = os.path.join(unpack_path, tex['file'])
            # TODO: delete cache when file changes
            if not os.path.isfile(converted_path):
                fmt = 'PNG' if new_ext == 'png' else 'JPEG'
                arm.utils.convert_image(image, converted_path, file_format=fmt)
            arm.assets.add(converted_path)
        else:
            # Link image path to assets
            # TODO: Khamake converts .PNG to .jpg? Convert ext to lowercase on windows
            if arm.utils.get_os() == 'win':
                s = filepath.rsplit('.', 1)
                arm.assets.add(arm.utils.asset_path(s[0] + '.' + s[1].lower()))
            else:
                arm.assets.add(arm.utils.asset_path(filepath))

    # if image_format != 'RGBA32':
    # tex['format'] = image_format

    interpolation = image_node.interpolation
    rpdat = arm.utils.get_rp()
    texfilter = rpdat.arm_texture_filter
    if texfilter == 'Anisotropic':
        interpolation = 'Smart'
    elif texfilter == 'Linear':
        interpolation = 'Linear'
    elif texfilter == 'Point':
        interpolation = 'Closest'

    # TODO: Blender seems to load full images on size request, cache size instead
    powimage = is_pow(image.size[0]) and is_pow(image.size[1])

    if interpolation == 'Cubic':  # Mipmap linear
        tex['mipmap_filter'] = 'linear'
        tex['generate_mipmaps'] = True
    elif interpolation == 'Smart':  # Mipmap anisotropic
        tex['min_filter'] = 'anisotropic'
        tex['mipmap_filter'] = 'linear'
        tex['generate_mipmaps'] = True
    elif interpolation == 'Closest':
        tex['min_filter'] = 'point'
        tex['mag_filter'] = 'point'
    # else defaults to linear

    if image_node.extension != 'REPEAT':  # Extend or clip
        tex['u_addressing'] = 'clamp'
        tex['v_addressing'] = 'clamp'

    if image.source == 'MOVIE':
        tex['source'] = 'movie'
        tex['min_filter'] = 'linear'
        tex['mag_filter'] = 'linear'
        tex['mipmap_filter'] = 'no'
        tex['generate_mipmaps'] = False

    return tex
Exemplo n.º 21
0
def check_saved(self):
    if bpy.data.filepath == "":
        msg = "Save blend file first"
        self.report({"ERROR"}, msg) if self is not None else log.warn(msg)
        return False
    return True
Exemplo n.º 22
0
def play_project(in_viewport, is_render=False, is_render_anim=False):
    global scripts_mtime
    global code_parsed
    wrd = bpy.data.worlds['Arm']

    log.clear()

    # Store area
    if arm.utils.with_krom() and in_viewport and bpy.context.area != None and bpy.context.area.type == 'VIEW_3D':
        state.play_area = bpy.context.area

    state.target = runtime_to_target(in_viewport)

    # Build data
    build_project(is_play=True, is_render=is_render, is_render_anim=is_render_anim, in_viewport=in_viewport)

    khajs_path = get_khajs_path(in_viewport, state.target)
    if not wrd.arm_cache_compiler or \
       not os.path.isfile(khajs_path) or \
       assets.khafile_defs_last != assets.khafile_defs or \
       state.last_target != state.target or \
       state.last_in_viewport != state.in_viewport or \
       state.target == 'native':
        wrd.arm_recompile = True

    state.last_target = state.target
    state.last_in_viewport = state.in_viewport

    if state.in_viewport:
        if arm.utils.get_rp().rp_gi != 'Off' and bpy.app.version < (2, 80, 1):
            log.warn('Use Blender 2.8 to run Voxel GI in viewport')

    # Trait sources modified
    state.mod_scripts = []
    script_path = arm.utils.get_fp() + '/Sources/' + arm.utils.safestr(wrd.arm_project_package)
    if os.path.isdir(script_path):
        new_mtime = scripts_mtime
        for fn in glob.iglob(os.path.join(script_path, '**', '*.hx'), recursive=True):
            mtime = os.path.getmtime(fn)
            if scripts_mtime < mtime:
                arm.utils.fetch_script_props(fn) # Trait props
                fn = fn.split('Sources/')[1]
                fn = fn[:-3] #.hx
                fn = fn.replace('/', '.')
                state.mod_scripts.append(fn)
                wrd.arm_recompile = True
                if new_mtime < mtime:
                    new_mtime = mtime
        scripts_mtime = new_mtime
        if len(state.mod_scripts) > 0: # Trait props
            arm.utils.fetch_trait_props()

    # New compile requred - traits changed
    if wrd.arm_recompile:
        state.recompiled = True
        if state.krom_running:
            # TODO: Unable to live-patch, stop player
            bpy.ops.arm.space_stop('EXEC_DEFAULT')
            return
            if not code_parsed:
                code_parsed = True
                barmory.parse_code()
        else:
            code_parsed = False

        mode = 'play'
        if state.target == 'native':
            state.compileproc = compile_project(target_name='--run')
        elif state.target == 'krom':
            if in_viewport:
                mode = 'play_viewport'
            state.compileproc = compile_project(target_name='krom')
        else: # Browser
            state.compileproc = compile_project(target_name='html5')
        threading.Timer(0.1, watch_compile, [mode]).start()
    else: # kha.js up to date
        state.recompiled = False
        compile_project(patch=True)
Exemplo n.º 23
0
def parse_mixrgb(node: bpy.types.ShaderNodeMixRGB,
                 out_socket: bpy.types.NodeSocket,
                 state: ParserState) -> vec3str:
    col1 = c.parse_vector_input(node.inputs[1])
    col2 = c.parse_vector_input(node.inputs[2])

    # Store factor in variable for linked factor input
    if node.inputs[0].is_linked:
        fac = c.node_name(node.name) + '_fac'
        state.curshader.write('float {0} = {1};'.format(
            fac, c.parse_value_input(node.inputs[0])))
    else:
        fac = c.parse_value_input(node.inputs[0])

    # TODO: Do not mix if factor is constant 0.0 or 1.0?

    blend = node.blend_type
    if blend == 'MIX':
        out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac)
    elif blend == 'ADD':
        out_col = 'mix({0}, {0} + {1}, {2})'.format(col1, col2, fac)
    elif blend == 'MULTIPLY':
        out_col = 'mix({0}, {0} * {1}, {2})'.format(col1, col2, fac)
    elif blend == 'SUBTRACT':
        out_col = 'mix({0}, {0} - {1}, {2})'.format(col1, col2, fac)
    elif blend == 'SCREEN':
        out_col = '(vec3(1.0) - (vec3(1.0 - {2}) + {2} * (vec3(1.0) - {1})) * (vec3(1.0) - {0}))'.format(
            col1, col2, fac)
    elif blend == 'DIVIDE':
        out_col = '(vec3((1.0 - {2}) * {0} + {2} * {0} / {1}))'.format(
            col1, col2, fac)
    elif blend == 'DIFFERENCE':
        out_col = 'mix({0}, abs({0} - {1}), {2})'.format(col1, col2, fac)
    elif blend == 'DARKEN':
        out_col = 'min({0}, {1} * {2})'.format(col1, col2, fac)
    elif blend == 'LIGHTEN':
        out_col = 'max({0}, {1} * {2})'.format(col1, col2, fac)
    elif blend == 'OVERLAY':
        out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac)  # Revert to mix
    elif blend == 'DODGE':
        out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac)  # Revert to mix
    elif blend == 'BURN':
        out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac)  # Revert to mix
    elif blend == 'HUE':
        out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac)  # Revert to mix
    elif blend == 'SATURATION':
        out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac)  # Revert to mix
    elif blend == 'VALUE':
        out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac)  # Revert to mix
    elif blend == 'COLOR':
        out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac)  # Revert to mix
    elif blend == 'SOFT_LIGHT':
        out_col = '((1.0 - {2}) * {0} + {2} * ((vec3(1.0) - {0}) * {1} * {0} + {0} * (vec3(1.0) - (vec3(1.0) - {1}) * (vec3(1.0) - {0}))));'.format(
            col1, col2, fac)
    elif blend == 'LINEAR_LIGHT':
        out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac)  # Revert to mix
        # out_col = '({0} + {2} * (2.0 * ({1} - vec3(0.5))))'.format(col1, col2, fac_var)
    else:
        log.warn(f'MixRGB node: unsupported blend type {node.blend_type}.')
        return col1

    if node.use_clamp:
        return 'clamp({0}, vec3(0.0), vec3(1.0))'.format(out_col)
    return out_col
Exemplo n.º 24
0
def parse_vector(node: bpy.types.Node, socket: bpy.types.NodeSocket) -> str:
    """Parses the vector/color output value from the given node and socket."""
    node_parser_funcs: Dict[str, Callable] = {
        'ATTRIBUTE': nodes_input.parse_attribute,

        # RGB outputs
        'RGB': nodes_input.parse_rgb,
        'TEX_BRICK': nodes_texture.parse_tex_brick,
        'TEX_CHECKER': nodes_texture.parse_tex_checker,
        'TEX_ENVIRONMENT': nodes_texture.parse_tex_environment,
        'TEX_GRADIENT': nodes_texture.parse_tex_gradient,
        'TEX_IMAGE': nodes_texture.parse_tex_image,
        'TEX_MAGIC': nodes_texture.parse_tex_magic,
        'TEX_MUSGRAVE': nodes_texture.parse_tex_musgrave,
        'TEX_NOISE': nodes_texture.parse_tex_noise,
        'TEX_POINTDENSITY': nodes_texture.parse_tex_pointdensity,
        'TEX_SKY': nodes_texture.parse_tex_sky,
        'TEX_VORONOI': nodes_texture.parse_tex_voronoi,
        'TEX_WAVE': nodes_texture.parse_tex_wave,
        'VERTEX_COLOR': nodes_input.parse_vertex_color,
        'BRIGHTCONTRAST': nodes_color.parse_brightcontrast,
        'GAMMA': nodes_color.parse_gamma,
        'HUE_SAT': nodes_color.parse_huesat,
        'INVERT': nodes_color.parse_invert,
        'MIX_RGB': nodes_color.parse_mixrgb,
        'BLACKBODY': nodes_converter.parse_blackbody,
        'VALTORGB': nodes_converter.parse_valtorgb,  # ColorRamp
        'CURVE_VEC': nodes_vector.parse_curvevec,  # Vector Curves
        'CURVE_RGB': nodes_color.parse_curvergb,
        'COMBHSV': nodes_converter.parse_combhsv,
        'COMBRGB': nodes_converter.parse_combrgb,
        'WAVELENGTH': nodes_converter.parse_wavelength,

        # Vector outputs
        'CAMERA': nodes_input.parse_camera,
        'NEW_GEOMETRY': nodes_input.parse_geometry,
        'HAIR_INFO': nodes_input.parse_hairinfo,
        'OBJECT_INFO': nodes_input.parse_objectinfo,
        'PARTICLE_INFO': nodes_input.parse_particleinfo,
        'TANGENT': nodes_input.parse_tangent,
        'TEX_COORD': nodes_input.parse_texcoord,
        'UVMAP': nodes_input.parse_uvmap,
        'BUMP': nodes_vector.parse_bump,
        'MAPPING': nodes_vector.parse_mapping,
        'NORMAL': nodes_vector.parse_normal,
        'NORMAL_MAP': nodes_vector.parse_normalmap,
        'VECT_TRANSFORM': nodes_vector.parse_vectortransform,
        '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:
        return node_parser_funcs[node.type](node, socket, state)

    elif node.type == 'GROUP':
        return parse_group(node, socket)

    elif node.type == 'GROUP_INPUT':
        return parse_group_input(node, socket)

    elif node.type == 'CUSTOM':
        if node.bl_idname == 'ArmShaderDataNode':
            return node.parse(state.frag, state.vert)

    log.warn(f'Material node type {node.type} not supported')
    return "vec3(0, 0, 0)"
Exemplo n.º 25
0
def build():
    rpdat = arm.utils.get_rp()
    if rpdat.rp_driver != 'Armory' and arm.api.drivers[
            rpdat.rp_driver]['make_rpath'] != None:
        arm.api.drivers[rpdat.rp_driver]['make_rpath']()
        return

    assets_path = arm.utils.get_sdk_path() + '/armory/Assets/'
    wrd = bpy.data.worlds['Arm']

    add_world_defs()

    mobile_mat = rpdat.arm_material_model == 'Mobile' or rpdat.arm_material_model == 'Solid'
    if not mobile_mat:
        # Always include
        assets.add(assets_path + 'brdf.png')
        assets.add_embedded_data('brdf.png')

    if rpdat.rp_hdr:
        assets.add_khafile_def('rp_hdr')

    assets.add_khafile_def('rp_renderer={0}'.format(rpdat.rp_renderer))
    if rpdat.rp_depthprepass:
        assets.add_khafile_def('rp_depthprepass')

    if rpdat.rp_shadowmap != 'Off':
        assets.add_khafile_def('rp_shadowmap')
        assets.add_khafile_def('rp_shadowmap_size={0}'.format(
            rpdat.rp_shadowmap))

    assets.add_khafile_def('rp_background={0}'.format(rpdat.rp_background))
    if rpdat.rp_background == 'World':
        assets.add_shader_pass('world_pass')
        if '_EnvClouds' in wrd.world_defs:
            assets.add(assets_path + 'noise256.png')
            assets.add_embedded_data('noise256.png')

    if rpdat.rp_renderer == 'Deferred' and not rpdat.rp_compositornodes:
        assets.add_shader_pass('copy_pass')
    if rpdat.rp_renderer == 'Forward' and not rpdat.rp_compositornodes and rpdat.rp_render_to_texture:
        assets.add_shader_pass('copy_pass')

    if rpdat.rp_render_to_texture:
        assets.add_khafile_def('rp_render_to_texture')

        if rpdat.rp_compositornodes:
            assets.add_khafile_def('rp_compositornodes')
            compo_depth = False
            if rpdat.arm_tonemap != 'Off':
                wrd.compo_defs = '_CTone' + rpdat.arm_tonemap
            if rpdat.rp_antialiasing == 'FXAA':
                wrd.compo_defs += '_CFXAA'
            if rpdat.arm_letterbox:
                wrd.compo_defs += '_CLetterbox'
            if rpdat.arm_grain:
                wrd.compo_defs += '_CGrain'
            if rpdat.arm_sharpen:
                wrd.compo_defs += '_CSharpen'
            if bpy.data.scenes[0].cycles.film_exposure != 1.0:
                wrd.compo_defs += '_CExposure'
            if rpdat.arm_fog:
                wrd.compo_defs += '_CFog'
                compo_depth = True
            if len(bpy.data.cameras
                   ) > 0 and bpy.data.cameras[0].dof_distance > 0.0:
                wrd.compo_defs += '_CDOF'
                compo_depth = True
            if compo_depth:
                wrd.compo_defs += '_CDepth'
                assets.add_khafile_def('rp_compositordepth')
            if rpdat.arm_lens_texture != '':
                wrd.compo_defs += '_CLensTex'
                assets.add_embedded_data('lenstexture.jpg')
            if rpdat.arm_fisheye:
                wrd.compo_defs += '_CFishEye'
            if rpdat.arm_vignette:
                wrd.compo_defs += '_CVignette'
            if rpdat.arm_lensflare:
                wrd.compo_defs += '_CGlare'
            if rpdat.arm_lut_texture != '':
                wrd.compo_defs += '_CLUT'
                assets.add_embedded_data('luttexture.jpg')
            if '_CDOF' in wrd.compo_defs or '_CFXAA' in wrd.compo_defs or '_CSharpen' in wrd.compo_defs:
                wrd.compo_defs += '_CTexStep'
            if '_CDOF' in wrd.compo_defs or '_CFog' in wrd.compo_defs or '_CGlare' in wrd.compo_defs:
                wrd.compo_defs += '_CCameraProj'
            assets.add_shader_pass('compositor_pass')

        assets.add_khafile_def('rp_antialiasing={0}'.format(
            rpdat.rp_antialiasing))

        if rpdat.rp_antialiasing == 'SMAA' or rpdat.rp_antialiasing == 'TAA':
            assets.add_shader_pass('smaa_edge_detect')
            assets.add_shader_pass('smaa_blend_weight')
            assets.add_shader_pass('smaa_neighborhood_blend')
            assets.add(assets_path + 'smaa_area.png')
            assets.add(assets_path + 'smaa_search.png')
            assets.add_embedded_data('smaa_area.png')
            assets.add_embedded_data('smaa_search.png')
            wrd.world_defs += '_SMAA'
            if rpdat.rp_antialiasing == 'TAA':
                assets.add_shader_pass('taa_pass')
                assets.add_shader_pass('copy_pass')

        if rpdat.rp_antialiasing == 'TAA' or rpdat.rp_motionblur == 'Object':
            assets.add_khafile_def('arm_veloc')
            wrd.world_defs += '_Veloc'
            if rpdat.rp_antialiasing == 'TAA':
                assets.add_khafile_def('arm_taa')

        assets.add_khafile_def('rp_supersampling={0}'.format(
            rpdat.rp_supersampling))
        if rpdat.rp_supersampling == '4':
            assets.add_shader_pass('supersample_resolve')

    if rpdat.rp_overlays:
        assets.add_khafile_def('rp_overlays')

    if rpdat.rp_translucency:
        assets.add_khafile_def('rp_translucency')
        assets.add_shader_pass('translucent_resolve')

    if rpdat.rp_stereo:
        assets.add_khafile_def('rp_stereo')
        assets.add_khafile_def('arm_vr')
        wrd.world_defs += '_VR'
        assets.add(assets_path + 'vr.png')
        assets.add_embedded_data('vr.png')

    rp_gi = rpdat.rp_gi
    has_voxels = arm.utils.voxel_support()
    if not has_voxels:
        rp_gi = 'Off'
    assets.add_khafile_def('rp_gi={0}'.format(rp_gi))
    if rpdat.rp_gi != 'Off':
        if has_voxels:
            assets.add_khafile_def('rp_gi={0}'.format(rpdat.rp_gi))
            assets.add_khafile_def('rp_voxelgi_resolution={0}'.format(
                rpdat.rp_voxelgi_resolution))
            assets.add_khafile_def('rp_voxelgi_resolution_z={0}'.format(
                rpdat.rp_voxelgi_resolution_z))
            if rpdat.rp_voxelgi_hdr:
                assets.add_khafile_def('rp_voxelgi_hdr')
            if rpdat.arm_voxelgi_shadows:
                assets.add_khafile_def('rp_voxelgi_shadows')
            if rpdat.arm_voxelgi_refraction:
                assets.add_khafile_def('rp_voxelgi_refraction')
        else:
            log.warn(
                'Disabling Voxel GI - unsupported target - use Krom instead')

    if rpdat.arm_rp_resolution == 'Custom':
        assets.add_khafile_def('rp_resolution_filter={0}'.format(
            rpdat.arm_rp_resolution_filter))

    assets.add_khafile_def('rp_ssgi={0}'.format(rpdat.rp_ssgi))
    if rpdat.rp_ssgi != 'Off':
        wrd.world_defs += '_SSAO'
        if rpdat.rp_ssgi == 'SSAO':
            assets.add_shader_pass('ssao_pass')
            assets.add_shader_pass('blur_edge_pass')
            assets.add(assets_path + 'noise8.png')
            assets.add_embedded_data('noise8.png')
        else:
            assets.add_shader_pass('ssgi_pass')
            assets.add_shader_pass('ssgi_blur_pass')

    if rpdat.rp_renderer == 'Deferred':
        assets.add_shader_pass('deferred_indirect')
        assets.add_shader_pass('deferred_light')
        assets.add_shader_pass('deferred_light_quad')

    if rpdat.rp_volumetriclight:
        assets.add_khafile_def('rp_volumetriclight')
        assets.add_shader_pass('volumetric_light_quad')
        assets.add_shader_pass('volumetric_light')
        assets.add_shader_pass('blur_bilat_pass')
        assets.add_shader_pass('blur_bilat_blend_pass')
        assets.add(assets_path + 'blue_noise64.png')
        assets.add_embedded_data('blue_noise64.png')

    if rpdat.rp_decals:
        assets.add_khafile_def('rp_decals')

    if rpdat.rp_ocean:
        assets.add_khafile_def('rp_ocean')
        assets.add_shader_pass('water_pass')

    if rpdat.rp_blending:
        assets.add_khafile_def('rp_blending')

    if rpdat.rp_bloom:
        assets.add_khafile_def('rp_bloom')
        assets.add_shader_pass('bloom_pass')
        assets.add_shader_pass('blur_gaus_pass')

    if rpdat.rp_sss:
        assets.add_khafile_def('rp_sss')
        wrd.world_defs += '_SSS'
        assets.add_shader_pass('sss_pass')

    if rpdat.rp_ssr:
        assets.add_khafile_def('rp_ssr')
        assets.add_shader_pass('ssr_pass')
        assets.add_shader_pass('blur_adaptive_pass')
        if rpdat.arm_ssr_half_res:
            assets.add_khafile_def('rp_ssr_half')
        if rpdat.rp_ssr_z_only:
            wrd.world_defs += '_SSRZOnly'

    if rpdat.rp_motionblur != 'Off':
        assets.add_khafile_def('rp_motionblur={0}'.format(rpdat.rp_motionblur))
        assets.add_shader_pass('copy_pass')
        if rpdat.rp_motionblur == 'Camera':
            assets.add_shader_pass('motion_blur_pass')
        else:
            assets.add_shader_pass('motion_blur_veloc_pass')

    if rpdat.rp_compositornodes and rpdat.rp_autoexposure:
        assets.add_khafile_def('rp_autoexposure')

    if rpdat.rp_dynres:
        assets.add_khafile_def('rp_dynres')

    if rpdat.arm_soft_shadows == 'On':
        if rpdat.rp_shadowmap_cascades == '1':
            assets.add_shader_pass('dilate_pass')
            assets.add_shader_pass('visibility_pass')
            assets.add_shader_pass('blur_shadow_pass')
            assets.add_khafile_def('rp_soft_shadows')
            wrd.world_defs += '_SoftShadows'
            if rpdat.arm_soft_shadows_penumbra != 1:
                wrd.world_defs += '_PenumbraScale'
        else:
            log.warn(
                'Disabling soft shadows - "Armory Render Path - Cascades" requires to be set to 1 for now'
            )

    gbuffer2_direct = '_SSS' in wrd.world_defs or '_Hair' in wrd.world_defs or rpdat.arm_voxelgi_refraction
    gbuffer2 = '_Veloc' in wrd.world_defs or gbuffer2_direct
    if gbuffer2:
        assets.add_khafile_def('rp_gbuffer2')
        wrd.world_defs += '_gbuffer2'
        if gbuffer2_direct:
            assets.add_khafile_def('rp_gbuffer2_direct')
            wrd.world_defs += '_gbuffer2direct'

    if callback != None:
        callback()
Exemplo n.º 26
0
def make(image_node, tex_name, matname=None):
    wrd = bpy.data.worlds['Arm']
    tex = {}
    tex['name'] = tex_name
    image = image_node.image
    if matname == None:
        matname = mat_state.material.name

    if image == None:
        return None

    if image.filepath == '':
        log.warn(matname + '/' + image.name + ' - file path not found')
        return None

    # Reference image name
    texpath = arm.utils.asset_path(image.filepath)
    texfile = arm.utils.extract_filename(image.filepath)
    tex['file'] = arm.utils.safestr(texfile)
    s = tex['file'].rsplit('.', 1)
    
    if len(s) == 1:
        log.warn(matname + '/' + image.name + ' - file extension required for image name')
        return None

    ext = s[1].lower()
    do_convert = ext != 'jpg' and ext != 'png' and ext != 'hdr' and ext != 'mp4' # Convert image
    if do_convert:
        tex['file'] = tex['file'].rsplit('.', 1)[0] + '.jpg'
        # log.warn(matname + '/' + image.name + ' - image format is not (jpg/png/hdr), converting to jpg.')

    if image.packed_file != None or not is_ascii(texfile):
        # Extract packed data / copy non-ascii texture
        unpack_path = arm.utils.get_fp_build() + '/compiled/Assets/unpacked'
        if not os.path.exists(unpack_path):
            os.makedirs(unpack_path)
        unpack_filepath = unpack_path + '/' + tex['file']
        
        if do_convert:
            if not os.path.isfile(unpack_filepath):
                arm.utils.write_image(image, unpack_filepath)
        else:

            # Write bytes if size is different or file does not exist yet
            if image.packed_file != None:
                if not os.path.isfile(unpack_filepath) or os.path.getsize(unpack_filepath) != image.packed_file.size:
                    with open(unpack_filepath, 'wb') as f:
                        f.write(image.packed_file.data)
            # Copy non-ascii texture
            else:
                if not os.path.isfile(unpack_filepath) or os.path.getsize(unpack_filepath) != os.path.getsize(texpath):
                    shutil.copy(texpath, unpack_filepath)

        assets.add(unpack_filepath)

    else:
        if not os.path.isfile(arm.utils.asset_path(image.filepath)):
            log.warn('Material ' + matname + '/' + image.name + ' - file not found(' + image.filepath + ')')
            return None

        if do_convert:
            converted_path = arm.utils.get_fp_build() + '/compiled/Assets/unpacked/' + tex['file']
            # TODO: delete cache when file changes
            if not os.path.isfile(converted_path):
                arm.utils.write_image(image, converted_path)
            assets.add(converted_path)
        else:
            # Link image path to assets
            # TODO: Khamake converts .PNG to .jpg? Convert ext to lowercase on windows
            if arm.utils.get_os() == 'win':
                s = image.filepath.rsplit('.', 1)
                assets.add(arm.utils.asset_path(s[0] + '.' + s[1].lower()))
            else:
                assets.add(arm.utils.asset_path(image.filepath))


    # if image_format != 'RGBA32':
        # tex['format'] = image_format
    
    interpolation = image_node.interpolation
    texfilter = wrd.texture_filtering_state
    if texfilter == 'Anisotropic':
        interpolation = 'Smart'
    elif texfilter == 'Linear':
        interpolation = 'Linear'
    elif texfilter == 'Point':
        interpolation = 'Closest'
    
    # TODO: Blender seems to load full images on size request, cache size instead
    powimage = is_pow(image.size[0]) and is_pow(image.size[1])

    if state.target == 'html5' and powimage == False and (image_node.interpolation == 'Cubic' or image_node.interpolation == 'Smart'):
        log.warn(matname + '/' + image.name + ' - non power of 2 texture using ' + image_node.interpolation + ' interpolation requires WebGL2')

    if interpolation == 'Cubic': # Mipmap linear
        tex['mipmap_filter'] = 'linear'
        tex['generate_mipmaps'] = True
    elif interpolation == 'Smart': # Mipmap anisotropic
        tex['min_filter'] = 'anisotropic'
        tex['mipmap_filter'] = 'linear'
        tex['generate_mipmaps'] = True
    elif interpolation == 'Closest':
        tex['min_filter'] = 'point'
        tex['mag_filter'] = 'point'
    # else defaults to linear

    if image_node.extension != 'REPEAT': # Extend or clip
        tex['u_addressing'] = 'clamp'
        tex['v_addressing'] = 'clamp'
    else:
        if state.target == 'html5' and powimage == False:
            log.warn(matname + '/' + image.name + ' - non power of 2 texture using repeat mode requires WebGL2')
            # tex['u_addressing'] = 'clamp'
            # tex['v_addressing'] = 'clamp'
    
    if image.source == 'MOVIE': # Just append movie texture trait for now
        movie_trait = {}
        movie_trait['type'] = 'Script'
        movie_trait['class_name'] = 'armory.trait.internal.MovieTexture'
        movie_trait['parameters'] = [tex['file']]
        for o in mat_state.mat_armusers[mat_state.material]:
            o['traits'].append(movie_trait)
        tex['source'] = 'movie'
        tex['file'] = '' # MovieTexture will load the video

    return tex
Exemplo n.º 27
0
def parse_tex_environment(node: bpy.types.ShaderNodeTexEnvironment, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str:
    if state.context == ParserContext.OBJECT:
        log.warn('Environment Texture node is not supported for object node trees, using default value')
        return c.to_vec3([0.0, 0.0, 0.0])

    if node.image is None:
        return c.to_vec3([1.0, 0.0, 1.0])

    world = state.world
    world.world_defs += '_EnvTex'

    curshader = state.curshader

    curshader.add_include('std/math.glsl')
    curshader.add_uniform('sampler2D envmap', link='_envmap')

    image = node.image
    filepath = image.filepath

    if image.packed_file is None and not os.path.isfile(arm.utils.asset_path(filepath)):
        log.warn(world.name + ' - unable to open ' + image.filepath)
        return c.to_vec3([1.0, 0.0, 1.0])

    # Reference image name
    tex_file = arm.utils.extract_filename(image.filepath)
    base = tex_file.rsplit('.', 1)
    ext = base[1].lower()

    if ext == 'hdr':
        target_format = 'HDR'
    else:
        target_format = 'JPEG'
    do_convert = ext != 'hdr' and ext != 'jpg'
    if do_convert:
        if ext == 'exr':
            tex_file = base[0] + '.hdr'
            target_format = 'HDR'
        else:
            tex_file = base[0] + '.jpg'
            target_format = 'JPEG'

    if image.packed_file is not None:
        # Extract packed data
        unpack_path = arm.utils.get_fp_build() + '/compiled/Assets/unpacked'
        if not os.path.exists(unpack_path):
            os.makedirs(unpack_path)
        unpack_filepath = unpack_path + '/' + tex_file
        filepath = unpack_filepath

        if do_convert:
            if not os.path.isfile(unpack_filepath):
                arm.utils.unpack_image(image, unpack_filepath, file_format=target_format)

        elif not os.path.isfile(unpack_filepath) or os.path.getsize(unpack_filepath) != image.packed_file.size:
            with open(unpack_filepath, 'wb') as f:
                f.write(image.packed_file.data)

        assets.add(unpack_filepath)
    else:
        if do_convert:
            unpack_path = arm.utils.get_fp_build() + '/compiled/Assets/unpacked'
            if not os.path.exists(unpack_path):
                os.makedirs(unpack_path)
            converted_path = unpack_path + '/' + tex_file
            filepath = converted_path
            # TODO: delete cache when file changes
            if not os.path.isfile(converted_path):
                arm.utils.convert_image(image, converted_path, file_format=target_format)
            assets.add(converted_path)
        else:
            # Link image path to assets
            assets.add(arm.utils.asset_path(image.filepath))

    rpdat = arm.utils.get_rp()

    if not state.radiance_written:
        # Generate prefiltered envmaps
        world.arm_envtex_name = tex_file
        world.arm_envtex_irr_name = tex_file.rsplit('.', 1)[0]
        disable_hdr = target_format == 'JPEG'
        from_srgb = image.colorspace_settings.name == "sRGB"

        mip_count = world.arm_envtex_num_mips
        mip_count = write_probes.write_probes(filepath, disable_hdr, from_srgb, mip_count, arm_radiance=rpdat.arm_radiance)

        world.arm_envtex_num_mips = mip_count

        state.radiance_written = True

        # Append LDR define
        if disable_hdr:
            world.world_defs += '_EnvLDR'

    wrd = bpy.data.worlds['Arm']
    mobile_mat = rpdat.arm_material_model == 'Mobile' or rpdat.arm_material_model == 'Solid'

    # Append radiance define
    if rpdat.arm_irradiance and rpdat.arm_radiance and not mobile_mat:
        wrd.world_defs += '_Rad'

    return 'texture(envmap, envMapEquirect(pos)).rgb * envmapStrength'
Exemplo n.º 28
0
def parse(material: Material, mat_data,
          mat_users: Dict[Material, List[Object]], mat_armusers) -> tuple:
    wrd = bpy.data.worlds['Arm']
    rpdat = arm.utils.get_rp()

    needs_sss = material_needs_sss(material)
    if needs_sss and rpdat.rp_sss_state != 'Off' and '_SSS' not in wrd.world_defs:
        # Must be set before calling make_shader.build()
        wrd.world_defs += '_SSS'

    # No batch - shader data per material
    if material.arm_custom_material != '':
        rpasses = ['mesh']

        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 = {'mesh': []}
        bind_textures = {'mesh': []}

        make_shader.make_instancing_and_skinning(material, mat_users)

        for idx, item in enumerate(material.arm_bind_textures_list):
            if item.uniform_name == '':
                log.warn(
                    f'Material "{material.name}": skipping export of bind texture at slot {idx + 1} with empty uniform name'
                )
                continue

            if item.image is not None:
                tex = cycles.make_texture(item.image, item.uniform_name,
                                          material.name, 'Linear', 'REPEAT')
                if tex is None:
                    continue
                bind_textures['mesh'].append(tex)
            else:
                log.warn(
                    f'Material "{material.name}": skipping export of bind texture at slot {idx + 1} ("{item.uniform_name}") with no image selected'
                )

    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
    else:
        rpasses, shader_data, shader_data_name, bind_constants, bind_textures = mat_batch.get(
            material)
        sd = shader_data.sd

    sss_used = False

    # Material
    for rp in rpasses:
        c = {
            'name': rp,
            'bind_constants': [] + bind_constants[rp],
            'bind_textures': [] + bind_textures[rp],
            'depth_read': material.arm_depth_read,
        }
        mat_data['contexts'].append(c)

        if rp == 'mesh':
            c['bind_constants'].append({
                'name': 'receiveShadow',
                'bool': material.arm_receive_shadow
            })

            if material.arm_material_id != 0:
                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 != 'Off':
                const = {'name': 'materialID'}
                if needs_sss:
                    const['int'] = 2
                    sss_used = True
                else:
                    const['int'] = 0
                c['bind_constants'].append(const)

            # TODO: Mesh only material batching
            if wrd.arm_batch_materials:
                # Set textures uniforms
                if len(c['bind_textures']) > 0:
                    c['bind_textures'] = []
                    for node in material.node_tree.nodes:
                        if node.type == 'TEX_IMAGE':
                            tex_name = arm.utils.safesrc(node.name)
                            tex = cycles.make_texture_from_image_node(
                                node, tex_name)
                            # 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 module
                            c['bind_constants'].append({
                                'name':
                                uname,
                                cycles.glsl_type(inp.type):
                                glsl_value(inp.default_value)
                            })

        elif rp == 'translucent':
            c['bind_constants'].append({
                'name': 'receiveShadow',
                'bool': material.arm_receive_shadow
            })

    if wrd.arm_single_data_file:
        mat_data['shader'] = shader_data_name
    else:
        # 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, sss_used
Exemplo n.º 29
0
def parse_color(world, node, context, envmap_strength_const):
    wrd = bpy.data.worlds['Arm']

    # Env map included
    if node.type == 'TEX_ENVIRONMENT' and node.image != None:

        image = node.image
        filepath = image.filepath

        if image.packed_file == None and not os.path.isfile(
                arm.utils.asset_path(filepath)):
            log.warn(world.name + ' - unable to open ' + image.filepath)
            return

        tex = {}
        context['bind_textures'].append(tex)
        tex['name'] = 'envmap'
        tex['u_addressing'] = 'clamp'
        tex['v_addressing'] = 'clamp'

        # Reference image name
        tex['file'] = arm.utils.extract_filename(image.filepath)
        base = tex['file'].rsplit('.', 1)
        ext = base[1].lower()

        if ext == 'hdr':
            target_format = 'HDR'
        else:
            target_format = 'JPEG'
        do_convert = ext != 'hdr' and ext != 'jpg'
        if do_convert:
            if ext == 'exr':
                tex['file'] = base[0] + '.hdr'
                target_format = 'HDR'
            else:
                tex['file'] = base[0] + '.jpg'
                target_format = 'JPEG'

        if image.packed_file != None:
            # Extract packed data
            unpack_path = arm.utils.get_fp_build(
            ) + '/compiled/Assets/unpacked'
            if not os.path.exists(unpack_path):
                os.makedirs(unpack_path)
            unpack_filepath = unpack_path + '/' + tex['file']
            filepath = unpack_filepath

            if do_convert:
                if not os.path.isfile(unpack_filepath):
                    arm.utils.write_image(image,
                                          unpack_filepath,
                                          file_format=target_format)

            elif os.path.isfile(unpack_filepath) == False or os.path.getsize(
                    unpack_filepath) != image.packed_file.size:
                with open(unpack_filepath, 'wb') as f:
                    f.write(image.packed_file.data)

            assets.add(unpack_filepath)
        else:
            if do_convert:
                converted_path = arm.utils.get_fp_build(
                ) + '/compiled/Assets/unpacked/' + tex['file']
                filepath = converted_path
                # TODO: delete cache when file changes
                if not os.path.isfile(converted_path):
                    arm.utils.write_image(image,
                                          converted_path,
                                          file_format=target_format)
                assets.add(converted_path)
            else:
                # Link image path to assets
                assets.add(arm.utils.asset_path(image.filepath))

        # Generate prefiltered envmaps
        world.world_envtex_name = tex['file']
        world.world_envtex_irr_name = tex['file'].rsplit('.', 1)[0]
        disable_hdr = target_format == 'JPEG'

        mip_count = world.world_envtex_num_mips
        mip_count = write_probes.write_probes(
            filepath,
            disable_hdr,
            mip_count,
            generate_radiance=wrd.generate_radiance)

        world.world_envtex_num_mips = mip_count

        # Append envtex define
        bpy.data.worlds['Arm'].world_defs += '_EnvTex'
        # Append LDR define
        if disable_hdr:
            bpy.data.worlds['Arm'].world_defs += '_EnvLDR'
        # Append radiance define
        if wrd.generate_irradiance and wrd.generate_radiance:
            bpy.data.worlds['Arm'].world_defs += '_Rad'

    # Static image background
    elif node.type == 'TEX_IMAGE':
        bpy.data.worlds['Arm'].world_defs += '_EnvImg'
        tex = {}
        context['bind_textures'].append(tex)
        tex['name'] = 'envmap'
        # No repeat for now
        tex['u_addressing'] = 'clamp'
        tex['v_addressing'] = 'clamp'

        image = node.image
        filepath = image.filepath

        if image.packed_file != None:
            # Extract packed data
            filepath = arm.utils.build_dir() + '/compiled/Assets/unpacked'
            unpack_path = arm.utils.get_fp() + filepath
            if not os.path.exists(unpack_path):
                os.makedirs(unpack_path)
            unpack_filepath = unpack_path + '/' + image.name
            if os.path.isfile(unpack_filepath) == False or os.path.getsize(
                    unpack_filepath) != image.packed_file.size:
                with open(unpack_filepath, 'wb') as f:
                    f.write(image.packed_file.data)
            assets.add(unpack_filepath)
        else:
            # Link image path to assets
            assets.add(arm.utils.asset_path(image.filepath))

        # Reference image name
        tex['file'] = arm.utils.extract_filename(image.filepath)

    # Append sky define
    elif node.type == 'TEX_SKY':
        # Match to cycles
        envmap_strength_const['float'] *= 0.1

        bpy.data.worlds['Arm'].world_defs += '_EnvSky'
        # Append sky properties to material
        const = {}
        const['name'] = 'sunDirection'
        sun_direction = [
            node.sun_direction[0], node.sun_direction[1], node.sun_direction[2]
        ]
        sun_direction[1] *= -1  # Fix Y orientation
        const['vec3'] = list(sun_direction)
        context['bind_constants'].append(const)

        world.world_envtex_sun_direction = sun_direction
        world.world_envtex_turbidity = node.turbidity
        world.world_envtex_ground_albedo = node.ground_albedo

        # Irradiance json file name
        wname = arm.utils.safestr(world.name)
        world.world_envtex_irr_name = wname
        write_probes.write_sky_irradiance(wname)

        # Radiance
        if wrd.generate_radiance_sky and wrd.generate_radiance and wrd.generate_irradiance:
            bpy.data.worlds['Arm'].world_defs += '_Rad'

            if wrd.generate_radiance_sky_type == 'Hosek':
                hosek_path = 'armory/Assets/hosek/'
            else:
                hosek_path = 'armory/Assets/hosek_fake/'

            sdk_path = arm.utils.get_sdk_path()
            # Use fake maps for now
            assets.add(sdk_path + hosek_path + 'hosek_radiance.hdr')
            for i in range(0, 8):
                assets.add(sdk_path + hosek_path + 'hosek_radiance_' + str(i) +
                           '.hdr')

            world.world_envtex_name = 'hosek'
            world.world_envtex_num_mips = 8
Exemplo n.º 30
0
def add_world_defs():
    wrd = bpy.data.worlds['Arm']
    rpdat = arm.utils.get_rp()

    # Screen-space ray-traced shadows
    if rpdat.arm_ssrs:
        wrd.world_defs += '_SSRS'

    if rpdat.arm_two_sided_area_light:
        wrd.world_defs += '_TwoSidedAreaLight'

    # Store contexts
    if rpdat.rp_hdr == False:
        wrd.world_defs += '_LDR'

    if wrd.arm_light_ies_texture != '':
        wrd.world_defs += '_LightIES'
        assets.add_embedded_data('iestexture.png')

    if wrd.arm_light_clouds_texture != '':
        wrd.world_defs += '_LightClouds'
        assets.add_embedded_data('cloudstexture.png')

    if rpdat.rp_renderer == 'Deferred':
        assets.add_khafile_def('arm_deferred')
        wrd.world_defs += '_Deferred'

    # GI
    voxelgi = False
    voxelao = False
    has_voxels = arm.utils.voxel_support()
    if has_voxels and rpdat.arm_material_model == 'Full':
        if rpdat.rp_gi == 'Voxel GI':
            voxelgi = True
        elif rpdat.rp_gi == 'Voxel AO':
            voxelao = True
    # Shadows
    if rpdat.rp_shadows:
        wrd.world_defs += '_ShadowMap'
        if rpdat.rp_shadowmap_cascades != '1':
            if voxelgi:
                log.warn('Disabling shadow cascades - Voxel GI does not support cascades yet')
            else:
                wrd.world_defs += '_CSM'
                assets.add_khafile_def('arm_csm')
    # SS
    if rpdat.rp_ssgi == 'RTGI' or rpdat.rp_ssgi == 'RTAO':
        if rpdat.rp_ssgi == 'RTGI':
            wrd.world_defs += '_RTGI'
        if rpdat.arm_ssgi_rays == '9':
            wrd.world_defs += '_SSGICone9'
    if rpdat.rp_autoexposure:
        wrd.world_defs += '_AutoExposure'

    if voxelgi or voxelao:
        assets.add_khafile_def('arm_voxelgi')
        wrd.world_defs += '_VoxelCones' + rpdat.arm_voxelgi_cones
        if rpdat.arm_voxelgi_revoxelize:
            assets.add_khafile_def('arm_voxelgi_revox')
            if rpdat.arm_voxelgi_camera:
                wrd.world_defs += '_VoxelGICam'
            if rpdat.arm_voxelgi_temporal:
                assets.add_khafile_def('arm_voxelgi_temporal')
                wrd.world_defs += '_VoxelGITemporal'

        if voxelgi:
            wrd.world_defs += '_VoxelGI'
            # assets.add_shader_external(arm.utils.get_sdk_path() + '/armory/Shaders/voxel_light/voxel_light.comp.glsl')
            # if rpdat.arm_voxelgi_bounces != "1":
            #     assets.add_khafile_def('rp_gi_bounces={0}'.format(rpdat.arm_voxelgi_bounces))
            #     assets.add_shader_external(arm.utils.get_sdk_path() + '/armory/Shaders/voxel_bounce/voxel_bounce.comp.glsl')
            # if rpdat.arm_voxelgi_shadows:
                # wrd.world_defs += '_VoxelGIShadow'
            if rpdat.rp_voxelgi_relight:
                assets.add_khafile_def('rp_voxelgi_relight')
        elif voxelao:
            wrd.world_defs += '_VoxelAOvar' # Write a shader variant

    if arm.utils.get_legacy_shaders() and not state.is_viewport:
        wrd.world_defs += '_Legacy'

    # Light defines
    point_lights = 0
    for bo in bpy.data.objects: # TODO: temp
        if bo.type == 'LIGHT':
            light = bo.data
            if light.type == 'AREA' and '_LTC' not in wrd.world_defs:
                wrd.world_defs += '_LTC'
                assets.add_khafile_def('arm_ltc')
            if light.type == 'SUN' and '_Sun' not in wrd.world_defs:
                wrd.world_defs += '_Sun'
            if light.type == 'POINT' or light.type == 'SPOT':
                point_lights += 1
                if light.type == 'SPOT' and '_Spot' not in wrd.world_defs:
                    wrd.world_defs += '_Spot'
                    assets.add_khafile_def('arm_spot')

    if point_lights == 1:
        wrd.world_defs += '_SinglePoint'
    elif point_lights > 1:
        wrd.world_defs += '_Clusters'
        assets.add_khafile_def('arm_clusters')

    if '_Rad' in wrd.world_defs or '_VoxelGI' in wrd.world_defs:
        wrd.world_defs += '_Brdf'