Spaces:
Sleeping
Sleeping
"""Material properties, conforming to the glTF 2.0 standards as specified in | |
https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#reference-material | |
and | |
https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness | |
Author: Matthew Matl | |
""" | |
import abc | |
import numpy as np | |
import six | |
from .constants import TexFlags | |
from .utils import format_color_vector, format_texture_source | |
from .texture import Texture | |
class Material(object): | |
"""Base for standard glTF 2.0 materials. | |
Parameters | |
---------- | |
name : str, optional | |
The user-defined name of this object. | |
normalTexture : (n,n,3) float or :class:`Texture`, optional | |
A tangent space normal map. The texture contains RGB components in | |
linear space. Each texel represents the XYZ components of a normal | |
vector in tangent space. Red [0 to 255] maps to X [-1 to 1]. Green | |
[0 to 255] maps to Y [-1 to 1]. Blue [128 to 255] maps to Z | |
[1/255 to 1]. The normal vectors use OpenGL conventions where +X is | |
right and +Y is up. +Z points toward the viewer. | |
occlusionTexture : (n,n,1) float or :class:`Texture`, optional | |
The occlusion map texture. The occlusion values are sampled from the R | |
channel. Higher values indicate areas that should receive full indirect | |
lighting and lower values indicate no indirect lighting. These values | |
are linear. If other channels are present (GBA), they are ignored for | |
occlusion calculations. | |
emissiveTexture : (n,n,3) float or :class:`Texture`, optional | |
The emissive map controls the color and intensity of the light being | |
emitted by the material. This texture contains RGB components in sRGB | |
color space. If a fourth component (A) is present, it is ignored. | |
emissiveFactor : (3,) float, optional | |
The RGB components of the emissive color of the material. These values | |
are linear. If an emissiveTexture is specified, this value is | |
multiplied with the texel values. | |
alphaMode : str, optional | |
The material's alpha rendering mode enumeration specifying the | |
interpretation of the alpha value of the main factor and texture. | |
Allowed Values: | |
- `"OPAQUE"` The alpha value is ignored and the rendered output is | |
fully opaque. | |
- `"MASK"` The rendered output is either fully opaque or fully | |
transparent depending on the alpha value and the specified alpha | |
cutoff value. | |
- `"BLEND"` The alpha value is used to composite the source and | |
destination areas. The rendered output is combined with the | |
background using the normal painting operation (i.e. the Porter | |
and Duff over operator). | |
alphaCutoff : float, optional | |
Specifies the cutoff threshold when in MASK mode. If the alpha value is | |
greater than or equal to this value then it is rendered as fully | |
opaque, otherwise, it is rendered as fully transparent. | |
A value greater than 1.0 will render the entire material as fully | |
transparent. This value is ignored for other modes. | |
doubleSided : bool, optional | |
Specifies whether the material is double sided. When this value is | |
false, back-face culling is enabled. When this value is true, | |
back-face culling is disabled and double sided lighting is enabled. | |
smooth : bool, optional | |
If True, the material is rendered smoothly by using only one normal | |
per vertex and face indexing. | |
wireframe : bool, optional | |
If True, the material is rendered in wireframe mode. | |
""" | |
def __init__(self, | |
name=None, | |
normalTexture=None, | |
occlusionTexture=None, | |
emissiveTexture=None, | |
emissiveFactor=None, | |
alphaMode=None, | |
alphaCutoff=None, | |
doubleSided=False, | |
smooth=True, | |
wireframe=False): | |
# Set defaults | |
if alphaMode is None: | |
alphaMode = 'OPAQUE' | |
if alphaCutoff is None: | |
alphaCutoff = 0.5 | |
if emissiveFactor is None: | |
emissiveFactor = np.zeros(3).astype(np.float32) | |
self.name = name | |
self.normalTexture = normalTexture | |
self.occlusionTexture = occlusionTexture | |
self.emissiveTexture = emissiveTexture | |
self.emissiveFactor = emissiveFactor | |
self.alphaMode = alphaMode | |
self.alphaCutoff = alphaCutoff | |
self.doubleSided = doubleSided | |
self.smooth = smooth | |
self.wireframe = wireframe | |
self._tex_flags = None | |
def name(self): | |
"""str : The user-defined name of this object. | |
""" | |
return self._name | |
def name(self, value): | |
if value is not None: | |
value = str(value) | |
self._name = value | |
def normalTexture(self): | |
"""(n,n,3) float or :class:`Texture` : The tangent-space normal map. | |
""" | |
return self._normalTexture | |
def normalTexture(self, value): | |
# TODO TMP | |
self._normalTexture = self._format_texture(value, 'RGB') | |
self._tex_flags = None | |
def occlusionTexture(self): | |
"""(n,n,1) float or :class:`Texture` : The ambient occlusion map. | |
""" | |
return self._occlusionTexture | |
def occlusionTexture(self, value): | |
self._occlusionTexture = self._format_texture(value, 'R') | |
self._tex_flags = None | |
def emissiveTexture(self): | |
"""(n,n,3) float or :class:`Texture` : The emission map. | |
""" | |
return self._emissiveTexture | |
def emissiveTexture(self, value): | |
self._emissiveTexture = self._format_texture(value, 'RGB') | |
self._tex_flags = None | |
def emissiveFactor(self): | |
"""(3,) float : Base multiplier for emission colors. | |
""" | |
return self._emissiveFactor | |
def emissiveFactor(self, value): | |
if value is None: | |
value = np.zeros(3) | |
self._emissiveFactor = format_color_vector(value, 3) | |
def alphaMode(self): | |
"""str : The mode for blending. | |
""" | |
return self._alphaMode | |
def alphaMode(self, value): | |
if value not in set(['OPAQUE', 'MASK', 'BLEND']): | |
raise ValueError('Invalid alpha mode {}'.format(value)) | |
self._alphaMode = value | |
def alphaCutoff(self): | |
"""float : The cutoff threshold in MASK mode. | |
""" | |
return self._alphaCutoff | |
def alphaCutoff(self, value): | |
if value < 0 or value > 1: | |
raise ValueError('Alpha cutoff must be in range [0,1]') | |
self._alphaCutoff = float(value) | |
def doubleSided(self): | |
"""bool : Whether the material is double-sided. | |
""" | |
return self._doubleSided | |
def doubleSided(self, value): | |
if not isinstance(value, bool): | |
raise TypeError('Double sided must be a boolean value') | |
self._doubleSided = value | |
def smooth(self): | |
"""bool : Whether to render the mesh smoothly by | |
interpolating vertex normals. | |
""" | |
return self._smooth | |
def smooth(self, value): | |
if not isinstance(value, bool): | |
raise TypeError('Double sided must be a boolean value') | |
self._smooth = value | |
def wireframe(self): | |
"""bool : Whether to render the mesh in wireframe mode. | |
""" | |
return self._wireframe | |
def wireframe(self, value): | |
if not isinstance(value, bool): | |
raise TypeError('Wireframe must be a boolean value') | |
self._wireframe = value | |
def is_transparent(self): | |
"""bool : If True, the object is partially transparent. | |
""" | |
return self._compute_transparency() | |
def tex_flags(self): | |
"""int : Texture availability flags. | |
""" | |
if self._tex_flags is None: | |
self._tex_flags = self._compute_tex_flags() | |
return self._tex_flags | |
def textures(self): | |
"""list of :class:`Texture` : The textures associated with this | |
material. | |
""" | |
return self._compute_textures() | |
def _compute_transparency(self): | |
return False | |
def _compute_tex_flags(self): | |
tex_flags = TexFlags.NONE | |
if self.normalTexture is not None: | |
tex_flags |= TexFlags.NORMAL | |
if self.occlusionTexture is not None: | |
tex_flags |= TexFlags.OCCLUSION | |
if self.emissiveTexture is not None: | |
tex_flags |= TexFlags.EMISSIVE | |
return tex_flags | |
def _compute_textures(self): | |
all_textures = [ | |
self.normalTexture, self.occlusionTexture, self.emissiveTexture | |
] | |
textures = set([t for t in all_textures if t is not None]) | |
return textures | |
def _format_texture(self, texture, target_channels='RGB'): | |
"""Format a texture as a float32 np array. | |
""" | |
if isinstance(texture, Texture) or texture is None: | |
return texture | |
else: | |
source = format_texture_source(texture, target_channels) | |
return Texture(source=source, source_channels=target_channels) | |
class MetallicRoughnessMaterial(Material): | |
"""A material based on the metallic-roughness material model from | |
Physically-Based Rendering (PBR) methodology. | |
Parameters | |
---------- | |
name : str, optional | |
The user-defined name of this object. | |
normalTexture : (n,n,3) float or :class:`Texture`, optional | |
A tangent space normal map. The texture contains RGB components in | |
linear space. Each texel represents the XYZ components of a normal | |
vector in tangent space. Red [0 to 255] maps to X [-1 to 1]. Green | |
[0 to 255] maps to Y [-1 to 1]. Blue [128 to 255] maps to Z | |
[1/255 to 1]. The normal vectors use OpenGL conventions where +X is | |
right and +Y is up. +Z points toward the viewer. | |
occlusionTexture : (n,n,1) float or :class:`Texture`, optional | |
The occlusion map texture. The occlusion values are sampled from the R | |
channel. Higher values indicate areas that should receive full indirect | |
lighting and lower values indicate no indirect lighting. These values | |
are linear. If other channels are present (GBA), they are ignored for | |
occlusion calculations. | |
emissiveTexture : (n,n,3) float or :class:`Texture`, optional | |
The emissive map controls the color and intensity of the light being | |
emitted by the material. This texture contains RGB components in sRGB | |
color space. If a fourth component (A) is present, it is ignored. | |
emissiveFactor : (3,) float, optional | |
The RGB components of the emissive color of the material. These values | |
are linear. If an emissiveTexture is specified, this value is | |
multiplied with the texel values. | |
alphaMode : str, optional | |
The material's alpha rendering mode enumeration specifying the | |
interpretation of the alpha value of the main factor and texture. | |
Allowed Values: | |
- `"OPAQUE"` The alpha value is ignored and the rendered output is | |
fully opaque. | |
- `"MASK"` The rendered output is either fully opaque or fully | |
transparent depending on the alpha value and the specified alpha | |
cutoff value. | |
- `"BLEND"` The alpha value is used to composite the source and | |
destination areas. The rendered output is combined with the | |
background using the normal painting operation (i.e. the Porter | |
and Duff over operator). | |
alphaCutoff : float, optional | |
Specifies the cutoff threshold when in MASK mode. If the alpha value is | |
greater than or equal to this value then it is rendered as fully | |
opaque, otherwise, it is rendered as fully transparent. | |
A value greater than 1.0 will render the entire material as fully | |
transparent. This value is ignored for other modes. | |
doubleSided : bool, optional | |
Specifies whether the material is double sided. When this value is | |
false, back-face culling is enabled. When this value is true, | |
back-face culling is disabled and double sided lighting is enabled. | |
smooth : bool, optional | |
If True, the material is rendered smoothly by using only one normal | |
per vertex and face indexing. | |
wireframe : bool, optional | |
If True, the material is rendered in wireframe mode. | |
baseColorFactor : (4,) float, optional | |
The RGBA components of the base color of the material. The fourth | |
component (A) is the alpha coverage of the material. The alphaMode | |
property specifies how alpha is interpreted. These values are linear. | |
If a baseColorTexture is specified, this value is multiplied with the | |
texel values. | |
baseColorTexture : (n,n,4) float or :class:`Texture`, optional | |
The base color texture. This texture contains RGB(A) components in sRGB | |
color space. The first three components (RGB) specify the base color of | |
the material. If the fourth component (A) is present, it represents the | |
alpha coverage of the material. Otherwise, an alpha of 1.0 is assumed. | |
The alphaMode property specifies how alpha is interpreted. | |
The stored texels must not be premultiplied. | |
metallicFactor : float | |
The metalness of the material. A value of 1.0 means the material is a | |
metal. A value of 0.0 means the material is a dielectric. Values in | |
between are for blending between metals and dielectrics such as dirty | |
metallic surfaces. This value is linear. If a metallicRoughnessTexture | |
is specified, this value is multiplied with the metallic texel values. | |
roughnessFactor : float | |
The roughness of the material. A value of 1.0 means the material is | |
completely rough. A value of 0.0 means the material is completely | |
smooth. This value is linear. If a metallicRoughnessTexture is | |
specified, this value is multiplied with the roughness texel values. | |
metallicRoughnessTexture : (n,n,2) float or :class:`Texture`, optional | |
The metallic-roughness texture. The metalness values are sampled from | |
the B channel. The roughness values are sampled from the G channel. | |
These values are linear. If other channels are present (R or A), they | |
are ignored for metallic-roughness calculations. | |
""" | |
def __init__(self, | |
name=None, | |
normalTexture=None, | |
occlusionTexture=None, | |
emissiveTexture=None, | |
emissiveFactor=None, | |
alphaMode=None, | |
alphaCutoff=None, | |
doubleSided=False, | |
smooth=True, | |
wireframe=False, | |
baseColorFactor=None, | |
baseColorTexture=None, | |
metallicFactor=1.0, | |
roughnessFactor=1.0, | |
metallicRoughnessTexture=None): | |
super(MetallicRoughnessMaterial, self).__init__( | |
name=name, | |
normalTexture=normalTexture, | |
occlusionTexture=occlusionTexture, | |
emissiveTexture=emissiveTexture, | |
emissiveFactor=emissiveFactor, | |
alphaMode=alphaMode, | |
alphaCutoff=alphaCutoff, | |
doubleSided=doubleSided, | |
smooth=smooth, | |
wireframe=wireframe | |
) | |
# Set defaults | |
if baseColorFactor is None: | |
baseColorFactor = np.ones(4).astype(np.float32) | |
self.baseColorFactor = baseColorFactor | |
self.baseColorTexture = baseColorTexture | |
self.metallicFactor = metallicFactor | |
self.roughnessFactor = roughnessFactor | |
self.metallicRoughnessTexture = metallicRoughnessTexture | |
def baseColorFactor(self): | |
"""(4,) float or :class:`Texture` : The RGBA base color multiplier. | |
""" | |
return self._baseColorFactor | |
def baseColorFactor(self, value): | |
if value is None: | |
value = np.ones(4) | |
self._baseColorFactor = format_color_vector(value, 4) | |
def baseColorTexture(self): | |
"""(n,n,4) float or :class:`Texture` : The diffuse texture. | |
""" | |
return self._baseColorTexture | |
def baseColorTexture(self, value): | |
self._baseColorTexture = self._format_texture(value, 'RGBA') | |
self._tex_flags = None | |
def metallicFactor(self): | |
"""float : The metalness of the material. | |
""" | |
return self._metallicFactor | |
def metallicFactor(self, value): | |
if value is None: | |
value = 1.0 | |
if value < 0 or value > 1: | |
raise ValueError('Metallic factor must be in range [0,1]') | |
self._metallicFactor = float(value) | |
def roughnessFactor(self): | |
"""float : The roughness of the material. | |
""" | |
return self.RoughnessFactor | |
def roughnessFactor(self, value): | |
if value is None: | |
value = 1.0 | |
if value < 0 or value > 1: | |
raise ValueError('Roughness factor must be in range [0,1]') | |
self.RoughnessFactor = float(value) | |
def metallicRoughnessTexture(self): | |
"""(n,n,2) float or :class:`Texture` : The metallic-roughness texture. | |
""" | |
return self._metallicRoughnessTexture | |
def metallicRoughnessTexture(self, value): | |
self._metallicRoughnessTexture = self._format_texture(value, 'GB') | |
self._tex_flags = None | |
def _compute_tex_flags(self): | |
tex_flags = super(MetallicRoughnessMaterial, self)._compute_tex_flags() | |
if self.baseColorTexture is not None: | |
tex_flags |= TexFlags.BASE_COLOR | |
if self.metallicRoughnessTexture is not None: | |
tex_flags |= TexFlags.METALLIC_ROUGHNESS | |
return tex_flags | |
def _compute_transparency(self): | |
if self.alphaMode == 'OPAQUE': | |
return False | |
cutoff = self.alphaCutoff | |
if self.alphaMode == 'BLEND': | |
cutoff = 1.0 | |
if self.baseColorFactor[3] < cutoff: | |
return True | |
if (self.baseColorTexture is not None and | |
self.baseColorTexture.is_transparent(cutoff)): | |
return True | |
return False | |
def _compute_textures(self): | |
textures = super(MetallicRoughnessMaterial, self)._compute_textures() | |
all_textures = [self.baseColorTexture, self.metallicRoughnessTexture] | |
all_textures = {t for t in all_textures if t is not None} | |
textures |= all_textures | |
return textures | |
class SpecularGlossinessMaterial(Material): | |
"""A material based on the specular-glossiness material model from | |
Physically-Based Rendering (PBR) methodology. | |
Parameters | |
---------- | |
name : str, optional | |
The user-defined name of this object. | |
normalTexture : (n,n,3) float or :class:`Texture`, optional | |
A tangent space normal map. The texture contains RGB components in | |
linear space. Each texel represents the XYZ components of a normal | |
vector in tangent space. Red [0 to 255] maps to X [-1 to 1]. Green | |
[0 to 255] maps to Y [-1 to 1]. Blue [128 to 255] maps to Z | |
[1/255 to 1]. The normal vectors use OpenGL conventions where +X is | |
right and +Y is up. +Z points toward the viewer. | |
occlusionTexture : (n,n,1) float or :class:`Texture`, optional | |
The occlusion map texture. The occlusion values are sampled from the R | |
channel. Higher values indicate areas that should receive full indirect | |
lighting and lower values indicate no indirect lighting. These values | |
are linear. If other channels are present (GBA), they are ignored for | |
occlusion calculations. | |
emissiveTexture : (n,n,3) float or :class:`Texture`, optional | |
The emissive map controls the color and intensity of the light being | |
emitted by the material. This texture contains RGB components in sRGB | |
color space. If a fourth component (A) is present, it is ignored. | |
emissiveFactor : (3,) float, optional | |
The RGB components of the emissive color of the material. These values | |
are linear. If an emissiveTexture is specified, this value is | |
multiplied with the texel values. | |
alphaMode : str, optional | |
The material's alpha rendering mode enumeration specifying the | |
interpretation of the alpha value of the main factor and texture. | |
Allowed Values: | |
- `"OPAQUE"` The alpha value is ignored and the rendered output is | |
fully opaque. | |
- `"MASK"` The rendered output is either fully opaque or fully | |
transparent depending on the alpha value and the specified alpha | |
cutoff value. | |
- `"BLEND"` The alpha value is used to composite the source and | |
destination areas. The rendered output is combined with the | |
background using the normal painting operation (i.e. the Porter | |
and Duff over operator). | |
alphaCutoff : float, optional | |
Specifies the cutoff threshold when in MASK mode. If the alpha value is | |
greater than or equal to this value then it is rendered as fully | |
opaque, otherwise, it is rendered as fully transparent. | |
A value greater than 1.0 will render the entire material as fully | |
transparent. This value is ignored for other modes. | |
doubleSided : bool, optional | |
Specifies whether the material is double sided. When this value is | |
false, back-face culling is enabled. When this value is true, | |
back-face culling is disabled and double sided lighting is enabled. | |
smooth : bool, optional | |
If True, the material is rendered smoothly by using only one normal | |
per vertex and face indexing. | |
wireframe : bool, optional | |
If True, the material is rendered in wireframe mode. | |
diffuseFactor : (4,) float | |
The RGBA components of the reflected diffuse color of the material. | |
Metals have a diffuse value of [0.0, 0.0, 0.0]. The fourth component | |
(A) is the opacity of the material. The values are linear. | |
diffuseTexture : (n,n,4) float or :class:`Texture`, optional | |
The diffuse texture. This texture contains RGB(A) components of the | |
reflected diffuse color of the material in sRGB color space. If the | |
fourth component (A) is present, it represents the alpha coverage of | |
the material. Otherwise, an alpha of 1.0 is assumed. | |
The alphaMode property specifies how alpha is interpreted. | |
The stored texels must not be premultiplied. | |
specularFactor : (3,) float | |
The specular RGB color of the material. This value is linear. | |
glossinessFactor : float | |
The glossiness or smoothness of the material. A value of 1.0 means the | |
material has full glossiness or is perfectly smooth. A value of 0.0 | |
means the material has no glossiness or is perfectly rough. This value | |
is linear. | |
specularGlossinessTexture : (n,n,4) or :class:`Texture`, optional | |
The specular-glossiness texture is a RGBA texture, containing the | |
specular color (RGB) in sRGB space and the glossiness value (A) in | |
linear space. | |
""" | |
def __init__(self, | |
name=None, | |
normalTexture=None, | |
occlusionTexture=None, | |
emissiveTexture=None, | |
emissiveFactor=None, | |
alphaMode=None, | |
alphaCutoff=None, | |
doubleSided=False, | |
smooth=True, | |
wireframe=False, | |
diffuseFactor=None, | |
diffuseTexture=None, | |
specularFactor=None, | |
glossinessFactor=1.0, | |
specularGlossinessTexture=None): | |
super(SpecularGlossinessMaterial, self).__init__( | |
name=name, | |
normalTexture=normalTexture, | |
occlusionTexture=occlusionTexture, | |
emissiveTexture=emissiveTexture, | |
emissiveFactor=emissiveFactor, | |
alphaMode=alphaMode, | |
alphaCutoff=alphaCutoff, | |
doubleSided=doubleSided, | |
smooth=smooth, | |
wireframe=wireframe | |
) | |
# Set defaults | |
if diffuseFactor is None: | |
diffuseFactor = np.ones(4).astype(np.float32) | |
if specularFactor is None: | |
specularFactor = np.ones(3).astype(np.float32) | |
self.diffuseFactor = diffuseFactor | |
self.diffuseTexture = diffuseTexture | |
self.specularFactor = specularFactor | |
self.glossinessFactor = glossinessFactor | |
self.specularGlossinessTexture = specularGlossinessTexture | |
def diffuseFactor(self): | |
"""(4,) float : The diffuse base color. | |
""" | |
return self._diffuseFactor | |
def diffuseFactor(self, value): | |
self._diffuseFactor = format_color_vector(value, 4) | |
def diffuseTexture(self): | |
"""(n,n,4) float or :class:`Texture` : The diffuse map. | |
""" | |
return self._diffuseTexture | |
def diffuseTexture(self, value): | |
self._diffuseTexture = self._format_texture(value, 'RGBA') | |
self._tex_flags = None | |
def specularFactor(self): | |
"""(3,) float : The specular color of the material. | |
""" | |
return self._specularFactor | |
def specularFactor(self, value): | |
self._specularFactor = format_color_vector(value, 3) | |
def glossinessFactor(self): | |
"""float : The glossiness of the material. | |
""" | |
return self.glossinessFactor | |
def glossinessFactor(self, value): | |
if value < 0 or value > 1: | |
raise ValueError('glossiness factor must be in range [0,1]') | |
self._glossinessFactor = float(value) | |
def specularGlossinessTexture(self): | |
"""(n,n,4) or :class:`Texture` : The specular-glossiness texture. | |
""" | |
return self._specularGlossinessTexture | |
def specularGlossinessTexture(self, value): | |
self._specularGlossinessTexture = self._format_texture(value, 'GB') | |
self._tex_flags = None | |
def _compute_tex_flags(self): | |
flags = super(SpecularGlossinessMaterial, self)._compute_tex_flags() | |
if self.diffuseTexture is not None: | |
flags |= TexFlags.DIFFUSE | |
if self.specularGlossinessTexture is not None: | |
flags |= TexFlags.SPECULAR_GLOSSINESS | |
return flags | |
def _compute_transparency(self): | |
if self.alphaMode == 'OPAQUE': | |
return False | |
cutoff = self.alphaCutoff | |
if self.alphaMode == 'BLEND': | |
cutoff = 1.0 | |
if self.diffuseFactor[3] < cutoff: | |
return True | |
if (self.diffuseTexture is not None and | |
self.diffuseTexture.is_transparent(cutoff)): | |
return True | |
return False | |
def _compute_textures(self): | |
textures = super(SpecularGlossinessMaterial, self)._compute_textures() | |
all_textures = [self.diffuseTexture, self.specularGlossinessTexture] | |
all_textures = {t for t in all_textures if t is not None} | |
textures |= all_textures | |
return textures | |