Source code for pyrender.light

"""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



[docs]@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
[docs]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 )
[docs]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')
[docs]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']