1 # SPDX-License-Identifier: Apache-2.0
2 # Copyright 2018-2021 The glTF-Blender-IO authors.
5 from ...io
.com
.gltf2_io
import TextureInfo
, MaterialPBRMetallicRoughness
6 from ..com
.gltf2_blender_material_helpers
import get_gltf_node_name
7 from .gltf2_blender_texture
import texture
8 from .gltf2_blender_KHR_materials_clearcoat
import \
9 clearcoat
, clearcoat_roughness
, clearcoat_normal
13 """Helper class. Stores material stuff to be passed around everywhere."""
14 def __init__(self
, gltf
, pymat
, mat
, vertex_color
):
18 self
.node_tree
= mat
.node_tree
19 self
.vertex_color
= vertex_color
20 if pymat
.pbr_metallic_roughness
is None:
21 pymat
.pbr_metallic_roughness
= \
22 MaterialPBRMetallicRoughness
.from_dict({})
25 alpha_mode
= self
.pymat
.alpha_mode
26 return alpha_mode
is None or alpha_mode
== 'OPAQUE'
28 def needs_emissive(self
):
30 self
.pymat
.emissive_texture
is not None or
31 (self
.pymat
.emissive_factor
or [0, 0, 0]) != [0, 0, 0]
35 def pbr_metallic_roughness(mh
: MaterialHelper
):
36 """Creates node tree for pbrMetallicRoughness materials."""
37 pbr_node
= mh
.node_tree
.nodes
.new('ShaderNodeBsdfPrincipled')
38 pbr_node
.location
= 10, 300
43 shader_socket
=pbr_node
.outputs
[0],
44 make_emission_socket
=False,
45 make_alpha_socket
=False,
48 locs
= calc_locations(mh
)
52 location
=locs
['emission'],
53 color_socket
=pbr_node
.inputs
['Emission'],
54 strength_socket
=pbr_node
.inputs
['Emission Strength'],
59 location
=locs
['base_color'],
60 color_socket
=pbr_node
.inputs
['Base Color'],
61 alpha_socket
=pbr_node
.inputs
['Alpha'] if not mh
.is_opaque() else None,
66 location
=locs
['metallic_roughness'],
67 metallic_socket
=pbr_node
.inputs
['Metallic'],
68 roughness_socket
=pbr_node
.inputs
['Roughness'],
73 location
=locs
['normal'],
74 normal_socket
=pbr_node
.inputs
['Normal'],
77 if mh
.pymat
.occlusion_texture
is not None:
78 node
= make_settings_node(mh
)
79 node
.location
= 40, -370
83 location
=locs
['occlusion'],
84 occlusion_socket
=node
.inputs
['Occlusion'],
89 location
=locs
['clearcoat'],
90 clearcoat_socket
=pbr_node
.inputs
['Clearcoat'],
95 location
=locs
['clearcoat_roughness'],
96 roughness_socket
=pbr_node
.inputs
['Clearcoat Roughness'],
101 location
=locs
['clearcoat_normal'],
102 normal_socket
=pbr_node
.inputs
['Clearcoat Normal'],
106 def calc_locations(mh
):
107 """Calculate locations to place each bit of the node graph at."""
108 # Lay the blocks out top-to-bottom, aligned on the right
111 height
= 460 # height of each block
115 clearcoat_ext
= mh
.pymat
.extensions
['KHR_materials_clearcoat']
119 locs
['base_color'] = (x
, y
)
120 if mh
.pymat
.pbr_metallic_roughness
.base_color_texture
is not None or mh
.vertex_color
:
122 locs
['metallic_roughness'] = (x
, y
)
123 if mh
.pymat
.pbr_metallic_roughness
.metallic_roughness_texture
is not None:
125 locs
['clearcoat'] = (x
, y
)
126 if 'clearcoatTexture' in clearcoat_ext
:
128 locs
['clearcoat_roughness'] = (x
, y
)
129 if 'clearcoatRoughnessTexture' in clearcoat_ext
:
131 locs
['emission'] = (x
, y
)
132 if mh
.pymat
.emissive_texture
is not None:
134 locs
['normal'] = (x
, y
)
135 if mh
.pymat
.normal_texture
is not None:
137 locs
['clearcoat_normal'] = (x
, y
)
138 if 'clearcoatNormalTexture' in clearcoat_ext
:
140 locs
['occlusion'] = (x
, y
)
141 if mh
.pymat
.occlusion_texture
is not None:
146 y_offset
= total_height
/ 2 - 20
149 locs
[key
] = (x
, y
+ y_offset
)
154 # These functions each create one piece of the node graph, slotting
155 # their outputs into the given socket, or setting its default value.
156 # location is roughly the upper-right corner of where to put nodes.
159 # [Texture] => [Emissive Factor] =>
160 def emission(mh
: MaterialHelper
, location
, color_socket
, strength_socket
=None):
162 emissive_factor
= mh
.pymat
.emissive_factor
or [0, 0, 0]
164 if color_socket
is None:
167 if mh
.pymat
.emissive_texture
is None:
168 color_socket
.default_value
= emissive_factor
+ [1]
171 # Put grayscale emissive factors into the Emission Strength
172 e0
, e1
, e2
= emissive_factor
173 if strength_socket
and e0
== e1
== e2
:
174 strength_socket
.default_value
= e0
176 # Otherwise, use a multiply node for it
178 if emissive_factor
!= [1, 1, 1]:
179 node
= mh
.node_tree
.nodes
.new('ShaderNodeMixRGB')
180 node
.label
= 'Emissive Factor'
181 node
.location
= x
- 140, y
182 node
.blend_type
= 'MULTIPLY'
184 mh
.node_tree
.links
.new(color_socket
, node
.outputs
[0])
186 node
.inputs
['Fac'].default_value
= 1.0
187 color_socket
= node
.inputs
['Color1']
188 node
.inputs
['Color2'].default_value
= emissive_factor
+ [1]
194 tex_info
=mh
.pymat
.emissive_texture
,
197 color_socket
=color_socket
,
201 # [Texture] => [Mix Colors] => [Color Factor] =>
202 # [Vertex Color] => [Mix Alphas] => [Alpha Factor] =>
210 """Handle base color (= baseColorTexture * vertexColor * baseColorFactor)."""
212 pbr
= mh
.pymat
.pbr_metallic_roughness
214 base_color_factor
= pbr
.base_color_factor
215 base_color_texture
= pbr
.base_color_texture
217 # Handle pbrSpecularGlossiness's diffuse with this function too,
218 # since it's almost exactly the same as base color.
219 base_color_factor
= \
220 mh
.pymat
.extensions
['KHR_materials_pbrSpecularGlossiness'] \
221 .get('diffuseFactor', [1, 1, 1, 1])
222 base_color_texture
= \
223 mh
.pymat
.extensions
['KHR_materials_pbrSpecularGlossiness'] \
224 .get('diffuseTexture', None)
225 if base_color_texture
is not None:
226 base_color_texture
= TextureInfo
.from_dict(base_color_texture
)
228 if base_color_factor
is None:
229 base_color_factor
= [1, 1, 1, 1]
231 if base_color_texture
is None and not mh
.vertex_color
:
232 color_socket
.default_value
= base_color_factor
[:3] + [1]
233 if alpha_socket
is not None:
234 alpha_socket
.default_value
= base_color_factor
[3]
237 # Mix in base color factor
238 needs_color_factor
= base_color_factor
[:3] != [1, 1, 1]
239 needs_alpha_factor
= base_color_factor
[3] != 1.0 and alpha_socket
is not None
240 if needs_color_factor
or needs_alpha_factor
:
241 if needs_color_factor
:
242 node
= mh
.node_tree
.nodes
.new('ShaderNodeMixRGB')
243 node
.label
= 'Color Factor'
244 node
.location
= x
- 140, y
245 node
.blend_type
= 'MULTIPLY'
247 mh
.node_tree
.links
.new(color_socket
, node
.outputs
[0])
249 node
.inputs
['Fac'].default_value
= 1.0
250 color_socket
= node
.inputs
['Color1']
251 node
.inputs
['Color2'].default_value
= base_color_factor
[:3] + [1]
253 if needs_alpha_factor
:
254 node
= mh
.node_tree
.nodes
.new('ShaderNodeMath')
255 node
.label
= 'Alpha Factor'
256 node
.location
= x
- 140, y
- 200
258 mh
.node_tree
.links
.new(alpha_socket
, node
.outputs
[0])
260 node
.operation
= 'MULTIPLY'
261 alpha_socket
= node
.inputs
[0]
262 node
.inputs
[1].default_value
= base_color_factor
[3]
266 # These are where the texture/vertex color node will put its output.
267 texture_color_socket
= color_socket
268 texture_alpha_socket
= alpha_socket
269 vcolor_color_socket
= color_socket
270 vcolor_alpha_socket
= alpha_socket
272 # Mix texture and vertex color together
273 if base_color_texture
is not None and mh
.vertex_color
:
274 node
= mh
.node_tree
.nodes
.new('ShaderNodeMixRGB')
275 node
.label
= 'Mix Vertex Color'
276 node
.location
= x
- 140, y
277 node
.blend_type
= 'MULTIPLY'
279 mh
.node_tree
.links
.new(color_socket
, node
.outputs
[0])
281 node
.inputs
['Fac'].default_value
= 1.0
282 texture_color_socket
= node
.inputs
['Color1']
283 vcolor_color_socket
= node
.inputs
['Color2']
285 if alpha_socket
is not None:
286 node
= mh
.node_tree
.nodes
.new('ShaderNodeMath')
287 node
.label
= 'Mix Vertex Alpha'
288 node
.location
= x
- 140, y
- 200
289 node
.operation
= 'MULTIPLY'
291 mh
.node_tree
.links
.new(alpha_socket
, node
.outputs
[0])
293 texture_alpha_socket
= node
.inputs
[0]
294 vcolor_alpha_socket
= node
.inputs
[1]
300 node
= mh
.node_tree
.nodes
.new('ShaderNodeVertexColor')
301 node
.layer_name
= 'Col'
302 node
.location
= x
- 250, y
- 240
304 mh
.node_tree
.links
.new(vcolor_color_socket
, node
.outputs
['Color'])
305 if vcolor_alpha_socket
is not None:
306 mh
.node_tree
.links
.new(vcolor_alpha_socket
, node
.outputs
['Alpha'])
311 if base_color_texture
is not None:
314 tex_info
=base_color_texture
,
315 label
='BASE COLOR' if not is_diffuse
else 'DIFFUSE',
317 color_socket
=texture_color_socket
,
318 alpha_socket
=texture_alpha_socket
,
322 # [Texture] => [Separate GB] => [Metal/Rough Factor] =>
323 def metallic_roughness(mh
: MaterialHelper
, location
, metallic_socket
, roughness_socket
):
325 pbr
= mh
.pymat
.pbr_metallic_roughness
326 metal_factor
= pbr
.metallic_factor
327 rough_factor
= pbr
.roughness_factor
328 if metal_factor
is None:
330 if rough_factor
is None:
333 if pbr
.metallic_roughness_texture
is None:
334 metallic_socket
.default_value
= metal_factor
335 roughness_socket
.default_value
= rough_factor
338 if metal_factor
!= 1.0 or rough_factor
!= 1.0:
340 if metal_factor
!= 1.0:
341 node
= mh
.node_tree
.nodes
.new('ShaderNodeMath')
342 node
.label
= 'Metallic Factor'
343 node
.location
= x
- 140, y
344 node
.operation
= 'MULTIPLY'
346 mh
.node_tree
.links
.new(metallic_socket
, node
.outputs
[0])
348 metallic_socket
= node
.inputs
[0]
349 node
.inputs
[1].default_value
= metal_factor
352 if rough_factor
!= 1.0:
353 node
= mh
.node_tree
.nodes
.new('ShaderNodeMath')
354 node
.label
= 'Roughness Factor'
355 node
.location
= x
- 140, y
- 200
356 node
.operation
= 'MULTIPLY'
358 mh
.node_tree
.links
.new(roughness_socket
, node
.outputs
[0])
360 roughness_socket
= node
.inputs
[0]
361 node
.inputs
[1].default_value
= rough_factor
366 node
= mh
.node_tree
.nodes
.new('ShaderNodeSeparateRGB')
367 node
.location
= x
- 150, y
- 75
369 mh
.node_tree
.links
.new(metallic_socket
, node
.outputs
['B'])
370 mh
.node_tree
.links
.new(roughness_socket
, node
.outputs
['G'])
372 color_socket
= node
.inputs
[0]
378 tex_info
=pbr
.metallic_roughness_texture
,
379 label
='METALLIC ROUGHNESS',
382 color_socket
=color_socket
,
386 # [Texture] => [Normal Map] =>
387 def normal(mh
: MaterialHelper
, location
, normal_socket
):
389 tex_info
= mh
.pymat
.normal_texture
395 node
= mh
.node_tree
.nodes
.new('ShaderNodeNormalMap')
396 node
.location
= x
- 150, y
- 40
398 uv_idx
= tex_info
.tex_coord
or 0
400 uv_idx
= tex_info
.extensions
['KHR_texture_transform']['texCoord']
403 node
.uv_map
= 'UVMap' if uv_idx
== 0 else 'UVMap.%03d' % uv_idx
405 scale
= tex_info
.scale
406 scale
= scale
if scale
is not None else 1
407 node
.inputs
['Strength'].default_value
= scale
409 mh
.node_tree
.links
.new(normal_socket
, node
.outputs
['Normal'])
411 color_socket
= node
.inputs
['Color']
421 color_socket
=color_socket
,
425 # [Texture] => [Separate R] => [Mix Strength] =>
426 def occlusion(mh
: MaterialHelper
, location
, occlusion_socket
):
429 if mh
.pymat
.occlusion_texture
is None:
432 strength
= mh
.pymat
.occlusion_texture
.strength
433 if strength
is None: strength
= 1.0
436 node
= mh
.node_tree
.nodes
.new('ShaderNodeMixRGB')
437 node
.label
= 'Occlusion Strength'
438 node
.location
= x
- 140, y
439 node
.blend_type
= 'MIX'
441 mh
.node_tree
.links
.new(occlusion_socket
, node
.outputs
[0])
443 node
.inputs
['Fac'].default_value
= strength
444 node
.inputs
['Color1'].default_value
= [1, 1, 1, 1]
445 occlusion_socket
= node
.inputs
['Color2']
450 node
= mh
.node_tree
.nodes
.new('ShaderNodeSeparateRGB')
451 node
.location
= x
- 150, y
- 75
453 mh
.node_tree
.links
.new(occlusion_socket
, node
.outputs
['R'])
455 color_socket
= node
.inputs
[0]
461 tex_info
=mh
.pymat
.occlusion_texture
,
465 color_socket
=color_socket
,
469 # => [Add Emission] => [Mix Alpha] => [Material Output]
470 def make_output_nodes(
474 make_emission_socket
,
478 Creates the Material Output node and connects shader_socket to it.
479 If requested, it can also create places to hookup the emission/alpha
480 in between shader_socket and the Output node too.
482 :return: a pair containing the sockets you should put emission and alpha
483 in (None if not requested).
486 emission_socket
= None
489 # Create an Emission node and add it to the shader.
490 if make_emission_socket
:
492 node
= mh
.node_tree
.nodes
.new('ShaderNodeEmission')
493 node
.location
= x
+ 50, y
+ 250
495 emission_socket
= node
.inputs
[0]
497 emission_output
= node
.outputs
[0]
500 node
= mh
.node_tree
.nodes
.new('ShaderNodeAddShader')
501 node
.location
= x
+ 250, y
+ 160
503 mh
.node_tree
.links
.new(node
.inputs
[0], emission_output
)
504 mh
.node_tree
.links
.new(node
.inputs
[1], shader_socket
)
506 shader_socket
= node
.outputs
[0]
508 if make_alpha_socket
:
515 # Mix with a Transparent BSDF. Mixing factor is the alpha value.
516 if make_alpha_socket
:
518 node
= mh
.node_tree
.nodes
.new('ShaderNodeBsdfTransparent')
519 node
.location
= x
+ 100, y
- 350
521 transparent_out
= node
.outputs
[0]
524 node
= mh
.node_tree
.nodes
.new('ShaderNodeMixShader')
525 node
.location
= x
+ 340, y
- 180
527 alpha_socket
= node
.inputs
[0]
528 mh
.node_tree
.links
.new(node
.inputs
[1], transparent_out
)
529 mh
.node_tree
.links
.new(node
.inputs
[2], shader_socket
)
531 shader_socket
= node
.outputs
[0]
538 node
= mh
.node_tree
.nodes
.new('ShaderNodeOutputMaterial')
539 node
.location
= x
+ 70, y
+ 10
541 mh
.node_tree
.links
.new(node
.inputs
[0], shader_socket
)
543 return emission_socket
, alpha_socket
546 def make_settings_node(mh
):
548 Make a Group node with a hookup for Occlusion. No effect in Blender, but
549 used to tell the exporter what the occlusion map should be.
551 node
= mh
.node_tree
.nodes
.new('ShaderNodeGroup')
552 node
.node_tree
= get_settings_group()
556 def get_settings_group():
557 gltf_node_group_name
= get_gltf_node_name()
558 if gltf_node_group_name
in bpy
.data
.node_groups
:
559 gltf_node_group
= bpy
.data
.node_groups
[gltf_node_group_name
]
561 # Create a new node group
562 gltf_node_group
= bpy
.data
.node_groups
.new(gltf_node_group_name
, 'ShaderNodeTree')
563 gltf_node_group
.inputs
.new("NodeSocketFloat", "Occlusion")
564 gltf_node_group
.nodes
.new('NodeGroupOutput')
565 gltf_node_group_input
= gltf_node_group
.nodes
.new('NodeGroupInput')
566 gltf_node_group_input
.location
= -200, 0
567 return gltf_node_group