vumichien's picture
First commit
b4c8bc3
raw history blame
No virus
28.1 kB
"""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
@six.add_metaclass(abc.ABCMeta)
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
@property
def name(self):
"""str : The user-defined name of this object.
"""
return self._name
@name.setter
def name(self, value):
if value is not None:
value = str(value)
self._name = value
@property
def normalTexture(self):
"""(n,n,3) float or :class:`Texture` : The tangent-space normal map.
"""
return self._normalTexture
@normalTexture.setter
def normalTexture(self, value):
# TODO TMP
self._normalTexture = self._format_texture(value, 'RGB')
self._tex_flags = None
@property
def occlusionTexture(self):
"""(n,n,1) float or :class:`Texture` : The ambient occlusion map.
"""
return self._occlusionTexture
@occlusionTexture.setter
def occlusionTexture(self, value):
self._occlusionTexture = self._format_texture(value, 'R')
self._tex_flags = None
@property
def emissiveTexture(self):
"""(n,n,3) float or :class:`Texture` : The emission map.
"""
return self._emissiveTexture
@emissiveTexture.setter
def emissiveTexture(self, value):
self._emissiveTexture = self._format_texture(value, 'RGB')
self._tex_flags = None
@property
def emissiveFactor(self):
"""(3,) float : Base multiplier for emission colors.
"""
return self._emissiveFactor
@emissiveFactor.setter
def emissiveFactor(self, value):
if value is None:
value = np.zeros(3)
self._emissiveFactor = format_color_vector(value, 3)
@property
def alphaMode(self):
"""str : The mode for blending.
"""
return self._alphaMode
@alphaMode.setter
def alphaMode(self, value):
if value not in set(['OPAQUE', 'MASK', 'BLEND']):
raise ValueError('Invalid alpha mode {}'.format(value))
self._alphaMode = value
@property
def alphaCutoff(self):
"""float : The cutoff threshold in MASK mode.
"""
return self._alphaCutoff
@alphaCutoff.setter
def alphaCutoff(self, value):
if value < 0 or value > 1:
raise ValueError('Alpha cutoff must be in range [0,1]')
self._alphaCutoff = float(value)
@property
def doubleSided(self):
"""bool : Whether the material is double-sided.
"""
return self._doubleSided
@doubleSided.setter
def doubleSided(self, value):
if not isinstance(value, bool):
raise TypeError('Double sided must be a boolean value')
self._doubleSided = value
@property
def smooth(self):
"""bool : Whether to render the mesh smoothly by
interpolating vertex normals.
"""
return self._smooth
@smooth.setter
def smooth(self, value):
if not isinstance(value, bool):
raise TypeError('Double sided must be a boolean value')
self._smooth = value
@property
def wireframe(self):
"""bool : Whether to render the mesh in wireframe mode.
"""
return self._wireframe
@wireframe.setter
def wireframe(self, value):
if not isinstance(value, bool):
raise TypeError('Wireframe must be a boolean value')
self._wireframe = value
@property
def is_transparent(self):
"""bool : If True, the object is partially transparent.
"""
return self._compute_transparency()
@property
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
@property
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
@property
def baseColorFactor(self):
"""(4,) float or :class:`Texture` : The RGBA base color multiplier.
"""
return self._baseColorFactor
@baseColorFactor.setter
def baseColorFactor(self, value):
if value is None:
value = np.ones(4)
self._baseColorFactor = format_color_vector(value, 4)
@property
def baseColorTexture(self):
"""(n,n,4) float or :class:`Texture` : The diffuse texture.
"""
return self._baseColorTexture
@baseColorTexture.setter
def baseColorTexture(self, value):
self._baseColorTexture = self._format_texture(value, 'RGBA')
self._tex_flags = None
@property
def metallicFactor(self):
"""float : The metalness of the material.
"""
return self._metallicFactor
@metallicFactor.setter
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)
@property
def roughnessFactor(self):
"""float : The roughness of the material.
"""
return self.RoughnessFactor
@roughnessFactor.setter
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)
@property
def metallicRoughnessTexture(self):
"""(n,n,2) float or :class:`Texture` : The metallic-roughness texture.
"""
return self._metallicRoughnessTexture
@metallicRoughnessTexture.setter
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
@property
def diffuseFactor(self):
"""(4,) float : The diffuse base color.
"""
return self._diffuseFactor
@diffuseFactor.setter
def diffuseFactor(self, value):
self._diffuseFactor = format_color_vector(value, 4)
@property
def diffuseTexture(self):
"""(n,n,4) float or :class:`Texture` : The diffuse map.
"""
return self._diffuseTexture
@diffuseTexture.setter
def diffuseTexture(self, value):
self._diffuseTexture = self._format_texture(value, 'RGBA')
self._tex_flags = None
@property
def specularFactor(self):
"""(3,) float : The specular color of the material.
"""
return self._specularFactor
@specularFactor.setter
def specularFactor(self, value):
self._specularFactor = format_color_vector(value, 3)
@property
def glossinessFactor(self):
"""float : The glossiness of the material.
"""
return self.glossinessFactor
@glossinessFactor.setter
def glossinessFactor(self, value):
if value < 0 or value > 1:
raise ValueError('glossiness factor must be in range [0,1]')
self._glossinessFactor = float(value)
@property
def specularGlossinessTexture(self):
"""(n,n,4) or :class:`Texture` : The specular-glossiness texture.
"""
return self._specularGlossinessTexture
@specularGlossinessTexture.setter
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