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 range is [-1.0, 1.0]. Generally, a value
72 inside [-0.1, 0.1] is sufficient.
73 The default value is 0.
74*/
75
76/*!
77 \qmlproperty real Light::shadowFactor
78 This property determines how dark the cast shadows should be. The value range is [0, 100], where
79 0 means no shadows and 100 means the light is fully shadowed.
80 The default value is 5.
81*/
82
83/*!
84 \qmlproperty enumeration Light::shadowMapQuality
85 The property sets the quality of the shadow map created for shadow rendering. Lower quality uses
86 less resources, but produces lower quality shadows while higher quality uses more resources, but
87 produces better quality shadows.
88
89 Supported quality values are:
90 \value Light.ShadowMapQualityLow Render shadowmap using 256x256 texture.
91 \value Light.ShadowMapQualityMedium Render shadowmap using 512x512 texture.
92 \value Light.ShadowMapQualityHigh Render shadowmap using 1024x1024 texture.
93 \value Light.ShadowMapQualityVeryHigh Render shadowmap using 2048x2048 texture.
94
95 The default value is \c Light.ShadowMapQualityLow
96*/
97
98/*!
99 \qmlproperty real Light::shadowMapFar
100 The property determines the maximum distance for the shadow map. Smaller
101 values improve the precision and effects of the map.
102 The default value is 5000. Unit is points in local coordinate space.
103*/
104
105/*!
106 \qmlproperty real Light::shadowFilter
107 This property sets how much blur is applied to the shadows.
108 The default value is 5.
109*/
110
111/*!
112 \qmlproperty enumeration Light::bakeMode
113 The property controls if the light is active in baked lighting, such as
114 when generating lightmaps.
115
116 \value Light.BakeModeDisabled The light is not used in baked lighting.
117
118 \value Light.BakeModeIndirect Indirect lighting contribution (for global
119 illumination) is baked for this light. Direct lighting (diffuse, specular,
120 real-time shadow mapping) is calculated normally for the light at run time.
121 At run time, when not in baking mode, the renderer will attempt to sample
122 the lightmap to get the indirect lighting data and combine that with the
123 results of the real-time calculations.
124
125 \value Light.BakeModeAll Both direct (diffuse, shadow) and indirect
126 lighting is baked for this light. The light will not have a specular
127 contribution and will not generate realtime shadow maps, but it will always
128 have baked shadows. At run time, when not in baking mode, the renderer will
129 attempt to sample the lightmap in place of the standard, real-time
130 calculations for diffuse lighting and shadow mapping.
131
132 The default value is \c Light.BakeModeDisabled
133
134 \note Just as with \l Model::usedInBakedLighting, designers and developers
135 must always evaluate on a per-light basis if the light is suitable to take
136 part in baked lighting.
137
138 \warning Lights with dynamically changing properties, for example, animated
139 position, rotation, or other properties, are not suitable for participating
140 in baked lighting.
141
142 This property is relevant both when baking and when using lightmaps. A
143 consistent state between the baking run and the subsequent runs that use
144 the generated data is essential. Changing to a different value will not
145 change the previously generated and persistently stored data in the
146 lightmaps, the engine's rendering behavior will however follow the
147 property's current value.
148
149 For more information on how to bake lightmaps, see the \l {Lightmaps and
150 Global Illumination}.
151
152 \sa Model::usedInBakedLighting, Model::bakedLightmap, Lightmapper, {Lightmaps and Global Illumination}
153*/
154
155QQuick3DAbstractLight::QQuick3DAbstractLight(QQuick3DNodePrivate &dd, QQuick3DNode *parent)
156 : QQuick3DNode(dd, parent)
157 , m_color(Qt::white)
158 , m_ambientColor(Qt::black) {}
159
160QQuick3DAbstractLight::~QQuick3DAbstractLight() {}
161
162QColor QQuick3DAbstractLight::color() const
163{
164 return m_color;
165}
166
167QColor QQuick3DAbstractLight::ambientColor() const
168{
169 return m_ambientColor;
170}
171
172float QQuick3DAbstractLight::brightness() const
173{
174 return m_brightness;
175}
176
177QQuick3DNode *QQuick3DAbstractLight::scope() const
178{
179 return m_scope;
180}
181
182bool QQuick3DAbstractLight::castsShadow() const
183{
184 return m_castsShadow;
185}
186
187float QQuick3DAbstractLight::shadowBias() const
188{
189 return m_shadowBias;
190}
191
192float QQuick3DAbstractLight::shadowFactor() const
193{
194 return m_shadowFactor;
195}
196
197QQuick3DAbstractLight::QSSGShadowMapQuality QQuick3DAbstractLight::shadowMapQuality() const
198{
199 return m_shadowMapQuality;
200}
201
202float QQuick3DAbstractLight::shadowMapFar() const
203{
204 return m_shadowMapFar;
205}
206
207float QQuick3DAbstractLight::shadowFilter() const
208{
209 return m_shadowFilter;
210}
211
212QQuick3DAbstractLight::QSSGBakeMode QQuick3DAbstractLight::bakeMode() const
213{
214 return m_bakeMode;
215}
216
217void QQuick3DAbstractLight::markAllDirty()
218{
219 m_dirtyFlags = DirtyFlags(DirtyFlag::ShadowDirty)
220 | DirtyFlags(DirtyFlag::ColorDirty)
221 | DirtyFlags(DirtyFlag::BrightnessDirty)
222 | DirtyFlags(DirtyFlag::FadeDirty)
223 | DirtyFlags(DirtyFlag::AreaDirty)
224 | DirtyFlags(DirtyFlag::BakeModeDirty);
225 QQuick3DNode::markAllDirty();
226}
227
228void QQuick3DAbstractLight::setColor(const QColor &color)
229{
230 if (m_color == color)
231 return;
232
233 m_color = color;
234 m_dirtyFlags.setFlag(flag: DirtyFlag::ColorDirty);
235 emit colorChanged();
236 update();
237}
238
239void QQuick3DAbstractLight::setAmbientColor(const QColor &ambientColor)
240{
241 if (m_ambientColor == ambientColor)
242 return;
243
244 m_ambientColor = ambientColor;
245 m_dirtyFlags.setFlag(flag: DirtyFlag::ColorDirty);
246 emit ambientColorChanged();
247 update();
248}
249
250void QQuick3DAbstractLight::setBrightness(float brightness)
251{
252 if (qFuzzyCompare(p1: m_brightness, p2: brightness))
253 return;
254
255 m_brightness = brightness;
256 m_dirtyFlags.setFlag(flag: DirtyFlag::BrightnessDirty);
257 emit brightnessChanged();
258 update();
259}
260
261void QQuick3DAbstractLight::setScope(QQuick3DNode *scope)
262{
263 if (m_scope == scope)
264 return;
265
266 m_scope = scope;
267 emit scopeChanged();
268 update();
269}
270
271void QQuick3DAbstractLight::setCastsShadow(bool castsShadow)
272{
273 if (m_castsShadow == castsShadow)
274 return;
275
276 m_castsShadow = castsShadow;
277 m_dirtyFlags.setFlag(flag: DirtyFlag::ShadowDirty);
278 emit castsShadowChanged();
279 update();
280}
281
282void QQuick3DAbstractLight::setShadowBias(float shadowBias)
283{
284 shadowBias = qBound(min: -1.0f, val: shadowBias, max: 1.0f);
285 if (qFuzzyCompare(p1: m_shadowBias, p2: shadowBias))
286 return;
287
288 m_shadowBias = shadowBias;
289 m_dirtyFlags.setFlag(flag: DirtyFlag::ShadowDirty);
290 emit shadowBiasChanged();
291 update();
292}
293
294void QQuick3DAbstractLight::setShadowFactor(float shadowFactor)
295{
296 shadowFactor = qBound(min: 0.0f, val: shadowFactor, max: 100.0f);
297 if (qFuzzyCompare(p1: m_shadowFactor, p2: shadowFactor))
298 return;
299
300 m_shadowFactor = shadowFactor;
301 m_dirtyFlags.setFlag(flag: DirtyFlag::ShadowDirty);
302 emit shadowFactorChanged();
303 update();
304}
305
306void QQuick3DAbstractLight::setShadowMapQuality(
307 QQuick3DAbstractLight::QSSGShadowMapQuality shadowMapQuality)
308{
309 if (m_shadowMapQuality == shadowMapQuality)
310 return;
311
312 m_shadowMapQuality = shadowMapQuality;
313 m_dirtyFlags.setFlag(flag: DirtyFlag::ShadowDirty);
314 emit shadowMapQualityChanged();
315 update();
316}
317
318void QQuick3DAbstractLight::setBakeMode(QQuick3DAbstractLight::QSSGBakeMode bakeMode)
319{
320 if (m_bakeMode == bakeMode)
321 return;
322
323 m_bakeMode = bakeMode;
324 m_dirtyFlags.setFlag(flag: DirtyFlag::BakeModeDirty);
325 emit bakeModeChanged();
326 update();
327}
328
329void QQuick3DAbstractLight::setShadowMapFar(float shadowMapFar)
330{
331 if (qFuzzyCompare(p1: m_shadowMapFar, p2: shadowMapFar))
332 return;
333
334 m_shadowMapFar = shadowMapFar;
335 m_dirtyFlags.setFlag(flag: DirtyFlag::ShadowDirty);
336 emit shadowMapFarChanged();
337 update();
338}
339
340void QQuick3DAbstractLight::setShadowFilter(float shadowFilter)
341{
342 if (qFuzzyCompare(p1: m_shadowFilter, p2: shadowFilter))
343 return;
344
345 m_shadowFilter = shadowFilter;
346 m_dirtyFlags.setFlag(flag: DirtyFlag::ShadowDirty);
347 emit shadowFilterChanged();
348 update();
349}
350
351quint32 QQuick3DAbstractLight::mapToShadowResolution(QSSGShadowMapQuality quality)
352{
353 switch (quality) {
354 case QSSGShadowMapQuality::ShadowMapQualityMedium:
355 return 9;
356 case QSSGShadowMapQuality::ShadowMapQualityHigh:
357 return 10;
358 case QSSGShadowMapQuality::ShadowMapQualityVeryHigh:
359 return 11;
360 default:
361 break;
362 }
363 return 8;
364}
365
366QSSGRenderGraphObject *QQuick3DAbstractLight::updateSpatialNode(QSSGRenderGraphObject *node)
367{
368 Q_ASSERT_X(node, __FUNCTION__, "Node must have been created in parent class.");
369
370 QQuick3DNode::updateSpatialNode(node);
371
372 QSSGRenderLight *light = static_cast<QSSGRenderLight *>(node);
373
374 if (m_dirtyFlags.toInt() != 0) // Some flag was set, so mark the light dirty!
375 light->markDirty(dirtyFlag: QSSGRenderLight::DirtyFlag::LightDirty);
376
377 if (m_dirtyFlags.testFlag(flag: DirtyFlag::ColorDirty)) {
378 m_dirtyFlags.setFlag(flag: DirtyFlag::ColorDirty, on: false);
379 light->m_diffuseColor = QSSGUtils::color::sRGBToLinear(color: m_color).toVector3D();
380 light->m_specularColor = light->m_diffuseColor;
381 light->m_ambientColor = QSSGUtils::color::sRGBToLinear(color: m_ambientColor).toVector3D();
382 }
383
384 if (m_dirtyFlags.testFlag(flag: DirtyFlag::BrightnessDirty)) {
385 m_dirtyFlags.setFlag(flag: DirtyFlag::BrightnessDirty, on: false);
386 light->m_brightness = m_brightness;
387 }
388
389 if (m_dirtyFlags.testFlag(flag: DirtyFlag::ShadowDirty)) {
390 m_dirtyFlags.setFlag(flag: DirtyFlag::ShadowDirty, on: false);
391 light->m_castShadow = m_castsShadow;
392 light->m_shadowBias = m_shadowBias;
393 light->m_shadowFactor = m_shadowFactor;
394 light->m_shadowMapRes = mapToShadowResolution(quality: m_shadowMapQuality);
395 light->m_shadowMapFar = m_shadowMapFar;
396 light->m_shadowFilter = m_shadowFilter;
397 }
398
399 if (m_dirtyFlags.testFlag(flag: DirtyFlag::BakeModeDirty)) {
400 m_dirtyFlags.setFlag(flag: DirtyFlag::BakeModeDirty, on: false);
401 light->m_bakingEnabled = m_bakeMode != QSSGBakeMode::BakeModeDisabled;
402 light->m_fullyBaked = m_bakeMode == QSSGBakeMode::BakeModeAll;
403 }
404
405 if (m_scope) {
406 // Special case:
407 // If the 'scope' is 'this' and this is the first call, then the spatial node is the one we just created.
408 // This is not unlikely, as it can make sense to put all child nodes that should receive light under the light node...
409 if (m_scope == this)
410 light->m_scope = light;
411 else
412 light->m_scope = static_cast<QSSGRenderNode*>(QQuick3DObjectPrivate::get(item: m_scope)->spatialNode);
413 } else {
414 light->m_scope = nullptr;
415 }
416
417 return node;
418}
419
420QT_END_NAMESPACE
421

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