1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as
6 // published by the Free Software Foundation, either version 3 of the
7 // License, or (at your option) any later version.
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU Affero General Public License for more details.
14 // You should have received a copy of the GNU Affero General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
19 #include "nel/3d/lighting_manager.h"
20 #include "nel/3d/point_light.h"
21 #include "nel/3d/transform.h"
22 #include "nel/misc/fast_floor.h"
23 #include "nel/3d/logic_info.h"
24 #include "nel/misc/aabbox.h"
25 #include "nel/misc/algo.h"
28 using namespace NLMISC
;
38 // ***************************************************************************
39 /* LightQuadGrid setup. This is the same setup for StaticLightedModelQuadGrid setup.
40 NB: with this setup, a light will lies into 4*4=16 squares of a quadGrid at max.
42 #define NL3D_LIGHT_QUAD_GRID_SIZE 128
43 #define NL3D_LIGHT_QUAD_GRID_ELTSIZE 10.f
44 #define NL3D_LIGHT_QUAD_GRID_RADIUS_LIMIT 20.f
45 // Factor for a level to the next: size/=factor, eltSize*=factor, and radiusLimit*=factor.
46 #define NL3D_LIGHT_QUAD_GRID_FACTOR 4
49 // this is a big radius for light that don't have attenuation
50 #define NL3D_DEFAULT_NOATT_LIGHT_RADIUS 1000.f
51 // this is used when the model is out of attBegin/attEnd, to modulate the influence
52 #define NL3D_DEFAULT_OUT_OF_ATT_LIGHT_INF_FACTOR 0.1f
53 // Defualt LightTransitionThreshold
54 #define NL3D_DEFAULT_LIGHT_TRANSITION_THRESHOLD 0.1f
57 // ***************************************************************************
58 CLightingManager::CLightingManager(bool bSmallScene
)
60 // Init the lightQuadGrids and StaticLightedModelQuadGrid
62 uint qgSize
= NL3D_LIGHT_QUAD_GRID_SIZE
;
63 float eltSize
= NL3D_LIGHT_QUAD_GRID_ELTSIZE
;
64 float radiusLimit
= NL3D_LIGHT_QUAD_GRID_RADIUS_LIMIT
;
72 for(uint i
=0;i
<NL3D_QUADGRID_LIGHT_NUM_LEVEL
;i
++)
74 // init _LightQuadGrid and _StaticLightedModelQuadGrid
75 _LightQuadGrid
[i
].create(qgSize
, eltSize
);
76 _StaticLightedModelQuadGrid
[i
].create(qgSize
, eltSize
);
77 _LightQuadGridRadiusLimit
[i
]= radiusLimit
;
79 // coarser quadGrid level
80 qgSize
/= NL3D_LIGHT_QUAD_GRID_FACTOR
;
81 qgSize
= max(qgSize
, 1U);
82 eltSize
*= NL3D_LIGHT_QUAD_GRID_FACTOR
;
83 radiusLimit
*= NL3D_LIGHT_QUAD_GRID_FACTOR
;
88 _NoAttLightRadius
= NL3D_DEFAULT_NOATT_LIGHT_RADIUS
;
89 _OutOfAttLightInfFactor
= NL3D_DEFAULT_OUT_OF_ATT_LIGHT_INF_FACTOR
;
90 _LightTransitionThreshold
= NL3D_DEFAULT_LIGHT_TRANSITION_THRESHOLD
;
93 // Default number of pointLight on an object.
94 setMaxLightContribution(3);
98 // ***************************************************************************
99 void CLightingManager::setMaxLightContribution(uint nlights
)
101 _MaxLightContribution
= min(nlights
, (uint
)NL3D_MAX_LIGHT_CONTRIBUTION
);
105 // ***************************************************************************
106 void CLightingManager::setNoAttLightRadius(float noAttLightRadius
)
108 nlassert(noAttLightRadius
>0);
109 _NoAttLightRadius
= noAttLightRadius
;
113 // ***************************************************************************
114 void CLightingManager::setOutOfAttLightInfFactor(float outOfAttLightInfFactor
)
116 outOfAttLightInfFactor
= max(0.f
, outOfAttLightInfFactor
);
117 _OutOfAttLightInfFactor
= outOfAttLightInfFactor
;
121 // ***************************************************************************
122 void CLightingManager::setLightTransitionThreshold(float lightTransitionThreshold
)
124 clamp(lightTransitionThreshold
, 0.f
, 1.f
);
125 _LightTransitionThreshold
= lightTransitionThreshold
;
130 // ***************************************************************************
131 void CLightingManager::clearDynamicLights()
134 for(uint i
=0;i
<NL3D_QUADGRID_LIGHT_NUM_LEVEL
;i
++)
136 // clear all the lights in the quadGrid.
137 _LightQuadGrid
[i
].clear();
141 _DynamicLightList
.clear();
144 // ***************************************************************************
145 void CLightingManager::addDynamicLight(CPointLight
*light
)
148 // Insert the light in the quadGrid.
150 CPointLightInfo plInfo
;
153 // build the bounding sphere for this light, with the AttenuationEnd of the light
154 float radius
=light
->getAttenuationEnd();
155 // if attenuation is disabled (ie getAttenuationEnd() return 0), then have a dummy radius
157 radius
= _NoAttLightRadius
;
158 plInfo
.Sphere
.Center
= light
->getPosition();
159 plInfo
.Sphere
.Radius
= radius
;
161 // build a bbox, so it includes the sphere
162 CVector bbmin
= light
->getPosition();
163 bbmin
-= CVector(radius
, radius
, radius
);
164 CVector bbmax
= light
->getPosition();
165 bbmax
+= CVector(radius
, radius
, radius
);
167 // choose the correct quadgrid according to the radius of the light.
169 for(qgId
= 0; qgId
<NL3D_QUADGRID_LIGHT_NUM_LEVEL
-1; qgId
++)
171 // if radius is inferior to the requested radius for this quadGrid, ok!
172 if(radius
<_LightQuadGridRadiusLimit
[qgId
])
175 // insert this light in the correct quadgrid.
176 _LightQuadGrid
[qgId
].insert(bbmin
, bbmax
, plInfo
);
179 // touch the objects around the lights.
182 // Select the static lightedModels to update around this light.
183 // Use the same level of _StaticLightedModelQuadGrid than me to not select too many squares in the quadGrid
184 _StaticLightedModelQuadGrid
[qgId
].select(bbmin
, bbmax
);
185 // For all those models.
186 CQuadGrid
<CTransform
*>::CIterator itModel
= _StaticLightedModelQuadGrid
[qgId
].begin();
187 while(itModel
!= _StaticLightedModelQuadGrid
[qgId
].end() )
189 CTransform
*model
= *itModel
;
190 const CVector
&modelPos
= model
->getWorldMatrix().getPos();
192 // test first if this model is in the area of the light.
193 if( plInfo
.Sphere
.include(modelPos
) )
195 // yes, then this model must recompute his lighting, because a dynamic light touch him.
196 model
->resetLighting();
204 // insert the light in the list.
206 _DynamicLightList
.push_back(light
);
210 // ***************************************************************************
211 CLightingManager::CQGItLightedModel
CLightingManager::eraseStaticLightedModel(CQGItLightedModel ite
)
213 // Erase the iterator for all levels
214 for(uint i
=0;i
<NL3D_QUADGRID_LIGHT_NUM_LEVEL
;i
++)
216 // NB: it is possible here that the iterator is NULL (ie end()).
217 _StaticLightedModelQuadGrid
[i
].erase(ite
.QgItes
[i
]);
220 // return end(), ie NULL iterators
221 return CQGItLightedModel();
224 // ***************************************************************************
225 CLightingManager::CQGItLightedModel
CLightingManager::insertStaticLightedModel(CTransform
*model
)
227 CQGItLightedModel ite
;
228 const CVector
&worldPos
= model
->getWorldMatrix().getPos();
230 // Insert the models in all levels, because addDynamicLight() may choose the best suited level to select
231 for(uint i
=0;i
<NL3D_QUADGRID_LIGHT_NUM_LEVEL
;i
++)
233 ite
.QgItes
[i
]= _StaticLightedModelQuadGrid
[i
].insert(worldPos
, worldPos
, model
);
236 // return the iterator
241 // ***************************************************************************
244 CPointLight
*PointLight
;
249 // ***************************************************************************
250 void CLightingManager::computeModelLightContributions(NLMISC::CRGBA sunAmbient
, CTransform
*model
, CLightContribution
&lightContrib
,
251 ILogicInfo
*logicInfo
)
255 // This is the list of light which touch this model.
256 static std::vector
<CPointLightInfluence
> lightList
;
257 // static, for malloc perf.
260 // the position of the model.
265 model
->getLightHotSpotInWorld(modelPos
, modelRadius
);
267 // First pass, fill the list of light which touch this model.
269 // get the dynamic lights around the model
270 getDynamicPointLightList(modelPos
, lightList
);
272 // if not already precomputed, append staticLights to this list.
273 if( !lightContrib
.FrozenStaticLightSetup
)
275 // If no logicInfo provided
278 // Default: suppose full SunLight and no PointLights.
279 lightContrib
.SunContribution
= 255;
280 // Take full SunAmbient.
281 lightContrib
.LocalAmbient
= sunAmbient
;
282 // do not append any pointLight to the setup
286 // NB: SunContribution is computed by logicInfo
287 logicInfo
->getStaticLightSetup(sunAmbient
, lightList
, lightContrib
.SunContribution
, lightContrib
.LocalAmbient
);
291 // Second pass, in the lightList, choose the best suited light to lit this model
294 // for each light, modulate the factor of influence
295 for(i
=0; i
<(sint
)lightList
.size();i
++)
297 CPointLight
*pl
= lightList
[i
].PointLight
;
299 // get the distance from the light to the model
300 float dist
= (pl
->getPosition() - modelPos
).norm();
301 float distMinusRadius
= dist
- modelRadius
;
303 // modulate the factor by the distance and the attenuation distance.
305 float attBegin
= pl
->getAttenuationBegin();
306 float attEnd
= pl
->getAttenuationEnd();
310 // influence is awlays 1.
313 // If SpotLight, must modulate with SpotAttenuation.
314 if(pl
->getType() == CPointLight::SpotLight
)
315 inf
*= pl
->computeLinearAttenuation(modelPos
, dist
, modelRadius
);
319 // if correct attenuation radius
320 if(distMinusRadius
<attBegin
)
322 // NB: we are sure that attBegin>0, because dist>=0.
323 // if < attBegin, inf should be ==1, but must select the nearest lights; for better
324 // understanding of the scene
325 inf
= 1 + _OutOfAttLightInfFactor
* (attBegin
- distMinusRadius
); // inf E [1, +oo[
326 // NB: this formula favour big lights (ie light with big attBegin).
328 // If SpotLight, must modulate with SpotAttenuation.
329 if(pl
->getType() == CPointLight::SpotLight
)
330 inf
*= pl
->computeLinearAttenuation(modelPos
, dist
, modelRadius
);
332 else if(distMinusRadius
<attEnd
)
334 // we are sure attEnd-attBegin>0 because of the test
335 // compute influence of the light: attenuation and SpotAttenuation
336 inf
= pl
->computeLinearAttenuation(modelPos
, dist
, modelRadius
);
340 // if >= attEnd, inf should be ==0, but must select the nearest lights; for better
341 // understanding of the scene
342 inf
= _OutOfAttLightInfFactor
* (attEnd
- distMinusRadius
); // inf E ]-oo, 0]
346 // modulate the influence with this factor
347 lightList
[i
].BkupInfluence
= lightList
[i
].Influence
;
348 lightList
[i
].Influence
*= inf
;
350 // Bkup distance to model.
351 lightList
[i
].DistanceToModel
= dist
;
354 // sort the light by influence
355 sort(lightList
.begin(), lightList
.end());
357 // prepare Light Merging.
358 static std::vector
<float> lightToMergeWeight
;
359 lightToMergeWeight
.clear();
360 lightToMergeWeight
.resize(lightList
.size(), 1);
363 // and choose only max light.
366 // skip already setuped light (statically)
367 if(lightContrib
.FrozenStaticLightSetup
)
368 startId
= lightContrib
.NumFrozenStaticLight
;
370 // If there is still place for unFrozen static lights or dynamic lights.
371 if(startId
< _MaxLightContribution
)
373 // setup the transition.
374 float deltaMinInfluence
= _LightTransitionThreshold
;
375 float minInfluence
= 0;
377 sint firstLightToMergeFull
= _MaxLightContribution
-startId
;
378 // If there is more light than we can accept in not merged mode, Must merge
379 if((sint
)lightList
.size() > firstLightToMergeFull
)
382 // the minInfluence is the influence of the first light not taken.
383 minInfluence
= lightList
[firstLightToMergeFull
].Influence
;
384 // but must still be >=0.
385 minInfluence
= max(minInfluence
, 0.f
);
387 // Any light under this minInfluence+deltaMinInfluence will be smoothly darken.
388 float minInfluenceStart
= minInfluence
+ deltaMinInfluence
;
389 float OOdeltaMinInfluence
= 1.0f
/ deltaMinInfluence
;
390 /* NB: this is not an error if we have only 3 light for example (assuming _MaxLightContribution=3),
391 and still have minInfluenceStart>0.
392 It's to have a continuity in the case of for example we have 3 lights in lightLists at frame T0,
393 resulting in "doMerge=0" and at frame T1 we have 4 lights, the farthest having an influence of 0
397 // fill maxLight at max.
398 for(i
=startId
;i
<(sint
)_MaxLightContribution
; i
++)
400 // if not so many pointLights found, end!!
401 if(ligthSrcId
>=lightList
.size())
405 CPointLight
*pl
= lightList
[ligthSrcId
].PointLight
;
406 float inf
= lightList
[ligthSrcId
].Influence
;
407 float bkupInf
= lightList
[ligthSrcId
].BkupInfluence
;
408 float distToModel
= lightList
[ligthSrcId
].DistanceToModel
;
410 lightContrib
.PointLight
[i
]= pl
;
412 // Compute the Final factor of influence of the light.
413 if(inf
>= minInfluenceStart
)
415 // For Static LightSetup BiLinear to work correctly, modulate with BkupInfluence
416 // don't worry about the precision of floor, because of *255.
417 lightContrib
.Factor
[i
]= (uint8
)NLMISC::OptFastFloor(bkupInf
*255);
418 // Indicate that this light don't need to be merged at all!
419 lightToMergeWeight
[ligthSrcId
]= 0;
423 float f
= (inf
-minInfluence
) * OOdeltaMinInfluence
;
424 // For Static LightSetup BiLinear to work correctly, modulate with BkupInfluence
425 // don't worry about the precision of floor, because of *255.
426 sint fi
= NLMISC::OptFastFloor( bkupInf
*f
*255 );
428 lightContrib
.Factor
[i
]= fi
;
429 // The rest of the light contribution is to be merged.
430 lightToMergeWeight
[ligthSrcId
]= 1-f
;
433 // Compute the Final Att factor for models using Global Attenuation. NB: modulate with Factor
434 // don't worry about the precision of floor, because of *255.
435 // NB: compute att on the center of the model => modelRadius==0
436 sint attFactor
= NLMISC::OptFastFloor( lightContrib
.Factor
[i
] * pl
->computeLinearAttenuation(modelPos
, distToModel
) );
437 lightContrib
.AttFactor
[i
]= (uint8
)attFactor
;
439 // must append this lightedModel to the list in the light.
440 lightContrib
.TransformIterator
[i
]= pl
->appendLightedModel(model
);
447 // Compute LightToMerge.
450 CRGBAF
mergedAmbient(0,0,0);
453 // For all lights in the lightList, merge Diffuse and Ambient term to mergedAmbient.
454 for(j
=0;j
<lightToMergeWeight
.size();j
++)
456 if(lightToMergeWeight
[j
] && lightList
[j
].Influence
>0)
458 CPointLight
*pl
= lightList
[j
].PointLight
;
460 // Get the original influence (ie for static PLs, biLinear influence)
461 float bkupInf
= lightList
[j
].BkupInfluence
;
462 // Get the attenuation of the pointLight to the model
463 float distToModel
= lightList
[j
].DistanceToModel
;
464 float attInf
= pl
->computeLinearAttenuation(modelPos
, distToModel
);
465 // Attenuate the color of the light by biLinear and distance/spot attenuation.
466 float lightInf
= attInf
* bkupInf
;
467 // Modulate also by the percentage to merge
468 lightInf
*= lightToMergeWeight
[j
];
469 // Add the full ambient term of the light, attenuated by light attenuation and WeightMerge
470 mergedAmbient
.R
+= pl
->getAmbient().R
* lightInf
;
471 mergedAmbient
.G
+= pl
->getAmbient().G
* lightInf
;
472 mergedAmbient
.B
+= pl
->getAmbient().B
* lightInf
;
473 // Add 0.25f of the diffuse term of the light, attenuated by light attenuation and WeightMerge
474 // mul by 0.25f, because this is the averaged contribution of the diffuse part of a
475 // light to a sphere (do the maths...).
476 float f
= lightInf
*0.25f
;
477 mergedAmbient
.R
+= pl
->getDiffuse().R
* f
;
478 mergedAmbient
.G
+= pl
->getDiffuse().G
* f
;
479 mergedAmbient
.B
+= pl
->getDiffuse().B
* f
;
484 // Setup the merged Light
487 // Because of floating point error, it appears that sometime result may be slightly below 0.
488 // => clamp necessary
489 v
= NLMISC::OptFastFloor(mergedAmbient
.R
); fastClamp8(v
); amb
.R
= v
;
490 v
= NLMISC::OptFastFloor(mergedAmbient
.G
); fastClamp8(v
); amb
.G
= v
;
491 v
= NLMISC::OptFastFloor(mergedAmbient
.B
); fastClamp8(v
); amb
.B
= v
;
493 lightContrib
.MergedPointLight
= amb
;
495 // Indicate we use the merged pointLight => the model must recompute lighting each frame
496 lightContrib
.UseMergedPointLight
= true;
500 // If the model is freezeHRC(), need to test each frame only if MergedPointLight is used.
501 lightContrib
.UseMergedPointLight
= false;
506 // point to end the list
511 if(i
<NL3D_MAX_LIGHT_CONTRIBUTION
)
513 lightContrib
.PointLight
[i
]= NULL
;
519 // ***************************************************************************
520 void CLightingManager::getDynamicPointLightList(const CVector
&worldPos
, std::vector
<CPointLightInfluence
> &lightList
)
522 // For all quadGrids.
523 for(uint qgId
=0; qgId
<NL3D_QUADGRID_LIGHT_NUM_LEVEL
; qgId
++)
525 CQuadGrid
<CPointLightInfo
> &quadGrid
= _LightQuadGrid
[qgId
];
527 // select the lights around this position in the quadGrids.
528 quadGrid
.select(worldPos
, worldPos
);
530 // for all possible found lights
531 CQuadGrid
<CPointLightInfo
>::CIterator itLight
;
532 for(itLight
= quadGrid
.begin(); itLight
!=quadGrid
.end(); itLight
++)
534 // verify it includes the entity
535 if( (*itLight
).Sphere
.include(worldPos
) )
537 // ok, insert in list.
538 CPointLightInfluence pli
;
539 pli
.PointLight
= (*itLight
).Light
;
540 // No special Influence degradation scheme for Dynamic lighting
542 lightList
.push_back( pli
);