from typing import Optional
import math
import numpy as np
import genesis as gs
from genesis.utils import mesh as mu
from .misc import FoamOptions
from .options import Options
from .textures import Texture, ColorTexture, ImageTexture, BatchTexture
############################ Base ############################
[docs]class Surface(Options):
"""
Base class for all surfaces types in Genesis.
A ``Surface`` object encapsulates all visual information used for rendering an entity or its sub-components (links,
geoms, ...). The surface contains different types textures: diffuse_texture, specular_texture, roughness_texture,
metallic_texture, transmission_texture, normal_texture, and emissive_texture. Each one of them is a
`gs.textures.Texture` object.
Tip
---
If any of the textures only has single value (instead of a map), you can use the shortcut attribute (e.g., `color`,
`roughness`, `metallic`, `emissive`) instead of creating a texture object.
Note
----
This class should *not* be instantiated directly.
Parameters
----------
color : tuple | None, optional
Diffuse color of the surface. Shortcut for `diffuse_texture` with a single color.
opacity : float | None, optional
Opacity of the surface. Shortcut for `opacity_texture` with a single value.
roughness : float | None, optional
Roughness of the surface. Shortcut for `roughness_texture` with a single value.
metallic : float | None, optional
Metallicness of the surface. Shortcut for `metallic_texture` with a single value.
emissive : tuple | None, optional
Emissive color of the surface. Shortcut for `emissive_texture` with a single color.
ior : float, optional
Index of Refraction.
opacity_texture : gs.textures.Texture | None, optional
Opacity texture of the surface.
roughness_texture : gs.textures.Texture | None, optional
Roughness texture of the surface.
metallic_texture : gs.textures.Texture | None, optional
Metallic texture of the surface.
normal_texture : gs.textures.Texture | None, optional
Normal texture of the surface.
emissive_texture : gs.textures.Texture | None, optional
Emissive texture of the surface.
default_roughness : float, optional
Default roughness value when `roughness` is not set and the asset does not have a roughness texture. Defaults
to 1.0.
vis_mode : str | None, optional
How the entity should be visualized, e.g.
- 'visual': Render the entity's visual geometry.
- 'collision': Render the entity's collision geometry.
- 'particle': Render the entity's particle representation (if applicable).
- 'sdf': Render the reconstructed surface mesh of the entity's sdf.
- 'recon': Render the reconstructed surface mesh of the entity's particle representation.
smooth : bool, optional
Whether to smooth face normals by interpolating vertex normals.
double_sided : bool | None, optional
Whether to render both sides of the surface. Useful for non-watertight 2D objects. Defaults to True for Cloth
material and False for others.
cutoff : float
The cutoff angle of emission. Defaults to 180.0.
normal_diff_clamp : float, optional
Controls the threshold for computing surface normals by interpolating vertex normals.
recon_backend : str, optional
Backend for surface reconstruction. Possible values are ['splashsurf', 'openvdb']. Defaults to 'splashsurf'.
generate_foam : bool, optional
Whether to generate foam particles for visual effects for particle-based entities.
foam_options : gs.options.FoamOptions, optional
Options for foam generation.
"""
# shortcuts
color: Optional[tuple] = None
opacity: Optional[float] = None
roughness: Optional[float] = None
metallic: Optional[float] = None
emissive: Optional[tuple] = None
ior: Optional[float] = None
# textures (can be either ColorTexture or ImageTexture)
opacity_texture: Optional[Texture] = None
roughness_texture: Optional[Texture] = None
metallic_texture: Optional[Texture] = None
normal_texture: Optional[Texture] = None
emissive_texture: Optional[Texture] = None
default_roughness: float = 1.0
vis_mode: Optional[str] = None
smooth: bool = True
double_sided: Optional[bool] = None
cutoff: float = 180.0
normal_diff_clamp: float = 180.0
recon_backend: str = "splashsurf"
generate_foam: bool = False
foam_options: Optional[FoamOptions] = None
@staticmethod
def shortcut_info(name, map_name):
return f"`{name}` is a shortcut for texture. When {name} is set, {map_name} setting is not allowed."
def __init__(self, **data):
super().__init__(**data)
if self.foam_options is None:
self.foam_options = FoamOptions()
if self.color is not None:
if self.get_texture() is not None:
gs.raise_exception(self.shortcut_info("color", "texture"))
self.set_texture(ColorTexture(color=self.color))
if self.opacity is not None:
if self.opacity_texture is not None:
gs.raise_exception(self.shortcut_info("opacity", "opacity_texture"))
self.opacity_texture = ColorTexture(color=(self.opacity,))
if self.roughness is not None:
if self.roughness_texture is not None:
gs.raise_exception(self.shortcut_info("roughness", "roughness_texture"))
self.roughness_texture = ColorTexture(color=(self.roughness,))
if self.metallic is not None:
if self.metallic_texture is not None:
gs.raise_exception(self.shortcut_info("metallic", "metallic_texture"))
self.metallic_texture = ColorTexture(color=(self.metallic,))
if self.emissive is not None:
if self.emissive_texture is not None:
gs.raise_exception(self.shortcut_info("emissive", "emissive_texture"))
self.emissive_texture = ColorTexture(color=self.emissive)
color_texture = self.get_texture()
if color_texture is not None:
opacity_texture = color_texture.check_dim(3)
if self.opacity_texture is None:
self.opacity_texture = opacity_texture
if self.emissive_texture is not None:
opacity_texture = self.emissive_texture.check_dim(3)
if self.opacity_texture is None:
self.opacity_texture = opacity_texture
def update_texture(
self,
color_texture=None,
opacity_texture=None,
roughness_texture=None,
metallic_texture=None,
normal_texture=None,
emissive_texture=None,
ior=None,
double_sided=None,
force=False,
):
"""
Update the surface textures using given attributes.
Note that if the surface already contains corresponding textures, the existing one have a higher priority and won't be overridden. Force overriding can be enable by setting force=True,
"""
# diffuse map (or specular for glass and emissive for emission)
if self.get_texture() is None or force:
if color_texture is not None:
self.set_texture(color_texture)
elif not force:
self.set_texture(ColorTexture())
# update opacity
if self.opacity_texture is None or force:
if opacity_texture is not None:
self.opacity_texture = opacity_texture
elif not force:
self.opacity_texture = ColorTexture(color=(1.0,))
# update roughness
if self.roughness_texture is None or force:
if roughness_texture is not None:
self.roughness_texture = roughness_texture
elif not force:
self.roughness_texture = ColorTexture(color=(self.default_roughness,))
# update metallic
if self.metallic_texture is None or force:
if metallic_texture is not None:
self.metallic_texture = metallic_texture
# update normal
if self.normal_texture is None or force:
if normal_texture is not None:
self.normal_texture = normal_texture
# update emissive
if self.emissive_texture is None or force:
if emissive_texture is not None:
self.emissive_texture = emissive_texture
# update ior
if self.ior is None or force:
if ior is not None:
self.ior = ior
elif not force:
self.ior = 1.5
# update double sided
if self.double_sided is None or force:
if double_sided is not None:
self.double_sided = double_sided
def requires_uv(self):
textures = (
self.get_texture(),
self.opacity_texture,
self.roughness_texture,
self.metallic_texture,
self.normal_texture,
self.emissive_texture,
)
return any(texture is not None and texture.requires_uv() for texture in textures)
def get_rgba(self, batch=False):
all_textures = []
for texture in (
self.get_texture() if self.emissive_texture is None else self.emissive_texture,
self.opacity_texture,
):
textures = texture.textures if isinstance(texture, BatchTexture) else [texture]
all_textures.append(textures if batch else textures[:1])
color_textures, opacity_textures = all_textures
rgba_textures = []
num_colors = len(color_textures)
num_opacities = len(opacity_textures)
num_rgba = num_colors * num_opacities // math.gcd(num_colors, num_opacities)
for i in range(num_rgba):
color_texture = color_textures[i % num_colors]
opacity_texture = opacity_textures[i % num_opacities]
if isinstance(color_texture, ColorTexture):
if isinstance(opacity_texture, ColorTexture):
rgba_texture = ColorTexture(color=(*color_texture.color, *opacity_texture.color))
elif isinstance(opacity_texture, ImageTexture) and opacity_texture.image_array is not None:
rgb_color = mu.color_f32_to_u8(color_texture.color)
rgb_array = np.full((*opacity_texture.image_array.shape[:2], 3), rgb_color, dtype=np.uint8)
rgba_array = np.dstack((rgb_array, opacity_texture.image_array))
rgba_scale = (1.0, 1.0, 1.0, *opacity_texture.image_color)
rgba_texture = ImageTexture(image_array=rgba_array, image_color=rgba_scale)
else:
rgba_texture = ColorTexture(color=(*color_texture.color, 1.0))
elif isinstance(color_texture, ImageTexture) and color_texture.image_array is not None:
if isinstance(opacity_texture, ColorTexture):
a_color = mu.color_f32_to_u8(opacity_texture.color)
a_array = np.full((*color_texture.image_array.shape[:2],), a_color, dtype=np.uint8)
rgba_array = np.dstack((color_texture.image_array, a_array))
rgba_scale = (*color_texture.image_color, 1.0)
elif (
isinstance(opacity_texture, ImageTexture)
and opacity_texture.image_array is not None
and opacity_texture.image_array.shape[:2] == color_texture.image_array.shape[:2]
):
rgba_array = np.dstack((color_texture.image_array, opacity_texture.image_array))
rgba_scale = (*color_texture.image_color, *opacity_texture.image_color)
else:
if isinstance(opacity_texture, ImageTexture) and opacity_texture.image_array is not None:
gs.logger.warning(
"Color and opacity image shapes do not match. Fall back to fully opaque texture."
)
a_array = np.full(color_texture.image_array.shape[:2], 255, dtype=np.uint8)
rgba_array = np.dstack((color_texture.image_array, a_array))
rgba_scale = (*color_texture.image_color, 1.0)
rgba_texture = ImageTexture(image_array=rgba_array, image_color=rgba_scale)
else:
rgba_texture = ColorTexture(color=(1.0, 1.0, 1.0, 1.0))
rgba_textures.append(rgba_texture)
return BatchTexture(textures=rgba_textures) if batch else rgba_textures[0]
def set_texture(self, texture):
raise NotImplementedError
def get_texture(self):
raise NotImplementedError
def get_emission(self):
return self.emissive_texture
############################ Three surface types ############################
[docs]class Glass(Surface):
"""
Base class for all surfaces types in Genesis.
A ``Surface`` object encapsulates all visual information used for rendering an entity or its sub-components (links, geoms, etc.)
The surface contains different types textures: diffuse_texture, specular_texture, roughness_texture, metallic_texture, normal_texture, and emissive_texture. Each one of them is a `gs.textures.Texture` object.
Tip
---
If any of the textures only has single value (instead of a map), you can use the shortcut attribute (e.g., `color`, `roughness`, `metallic`, `emissive`) instead of creating a texture object.
Note
----
This class should *not* be instantiated directly.
Parameters
----------
color : tuple | None, optional
Diffuse color of the surface. Shortcut for `diffuse_texture` with a single color.
roughness : float | None, optional
Roughness of the surface. Shortcut for `roughness_texture` with a single value.
ior : float, optional
Index of Refraction.
subsurface : bool
Whether apply a simple BSSRDF subsurface to the glass material.
thickness : float | None, optional
The thickness of the top surface when 'subsurface' is set to True, that is, the maximum distance of subsurface scattering. Shortcut for `thickness_texture` with a single value.
metallic : float | None, optional
Metallicness of the surface. Shortcut for `metallic_texture` with a single value.
emissive : tuple | None, optional
Emissive color of the surface. Shortcut for `emissive_texture` with a single color.
specular_texture : gs.textures.Texture | None, optional
Specular texture of the surface.
transmission_texture : gs.textures.Texture | None, optional
Transmission texture of the surface.
opacity_texture : gs.textures.Texture | None, optional
Opacity texture of the surface.
roughness_texture : gs.textures.Texture | None, optional
Roughness texture of the surface.
metallic_texture : gs.textures.Texture | None, optional
Metallic texture of the surface.
normal_texture : gs.textures.Texture | None, optional
Normal texture of the surface.
emissive_texture : gs.textures.Texture | None, optional
Emissive texture of the surface.
thickness_texture : gs.textures.Texture | None, optional
The thickness of the top surface.
default_roughness : float, optional
Default roughness value when `roughness` is not set and the asset does not have a roughness texture. Defaults to 1.0.
vis_mode : str | None, optional
How the entity should be visualized. Possible values are ['visual', 'particle', 'collision', 'sdf', 'recon'].
- 'visual': Render the entity's visual geometry.
- 'collision': Render the entity's collision geometry.
- 'particle': Render the entity's particle representation (if applicable).
- 'sdf': Render the reconstructed surface mesh of the entity's sdf.
- 'recon': Render the reconstructed surface mesh of the entity's particle representation.
smooth : bool, optional
Whether to smooth face normals by interpolating vertex normals.
double_sided : bool | None, optional
Whether to render both sides of the surface. Useful for non-watertight 2D objects. Defaults to True for Cloth material and False for others.
normal_diff_clamp : float, optional
Controls the threshold for computing surface normals by interpolating vertex normals.
recon_backend : str, optional
Backend for surface reconstruction. Possible values are ['splashsurf', 'openvdb'].
generate_foam : bool, optional
Whether to generate foam particles for visual effects for particle-based entities.
foam_options : gs.options.FoamOptions, optional
Options for foam generation.
"""
roughness: float = 0.0
ior: float = 1.5
subsurface: bool = False
thickness: Optional[float] = None
thickness_texture: Optional[Texture] = None
specular_texture: Optional[Texture] = None
diffuse_texture: Optional[Texture] = None
transmission_texture: Optional[Texture] = None
def __init__(self, **data):
super().__init__(**data)
if self.thickness is not None:
if self.thickness_texture is not None:
gs.raise_exception(self.shortcut_info("thickness", "thickness_texture"))
self.thickness_texture = ColorTexture(color=(self.thickness,))
def get_texture(self):
return self.specular_texture
def set_texture(self, texture):
# for simplicity, let's use the same texture for specular and transmission
self.specular_texture = texture
self.transmission_texture = texture
[docs]class Plastic(Surface):
"""
Plastic surface is the most basic type of surface.
Parameters
----------
color : tuple | None, optional
Diffuse color of the surface. Shortcut for `diffuse_texture` with a single color.
roughness : float | None, optional
Roughness of the surface. Shortcut for `roughness_texture` with a single value.
metallic : float | None, optional
Metallicness of the surface. Shortcut for `metallic_texture` with a single value.
emissive : tuple | None, optional
Emissive color of the surface. Shortcut for `emissive_texture` with a single color.
ior : float, optional
Index of Refraction.
diffuse_texture : gs.textures.Texture | None, optional
Diffuse (basic color) texture of the surface.
specular_texture : gs.textures.Texture | None, optional
Specular texture of the surface.
opacity_texture : gs.textures.Texture | None, optional
Opacity texture of the surface.
roughness_texture : gs.textures.Texture | None, optional
Roughness texture of the surface.
metallic_texture : gs.textures.Texture | None, optional
Metallic texture of the surface.
normal_texture : gs.textures.Texture | None, optional
Normal texture of the surface.
emissive_texture : gs.textures.Texture | None, optional
Emissive texture of the surface.
default_roughness : float, optional
Default roughness value when `roughness` is not set and the asset does not have a roughness texture. Defaults to 1.0.
vis_mode : str | None, optional
How the entity should be visualized. Possible values are ['visual', 'particle', 'collision', 'sdf', 'recon'].
- 'visual': Render the entity's visual geometry.
- 'collision': Render the entity's collision geometry.
- 'particle': Render the entity's particle representation (if applicable).
- 'sdf': Render the reconstructed surface mesh of the entity's sdf.
- 'recon': Render the reconstructed surface mesh of the entity's particle representation.
smooth : bool, optional
Whether to smooth face normals by interpolating vertex normals.
double_sided : bool | None, optional
Whether to render both sides of the surface. Useful for non-watertight 2D objects. Defaults to True for Cloth material and False for others.
normal_diff_clamp : float, optional
Controls the threshold for computing surface normals by interpolating vertex normals.
recon_backend : str, optional
Backend for surface reconstruction. Possible values are ['splashsurf', 'openvdb'].
generate_foam : bool, optional
Whether to generate foam particles for visual effects for particle-based entities.
foam_options : gs.options.FoamOptions, optional
Options for foam generation.
"""
ior: float = 1.0
diffuse_texture: Optional[Texture] = None
specular_texture: Optional[Texture] = None
def get_texture(self):
return self.diffuse_texture
def set_texture(self, texture):
self.diffuse_texture = texture
class BSDF(Surface):
"""
Plastic surface is the most basic type of surface.
Parameters
----------
color : tuple | None, optional
Diffuse color of the surface. Shortcut for `diffuse_texture` with a single color.
roughness : float | None, optional
Roughness of the surface. Shortcut for `roughness_texture` with a single value.
metallic : float | None, optional
Metallicness of the surface. Shortcut for `metallic_texture` with a single value.
emissive : tuple | None, optional
Emissive color of the surface. Shortcut for `emissive_texture` with a single color.
ior : float, optional
Index of Refraction.
diffuse_texture : gs.textures.Texture | None, optional
Diffuse (basic color) texture of the surface.
specular_tint : gs.textures.Texture | None, optional
Specular texture of the surface.
opacity_texture : gs.textures.Texture | None, optional
Opacity texture of the surface.
roughness_texture : gs.textures.Texture | None, optional
Roughness texture of the surface.
metallic_texture : gs.textures.Texture | None, optional
Metallic texture of the surface.
normal_texture : gs.textures.Texture | None, optional
Normal texture of the surface.
emissive_texture : gs.textures.Texture | None, optional
Emissive texture of the surface.
default_roughness : float, optional
Default roughness value when `roughness` is not set and the asset does not have a roughness texture. Defaults to 1.0.
vis_mode : str | None, optional
How the entity should be visualized. Possible values are ['visual', 'particle', 'collision', 'sdf', 'recon'].
- 'visual': Render the entity's visual geometry.
- 'collision': Render the entity's collision geometry.
- 'particle': Render the entity's particle representation (if applicable).
- 'sdf': Render the reconstructed surface mesh of the entity's sdf.
- 'recon': Render the reconstructed surface mesh of the entity's particle representation.
smooth : bool, optional
Whether to smooth face normals by interpolating vertex normals.
double_sided : bool | None, optional
Whether to render both sides of the surface. Useful for non-watertight 2D objects. Defaults to True for Cloth material and False for others.
normal_diff_clamp : float, optional
Controls the threshold for computing surface normals by interpolating vertex normals.
recon_backend : str, optional
Backend for surface reconstruction. Possible values are ['splashsurf', 'openvdb'].
generate_foam : bool, optional
Whether to generate foam particles for visual effects for particle-based entities.
foam_options : gs.options.FoamOptions, optional
Options for foam generation.
"""
diffuse_texture: Optional[Texture] = None
specular_trans: float = 0.0
diffuse_trans: float = 0.0
ior: float = 1.0
def get_texture(self):
return self.diffuse_texture
def set_texture(self, texture):
self.diffuse_texture = texture
[docs]class Emission(Surface):
"""
Emission surface. This surface emits light. Note that in Genesis's ray tracing pipeline, lights are not a special type of objects, but simply entities with emission surfaces.
Parameters
----------
emissive : tuple | None, optional
Emissive color of the surface. Shortcut for `emissive_texture` with a single color.
emissive_texture : gs.textures.Texture | None, optional
Emissive texture of the surface.
"""
def get_texture(self):
return self.emissive_texture
def set_texture(self, texture):
self.emissive_texture = texture
############################ Handy shortcuts ############################
[docs]class Default(BSDF):
"""
The default surface type used in Genesis. This is just an alias for `Plastic`.
"""
pass
[docs]class Rough(Plastic):
"""
Shortcut for a rough plastic surface.
"""
roughness: float = 1.0
ior: float = 1.5
[docs]class Smooth(Plastic):
"""
Shortcut for a smooth plastic surface.
"""
roughness: float = 0.1
ior: float = 1.5
[docs]class Reflective(Plastic):
"""
Shortcut for a reflective (smoother than `Smooth`) plastic surface.
"""
roughness: float = 0.01
ior: float = 2.0
[docs]class Collision(Plastic):
"""
Default surface type for collision geometry with a grey color by default.
"""
color: tuple = (0.5, 0.5, 0.5)
def __init__(self, **data):
super().__init__(**data)
self.diffuse_texture = ColorTexture(color=self.color)
[docs]class Water(Glass):
"""
Shortcut for a water surface (using Glass surface with proper values).
"""
color: tuple = (0.61, 0.98, 0.93)
roughness: float = 0.2
ior: float = 1.2
def __init__(self, **data):
super().__init__(**data)
[docs]class Iron(Metal):
"""
Shortcut for an metallic surface with `metal_type = 'iron'`.
"""
metal_type: str = "iron"
[docs]class Aluminium(Metal):
"""
Shortcut for an metallic surface with `metal_type = 'aluminium'`.
"""
metal_type: str = "aluminium"
[docs]class Copper(Metal):
"""
Shortcut for an metallic surface with `metal_type = 'copper'`.
"""
metal_type: str = "copper"
[docs]class Gold(Metal):
"""
Shortcut for an metallic surface with `metal_type = 'gold'`.
"""
metal_type: str = "gold"