示例#1
0
def parse_mixshader(node: bpy.types.ShaderNodeMixShader,
                    out_socket: NodeSocket, state: ParserState) -> None:
    prefix = '' if node.inputs[0].is_linked else 'const '
    fac = c.parse_value_input(node.inputs[0])
    fac_var = c.node_name(node.name) + '_fac'
    fac_inv_var = c.node_name(node.name) + '_fac_inv'
    state.curshader.write('{0}float {1} = {2};'.format(prefix, fac_var, fac))
    state.curshader.write('{0}float {1} = 1.0 - {2};'.format(
        prefix, fac_inv_var, fac_var))
    bc1, rough1, met1, occ1, spec1, opac1, emi1 = c.parse_shader_input(
        node.inputs[1])
    bc2, rough2, met2, occ2, spec2, opac2, emi2 = c.parse_shader_input(
        node.inputs[2])
    if state.parse_surface:
        state.out_basecol = '({0} * {3} + {1} * {2})'.format(
            bc1, bc2, fac_var, fac_inv_var)
        state.out_roughness = '({0} * {3} + {1} * {2})'.format(
            rough1, rough2, fac_var, fac_inv_var)
        state.out_metallic = '({0} * {3} + {1} * {2})'.format(
            met1, met2, fac_var, fac_inv_var)
        state.out_occlusion = '({0} * {3} + {1} * {2})'.format(
            occ1, occ2, fac_var, fac_inv_var)
        state.out_specular = '({0} * {3} + {1} * {2})'.format(
            spec1, spec2, fac_var, fac_inv_var)
        state.out_emission = '({0} * {3} + {1} * {2})'.format(
            emi1, emi2, fac_var, fac_inv_var)
    if state.parse_opacity:
        state.out_opacity = '({0} * {3} + {1} * {2})'.format(
            opac1, opac2, fac_var, fac_inv_var)
示例#2
0
def parse_rgb(node: bpy.types.ShaderNodeRGB, out_socket: bpy.types.NodeSocket,
              state: ParserState) -> vec3str:
    if node.arm_material_param:
        nn = 'param_' + c.node_name(node.name)
        state.curshader.add_uniform(f'vec3 {nn}', link=f'{node.name}')
        return nn
    else:
        return c.to_vec3(out_socket.default_value)
