Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
proceduralskytexturedata.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5
6/*
7 Based on "sky.cpp" from the Godot engine v3
8 Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.
9 Copyright (c) 2014-2022 Godot Engine contributors.
10*/
11
13#include <QtQuick3DRuntimeRender/private/qssgrendertexturedata_p.h>
14#include <QtQuick3DRuntimeRender/private/qssgrenderimage_p.h>
15
16#include <QtGui/qquaternion.h>
17
19
20/*!
21 \qmltype ProceduralSkyTextureData
22 \inqmlmodule QtQuick3D.Helpers
23 \inherits TextureData
24 \brief Generates an HDR skybox cubemap.
25
26 This helper type provides an easy way to generate a lightprobe/skybox texture in HDR format. Note that
27 generating a lightprobe is an expensive process that can take significant time on embedded hardware.
28
29 The generated cubemap consists of three elements: the sky, the ground, and the sun. The sky and the
30 ground cover the top and bottom hemispheres. The position of the sun can be specified by setting
31 \l sunLatitude and \l sunLongitude.
32
33 \qml
34 View3D {
35 environment: SceneEnvironment {
36 backgroundMode: SceneEnvironment.SkyBox
37 lightProbe: Texture {
38 textureData: ProceduralSkyTextureData {
39 }
40 }
41 }
42 }
43 \endqml
44
45 \image sceneenvironment_lightprobe_proceduralsky.jpg {Architectural scene lit by procedural sky visible in background}
46
47 \l ProceduralSkyMaterial offers the same sky model rendered on the GPU
48 via \l SceneEnvironment::skyMaterial. It avoids the CPU texture baking
49 and updates in real time when its properties change, which makes it the
50 preferred choice when the sky parameters are animated or when startup
51 time matters.
52
53 \sa SceneEnvironment, ProceduralSkyMaterial
54*/
55
56/*! \qmlproperty color ProceduralSkyTextureData::skyTopColor
57 Specifies the sky color at the top of the skybox. The top half of the skybox has a gradient from \l skyHorizonColor to \c skyTopColor.
58 */
59
60/*! \qmlproperty color ProceduralSkyTextureData::skyHorizonColor
61 Specifies the sky color at the horizon. The top half of the skybox has a gradient from \c skyHorizonColor to \l skyTopColor.
62 */
63
64/*! \qmlproperty real ProceduralSkyTextureData::skyCurve
65 Modifies the curve of the sky gradient.
66 */
67
68/*! \qmlproperty real ProceduralSkyTextureData::skyEnergy
69 Specifies the intensity of the top half of the skybox. The sky gradient is multiplied with this factor.
70*/
71
72/*! \qmlproperty color ProceduralSkyTextureData::groundBottomColor
73 Specifies the ground color at the bottom of the skybox. The bottom half of the skybox has
74 a gradient from \l groundHorizonColor to \c groundBottomColor.
75*/
76
77/*! \qmlproperty color ProceduralSkyTextureData::groundHorizonColor
78 Specifies the ground color at the horizon. The bottom half of the skybox has
79 a gradient from \c groundHorizonColor to \l groundBottomColor.
80*/
81
82/*! \qmlproperty real ProceduralSkyTextureData::groundCurve
83 Modifies the curve of the ground gradient.
84*/
85
86/*! \qmlproperty real ProceduralSkyTextureData::groundEnergy
87 Specifies the intensity of the bottom half of the skybox. The ground gradient is multiplied with this factor.
88*/
89
90/*! \qmlproperty color ProceduralSkyTextureData::sunColor
91 Specifies the color of the sun.
92*/
93
94/*! \qmlproperty real ProceduralSkyTextureData::sunLatitude
95 Specifies the angle between the horizon and the sun position.
96 */
97
98/*! \qmlproperty real ProceduralSkyTextureData::sunLongitude
99 Specifies the angle between the forward direction and the sun position.
100*/
101
102/*! \qmlproperty real ProceduralSkyTextureData::sunAngleMin
103 Specifies the angle from the center of the sun to where it starts to fade.
104*/
105
106/*! \qmlproperty real ProceduralSkyTextureData::sunAngleMax
107 Specifies the angle from the center of the sun to where it fades out completely.
108*/
109
110/*! \qmlproperty real ProceduralSkyTextureData::sunCurve
111 Modifies the curve of the sun gradient.
112*/
113
114/*! \qmlproperty real ProceduralSkyTextureData::sunEnergy
115 Specifies the intensity of the sun.
116*/
117
118/*! \qmlproperty SkyTextureQuality ProceduralSkyTextureData::textureQuality
119 This property sets the quality of the sky texture. Supported values are:
120
121 \value ProceduralSkyTextureData.SkyTextureQualityLow Generate a 512x512 texture
122 \value ProceduralSkyTextureData.SkyTextureQualityMedium Generate a 1024x1024 texture
123 \value ProceduralSkyTextureData.SkyTextureQualityHigh Generate a 2048x2048 texture
124 \value ProceduralSkyTextureData.SkyTextureQualityVeryHigh Generate a 4096x4096 texture
125*/
126
127ProceduralSkyTextureData::ProceduralSkyTextureData()
128{
129 scheduleTextureUpdate();
130}
131
132ProceduralSkyTextureData::~ProceduralSkyTextureData()
133{
134}
135
136QColor ProceduralSkyTextureData::skyTopColor() const
137{
138 return m_skyTopColor;
139}
140
141QColor ProceduralSkyTextureData::skyHorizonColor() const
142{
143 return m_skyHorizonColor;
144}
145
146float ProceduralSkyTextureData::skyCurve() const
147{
148 return m_skyCurve;
149}
150
151float ProceduralSkyTextureData::skyEnergy() const
152{
153 return m_skyEnergy;
154}
155
156QColor ProceduralSkyTextureData::groundBottomColor() const
157{
158 return m_groundBottomColor;
159}
160
161QColor ProceduralSkyTextureData::groundHorizonColor() const
162{
163 return m_groundHorizonColor;
164}
165
166float ProceduralSkyTextureData::groundCurve() const
167{
168 return m_groundCurve;
169}
170
171float ProceduralSkyTextureData::groundEnergy() const
172{
173 return m_groundEnergy;
174}
175
176QColor ProceduralSkyTextureData::sunColor() const
177{
178 return m_sunColor;
179}
180
181float ProceduralSkyTextureData::sunLatitude() const
182{
183 return m_sunLatitude;
184}
185
186float ProceduralSkyTextureData::sunLongitude() const
187{
188 return m_sunLongitude;
189}
190
191float ProceduralSkyTextureData::sunAngleMin() const
192{
193 return m_sunAngleMin;
194}
195
196float ProceduralSkyTextureData::sunAngleMax() const
197{
198 return m_sunAngleMax;
199}
200
201float ProceduralSkyTextureData::sunCurve() const
202{
203 return m_sunCurve;
204}
205
206float ProceduralSkyTextureData::sunEnergy() const
207{
208 return m_sunEnergy;
209}
210
211ProceduralSkyTextureData::SkyTextureQuality ProceduralSkyTextureData::textureQuality() const
212{
213 return m_textureQuality;
214}
215
216void ProceduralSkyTextureData::setSkyTopColor(QColor skyTopColor)
217{
218 if (m_skyTopColor == skyTopColor)
219 return;
220
221 m_skyTopColor = skyTopColor;
222 emit skyTopColorChanged(m_skyTopColor);
223 scheduleTextureUpdate();
224}
225
226void ProceduralSkyTextureData::setSkyHorizonColor(QColor skyHorizonColor)
227{
228 if (m_skyHorizonColor == skyHorizonColor)
229 return;
230
231 m_skyHorizonColor = skyHorizonColor;
232 emit skyHorizonColorChanged(m_skyHorizonColor);
233 scheduleTextureUpdate();
234}
235
236void ProceduralSkyTextureData::setSkyCurve(float skyCurve)
237{
238 if (qFuzzyCompare(m_skyCurve, skyCurve))
239 return;
240
241 m_skyCurve = skyCurve;
242 emit skyCurveChanged(m_skyCurve);
243 scheduleTextureUpdate();
244}
245
246void ProceduralSkyTextureData::setSkyEnergy(float skyEnergy)
247{
248 if (qFuzzyCompare(m_skyEnergy, skyEnergy))
249 return;
250
251 m_skyEnergy = skyEnergy;
252 emit skyEnergyChanged(m_skyEnergy);
253 scheduleTextureUpdate();
254}
255
256void ProceduralSkyTextureData::setGroundBottomColor(QColor groundBottomColor)
257{
258 if (m_groundBottomColor == groundBottomColor)
259 return;
260
261 m_groundBottomColor = groundBottomColor;
262 emit groundBottomColorChanged(m_groundBottomColor);
263 scheduleTextureUpdate();
264}
265
266void ProceduralSkyTextureData::setGroundHorizonColor(QColor groundHorizonColor)
267{
268 if (m_groundHorizonColor == groundHorizonColor)
269 return;
270
271 m_groundHorizonColor = groundHorizonColor;
272 emit groundHorizonColorChanged(m_groundHorizonColor);
273 scheduleTextureUpdate();
274}
275
276void ProceduralSkyTextureData::setGroundCurve(float groundCurve)
277{
278 if (qFuzzyCompare(m_groundCurve, groundCurve))
279 return;
280
281 m_groundCurve = groundCurve;
282 emit groundCurveChanged(m_groundCurve);
283 scheduleTextureUpdate();
284}
285
286void ProceduralSkyTextureData::setGroundEnergy(float groundEnergy)
287{
288 if (qFuzzyCompare(m_groundEnergy, groundEnergy))
289 return;
290
291 m_groundEnergy = groundEnergy;
292 emit groundEnergyChanged(m_groundEnergy);
293 scheduleTextureUpdate();
294}
295
296void ProceduralSkyTextureData::setSunColor(QColor sunColor)
297{
298 if (m_sunColor == sunColor)
299 return;
300
301 m_sunColor = sunColor;
302 emit sunColorChanged(m_sunColor);
303 scheduleTextureUpdate();
304}
305
306void ProceduralSkyTextureData::setSunLatitude(float sunLatitude)
307{
308 if (qFuzzyCompare(m_sunLatitude, sunLatitude))
309 return;
310
311 m_sunLatitude = sunLatitude;
312 emit sunLatitudeChanged(m_sunLatitude);
313 scheduleTextureUpdate();
314}
315
316void ProceduralSkyTextureData::setSunLongitude(float sunLongitude)
317{
318 if (qFuzzyCompare(m_sunLongitude, sunLongitude))
319 return;
320
321 m_sunLongitude = sunLongitude;
322 emit sunLongitudeChanged(m_sunLongitude);
323 scheduleTextureUpdate();
324}
325
326void ProceduralSkyTextureData::setSunAngleMin(float sunAngleMin)
327{
328 if (qFuzzyCompare(m_sunAngleMin, sunAngleMin))
329 return;
330
331 m_sunAngleMin = sunAngleMin;
332 emit sunAngleMinChanged(m_sunAngleMin);
333 scheduleTextureUpdate();
334}
335
336void ProceduralSkyTextureData::setSunAngleMax(float sunAngleMax)
337{
338 if (qFuzzyCompare(m_sunAngleMax, sunAngleMax))
339 return;
340
341 m_sunAngleMax = sunAngleMax;
342 emit sunAngleMaxChanged(m_sunAngleMax);
343 scheduleTextureUpdate();
344}
345
346void ProceduralSkyTextureData::setSunCurve(float sunCurve)
347{
348 if (qFuzzyCompare(m_sunCurve, sunCurve))
349 return;
350
351 m_sunCurve = sunCurve;
352 emit sunCurveChanged(m_sunCurve);
353 scheduleTextureUpdate();
354}
355
356void ProceduralSkyTextureData::setSunEnergy(float sunEnergy)
357{
358 if (qFuzzyCompare(m_sunEnergy, sunEnergy))
359 return;
360
361 m_sunEnergy = sunEnergy;
362 emit sunEnergyChanged(m_sunEnergy);
363 scheduleTextureUpdate();
364}
365
366void ProceduralSkyTextureData::setTextureQuality(ProceduralSkyTextureData::SkyTextureQuality textureQuality)
367{
368 if (m_textureQuality == textureQuality)
369 return;
370
371 m_textureQuality = textureQuality;
372 emit textureQualityChanged(m_textureQuality);
373 scheduleTextureUpdate();
374}
375
376void ProceduralSkyTextureData::generateRGBA16FTexture()
377{
378 int size = 0;
379 switch (m_textureQuality) {
380 case SkyTextureQuality::SkyTextureQualityLow:
381 size = 512;
382 break;
383 case SkyTextureQuality::SkyTextureQualityMedium:
384 size = 1024;
385 break;
386 case SkyTextureQuality::SkyTextureQualityHigh:
387 size = 2048;
388 break;
389 case SkyTextureQuality::SkyTextureQualityVeryHigh:
390 size = 4096;
391 break;
392 }
393
394 const int width = size;
395 const int height = width / 2;
396 setSize(QSize(width, height));
397 setFormat(Format::RGBA16F);
398 setHasTransparency(false);
399 const int dataSize = width * height * 4 * 2; // 2 bytes per channel
400 QByteArray imageData;
401 imageData.resize(dataSize);
402 generateSkyTexture(width, height, imageData, false);
403 setTextureData(imageData);
404}
405
406QByteArray ProceduralSkyTextureData::generateSkyTexture(int width, int height, QByteArray &imageData, bool isRGBE) const
407{
408 quint32 *data = reinterpret_cast<quint32 *>(imageData.data());
409
410 LinearColor skyTopLinear(m_skyTopColor);
411 LinearColor skyHorizonLinear(m_skyHorizonColor);
412 LinearColor groundBottomLinear(m_groundBottomColor);
413 LinearColor groundHorizonLinear(m_groundHorizonColor);
414 LinearColor sunLinear(m_sunColor);
415 sunLinear.r *= m_sunEnergy;
416 sunLinear.g *= m_sunEnergy;
417 sunLinear.b *= m_sunEnergy;
418
419 QVector3D sun(0, 0, -1);
420
421 sun = QQuaternion::fromAxisAndAngle(QVector3D(1, 0, 0), m_sunLatitude) * sun;
422 sun = QQuaternion::fromAxisAndAngle(QVector3D(0, 1, 0), m_sunLongitude) * sun;
423 sun.normalize();
424
425 auto clamp = [](float value, float min, float max) {
426 if (value < min)
427 return min;
428 else if (value > max)
429 return max;
430 return value;
431 };
432
433 auto ease = [](float x, float c) {
434 if (x < 0.0f)
435 x = 0.0f;
436 else if (x > 1.0f)
437 x = 1.0f;
438 if (c > 0.0f) {
439 if (c < 1.0f) {
440 return 1.0f - qPow(1.0f - x, 1.0f / c);
441 } else {
442 return qPow(x, c);
443 }
444 } else if (c < 0.0f) {
445 if (x < 0.5f) {
446 return qPow(x * 2.0f, -c) * 0.5f;
447 } else {
448 return (1.0f - qPow(1.0f - (x - 0.5f) * 2.0f, -c)) * 0.5f + 0.5f;
449 }
450 } else
451 return 0.0f;
452 };
453
454 for (int i = 0; i < width; i++) {
455
456 float u = float(i) / (width - 1);
457 float phi = u * 2.0 * M_PI;
458
459 for (int j = 0; j < height; j++) {
460 float v = float(j) / (height - 1);
461 float theta = v * M_PI;
462
463 QVector3D normal(qSin(phi) * qSin(theta) * -1.0,
464 qCos(theta),
465 qCos(phi) * qSin(theta) * -1.0);
466 normal.normalize();
467 float vAngle = qAcos(clamp(normal.y(), -1.0, 1.0));
468 LinearColor color;
469
470 if (normal.y() < 0) {
471 // Ground color
472 float c = (vAngle - (M_PI * 0.5f)) / (M_PI * 0.5f);
473 color = groundHorizonLinear.interpolate(groundBottomLinear, ease(c, m_groundCurve));
474 color.r *= m_groundEnergy;
475 color.g *= m_groundEnergy;
476 color.b *= m_groundEnergy;
477 } else {
478 // Sky color
479 float c = vAngle / (M_PI * 0.5f);
480 color = skyHorizonLinear.interpolate(skyTopLinear, ease(1.0 - c, m_skyCurve));
481 color.r *= m_skyEnergy;
482 color.g *= m_skyEnergy;
483 color.b *= m_skyEnergy;
484
485 float sunAngle = qRadiansToDegrees(qAcos(clamp(QVector3D::dotProduct(sun, normal), -1.0f, 1.0f)));
486 if (sunAngle < m_sunAngleMin) {
487 color = color.blend(sunLinear);
488 } else if (sunAngle < m_sunAngleMax) {
489 float c2 = (sunAngle - m_sunAngleMin) / (m_sunAngleMax - m_sunAngleMin);
490 c2 = ease(c2, m_sunCurve);
491 color = color.blend(sunLinear).interpolate(color, c2);
492 }
493 }
494
495 // Write from bottom to top
496 if (isRGBE) {
497 data[(height - j - 1) * width + i] = color.toRGBE8();
498 } else {
499 // RGBA16F
500 const int offset = ((height - j - 1) * width + i) * 2;
501 qfloat16 *fData = reinterpret_cast<qfloat16 *>(data + offset);
502 float pixel[4] = {color.r, color.g, color.b, color.a };
503 qFloatToFloat16(fData, pixel, 4);
504 }
505 }
506 }
507
508 return imageData;
509}
510
511void ProceduralSkyTextureData::scheduleTextureUpdate()
512{
513 generateRGBA16FTexture();
514}
515
516ProceduralSkyTextureData::LinearColor::LinearColor(const QColor &color)
517{
518 const float red = color.redF();
519 const float green = color.greenF();
520 const float blue = color.blueF();
521 const float alpha = color.alphaF();
522
523 r = red < 0.04045 ? red * (1.0 / 12.92) : qPow((red + 0.055) * (1.0 / (1 + 0.055)), 2.4),
524 g = green < 0.04045 ? green * (1.0 / 12.92) : qPow((green + 0.055) * (1.0 / (1 + 0.055)), 2.4),
525 b = blue < 0.04045 ? blue * (1.0 / 12.92) : qPow((blue + 0.055) * (1.0 / (1 + 0.055)), 2.4),
526 a = alpha;
527}
528
529ProceduralSkyTextureData::LinearColor ProceduralSkyTextureData::LinearColor::interpolate(const ProceduralSkyTextureData::LinearColor &color, float value) const
530{
531 LinearColor copy = *this;
532
533 copy.r += (value * (color.r - r));
534 copy.g += (value * (color.g - g));
535 copy.b += (value * (color.b - b));
536 copy.a += (value * (color.a - a));
537
538 return copy;
539}
540
541ProceduralSkyTextureData::LinearColor ProceduralSkyTextureData::LinearColor::blend(const ProceduralSkyTextureData::LinearColor &color) const
542{
543 LinearColor copy;
544 float sa = 1.0 - color.a;
545 copy.a = a * sa + color.a;
546 if (copy.a == 0) {
547 return LinearColor();
548 } else {
549 copy.r = (r * a * sa + color.r * color.a) / copy.a;
550 copy.g = (g * a * sa + color.g * color.a) / copy.a;
551 copy.b = (b * a * sa + color.b * color.a) / copy.a;
552 }
553 return copy;
554}
555
556quint32 ProceduralSkyTextureData::LinearColor::toRGBA8() const
557{
558 return (quint32(lrintf(r)) & 0xFF) |
559 ((quint32(lrintf(g)) & 0xFF) << 8) |
560 ((quint32(lrintf(b)) & 0xFF) << 16) |
561 ((quint32(lrintf(a)) & 0xFF) << 24);
562}
563
564quint32 ProceduralSkyTextureData::LinearColor::toRGBE8() const
565{
566 float v = 0.0f;
567 int exp = 0;
568
569 v = r;
570 if (g > v)
571 v = g;
572 if (b > v)
573 v = b;
574
575 v = frexp(v, &exp) * 256.0f / v;
576 quint32 result = 0;
577 quint8 *components = reinterpret_cast<quint8*>(&result);
578 components[0] = quint8(r * v);
579 components[1] = quint8(g * v);
580 components[2] = quint8(b * v);
581 components[3] = quint8(exp + 128);
582 return result;
583}
584
585quint32 ProceduralSkyTextureData::LinearColor::toRGBE9995() const
586{
587 const float pow2to9 = 512.0f;
588 const float B = 15.0f;
589 const float N = 9.0f;
590
591 float sharedExp = 65408.000f;
592
593 float cRed = qMax(0.0f, qMin(sharedExp, r));
594 float cGreen = qMax(0.0f, qMin(sharedExp, g));
595 float cBlue = qMax(0.0f, qMin(sharedExp, b));
596
597 float cMax = qMax(cRed, qMax(cGreen, cBlue));
598
599 float expp = qMax(-B - 1.0f, floor(std::log(cMax) / M_LN2)) + 1.0f + B;
600
601 float sMax = (float)floor((cMax / qPow(2.0f, expp - B - N)) + 0.5f);
602
603 float exps = expp + 1.0f;
604
605 if (0.0 <= sMax && sMax < pow2to9) {
606 exps = expp;
607 }
608
609 float sRed = qFloor((cRed / pow(2.0f, exps - B - N)) + 0.5f);
610 float sGreen = qFloor((cGreen / pow(2.0f, exps - B - N)) + 0.5f);
611 float sBlue = qFloor((cBlue / pow(2.0f, exps - B - N)) + 0.5f);
612
613 return (quint32(lrintf(sRed)) & 0x1FF) |
614 ((quint32(lrintf(sGreen)) & 0x1FF) << 9) |
615 ((quint32(lrintf(sBlue)) & 0x1FF) << 18) |
616 ((quint32(lrintf(exps)) & 0x1F) << 27);
617}
618
619QT_END_NAMESPACE
Combined button and popup list for selecting options.