1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qquick3dreflectionprobe_p.h"
5#include "qquick3dnode_p_p.h"
6
7#include <QtQuick3DRuntimeRender/private/qssgrenderreflectionprobe_p.h>
8#include <QtQuick3DUtils/private/qssgutils_p.h>
9
10QT_BEGIN_NAMESPACE
11
12/*!
13 \qmltype ReflectionProbe
14 \inherits Node
15 \inqmlmodule QtQuick3D
16 \brief Defines a reflection probe in the scene.
17
18 A reflection probe is used to provide reflections of the current scene to the objects. The probe
19 provides properties to the runtime which are then used to render the scene to a cube map. The cube map
20 is then used as the reflection information for the reflecting objects.
21
22 \sa {Qt Quick 3D - Reflection Probes Example}
23*/
24
25QQuick3DReflectionProbe::QQuick3DReflectionProbe(QQuick3DNode *parent)
26 : QQuick3DNode(*(new QQuick3DNodePrivate(QQuick3DNodePrivate::Type::ReflectionProbe)), parent)
27{
28 QObject::connect(sender: this, signal: &QQuick3DReflectionProbe::scenePositionChanged, context: this, slot: &QQuick3DReflectionProbe::updateDebugView);
29}
30
31void QQuick3DReflectionProbe::itemChange(QQuick3DObject::ItemChange change,
32 const QQuick3DObject::ItemChangeData &value)
33{
34 if (change == QQuick3DObject::ItemSceneChange)
35 updateSceneManager(window: value.sceneManager);
36}
37
38/*!
39 \qmlproperty enumeration ReflectionProbe::quality
40
41 Quality determines the resolution of the cube map.
42
43 Possible values are:
44 \value ReflectionProbe.VeryLow
45 Renders a reflection map using a 128x128 texture.
46 \value ReflectionProbe.Low
47 Renders a reflection map using a 256x256 texture.
48 \value ReflectionProbe.Medium
49 Renders a reflection map using a 512x512 texture.
50 \value ReflectionProbe.High
51 Renders a reflection map using a 1024x1024 texture.
52 \value ReflectionProbe.VeryHigh
53 Renders a reflection map using a 2048x2048 texture.
54
55 The default value is \c ReflectionProbe.Low
56*/
57QQuick3DReflectionProbe::ReflectionQuality QQuick3DReflectionProbe::quality() const
58{
59 return m_quality;
60}
61
62/*!
63 \qmlproperty Color ReflectionProbe::clearColor
64
65 Clear color is the color used to clear the cube map prior rendering the scene.
66*/
67QColor QQuick3DReflectionProbe::clearColor() const
68{
69 return m_clearColor;
70}
71
72/*!
73 \qmlproperty enumeration ReflectionProbe::refreshMode
74
75 Refresh mode tells the runtime how often the cube map should be updated.
76
77 Possible values are:
78 \value ReflectionProbe.FirstFrame
79 Renders the scene on the first frame.
80 \value ReflectionProbe.EveryFrame
81 Renders the scene every frame.
82
83 The default value is \c ReflectionProbe.EveryFrame
84 \note Use \c ReflectionProbe.FirstFrame for improved performance.
85*/
86QQuick3DReflectionProbe::ReflectionRefreshMode QQuick3DReflectionProbe::refreshMode() const
87{
88 return m_refreshMode;
89}
90
91/*!
92 \qmlproperty enumeration ReflectionProbe::timeSlicing
93
94 Time slicing determines how the cube map render is timed.
95
96 Possible values are:
97 \value ReflectionProbe.None
98 All faces of the cube map are rendered and prefiltered during one frame.
99
100 \value ReflectionProbe.AllFacesAtOnce
101 All faces are rendered during one frame but the prefiltering
102 is divided to subsquent frames with each mip level handled on
103 their own frame. Rough surface reflections are thus refreshed
104 every sixth frame while smooth surfaces have reflections
105 that refresh every frame.
106
107 \value ReflectionProbe.IndividualFaces
108 Each face is rendered and prefiltered in a separate frame.
109 Thus all reflections are refreshed every sixth frame.
110
111 The default value is \c ReflectionProbe.None
112 \note Use \c ReflectionProbe.AllFacesAtOnce or
113 \c ReflectionProbe.IndividualFaces to increase performance.
114*/
115QQuick3DReflectionProbe::ReflectionTimeSlicing QQuick3DReflectionProbe::timeSlicing() const
116{
117 return m_timeSlicing;
118}
119
120/*!
121 \qmlproperty bool ReflectionProbe::parallaxCorrection
122
123 By default the reflections provided by the reflection probe are assumed to be from an infinite distance similar
124 to the skybox. This works fine for environmental reflections but for tight spaces this causes perspective errors
125 in the reflections. To fix this parallax correction can be turned on. The distance of the reflection is then
126 determined by the \l ReflectionProbe::boxSize property.
127
128 \sa boxSize
129*/
130bool QQuick3DReflectionProbe::parallaxCorrection() const
131{
132 return m_parallaxCorrection;
133}
134
135/*!
136 \qmlproperty vector3d ReflectionProbe::boxSize
137
138 Box size is used to determine which objects get their reflections from this ReflectionProbe. Objects that are
139 inside the box are under the influence of this ReflectionProbe. If an object lies inside more than one reflection
140 probe at the same time, the object is considered to be inside the nearest reflection probe.
141 With \l ReflectionProbe::parallaxCorrection turned on the size is also used to calculate the distance of
142 the reflections in the cube map.
143
144 \sa parallaxCorrection
145*/
146QVector3D QQuick3DReflectionProbe::boxSize() const
147{
148 return m_boxSize;
149}
150
151/*!
152 \qmlproperty bool ReflectionProbe::debugView
153 \since 6.4
154
155 If this property is set to true, a wireframe is rendered to visualize the reflection probe box.
156*/
157bool QQuick3DReflectionProbe::debugView() const
158{
159 return m_debugView;
160}
161
162/*!
163 \qmlproperty vector3d ReflectionProbe::boxOffset
164
165 Box offset is used to move the box relative to the reflection probe position. Since the probe
166 captures the environment from its position, this property can be used to move the box around
167 without affecting the position where the probe capture the environment. This property
168 alongside with \l ReflectionProbe::boxSize will be used to determine the object that fall
169 inside the box.
170 With \l ReflectionProbe::parallaxCorrection turned on, this property can be used to position
171 the box to get more accurate reflections.
172
173 \sa parallaxCorrection
174*/
175QVector3D QQuick3DReflectionProbe::boxOffset() const
176{
177 return m_boxOffset;
178}
179
180/*!
181 \qmlproperty CubeMapTexture ReflectionProbe::texture
182 \since 6.5
183
184 Instead of rendering the scene, this cube map texture is used to show reflections
185 in objects affected by this reflection probe.
186
187 \sa CubeMapTexture
188*/
189QQuick3DCubeMapTexture *QQuick3DReflectionProbe::texture() const
190{
191 return m_texture;
192}
193
194/*!
195 \qmlmethod ReflectionProbe::scheduleUpdate()
196
197 Updates the reflection probe render when called while \l ReflectionProbe::refreshMode
198 is set as \c ReflectionProbe.FirstFrame.
199*/
200void QQuick3DReflectionProbe::scheduleUpdate()
201{
202 m_dirtyFlags.setFlag(flag: DirtyFlag::RefreshModeDirty);
203 update();
204}
205
206void QQuick3DReflectionProbe::setQuality(QQuick3DReflectionProbe::ReflectionQuality reflectionQuality)
207{
208 if (m_quality == reflectionQuality)
209 return;
210
211 m_quality = reflectionQuality;
212 m_dirtyFlags.setFlag(flag: DirtyFlag::QualityDirty);
213 emit qualityChanged();
214 update();
215}
216
217void QQuick3DReflectionProbe::setClearColor(const QColor &clearColor)
218{
219 if (m_clearColor == clearColor)
220 return;
221 m_clearColor = clearColor;
222 m_dirtyFlags.setFlag(flag: DirtyFlag::ClearColorDirty);
223 emit clearColorChanged();
224 update();
225}
226
227void QQuick3DReflectionProbe::setRefreshMode(ReflectionRefreshMode newRefreshMode)
228{
229 if (m_refreshMode == newRefreshMode)
230 return;
231 m_refreshMode = newRefreshMode;
232 m_dirtyFlags.setFlag(flag: DirtyFlag::RefreshModeDirty);
233 emit refreshModeChanged();
234 update();
235}
236
237void QQuick3DReflectionProbe::setTimeSlicing(ReflectionTimeSlicing newTimeSlicing)
238{
239 if (m_timeSlicing == newTimeSlicing)
240 return;
241 m_timeSlicing = newTimeSlicing;
242 m_dirtyFlags.setFlag(flag: DirtyFlag::TimeSlicingDirty);
243 emit timeSlicingChanged();
244 update();
245}
246
247void QQuick3DReflectionProbe::setParallaxCorrection(bool parallaxCorrection)
248{
249 if (m_parallaxCorrection == parallaxCorrection)
250 return;
251 m_parallaxCorrection = parallaxCorrection;
252 m_dirtyFlags.setFlag(flag: DirtyFlag::ParallaxCorrectionDirty);
253 emit parallaxCorrectionChanged();
254 update();
255}
256
257void QQuick3DReflectionProbe::setBoxSize(const QVector3D &boxSize)
258{
259 if (m_boxSize == boxSize)
260 return;
261 m_boxSize = boxSize;
262 m_dirtyFlags.setFlag(flag: DirtyFlag::BoxDirty);
263 emit boxSizeChanged();
264 createDebugView();
265 updateDebugView();
266 update();
267}
268
269void QQuick3DReflectionProbe::setDebugView(bool debugView)
270{
271 if (m_debugView == debugView)
272 return;
273 m_debugView = debugView;
274 emit debugViewChanged();
275 createDebugView();
276 updateDebugView();
277}
278
279void QQuick3DReflectionProbe::setBoxOffset(const QVector3D &boxOffset)
280{
281 if (m_boxOffset == boxOffset)
282 return;
283 m_boxOffset = boxOffset;
284 m_dirtyFlags.setFlag(flag: DirtyFlag::BoxDirty);
285 emit boxOffsetChanged();
286 createDebugView();
287 updateDebugView();
288 update();
289}
290
291void QQuick3DReflectionProbe::setTexture(QQuick3DCubeMapTexture *newTexture)
292{
293 if (m_texture == newTexture)
294 return;
295 QQuick3DObjectPrivate::attachWatcher(context: this, setter: &QQuick3DReflectionProbe::setTexture, newO: newTexture, oldO: m_texture);
296 m_texture = newTexture;
297 m_dirtyFlags.setFlag(flag: DirtyFlag::TextureDirty);
298 emit textureChanged();
299 update();
300}
301
302QSSGRenderGraphObject *QQuick3DReflectionProbe::updateSpatialNode(QSSGRenderGraphObject *node)
303{
304 if (!node) {
305 markAllDirty();
306 node = new QSSGRenderReflectionProbe();
307 }
308
309 QQuick3DNode::updateSpatialNode(node);
310
311 QSSGRenderReflectionProbe *probe = static_cast<QSSGRenderReflectionProbe *>(node);
312
313 if (m_dirtyFlags.testFlag(flag: DirtyFlag::QualityDirty)) {
314 m_dirtyFlags.setFlag(flag: DirtyFlag::QualityDirty, on: false);
315 probe->reflectionMapRes = mapToReflectionResolution(quality: m_quality);
316 }
317
318 if (m_dirtyFlags.testFlag(flag: DirtyFlag::ClearColorDirty)) {
319 m_dirtyFlags.setFlag(flag: DirtyFlag::ClearColorDirty, on: false);
320 probe->clearColor = m_clearColor;
321 }
322
323 if (m_dirtyFlags.testFlag(flag: DirtyFlag::RefreshModeDirty)) {
324 m_dirtyFlags.setFlag(flag: DirtyFlag::RefreshModeDirty, on: false);
325 switch (m_refreshMode) {
326 case ReflectionRefreshMode::FirstFrame:
327 probe->refreshMode = QSSGRenderReflectionProbe::ReflectionRefreshMode::FirstFrame;
328 break;
329 case ReflectionRefreshMode::EveryFrame:
330 probe->refreshMode = QSSGRenderReflectionProbe::ReflectionRefreshMode::EveryFrame;
331 break;
332 }
333 probe->hasScheduledUpdate = true;
334 }
335
336 if (m_dirtyFlags.testFlag(flag: DirtyFlag::TimeSlicingDirty)) {
337 m_dirtyFlags.setFlag(flag: DirtyFlag::TimeSlicingDirty, on: false);
338 switch (m_timeSlicing) {
339 case ReflectionTimeSlicing::None:
340 probe->timeSlicing = QSSGRenderReflectionProbe::ReflectionTimeSlicing::None;
341 break;
342 case ReflectionTimeSlicing::AllFacesAtOnce:
343 probe->timeSlicing = QSSGRenderReflectionProbe::ReflectionTimeSlicing::AllFacesAtOnce;
344 break;
345 case ReflectionTimeSlicing::IndividualFaces:
346 probe->timeSlicing = QSSGRenderReflectionProbe::ReflectionTimeSlicing::IndividualFaces;
347 break;
348 }
349 }
350
351 if (m_dirtyFlags.testFlag(flag: DirtyFlag::ParallaxCorrectionDirty)) {
352 m_dirtyFlags.setFlag(flag: DirtyFlag::ParallaxCorrectionDirty, on: false);
353 probe->parallaxCorrection = m_parallaxCorrection;
354 }
355
356 if (m_dirtyFlags.testFlag(flag: DirtyFlag::BoxDirty)) {
357 m_dirtyFlags.setFlag(flag: DirtyFlag::BoxDirty, on: false);
358 probe->boxSize = m_boxSize;
359 probe->boxOffset = m_boxOffset;
360 }
361
362 if (m_dirtyFlags.testFlag(flag: DirtyFlag::TextureDirty)) {
363 m_dirtyFlags.setFlag(flag: DirtyFlag::TextureDirty, on: false);
364 if (m_texture)
365 probe->texture = m_texture->getRenderImage();
366 else
367 probe->texture = nullptr;
368 }
369
370 return node;
371}
372
373void QQuick3DReflectionProbe::markAllDirty()
374{
375 m_dirtyFlags = DirtyFlags(DirtyFlag::QualityDirty)
376 | DirtyFlags(DirtyFlag::ClearColorDirty)
377 | DirtyFlags(DirtyFlag::RefreshModeDirty)
378 | DirtyFlags(DirtyFlag::ParallaxCorrectionDirty)
379 | DirtyFlags(DirtyFlag::BoxDirty)
380 | DirtyFlags(DirtyFlag::TimeSlicingDirty);
381 QQuick3DNode::markAllDirty();
382}
383
384void QQuick3DReflectionProbe::findSceneView()
385{
386 // If we have not specified a scene view we find the first available one
387 if (m_sceneView != nullptr)
388 return;
389
390 QObject *parent = this;
391 while (parent->parent() != nullptr) {
392 parent = parent->parent();
393 }
394
395 // Breath-first search through children
396 QList<QObject *> queue;
397 queue.append(t: parent);
398 while (!queue.empty()) {
399 auto node = queue.takeFirst();
400 if (auto converted = qobject_cast<QQuick3DViewport *>(object: node); converted != nullptr) {
401 m_sceneView = converted;
402 break;
403 }
404 queue.append(l: node->children());
405 }
406}
407
408void QQuick3DReflectionProbe::createDebugView()
409{
410
411 if (m_debugView) {
412 findSceneView();
413 if (!m_sceneView) {
414 qWarning() << "ReflectionProbe: Can not create debug view. A root View3D could not be found.";
415 return;
416 }
417
418 if (!m_debugViewGeometry)
419 m_debugViewGeometry = new QQuick3DGeometry(this);
420
421 m_debugViewGeometry->clear();
422 m_debugViewGeometry->addAttribute(semantic: QQuick3DGeometry::Attribute::PositionSemantic, offset: 0,
423 componentType: QQuick3DGeometry::Attribute::ComponentType::F32Type);
424 m_debugViewGeometry->setStride(12);
425 m_debugViewGeometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Lines);
426
427 QVector<QVector3D> m_vertices;
428 //Lines
429 m_vertices.append(t: QVector3D(-0.5, -0.5, 0.5));
430 m_vertices.append(t: QVector3D(0.5, -0.5, 0.5));
431
432 m_vertices.append(t: QVector3D(0.5, -0.5, 0.5));
433 m_vertices.append(t: QVector3D(0.5, 0.5, 0.5));
434
435 m_vertices.append(t: QVector3D(0.5, 0.5, 0.5));
436 m_vertices.append(t: QVector3D(-0.5, 0.5, 0.5));
437
438 m_vertices.append(t: QVector3D(-0.5, 0.5, 0.5));
439 m_vertices.append(t: QVector3D(-0.5, -0.5, 0.5));
440
441 m_vertices.append(t: QVector3D(-0.5, -0.5, -0.5));
442 m_vertices.append(t: QVector3D(0.5, -0.5, -0.5));
443
444 m_vertices.append(t: QVector3D(0.5, -0.5, -0.5));
445 m_vertices.append(t: QVector3D(0.5, 0.5, -0.5));
446
447 m_vertices.append(t: QVector3D(0.5, 0.5, -0.5));
448 m_vertices.append(t: QVector3D(-0.5, 0.5, -0.5));
449
450 m_vertices.append(t: QVector3D(-0.5, 0.5, -0.5));
451 m_vertices.append(t: QVector3D(-0.5, -0.5, -0.5));
452
453 m_vertices.append(t: QVector3D(-0.5, 0.5, -0.5));
454 m_vertices.append(t: QVector3D(-0.5, 0.5, 0.5));
455
456 m_vertices.append(t: QVector3D(0.5, 0.5, -0.5));
457 m_vertices.append(t: QVector3D(0.5, 0.5, 0.5));
458
459 m_vertices.append(t: QVector3D(-0.5, -0.5, -0.5));
460 m_vertices.append(t: QVector3D(-0.5, -0.5, 0.5));
461
462 m_vertices.append(t: QVector3D(0.5, -0.5, -0.5));
463 m_vertices.append(t: QVector3D(0.5, -0.5, 0.5));
464
465 QByteArray vertexData;
466 vertexData.resize(size: m_vertices.size() * 3 * sizeof(float));
467 float *data = reinterpret_cast<float *>(vertexData.data());
468 for (int i = 0; i < m_vertices.size(); i++) {
469 data[0] = m_vertices[i].x();
470 data[1] = m_vertices[i].y();
471 data[2] = m_vertices[i].z();
472 data += 3;
473 }
474
475 m_debugViewGeometry->setVertexData(vertexData);
476 m_debugViewGeometry->update();
477
478 if (!m_debugViewModel) {
479 m_debugViewModel = new QQuick3DModel();
480 m_debugViewModel->setParentItem(m_sceneView->scene());
481 m_debugViewModel->setParent(this);
482 m_debugViewModel->setCastsShadows(false);
483 m_debugViewModel->setCastsReflections(false);
484 m_debugViewModel->setGeometry(m_debugViewGeometry);
485 }
486
487 if (!m_debugViewMaterial) {
488 m_debugViewMaterial = new QQuick3DDefaultMaterial();
489 m_debugViewMaterial->setParentItem(m_debugViewModel);
490 m_debugViewMaterial->setParent(m_debugViewModel);
491 m_debugViewMaterial->setDiffuseColor(QColor(3, 252, 219));
492 m_debugViewMaterial->setLighting(QQuick3DDefaultMaterial::NoLighting);
493 m_debugViewMaterial->setCullMode(QQuick3DMaterial::NoCulling);
494 QQmlListReference materialsRef(m_debugViewModel, "materials");
495 materialsRef.append(m_debugViewMaterial);
496 }
497 } else {
498 if (m_debugViewModel) {
499 delete m_debugViewModel;
500 m_debugViewModel = nullptr;
501 m_debugViewMaterial = nullptr;
502 }
503
504 if (m_debugViewGeometry) {
505 delete m_debugViewGeometry;
506 m_debugViewGeometry = nullptr;
507 }
508 }
509}
510
511void QQuick3DReflectionProbe::updateDebugView()
512{
513 if (m_debugViewModel) {
514 m_debugViewModel->setPosition(scenePosition() + m_boxOffset);
515 m_debugViewModel->setScale(m_boxSize);
516 }
517}
518
519quint32 QQuick3DReflectionProbe::mapToReflectionResolution(ReflectionQuality quality)
520{
521 switch (quality) {
522 case ReflectionQuality::Low:
523 return 8;
524 case ReflectionQuality::Medium:
525 return 9;
526 case ReflectionQuality::High:
527 return 10;
528 case ReflectionQuality::VeryHigh:
529 return 11;
530 default:
531 break;
532 }
533 return 7;
534}
535
536void QQuick3DReflectionProbe::updateSceneManager(QQuick3DSceneManager *sceneManager)
537{
538 // Check all the resource value's scene manager, and update as necessary.
539 if (sceneManager)
540 QQuick3DObjectPrivate::refSceneManager(obj: m_texture, mgr&: *sceneManager);
541 else
542 QQuick3DObjectPrivate::derefSceneManager(obj: m_texture);
543}
544
545
546QT_END_NAMESPACE
547

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