def __gather_texture_transform_and_tex_coord(primary_socket, export_settings): # We're expecting # # [UV Map] => [Mapping] => [UV Wrapping] => [Texture Node] => ... => primary_socket # # The [UV Wrapping] is for wrap modes like MIRROR that use nodes, # [Mapping] is for KHR_texture_transform, and [UV Map] is for texCoord. blender_shader_node = __get_tex_from_socket(primary_socket).shader_node # Skip over UV wrapping stuff (it goes in the sampler) result = detect_manual_uv_wrapping(blender_shader_node) if result: node = previous_node(result['next_socket']) else: node = previous_node(blender_shader_node.inputs['Vector']) texture_transform = None if node and node.type == 'MAPPING': texture_transform = gltf2_blender_get.get_texture_transform_from_mapping_node( node) node = previous_node(node.inputs['Vector']) texcoord_idx = 0 if node and node.type == 'UVMAP' and node.uv_map: # Try to gather map index. for blender_mesh in bpy.data.meshes: i = blender_mesh.uv_layers.find(node.uv_map) if i >= 0: texcoord_idx = i break return texture_transform, texcoord_idx or None
def __detect_lightpath_trick(socket): # Detects this (used to prevent casting light on other objects) See ex. # https://blender.stackexchange.com/a/21535/88681 # # [ Lightpath ] [ Mix ] # [ Is Camera Ray] => [Factor ] => socket # (don't care) => [Shader ] # next_socket => [ Emission ] => [Shader ] # # The Emission node can be omitted. # Returns None if not detected. Otherwise, a dict containing # next_socket. prev = gltf2_blender_get.previous_node(socket) if prev is None or prev.type != 'MIX_SHADER': return None in0 = gltf2_blender_get.previous_socket(prev.inputs[0]) if in0 is None or in0.node.type != 'LIGHT_PATH': return None if in0.name != 'Is Camera Ray': return None next_socket = prev.inputs[2] # Detect emission prev = gltf2_blender_get.previous_node(next_socket) if prev is not None and prev.type == 'EMISSION': next_socket = prev.inputs[0] return {'next_socket': next_socket}
def detect_manual_uv_wrapping(blender_shader_node): # Detects UV wrapping done using math nodes. This is for emulating wrap # modes Blender doesn't support. It looks like # # next_socket => [Sep XYZ] => [Wrap S] => [Comb XYZ] => blender_shader_node # => [Wrap T] => # # The [Wrap _] blocks are either math nodes (eg. PINGPONG for mirrored # repeat), or can be omitted. # # Returns None if not detected. Otherwise a dict containing the wrap # mode in each direction (or None), and next_socket. result = {} comb = previous_node(blender_shader_node.inputs['Vector']) if comb is None or comb.type != 'COMBXYZ': return None for soc in ['X', 'Y']: node = previous_node(comb.inputs[soc]) if node is None: return None if node.type == 'SEPXYZ': # Passed through without change wrap = None prev_socket = previous_socket(comb.inputs[soc]) elif node.type == 'MATH': # Math node applies a manual wrap if (node.operation == 'PINGPONG' and get_const_from_socket( node.inputs[1], kind='VALUE') == 1.0): # scale = 1 wrap = TextureWrap.MirroredRepeat elif (node.operation == 'WRAP' and get_const_from_socket( node.inputs[1], kind='VALUE') == 0.0 and # min = 0 get_const_from_socket(node.inputs[2], kind='VALUE') == 1.0): # max = 1 wrap = TextureWrap.Repeat else: return None prev_socket = previous_socket(node.inputs[0]) else: return None if prev_socket is None: return None prev_node = prev_socket.node if prev_node.type != 'SEPXYZ': return None # Make sure X goes to X, etc. if prev_socket.name != soc: return None # Make sure both attach to the same SeparateXYZ node if soc == 'X': sep = prev_node else: if sep != prev_node: return None result['wrap_s' if soc == 'X' else 'wrap_t'] = wrap result['next_socket'] = sep.inputs[0] return result
def __detect_mix_alpha(socket): # Detects this (used for an alpha hookup) # # [ Mix ] # alpha_socket => [Factor ] => socket # [Transparent] => [Shader ] # next_socket => [Shader ] # # Returns None if not detected. Otherwise, a dict containing alpha_socket # and next_socket. prev = gltf2_blender_get.previous_node(socket) if prev is None or prev.type != 'MIX_SHADER': return None in1 = gltf2_blender_get.previous_node(prev.inputs[1]) if in1 is None or in1.type != 'BSDF_TRANSPARENT': return None return { 'alpha_socket': prev.inputs[0], 'next_socket': prev.inputs[2], }
def __gather_occlusion_strength(primary_socket, export_settings): # Look for a MixRGB node that mixes with pure white in front of # primary_socket. The mix factor gives the occlusion strength. node = gltf2_blender_get.previous_node(primary_socket) if node and node.type == 'MIX_RGB' and node.blend_type == 'MIX': fac = gltf2_blender_get.get_const_from_socket(node.inputs['Fac'], kind='VALUE') col1 = gltf2_blender_get.get_const_from_socket(node.inputs['Color1'], kind='RGB') col2 = gltf2_blender_get.get_const_from_socket(node.inputs['Color2'], kind='RGB') if fac is not None: if col1 == [1, 1, 1] and col2 is None: return fac if col1 is None and col2 == [1, 1, 1]: return 1.0 - fac # reversed for reversed inputs return None