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