OpenMotionLab/MotionGPT
118
1"""Material properties, conforming to the glTF 2.0 standards as specified in2https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#reference-material3and4https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness5 6Author: Matthew Matl7"""8import abc9import numpy as np10import six11 12from .constants import TexFlags13from .utils import format_color_vector, format_texture_source14from .texture import Texture15 16 17@six.add_metaclass(abc.ABCMeta)18class Material(object):19 """Base for standard glTF 2.0 materials.20 21 Parameters22 ----------23 name : str, optional24 The user-defined name of this object.25 normalTexture : (n,n,3) float or :class:`Texture`, optional26 A tangent space normal map. The texture contains RGB components in27 linear space. Each texel represents the XYZ components of a normal28 vector in tangent space. Red [0 to 255] maps to X [-1 to 1]. Green29 [0 to 255] maps to Y [-1 to 1]. Blue [128 to 255] maps to Z30 [1/255 to 1]. The normal vectors use OpenGL conventions where +X is31 right and +Y is up. +Z points toward the viewer.32 occlusionTexture : (n,n,1) float or :class:`Texture`, optional33 The occlusion map texture. The occlusion values are sampled from the R34 channel. Higher values indicate areas that should receive full indirect35 lighting and lower values indicate no indirect lighting. These values36 are linear. If other channels are present (GBA), they are ignored for37 occlusion calculations.38 emissiveTexture : (n,n,3) float or :class:`Texture`, optional39 The emissive map controls the color and intensity of the light being40 emitted by the material. This texture contains RGB components in sRGB41 color space. If a fourth component (A) is present, it is ignored.42 emissiveFactor : (3,) float, optional43 The RGB components of the emissive color of the material. These values44 are linear. If an emissiveTexture is specified, this value is45 multiplied with the texel values.46 alphaMode : str, optional47 The material's alpha rendering mode enumeration specifying the48 interpretation of the alpha value of the main factor and texture.49 Allowed Values:50 51 - `"OPAQUE"` The alpha value is ignored and the rendered output is52 fully opaque.53 - `"MASK"` The rendered output is either fully opaque or fully54 transparent depending on the alpha value and the specified alpha55 cutoff value.56 - `"BLEND"` The alpha value is used to composite the source and57 destination areas. The rendered output is combined with the58 background using the normal painting operation (i.e. the Porter59 and Duff over operator).60 61 alphaCutoff : float, optional62 Specifies the cutoff threshold when in MASK mode. If the alpha value is63 greater than or equal to this value then it is rendered as fully64 opaque, otherwise, it is rendered as fully transparent.65 A value greater than 1.0 will render the entire material as fully66 transparent. This value is ignored for other modes.67 doubleSided : bool, optional68 Specifies whether the material is double sided. When this value is69 false, back-face culling is enabled. When this value is true,70 back-face culling is disabled and double sided lighting is enabled.71 smooth : bool, optional72 If True, the material is rendered smoothly by using only one normal73 per vertex and face indexing.74 wireframe : bool, optional75 If True, the material is rendered in wireframe mode.76 """77 78 def __init__(self,79 name=None,80 normalTexture=None,81 occlusionTexture=None,82 emissiveTexture=None,83 emissiveFactor=None,84 alphaMode=None,85 alphaCutoff=None,86 doubleSided=False,87 smooth=True,88 wireframe=False):89 90 # Set defaults91 if alphaMode is None:92 alphaMode = 'OPAQUE'93 94 if alphaCutoff is None:95 alphaCutoff = 0.596 97 if emissiveFactor is None:98 emissiveFactor = np.zeros(3).astype(np.float32)99 100 self.name = name101 self.normalTexture = normalTexture102 self.occlusionTexture = occlusionTexture103 self.emissiveTexture = emissiveTexture104 self.emissiveFactor = emissiveFactor105 self.alphaMode = alphaMode106 self.alphaCutoff = alphaCutoff107 self.doubleSided = doubleSided108 self.smooth = smooth109 self.wireframe = wireframe110 111 self._tex_flags = None112 113 @property114 def name(self):115 """str : The user-defined name of this object.116 """117 return self._name118 119 @name.setter120 def name(self, value):121 if value is not None:122 value = str(value)123 self._name = value124 125 @property126 def normalTexture(self):127 """(n,n,3) float or :class:`Texture` : The tangent-space normal map.128 """129 return self._normalTexture130 131 @normalTexture.setter132 def normalTexture(self, value):133 # TODO TMP134 self._normalTexture = self._format_texture(value, 'RGB')135 self._tex_flags = None136 137 @property138 def occlusionTexture(self):139 """(n,n,1) float or :class:`Texture` : The ambient occlusion map.140 """141 return self._occlusionTexture142 143 @occlusionTexture.setter144 def occlusionTexture(self, value):145 self._occlusionTexture = self._format_texture(value, 'R')146 self._tex_flags = None147 148 @property149 def emissiveTexture(self):150 """(n,n,3) float or :class:`Texture` : The emission map.151 """152 return self._emissiveTexture153 154 @emissiveTexture.setter155 def emissiveTexture(self, value):156 self._emissiveTexture = self._format_texture(value, 'RGB')157 self._tex_flags = None158 159 @property160 def emissiveFactor(self):161 """(3,) float : Base multiplier for emission colors.162 """163 return self._emissiveFactor164 165 @emissiveFactor.setter166 def emissiveFactor(self, value):167 if value is None:168 value = np.zeros(3)169 self._emissiveFactor = format_color_vector(value, 3)170 171 @property172 def alphaMode(self):173 """str : The mode for blending.174 """175 return self._alphaMode176 177 @alphaMode.setter178 def alphaMode(self, value):179 if value not in set(['OPAQUE', 'MASK', 'BLEND']):180 raise ValueError('Invalid alpha mode {}'.format(value))181 self._alphaMode = value182 183 @property184 def alphaCutoff(self):185 """float : The cutoff threshold in MASK mode.186 """187 return self._alphaCutoff188 189 @alphaCutoff.setter190 def alphaCutoff(self, value):191 if value < 0 or value > 1:192 raise ValueError('Alpha cutoff must be in range [0,1]')193 self._alphaCutoff = float(value)194 195 @property196 def doubleSided(self):197 """bool : Whether the material is double-sided.198 """199 return self._doubleSided200 201 @doubleSided.setter202 def doubleSided(self, value):203 if not isinstance(value, bool):204 raise TypeError('Double sided must be a boolean value')205 self._doubleSided = value206 207 @property208 def smooth(self):209 """bool : Whether to render the mesh smoothly by210 interpolating vertex normals.211 """212 return self._smooth213 214 @smooth.setter215 def smooth(self, value):216 if not isinstance(value, bool):217 raise TypeError('Double sided must be a boolean value')218 self._smooth = value219 220 @property221 def wireframe(self):222 """bool : Whether to render the mesh in wireframe mode.223 """224 return self._wireframe225 226 @wireframe.setter227 def wireframe(self, value):228 if not isinstance(value, bool):229 raise TypeError('Wireframe must be a boolean value')230 self._wireframe = value231 232 @property233 def is_transparent(self):234 """bool : If True, the object is partially transparent.235 """236 return self._compute_transparency()237 238 @property239 def tex_flags(self):240 """int : Texture availability flags.241 """242 if self._tex_flags is None:243 self._tex_flags = self._compute_tex_flags()244 return self._tex_flags245 246 @property247 def textures(self):248 """list of :class:`Texture` : The textures associated with this249 material.250 """251 return self._compute_textures()252 253 def _compute_transparency(self):254 return False255 256 def _compute_tex_flags(self):257 tex_flags = TexFlags.NONE258 if self.normalTexture is not None:259 tex_flags |= TexFlags.NORMAL260 if self.occlusionTexture is not None:261 tex_flags |= TexFlags.OCCLUSION262 if self.emissiveTexture is not None:263 tex_flags |= TexFlags.EMISSIVE264 return tex_flags265 266 def _compute_textures(self):267 all_textures = [268 self.normalTexture, self.occlusionTexture, self.emissiveTexture269 ]270 textures = set([t for t in all_textures if t is not None])271 return textures272 273 def _format_texture(self, texture, target_channels='RGB'):274 """Format a texture as a float32 np array.275 """276 if isinstance(texture, Texture) or texture is None:277 return texture278 else:279 source = format_texture_source(texture, target_channels)280 return Texture(source=source, source_channels=target_channels)281 282 283class MetallicRoughnessMaterial(Material):284 """A material based on the metallic-roughness material model from285 Physically-Based Rendering (PBR) methodology.286 287 Parameters288 ----------289 name : str, optional290 The user-defined name of this object.291 normalTexture : (n,n,3) float or :class:`Texture`, optional292 A tangent space normal map. The texture contains RGB components in293 linear space. Each texel represents the XYZ components of a normal294 vector in tangent space. Red [0 to 255] maps to X [-1 to 1]. Green295 [0 to 255] maps to Y [-1 to 1]. Blue [128 to 255] maps to Z296 [1/255 to 1]. The normal vectors use OpenGL conventions where +X is297 right and +Y is up. +Z points toward the viewer.298 occlusionTexture : (n,n,1) float or :class:`Texture`, optional299 The occlusion map texture. The occlusion values are sampled from the R300 channel. Higher values indicate areas that should receive full indirect301 lighting and lower values indicate no indirect lighting. These values302 are linear. If other channels are present (GBA), they are ignored for303 occlusion calculations.304 emissiveTexture : (n,n,3) float or :class:`Texture`, optional305 The emissive map controls the color and intensity of the light being306 emitted by the material. This texture contains RGB components in sRGB307 color space. If a fourth component (A) is present, it is ignored.308 emissiveFactor : (3,) float, optional309 The RGB components of the emissive color of the material. These values310 are linear. If an emissiveTexture is specified, this value is311 multiplied with the texel values.312 alphaMode : str, optional313 The material's alpha rendering mode enumeration specifying the314 interpretation of the alpha value of the main factor and texture.315 Allowed Values:316 317 - `"OPAQUE"` The alpha value is ignored and the rendered output is318 fully opaque.319 - `"MASK"` The rendered output is either fully opaque or fully320 transparent depending on the alpha value and the specified alpha321 cutoff value.322 - `"BLEND"` The alpha value is used to composite the source and323 destination areas. The rendered output is combined with the324 background using the normal painting operation (i.e. the Porter325 and Duff over operator).326 327 alphaCutoff : float, optional328 Specifies the cutoff threshold when in MASK mode. If the alpha value is329 greater than or equal to this value then it is rendered as fully330 opaque, otherwise, it is rendered as fully transparent.331 A value greater than 1.0 will render the entire material as fully332 transparent. This value is ignored for other modes.333 doubleSided : bool, optional334 Specifies whether the material is double sided. When this value is335 false, back-face culling is enabled. When this value is true,336 back-face culling is disabled and double sided lighting is enabled.337 smooth : bool, optional338 If True, the material is rendered smoothly by using only one normal339 per vertex and face indexing.340 wireframe : bool, optional341 If True, the material is rendered in wireframe mode.342 baseColorFactor : (4,) float, optional343 The RGBA components of the base color of the material. The fourth344 component (A) is the alpha coverage of the material. The alphaMode345 property specifies how alpha is interpreted. These values are linear.346 If a baseColorTexture is specified, this value is multiplied with the347 texel values.348 baseColorTexture : (n,n,4) float or :class:`Texture`, optional349 The base color texture. This texture contains RGB(A) components in sRGB350 color space. The first three components (RGB) specify the base color of351 the material. If the fourth component (A) is present, it represents the352 alpha coverage of the material. Otherwise, an alpha of 1.0 is assumed.353 The alphaMode property specifies how alpha is interpreted.354 The stored texels must not be premultiplied.355 metallicFactor : float356 The metalness of the material. A value of 1.0 means the material is a357 metal. A value of 0.0 means the material is a dielectric. Values in358 between are for blending between metals and dielectrics such as dirty359 metallic surfaces. This value is linear. If a metallicRoughnessTexture360 is specified, this value is multiplied with the metallic texel values.361 roughnessFactor : float362 The roughness of the material. A value of 1.0 means the material is363 completely rough. A value of 0.0 means the material is completely364 smooth. This value is linear. If a metallicRoughnessTexture is365 specified, this value is multiplied with the roughness texel values.366 metallicRoughnessTexture : (n,n,2) float or :class:`Texture`, optional367 The metallic-roughness texture. The metalness values are sampled from368 the B channel. The roughness values are sampled from the G channel.369 These values are linear. If other channels are present (R or A), they370 are ignored for metallic-roughness calculations.371 """372 373 def __init__(self,374 name=None,375 normalTexture=None,376 occlusionTexture=None,377 emissiveTexture=None,378 emissiveFactor=None,379 alphaMode=None,380 alphaCutoff=None,381 doubleSided=False,382 smooth=True,383 wireframe=False,384 baseColorFactor=None,385 baseColorTexture=None,386 metallicFactor=1.0,387 roughnessFactor=1.0,388 metallicRoughnessTexture=None):389 super(MetallicRoughnessMaterial, self).__init__(390 name=name,391 normalTexture=normalTexture,392 occlusionTexture=occlusionTexture,393 emissiveTexture=emissiveTexture,394 emissiveFactor=emissiveFactor,395 alphaMode=alphaMode,396 alphaCutoff=alphaCutoff,397 doubleSided=doubleSided,398 smooth=smooth,399 wireframe=wireframe400 )401 402 # Set defaults403 if baseColorFactor is None:404 baseColorFactor = np.ones(4).astype(np.float32)405 406 self.baseColorFactor = baseColorFactor407 self.baseColorTexture = baseColorTexture408 self.metallicFactor = metallicFactor409 self.roughnessFactor = roughnessFactor410 self.metallicRoughnessTexture = metallicRoughnessTexture411 412 @property413 def baseColorFactor(self):414 """(4,) float or :class:`Texture` : The RGBA base color multiplier.415 """416 return self._baseColorFactor417 418 @baseColorFactor.setter419 def baseColorFactor(self, value):420 if value is None:421 value = np.ones(4)422 self._baseColorFactor = format_color_vector(value, 4)423 424 @property425 def baseColorTexture(self):426 """(n,n,4) float or :class:`Texture` : The diffuse texture.427 """428 return self._baseColorTexture429 430 @baseColorTexture.setter431 def baseColorTexture(self, value):432 self._baseColorTexture = self._format_texture(value, 'RGBA')433 self._tex_flags = None434 435 @property436 def metallicFactor(self):437 """float : The metalness of the material.438 """439 return self._metallicFactor440 441 @metallicFactor.setter442 def metallicFactor(self, value):443 if value is None:444 value = 1.0445 if value < 0 or value > 1:446 raise ValueError('Metallic factor must be in range [0,1]')447 self._metallicFactor = float(value)448 449 @property450 def roughnessFactor(self):451 """float : The roughness of the material.452 """453 return self.RoughnessFactor454 455 @roughnessFactor.setter456 def roughnessFactor(self, value):457 if value is None:458 value = 1.0459 if value < 0 or value > 1:460 raise ValueError('Roughness factor must be in range [0,1]')461 self.RoughnessFactor = float(value)462 463 @property464 def metallicRoughnessTexture(self):465 """(n,n,2) float or :class:`Texture` : The metallic-roughness texture.466 """467 return self._metallicRoughnessTexture468 469 @metallicRoughnessTexture.setter470 def metallicRoughnessTexture(self, value):471 self._metallicRoughnessTexture = self._format_texture(value, 'GB')472 self._tex_flags = None473 474 def _compute_tex_flags(self):475 tex_flags = super(MetallicRoughnessMaterial, self)._compute_tex_flags()476 if self.baseColorTexture is not None:477 tex_flags |= TexFlags.BASE_COLOR478 if self.metallicRoughnessTexture is not None:479 tex_flags |= TexFlags.METALLIC_ROUGHNESS480 return tex_flags481 482 def _compute_transparency(self):483 if self.alphaMode == 'OPAQUE':484 return False485 cutoff = self.alphaCutoff486 if self.alphaMode == 'BLEND':487 cutoff = 1.0488 if self.baseColorFactor[3] < cutoff:489 return True490 if (self.baseColorTexture is not None and491 self.baseColorTexture.is_transparent(cutoff)):492 return True493 return False494 495 def _compute_textures(self):496 textures = super(MetallicRoughnessMaterial, self)._compute_textures()497 all_textures = [self.baseColorTexture, self.metallicRoughnessTexture]498 all_textures = {t for t in all_textures if t is not None}499 textures |= all_textures500 return textures501 502 503class SpecularGlossinessMaterial(Material):504 """A material based on the specular-glossiness material model from505 Physically-Based Rendering (PBR) methodology.506 507 Parameters508 ----------509 name : str, optional510 The user-defined name of this object.511 normalTexture : (n,n,3) float or :class:`Texture`, optional512 A tangent space normal map. The texture contains RGB components in513 linear space. Each texel represents the XYZ components of a normal514 vector in tangent space. Red [0 to 255] maps to X [-1 to 1]. Green515 [0 to 255] maps to Y [-1 to 1]. Blue [128 to 255] maps to Z516 [1/255 to 1]. The normal vectors use OpenGL conventions where +X is517 right and +Y is up. +Z points toward the viewer.518 occlusionTexture : (n,n,1) float or :class:`Texture`, optional519 The occlusion map texture. The occlusion values are sampled from the R520 channel. Higher values indicate areas that should receive full indirect521 lighting and lower values indicate no indirect lighting. These values522 are linear. If other channels are present (GBA), they are ignored for523 occlusion calculations.524 emissiveTexture : (n,n,3) float or :class:`Texture`, optional525 The emissive map controls the color and intensity of the light being526 emitted by the material. This texture contains RGB components in sRGB527 color space. If a fourth component (A) is present, it is ignored.528 emissiveFactor : (3,) float, optional529 The RGB components of the emissive color of the material. These values530 are linear. If an emissiveTexture is specified, this value is531 multiplied with the texel values.532 alphaMode : str, optional533 The material's alpha rendering mode enumeration specifying the534 interpretation of the alpha value of the main factor and texture.535 Allowed Values:536 537 - `"OPAQUE"` The alpha value is ignored and the rendered output is538 fully opaque.539 - `"MASK"` The rendered output is either fully opaque or fully540 transparent depending on the alpha value and the specified alpha541 cutoff value.542 - `"BLEND"` The alpha value is used to composite the source and543 destination areas. The rendered output is combined with the544 background using the normal painting operation (i.e. the Porter545 and Duff over operator).546 547 alphaCutoff : float, optional548 Specifies the cutoff threshold when in MASK mode. If the alpha value is549 greater than or equal to this value then it is rendered as fully550 opaque, otherwise, it is rendered as fully transparent.551 A value greater than 1.0 will render the entire material as fully552 transparent. This value is ignored for other modes.553 doubleSided : bool, optional554 Specifies whether the material is double sided. When this value is555 false, back-face culling is enabled. When this value is true,556 back-face culling is disabled and double sided lighting is enabled.557 smooth : bool, optional558 If True, the material is rendered smoothly by using only one normal559 per vertex and face indexing.560 wireframe : bool, optional561 If True, the material is rendered in wireframe mode.562 diffuseFactor : (4,) float563 The RGBA components of the reflected diffuse color of the material.564 Metals have a diffuse value of [0.0, 0.0, 0.0]. The fourth component565 (A) is the opacity of the material. The values are linear.566 diffuseTexture : (n,n,4) float or :class:`Texture`, optional567 The diffuse texture. This texture contains RGB(A) components of the568 reflected diffuse color of the material in sRGB color space. If the569 fourth component (A) is present, it represents the alpha coverage of570 the material. Otherwise, an alpha of 1.0 is assumed.571 The alphaMode property specifies how alpha is interpreted.572 The stored texels must not be premultiplied.573 specularFactor : (3,) float574 The specular RGB color of the material. This value is linear.575 glossinessFactor : float576 The glossiness or smoothness of the material. A value of 1.0 means the577 material has full glossiness or is perfectly smooth. A value of 0.0578 means the material has no glossiness or is perfectly rough. This value579 is linear.580 specularGlossinessTexture : (n,n,4) or :class:`Texture`, optional581 The specular-glossiness texture is a RGBA texture, containing the582 specular color (RGB) in sRGB space and the glossiness value (A) in583 linear space.584 """585 586 def __init__(self,587 name=None,588 normalTexture=None,589 occlusionTexture=None,590 emissiveTexture=None,591 emissiveFactor=None,592 alphaMode=None,593 alphaCutoff=None,594 doubleSided=False,595 smooth=True,596 wireframe=False,597 diffuseFactor=None,598 diffuseTexture=None,599 specularFactor=None,600 glossinessFactor=1.0,601 specularGlossinessTexture=None):602 super(SpecularGlossinessMaterial, self).__init__(603 name=name,604 normalTexture=normalTexture,605 occlusionTexture=occlusionTexture,606 emissiveTexture=emissiveTexture,607 emissiveFactor=emissiveFactor,608 alphaMode=alphaMode,609 alphaCutoff=alphaCutoff,610 doubleSided=doubleSided,611 smooth=smooth,612 wireframe=wireframe613 )614 615 # Set defaults616 if diffuseFactor is None:617 diffuseFactor = np.ones(4).astype(np.float32)618 if specularFactor is None:619 specularFactor = np.ones(3).astype(np.float32)620 621 self.diffuseFactor = diffuseFactor622 self.diffuseTexture = diffuseTexture623 self.specularFactor = specularFactor624 self.glossinessFactor = glossinessFactor625 self.specularGlossinessTexture = specularGlossinessTexture626 627 @property628 def diffuseFactor(self):629 """(4,) float : The diffuse base color.630 """631 return self._diffuseFactor632 633 @diffuseFactor.setter634 def diffuseFactor(self, value):635 self._diffuseFactor = format_color_vector(value, 4)636 637 @property638 def diffuseTexture(self):639 """(n,n,4) float or :class:`Texture` : The diffuse map.640 """641 return self._diffuseTexture642 643 @diffuseTexture.setter644 def diffuseTexture(self, value):645 self._diffuseTexture = self._format_texture(value, 'RGBA')646 self._tex_flags = None647 648 @property649 def specularFactor(self):650 """(3,) float : The specular color of the material.651 """652 return self._specularFactor653 654 @specularFactor.setter655 def specularFactor(self, value):656 self._specularFactor = format_color_vector(value, 3)657 658 @property659 def glossinessFactor(self):660 """float : The glossiness of the material.661 """662 return self.glossinessFactor663 664 @glossinessFactor.setter665 def glossinessFactor(self, value):666 if value < 0 or value > 1:667 raise ValueError('glossiness factor must be in range [0,1]')668 self._glossinessFactor = float(value)669 670 @property671 def specularGlossinessTexture(self):672 """(n,n,4) or :class:`Texture` : The specular-glossiness texture.673 """674 return self._specularGlossinessTexture675 676 @specularGlossinessTexture.setter677 def specularGlossinessTexture(self, value):678 self._specularGlossinessTexture = self._format_texture(value, 'GB')679 self._tex_flags = None680 681 def _compute_tex_flags(self):682 flags = super(SpecularGlossinessMaterial, self)._compute_tex_flags()683 if self.diffuseTexture is not None:684 flags |= TexFlags.DIFFUSE685 if self.specularGlossinessTexture is not None:686 flags |= TexFlags.SPECULAR_GLOSSINESS687 return flags688 689 def _compute_transparency(self):690 if self.alphaMode == 'OPAQUE':691 return False692 cutoff = self.alphaCutoff693 if self.alphaMode == 'BLEND':694 cutoff = 1.0695 if self.diffuseFactor[3] < cutoff:696 return True697 if (self.diffuseTexture is not None and698 self.diffuseTexture.is_transparent(cutoff)):699 return True700 return False701 702 def _compute_textures(self):703 textures = super(SpecularGlossinessMaterial, self)._compute_textures()704 all_textures = [self.diffuseTexture, self.specularGlossinessTexture]705 all_textures = {t for t in all_textures if t is not None}706 textures |= all_textures707 return textures708 