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 a 256x256 texture.
93 \value Light.ShadowMapQualityMedium Render shadowmap using a 512x512 texture.
94 \value Light.ShadowMapQualityHigh Render shadowmap using a 1024x1024 texture.
95 \value Light.ShadowMapQualityVeryHigh Render shadowmap using a 2048x2048 texture.
96 \value Light.ShadowMapQualityUltra Render shadowmap using a 4096x4096 texture.
97
98 The default value is \c Light.ShadowMapQualityLow
99*/
100
101/*!
102 \qmlproperty real Light::shadowMapFar
103 The property determines the maximum distance for the shadow map. Smaller
104 values improve the precision and effects of the map.
105 The default value is 5000. Unit is points in local coordinate space.
106*/
107
108/*!
109 \qmlproperty real Light::shadowFilter
110 This property sets how much blur is applied to the shadows.
111
112 The default value is 5.
113
114 \deprecated [6.8] No longer used for anything, use \l{Light::}{pcfFactor} instead.
115
116 \sa Light::softShadowQuality
117*/
118
119/*!
120 \qmlproperty enumeration Light::bakeMode
121 The property controls if the light is active in baked lighting, such as
122 when generating lightmaps.
123
124 \value Light.BakeModeDisabled The light is not used in baked lighting.
125
126 \value Light.BakeModeIndirect Indirect lighting contribution (for global
127 illumination) is baked for this light. Direct lighting (diffuse, specular,
128 real-time shadow mapping) is calculated normally for the light at run time.
129 At run time, when not in baking mode, the renderer will attempt to sample
130 the lightmap to get the indirect lighting data and combine that with the
131 results of the real-time calculations.
132
133 \value Light.BakeModeAll Both direct (diffuse, shadow) and indirect
134 lighting is baked for this light. The light will not have a specular
135 contribution and will not generate realtime shadow maps, but it will always
136 have baked shadows. At run time, when not in baking mode, the renderer will
137 attempt to sample the lightmap in place of the standard, real-time
138 calculations for diffuse lighting and shadow mapping.
139
140 The default value is \c Light.BakeModeDisabled
141
142 \note Just as with \l Model::usedInBakedLighting, designers and developers
143 must always evaluate on a per-light basis if the light is suitable to take
144 part in baked lighting.
145
146 \warning Lights with dynamically changing properties, for example, animated
147 position, rotation, or other properties, are not suitable for participating
148 in baked lighting.
149
150 This property is relevant both when baking and when using lightmaps. A
151 consistent state between the baking run and the subsequent runs that use
152 the generated data is essential. Changing to a different value will not
153 change the previously generated and persistently stored data in the
154 lightmaps, the engine's rendering behavior will however follow the
155 property's current value.
156
157 For more information on how to bake lightmaps, see the \l {Lightmaps and
158 Global Illumination}.
159
160 \sa Model::usedInBakedLighting, Model::bakedLightmap, Lightmapper, {Lightmaps and Global Illumination}
161*/
162
163/*!
164 \qmlproperty enumeration Light::softShadowQuality
165 \since 6.8
166
167 The property controls the soft shadow quality.
168
169 \value Light.Hard No soft shadows.
170 \value Light.PCF4 Percentage-closer filtering soft shadows with 4 samples.
171 \value Light.PCF8 Percentage-closer filtering soft shadows with 8 samples.
172 \value Light.PCF16 Percentage-closer filtering soft shadows with 16 samples.
173 \value Light.PCF32 Percentage-closer filtering soft shadows with 32 samples.
174 \value Light.PCF64 Percentage-closer filtering soft shadows with 64 samples.
175
176 Default value: \c Light.PCF4
177
178 \sa Light::pcfFactor, Light::shadowFilter
179*/
180
181/*!
182 \qmlproperty real Light::pcfFactor
183 \since 6.8
184
185 The property controls the PCF (percentage-closer filtering) factor. This
186 value tries to approximate the radius of a PCF filtering in world space.
187
188 \note PCF needs to be set in \l{Light::}{softShadowQuality} for this property
189 to have an effect.
190
191 Default value: \c{2.0}
192
193 \sa Light::softShadowQuality
194*/
195
196/*!
197 \qmlproperty bool Light::use32BitShadowmap
198 \since 6.9
199
200 The property controls if a 32-bit shadowmap depth buffer should be used for the light.
201
202 Default value: \c{false}
203
204 \sa Light::castsShadow
205*/
206
207QQuick3DAbstractLight::QQuick3DAbstractLight(QQuick3DNodePrivate &dd, QQuick3DNode *parent)
208 : QQuick3DNode(dd, parent)
209 , m_color(Qt::white)
210 , m_ambientColor(Qt::black) {}
211
212QQuick3DAbstractLight::~QQuick3DAbstractLight() {}
213
214QColor QQuick3DAbstractLight::color() const
215{
216 return m_color;
217}
218
219QColor QQuick3DAbstractLight::ambientColor() const
220{
221 return m_ambientColor;
222}
223
224float QQuick3DAbstractLight::brightness() const
225{
226 return m_brightness;
227}
228
229QQuick3DNode *QQuick3DAbstractLight::scope() const
230{
231 return m_scope;
232}
233
234bool QQuick3DAbstractLight::castsShadow() const
235{
236 return m_castsShadow;
237}
238
239float QQuick3DAbstractLight::shadowBias() const
240{
241 return m_shadowBias;
242}
243
244float QQuick3DAbstractLight::shadowFactor() const
245{
246 return m_shadowFactor;
247}
248
249QQuick3DAbstractLight::QSSGShadowMapQuality QQuick3DAbstractLight::shadowMapQuality() const
250{
251 return m_shadowMapQuality;
252}
253
254QQuick3DAbstractLight::QSSGSoftShadowQuality QQuick3DAbstractLight::softShadowQuality() const
255{
256 return m_softShadowQuality;
257}
258
259float QQuick3DAbstractLight::shadowMapFar() const
260{
261 return m_shadowMapFar;
262}
263
264float QQuick3DAbstractLight::shadowFilter() const
265{
266 return m_shadowFilter;
267}
268
269QQuick3DAbstractLight::QSSGBakeMode QQuick3DAbstractLight::bakeMode() const
270{
271 return m_bakeMode;
272}
273
274float QQuick3DAbstractLight::pcfFactor() const
275{
276 return m_pcfFactor;
277}
278
279bool QQuick3DAbstractLight::use32BitShadowmap() const
280{
281 return m_use32BitShadowmap;
282}
283
284void QQuick3DAbstractLight::markAllDirty()
285{
286 m_dirtyFlags = DirtyFlags(DirtyFlag::ShadowDirty)
287 | DirtyFlags(DirtyFlag::ColorDirty)
288 | DirtyFlags(DirtyFlag::BrightnessDirty)
289 | DirtyFlags(DirtyFlag::FadeDirty)
290 | DirtyFlags(DirtyFlag::AreaDirty)
291 | DirtyFlags(DirtyFlag::BakeModeDirty);
292 QQuick3DNode::markAllDirty();
293}
294
295void QQuick3DAbstractLight::setColor(const QColor &color)
296{
297 if (m_color == color)
298 return;
299
300 m_color = color;
301 m_dirtyFlags.setFlag(flag: DirtyFlag::ColorDirty);
302 emit colorChanged();
303 update();
304}
305
306void QQuick3DAbstractLight::setAmbientColor(const QColor &ambientColor)
307{
308 if (m_ambientColor == ambientColor)
309 return;
310
311 m_ambientColor = ambientColor;
312 m_dirtyFlags.setFlag(flag: DirtyFlag::ColorDirty);
313 emit ambientColorChanged();
314 update();
315}
316
317void QQuick3DAbstractLight::setBrightness(float brightness)
318{
319 if (qFuzzyCompare(p1: m_brightness, p2: brightness))
320 return;
321
322 m_brightness = brightness;
323 m_dirtyFlags.setFlag(flag: DirtyFlag::BrightnessDirty);
324 emit brightnessChanged();
325 update();
326}
327
328void QQuick3DAbstractLight::setScope(QQuick3DNode *scope)
329{
330 if (m_scope == scope)
331 return;
332
333 m_scope = scope;
334 emit scopeChanged();
335 update();
336}
337
338void QQuick3DAbstractLight::setCastsShadow(bool castsShadow)
339{
340 if (m_castsShadow == castsShadow)
341 return;
342
343 m_castsShadow = castsShadow;
344 m_dirtyFlags.setFlag(flag: DirtyFlag::ShadowDirty);
345 emit castsShadowChanged();
346 update();
347}
348
349void QQuick3DAbstractLight::setShadowBias(float shadowBias)
350{
351 if (qFuzzyCompare(p1: m_shadowBias, p2: shadowBias))
352 return;
353
354 m_shadowBias = shadowBias;
355 m_dirtyFlags.setFlag(flag: DirtyFlag::ShadowDirty);
356 emit shadowBiasChanged();
357 update();
358}
359
360void QQuick3DAbstractLight::setShadowFactor(float shadowFactor)
361{
362 shadowFactor = qBound(min: 0.0f, val: shadowFactor, max: 100.0f);
363 if (qFuzzyCompare(p1: m_shadowFactor, p2: shadowFactor))
364 return;
365
366 m_shadowFactor = shadowFactor;
367 m_dirtyFlags.setFlag(flag: DirtyFlag::ShadowDirty);
368 emit shadowFactorChanged();
369 update();
370}
371
372void QQuick3DAbstractLight::setShadowMapQuality(
373 QQuick3DAbstractLight::QSSGShadowMapQuality shadowMapQuality)
374{
375 if (m_shadowMapQuality == shadowMapQuality)
376 return;
377
378 m_shadowMapQuality = shadowMapQuality;
379 m_dirtyFlags.setFlag(flag: DirtyFlag::ShadowDirty);
380 emit shadowMapQualityChanged();
381 update();
382}
383
384void QQuick3DAbstractLight::setSoftShadowQuality(QSSGSoftShadowQuality softShadowQuality)
385{
386 if (m_softShadowQuality == softShadowQuality)
387 return;
388
389 m_softShadowQuality = softShadowQuality;
390 m_dirtyFlags.setFlag(flag: DirtyFlag::ShadowDirty);
391 emit softShadowQualityChanged();
392 update();
393}
394
395void QQuick3DAbstractLight::setBakeMode(QQuick3DAbstractLight::QSSGBakeMode bakeMode)
396{
397 if (m_bakeMode == bakeMode)
398 return;
399
400 m_bakeMode = bakeMode;
401 m_dirtyFlags.setFlag(flag: DirtyFlag::BakeModeDirty);
402 emit bakeModeChanged();
403 update();
404}
405
406void QQuick3DAbstractLight::setPcfFactor(float pcfFactor)
407{
408 if (m_pcfFactor == pcfFactor)
409 return;
410
411 m_pcfFactor = pcfFactor;
412 m_dirtyFlags.setFlag(flag: DirtyFlag::ShadowDirty);
413 emit pcfFactorChanged();
414 update();
415}
416
417void QQuick3DAbstractLight::setUse32BitShadowmap(bool use32BitShadowmap)
418{
419 if (m_use32BitShadowmap == use32BitShadowmap)
420 return;
421
422 m_use32BitShadowmap = use32BitShadowmap;
423 m_dirtyFlags.setFlag(flag: DirtyFlag::ShadowDirty);
424 emit use32BitShadowmapChanged();
425 update();
426}
427
428void QQuick3DAbstractLight::setShadowMapFar(float shadowMapFar)
429{
430 if (qFuzzyCompare(p1: m_shadowMapFar, p2: shadowMapFar))
431 return;
432
433 m_shadowMapFar = shadowMapFar;
434 m_dirtyFlags.setFlag(flag: DirtyFlag::ShadowDirty);
435 emit shadowMapFarChanged();
436 update();
437}
438
439void QQuick3DAbstractLight::setShadowFilter(float shadowFilter)
440{
441 if (qFuzzyCompare(p1: m_shadowFilter, p2: shadowFilter))
442 return;
443
444 m_shadowFilter = shadowFilter;
445 m_dirtyFlags.setFlag(flag: DirtyFlag::ShadowDirty);
446 emit shadowFilterChanged();
447 update();
448}
449
450quint32 QQuick3DAbstractLight::mapToShadowResolution(QSSGShadowMapQuality quality)
451{
452 switch (quality) {
453 case QSSGShadowMapQuality::ShadowMapQualityMedium:
454 return 512;
455 case QSSGShadowMapQuality::ShadowMapQualityHigh:
456 return 1024;
457 case QSSGShadowMapQuality::ShadowMapQualityVeryHigh:
458 return 2048;
459 case QSSGShadowMapQuality::ShadowMapQualityUltra:
460 return 4096;
461 default:
462 break;
463 }
464 return 256;
465}
466
467QSSGRenderGraphObject *QQuick3DAbstractLight::updateSpatialNode(QSSGRenderGraphObject *node)
468{
469 Q_ASSERT_X(node, __FUNCTION__, "Node must have been created in parent class.");
470
471 QQuick3DNode::updateSpatialNode(node);
472
473 QSSGRenderLight *light = static_cast<QSSGRenderLight *>(node);
474
475 if (m_dirtyFlags.toInt() != 0) // Some flag was set, so mark the light dirty!
476 light->markDirty(dirtyFlag: QSSGRenderLight::DirtyFlag::LightDirty);
477
478 if (m_dirtyFlags.testFlag(flag: DirtyFlag::ColorDirty)) {
479 m_dirtyFlags.setFlag(flag: DirtyFlag::ColorDirty, on: false);
480 light->m_diffuseColor = QSSGUtils::color::sRGBToLinear(color: m_color).toVector3D();
481 light->m_specularColor = light->m_diffuseColor;
482 light->m_ambientColor = QSSGUtils::color::sRGBToLinear(color: m_ambientColor).toVector3D();
483 }
484
485 if (m_dirtyFlags.testFlag(flag: DirtyFlag::BrightnessDirty)) {
486 m_dirtyFlags.setFlag(flag: DirtyFlag::BrightnessDirty, on: false);
487 light->m_brightness = m_brightness;
488 }
489
490 if (m_dirtyFlags.testFlag(flag: DirtyFlag::ShadowDirty)) {
491 m_dirtyFlags.setFlag(flag: DirtyFlag::ShadowDirty, on: false);
492 light->m_castShadow = m_castsShadow;
493 light->m_shadowBias = m_shadowBias;
494 light->m_shadowFactor = m_shadowFactor;
495 light->m_shadowMapRes = mapToShadowResolution(quality: m_shadowMapQuality);
496 light->m_softShadowQuality = static_cast<QSSGRenderLight::SoftShadowQuality>(m_softShadowQuality);
497 light->m_shadowMapFar = m_shadowMapFar;
498 light->m_shadowFilter = m_shadowFilter;
499 light->m_pcfFactor = m_pcfFactor;
500 light->m_use32BitShadowmap = m_use32BitShadowmap;
501 }
502
503 if (m_dirtyFlags.testFlag(flag: DirtyFlag::BakeModeDirty)) {
504 m_dirtyFlags.setFlag(flag: DirtyFlag::BakeModeDirty, on: false);
505 light->m_bakingEnabled = m_bakeMode != QSSGBakeMode::BakeModeDisabled;
506 light->m_fullyBaked = m_bakeMode == QSSGBakeMode::BakeModeAll;
507 }
508
509 if (m_scope) {
510 // Special case:
511 // If the 'scope' is 'this' and this is the first call, then the spatial node is the one we just created.
512 // This is not unlikely, as it can make sense to put all child nodes that should receive light under the light node...
513 if (m_scope == this)
514 light->m_scope = light;
515 else
516 light->m_scope = static_cast<QSSGRenderNode*>(QQuick3DObjectPrivate::get(item: m_scope)->spatialNode);
517 } else {
518 light->m_scope = nullptr;
519 }
520
521 return node;
522}
523
524QT_END_NAMESPACE
525

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