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