OpenMotionLab/MotionGPT
118
1"""Punctual light sources as defined by the glTF 2.0 KHR extension at2https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual3 4Author: Matthew Matl5"""6import abc7import numpy as np8import six9 10from OpenGL.GL import *11 12from .utils import format_color_vector13from .texture import Texture14from .constants import SHADOW_TEX_SZ15from .camera import OrthographicCamera, PerspectiveCamera16 17 18 19@six.add_metaclass(abc.ABCMeta)20class Light(object):21 """Base class for all light objects.22 23 Parameters24 ----------25 color : (3,) float26 RGB value for the light's color in linear space.27 intensity : float28 Brightness of light. The units that this is defined in depend on the29 type of light. Point and spot lights use luminous intensity in candela30 (lm/sr), while directional lights use illuminance in lux (lm/m2).31 name : str, optional32 Name of the light.33 """34 def __init__(self,35 color=None,36 intensity=None,37 name=None):38 39 if color is None:40 color = np.ones(3)41 if intensity is None:42 intensity = 1.043 44 self.name = name45 self.color = color46 self.intensity = intensity47 self._shadow_camera = None48 self._shadow_texture = None49 50 @property51 def name(self):52 """str : The user-defined name of this object.53 """54 return self._name55 56 @name.setter57 def name(self, value):58 if value is not None:59 value = str(value)60 self._name = value61 62 @property63 def color(self):64 """(3,) float : The light's color.65 """66 return self._color67 68 @color.setter69 def color(self, value):70 self._color = format_color_vector(value, 3)71 72 @property73 def intensity(self):74 """float : The light's intensity in candela or lux.75 """76 return self._intensity77 78 @intensity.setter79 def intensity(self, value):80 self._intensity = float(value)81 82 @property83 def shadow_texture(self):84 """:class:`.Texture` : A texture used to hold shadow maps for this light.85 """86 return self._shadow_texture87 88 @shadow_texture.setter89 def shadow_texture(self, value):90 if self._shadow_texture is not None:91 if self._shadow_texture._in_context():92 self._shadow_texture.delete()93 self._shadow_texture = value94 95 @abc.abstractmethod96 def _generate_shadow_texture(self, size=None):97 """Generate a shadow texture for this light.98 99 Parameters100 ----------101 size : int, optional102 Size of texture map. Must be a positive power of two.103 """104 pass105 106 @abc.abstractmethod107 def _get_shadow_camera(self, scene_scale):108 """Generate and return a shadow mapping camera for this light.109 110 Parameters111 ----------112 scene_scale : float113 Length of scene's bounding box diagonal.114 115 Returns116 -------117 camera : :class:`.Camera`118 The camera used to render shadowmaps for this light.119 """120 pass121 122 123class DirectionalLight(Light):124 """Directional lights are light sources that act as though they are125 infinitely far away and emit light in the direction of the local -z axis.126 This light type inherits the orientation of the node that it belongs to;127 position and scale are ignored except for their effect on the inherited128 node orientation. Because it is at an infinite distance, the light is129 not attenuated. Its intensity is defined in lumens per metre squared,130 or lux (lm/m2).131 132 Parameters133 ----------134 color : (3,) float, optional135 RGB value for the light's color in linear space. Defaults to white136 (i.e. [1.0, 1.0, 1.0]).137 intensity : float, optional138 Brightness of light, in lux (lm/m^2). Defaults to 1.0139 name : str, optional140 Name of the light.141 """142 143 def __init__(self,144 color=None,145 intensity=None,146 name=None):147 super(DirectionalLight, self).__init__(148 color=color,149 intensity=intensity,150 name=name,151 )152 153 def _generate_shadow_texture(self, size=None):154 """Generate a shadow texture for this light.155 156 Parameters157 ----------158 size : int, optional159 Size of texture map. Must be a positive power of two.160 """161 if size is None:162 size = SHADOW_TEX_SZ163 self.shadow_texture = Texture(width=size, height=size,164 source_channels='D', data_format=GL_FLOAT)165 166 def _get_shadow_camera(self, scene_scale):167 """Generate and return a shadow mapping camera for this light.168 169 Parameters170 ----------171 scene_scale : float172 Length of scene's bounding box diagonal.173 174 Returns175 -------176 camera : :class:`.Camera`177 The camera used to render shadowmaps for this light.178 """179 return OrthographicCamera(180 znear=0.01 * scene_scale,181 zfar=10 * scene_scale,182 xmag=scene_scale,183 ymag=scene_scale184 )185 186 187class PointLight(Light):188 """Point lights emit light in all directions from their position in space;189 rotation and scale are ignored except for their effect on the inherited190 node position. The brightness of the light attenuates in a physically191 correct manner as distance increases from the light's position (i.e.192 brightness goes like the inverse square of the distance). Point light193 intensity is defined in candela, which is lumens per square radian (lm/sr).194 195 Parameters196 ----------197 color : (3,) float198 RGB value for the light's color in linear space.199 intensity : float200 Brightness of light in candela (lm/sr).201 range : float202 Cutoff distance at which light's intensity may be considered to203 have reached zero. If None, the range is assumed to be infinite.204 name : str, optional205 Name of the light.206 """207 208 def __init__(self,209 color=None,210 intensity=None,211 range=None,212 name=None):213 super(PointLight, self).__init__(214 color=color,215 intensity=intensity,216 name=name,217 )218 self.range = range219 220 @property221 def range(self):222 """float : The cutoff distance for the light.223 """224 return self._range225 226 @range.setter227 def range(self, value):228 if value is not None:229 value = float(value)230 if value <= 0:231 raise ValueError('Range must be > 0')232 self._range = value233 self._range = value234 235 def _generate_shadow_texture(self, size=None):236 """Generate a shadow texture for this light.237 238 Parameters239 ----------240 size : int, optional241 Size of texture map. Must be a positive power of two.242 """243 raise NotImplementedError('Shadows not implemented for point lights')244 245 def _get_shadow_camera(self, scene_scale):246 """Generate and return a shadow mapping camera for this light.247 248 Parameters249 ----------250 scene_scale : float251 Length of scene's bounding box diagonal.252 253 Returns254 -------255 camera : :class:`.Camera`256 The camera used to render shadowmaps for this light.257 """258 raise NotImplementedError('Shadows not implemented for point lights')259 260 261class SpotLight(Light):262 """Spot lights emit light in a cone in the direction of the local -z axis.263 The angle and falloff of the cone is defined using two numbers, the264 ``innerConeAngle`` and ``outerConeAngle``.265 As with point lights, the brightness266 also attenuates in a physically correct manner as distance increases from267 the light's position (i.e. brightness goes like the inverse square of the268 distance). Spot light intensity refers to the brightness inside the269 ``innerConeAngle`` (and at the location of the light) and is defined in270 candela, which is lumens per square radian (lm/sr). A spot light's position271 and orientation are inherited from its node transform. Inherited scale does272 not affect cone shape, and is ignored except for its effect on position273 and orientation.274 275 Parameters276 ----------277 color : (3,) float278 RGB value for the light's color in linear space.279 intensity : float280 Brightness of light in candela (lm/sr).281 range : float282 Cutoff distance at which light's intensity may be considered to283 have reached zero. If None, the range is assumed to be infinite.284 innerConeAngle : float285 Angle, in radians, from centre of spotlight where falloff begins.286 Must be greater than or equal to ``0`` and less287 than ``outerConeAngle``. Defaults to ``0``.288 outerConeAngle : float289 Angle, in radians, from centre of spotlight where falloff ends.290 Must be greater than ``innerConeAngle`` and less than or equal to291 ``PI / 2.0``. Defaults to ``PI / 4.0``.292 name : str, optional293 Name of the light.294 """295 296 def __init__(self,297 color=None,298 intensity=None,299 range=None,300 innerConeAngle=0.0,301 outerConeAngle=(np.pi / 4.0),302 name=None):303 super(SpotLight, self).__init__(304 name=name,305 color=color,306 intensity=intensity,307 )308 self.outerConeAngle = outerConeAngle309 self.innerConeAngle = innerConeAngle310 self.range = range311 312 @property313 def innerConeAngle(self):314 """float : The inner cone angle in radians.315 """316 return self._innerConeAngle317 318 @innerConeAngle.setter319 def innerConeAngle(self, value):320 if value < 0.0 or value > self.outerConeAngle:321 raise ValueError('Invalid value for inner cone angle')322 self._innerConeAngle = float(value)323 324 @property325 def outerConeAngle(self):326 """float : The outer cone angle in radians.327 """328 return self._outerConeAngle329 330 @outerConeAngle.setter331 def outerConeAngle(self, value):332 if value < 0.0 or value > np.pi / 2.0 + 1e-9:333 raise ValueError('Invalid value for outer cone angle')334 self._outerConeAngle = float(value)335 336 @property337 def range(self):338 """float : The cutoff distance for the light.339 """340 return self._range341 342 @range.setter343 def range(self, value):344 if value is not None:345 value = float(value)346 if value <= 0:347 raise ValueError('Range must be > 0')348 self._range = value349 self._range = value350 351 def _generate_shadow_texture(self, size=None):352 """Generate a shadow texture for this light.353 354 Parameters355 ----------356 size : int, optional357 Size of texture map. Must be a positive power of two.358 """359 if size is None:360 size = SHADOW_TEX_SZ361 self.shadow_texture = Texture(width=size, height=size,362 source_channels='D', data_format=GL_FLOAT)363 364 def _get_shadow_camera(self, scene_scale):365 """Generate and return a shadow mapping camera for this light.366 367 Parameters368 ----------369 scene_scale : float370 Length of scene's bounding box diagonal.371 372 Returns373 -------374 camera : :class:`.Camera`375 The camera used to render shadowmaps for this light.376 """377 return PerspectiveCamera(378 znear=0.01 * scene_scale,379 zfar=10 * scene_scale,380 yfov=np.clip(2 * self.outerConeAngle + np.pi / 16.0, 0.0, np.pi),381 aspectRatio=1.0382 )383 384 385__all__ = ['Light', 'DirectionalLight', 'SpotLight', 'PointLight']386 