glTF importer: rewrite material import

This commit is contained in:
Julien Duroure 2020-01-29 21:57:22 +01:00
parent e88c7ee2a7
commit 2caaf64ab1
12 changed files with 793 additions and 990 deletions

View File

@ -15,7 +15,7 @@
bl_info = {
'name': 'glTF 2.0 format',
'author': 'Julien Duroure, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors',
"version": (1, 2, 16),
"version": (1, 2, 17),
'blender': (2, 81, 6),
'location': 'File > Import-Export',
'description': 'Import-Export as glTF 2.0',

View File

@ -13,50 +13,6 @@
# limitations under the License.
def get_output_node(node_tree):
"""Retrieve output node."""
output = [node for node in node_tree.nodes if node.type == 'OUTPUT_MATERIAL'][0]
return output
def get_output_surface_input(node_tree):
"""Retrieve surface input of output node."""
output_node = get_output_node(node_tree)
return output_node.inputs['Surface']
def get_diffuse_texture(node_tree):
"""Retrieve diffuse texture node."""
for node in node_tree.nodes:
print(node.name)
if node.label == 'BASE COLOR':
return node
return None
def get_preoutput_node_output(node_tree):
"""Retrieve node just before output node."""
output_node = get_output_node(node_tree)
preoutput_node = output_node.inputs['Surface'].links[0].from_node
# Pre output node is Principled BSDF or any BSDF => BSDF
if 'BSDF' in preoutput_node.type:
return preoutput_node.outputs['BSDF']
elif 'SHADER' in preoutput_node.type:
return preoutput_node.outputs['Shader']
else:
print(preoutput_node.type)
def get_base_color_node(node_tree):
"""Returns the last node of the diffuse block."""
for node in node_tree.nodes:
if node.label == 'BASE COLOR':
return node
return None
def get_gltf_node_name():
return "glTF Settings"

View File

@ -13,255 +13,153 @@
# limitations under the License.
import bpy
from .gltf2_blender_material_utils import make_texture_block
from ...io.com.gltf2_io import TextureInfo
from .gltf2_blender_pbrMetallicRoughness import \
base_color, emission, normal, occlusion, make_output_nodes, make_settings_node
from .gltf2_blender_texture import texture
class BlenderKHR_materials_pbrSpecularGlossiness():
"""Blender KHR_materials_pbrSpecularGlossiness extension."""
def __new__(cls, *args, **kwargs):
raise RuntimeError("%s should not be instantiated" % cls)
def pbr_specular_glossiness(mh):
"""Creates node tree for pbrSpecularGlossiness materials."""
# This does option #1 from
# https://github.com/KhronosGroup/glTF-Blender-IO/issues/303
@staticmethod
def create(gltf, pbrSG, mat_name, vertex_color):
"""KHR_materials_pbrSpecularGlossiness creation."""
engine = bpy.context.scene.render.engine
if engine in ['CYCLES', 'BLENDER_EEVEE']:
BlenderKHR_materials_pbrSpecularGlossiness.create_nodetree(gltf, pbrSG, mat_name, vertex_color)
# Sum a Glossy and Diffuse Shader
glossy_node = mh.node_tree.nodes.new('ShaderNodeBsdfGlossy')
diffuse_node = mh.node_tree.nodes.new('ShaderNodeBsdfDiffuse')
add_node = mh.node_tree.nodes.new('ShaderNodeAddShader')
glossy_node.location = 10, 220
diffuse_node.location = 10, 0
add_node.location = 230, 100
mh.node_tree.links.new(add_node.inputs[0], glossy_node.outputs[0])
mh.node_tree.links.new(add_node.inputs[1], diffuse_node.outputs[0])
@staticmethod
def create_nodetree(gltf, pbrSG, mat_name, vertex_color):
"""Node tree creation."""
material = bpy.data.materials[mat_name]
material.use_nodes = True
node_tree = material.node_tree
emission_socket, alpha_socket = make_output_nodes(
mh,
location=(370, 250),
shader_socket=add_node.outputs[0],
make_emission_socket=mh.needs_emissive(),
make_alpha_socket=not mh.is_opaque(),
)
# delete all nodes except output
for node in list(node_tree.nodes):
if not node.type == 'OUTPUT_MATERIAL':
node_tree.nodes.remove(node)
emission(
mh,
location=(-200, 860),
color_socket=emission_socket,
)
output_node = node_tree.nodes[0]
output_node.location = 1000, 0
base_color(
mh,
is_diffuse=True,
location=(-200, 380),
color_socket=diffuse_node.inputs['Color'],
alpha_socket=alpha_socket,
)
# create PBR node
diffuse = node_tree.nodes.new('ShaderNodeBsdfDiffuse')
diffuse.location = 0, 0
glossy = node_tree.nodes.new('ShaderNodeBsdfGlossy')
glossy.location = 0, 100
mix = node_tree.nodes.new('ShaderNodeMixShader')
mix.location = 500, 0
specular_glossiness(
mh,
location=(-200, -100),
specular_socket=glossy_node.inputs['Color'],
roughness_socket=glossy_node.inputs['Roughness'],
)
copy_socket(
mh,
copy_from=glossy_node.inputs['Roughness'],
copy_to=diffuse_node.inputs['Roughness'],
)
glossy.inputs[1].default_value = 1 - pbrSG['glossinessFactor']
normal(
mh,
location=(-200, -580),
normal_socket=glossy_node.inputs['Normal'],
)
copy_socket(
mh,
copy_from=glossy_node.inputs['Normal'],
copy_to=diffuse_node.inputs['Normal'],
)
if pbrSG['diffuse_type'] == gltf.SIMPLE:
if not vertex_color:
# change input values
diffuse.inputs[0].default_value = pbrSG['diffuseFactor']
if mh.pymat.occlusion_texture is not None:
node = make_settings_node(mh, location=(610, -1060))
occlusion(
mh,
location=(510, -970),
occlusion_socket=node.inputs['Occlusion'],
)
else:
# Create vertexcolor node to get COLOR_0 data
vertexcolor_node = node_tree.nodes.new('ShaderNodeVertexColor')
vertexcolor_node.layer_name = 'Col'
vertexcolor_node.location = -500, 0
# links
node_tree.links.new(diffuse.inputs[0], vertexcolor_node.outputs[0])
# [Texture] => [Spec/Gloss Factor] => [Gloss to Rough] =>
def specular_glossiness(mh, location, specular_socket, roughness_socket):
x, y = location
spec_factor = mh.pymat.extensions \
['KHR_materials_pbrSpecularGlossiness'] \
.get('specularFactor', [1, 1, 1])
gloss_factor = mh.pymat.extensions \
['KHR_materials_pbrSpecularGlossiness'] \
.get('glossinessFactor', 1)
spec_gloss_texture = mh.pymat.extensions \
['KHR_materials_pbrSpecularGlossiness'] \
.get('specularGlossinessTexture', None)
if spec_gloss_texture is not None:
spec_gloss_texture = TextureInfo.from_dict(spec_gloss_texture)
elif pbrSG['diffuse_type'] == gltf.TEXTURE_FACTOR:
if spec_gloss_texture is None:
specular_socket.default_value = spec_factor + [1]
roughness_socket.default_value = 1 - gloss_factor
return
# TODO alpha ?
if vertex_color:
# TODO tree locations
# Create vertexcolor / separate / math nodes
vertexcolor_node = node_tree.nodes.new('ShaderNodeVertexColor')
vertexcolor_node.layer_name = 'Col'
# (1 - x) converts glossiness to roughness
node = mh.node_tree.nodes.new('ShaderNodeInvert')
node.label = 'Invert (Gloss to Rough)'
node.location = x - 140, y - 75
# Outputs
mh.node_tree.links.new(roughness_socket, node.outputs[0])
# Inputs
node.inputs['Fac'].default_value = 1
glossiness_socket = node.inputs['Color']
separate_vertex_color = node_tree.nodes.new('ShaderNodeSeparateRGB')
math_vc_R = node_tree.nodes.new('ShaderNodeMath')
math_vc_R.operation = 'MULTIPLY'
x -= 250
math_vc_G = node_tree.nodes.new('ShaderNodeMath')
math_vc_G.operation = 'MULTIPLY'
# Mix in spec/gloss factor
if spec_factor != [1, 1, 1] or gloss_factor != 1:
if spec_factor != [1, 1, 1]:
node = mh.node_tree.nodes.new('ShaderNodeMixRGB')
node.label = 'Specular Factor'
node.location = x - 140, y
node.blend_type = 'MULTIPLY'
# Outputs
mh.node_tree.links.new(specular_socket, node.outputs[0])
# Inputs
node.inputs['Fac'].default_value = 1.0
specular_socket = node.inputs['Color1']
node.inputs['Color2'].default_value = spec_factor + [1]
math_vc_B = node_tree.nodes.new('ShaderNodeMath')
math_vc_B.operation = 'MULTIPLY'
if gloss_factor != 1:
node = mh.node_tree.nodes.new('ShaderNodeMath')
node.label = 'Glossiness Factor'
node.location = x - 140, y - 200
node.operation = 'MULTIPLY'
# Outputs
mh.node_tree.links.new(glossiness_socket, node.outputs[0])
# Inputs
glossiness_socket = node.inputs[0]
node.inputs[1].default_value = gloss_factor
# create UV Map / Mapping / Texture nodes / separate & math and combine
text_node = make_texture_block(
gltf,
node_tree,
TextureInfo.from_dict(pbrSG['diffuseTexture']),
location=(-1000, 500),
label='DIFFUSE',
name='diffuseTexture',
)
x -= 200
combine = node_tree.nodes.new('ShaderNodeCombineRGB')
combine.location = -250, 500
texture(
mh,
tex_info=spec_gloss_texture,
label='SPECULAR GLOSSINESS',
location=(x, y),
color_socket=specular_socket,
alpha_socket=glossiness_socket,
)
math_R = node_tree.nodes.new('ShaderNodeMath')
math_R.location = -500, 750
math_R.operation = 'MULTIPLY'
math_R.inputs[1].default_value = pbrSG['diffuseFactor'][0]
math_G = node_tree.nodes.new('ShaderNodeMath')
math_G.location = -500, 500
math_G.operation = 'MULTIPLY'
math_G.inputs[1].default_value = pbrSG['diffuseFactor'][1]
math_B = node_tree.nodes.new('ShaderNodeMath')
math_B.location = -500, 250
math_B.operation = 'MULTIPLY'
math_B.inputs[1].default_value = pbrSG['diffuseFactor'][2]
separate = node_tree.nodes.new('ShaderNodeSeparateRGB')
separate.location = -750, 500
# Create links
if vertex_color:
node_tree.links.new(separate_vertex_color.inputs[0], vertexcolor_node.outputs[0])
node_tree.links.new(math_vc_R.inputs[1], separate_vertex_color.outputs[0])
node_tree.links.new(math_vc_G.inputs[1], separate_vertex_color.outputs[1])
node_tree.links.new(math_vc_B.inputs[1], separate_vertex_color.outputs[2])
node_tree.links.new(math_vc_R.inputs[0], math_R.outputs[0])
node_tree.links.new(math_vc_G.inputs[0], math_G.outputs[0])
node_tree.links.new(math_vc_B.inputs[0], math_B.outputs[0])
node_tree.links.new(combine.inputs[0], math_vc_R.outputs[0])
node_tree.links.new(combine.inputs[1], math_vc_G.outputs[0])
node_tree.links.new(combine.inputs[2], math_vc_B.outputs[0])
else:
node_tree.links.new(combine.inputs[0], math_R.outputs[0])
node_tree.links.new(combine.inputs[1], math_G.outputs[0])
node_tree.links.new(combine.inputs[2], math_B.outputs[0])
# Common for both mode (non vertex color / vertex color)
node_tree.links.new(math_R.inputs[0], separate.outputs[0])
node_tree.links.new(math_G.inputs[0], separate.outputs[1])
node_tree.links.new(math_B.inputs[0], separate.outputs[2])
node_tree.links.new(separate.inputs[0], text_node.outputs[0])
node_tree.links.new(diffuse.inputs[0], combine.outputs[0])
elif pbrSG['diffuse_type'] == gltf.TEXTURE:
# TODO alpha ?
if vertex_color:
# Create vertexcolor / separate / math nodes
vertexcolor_node = node_tree.nodes.new('ShaderNodeVertexColor')
vertexcolor_node.layer_name = 'Col'
vertexcolor_node.location = -2000, 250
separate_vertex_color = node_tree.nodes.new('ShaderNodeSeparateRGB')
separate_vertex_color.location = -1500, 250
math_vc_R = node_tree.nodes.new('ShaderNodeMath')
math_vc_R.operation = 'MULTIPLY'
math_vc_R.location = -1000, 750
math_vc_G = node_tree.nodes.new('ShaderNodeMath')
math_vc_G.operation = 'MULTIPLY'
math_vc_G.location = -1000, 500
math_vc_B = node_tree.nodes.new('ShaderNodeMath')
math_vc_B.operation = 'MULTIPLY'
math_vc_B.location = -1000, 250
combine = node_tree.nodes.new('ShaderNodeCombineRGB')
combine.location = -500, 500
separate = node_tree.nodes.new('ShaderNodeSeparateRGB')
separate.location = -1500, 500
# create UV Map / Mapping / Texture nodes / separate & math and combine
if vertex_color:
location = (-2000, 500)
else:
location = (-500, 500)
text_node = text_node = make_texture_block(
gltf,
node_tree,
TextureInfo.from_dict(pbrSG['diffuseTexture']),
location=location,
label='DIFFUSE',
name='diffuseTexture',
)
# Create links
if vertex_color:
node_tree.links.new(separate_vertex_color.inputs[0], vertexcolor_node.outputs[0])
node_tree.links.new(math_vc_R.inputs[1], separate_vertex_color.outputs[0])
node_tree.links.new(math_vc_G.inputs[1], separate_vertex_color.outputs[1])
node_tree.links.new(math_vc_B.inputs[1], separate_vertex_color.outputs[2])
node_tree.links.new(combine.inputs[0], math_vc_R.outputs[0])
node_tree.links.new(combine.inputs[1], math_vc_G.outputs[0])
node_tree.links.new(combine.inputs[2], math_vc_B.outputs[0])
node_tree.links.new(separate.inputs[0], text_node.outputs[0])
node_tree.links.new(diffuse.inputs[0], combine.outputs[0])
node_tree.links.new(math_vc_R.inputs[0], separate.outputs[0])
node_tree.links.new(math_vc_G.inputs[0], separate.outputs[1])
node_tree.links.new(math_vc_B.inputs[0], separate.outputs[2])
else:
node_tree.links.new(diffuse.inputs[0], text_node.outputs[0])
if pbrSG['specgloss_type'] == gltf.SIMPLE:
combine = node_tree.nodes.new('ShaderNodeCombineRGB')
combine.inputs[0].default_value = pbrSG['specularFactor'][0]
combine.inputs[1].default_value = pbrSG['specularFactor'][1]
combine.inputs[2].default_value = pbrSG['specularFactor'][2]
# links
node_tree.links.new(glossy.inputs[0], combine.outputs[0])
elif pbrSG['specgloss_type'] == gltf.TEXTURE:
spec_text = make_texture_block(
gltf,
node_tree,
TextureInfo.from_dict(pbrSG['specularGlossinessTexture']),
location=(-1000, 0),
label='SPECULAR GLOSSINESS',
name='specularGlossinessTexture',
colorspace='NONE',
)
# links
node_tree.links.new(glossy.inputs[0], spec_text.outputs[0])
node_tree.links.new(mix.inputs[0], spec_text.outputs[1])
elif pbrSG['specgloss_type'] == gltf.TEXTURE_FACTOR:
spec_text = make_texture_block(
gltf,
node_tree,
TextureInfo.from_dict(pbrSG['specularGlossinessTexture']),
location=(-1000, 0),
label='SPECULAR GLOSSINESS',
name='specularGlossinessTexture',
colorspace='NONE',
)
spec_math = node_tree.nodes.new('ShaderNodeMath')
spec_math.operation = 'MULTIPLY'
spec_math.inputs[0].default_value = pbrSG['glossinessFactor']
spec_math.location = -250, 100
# links
node_tree.links.new(spec_math.inputs[1], spec_text.outputs[0])
node_tree.links.new(mix.inputs[0], spec_text.outputs[1])
node_tree.links.new(glossy.inputs[1], spec_math.outputs[0])
node_tree.links.new(glossy.inputs[0], spec_text.outputs[0])
# link node to output
node_tree.links.new(mix.inputs[2], diffuse.outputs[0])
node_tree.links.new(mix.inputs[1], glossy.outputs[0])
node_tree.links.new(output_node.inputs[0], mix.outputs[0])
def copy_socket(mh, copy_from, copy_to):
"""Copy the links/default value from one socket to another."""
copy_to.default_value = copy_from.default_value
for link in copy_from.links:
mh.node_tree.links.new(copy_to, link.from_socket)

View File

@ -13,37 +13,44 @@
# limitations under the License.
import bpy
from ...io.com.gltf2_io import MaterialPBRMetallicRoughness
from .gltf2_blender_pbrMetallicRoughness import BlenderPbr
from .gltf2_blender_pbrMetallicRoughness import base_color, make_output_nodes
class BlenderKHR_materials_unlit():
"""Blender KHR_materials_unlit extension."""
def __new__(cls, *args, **kwargs):
raise RuntimeError("%s should not be instantiated" % cls)
@staticmethod
def create(gltf, material_index, unlit, mat_name, vertex_color):
"""KHR_materials_unlit creation."""
engine = bpy.context.scene.render.engine
if engine in ['CYCLES', 'BLENDER_EEVEE']:
BlenderKHR_materials_unlit.create_nodetree(gltf, material_index, unlit, mat_name, vertex_color)
def unlit(mh):
"""Creates node tree for unlit materials."""
# Emission node for the base color
emission_node = mh.node_tree.nodes.new('ShaderNodeEmission')
emission_node.location = 10, 126
@staticmethod
def create_nodetree(gltf, material_index, unlit, mat_name, vertex_color):
"""Node tree creation."""
material = bpy.data.materials[mat_name]
material.use_nodes = True
# Lightpath trick: makes Emission visible only to camera rays.
# [Is Camera Ray] => [Mix] =>
# [Transparent] => [ ]
# [Emission] => [ ]
lightpath_node = mh.node_tree.nodes.new('ShaderNodeLightPath')
transparent_node = mh.node_tree.nodes.new('ShaderNodeBsdfTransparent')
mix_node = mh.node_tree.nodes.new('ShaderNodeMixShader')
lightpath_node.location = 10, 600
transparent_node.location = 10, 240
mix_node.location = 260, 320
mh.node_tree.links.new(mix_node.inputs['Fac'], lightpath_node.outputs['Is Camera Ray'])
mh.node_tree.links.new(mix_node.inputs[1], transparent_node.outputs[0])
mh.node_tree.links.new(mix_node.inputs[2], emission_node.outputs[0])
# Using transparency requires alpha blending for Eevee
if mh.is_opaque():
mh.mat.blend_method = 'HASHED' # TODO check best result in eevee
pymaterial = gltf.data.materials[material_index]
if pymaterial.pbr_metallic_roughness is None:
# If no pbr material is set, we need to apply all default of pbr
pbr = {}
pbr["baseColorFactor"] = [1.0, 1.0, 1.0, 1.0]
pbr["metallicFactor"] = 1.0
pbr["roughnessFactor"] = 1.0
pymaterial.pbr_metallic_roughness = MaterialPBRMetallicRoughness.from_dict(pbr)
pymaterial.pbr_metallic_roughness.color_type = gltf.SIMPLE
pymaterial.pbr_metallic_roughness.metallic_type = gltf.SIMPLE
_emission_socket, alpha_socket = make_output_nodes(
mh,
location=(420, 280) if mh.is_opaque() else (150, 130),
shader_socket=mix_node.outputs[0],
make_emission_socket=False,
make_alpha_socket=not mh.is_opaque(),
)
BlenderPbr.create_nodetree(gltf, pymaterial.pbr_metallic_roughness, mat_name, vertex_color, nodetype='unlit')
base_color(
mh,
location=(-200, 380),
color_socket=emission_node.inputs['Color'],
alpha_socket=alpha_socket,
)

View File

@ -87,76 +87,6 @@ class BlenderGlTF():
for material in gltf.data.materials:
material.blender_material = {}
if material.pbr_metallic_roughness:
# Init
material.pbr_metallic_roughness.color_type = gltf.SIMPLE
material.pbr_metallic_roughness.vertex_color = False
material.pbr_metallic_roughness.metallic_type = gltf.SIMPLE
if material.pbr_metallic_roughness.base_color_texture:
material.pbr_metallic_roughness.color_type = gltf.TEXTURE
if material.pbr_metallic_roughness.metallic_roughness_texture:
material.pbr_metallic_roughness.metallic_type = gltf.TEXTURE
if material.pbr_metallic_roughness.base_color_factor:
if material.pbr_metallic_roughness.color_type == gltf.TEXTURE and \
material.pbr_metallic_roughness.base_color_factor != [1.0, 1.0, 1.0, 1.0]:
material.pbr_metallic_roughness.color_type = gltf.TEXTURE_FACTOR
else:
material.pbr_metallic_roughness.base_color_factor = [1.0, 1.0, 1.0, 1.0]
if material.pbr_metallic_roughness.metallic_factor is not None:
if material.pbr_metallic_roughness.metallic_type == gltf.TEXTURE \
and material.pbr_metallic_roughness.metallic_factor != 1.0:
material.pbr_metallic_roughness.metallic_type = gltf.TEXTURE_FACTOR
else:
material.pbr_metallic_roughness.metallic_factor = 1.0
if material.pbr_metallic_roughness.roughness_factor is not None:
if material.pbr_metallic_roughness.metallic_type == gltf.TEXTURE \
and material.pbr_metallic_roughness.roughness_factor != 1.0:
material.pbr_metallic_roughness.metallic_type = gltf.TEXTURE_FACTOR
else:
material.pbr_metallic_roughness.roughness_factor = 1.0
# pre compute material for KHR_materials_pbrSpecularGlossiness
if material.extensions is not None \
and 'KHR_materials_pbrSpecularGlossiness' in material.extensions.keys():
# Init
material.extensions['KHR_materials_pbrSpecularGlossiness']['diffuse_type'] = gltf.SIMPLE
material.extensions['KHR_materials_pbrSpecularGlossiness']['vertex_color'] = False
material.extensions['KHR_materials_pbrSpecularGlossiness']['specgloss_type'] = gltf.SIMPLE
if 'diffuseTexture' in material.extensions['KHR_materials_pbrSpecularGlossiness'].keys():
material.extensions['KHR_materials_pbrSpecularGlossiness']['diffuse_type'] = gltf.TEXTURE
if 'diffuseFactor' in material.extensions['KHR_materials_pbrSpecularGlossiness'].keys():
if material.extensions['KHR_materials_pbrSpecularGlossiness']['diffuse_type'] == gltf.TEXTURE \
and material.extensions['KHR_materials_pbrSpecularGlossiness']['diffuseFactor'] != \
[1.0, 1.0, 1.0, 1.0]:
material.extensions['KHR_materials_pbrSpecularGlossiness']['diffuse_type'] = \
gltf.TEXTURE_FACTOR
else:
material.extensions['KHR_materials_pbrSpecularGlossiness']['diffuseFactor'] = \
[1.0, 1.0, 1.0, 1.0]
if 'specularGlossinessTexture' in material.extensions['KHR_materials_pbrSpecularGlossiness'].keys():
material.extensions['KHR_materials_pbrSpecularGlossiness']['specgloss_type'] = gltf.TEXTURE
if 'specularFactor' in material.extensions['KHR_materials_pbrSpecularGlossiness'].keys():
if material.extensions['KHR_materials_pbrSpecularGlossiness']['specgloss_type'] == \
gltf.TEXTURE \
and material.extensions['KHR_materials_pbrSpecularGlossiness']['specularFactor'] != \
[1.0, 1.0, 1.0]:
material.extensions['KHR_materials_pbrSpecularGlossiness']['specgloss_type'] = \
gltf.TEXTURE_FACTOR
else:
material.extensions['KHR_materials_pbrSpecularGlossiness']['specularFactor'] = [1.0, 1.0, 1.0]
if 'glossinessFactor' not in material.extensions['KHR_materials_pbrSpecularGlossiness'].keys():
material.extensions['KHR_materials_pbrSpecularGlossiness']['glossinessFactor'] = 1.0
# images
if gltf.data.images is not None:
for img in gltf.data.images:

View File

@ -1,92 +0,0 @@
# Copyright 2018-2019 The glTF-Blender-IO authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import bpy
from .gltf2_blender_material_utils import make_texture_block
from ..com.gltf2_blender_material_helpers import get_preoutput_node_output
class BlenderEmissiveMap():
"""Blender Emissive Map."""
def __new__(cls, *args, **kwargs):
raise RuntimeError("%s should not be instantiated" % cls)
@staticmethod
def create(gltf, material_idx, vertex_color, factor_only=False):
"""Create emissive map."""
engine = bpy.context.scene.render.engine
if engine in ['CYCLES', 'BLENDER_EEVEE']:
BlenderEmissiveMap.create_nodetree(gltf, material_idx, vertex_color, factor_only)
def create_nodetree(gltf, material_idx, vertex_color, factor_only=False):
"""Create node tree."""
pymaterial = gltf.data.materials[material_idx]
material = bpy.data.materials[pymaterial.blender_material[vertex_color]]
node_tree = material.node_tree
# check if there is some emissive_factor on material
if pymaterial.emissive_factor is None:
# Default in glTF specification is 0/0/0 --> No emission
pymaterial.emissive_factor = [0.0, 0.0, 0.0]
# retrieve principled node and output node
principled = get_preoutput_node_output(node_tree)
output = [node for node in node_tree.nodes if node.type == 'OUTPUT_MATERIAL'][0]
# add nodes
emit = node_tree.nodes.new('ShaderNodeEmission')
emit.location = 0, 1000
if factor_only is False:
if pymaterial.emissive_factor != [1.0, 1.0, 1.0]:
mult_node = node_tree.nodes.new('ShaderNodeMixRGB')
mult_node.blend_type = 'MULTIPLY'
mult_node.inputs['Fac'].default_value = 1.0
mult_node.location = -500, 1000
mult_node.inputs['Color2'].default_value = [
pymaterial.emissive_factor[0],
pymaterial.emissive_factor[1],
pymaterial.emissive_factor[2],
1.0,
]
text = make_texture_block(
gltf,
node_tree,
pymaterial.emissive_texture,
location=(-1000, 1000),
label='EMISSIVE',
name='emissiveTexture',
)
# create links
if pymaterial.emissive_factor != [1.0, 1.0, 1.0]:
node_tree.links.new(mult_node.inputs[1], text.outputs[0])
node_tree.links.new(emit.inputs[0], mult_node.outputs[0])
else:
node_tree.links.new(emit.inputs[0], text.outputs[0])
else:
emissive_color = pymaterial.emissive_factor + [1] # add alpha
emit.inputs[0].default_value = emissive_color
add = node_tree.nodes.new('ShaderNodeAddShader')
add.location = 500, 500
# following links will modify PBR node tree
node_tree.links.new(add.inputs[0], emit.outputs[0])
node_tree.links.new(add.inputs[1], principled)
node_tree.links.new(output.inputs[0], add.outputs[0])

View File

@ -1,87 +0,0 @@
# Copyright 2018-2019 The glTF-Blender-IO authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import bpy
from .gltf2_blender_material_utils import make_texture_block
class BlenderNormalMap():
"""Blender Normal map."""
def __new__(cls, *args, **kwargs):
raise RuntimeError("%s should not be instantiated" % cls)
@staticmethod
def create(gltf, material_idx, vertex_color):
"""Creation of Normal map."""
engine = bpy.context.scene.render.engine
if engine in ['CYCLES', 'BLENDER_EEVEE']:
BlenderNormalMap.create_nodetree(gltf, material_idx, vertex_color)
def create_nodetree(gltf, material_idx, vertex_color):
"""Creation of Nodetree."""
pymaterial = gltf.data.materials[material_idx]
material = bpy.data.materials[pymaterial.blender_material[vertex_color]]
node_tree = material.node_tree
# retrieve principled node and output node
principled = None
diffuse = None
glossy = None
if len([node for node in node_tree.nodes if node.type == "BSDF_PRINCIPLED"]) != 0:
principled = [node for node in node_tree.nodes if node.type == "BSDF_PRINCIPLED"][0]
else:
# No principled, we are probably coming from extension
diffuse = [node for node in node_tree.nodes if node.type == "BSDF_DIFFUSE"][0]
glossy = [node for node in node_tree.nodes if node.type == "BSDF_GLOSSY"][0]
# add nodes
text = make_texture_block(
gltf,
node_tree,
pymaterial.normal_texture,
location=(-500, -500),
label='NORMALMAP',
name='normalTexture',
colorspace='NONE',
)
normalmap_node = node_tree.nodes.new('ShaderNodeNormalMap')
normalmap_node.location = -250, -500
tex_info = pymaterial.normal_texture
texcoord_idx = tex_info.tex_coord or 0
if tex_info.extensions and 'KHR_texture_transform' in tex_info.extensions:
if 'texCoord' in tex_info.extensions['KHR_texture_transform']:
texcoord_idx = tex_info.extensions['KHR_texture_transform']['texCoord']
normalmap_node.uv_map = 'UVMap' if texcoord_idx == 0 else 'UVMap.%03d' % texcoord_idx
# Set strength
if pymaterial.normal_texture.scale is not None:
normalmap_node.inputs[0].default_value = pymaterial.normal_texture.scale
else:
normalmap_node.inputs[0].default_value = 1.0 # Default
# create links
node_tree.links.new(normalmap_node.inputs[1], text.outputs[0])
# following links will modify PBR node tree
if principled:
node_tree.links.new(principled.inputs[19], normalmap_node.outputs[0])
if diffuse:
node_tree.links.new(diffuse.inputs[2], normalmap_node.outputs[0])
if glossy:
node_tree.links.new(glossy.inputs[2], normalmap_node.outputs[0])

View File

@ -1,100 +0,0 @@
# Copyright 2018-2019 The glTF-Blender-IO authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import bpy
from .gltf2_blender_image import BlenderImage
from .gltf2_blender_material_utils import make_texture_block
from ..com.gltf2_blender_material_helpers import get_gltf_node_name
class BlenderOcclusionMap():
"""Blender Occlusion map."""
def __new__(cls, *args, **kwargs):
raise RuntimeError("%s should not be instantiated" % cls)
@staticmethod
def create(gltf, material_idx, vertex_color):
"""Occlusion map creation."""
engine = bpy.context.scene.render.engine
if engine in ['CYCLES', 'BLENDER_EEVEE']:
BlenderOcclusionMap.create_nodetree(gltf, material_idx, vertex_color)
def create_nodetree(gltf, material_idx, vertex_color):
"""Nodetree creation."""
pymaterial = gltf.data.materials[material_idx]
pytexture = gltf.data.textures[pymaterial.occlusion_texture.index]
material = bpy.data.materials[pymaterial.blender_material[vertex_color]]
node_tree = material.node_tree
# Pack texture. Occlusion is calculated from Cycles or Eevee, so do nothing
blender_image_name = None
if pytexture.source is not None:
BlenderImage.create(gltf, pytexture.source)
pyimg = gltf.data.images[pytexture.source]
blender_image_name = pyimg.blender_image_name
bpy.data.images[blender_image_name].use_fake_user = True
# Create a fake node group for exporter
gltf_node_group_name = get_gltf_node_name()
if gltf_node_group_name in bpy.data.node_groups:
gltf_node_group = bpy.data.node_groups[gltf_node_group_name]
else:
# Create a new node group
gltf_node_group = bpy.data.node_groups.new(gltf_node_group_name, 'ShaderNodeTree')
gltf_node_group.inputs.new("NodeSocketFloat", "Occlusion")
gltf_node_group.nodes.new('NodeGroupOutput')
gltf_node_group_input = gltf_node_group.nodes.new('NodeGroupInput')
gltf_node_group_input.location = -200, 0
# Set the node group inside material node tree
ao_node = node_tree.nodes.new('ShaderNodeGroup')
ao_node.node_tree = gltf_node_group
ao_node.location = 0, 200
# Check if the texture node already exists (if used by other parameter metal / roughness)
found = False
if blender_image_name is not None:
for node in [node for node in node_tree.nodes if node.type == "TEX_IMAGE"]:
if node.image and blender_image_name == node.image.name:
# This is our image !
# Retrieve separate node if found
if node.outputs['Color'].is_linked:
for link in node.outputs['Color'].links:
if link.to_node.type == 'SEPRGB':
node_tree.links.new(ao_node.inputs[0], link.to_node.outputs[0])
found = True
break
if found:
break
if not found:
# Need to create the texture node & separate node
separate = node_tree.nodes.new('ShaderNodeSeparateRGB')
separate.location = -500, 1500
ao_node.location = 0, 1500
text = make_texture_block(
gltf,
node_tree,
pymaterial.occlusion_texture,
location=(-1000, 1500),
label='OCCLUSION',
name='occlusionTexture',
)
# Links
node_tree.links.new(separate.inputs[0], text.outputs[0])
node_tree.links.new(ao_node.inputs[0], separate.outputs[0])

View File

@ -15,16 +15,9 @@
import bpy
from ..com.gltf2_blender_extras import set_extras
from .gltf2_blender_pbrMetallicRoughness import BlenderPbr
from .gltf2_blender_KHR_materials_pbrSpecularGlossiness import BlenderKHR_materials_pbrSpecularGlossiness
from .gltf2_blender_KHR_materials_unlit import BlenderKHR_materials_unlit
from .gltf2_blender_map_emissive import BlenderEmissiveMap
from .gltf2_blender_map_normal import BlenderNormalMap
from .gltf2_blender_map_occlusion import BlenderOcclusionMap
from ..com.gltf2_blender_material_helpers import get_output_surface_input
from ..com.gltf2_blender_material_helpers import get_preoutput_node_output
from ..com.gltf2_blender_material_helpers import get_base_color_node
from ...io.com.gltf2_io import MaterialPBRMetallicRoughness
from .gltf2_blender_pbrMetallicRoughness import MaterialHelper, pbr_metallic_roughness
from .gltf2_blender_KHR_materials_pbrSpecularGlossiness import pbr_specular_glossiness
from .gltf2_blender_KHR_materials_unlit import unlit
class BlenderMaterial():
@ -37,141 +30,65 @@ class BlenderMaterial():
"""Material creation."""
pymaterial = gltf.data.materials[material_idx]
if vertex_color is None:
if pymaterial.name is not None:
name = pymaterial.name
else:
name = "Material_" + str(material_idx)
else:
if pymaterial.name is not None:
name = pymaterial.name + "_" + vertex_color
else:
name = "Material_" + str(material_idx) + "_" + vertex_color
name = pymaterial.name
if name is None:
name = "Material_" + str(material_idx)
if vertex_color is not None:
name += "_" + vertex_color
mat = bpy.data.materials.new(name)
pymaterial.blender_material[vertex_color] = mat.name
set_extras(mat, pymaterial.extras)
BlenderMaterial.set_double_sided(pymaterial, mat)
BlenderMaterial.set_alpha_mode(pymaterial, mat)
BlenderMaterial.set_viewport_color(pymaterial, mat, vertex_color)
mat.use_backface_culling = (pymaterial.double_sided != True)
mat.use_nodes = True
while mat.node_tree.nodes: # clear all nodes
mat.node_tree.nodes.remove(mat.node_tree.nodes[0])
ignore_map = False
mh = MaterialHelper(gltf, pymaterial, mat, vertex_color)
if pymaterial.extensions is not None :
if 'KHR_materials_unlit' in pymaterial.extensions.keys():
ignore_map = True
BlenderKHR_materials_unlit.create(
gltf, material_idx,
pymaterial.extensions['KHR_materials_unlit'],
mat.name,
vertex_color
)
elif 'KHR_materials_pbrSpecularGlossiness' in pymaterial.extensions.keys():
BlenderKHR_materials_pbrSpecularGlossiness.create(
gltf, pymaterial.extensions['KHR_materials_pbrSpecularGlossiness'], mat.name, vertex_color
)
exts = pymaterial.extensions or {}
if 'KHR_materials_unlit' in exts:
unlit(mh)
elif 'KHR_materials_pbrSpecularGlossiness' in exts:
pbr_specular_glossiness(mh)
else:
# create pbr material
if pymaterial.pbr_metallic_roughness is None:
# If no pbr material is set, we need to apply all default of pbr
pbr = {}
pbr["baseColorFactor"] = [1.0, 1.0, 1.0, 1.0]
pbr["metallicFactor"] = 1.0
pbr["roughnessFactor"] = 1.0
pymaterial.pbr_metallic_roughness = MaterialPBRMetallicRoughness.from_dict(pbr)
pymaterial.pbr_metallic_roughness.color_type = gltf.SIMPLE
pymaterial.pbr_metallic_roughness.metallic_type = gltf.SIMPLE
BlenderPbr.create(gltf, pymaterial.pbr_metallic_roughness, mat.name, vertex_color)
if ignore_map == False:
# add emission map if needed
if pymaterial.emissive_texture is not None:
BlenderEmissiveMap.create(gltf, material_idx, vertex_color)
elif pymaterial.emissive_factor is not None:
# add emissive factor only if there is not emissive texture
BlenderEmissiveMap.create(gltf, material_idx, vertex_color, factor_only=True)
# add normal map if needed
if pymaterial.normal_texture is not None:
BlenderNormalMap.create(gltf, material_idx, vertex_color)
# add occlusion map if needed
# will be pack, but not used
if pymaterial.occlusion_texture is not None:
BlenderOcclusionMap.create(gltf, material_idx, vertex_color)
if pymaterial.alpha_mode is not None and pymaterial.alpha_mode != 'OPAQUE':
BlenderMaterial.blender_alpha(gltf, material_idx, vertex_color, pymaterial.alpha_mode)
pbr_metallic_roughness(mh)
@staticmethod
def blender_alpha(gltf, material_idx, vertex_color, alpha_mode):
"""Set alpha."""
pymaterial = gltf.data.materials[material_idx]
material = bpy.data.materials[pymaterial.blender_material[vertex_color]]
def set_double_sided(pymaterial, mat):
mat.use_backface_culling = (pymaterial.double_sided != True)
# Set alpha value in material
@staticmethod
def set_alpha_mode(pymaterial, mat):
alpha_mode = pymaterial.alpha_mode
if alpha_mode == 'BLEND':
material.blend_method = 'BLEND'
elif alpha_mode == "MASK":
material.blend_method = 'CLIP'
alpha_cutoff = pymaterial.alpha_cutoff if pymaterial.alpha_cutoff is not None else 0.5
material.alpha_threshold = alpha_cutoff
mat.blend_method = 'BLEND'
elif alpha_mode == 'MASK':
mat.blend_method = 'CLIP'
alpha_cutoff = pymaterial.alpha_cutoff
alpha_cutoff = alpha_cutoff if alpha_cutoff is not None else 0.5
mat.alpha_threshold = alpha_cutoff
node_tree = material.node_tree
# Add nodes for basic transparency
# Add mix shader between output and Principled BSDF
trans = node_tree.nodes.new('ShaderNodeBsdfTransparent')
trans.location = 750, -500
mix = node_tree.nodes.new('ShaderNodeMixShader')
mix.location = 1000, 0
@staticmethod
def set_viewport_color(pymaterial, mat, vertex_color):
# If there is no texture and no vertex color, use the base color as
# the color for the Solid view.
if vertex_color:
return
output_surface_input = get_output_surface_input(node_tree)
preoutput_node_output = get_preoutput_node_output(node_tree)
exts = pymaterial.extensions or {}
if 'KHR_materials_pbrSpecularGlossiness' in exts:
# TODO
return
else:
pbr = pymaterial.pbr_metallic_roughness
if pbr is None or pbr.base_color_texture is not None:
return
color = pbr.base_color_factor or [1, 1, 1, 1]
link = output_surface_input.links[0]
node_tree.links.remove(link)
# PBR => Mix input 1
node_tree.links.new(preoutput_node_output, mix.inputs[1])
# Trans => Mix input 2
node_tree.links.new(trans.outputs['BSDF'], mix.inputs[2])
# Mix => Output
node_tree.links.new(mix.outputs['Shader'], output_surface_input)
# alpha blend factor
add = node_tree.nodes.new('ShaderNodeMath')
add.operation = 'ADD'
add.location = 750, -250
diffuse_factor = 1.0
if pymaterial.extensions is not None and 'KHR_materials_pbrSpecularGlossiness' in pymaterial.extensions:
diffuse_factor = pymaterial.extensions['KHR_materials_pbrSpecularGlossiness']['diffuseFactor'][3]
elif pymaterial.pbr_metallic_roughness:
diffuse_factor = pymaterial.pbr_metallic_roughness.base_color_factor[3]
add.inputs[0].default_value = abs(1.0 - diffuse_factor)
add.inputs[1].default_value = 0.0
node_tree.links.new(add.outputs['Value'], mix.inputs[0])
# Take diffuse texture alpha into account if any
diffuse_texture = get_base_color_node(node_tree)
if diffuse_texture:
inverter = node_tree.nodes.new('ShaderNodeInvert')
inverter.location = 250, -250
inverter.inputs[1].default_value = (1.0, 1.0, 1.0, 1.0)
node_tree.links.new(diffuse_texture.outputs['Alpha'], inverter.inputs[0])
mult = node_tree.nodes.new('ShaderNodeMath')
mult.operation = 'MULTIPLY' if pymaterial.alpha_mode == 'BLEND' else 'GREATER_THAN'
mult.location = 500, -250
# Note that `1.0 - pymaterial.alpha_cutoff` is used due to the invert node above.
alpha_cutoff = 1.0 if pymaterial.alpha_mode == 'BLEND' else \
1.0 - pymaterial.alpha_cutoff if pymaterial.alpha_cutoff is not None else 0.5
mult.inputs[1].default_value = alpha_cutoff
node_tree.links.new(inverter.outputs['Color'], mult.inputs[0])
node_tree.links.new(mult.outputs['Value'], add.inputs[0])
mat.diffuse_color = color

View File

@ -13,252 +13,478 @@
# limitations under the License.
import bpy
from .gltf2_blender_material_utils import make_texture_block
from ..com.gltf2_blender_conversion import texture_transform_gltf_to_blender
from ...io.com.gltf2_io import TextureInfo, MaterialPBRMetallicRoughness
from ..com.gltf2_blender_material_helpers import get_gltf_node_name
from .gltf2_blender_texture import texture
class BlenderPbr():
"""Blender Pbr."""
def __new__(cls, *args, **kwargs):
raise RuntimeError("%s should not be instantiated" % cls)
class MaterialHelper:
"""Helper class. Stores material stuff to be passed around everywhere."""
def __init__(self, gltf, pymat, mat, vertex_color):
self.gltf = gltf
self.pymat = pymat
self.mat = mat
self.node_tree = mat.node_tree
self.vertex_color = vertex_color
if pymat.pbr_metallic_roughness is None:
pymat.pbr_metallic_roughness = \
MaterialPBRMetallicRoughness.from_dict({})
def create(gltf, pypbr, mat_name, vertex_color):
"""Pbr creation."""
engine = bpy.context.scene.render.engine
if engine in ['CYCLES', 'BLENDER_EEVEE']:
BlenderPbr.create_nodetree(gltf, pypbr, mat_name, vertex_color)
def is_opaque(self):
alpha_mode = self.pymat.alpha_mode
return alpha_mode is None or alpha_mode == 'OPAQUE'
def create_nodetree(gltf, pypbr, mat_name, vertex_color, nodetype='principled'):
"""Nodetree creation."""
material = bpy.data.materials[mat_name]
material.use_nodes = True
node_tree = material.node_tree
def needs_emissive(self):
return (
self.pymat.emissive_texture is not None or
(self.pymat.emissive_factor or [0, 0, 0]) != [0, 0, 0]
)
# If there is no diffuse texture, but only a color, wihtout
# vertex_color, we set this color in viewport color
if pypbr.color_type == gltf.SIMPLE and not vertex_color:
# Manage some change in beta version on 20190129
if len(material.diffuse_color) == 3:
material.diffuse_color = pypbr.base_color_factor[:3]
else:
material.diffuse_color = pypbr.base_color_factor
# delete all nodes except output
for node in list(node_tree.nodes):
if not node.type == 'OUTPUT_MATERIAL':
node_tree.nodes.remove(node)
def pbr_metallic_roughness(mh: MaterialHelper):
"""Creates node tree for pbrMetallicRoughness materials."""
pbr_node = mh.node_tree.nodes.new('ShaderNodeBsdfPrincipled')
pbr_node.location = 10, 300
output_node = node_tree.nodes[0]
output_node.location = 1250, 0
# Create material output.
# TODO: when the exporter can understand them, use the emission/alpha
# socket on the Principled node instead
emission_socket, alpha_socket = make_output_nodes(
mh,
location=(250, 260),
shader_socket=pbr_node.outputs[0],
make_emission_socket=mh.needs_emissive(),
make_alpha_socket=not mh.is_opaque(),
)
# create Main node
if nodetype == "principled":
main_node = node_tree.nodes.new('ShaderNodeBsdfPrincipled')
main_node.location = 0, 0
elif nodetype == "unlit":
main_node = node_tree.nodes.new('ShaderNodeEmission')
main_node.location = 750, -300
emission(
mh,
location=(-200, 860),
color_socket=emission_socket,
)
if pypbr.color_type == gltf.SIMPLE:
base_color(
mh,
location=(-200, 380),
color_socket=pbr_node.inputs['Base Color'],
alpha_socket=alpha_socket,
)
if not vertex_color:
metallic_roughness(
mh,
location=(-200, -100),
metallic_socket=pbr_node.inputs['Metallic'],
roughness_socket=pbr_node.inputs['Roughness'],
)
# change input values
main_node.inputs[0].default_value = pypbr.base_color_factor
if nodetype == "principled":
# TODO : currently set metallic & specular in same way
main_node.inputs[5].default_value = pypbr.metallic_factor
main_node.inputs[7].default_value = pypbr.roughness_factor
normal(
mh,
location=(-200, -580),
normal_socket=pbr_node.inputs['Normal'],
)
else:
# Create attribute node to get COLOR_0 data
vertexcolor_node = node_tree.nodes.new('ShaderNodeVertexColor')
vertexcolor_node.layer_name = 'Col'
vertexcolor_node.location = -500, 0
if mh.pymat.occlusion_texture is not None:
node = make_settings_node(mh, location=(610, -1060))
occlusion(
mh,
location=(510, -970),
occlusion_socket=node.inputs['Occlusion'],
)
if nodetype == "principled":
# TODO : currently set metallic & specular in same way
main_node.inputs[5].default_value = pypbr.metallic_factor
main_node.inputs[7].default_value = pypbr.roughness_factor
# links
rgb_node = node_tree.nodes.new('ShaderNodeMixRGB')
rgb_node.blend_type = 'MULTIPLY'
rgb_node.inputs['Fac'].default_value = 1.0
rgb_node.inputs['Color1'].default_value = pypbr.base_color_factor
node_tree.links.new(rgb_node.inputs['Color2'], vertexcolor_node.outputs[0])
node_tree.links.new(main_node.inputs[0], rgb_node.outputs[0])
# These functions each create one piece of the node graph, slotting
# their outputs into the given socket, or setting its default value.
# location is roughly the upper-right corner of where to put nodes.
elif pypbr.color_type == gltf.TEXTURE_FACTOR:
# TODO alpha ?
if vertex_color:
# TODO tree locations
# Create attribute / separate / math nodes
vertexcolor_node = node_tree.nodes.new('ShaderNodeVertexColor')
vertexcolor_node.layer_name = 'Col'
# [Texture] => [Emissive Factor] =>
def emission(mh: MaterialHelper, location, color_socket):
x, y = location
emissive_factor = mh.pymat.emissive_factor or [0, 0, 0]
vc_mult_node = node_tree.nodes.new('ShaderNodeMixRGB')
vc_mult_node.blend_type = 'MULTIPLY'
vc_mult_node.inputs['Fac'].default_value = 1.0
if color_socket is None:
return
# create UV Map / Mapping / Texture nodes / separate & math and combine
text_node = make_texture_block(
gltf,
node_tree,
pypbr.base_color_texture,
location=(-1000, 500),
label='BASE COLOR',
name='baseColorTexture',
)
if mh.pymat.emissive_texture is None:
color_socket.default_value = emissive_factor + [1]
return
mult_node = node_tree.nodes.new('ShaderNodeMixRGB')
mult_node.blend_type = 'MULTIPLY'
mult_node.inputs['Fac'].default_value = 1.0
mult_node.inputs['Color2'].default_value = [
pypbr.base_color_factor[0],
pypbr.base_color_factor[1],
pypbr.base_color_factor[2],
pypbr.base_color_factor[3],
]
# Mix emissive factor
if emissive_factor != [1, 1, 1]:
node = mh.node_tree.nodes.new('ShaderNodeMixRGB')
node.label = 'Emissive Factor'
node.location = x - 140, y
node.blend_type = 'MULTIPLY'
# Outputs
mh.node_tree.links.new(color_socket, node.outputs[0])
# Inputs
node.inputs['Fac'].default_value = 1.0
color_socket = node.inputs['Color1']
node.inputs['Color2'].default_value = emissive_factor + [1]
# Create links
if vertex_color:
node_tree.links.new(vc_mult_node.inputs[2], vertexcolor_node.outputs[0])
node_tree.links.new(vc_mult_node.inputs[1], mult_node.outputs[0])
node_tree.links.new(main_node.inputs[0], vc_mult_node.outputs[0])
x -= 200
else:
node_tree.links.new(main_node.inputs[0], mult_node.outputs[0])
texture(
mh,
tex_info=mh.pymat.emissive_texture,
label='EMISSIVE',
location=(x, y),
color_socket=color_socket,
)
# Common for both mode (non vertex color / vertex color)
node_tree.links.new(mult_node.inputs[1], text_node.outputs[0])
elif pypbr.color_type == gltf.TEXTURE:
# [Texture] => [Mix Colors] => [Color Factor] =>
# [Vertex Color] => [Mix Alphas] => [Alpha Factor] =>
def base_color(
mh: MaterialHelper,
location,
color_socket,
alpha_socket=None,
is_diffuse=False,
):
"""Handle base color (= baseColorTexture * vertexColor * baseColorFactor)."""
x, y = location
pbr = mh.pymat.pbr_metallic_roughness
if not is_diffuse:
base_color_factor = pbr.base_color_factor
base_color_texture = pbr.base_color_texture
else:
# Handle pbrSpecularGlossiness's diffuse with this function too,
# since it's almost exactly the same as base color.
base_color_factor = \
mh.pymat.extensions['KHR_materials_pbrSpecularGlossiness'] \
.get('diffuseFactor', [1, 1, 1, 1])
base_color_texture = \
mh.pymat.extensions['KHR_materials_pbrSpecularGlossiness'] \
.get('diffuseTexture', None)
if base_color_texture is not None:
base_color_texture = TextureInfo.from_dict(base_color_texture)
# TODO alpha ?
if vertex_color:
# Create attribute / separate / math nodes
vertexcolor_node = node_tree.nodes.new('ShaderNodeVertexColor')
vertexcolor_node.layer_name = 'Col'
vertexcolor_node.location = -2000, 250
if base_color_factor is None:
base_color_factor = [1, 1, 1, 1]
vc_mult_node = node_tree.nodes.new('ShaderNodeMixRGB')
vc_mult_node.blend_type = 'MULTIPLY'
vc_mult_node.inputs['Fac'].default_value = 1.0
if base_color_texture is None and not mh.vertex_color:
color_socket.default_value = base_color_factor
if alpha_socket is not None:
alpha_socket.default_value = base_color_factor[3]
return
# create UV Map / Mapping / Texture nodes / separate & math and combine
if vertex_color:
location = -2000, 500
else:
location = -500, 500
text_node = make_texture_block(
gltf,
node_tree,
pypbr.base_color_texture,
location=location,
label='BASE COLOR',
name='baseColorTexture',
)
# Mix in base color factor
needs_color_factor = base_color_factor[:3] != [1, 1, 1]
needs_alpha_factor = base_color_factor[3] != 1.0 and alpha_socket is not None
if needs_color_factor or needs_alpha_factor:
# For now, always create the color factor node because the exporter
# reads the alpha value from here. Can get rid of "or needs_alpha_factor"
# when it learns to understand the alpha socket.
if needs_color_factor or needs_alpha_factor:
node = mh.node_tree.nodes.new('ShaderNodeMixRGB')
node.label = 'Color Factor'
node.location = x - 140, y
node.blend_type = 'MULTIPLY'
# Outputs
mh.node_tree.links.new(color_socket, node.outputs[0])
# Inputs
node.inputs['Fac'].default_value = 1.0
color_socket = node.inputs['Color1']
node.inputs['Color2'].default_value = base_color_factor
# Create links
if vertex_color:
node_tree.links.new(vc_mult_node.inputs[2], vertexcolor_node.outputs[0])
node_tree.links.new(vc_mult_node.inputs[1], text_node.outputs[0])
node_tree.links.new(main_node.inputs[0], vc_mult_node.outputs[0])
if needs_alpha_factor:
node = mh.node_tree.nodes.new('ShaderNodeMath')
node.label = 'Alpha Factor'
node.location = x - 140, y - 200
# Outputs
mh.node_tree.links.new(alpha_socket, node.outputs[0])
# Inputs
node.operation = 'MULTIPLY'
alpha_socket = node.inputs[0]
node.inputs[1].default_value = base_color_factor[3]
else:
node_tree.links.new(main_node.inputs[0], text_node.outputs[0])
x -= 200
if nodetype == 'principled':
# Says metallic, but it means metallic & Roughness values
if pypbr.metallic_type == gltf.SIMPLE:
main_node.inputs[4].default_value = pypbr.metallic_factor
main_node.inputs[7].default_value = pypbr.roughness_factor
# These are where the texture/vertex color node will put its output.
texture_color_socket = color_socket
texture_alpha_socket = alpha_socket
vcolor_color_socket = color_socket
vcolor_alpha_socket = alpha_socket
elif pypbr.metallic_type == gltf.TEXTURE:
# Mix texture and vertex color together
if base_color_texture is not None and mh.vertex_color:
node = mh.node_tree.nodes.new('ShaderNodeMixRGB')
node.label = 'Mix Vertex Color'
node.location = x - 140, y
node.blend_type = 'MULTIPLY'
# Outputs
mh.node_tree.links.new(color_socket, node.outputs[0])
# Inputs
node.inputs['Fac'].default_value = 1.0
texture_color_socket = node.inputs['Color1']
vcolor_color_socket = node.inputs['Color2']
metallic_text = make_texture_block(
gltf,
node_tree,
pypbr.metallic_roughness_texture,
location=(-500, 0),
label='METALLIC ROUGHNESS',
name='metallicRoughnessTexture',
colorspace='NONE',
)
if alpha_socket is not None:
node = mh.node_tree.nodes.new('ShaderNodeMath')
node.label = 'Mix Vertex Alpha'
node.location = x - 140, y - 200
node.operation = 'MULTIPLY'
# Outputs
mh.node_tree.links.new(alpha_socket, node.outputs[0])
# Inputs
texture_alpha_socket = node.inputs[0]
vcolor_alpha_socket = node.inputs[1]
metallic_separate = node_tree.nodes.new('ShaderNodeSeparateRGB')
metallic_separate.location = -250, 0
x -= 200
# links
node_tree.links.new(metallic_separate.inputs[0], metallic_text.outputs[0])
node_tree.links.new(main_node.inputs[4], metallic_separate.outputs[2]) # metallic
node_tree.links.new(main_node.inputs[7], metallic_separate.outputs[1]) # Roughness
# Vertex Color
if mh.vertex_color:
node = mh.node_tree.nodes.new('ShaderNodeVertexColor')
node.layer_name = 'Col'
node.location = x - 250, y - 240
# Outputs
mh.node_tree.links.new(vcolor_color_socket, node.outputs['Color'])
if vcolor_alpha_socket is not None:
mh.node_tree.links.new(vcolor_alpha_socket, node.outputs['Alpha'])
elif pypbr.metallic_type == gltf.TEXTURE_FACTOR:
x -= 280
metallic_text = make_texture_block(
gltf,
node_tree,
pypbr.metallic_roughness_texture,
location=(-1000, 0),
label='METALLIC ROUGHNESS',
name='metallicRoughnessTexture',
colorspace='NONE',
)
# Texture
if base_color_texture is not None:
texture(
mh,
tex_info=base_color_texture,
label='BASE COLOR' if not is_diffuse else 'DIFFUSE',
location=(x, y),
color_socket=texture_color_socket,
alpha_socket=texture_alpha_socket,
)
metallic_separate = node_tree.nodes.new('ShaderNodeSeparateRGB')
metallic_separate.location = -500, 0
metallic_math = node_tree.nodes.new('ShaderNodeMath')
metallic_math.operation = 'MULTIPLY'
metallic_math.inputs[1].default_value = pypbr.metallic_factor
metallic_math.location = -250, 100
# [Texture] => [Separate GB] => [Metal/Rough Factor] =>
def metallic_roughness(mh: MaterialHelper, location, metallic_socket, roughness_socket):
x, y = location
pbr = mh.pymat.pbr_metallic_roughness
metal_factor = pbr.metallic_factor
rough_factor = pbr.roughness_factor
if metal_factor is None:
metal_factor = 1.0
if rough_factor is None:
rough_factor = 1.0
roughness_math = node_tree.nodes.new('ShaderNodeMath')
roughness_math.operation = 'MULTIPLY'
roughness_math.inputs[1].default_value = pypbr.roughness_factor
roughness_math.location = -250, -100
if pbr.metallic_roughness_texture is None:
metallic_socket.default_value = metal_factor
roughness_socket.default_value = rough_factor
return
# links
node_tree.links.new(metallic_separate.inputs[0], metallic_text.outputs[0])
if metal_factor != 1.0 or rough_factor != 1.0:
# Mix metal factor
if metal_factor != 1.0:
node = mh.node_tree.nodes.new('ShaderNodeMath')
node.label = 'Metallic Factor'
node.location = x - 140, y
node.operation = 'MULTIPLY'
# Outputs
mh.node_tree.links.new(metallic_socket, node.outputs[0])
# Inputs
metallic_socket = node.inputs[0]
node.inputs[1].default_value = metal_factor
# metallic
node_tree.links.new(metallic_math.inputs[0], metallic_separate.outputs[2])
node_tree.links.new(main_node.inputs[4], metallic_math.outputs[0])
# Mix rough factor
if rough_factor != 1.0:
node = mh.node_tree.nodes.new('ShaderNodeMath')
node.label = 'Roughness Factor'
node.location = x - 140, y - 200
node.operation = 'MULTIPLY'
# Outputs
mh.node_tree.links.new(roughness_socket, node.outputs[0])
# Inputs
roughness_socket = node.inputs[0]
node.inputs[1].default_value = rough_factor
# roughness
node_tree.links.new(roughness_math.inputs[0], metallic_separate.outputs[1])
node_tree.links.new(main_node.inputs[7], roughness_math.outputs[0])
x -= 200
# link node to output
if nodetype == 'principled':
node_tree.links.new(output_node.inputs[0], main_node.outputs[0])
elif nodetype == 'unlit':
mix = node_tree.nodes.new('ShaderNodeMixShader')
mix.location = 1000, 0
path = node_tree.nodes.new('ShaderNodeLightPath')
path.location = 500, 300
if pypbr.color_type != gltf.SIMPLE:
math = node_tree.nodes.new('ShaderNodeMath')
math.location = 750, 200
math.operation = 'MULTIPLY'
# Separate RGB
node = mh.node_tree.nodes.new('ShaderNodeSeparateRGB')
node.location = x - 150, y - 75
# Outputs
mh.node_tree.links.new(metallic_socket, node.outputs['B'])
mh.node_tree.links.new(roughness_socket, node.outputs['G'])
# Inputs
color_socket = node.inputs[0]
# Set material alpha mode to blend
# This is needed for Eevee
material.blend_method = 'HASHED' # TODO check best result in eevee
x -= 200
transparent = node_tree.nodes.new('ShaderNodeBsdfTransparent')
transparent.location = 750, 0
texture(
mh,
tex_info=pbr.metallic_roughness_texture,
label='METALLIC ROUGHNESS',
location=(x, y),
is_data=True,
color_socket=color_socket,
)
node_tree.links.new(output_node.inputs[0], mix.outputs[0])
node_tree.links.new(mix.inputs[2], main_node.outputs[0])
node_tree.links.new(mix.inputs[1], transparent.outputs[0])
if pypbr.color_type != gltf.SIMPLE:
node_tree.links.new(math.inputs[0], path.outputs[0])
node_tree.links.new(math.inputs[1], text_node.outputs[1])
node_tree.links.new(mix.inputs[0], math.outputs[0])
else:
node_tree.links.new(mix.inputs[0], path.outputs[0])
# [Texture] => [Normal Map] =>
def normal(mh: MaterialHelper, location, normal_socket):
x,y = location
tex_info = mh.pymat.normal_texture
if tex_info is None:
return
# Normal map
node = mh.node_tree.nodes.new('ShaderNodeNormalMap')
node.location = x - 150, y - 40
# Set UVMap
uv_idx = tex_info.tex_coord or 0
try:
uv_idx = tex_info.extensions['KHR_texture_transform']['texCoord']
except Exception:
pass
node.uv_map = 'UVMap' if uv_idx == 0 else 'UVMap.%03d' % uv_idx
# Set strength
scale = tex_info.scale
scale = scale if scale is not None else 1
node.inputs['Strength'].default_value = scale
# Outputs
mh.node_tree.links.new(normal_socket, node.outputs['Normal'])
# Inputs
color_socket = node.inputs['Color']
x -= 200
texture(
mh,
tex_info=tex_info,
label='NORMALMAP',
location=(x, y),
is_data=True,
color_socket=color_socket,
)
# [Texture] => [Separate R] =>
def occlusion(mh: MaterialHelper, location, occlusion_socket):
x, y = location
if mh.pymat.occlusion_texture is None:
return
# Separate RGB
node = mh.node_tree.nodes.new('ShaderNodeSeparateRGB')
node.location = x - 150, y - 75
# Outputs
mh.node_tree.links.new(occlusion_socket, node.outputs['R'])
# Inputs
color_socket = node.inputs[0]
x -= 200
texture(
mh,
tex_info=mh.pymat.occlusion_texture,
label='OCCLUSION',
location=(x, y),
is_data=True,
color_socket=color_socket,
)
# => [Add Emission] => [Mix Alpha] => [Material Output]
def make_output_nodes(
mh: MaterialHelper,
location,
shader_socket,
make_emission_socket,
make_alpha_socket,
):
"""
Creates the Material Output node and connects shader_socket to it.
If requested, it can also create places to hookup the emission/alpha
in between shader_socket and the Output node too.
:return: a pair containing the sockets you should put emission and alpha
in (None if not requested).
"""
x, y = location
emission_socket = None
alpha_socket = None
# Create an Emission node and add it to the shader.
if make_emission_socket:
# Emission
node = mh.node_tree.nodes.new('ShaderNodeEmission')
node.location = x + 50, y + 250
# Inputs
emission_socket = node.inputs[0]
# Outputs
emission_output = node.outputs[0]
# Add
node = mh.node_tree.nodes.new('ShaderNodeAddShader')
node.location = x + 250, y + 160
# Inputs
mh.node_tree.links.new(node.inputs[0], emission_output)
mh.node_tree.links.new(node.inputs[1], shader_socket)
# Outputs
shader_socket = node.outputs[0]
if make_alpha_socket:
x += 200
y += 175
else:
x += 380
y += 125
# Mix with a Transparent BSDF. Mixing factor is the alpha value.
if make_alpha_socket:
# Transparent BSDF
node = mh.node_tree.nodes.new('ShaderNodeBsdfTransparent')
node.location = x + 100, y - 350
# Outputs
transparent_out = node.outputs[0]
# Mix
node = mh.node_tree.nodes.new('ShaderNodeMixShader')
node.location = x + 340, y - 180
# Inputs
alpha_socket = node.inputs[0]
mh.node_tree.links.new(node.inputs[1], transparent_out)
mh.node_tree.links.new(node.inputs[2], shader_socket)
# Outputs
shader_socket = node.outputs[0]
x += 480
y -= 210
# Material output
node = mh.node_tree.nodes.new('ShaderNodeOutputMaterial')
node.location = x + 70, y + 10
# Outputs
mh.node_tree.links.new(node.inputs[0], shader_socket)
return emission_socket, alpha_socket
def make_settings_node(mh, location):
"""
Make a Group node with a hookup for Occlusion. No effect in Blender, but
used to tell the exporter what the occlusion map should be.
"""
node = mh.node_tree.nodes.new('ShaderNodeGroup')
node.node_tree = get_settings_group()
node.location = location
return node
def get_settings_group():
gltf_node_group_name = get_gltf_node_name()
if gltf_node_group_name in bpy.data.node_groups:
gltf_node_group = bpy.data.node_groups[gltf_node_group_name]
else:
# Create a new node group
gltf_node_group = bpy.data.node_groups.new(gltf_node_group_name, 'ShaderNodeTree')
gltf_node_group.inputs.new("NodeSocketFloat", "Occlusion")
gltf_node_group.nodes.new('NodeGroupOutput')
gltf_node_group_input = gltf_node_group.nodes.new('NodeGroupInput')
gltf_node_group_input.location = -200, 0
return gltf_node_group

View File

@ -0,0 +1,152 @@
# Copyright 2018-2019 The glTF-Blender-IO authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import bpy
from .gltf2_blender_image import BlenderImage
from ..com.gltf2_blender_conversion import texture_transform_gltf_to_blender
from io_scene_gltf2.io.com.gltf2_io import Sampler
from io_scene_gltf2.io.com.gltf2_io_debug import print_console
from io_scene_gltf2.io.com.gltf2_io_constants import TextureFilter, TextureWrap
def texture(
mh,
tex_info,
location, # Upper-right corner of the TexImage node
label, # Label for the TexImg node
color_socket,
alpha_socket=None,
is_data=False,
):
"""Creates nodes for a TextureInfo and hooks up the color/alpha outputs."""
x, y = location
# Image Texture
tex_img = mh.node_tree.nodes.new('ShaderNodeTexImage')
tex_img.location = x - 240, y
tex_img.label = label
# Get image
pytexture = mh.gltf.data.textures[tex_info.index]
if pytexture.source is not None:
BlenderImage.create(mh.gltf, pytexture.source)
pyimg = mh.gltf.data.images[pytexture.source]
blender_image_name = pyimg.blender_image_name
if blender_image_name:
tex_img.image = bpy.data.images[blender_image_name]
# Set colorspace for data images
if is_data:
if tex_img.image:
tex_img.image.colorspace_settings.is_data = True
# Set wrapping/filtering
if pytexture.sampler is not None:
pysampler = mh.gltf.data.samplers[pytexture.sampler]
else:
pysampler = Sampler.from_dict({})
set_filtering(tex_img, pysampler)
set_wrap_mode(tex_img, pysampler)
# Outputs
mh.node_tree.links.new(color_socket, tex_img.outputs['Color'])
if alpha_socket is not None:
mh.node_tree.links.new(alpha_socket, tex_img.outputs['Alpha'])
# Inputs
uv_socket = tex_img.inputs[0]
x -= 340
# UV Transform (for KHR_texture_transform)
mapping = mh.node_tree.nodes.new('ShaderNodeMapping')
mapping.location = x - 160, y + 30
mapping.vector_type = 'POINT'
if tex_info.extensions and 'KHR_texture_transform' in tex_info.extensions:
transform = tex_info.extensions['KHR_texture_transform']
transform = texture_transform_gltf_to_blender(transform)
mapping.inputs['Location'].default_value[0] = transform['offset'][0]
mapping.inputs['Location'].default_value[1] = transform['offset'][1]
mapping.inputs['Rotation'].default_value[2] = transform['rotation']
mapping.inputs['Scale'].default_value[0] = transform['scale'][0]
mapping.inputs['Scale'].default_value[1] = transform['scale'][1]
# Outputs
mh.node_tree.links.new(uv_socket, mapping.outputs[0])
# Inputs
uv_socket = mapping.inputs[0]
x -= 260
# UV Map
uv_map = mh.node_tree.nodes.new('ShaderNodeUVMap')
uv_map.location = x - 160, y - 70
# Get UVMap
uv_idx = tex_info.tex_coord or 0
try:
uv_idx = tex_info.extensions['KHR_texture_transform']['texCoord']
except Exception:
pass
uv_map.uv_map = 'UVMap' if uv_idx == 0 else 'UVMap.%03d' % uv_idx
# Outputs
mh.node_tree.links.new(uv_socket, uv_map.outputs[0])
def set_filtering(tex_img, pysampler):
"""Set the filtering/interpolation on an Image Texture from the glTf sampler."""
minf = pysampler.min_filter
magf = pysampler.mag_filter
# Ignore mipmapping
if minf in [TextureFilter.NearestMipmapNearest, TextureFilter.NearestMipmapLinear]:
minf = TextureFilter.Nearest
elif minf in [TextureFilter.LinearMipmapNearest, TextureFilter.LinearMipmapLinear]:
minf = TextureFilter.Linear
# If both are nearest or the only specified one was nearest, use nearest.
if (minf, magf) in [
(TextureFilter.Nearest, TextureFilter.Nearest),
(TextureFilter.Nearest, None),
(None, TextureFilter.Nearest),
]:
tex_img.interpolation = 'Closest'
else:
tex_img.interpolation = 'Linear'
def set_wrap_mode(tex_img, pysampler):
"""Set the extension on an Image Texture node from the pysampler."""
wrap_s = pysampler.wrap_s
wrap_t = pysampler.wrap_t
if wrap_s is None:
wrap_s = TextureWrap.Repeat
if wrap_t is None:
wrap_t = TextureWrap.Repeat
# The extension property on the Image Texture node can only handle the case
# where both directions are the same and are either REPEAT or CLAMP_TO_EDGE.
if (wrap_s, wrap_t) == (TextureWrap.Repeat, TextureWrap.Repeat):
extension = TextureWrap.Repeat
elif (wrap_s, wrap_t) == (TextureWrap.ClampToEdge, TextureWrap.ClampToEdge):
extension = TextureWrap.ClampToEdge
else:
print_console('WARNING',
'texture wrap mode unsupported: (%s, %s)' % (wrap_name(wrap_s), wrap_name(wrap_t)),
)
# Default to repeat
extension = TextureWrap.Repeat
if extension == TextureWrap.Repeat:
tex_img.extension = 'REPEAT'
elif extension == TextureWrap.ClampToEdge:
tex_img.extension = 'EXTEND'
def wrap_name(wrap):
if wrap == TextureWrap.ClampToEdge: return 'CLAMP_TO_EDGE'
if wrap == TextureWrap.MirroredRepeat: return 'MIRRORED_REPEAT'
if wrap == TextureWrap.Repeat: return 'REPEAT'
return 'UNKNOWN (%s)' % wrap

View File

@ -40,10 +40,6 @@ class glTFImporter():
self.log = log.logger
self.log_handler = log.hdlr
self.SIMPLE = 1
self.TEXTURE = 2
self.TEXTURE_FACTOR = 3
# TODO: move to a com place?
self.extensions_managed = [
'KHR_materials_pbrSpecularGlossiness',