Spaces:
Sleeping
Sleeping
"""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 | |
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 | |
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 color(self): | |
"""(3,) float : The light's color. | |
""" | |
return self._color | |
def color(self, value): | |
self._color = format_color_vector(value, 3) | |
def intensity(self): | |
"""float : The light's intensity in candela or lux. | |
""" | |
return self._intensity | |
def intensity(self, value): | |
self._intensity = float(value) | |
def shadow_texture(self): | |
""":class:`.Texture` : A texture used to hold shadow maps for this light. | |
""" | |
return self._shadow_texture | |
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 | |
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 | |
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 | |
def range(self): | |
"""float : The cutoff distance for the light. | |
""" | |
return self._range | |
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 | |
def innerConeAngle(self): | |
"""float : The inner cone angle in radians. | |
""" | |
return self._innerConeAngle | |
def innerConeAngle(self, value): | |
if value < 0.0 or value > self.outerConeAngle: | |
raise ValueError('Invalid value for inner cone angle') | |
self._innerConeAngle = float(value) | |
def outerConeAngle(self): | |
"""float : The outer cone angle in radians. | |
""" | |
return self._outerConeAngle | |
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) | |
def range(self): | |
"""float : The cutoff distance for the light. | |
""" | |
return self._range | |
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'] | |