1# Copyright 2018-2019 The glTF-Blender-IO authors. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15import bpy 16 17from .gltf2_blender_image import BlenderImage 18from ..com.gltf2_blender_conversion import texture_transform_gltf_to_blender 19from io_scene_gltf2.io.com.gltf2_io import Sampler 20from io_scene_gltf2.io.com.gltf2_io_constants import TextureFilter, TextureWrap 21 22def texture( 23 mh, 24 tex_info, 25 location, # Upper-right corner of the TexImage node 26 label, # Label for the TexImg node 27 color_socket, 28 alpha_socket=None, 29 is_data=False, 30): 31 """Creates nodes for a TextureInfo and hooks up the color/alpha outputs.""" 32 x, y = location 33 pytexture = mh.gltf.data.textures[tex_info.index] 34 if pytexture.sampler is not None: 35 pysampler = mh.gltf.data.samplers[pytexture.sampler] 36 else: 37 pysampler = Sampler.from_dict({}) 38 39 needs_uv_map = False # whether to create UVMap node 40 41 # Image Texture 42 tex_img = mh.node_tree.nodes.new('ShaderNodeTexImage') 43 tex_img.location = x - 240, y 44 tex_img.label = label 45 # Get image 46 if pytexture.source is not None: 47 BlenderImage.create(mh.gltf, pytexture.source) 48 pyimg = mh.gltf.data.images[pytexture.source] 49 blender_image_name = pyimg.blender_image_name 50 if blender_image_name: 51 tex_img.image = bpy.data.images[blender_image_name] 52 # Set colorspace for data images 53 if is_data: 54 if tex_img.image: 55 tex_img.image.colorspace_settings.is_data = True 56 # Set filtering 57 set_filtering(tex_img, pysampler) 58 # Outputs 59 mh.node_tree.links.new(color_socket, tex_img.outputs['Color']) 60 if alpha_socket is not None: 61 mh.node_tree.links.new(alpha_socket, tex_img.outputs['Alpha']) 62 # Inputs 63 uv_socket = tex_img.inputs[0] 64 65 x -= 340 66 67 # Do wrapping 68 wrap_s = pysampler.wrap_s 69 wrap_t = pysampler.wrap_t 70 if wrap_s is None: 71 wrap_s = TextureWrap.Repeat 72 if wrap_t is None: 73 wrap_t = TextureWrap.Repeat 74 # If wrapping is REPEATxREPEAT or CLAMPxCLAMP, just set tex_img.extension 75 if (wrap_s, wrap_t) == (TextureWrap.Repeat, TextureWrap.Repeat): 76 tex_img.extension = 'REPEAT' 77 elif (wrap_s, wrap_t) == (TextureWrap.ClampToEdge, TextureWrap.ClampToEdge): 78 tex_img.extension = 'EXTEND' 79 else: 80 # Otherwise separate the UV components and use math nodes to compute 81 # the wrapped UV coordinates 82 # => [Separate XYZ] => [Wrap for S] => [Combine XYZ] => 83 # => [Wrap for T] => 84 85 tex_img.extension = 'EXTEND' # slightly better errors near the edge than REPEAT 86 87 # Combine XYZ 88 com_uv = mh.node_tree.nodes.new('ShaderNodeCombineXYZ') 89 com_uv.location = x - 140, y - 100 90 mh.node_tree.links.new(uv_socket, com_uv.outputs[0]) 91 u_socket = com_uv.inputs[0] 92 v_socket = com_uv.inputs[1] 93 x -= 200 94 95 for i in [0, 1]: 96 wrap = [wrap_s, wrap_t][i] 97 socket = [u_socket, v_socket][i] 98 if wrap == TextureWrap.Repeat: 99 # WRAP node for REPEAT 100 math = mh.node_tree.nodes.new('ShaderNodeMath') 101 math.location = x - 140, y + 30 - i*200 102 math.operation = 'WRAP' 103 math.inputs[1].default_value = 0 104 math.inputs[2].default_value = 1 105 mh.node_tree.links.new(socket, math.outputs[0]) 106 socket = math.inputs[0] 107 elif wrap == TextureWrap.MirroredRepeat: 108 # PINGPONG node for MIRRORED_REPEAT 109 math = mh.node_tree.nodes.new('ShaderNodeMath') 110 math.location = x - 140, y + 30 - i*200 111 math.operation = 'PINGPONG' 112 math.inputs[1].default_value = 1 113 mh.node_tree.links.new(socket, math.outputs[0]) 114 socket = math.inputs[0] 115 else: 116 # Pass-through CLAMP since the tex_img node is set to EXTEND 117 pass 118 if i == 0: 119 u_socket = socket 120 else: 121 v_socket = socket 122 x -= 200 123 124 # Separate XYZ 125 sep_uv = mh.node_tree.nodes.new('ShaderNodeSeparateXYZ') 126 sep_uv.location = x - 140, y - 100 127 mh.node_tree.links.new(u_socket, sep_uv.outputs[0]) 128 mh.node_tree.links.new(v_socket, sep_uv.outputs[1]) 129 uv_socket = sep_uv.inputs[0] 130 x -= 200 131 132 needs_uv_map = True 133 134 # UV Transform (for KHR_texture_transform) 135 needs_tex_transform = 'KHR_texture_transform' in (tex_info.extensions or {}) 136 if needs_tex_transform: 137 mapping = mh.node_tree.nodes.new('ShaderNodeMapping') 138 mapping.location = x - 160, y + 30 139 mapping.vector_type = 'POINT' 140 # Outputs 141 mh.node_tree.links.new(uv_socket, mapping.outputs[0]) 142 # Inputs 143 uv_socket = mapping.inputs[0] 144 145 transform = tex_info.extensions['KHR_texture_transform'] 146 transform = texture_transform_gltf_to_blender(transform) 147 mapping.inputs['Location'].default_value[0] = transform['offset'][0] 148 mapping.inputs['Location'].default_value[1] = transform['offset'][1] 149 mapping.inputs['Rotation'].default_value[2] = transform['rotation'] 150 mapping.inputs['Scale'].default_value[0] = transform['scale'][0] 151 mapping.inputs['Scale'].default_value[1] = transform['scale'][1] 152 153 x -= 260 154 needs_uv_map = True 155 156 # UV Map 157 uv_idx = tex_info.tex_coord or 0 158 try: 159 uv_idx = tex_info.extensions['KHR_texture_transform']['texCoord'] 160 except Exception: 161 pass 162 if uv_idx != 0 or needs_uv_map: 163 uv_map = mh.node_tree.nodes.new('ShaderNodeUVMap') 164 uv_map.location = x - 160, y - 70 165 uv_map.uv_map = 'UVMap' if uv_idx == 0 else 'UVMap.%03d' % uv_idx 166 # Outputs 167 mh.node_tree.links.new(uv_socket, uv_map.outputs[0]) 168 169def set_filtering(tex_img, pysampler): 170 """Set the filtering/interpolation on an Image Texture from the glTf sampler.""" 171 minf = pysampler.min_filter 172 magf = pysampler.mag_filter 173 174 # Ignore mipmapping 175 if minf in [TextureFilter.NearestMipmapNearest, TextureFilter.NearestMipmapLinear]: 176 minf = TextureFilter.Nearest 177 elif minf in [TextureFilter.LinearMipmapNearest, TextureFilter.LinearMipmapLinear]: 178 minf = TextureFilter.Linear 179 180 # If both are nearest or the only specified one was nearest, use nearest. 181 if (minf, magf) in [ 182 (TextureFilter.Nearest, TextureFilter.Nearest), 183 (TextureFilter.Nearest, None), 184 (None, TextureFilter.Nearest), 185 ]: 186 tex_img.interpolation = 'Closest' 187 else: 188 tex_img.interpolation = 'Linear' 189