1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qquick3dabstractlight_p.h"
5#include "qquick3dobject_p.h"
6#include "qquick3dnode_p_p.h"
7
8#include <QtQuick3DRuntimeRender/private/qssgrenderlight_p.h>
9#include <QtQuick3DUtils/private/qssgutils_p.h>
10
11QT_BEGIN_NAMESPACE
12
13/*!
14 \qmltype Light
15 \inherits Node
16 \inqmlmodule QtQuick3D
17 \brief An uncreatable abstract base type for all lights.
18
19 Light itself is an uncreatable base for all of its subtypes. The subtypes provide multiple
20 options to determine the style of the light.
21
22 For usage examples, see \l{Qt Quick 3D - Lights Example}.
23
24 \sa DirectionalLight, PointLight
25*/
26
27/*!
28 \qmlproperty color Light::color
29 This property defines the color applied to models illuminated by this light.
30 The default value is white, rgb(255, 255, 255).
31 */
32
33/*!
34 \qmlproperty color Light::ambientColor
35 The property defines the ambient color applied to materials before being lit by this light.
36 The default value is black, rgb(0, 0, 0).
37 */
38
39/*!
40 \qmlproperty real Light::brightness
41 This property defines an overall multiplier for this light’s effects.
42 The default value is 1.
43*/
44
45/*!
46 \qmlproperty Node Light::scope
47
48 The property allows the selection of a Node in the scene. Only that node
49 and its children are affected by this light. By default the value is null,
50 which indicates no scope selected.
51
52 \note Scoped lights cannot cast real-time shadows, meaning a Light with a
53 scope set should not set \l castsShadow to true. They can however generate
54 baked shadows when \l bakeMode is set to Light.BakeModeAll.
55*/
56
57/*!
58 \qmlproperty bool Light::castsShadow
59
60 When this property is enabled, the light will cast (real-time) shadows. The
61 default value is false.
62
63 \note When \l bakeMode is set to Light.BakeModeAll, this property has no
64 effect. A fully baked light always has baked shadows, but it will never
65 participate in real-time shadow mapping.
66*/
67
68/*!
69 \qmlproperty real Light::shadowBias
70 This property is used to tweak the shadowing effect when objects
71 are casting shadows on themselves. The value tries to approximate the offset
72 in world space so it needs to be tweaked depending on the size of your scene.
73
74 The default value is \c{10}
75*/
76
77/*!
78 \qmlproperty real Light::shadowFactor
79 This property determines how dark the cast shadows should be. The value range is [0, 100], where
80 0 means no shadows and 100 means the light is fully shadowed.
81
82 The default value is \c{75}.
83*/
84
85/*!
86 \qmlproperty enumeration Light::shadowMapQuality
87 The property sets the quality of the shadow map created for shadow rendering. Lower quality uses
88 less resources, but produces lower quality shadows while higher quality uses more resources, but
89 produces better quality shadows.
90
91 Supported quality values are:
92 \value Light.ShadowMapQualityLow Render shadowmap using 256x256 texture.
93 \value Light.ShadowMapQualityMedium Render shadowmap using 512x512 texture.
94 \value Light.ShadowMapQualityHigh Render shadowmap using 1024x1024 texture.
95 \value Light.ShadowMapQualityVeryHigh Render shadowmap using 2048x2048 texture.
96
97 The default value is \c Light.ShadowMapQualityLow
98*/
99
100/*!
101 \qmlproperty real Light::shadowMapFar
102 The property determines the maximum distance for the shadow map. Smaller
103 values improve the precision and effects of the map.
104 The default value is 5000. Unit is points in local coordinate space.
105*/
106
107/*!
108 \qmlproperty real Light::shadowFilter
109 This property sets how much blur is applied to the shadows.
110
111 The default value is 5.
112
113 \deprecated [6.8] No longer used for anything, use \l{Light::}{pcfFactor} instead.
114
115 \sa Light::softShadowQuality
116*/
117
118/*!
119 \qmlproperty enumeration Light::bakeMode
120 The property controls if the light is active in baked lighting, such as
121 when generating lightmaps.
122
123 \value Light.BakeModeDisabled The light is not used in baked lighting.
124
125 \value Light.BakeModeIndirect Indirect lighting contribution (for global
126 illumination) is baked for this light. Direct lighting (diffuse, specular,
127 real-time shadow mapping) is calculated normally for the light at run time.
128 At run time, when not in baking mode, the renderer will attempt to sample
129 the lightmap to get the indirect lighting data and combine that with the
130 results of the real-time calculations.
131
132 \value Light.BakeModeAll Both direct (diffuse, shadow) and indirect
133 lighting is baked for this light. The light will not have a specular
134 contribution and will not generate realtime shadow maps, but it will always
135 have baked shadows. At run time, when not in baking mode, the renderer will
136 attempt to sample the lightmap in place of the standard, real-time
137 calculations for diffuse lighting and shadow mapping.
138
139 The default value is \c Light.BakeModeDisabled
140
141 \note Just as with \l Model::usedInBakedLighting, designers and developers
142 must always evaluate on a per-light basis if the light is suitable to take
143 part in baked lighting.
144
145 \warning Lights with dynamically changing properties, for example, animated
146 position, rotation, or other properties, are not suitable for participating
147 in baked lighting.
148
149 This property is relevant both when baking and when using lightmaps. A
150 consistent state between the baking run and the subsequent runs that use
151 the generated data is essential. Changing to a different value will not
152 change the previously generated and persistently stored data in the
153 lightmaps, the engine's rendering behavior will however follow the
154 property's current value.
155
156 For more information on how to bake lightmaps, see the \l {Lightmaps and
157 Global Illumination}.
158
159 \sa Model::usedInBakedLighting, Model::bakedLightmap, Lightmapper, {Lightmaps and Global Illumination}
160*/
161
162/*!
163 \qmlproperty enumeration Light::softShadowQuality
164 \since 6.8
165
166 The property controls the soft shadow quality.
167
168 \value Light.Hard No soft shadows.
169 \value Light.PCF4 Percentage-closer filtering soft shadows with 4 samples.
170 \value Light.PCF8 Percentage-closer filtering soft shadows with 8 samples.
171 \value Light.PCF16 Percentage-closer filtering soft shadows with 16 samples.
172 \value Light.PCF32 Percentage-closer filtering soft shadows with 32 samples.
173 \value Light.PCF64 Percentage-closer filtering soft shadows with 64 samples.
174
175 Default value: \c Light.PCF4
176
177 \sa Light::pcfFactor, Light::shadowFilter
178*/
179
180/*!
181 \qmlproperty real Light::pcfFactor
182 \since 6.8
183
184 The property controls the PCF (percentage-closer filtering) factor. This
185 value tries to approximate the radius of a PCF filtering in world space.
186
187 \note PCF needs to be set in \l{Light::}{softShadowQuality} for this property
188 to have an effect.
189
190 Default value: \c{2.0}
191
192 \sa Light::softShadowQuality
193*/
194
195QQuick3DAbstractLight::QQuick3DAbstractLight(QQuick3DNodePrivate &dd, QQuick3DNode *parent)
196 : QQuick3DNode(dd, parent)
197 , m_color(Qt::white)
198 , m_ambientColor(Qt::black) {}
199
200QQuick3DAbstractLight::~QQuick3DAbstractLight() {}
201
202QColor QQuick3DAbstractLight::color() const
203{
204 return m_color;
205}
206
207QColor QQuick3DAbstractLight::ambientColor() const
208{
209 return m_ambientColor;
210}
211
212float QQuick3DAbstractLight::brightness() const
213{
214 return m_brightness;
215}
216
217QQuick3DNode *QQuick3DAbstractLight::scope() const
218{
219 return m_scope;
220}
221
222bool QQuick3DAbstractLight::castsShadow() const
223{
224 return m_castsShadow;
225}
226
227float QQuick3DAbstractLight::shadowBias() const
228{
229 return m_shadowBias;
230}
231
232float QQuick3DAbstractLight::shadowFactor() const
233{
234 return m_shadowFactor;
235}
236
237QQuick3DAbstractLight::QSSGShadowMapQuality QQuick3DAbstractLight::shadowMapQuality() const
238{
239 return m_shadowMapQuality;
240}
241
242QQuick3DAbstractLight::QSSGSoftShadowQuality QQuick3DAbstractLight::softShadowQuality() const
243{
244 return m_softShadowQuality;
245}
246
247float QQuick3DAbstractLight::shadowMapFar() const
248{
249 return m_shadowMapFar;
250}
251
252float QQuick3DAbstractLight::shadowFilter() const
253{
254 return m_shadowFilter;
255}
256
257QQuick3DAbstractLight::QSSGBakeMode QQuick3DAbstractLight::bakeMode() const
258{
259 return m_bakeMode;
260}
261
262float QQuick3DAbstractLight::pcfFactor() const
263{
264 return m_pcfFactor;
265}
266
267void QQuick3DAbstractLight::markAllDirty()
268{
269 m_dirtyFlags = DirtyFlags(DirtyFlag::ShadowDirty)
270 | DirtyFlags(DirtyFlag::ColorDirty)
271 | DirtyFlags(DirtyFlag::BrightnessDirty)
272 | DirtyFlags(DirtyFlag::FadeDirty)
273 | DirtyFlags(DirtyFlag::AreaDirty)
274 | DirtyFlags(DirtyFlag::BakeModeDirty);
275 QQuick3DNode::markAllDirty();
276}
277
278void QQuick3DAbstractLight::setColor(const QColor &color)
279{
280 if (m_color == color)
281 return;
282
283 m_color = color;
284 m_dirtyFlags.setFlag(flag: DirtyFlag::ColorDirty);
285 emit colorChanged();
286 update();
287}
288
289void QQuick3DAbstractLight::setAmbientColor(const QColor &ambientColor)
290{
291 if (m_ambientColor == ambientColor)
292 return;
293
294 m_ambientColor = ambientColor;
295 m_dirtyFlags.setFlag(flag: DirtyFlag::ColorDirty);
296 emit ambientColorChanged();
297 update();
298}
299
300void QQuick3DAbstractLight::setBrightness(float brightness)
301{
302 if (qFuzzyCompare(p1: m_brightness, p2: brightness))
303 return;
304
305 m_brightness = brightness;
306 m_dirtyFlags.setFlag(flag: DirtyFlag::BrightnessDirty);
307 emit brightnessChanged();
308 update();
309}
310
311void QQuick3DAbstractLight::setScope(QQuick3DNode *scope)
312{
313 if (m_scope == scope)
314 return;
315
316 m_scope = scope;
317 emit scopeChanged();
318 update();
319}
320
321void QQuick3DAbstractLight::setCastsShadow(bool castsShadow)
322{
323 if (m_castsShadow == castsShadow)
324 return;
325
326 m_castsShadow = castsShadow;
327 m_dirtyFlags.setFlag(flag: DirtyFlag::ShadowDirty);
328 emit castsShadowChanged();
329 update();
330}
331
332void QQuick3DAbstractLight::setShadowBias(float shadowBias)
333{
334 if (qFuzzyCompare(p1: m_shadowBias, p2: shadowBias))
335 return;
336
337 m_shadowBias = shadowBias;
338 m_dirtyFlags.setFlag(flag: DirtyFlag::ShadowDirty);
339 emit shadowBiasChanged();
340 update();
341}
342
343void QQuick3DAbstractLight::setShadowFactor(float shadowFactor)
344{
345 shadowFactor = qBound(min: 0.0f, val: shadowFactor, max: 100.0f);
346 if (qFuzzyCompare(p1: m_shadowFactor, p2: shadowFactor))
347 return;
348
349 m_shadowFactor = shadowFactor;
350 m_dirtyFlags.setFlag(flag: DirtyFlag::ShadowDirty);
351 emit shadowFactorChanged();
352 update();
353}
354
355void QQuick3DAbstractLight::setShadowMapQuality(
356 QQuick3DAbstractLight::QSSGShadowMapQuality shadowMapQuality)
357{
358 if (m_shadowMapQuality == shadowMapQuality)
359 return;
360
361 m_shadowMapQuality = shadowMapQuality;
362 m_dirtyFlags.setFlag(flag: DirtyFlag::ShadowDirty);
363 emit shadowMapQualityChanged();
364 update();
365}
366
367void QQuick3DAbstractLight::setSoftShadowQuality(QSSGSoftShadowQuality softShadowQuality)
368{
369 if (m_softShadowQuality == softShadowQuality)
370 return;
371
372 m_softShadowQuality = softShadowQuality;
373 m_dirtyFlags.setFlag(flag: DirtyFlag::ShadowDirty);
374 emit softShadowQualityChanged();
375 update();
376}
377
378void QQuick3DAbstractLight::setBakeMode(QQuick3DAbstractLight::QSSGBakeMode bakeMode)
379{
380 if (m_bakeMode == bakeMode)
381 return;
382
383 m_bakeMode = bakeMode;
384 m_dirtyFlags.setFlag(flag: DirtyFlag::BakeModeDirty);
385 emit bakeModeChanged();
386 update();
387}
388
389void QQuick3DAbstractLight::setPcfFactor(float pcfFactor)
390{
391 if (m_pcfFactor == pcfFactor)
392 return;
393
394 m_pcfFactor = pcfFactor;
395 m_dirtyFlags.setFlag(flag: DirtyFlag::ShadowDirty);
396 emit pcfFactorChanged();
397 update();
398}
399
400void QQuick3DAbstractLight::setShadowMapFar(float shadowMapFar)
401{
402 if (qFuzzyCompare(p1: m_shadowMapFar, p2: shadowMapFar))
403 return;
404
405 m_shadowMapFar = shadowMapFar;
406 m_dirtyFlags.setFlag(flag: DirtyFlag::ShadowDirty);
407 emit shadowMapFarChanged();
408 update();
409}
410
411void QQuick3DAbstractLight::setShadowFilter(float shadowFilter)
412{
413 if (qFuzzyCompare(p1: m_shadowFilter, p2: shadowFilter))
414 return;
415
416 m_shadowFilter = shadowFilter;
417 m_dirtyFlags.setFlag(flag: DirtyFlag::ShadowDirty);
418 emit shadowFilterChanged();
419 update();
420}
421
422quint32 QQuick3DAbstractLight::mapToShadowResolution(QSSGShadowMapQuality quality)
423{
424 switch (quality) {
425 case QSSGShadowMapQuality::ShadowMapQualityMedium:
426 return 512;
427 case QSSGShadowMapQuality::ShadowMapQualityHigh:
428 return 1024;
429 case QSSGShadowMapQuality::ShadowMapQualityVeryHigh:
430 return 2048;
431 default:
432 break;
433 }
434 return 256;
435}
436
437QSSGRenderGraphObject *QQuick3DAbstractLight::updateSpatialNode(QSSGRenderGraphObject *node)
438{
439 Q_ASSERT_X(node, __FUNCTION__, "Node must have been created in parent class.");
440
441 QQuick3DNode::updateSpatialNode(node);
442
443 QSSGRenderLight *light = static_cast<QSSGRenderLight *>(node);
444
445 if (m_dirtyFlags.toInt() != 0) // Some flag was set, so mark the light dirty!
446 light->markDirty(dirtyFlag: QSSGRenderLight::DirtyFlag::LightDirty);
447
448 if (m_dirtyFlags.testFlag(flag: DirtyFlag::ColorDirty)) {
449 m_dirtyFlags.setFlag(flag: DirtyFlag::ColorDirty, on: false);
450 light->m_diffuseColor = QSSGUtils::color::sRGBToLinear(color: m_color).toVector3D();
451 light->m_specularColor = light->m_diffuseColor;
452 light->m_ambientColor = QSSGUtils::color::sRGBToLinear(color: m_ambientColor).toVector3D();
453 }
454
455 if (m_dirtyFlags.testFlag(flag: DirtyFlag::BrightnessDirty)) {
456 m_dirtyFlags.setFlag(flag: DirtyFlag::BrightnessDirty, on: false);
457 light->m_brightness = m_brightness;
458 }
459
460 if (m_dirtyFlags.testFlag(flag: DirtyFlag::ShadowDirty)) {
461 m_dirtyFlags.setFlag(flag: DirtyFlag::ShadowDirty, on: false);
462 light->m_castShadow = m_castsShadow;
463 light->m_shadowBias = m_shadowBias;
464 light->m_shadowFactor = m_shadowFactor;
465 light->m_shadowMapRes = mapToShadowResolution(quality: m_shadowMapQuality);
466 light->m_softShadowQuality = static_cast<QSSGRenderLight::SoftShadowQuality>(m_softShadowQuality);
467 light->m_shadowMapFar = m_shadowMapFar;
468 light->m_shadowFilter = m_shadowFilter;
469 light->m_pcfFactor = m_pcfFactor;
470 }
471
472 if (m_dirtyFlags.testFlag(flag: DirtyFlag::BakeModeDirty)) {
473 m_dirtyFlags.setFlag(flag: DirtyFlag::BakeModeDirty, on: false);
474 light->m_bakingEnabled = m_bakeMode != QSSGBakeMode::BakeModeDisabled;
475 light->m_fullyBaked = m_bakeMode == QSSGBakeMode::BakeModeAll;
476 }
477
478 if (m_scope) {
479 // Special case:
480 // If the 'scope' is 'this' and this is the first call, then the spatial node is the one we just created.
481 // This is not unlikely, as it can make sense to put all child nodes that should receive light under the light node...
482 if (m_scope == this)
483 light->m_scope = light;
484 else
485 light->m_scope = static_cast<QSSGRenderNode*>(QQuick3DObjectPrivate::get(item: m_scope)->spatialNode);
486 } else {
487 light->m_scope = nullptr;
488 }
489
490 return node;
491}
492
493QT_END_NAMESPACE
494

source code of qtquick3d/src/quick3d/qquick3dabstractlight.cpp