File headers: use SPDX license identifiers
[blender-addons.git] / io_scene_gltf2 / blender / imp / gltf2_blender_pbrMetallicRoughness.py
blob1780998c0aa67591d1395523350da585e747b2db
1 # SPDX-License-Identifier: Apache-2.0
2 # Copyright 2018-2021 The glTF-Blender-IO authors.
4 import bpy
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
12 class MaterialHelper:
13 """Helper class. Stores material stuff to be passed around everywhere."""
14 def __init__(self, gltf, pymat, mat, vertex_color):
15 self.gltf = gltf
16 self.pymat = pymat
17 self.mat = mat
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({})
24 def is_opaque(self):
25 alpha_mode = self.pymat.alpha_mode
26 return alpha_mode is None or alpha_mode == 'OPAQUE'
28 def needs_emissive(self):
29 return (
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
40 make_output_nodes(
41 mh,
42 location=(250, 260),
43 shader_socket=pbr_node.outputs[0],
44 make_emission_socket=False,
45 make_alpha_socket=False,
48 locs = calc_locations(mh)
50 emission(
51 mh,
52 location=locs['emission'],
53 color_socket=pbr_node.inputs['Emission'],
54 strength_socket=pbr_node.inputs['Emission Strength'],
57 base_color(
58 mh,
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,
64 metallic_roughness(
65 mh,
66 location=locs['metallic_roughness'],
67 metallic_socket=pbr_node.inputs['Metallic'],
68 roughness_socket=pbr_node.inputs['Roughness'],
71 normal(
72 mh,
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
80 node.width = 180
81 occlusion(
82 mh,
83 location=locs['occlusion'],
84 occlusion_socket=node.inputs['Occlusion'],
87 clearcoat(
88 mh,
89 location=locs['clearcoat'],
90 clearcoat_socket=pbr_node.inputs['Clearcoat'],
93 clearcoat_roughness(
94 mh,
95 location=locs['clearcoat_roughness'],
96 roughness_socket=pbr_node.inputs['Clearcoat Roughness'],
99 clearcoat_normal(
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
109 x = -200
110 y = 0
111 height = 460 # height of each block
112 locs = {}
114 try:
115 clearcoat_ext = mh.pymat.extensions['KHR_materials_clearcoat']
116 except Exception:
117 clearcoat_ext = {}
119 locs['base_color'] = (x, y)
120 if mh.pymat.pbr_metallic_roughness.base_color_texture is not None or mh.vertex_color:
121 y -= height
122 locs['metallic_roughness'] = (x, y)
123 if mh.pymat.pbr_metallic_roughness.metallic_roughness_texture is not None:
124 y -= height
125 locs['clearcoat'] = (x, y)
126 if 'clearcoatTexture' in clearcoat_ext:
127 y -= height
128 locs['clearcoat_roughness'] = (x, y)
129 if 'clearcoatRoughnessTexture' in clearcoat_ext:
130 y -= height
131 locs['emission'] = (x, y)
132 if mh.pymat.emissive_texture is not None:
133 y -= height
134 locs['normal'] = (x, y)
135 if mh.pymat.normal_texture is not None:
136 y -= height
137 locs['clearcoat_normal'] = (x, y)
138 if 'clearcoatNormalTexture' in clearcoat_ext:
139 y -= height
140 locs['occlusion'] = (x, y)
141 if mh.pymat.occlusion_texture is not None:
142 y -= height
144 # Center things
145 total_height = -y
146 y_offset = total_height / 2 - 20
147 for key in locs:
148 x, y = locs[key]
149 locs[key] = (x, y + y_offset)
151 return locs
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):
161 x, y = location
162 emissive_factor = mh.pymat.emissive_factor or [0, 0, 0]
164 if color_socket is None:
165 return
167 if mh.pymat.emissive_texture is None:
168 color_socket.default_value = emissive_factor + [1]
169 return
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
177 else:
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'
183 # Outputs
184 mh.node_tree.links.new(color_socket, node.outputs[0])
185 # Inputs
186 node.inputs['Fac'].default_value = 1.0
187 color_socket = node.inputs['Color1']
188 node.inputs['Color2'].default_value = emissive_factor + [1]
190 x -= 200
192 texture(
194 tex_info=mh.pymat.emissive_texture,
195 label='EMISSIVE',
196 location=(x, y),
197 color_socket=color_socket,
201 # [Texture] => [Mix Colors] => [Color Factor] =>
202 # [Vertex Color] => [Mix Alphas] => [Alpha Factor] =>
203 def base_color(
204 mh: MaterialHelper,
205 location,
206 color_socket,
207 alpha_socket=None,
208 is_diffuse=False,
210 """Handle base color (= baseColorTexture * vertexColor * baseColorFactor)."""
211 x, y = location
212 pbr = mh.pymat.pbr_metallic_roughness
213 if not is_diffuse:
214 base_color_factor = pbr.base_color_factor
215 base_color_texture = pbr.base_color_texture
216 else:
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]
235 return
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'
246 # Outputs
247 mh.node_tree.links.new(color_socket, node.outputs[0])
248 # Inputs
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
257 # Outputs
258 mh.node_tree.links.new(alpha_socket, node.outputs[0])
259 # Inputs
260 node.operation = 'MULTIPLY'
261 alpha_socket = node.inputs[0]
262 node.inputs[1].default_value = base_color_factor[3]
264 x -= 200
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'
278 # Outputs
279 mh.node_tree.links.new(color_socket, node.outputs[0])
280 # Inputs
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'
290 # Outputs
291 mh.node_tree.links.new(alpha_socket, node.outputs[0])
292 # Inputs
293 texture_alpha_socket = node.inputs[0]
294 vcolor_alpha_socket = node.inputs[1]
296 x -= 200
298 # Vertex Color
299 if mh.vertex_color:
300 node = mh.node_tree.nodes.new('ShaderNodeVertexColor')
301 node.layer_name = 'Col'
302 node.location = x - 250, y - 240
303 # Outputs
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'])
308 x -= 280
310 # Texture
311 if base_color_texture is not None:
312 texture(
314 tex_info=base_color_texture,
315 label='BASE COLOR' if not is_diffuse else 'DIFFUSE',
316 location=(x, y),
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):
324 x, y = location
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:
329 metal_factor = 1.0
330 if rough_factor is None:
331 rough_factor = 1.0
333 if pbr.metallic_roughness_texture is None:
334 metallic_socket.default_value = metal_factor
335 roughness_socket.default_value = rough_factor
336 return
338 if metal_factor != 1.0 or rough_factor != 1.0:
339 # Mix metal factor
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'
345 # Outputs
346 mh.node_tree.links.new(metallic_socket, node.outputs[0])
347 # Inputs
348 metallic_socket = node.inputs[0]
349 node.inputs[1].default_value = metal_factor
351 # Mix rough 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'
357 # Outputs
358 mh.node_tree.links.new(roughness_socket, node.outputs[0])
359 # Inputs
360 roughness_socket = node.inputs[0]
361 node.inputs[1].default_value = rough_factor
363 x -= 200
365 # Separate RGB
366 node = mh.node_tree.nodes.new('ShaderNodeSeparateRGB')
367 node.location = x - 150, y - 75
368 # Outputs
369 mh.node_tree.links.new(metallic_socket, node.outputs['B'])
370 mh.node_tree.links.new(roughness_socket, node.outputs['G'])
371 # Inputs
372 color_socket = node.inputs[0]
374 x -= 200
376 texture(
378 tex_info=pbr.metallic_roughness_texture,
379 label='METALLIC ROUGHNESS',
380 location=(x, y),
381 is_data=True,
382 color_socket=color_socket,
386 # [Texture] => [Normal Map] =>
387 def normal(mh: MaterialHelper, location, normal_socket):
388 x,y = location
389 tex_info = mh.pymat.normal_texture
391 if tex_info is None:
392 return
394 # Normal map
395 node = mh.node_tree.nodes.new('ShaderNodeNormalMap')
396 node.location = x - 150, y - 40
397 # Set UVMap
398 uv_idx = tex_info.tex_coord or 0
399 try:
400 uv_idx = tex_info.extensions['KHR_texture_transform']['texCoord']
401 except Exception:
402 pass
403 node.uv_map = 'UVMap' if uv_idx == 0 else 'UVMap.%03d' % uv_idx
404 # Set strength
405 scale = tex_info.scale
406 scale = scale if scale is not None else 1
407 node.inputs['Strength'].default_value = scale
408 # Outputs
409 mh.node_tree.links.new(normal_socket, node.outputs['Normal'])
410 # Inputs
411 color_socket = node.inputs['Color']
413 x -= 200
415 texture(
417 tex_info=tex_info,
418 label='NORMALMAP',
419 location=(x, y),
420 is_data=True,
421 color_socket=color_socket,
425 # [Texture] => [Separate R] => [Mix Strength] =>
426 def occlusion(mh: MaterialHelper, location, occlusion_socket):
427 x, y = location
429 if mh.pymat.occlusion_texture is None:
430 return
432 strength = mh.pymat.occlusion_texture.strength
433 if strength is None: strength = 1.0
434 if strength != 1.0:
435 # Mix with white
436 node = mh.node_tree.nodes.new('ShaderNodeMixRGB')
437 node.label = 'Occlusion Strength'
438 node.location = x - 140, y
439 node.blend_type = 'MIX'
440 # Outputs
441 mh.node_tree.links.new(occlusion_socket, node.outputs[0])
442 # Inputs
443 node.inputs['Fac'].default_value = strength
444 node.inputs['Color1'].default_value = [1, 1, 1, 1]
445 occlusion_socket = node.inputs['Color2']
447 x -= 200
449 # Separate RGB
450 node = mh.node_tree.nodes.new('ShaderNodeSeparateRGB')
451 node.location = x - 150, y - 75
452 # Outputs
453 mh.node_tree.links.new(occlusion_socket, node.outputs['R'])
454 # Inputs
455 color_socket = node.inputs[0]
457 x -= 200
459 texture(
461 tex_info=mh.pymat.occlusion_texture,
462 label='OCCLUSION',
463 location=(x, y),
464 is_data=True,
465 color_socket=color_socket,
469 # => [Add Emission] => [Mix Alpha] => [Material Output]
470 def make_output_nodes(
471 mh: MaterialHelper,
472 location,
473 shader_socket,
474 make_emission_socket,
475 make_alpha_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).
485 x, y = location
486 emission_socket = None
487 alpha_socket = None
489 # Create an Emission node and add it to the shader.
490 if make_emission_socket:
491 # Emission
492 node = mh.node_tree.nodes.new('ShaderNodeEmission')
493 node.location = x + 50, y + 250
494 # Inputs
495 emission_socket = node.inputs[0]
496 # Outputs
497 emission_output = node.outputs[0]
499 # Add
500 node = mh.node_tree.nodes.new('ShaderNodeAddShader')
501 node.location = x + 250, y + 160
502 # Inputs
503 mh.node_tree.links.new(node.inputs[0], emission_output)
504 mh.node_tree.links.new(node.inputs[1], shader_socket)
505 # Outputs
506 shader_socket = node.outputs[0]
508 if make_alpha_socket:
509 x += 200
510 y += 175
511 else:
512 x += 380
513 y += 125
515 # Mix with a Transparent BSDF. Mixing factor is the alpha value.
516 if make_alpha_socket:
517 # Transparent BSDF
518 node = mh.node_tree.nodes.new('ShaderNodeBsdfTransparent')
519 node.location = x + 100, y - 350
520 # Outputs
521 transparent_out = node.outputs[0]
523 # Mix
524 node = mh.node_tree.nodes.new('ShaderNodeMixShader')
525 node.location = x + 340, y - 180
526 # Inputs
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)
530 # Outputs
531 shader_socket = node.outputs[0]
534 x += 480
535 y -= 210
537 # Material output
538 node = mh.node_tree.nodes.new('ShaderNodeOutputMaterial')
539 node.location = x + 70, y + 10
540 # Outputs
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()
553 return node
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]
560 else:
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