示例#3
0
def parse_mapping(node: bpy.types.ShaderNodeMapping, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str:
    # Only "Point", "Texture" and "Vector" types supported for now..
    # More information about the order of operations for this node:
    # https://docs.blender.org/manual/en/latest/render/shader_nodes/vector/mapping.html#properties

    input_vector: bpy.types.NodeSocket = node.inputs[0]
    input_location: bpy.types.NodeSocket = node.inputs['Location']
    input_rotation: bpy.types.NodeSocket = node.inputs['Rotation']
    input_scale: bpy.types.NodeSocket = node.inputs['Scale']
    out = c.parse_vector_input(input_vector) if input_vector.is_linked else c.to_vec3(input_vector.default_value)
    location = c.parse_vector_input(input_location) if input_location.is_linked else c.to_vec3(input_location.default_value)
    rotation = c.parse_vector_input(input_rotation) if input_rotation.is_linked else c.to_vec3(input_rotation.default_value)
    scale = c.parse_vector_input(input_scale) if input_scale.is_linked else c.to_vec3(input_scale.default_value)

    # Use inner functions because the order of operations varies between
    # mapping node vector types. This adds a slight overhead but makes
    # the code much more readable.
    # - "Point" and "Vector" use Scale -> Rotate -> Translate
    # - "Texture" uses Translate -> Rotate -> Scale
    def calc_location(output: str) -> str:
        # Vectors and Eulers support the "!=" operator
        if input_scale.is_linked or input_scale.default_value != Vector((1, 1, 1)):
            if node.vector_type == 'TEXTURE':
                output = f'({output} / {scale})'
            else:
                output = f'({output} * {scale})'

        return output

    def calc_scale(output: str) -> str:
        if input_location.is_linked or input_location.default_value != Vector((0, 0, 0)):
            # z location is a little off sometimes?...
            if node.vector_type == 'TEXTURE':
                output = f'({output} - {location})'
            else:
                output = f'({output} + {location})'
        return output

    out = calc_location(out) if node.vector_type == 'TEXTURE' else calc_scale(out)

    if input_rotation.is_linked or input_rotation.default_value != Euler((0, 0, 0)):
        var_name = c.node_name(node.name) + "_rotation"
        if node.vector_type == 'TEXTURE':
            state.curshader.write(f'mat3 {var_name}X = mat3(1.0, 0.0, 0.0, 0.0, cos({rotation}.x), sin({rotation}.x), 0.0, -sin({rotation}.x), cos({rotation}.x));')
            state.curshader.write(f'mat3 {var_name}Y = mat3(cos({rotation}.y), 0.0, -sin({rotation}.y), 0.0, 1.0, 0.0, sin({rotation}.y), 0.0, cos({rotation}.y));')
            state.curshader.write(f'mat3 {var_name}Z = mat3(cos({rotation}.z), sin({rotation}.z), 0.0, -sin({rotation}.z), cos({rotation}.z), 0.0, 0.0, 0.0, 1.0);')
        else:
            # A little bit redundant, but faster than 12 more multiplications to make it work dynamically
            state.curshader.write(f'mat3 {var_name}X = mat3(1.0, 0.0, 0.0, 0.0, cos(-{rotation}.x), sin(-{rotation}.x), 0.0, -sin(-{rotation}.x), cos(-{rotation}.x));')
            state.curshader.write(f'mat3 {var_name}Y = mat3(cos(-{rotation}.y), 0.0, -sin(-{rotation}.y), 0.0, 1.0, 0.0, sin(-{rotation}.y), 0.0, cos(-{rotation}.y));')
            state.curshader.write(f'mat3 {var_name}Z = mat3(cos(-{rotation}.z), sin(-{rotation}.z), 0.0, -sin(-{rotation}.z), cos(-{rotation}.z), 0.0, 0.0, 0.0, 1.0);')

        # XYZ-order euler rotation
        out = f'{out} * {var_name}X * {var_name}Y * {var_name}Z'

    out = calc_scale(out) if node.vector_type == 'TEXTURE' else calc_location(out)

    return out
示例#4
0
def parse_value(node: bpy.types.ShaderNodeValue,
                out_socket: bpy.types.NodeSocket,
                state: ParserState) -> floatstr:
    if node.arm_material_param:
        nn = 'param_' + c.node_name(node.name)
        state.curshader.add_uniform('float {0}'.format(nn),
                                    link='{0}'.format(node.name))
        return nn
    else:
        return c.to_vec1(node.outputs[0].default_value)
示例#5
0
def parse_curvevec(node: bpy.types.ShaderNodeVectorCurve, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str:
    fac = c.parse_value_input(node.inputs[0])
    vec = c.parse_vector_input(node.inputs[1])
    curves = node.mapping.curves
    name = c.node_name(node.name)
    # mapping.curves[0].points[0].handle_type # bezier curve
    return '(vec3({0}, {1}, {2}) * {3})'.format(
        c.vector_curve(name + '0', vec + '.x', curves[0].points),
        c.vector_curve(name + '1', vec + '.y', curves[1].points),
        c.vector_curve(name + '2', vec + '.z', curves[2].points), fac)
示例#6
0
def parse_curvergb(node: bpy.types.ShaderNodeRGBCurve,
                   out_socket: bpy.types.NodeSocket,
                   state: ParserState) -> vec3str:
    fac = c.parse_value_input(node.inputs[0])
    vec = c.parse_vector_input(node.inputs[1])
    curves = node.mapping.curves
    name = c.node_name(node.name)
    # mapping.curves[0].points[0].handle_type
    return '(sqrt(vec3({0}, {1}, {2}) * vec3({4}, {5}, {6})) * {3})'.format(
        c.vector_curve(name + '0', vec + '.x', curves[0].points),
        c.vector_curve(name + '1', vec + '.y', curves[1].points),
        c.vector_curve(name + '2', vec + '.z', curves[2].points), fac,
        c.vector_curve(name + '3a', vec + '.x', curves[3].points),
        c.vector_curve(name + '3b', vec + '.y', curves[3].points),
        c.vector_curve(name + '3c', vec + '.z', curves[3].points))
示例#7
0
def parse_sephsv(node: bpy.types.ShaderNodeSeparateHSV,
                 out_socket: bpy.types.NodeSocket,
                 state: ParserState) -> floatstr:
    state.curshader.add_function(c_functions.str_hue_sat)

    hsv_var = c.node_name(node.name) + '_hsv'
    state.curshader.write(
        f'const vec3 {hsv_var} = rgb_to_hsv({c.parse_vector_input(node.inputs["Color"])}.rgb);'
    )

    if out_socket == node.outputs[0]:
        return f'{hsv_var}.x'
    elif out_socket == node.outputs[1]:
        return f'{hsv_var}.y'
    elif out_socket == node.outputs[2]:
        return f'{hsv_var}.z'
示例#8
0
def parse_rgb(node: bpy.types.ShaderNodeRGB, out_socket: bpy.types.NodeSocket,
              state: ParserState) -> vec3str:
    if node.arm_material_param:
        nn = 'param_' + c.node_name(node.name)
        v = out_socket.default_value
        value = []
        value.append(float(v[0]))
        value.append(float(v[1]))
        value.append(float(v[2]))
        is_arm_mat_param = True
        state.curshader.add_uniform(f'vec3 {nn}',
                                    link=f'{node.name}',
                                    default_value=value,
                                    is_arm_mat_param=is_arm_mat_param)
        return nn
    else:
        return c.to_vec3(out_socket.default_value)
示例#9
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
示例#10
0
def parse_tex_image(node: bpy.types.ShaderNodeTexImage, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]:
    if state.context == ParserContext.OBJECT:
        # Color or Alpha output
        use_color_out = out_socket == node.outputs[0]

        # Already fetched
        if c.is_parsed(c.store_var_name(node)):
            if use_color_out:
                return f'{c.store_var_name(node)}.rgb'
            else:
                return f'{c.store_var_name(node)}.a'

        tex_name = c.node_name(node.name)
        tex = c.make_texture(node, tex_name)
        tex_link = None
        tex_default_file = None
        is_arm_mat_param = None
        if node.arm_material_param:
            tex_link = node.name
            is_arm_mat_param = True

        if tex is not None:
            state.curshader.write_textures += 1
            if node.arm_material_param and tex['file'] is not None:
                tex_default_file = tex['file']
            if use_color_out:
                to_linear = node.image is not None and node.image.colorspace_settings.name == 'sRGB'
                res = f'{c.texture_store(node, tex, tex_name, to_linear, tex_link=tex_link, default_value=tex_default_file, is_arm_mat_param=is_arm_mat_param)}.rgb'
            else:
                res = f'{c.texture_store(node, tex, tex_name, tex_link=tex_link, default_value=tex_default_file, is_arm_mat_param=is_arm_mat_param)}.a'
            state.curshader.write_textures -= 1
            return res

        # Empty texture
        elif node.image is None:
            tex = {
                'name': tex_name,
                'file': ''
            }
            if use_color_out:
                return '{0}.rgb'.format(c.texture_store(node, tex, tex_name, to_linear=False, tex_link=tex_link, is_arm_mat_param=is_arm_mat_param))
            return '{0}.a'.format(c.texture_store(node, tex, tex_name, to_linear=True, tex_link=tex_link, is_arm_mat_param=is_arm_mat_param))

        # Pink color for missing texture
        else:
            tex_store = c.store_var_name(node)

            if use_color_out:
                state.parsed.add(tex_store)
                state.curshader.write_textures += 1
                state.curshader.write(f'vec4 {tex_store} = vec4(1.0, 0.0, 1.0, 1.0);')
                state.curshader.write_textures -= 1
                return f'{tex_store}.rgb'
            else:
                state.curshader.write(f'vec4 {tex_store} = vec4(1.0, 0.0, 1.0, 1.0);')
                return f'{tex_store}.a'

    # World context
    # TODO: Merge with above implementation to also allow mappings other than using view coordinates
    else:
        world = state.world
        world.world_defs += '_EnvImg'

        # Background texture
        state.curshader.add_uniform('sampler2D envmap', link='_envmap')
        state.curshader.add_uniform('vec2 screenSize', link='_screenSize')

        image = node.image
        filepath = image.filepath

        if image.packed_file is not 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 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:
            # Link image path to assets
            assets.add(arm.utils.asset_path(image.filepath))

        # 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'

        # 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"

        rpdat = arm.utils.get_rp()
        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

        # Will have to get rid of gl_FragCoord, pass texture coords from vertex shader
        state.curshader.write_init('vec2 texco = gl_FragCoord.xy / screenSize;')
        return 'texture(envmap, vec2(texco.x, 1.0 - texco.y)).rgb * envmapStrength'
示例#11
0
def parse_valtorgb(node: bpy.types.ShaderNodeValToRGB,
                   out_socket: bpy.types.NodeSocket,
                   state: ParserState) -> Union[floatstr, vec3str]:
    # Alpha (TODO: make ColorRamp calculation vec4-based and split afterwards)
    if out_socket == node.outputs[1]:
        return '1.0'

    input_fac: bpy.types.NodeSocket = node.inputs[0]

    fac: str = c.parse_value_input(
        input_fac) if input_fac.is_linked else c.to_vec1(
            input_fac.default_value)
    interp = node.color_ramp.interpolation
    elems = node.color_ramp.elements

    if len(elems) == 1:
        return c.to_vec3(elems[0].color)

    # Write color array
    # The last entry is included twice so that the interpolation
    # between indices works (no out of bounds error)
    cols_var = c.node_name(node.name).upper() + '_COLS'
    cols_entries = ', '.join(
        f'vec3({elem.color[0]}, {elem.color[1]}, {elem.color[2]})'
        for elem in elems)
    cols_entries += f', vec3({elems[len(elems) - 1].color[0]}, {elems[len(elems) - 1].color[1]}, {elems[len(elems) - 1].color[2]})'
    state.curshader.add_const("vec3",
                              cols_var,
                              cols_entries,
                              array_size=len(elems) + 1)

    fac_var = c.node_name(node.name) + '_fac'
    state.curshader.write(f'float {fac_var} = {fac};')

    # Get index of the nearest left element relative to the factor
    index = '0 + '
    index += ' + '.join([
        f'(({fac_var} > {elems[i].position}) ? 1 : 0)'
        for i in range(1, len(elems))
    ])

    # Write index
    index_var = c.node_name(node.name) + '_i'
    state.curshader.write(f'int {index_var} = {index};')

    if interp == 'CONSTANT':
        return f'{cols_var}[{index_var}]'

    # Linear interpolation
    else:
        # Write factor array
        facs_var = c.node_name(node.name).upper() + '_FACS'
        facs_entries = ', '.join(str(elem.position) for elem in elems)
        # Add one more entry at the rightmost position so that the
        # interpolation between indices works (no out of bounds error)
        facs_entries += ', 1.0'
        state.curshader.add_const("float",
                                  facs_var,
                                  facs_entries,
                                  array_size=len(elems) + 1)

        # Mix color
        prev_stop_fac = f'{facs_var}[{index_var}]'
        next_stop_fac = f'{facs_var}[{index_var} + 1]'
        prev_stop_col = f'{cols_var}[{index_var}]'
        next_stop_col = f'{cols_var}[{index_var} + 1]'
        rel_pos = f'({fac_var} - {prev_stop_fac}) * (1.0 / ({next_stop_fac} - {prev_stop_fac}))'
        return f'mix({prev_stop_col}, {next_stop_col}, max({rel_pos}, 0.0))'