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 | |
10 | QT_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 | |
25 | QQuick3DReflectionProbe::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 | |
31 | void 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 | */ |
57 | QQuick3DReflectionProbe::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 | */ |
67 | QColor 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 | */ |
86 | QQuick3DReflectionProbe::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 | */ |
115 | QQuick3DReflectionProbe::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 | */ |
130 | bool 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 | */ |
146 | QVector3D 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 | */ |
157 | bool 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 | */ |
175 | QVector3D 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 | */ |
189 | QQuick3DCubeMapTexture *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 | */ |
200 | void QQuick3DReflectionProbe::scheduleUpdate() |
201 | { |
202 | m_dirtyFlags.setFlag(flag: DirtyFlag::RefreshModeDirty); |
203 | update(); |
204 | } |
205 | |
206 | void 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 | |
217 | void 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 | |
227 | void 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 | |
237 | void 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 | |
247 | void 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 | |
257 | void 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 | |
269 | void 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 | |
279 | void 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 | |
291 | void 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 | |
302 | QSSGRenderGraphObject *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 | |
373 | void 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 | |
384 | void 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 | |
408 | void 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 | |
511 | void QQuick3DReflectionProbe::updateDebugView() |
512 | { |
513 | if (m_debugViewModel) { |
514 | m_debugViewModel->setPosition(scenePosition() + m_boxOffset); |
515 | m_debugViewModel->setScale(m_boxSize); |
516 | } |
517 | } |
518 | |
519 | quint32 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 | |
536 | void 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 | |
546 | QT_END_NAMESPACE |
547 | |