| | """Punctual light sources as defined by the glTF 2.0 KHR extension at |
| | https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual |
| | |
| | Author: Matthew Matl |
| | """ |
| | import abc |
| | import numpy as np |
| | import six |
| |
|
| | from OpenGL.GL import * |
| |
|
| | from .utils import format_color_vector |
| | from .texture import Texture |
| | from .constants import SHADOW_TEX_SZ |
| | from .camera import OrthographicCamera, PerspectiveCamera |
| |
|
| |
|
| |
|
| | @six.add_metaclass(abc.ABCMeta) |
| | class Light(object): |
| | """Base class for all light objects. |
| | |
| | Parameters |
| | ---------- |
| | color : (3,) float |
| | RGB value for the light's color in linear space. |
| | intensity : float |
| | Brightness of light. The units that this is defined in depend on the |
| | type of light. Point and spot lights use luminous intensity in candela |
| | (lm/sr), while directional lights use illuminance in lux (lm/m2). |
| | name : str, optional |
| | Name of the light. |
| | """ |
| | def __init__(self, |
| | color=None, |
| | intensity=None, |
| | name=None): |
| |
|
| | if color is None: |
| | color = np.ones(3) |
| | if intensity is None: |
| | intensity = 1.0 |
| |
|
| | self.name = name |
| | self.color = color |
| | self.intensity = intensity |
| | self._shadow_camera = None |
| | self._shadow_texture = 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 color(self): |
| | """(3,) float : The light's color. |
| | """ |
| | return self._color |
| |
|
| | @color.setter |
| | def color(self, value): |
| | self._color = format_color_vector(value, 3) |
| |
|
| | @property |
| | def intensity(self): |
| | """float : The light's intensity in candela or lux. |
| | """ |
| | return self._intensity |
| |
|
| | @intensity.setter |
| | def intensity(self, value): |
| | self._intensity = float(value) |
| |
|
| | @property |
| | def shadow_texture(self): |
| | """:class:`.Texture` : A texture used to hold shadow maps for this light. |
| | """ |
| | return self._shadow_texture |
| |
|
| | @shadow_texture.setter |
| | def shadow_texture(self, value): |
| | if self._shadow_texture is not None: |
| | if self._shadow_texture._in_context(): |
| | self._shadow_texture.delete() |
| | self._shadow_texture = value |
| |
|
| | @abc.abstractmethod |
| | def _generate_shadow_texture(self, size=None): |
| | """Generate a shadow texture for this light. |
| | |
| | Parameters |
| | ---------- |
| | size : int, optional |
| | Size of texture map. Must be a positive power of two. |
| | """ |
| | pass |
| |
|
| | @abc.abstractmethod |
| | def _get_shadow_camera(self, scene_scale): |
| | """Generate and return a shadow mapping camera for this light. |
| | |
| | Parameters |
| | ---------- |
| | scene_scale : float |
| | Length of scene's bounding box diagonal. |
| | |
| | Returns |
| | ------- |
| | camera : :class:`.Camera` |
| | The camera used to render shadowmaps for this light. |
| | """ |
| | pass |
| |
|
| |
|
| | class DirectionalLight(Light): |
| | """Directional lights are light sources that act as though they are |
| | infinitely far away and emit light in the direction of the local -z axis. |
| | This light type inherits the orientation of the node that it belongs to; |
| | position and scale are ignored except for their effect on the inherited |
| | node orientation. Because it is at an infinite distance, the light is |
| | not attenuated. Its intensity is defined in lumens per metre squared, |
| | or lux (lm/m2). |
| | |
| | Parameters |
| | ---------- |
| | color : (3,) float, optional |
| | RGB value for the light's color in linear space. Defaults to white |
| | (i.e. [1.0, 1.0, 1.0]). |
| | intensity : float, optional |
| | Brightness of light, in lux (lm/m^2). Defaults to 1.0 |
| | name : str, optional |
| | Name of the light. |
| | """ |
| |
|
| | def __init__(self, |
| | color=None, |
| | intensity=None, |
| | name=None): |
| | super(DirectionalLight, self).__init__( |
| | color=color, |
| | intensity=intensity, |
| | name=name, |
| | ) |
| |
|
| | def _generate_shadow_texture(self, size=None): |
| | """Generate a shadow texture for this light. |
| | |
| | Parameters |
| | ---------- |
| | size : int, optional |
| | Size of texture map. Must be a positive power of two. |
| | """ |
| | if size is None: |
| | size = SHADOW_TEX_SZ |
| | self.shadow_texture = Texture(width=size, height=size, |
| | source_channels='D', data_format=GL_FLOAT) |
| |
|
| | def _get_shadow_camera(self, scene_scale): |
| | """Generate and return a shadow mapping camera for this light. |
| | |
| | Parameters |
| | ---------- |
| | scene_scale : float |
| | Length of scene's bounding box diagonal. |
| | |
| | Returns |
| | ------- |
| | camera : :class:`.Camera` |
| | The camera used to render shadowmaps for this light. |
| | """ |
| | return OrthographicCamera( |
| | znear=0.01 * scene_scale, |
| | zfar=10 * scene_scale, |
| | xmag=scene_scale, |
| | ymag=scene_scale |
| | ) |
| |
|
| |
|
| | class PointLight(Light): |
| | """Point lights emit light in all directions from their position in space; |
| | rotation and scale are ignored except for their effect on the inherited |
| | node position. The brightness of the light attenuates in a physically |
| | correct manner as distance increases from the light's position (i.e. |
| | brightness goes like the inverse square of the distance). Point light |
| | intensity is defined in candela, which is lumens per square radian (lm/sr). |
| | |
| | Parameters |
| | ---------- |
| | color : (3,) float |
| | RGB value for the light's color in linear space. |
| | intensity : float |
| | Brightness of light in candela (lm/sr). |
| | range : float |
| | Cutoff distance at which light's intensity may be considered to |
| | have reached zero. If None, the range is assumed to be infinite. |
| | name : str, optional |
| | Name of the light. |
| | """ |
| |
|
| | def __init__(self, |
| | color=None, |
| | intensity=None, |
| | range=None, |
| | name=None): |
| | super(PointLight, self).__init__( |
| | color=color, |
| | intensity=intensity, |
| | name=name, |
| | ) |
| | self.range = range |
| |
|
| | @property |
| | def range(self): |
| | """float : The cutoff distance for the light. |
| | """ |
| | return self._range |
| |
|
| | @range.setter |
| | def range(self, value): |
| | if value is not None: |
| | value = float(value) |
| | if value <= 0: |
| | raise ValueError('Range must be > 0') |
| | self._range = value |
| | self._range = value |
| |
|
| | def _generate_shadow_texture(self, size=None): |
| | """Generate a shadow texture for this light. |
| | |
| | Parameters |
| | ---------- |
| | size : int, optional |
| | Size of texture map. Must be a positive power of two. |
| | """ |
| | raise NotImplementedError('Shadows not implemented for point lights') |
| |
|
| | def _get_shadow_camera(self, scene_scale): |
| | """Generate and return a shadow mapping camera for this light. |
| | |
| | Parameters |
| | ---------- |
| | scene_scale : float |
| | Length of scene's bounding box diagonal. |
| | |
| | Returns |
| | ------- |
| | camera : :class:`.Camera` |
| | The camera used to render shadowmaps for this light. |
| | """ |
| | raise NotImplementedError('Shadows not implemented for point lights') |
| |
|
| |
|
| | class SpotLight(Light): |
| | """Spot lights emit light in a cone in the direction of the local -z axis. |
| | The angle and falloff of the cone is defined using two numbers, the |
| | ``innerConeAngle`` and ``outerConeAngle``. |
| | As with point lights, the brightness |
| | also attenuates in a physically correct manner as distance increases from |
| | the light's position (i.e. brightness goes like the inverse square of the |
| | distance). Spot light intensity refers to the brightness inside the |
| | ``innerConeAngle`` (and at the location of the light) and is defined in |
| | candela, which is lumens per square radian (lm/sr). A spot light's position |
| | and orientation are inherited from its node transform. Inherited scale does |
| | not affect cone shape, and is ignored except for its effect on position |
| | and orientation. |
| | |
| | Parameters |
| | ---------- |
| | color : (3,) float |
| | RGB value for the light's color in linear space. |
| | intensity : float |
| | Brightness of light in candela (lm/sr). |
| | range : float |
| | Cutoff distance at which light's intensity may be considered to |
| | have reached zero. If None, the range is assumed to be infinite. |
| | innerConeAngle : float |
| | Angle, in radians, from centre of spotlight where falloff begins. |
| | Must be greater than or equal to ``0`` and less |
| | than ``outerConeAngle``. Defaults to ``0``. |
| | outerConeAngle : float |
| | Angle, in radians, from centre of spotlight where falloff ends. |
| | Must be greater than ``innerConeAngle`` and less than or equal to |
| | ``PI / 2.0``. Defaults to ``PI / 4.0``. |
| | name : str, optional |
| | Name of the light. |
| | """ |
| |
|
| | def __init__(self, |
| | color=None, |
| | intensity=None, |
| | range=None, |
| | innerConeAngle=0.0, |
| | outerConeAngle=(np.pi / 4.0), |
| | name=None): |
| | super(SpotLight, self).__init__( |
| | name=name, |
| | color=color, |
| | intensity=intensity, |
| | ) |
| | self.outerConeAngle = outerConeAngle |
| | self.innerConeAngle = innerConeAngle |
| | self.range = range |
| |
|
| | @property |
| | def innerConeAngle(self): |
| | """float : The inner cone angle in radians. |
| | """ |
| | return self._innerConeAngle |
| |
|
| | @innerConeAngle.setter |
| | def innerConeAngle(self, value): |
| | if value < 0.0 or value > self.outerConeAngle: |
| | raise ValueError('Invalid value for inner cone angle') |
| | self._innerConeAngle = float(value) |
| |
|
| | @property |
| | def outerConeAngle(self): |
| | """float : The outer cone angle in radians. |
| | """ |
| | return self._outerConeAngle |
| |
|
| | @outerConeAngle.setter |
| | def outerConeAngle(self, value): |
| | if value < 0.0 or value > np.pi / 2.0 + 1e-9: |
| | raise ValueError('Invalid value for outer cone angle') |
| | self._outerConeAngle = float(value) |
| |
|
| | @property |
| | def range(self): |
| | """float : The cutoff distance for the light. |
| | """ |
| | return self._range |
| |
|
| | @range.setter |
| | def range(self, value): |
| | if value is not None: |
| | value = float(value) |
| | if value <= 0: |
| | raise ValueError('Range must be > 0') |
| | self._range = value |
| | self._range = value |
| |
|
| | def _generate_shadow_texture(self, size=None): |
| | """Generate a shadow texture for this light. |
| | |
| | Parameters |
| | ---------- |
| | size : int, optional |
| | Size of texture map. Must be a positive power of two. |
| | """ |
| | if size is None: |
| | size = SHADOW_TEX_SZ |
| | self.shadow_texture = Texture(width=size, height=size, |
| | source_channels='D', data_format=GL_FLOAT) |
| |
|
| | def _get_shadow_camera(self, scene_scale): |
| | """Generate and return a shadow mapping camera for this light. |
| | |
| | Parameters |
| | ---------- |
| | scene_scale : float |
| | Length of scene's bounding box diagonal. |
| | |
| | Returns |
| | ------- |
| | camera : :class:`.Camera` |
| | The camera used to render shadowmaps for this light. |
| | """ |
| | return PerspectiveCamera( |
| | znear=0.01 * scene_scale, |
| | zfar=10 * scene_scale, |
| | yfov=np.clip(2 * self.outerConeAngle + np.pi / 16.0, 0.0, np.pi), |
| | aspectRatio=1.0 |
| | ) |
| |
|
| |
|
| | __all__ = ['Light', 'DirectionalLight', 'SpotLight', 'PointLight'] |
| |
|