SLProject  4.2.000
A platform independent 3D computer graphics framework for desktop OS, Android, iOS and online in web browsers
SLLightSpot.cpp
Go to the documentation of this file.
1 /**
2  * \file SLLightSpot.cpp
3  * \authors Marcus Hudritsch
4  * \date July 2014
5  * \authors Marcus Hudritsch
6  * \copyright http://opensource.org/licenses/GPL-3.0
7  * \remarks Please use clangformat to format the code. See more code style on
8  * https://github.com/cpvrlab/SLProject4/wiki/SLProject-Coding-Style
9 */
10 
11 #include <SLLightSpot.h>
12 #include <SLRay.h>
13 #include <SLScene.h>
14 #include <SLSceneView.h>
15 #include <SLShadowMap.h>
16 #include <SLSphere.h>
17 #include <SLSpheric.h>
18 
19 //-----------------------------------------------------------------------------
20 /**
21  * @brief Construct a new SLLightSpot::SLLightSpot object
22  * @remarks It is important that during instantiation NO OpenGL functions (gl*)
23  * get called because this constructor will be most probably called in a parallel
24  * thread from within an SLScene::registerAssetsToLoad or SLScene::assemble
25  * function. All objects that get rendered have to do their OpenGL initialization
26  * when they are used the first time during rendering in the main thread.
27  * @param assetMgr AssetManager that will own the light mesh
28  * @param s SLScene pointer
29  * @param radius Radius of the spot light sphere
30  * @param spotAngleDEG Spot cone shine angle in degrees
31  * @param hasMesh Boolean if a mesh should be created and shown
32  */
34  SLScene* s,
35  SLfloat radius,
36  SLfloat spotAngleDEG,
37  SLbool hasMesh)
38  : SLNode("LightSpot Node")
39 {
40  _radius = radius;
41  _samples.samples(1, 1, false);
42  spotCutOffDEG(spotAngleDEG);
43 
44  if (hasMesh)
45  {
46  SLMaterial* mat = new SLMaterial(assetMgr,
47  "LightSpot Mesh Mat",
50  if (spotAngleDEG < 180.0f)
51  addMesh(new SLSpheric(assetMgr,
52  radius,
53  0.0f,
54  spotAngleDEG,
55  16,
56  16,
57  "LightSpot Mesh",
58  mat));
59  else
60  addMesh(new SLSphere(assetMgr,
61  radius,
62  16,
63  16,
64  "LightSpot Mesh",
65  mat));
66  _castsShadows = false;
67  }
68 
69  init(s);
70 }
71 //-----------------------------------------------------------------------------
73  SLScene* s,
74  SLfloat posx,
75  SLfloat posy,
76  SLfloat posz,
77  SLfloat radius,
78  SLfloat spotAngleDEG,
79  SLfloat ambiPower,
80  SLfloat diffPower,
81  SLfloat specPower,
82  SLbool hasMesh)
83  : SLNode("LightSpot Node"),
84  SLLight(ambiPower, diffPower, specPower)
85 {
86  _radius = radius;
87  _samples.samples(1, 1, false);
88  _castsShadows = false;
89  spotCutOffDEG(spotAngleDEG);
90 
91  translate(posx, posy, posz, TS_object);
92 
93  if (hasMesh)
94  {
95  SLMaterial* mat = new SLMaterial(assetMgr,
96  "LightSpot Mesh Mat",
99  if (spotAngleDEG < 180.0f)
100  addMesh(new SLSpheric(assetMgr,
101  radius,
102  0.0f,
103  spotAngleDEG,
104  32,
105  32,
106  "LightSpot Mesh",
107  mat));
108  else
109  addMesh(new SLSphere(assetMgr,
110  radius,
111  32,
112  32,
113  "LightSpot Mesh",
114  mat));
115  }
116  init(s);
117 }
118 //-----------------------------------------------------------------------------
120 {
121  delete _shadowMap;
122 }
123 //-----------------------------------------------------------------------------
124 /*!
125 SLLightSpot::init sets the light id, the light states & creates an emissive mat.
126 */
128 {
129  // Check if OpenGL lights are available
130  if (s->lights().size() >= SL_MAX_LIGHTS)
131  SL_EXIT_MSG("Max. NO. of lights is exceeded!");
132 
133  // Add the light to the lights array of the scene
134  if (_id == -1)
135  {
136  _id = (SLint)s->lights().size();
137  s->lights().push_back(this);
138  }
139 
140  // Set emissive light material to the lights diffuse color
141  if (_mesh)
142  if (_mesh->mat())
144 }
145 //-----------------------------------------------------------------------------
146 /*!
147 SLLightSpot::hitRec calls the recursive node intersection.
148 */
150 {
151  // do not intersect shadow rays
152  if (ray->type == SHADOW) return false;
153 
154  // only allow intersection with primary rays (no lights in reflections)
155  if (ray->type != PRIMARY) return false;
156 
157  // call the intersection routine of the node
158  return SLNode::hitRec(ray);
159 }
160 //-----------------------------------------------------------------------------
161 //! SLLightSpot::statsRec updates the statistic parameters
163 {
164  stats.numBytes += sizeof(SLLightSpot);
165  stats.numBytes += _samples.sizeInBytes();
166  SLNode::statsRec(stats);
167 }
168 //-----------------------------------------------------------------------------
169 /*!
170 SLLightSpot::drawMesh sets the light states and calls then the drawMesh
171 method of its node.
172 */
174 {
175  if (_id != -1)
176  {
177  // Set emissive light mesh material to the lights diffuse color
178  if (_mesh)
179  {
180  if (_mesh->mat())
182 
183  // now draw the single mesh of the node
185  }
186 
187  // Draw the volume affected by the shadow map
188  if (_createsShadows && _isOn && sv->s()->singleNodeSelected() == this)
189  {
191  _shadowMap->drawRays();
192  }
193  }
194 }
195 //-----------------------------------------------------------------------------
196 /*! Creates an fixed sized standard shadow map for the spotlight.
197  * \param lightClipNear The light frustums near clipping distance
198  * \param lightClipFar The light frustums near clipping distance
199  * \param size Ignored for spot lights
200  * \param texSize Shadow texture map size
201  */
202 void SLLightSpot::createShadowMap(float lightClipNear,
203  float lightClipFar,
204  SLVec2f size,
205  SLVec2i texSize)
206 {
207  if (!_shadowMap)
208  delete _shadowMap;
209 
210  _shadowMap = new SLShadowMap(this,
211  lightClipNear,
212  lightClipFar,
213  size,
214  texSize);
215 }
216 //-----------------------------------------------------------------------------
217 /*! Creates an automatic sized shadow map for the spot light.
218  * \param camera Pointer to the camera for witch the shadow map gets sized
219  * \param texSize Shadow texture map size
220  * \param numCascades This value is ignored (default 0)
221  */
223  SLVec2i texSize,
224  int numCascades)
225 {
226  (void)numCascades;
227  if (!_shadowMap)
228  delete _shadowMap;
229 
230  _shadowMap = new SLShadowMap(this,
231  camera,
232  texSize,
233  0);
234 }
235 //-----------------------------------------------------------------------------
236 /*!
237 SLLightSpot::shadowTest returns 0.0 if the hit point is completely shaded and
238 1.0 if it is 100% lighted. A return value in between is calculate by the ratio
239 of the shadow rays not blocked to the total number of casted shadow rays.
240 */
241 SLfloat SLLightSpot::shadowTest(SLRay* ray, // ray of hit point
242  const SLVec3f& L, // vector from hit point to light
243  SLfloat lightDist, // distance to light
244  SLNode* root3D)
245 {
246  if (_samples.samples() == 1)
247  {
248  // define shadow ray and shoot
249  SLRay shadowRay(lightDist, L, ray);
250  root3D->hitRec(&shadowRay);
251 
252  if (shadowRay.length < lightDist && shadowRay.hitMesh)
253  {
254  // Handle shadow value of transparent materials
255  if (shadowRay.hitMesh->mat()->hasAlpha())
256  {
257  shadowRay.hitMesh->preShade(&shadowRay);
258  SLfloat shadowTransp = Utils::abs(shadowRay.dir.dot(shadowRay.hitNormal));
259  return shadowTransp * shadowRay.hitMesh->mat()->kt();
260  }
261  else
262  return 0.0f;
263  }
264  else
265  return 1.0f;
266  }
267  else // do light sampling for soft shadows
268  {
269  SLVec3f C(updateAndGetWM().translation()); // Center of light
270  SLVec3f LightX, LightY; // main axis of sample plane
271  SLfloat lighted = 0.0f; // return value
272  SLfloat invSamples = 1.0f / (_samples.samples());
273  SLbool outerCircleIsLighting = true;
274  SLbool innerCircleIsNotLighting = true;
275 
276  // Build normalized plain vectors X and Y that are perpendicular to L (=Z)
277  if (fabs(L.x) >= fabs(L.y))
278  {
279  SLfloat invLength = 1.0f / sqrt(L.x * L.x + L.z * L.z);
280  LightX.set(L.z * invLength, 0, -L.x * invLength);
281  }
282  else
283  {
284  SLfloat invLength = 1.0f / sqrt(L.y * L.y + L.z * L.z);
285  LightX.set(0, L.z * invLength, -L.y * invLength);
286  }
287  LightY.cross(L, LightX);
288  LightY *= _radius;
289  LightX *= _radius;
290 
291  // Loop over radius r and angle phi of light circle
292  for (SLint iR = (SLint)_samples.samplesX() - 1; iR >= 0; --iR)
293  {
294  for (SLint iPhi = (SLint)_samples.samplesY() - 1; iPhi >= 0; --iPhi)
295  {
296  SLVec2f discPos(_samples.point((SLuint)iR, (SLuint)iPhi));
297 
298  // calculate disc position and vector LDisc to it
299  SLVec3f conePos(C + discPos.x * LightX + discPos.y * LightY);
300  SLVec3f LDisc(conePos - ray->hitPoint);
301  LDisc.normalize();
302 
303  SLRay shadowRay(lightDist, LDisc, ray);
304 
305  root3D->hitRec(&shadowRay);
306 
307  if (shadowRay.length < lightDist)
308  outerCircleIsLighting = false;
309  else
310  {
311  lighted += invSamples; // sum up the light
312  innerCircleIsNotLighting = false;
313  }
314  }
315 
316  // Early break 1:
317  // If the outer circle of shadow rays where not blocked return 1.0
318  if (outerCircleIsLighting) return 1.0f;
319 
320  // Early break 2:
321  // If a circle was completely shaded return lighted amount
322  if (innerCircleIsNotLighting) return lighted;
323  innerCircleIsNotLighting = true;
324  }
325  return lighted;
326  }
327 }
328 //-----------------------------------------------------------------------------
329 /*!
330 SLLightSpot::shadowTest returns 0.0 if the hit point is completely shaded and
331 1.0 if it is 100% lighted. A return value inbetween is calculate by the ratio
332 of the shadow rays not blocked to the total number of casted shadow rays.
333 */
334 SLfloat SLLightSpot::shadowTestMC(SLRay* ray, // ray of hit point
335  const SLVec3f& L, // vector from hit point to light
336  SLfloat lightDist, // distance to light
337  SLNode* root3D)
338 {
339  if (_samples.samples() == 1)
340  {
341  // define shadow ray and shoot
342  SLRay shadowRay(lightDist, L, ray);
343  root3D->hitRec(&shadowRay);
344 
345  if (shadowRay.length < lightDist)
346  {
347  // Handle shadow value of transparent materials
348  if (shadowRay.hitMesh->mat()->hasAlpha())
349  {
350  shadowRay.hitMesh->preShade(&shadowRay);
351  SLfloat shadowTransp = Utils::abs(shadowRay.dir.dot(shadowRay.hitNormal));
352  return shadowTransp * shadowRay.hitMesh->mat()->kt();
353  }
354  else
355  return 0.0f;
356  }
357  else
358  return 1.0f;
359  }
360  else // do light sampling for soft shadows
361  {
362  SLVec3f C(updateAndGetWM().translation()); // Center of light
363  SLVec3f LightX, LightY; // main axis of sample plane
364  SLfloat lighted = 0.0f; // return value
365  SLfloat invSamples = 1.0f / (_samples.samples());
366  SLbool outerCircleIsLighting = true;
367  SLbool innerCircleIsNotLighting = true;
368 
369  // Build normalized plain vectors X and Y that are perpendicular to L (=Z)
370  if (fabs(L.x) >= fabs(L.y))
371  {
372  SLfloat invLength = 1.0f / sqrt(L.x * L.x + L.z * L.z);
373  LightX.set(L.z * invLength, 0, -L.x * invLength);
374  }
375  else
376  {
377  SLfloat invLength = 1.0f / sqrt(L.y * L.y + L.z * L.z);
378  LightX.set(0, L.z * invLength, -L.y * invLength);
379  }
380  LightY.cross(L, LightX);
381  LightY *= _radius;
382  LightX *= _radius;
383 
384  // Loop over radius r and angle phi of light circle
385  for (SLint iR = (SLint)_samples.samplesX() - 1; iR >= 0; --iR)
386  {
387  for (SLint iPhi = (SLint)_samples.samplesY() - 1; iPhi >= 0; --iPhi)
388  {
389  SLVec2f discPos(_samples.point((SLuint)iR, (SLuint)iPhi));
390 
391  // calculate disc position and vector LDisc to it
392  SLVec3f conePos(C + discPos.x * LightX + discPos.y * LightY);
393  SLVec3f LDisc(conePos - ray->hitPoint);
394  LDisc.normalize();
395 
396  SLRay shadowRay(lightDist, LDisc, ray);
397 
398  root3D->hitRec(&shadowRay);
399 
400  if (shadowRay.length < lightDist)
401  outerCircleIsLighting = false;
402  else
403  {
404  lighted += invSamples; // sum up the light
405  innerCircleIsNotLighting = false;
406  }
407  }
408 
409  // Early break 1:
410  // If the outer circle of shadow rays where not blocked return 1.0
411  if (outerCircleIsLighting) return 1.0f;
412 
413  // Early break 2:
414  // If a circle was completely shaded return lighted amount
415  if (innerCircleIsNotLighting) return lighted;
416  innerCircleIsNotLighting = true;
417  }
418  return 0.0f;
419  }
420 }
421 //-----------------------------------------------------------------------------
float SLfloat
Definition: SL.h:173
unsigned int SLuint
Definition: SL.h:171
bool SLbool
Definition: SL.h:175
#define SL_EXIT_MSG(message)
Definition: SL.h:240
int SLint
Definition: SL.h:170
@ TS_object
Definition: SLEnums.h:210
typedef void(SL_STDCALL *cbOnImGuiBuild)(SLScene *s
Callback function typedef for ImGui build function.
static const SLint SL_MAX_LIGHTS
max. number of used lights
Definition: SLGLState.h:49
@ SHADOW
Definition: SLRay.h:26
@ PRIMARY
Definition: SLRay.h:23
Toplevel holder of the assets meshes, materials, textures and shaders.
Active or visible camera node class.
Definition: SLCamera.h:54
Abstract Light class for OpenGL light sources.
Definition: SLLight.h:61
SLint _id
OpenGL light number (0-7)
Definition: SLLight.h:208
SLShadowMap * _shadowMap
Used for shadow mapping.
Definition: SLLight.h:224
SLfloat spotCutOffDEG() const
Definition: SLLight.h:140
SLbool _isOn
Flag if light is on or off.
Definition: SLLight.h:209
SLCol4f diffuseColor()
Definition: SLLight.h:136
SLbool _createsShadows
flag if light creates shadows or not
Definition: SLLight.h:223
bool hitRec(SLRay *ray) override
SLfloat shadowTestMC(SLRay *ray, const SLVec3f &L, SLfloat lightDist, SLNode *root3D) override
SLRaySamples2D _samples
2D sample points for soft shadows
Definition: SLLightSpot.h:124
SLfloat radius() const
Definition: SLLightSpot.h:80
void createShadowMapAutoSize(SLCamera *camera, SLVec2i texSize=SLVec2i(1024, 1024), int numCascades=0) override
void createShadowMap(float lightClipNear=0.1f, float lightClipFar=20.0f, SLVec2f size=SLVec2f(8, 8), SLVec2i texSize=SLVec2i(1024, 1024)) override
void drawMesh(SLSceneView *sv) override
void statsRec(SLNodeStats &stats) override
SLLightSpot::statsRec updates the statistic parameters.
~SLLightSpot() override
SLfloat shadowTest(SLRay *ray, const SLVec3f &L, SLfloat lightDist, SLNode *root3D) override
SLfloat _radius
The sphere lights radius.
Definition: SLLightSpot.h:123
void init(SLScene *s)
SLLightSpot(SLAssetManager *assetMgr, SLScene *s, SLfloat radius=0.3f, SLfloat spotAngleDEG=180.0f, SLbool hasMesh=true)
Construct a new SLLightSpot::SLLightSpot object.
Definition: SLLightSpot.cpp:33
Defines a standard CG material with textures and a shader program.
Definition: SLMaterial.h:56
SLbool hasAlpha()
Returns true if there is any transparency in diffuse alpha or textures.
Definition: SLMaterial.h:125
void kt(SLfloat kt)
Definition: SLMaterial.h:190
void emissive(const SLCol4f &emis)
Definition: SLMaterial.h:174
SLMaterial * mat() const
Definition: SLMesh.h:177
virtual void preShade(SLRay *ray)
Definition: SLMesh.cpp:1470
SLNode represents a node in a hierarchical scene graph.
Definition: SLNode.h:147
virtual void drawMesh(SLSceneView *sv)
Draws the single mesh.
Definition: SLNode.cpp:176
void translation(const SLVec3f &pos, SLTransformSpace relativeTo=TS_parent)
Definition: SLNode.cpp:828
const SLMat4f & updateAndGetWM() const
Definition: SLNode.cpp:703
bool _castsShadows
flag if meshes of node should cast shadows
Definition: SLNode.h:357
virtual void addMesh(SLMesh *mesh)
Definition: SLNode.cpp:157
virtual void statsRec(SLNodeStats &stats)
Definition: SLNode.cpp:479
SLMesh * _mesh
pointer to a single mesh
Definition: SLNode.h:346
virtual bool hitRec(SLRay *ray)
Definition: SLNode.cpp:508
void translate(const SLVec3f &vec, SLTransformSpace relativeTo=TS_object)
Definition: SLNode.cpp:906
Ray class with ray and intersection properties.
Definition: SLRay.h:40
SLRayType type
PRIMARY, REFLECTED, REFRACTED, SHADOW.
Definition: SLRay.h:92
SLMesh * hitMesh
Points to the intersected mesh.
Definition: SLRay.h:106
SLVec3f dir
Direction vector of ray in WS.
Definition: SLRay.h:76
SLfloat length
length from origin to an intersection
Definition: SLRay.h:77
SLVec3f hitPoint
Point of intersection.
Definition: SLRay.h:110
SLVec3f hitNormal
Surface normal at intersection point.
Definition: SLRay.h:111
SLuint sizeInBytes()
void samples(SLuint x, SLuint y, SLbool evenlyDistributed=true)
Resets the sample point array by the sqrt of the no. of samples.
void point(SLuint x, SLuint y, SLVec2f point)
SLuint samplesY()
SLuint samplesX()
The SLScene class represents the top level instance holding the scene structure.
Definition: SLScene.h:47
SLVLight & lights()
Definition: SLScene.h:107
SLNode * singleNodeSelected()
Returns the node if only one is selected. See also SLMesh::selectNodeMesh.
Definition: SLScene.h:116
SceneView class represents a dynamic real time 3D view onto the scene.
Definition: SLSceneView.h:69
SLScene * s()
Definition: SLSceneView.h:167
Class for standard and cascaded shadow mapping.
Definition: SLShadowMap.h:39
void drawFrustum()
SLShadowMap::drawFrustum draws the volume affected by the shadow map.
void drawRays()
SLSphere creates a sphere mesh based on SLSpheric w. 180 deg polar angle.
Definition: SLSphere.h:33
SLSphere creates a sphere mesh based on SLRevolver.
Definition: SLSpheric.h:21
T y
Definition: SLVec2.h:30
T x
Definition: SLVec2.h:30
T y
Definition: SLVec3.h:43
void cross(const SLVec3 &a, const SLVec3 &b)
Definition: SLVec3.h:118
T x
Definition: SLVec3.h:43
SLVec3 & normalize()
Definition: SLVec3.h:124
void set(const T X, const T Y, const T Z)
Definition: SLVec3.h:59
T dot(const SLVec3 &v) const
Definition: SLVec3.h:117
T z
Definition: SLVec3.h:43
static SLVec4 BLACK
Definition: SLVec4.h:213
T abs(T a)
Definition: Utils.h:249
Struct for scene graph statistics.
Definition: SLNode.h:37
SLuint numBytes
NO. of bytes allocated.
Definition: SLNode.h:39