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 | |
11 | QT_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 | |
195 | QQuick3DAbstractLight::QQuick3DAbstractLight(QQuick3DNodePrivate &dd, QQuick3DNode *parent) |
196 | : QQuick3DNode(dd, parent) |
197 | , m_color(Qt::white) |
198 | , m_ambientColor(Qt::black) {} |
199 | |
200 | QQuick3DAbstractLight::~QQuick3DAbstractLight() {} |
201 | |
202 | QColor QQuick3DAbstractLight::color() const |
203 | { |
204 | return m_color; |
205 | } |
206 | |
207 | QColor QQuick3DAbstractLight::ambientColor() const |
208 | { |
209 | return m_ambientColor; |
210 | } |
211 | |
212 | float QQuick3DAbstractLight::brightness() const |
213 | { |
214 | return m_brightness; |
215 | } |
216 | |
217 | QQuick3DNode *QQuick3DAbstractLight::scope() const |
218 | { |
219 | return m_scope; |
220 | } |
221 | |
222 | bool QQuick3DAbstractLight::castsShadow() const |
223 | { |
224 | return m_castsShadow; |
225 | } |
226 | |
227 | float QQuick3DAbstractLight::shadowBias() const |
228 | { |
229 | return m_shadowBias; |
230 | } |
231 | |
232 | float QQuick3DAbstractLight::shadowFactor() const |
233 | { |
234 | return m_shadowFactor; |
235 | } |
236 | |
237 | QQuick3DAbstractLight::QSSGShadowMapQuality QQuick3DAbstractLight::shadowMapQuality() const |
238 | { |
239 | return m_shadowMapQuality; |
240 | } |
241 | |
242 | QQuick3DAbstractLight::QSSGSoftShadowQuality QQuick3DAbstractLight::softShadowQuality() const |
243 | { |
244 | return m_softShadowQuality; |
245 | } |
246 | |
247 | float QQuick3DAbstractLight::shadowMapFar() const |
248 | { |
249 | return m_shadowMapFar; |
250 | } |
251 | |
252 | float QQuick3DAbstractLight::shadowFilter() const |
253 | { |
254 | return m_shadowFilter; |
255 | } |
256 | |
257 | QQuick3DAbstractLight::QSSGBakeMode QQuick3DAbstractLight::bakeMode() const |
258 | { |
259 | return m_bakeMode; |
260 | } |
261 | |
262 | float QQuick3DAbstractLight::pcfFactor() const |
263 | { |
264 | return m_pcfFactor; |
265 | } |
266 | |
267 | void 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 | |
278 | void 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 | |
289 | void 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 | |
300 | void 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 | |
311 | void QQuick3DAbstractLight::setScope(QQuick3DNode *scope) |
312 | { |
313 | if (m_scope == scope) |
314 | return; |
315 | |
316 | m_scope = scope; |
317 | emit scopeChanged(); |
318 | update(); |
319 | } |
320 | |
321 | void 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 | |
332 | void 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 | |
343 | void 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 | |
355 | void 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 | |
367 | void 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 | |
378 | void 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 | |
389 | void 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 | |
400 | void 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 | |
411 | void 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 | |
422 | quint32 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 | |
437 | QSSGRenderGraphObject *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 | |
493 | QT_END_NAMESPACE |
494 | |