Revision control
++ // Copyright (C) 2022 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++
++ /*
++ Based on "sky.cpp" from the Godot engine v3
++ Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.
++ Copyright (c) 2014-2022 Godot Engine contributors.
++ */
++
++ #include "proceduralskytexturedata_p.h"
++ #include <QtQuick3DRuntimeRender/private/qssgrendertexturedata_p.h>
++ #include <QtQuick3DRuntimeRender/private/qssgrenderimage_p.h>
++
++ #include <QtGui/qquaternion.h>
++
++ QT_BEGIN_NAMESPACE
++
++ /*!
++ \qmltype ProceduralSkyTextureData
++ \inqmlmodule QtQuick3D.Helpers
++ \inherits TextureData
++ \brief Generates an HDR skybox cubemap.
++
++ This helper type provides an easy way to generate a lightprobe/skybox texture in HDR format. Note that
++ generating a lightprobe is an expensive process that can take significant time on embedded hardware.
++
++ The generated cubemap consists of three elements: the sky, the ground, and the sun. The sky and the
++ ground cover the top and bottom hemispheres. The position of the sun can be specified by setting
++ \l sunLatitude and \l sunLongitude.
++
++ \qml
++ View3D {
++ environment: SceneEnvironment {
++ backgroundMode: SceneEnvironment.SkyBox
++ lightProbe: Texture {
++ textureData: ProceduralSkyTextureData {
++ }
++ }
++ }
++ }
++ \endqml
++
++ \image sceneenvironment_lightprobe_proceduralsky.jpg {Architectural scene lit by procedural sky visible in background}
++
++ \l ProceduralSkyMaterial offers the same sky model rendered on the GPU
++ via \l SceneEnvironment::skyMaterial. It avoids the CPU texture baking
++ and updates in real time when its properties change, which makes it the
++ preferred choice when the sky parameters are animated or when startup
++ time matters.
++
++ \sa SceneEnvironment, ProceduralSkyMaterial
++ */
++
++ /*! \qmlproperty color ProceduralSkyTextureData::skyTopColor
++ 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.
++ */
++
++ /*! \qmlproperty color ProceduralSkyTextureData::skyHorizonColor
++ Specifies the sky color at the horizon. The top half of the skybox has a gradient from \c skyHorizonColor to \l skyTopColor.
++ */
++
++ /*! \qmlproperty real ProceduralSkyTextureData::skyCurve
++ Modifies the curve of the sky gradient.
++ */
++
++ /*! \qmlproperty real ProceduralSkyTextureData::skyEnergy
++ Specifies the intensity of the top half of the skybox. The sky gradient is multiplied with this factor.
++ */
++
++ /*! \qmlproperty color ProceduralSkyTextureData::groundBottomColor
++ Specifies the ground color at the bottom of the skybox. The bottom half of the skybox has
++ a gradient from \l groundHorizonColor to \c groundBottomColor.
++ */
++
++ /*! \qmlproperty color ProceduralSkyTextureData::groundHorizonColor
++ Specifies the ground color at the horizon. The bottom half of the skybox has
++ a gradient from \c groundHorizonColor to \l groundBottomColor.
++ */
++
++ /*! \qmlproperty real ProceduralSkyTextureData::groundCurve
++ Modifies the curve of the ground gradient.
++ */
++
++ /*! \qmlproperty real ProceduralSkyTextureData::groundEnergy
++ Specifies the intensity of the bottom half of the skybox. The ground gradient is multiplied with this factor.
++ */
++
++ /*! \qmlproperty color ProceduralSkyTextureData::sunColor
++ Specifies the color of the sun.
++ */
++
++ /*! \qmlproperty real ProceduralSkyTextureData::sunLatitude
++ Specifies the angle between the horizon and the sun position.
++ */
++
++ /*! \qmlproperty real ProceduralSkyTextureData::sunLongitude
++ Specifies the angle between the forward direction and the sun position.
++ */
++
++ /*! \qmlproperty real ProceduralSkyTextureData::sunAngleMin
++ Specifies the angle from the center of the sun to where it starts to fade.
++ */
++
++ /*! \qmlproperty real ProceduralSkyTextureData::sunAngleMax
++ Specifies the angle from the center of the sun to where it fades out completely.
++ */
++
++ /*! \qmlproperty real ProceduralSkyTextureData::sunCurve
++ Modifies the curve of the sun gradient.
++ */
++
++ /*! \qmlproperty real ProceduralSkyTextureData::sunEnergy
++ Specifies the intensity of the sun.
++ */
++
++ /*! \qmlproperty SkyTextureQuality ProceduralSkyTextureData::textureQuality
++ This property sets the quality of the sky texture. Supported values are:
++
++ \value ProceduralSkyTextureData.SkyTextureQualityLow Generate a 512x512 texture
++ \value ProceduralSkyTextureData.SkyTextureQualityMedium Generate a 1024x1024 texture
++ \value ProceduralSkyTextureData.SkyTextureQualityHigh Generate a 2048x2048 texture
++ \value ProceduralSkyTextureData.SkyTextureQualityVeryHigh Generate a 4096x4096 texture
++ */
++
++ ProceduralSkyTextureData::ProceduralSkyTextureData()
++ {
++ scheduleTextureUpdate();
++ }
++
++ ProceduralSkyTextureData::~ProceduralSkyTextureData()
++ {
++ }
++
++ QColor ProceduralSkyTextureData::skyTopColor() const
++ {
++ return m_skyTopColor;
++ }
++
++ QColor ProceduralSkyTextureData::skyHorizonColor() const
++ {
++ return m_skyHorizonColor;
++ }
++
++ float ProceduralSkyTextureData::skyCurve() const
++ {
++ return m_skyCurve;
++ }
++
++ float ProceduralSkyTextureData::skyEnergy() const
++ {
++ return m_skyEnergy;
++ }
++
++ QColor ProceduralSkyTextureData::groundBottomColor() const
++ {
++ return m_groundBottomColor;
++ }
++
++ QColor ProceduralSkyTextureData::groundHorizonColor() const
++ {
++ return m_groundHorizonColor;
++ }
++
++ float ProceduralSkyTextureData::groundCurve() const
++ {
++ return m_groundCurve;
++ }
++
++ float ProceduralSkyTextureData::groundEnergy() const
++ {
++ return m_groundEnergy;
++ }
++
++ QColor ProceduralSkyTextureData::sunColor() const
++ {
++ return m_sunColor;
++ }
++
++ float ProceduralSkyTextureData::sunLatitude() const
++ {
++ return m_sunLatitude;
++ }
++
++ float ProceduralSkyTextureData::sunLongitude() const
++ {
++ return m_sunLongitude;
++ }
++
++ float ProceduralSkyTextureData::sunAngleMin() const
++ {
++ return m_sunAngleMin;
++ }
++
++ float ProceduralSkyTextureData::sunAngleMax() const
++ {
++ return m_sunAngleMax;
++ }
++
++ float ProceduralSkyTextureData::sunCurve() const
++ {
++ return m_sunCurve;
++ }
++
++ float ProceduralSkyTextureData::sunEnergy() const
++ {
++ return m_sunEnergy;
++ }
++
++ ProceduralSkyTextureData::SkyTextureQuality ProceduralSkyTextureData::textureQuality() const
++ {
++ return m_textureQuality;
++ }
++
++ void ProceduralSkyTextureData::setSkyTopColor(QColor skyTopColor)
++ {
++ if (m_skyTopColor == skyTopColor)
++ return;
++
++ m_skyTopColor = skyTopColor;
++ emit skyTopColorChanged(m_skyTopColor);
++ scheduleTextureUpdate();
++ }
++
++ void ProceduralSkyTextureData::setSkyHorizonColor(QColor skyHorizonColor)
++ {
++ if (m_skyHorizonColor == skyHorizonColor)
++ return;
++
++ m_skyHorizonColor = skyHorizonColor;
++ emit skyHorizonColorChanged(m_skyHorizonColor);
++ scheduleTextureUpdate();
++ }
++
++ void ProceduralSkyTextureData::setSkyCurve(float skyCurve)
++ {
++ if (qFuzzyCompare(m_skyCurve, skyCurve))
++ return;
++
++ m_skyCurve = skyCurve;
++ emit skyCurveChanged(m_skyCurve);
++ scheduleTextureUpdate();
++ }
++
++ void ProceduralSkyTextureData::setSkyEnergy(float skyEnergy)
++ {
++ if (qFuzzyCompare(m_skyEnergy, skyEnergy))
++ return;
++
++ m_skyEnergy = skyEnergy;
++ emit skyEnergyChanged(m_skyEnergy);
++ scheduleTextureUpdate();
++ }
++
++ void ProceduralSkyTextureData::setGroundBottomColor(QColor groundBottomColor)
++ {
++ if (m_groundBottomColor == groundBottomColor)
++ return;
++
++ m_groundBottomColor = groundBottomColor;
++ emit groundBottomColorChanged(m_groundBottomColor);
++ scheduleTextureUpdate();
++ }
++
++ void ProceduralSkyTextureData::setGroundHorizonColor(QColor groundHorizonColor)
++ {
++ if (m_groundHorizonColor == groundHorizonColor)
++ return;
++
++ m_groundHorizonColor = groundHorizonColor;
++ emit groundHorizonColorChanged(m_groundHorizonColor);
++ scheduleTextureUpdate();
++ }
++
++ void ProceduralSkyTextureData::setGroundCurve(float groundCurve)
++ {
++ if (qFuzzyCompare(m_groundCurve, groundCurve))
++ return;
++
++ m_groundCurve = groundCurve;
++ emit groundCurveChanged(m_groundCurve);
++ scheduleTextureUpdate();
++ }
++
++ void ProceduralSkyTextureData::setGroundEnergy(float groundEnergy)
++ {
++ if (qFuzzyCompare(m_groundEnergy, groundEnergy))
++ return;
++
++ m_groundEnergy = groundEnergy;
++ emit groundEnergyChanged(m_groundEnergy);
++ scheduleTextureUpdate();
++ }
++
++ void ProceduralSkyTextureData::setSunColor(QColor sunColor)
++ {
++ if (m_sunColor == sunColor)
++ return;
++
++ m_sunColor = sunColor;
++ emit sunColorChanged(m_sunColor);
++ scheduleTextureUpdate();
++ }
++
++ void ProceduralSkyTextureData::setSunLatitude(float sunLatitude)
++ {
++ if (qFuzzyCompare(m_sunLatitude, sunLatitude))
++ return;
++
++ m_sunLatitude = sunLatitude;
++ emit sunLatitudeChanged(m_sunLatitude);
++ scheduleTextureUpdate();
++ }
++
++ void ProceduralSkyTextureData::setSunLongitude(float sunLongitude)
++ {
++ if (qFuzzyCompare(m_sunLongitude, sunLongitude))
++ return;
++
++ m_sunLongitude = sunLongitude;
++ emit sunLongitudeChanged(m_sunLongitude);
++ scheduleTextureUpdate();
++ }
++
++ void ProceduralSkyTextureData::setSunAngleMin(float sunAngleMin)
++ {
++ if (qFuzzyCompare(m_sunAngleMin, sunAngleMin))
++ return;
++
++ m_sunAngleMin = sunAngleMin;
++ emit sunAngleMinChanged(m_sunAngleMin);
++ scheduleTextureUpdate();
++ }
++
++ void ProceduralSkyTextureData::setSunAngleMax(float sunAngleMax)
++ {
++ if (qFuzzyCompare(m_sunAngleMax, sunAngleMax))
++ return;
++
++ m_sunAngleMax = sunAngleMax;
++ emit sunAngleMaxChanged(m_sunAngleMax);
++ scheduleTextureUpdate();
++ }
++
++ void ProceduralSkyTextureData::setSunCurve(float sunCurve)
++ {
++ if (qFuzzyCompare(m_sunCurve, sunCurve))
++ return;
++
++ m_sunCurve = sunCurve;
++ emit sunCurveChanged(m_sunCurve);
++ scheduleTextureUpdate();
++ }
++
++ void ProceduralSkyTextureData::setSunEnergy(float sunEnergy)
++ {
++ if (qFuzzyCompare(m_sunEnergy, sunEnergy))
++ return;
++
++ m_sunEnergy = sunEnergy;
++ emit sunEnergyChanged(m_sunEnergy);
++ scheduleTextureUpdate();
++ }
++
++ void ProceduralSkyTextureData::setTextureQuality(ProceduralSkyTextureData::SkyTextureQuality textureQuality)
++ {
++ if (m_textureQuality == textureQuality)
++ return;
++
++ m_textureQuality = textureQuality;
++ emit textureQualityChanged(m_textureQuality);
++ scheduleTextureUpdate();
++ }
++
++ void ProceduralSkyTextureData::generateRGBA16FTexture()
++ {
++ int size = 0;
++ switch (m_textureQuality) {
++ case SkyTextureQuality::SkyTextureQualityLow:
++ size = 512;
++ break;
++ case SkyTextureQuality::SkyTextureQualityMedium:
++ size = 1024;
++ break;
++ case SkyTextureQuality::SkyTextureQualityHigh:
++ size = 2048;
++ break;
++ case SkyTextureQuality::SkyTextureQualityVeryHigh:
++ size = 4096;
++ break;
++ }
++
++ const int width = size;
++ const int height = width / 2;
++ setSize(QSize(width, height));
++ setFormat(Format::RGBA16F);
++ setHasTransparency(false);
++ const int dataSize = width * height * 4 * 2; // 2 bytes per channel
++ QByteArray imageData;
++ imageData.resize(dataSize);
++ generateSkyTexture(width, height, imageData, false);
++ setTextureData(imageData);
++ }
++
++ QByteArray ProceduralSkyTextureData::generateSkyTexture(int width, int height, QByteArray &imageData, bool isRGBE) const
++ {
++ quint32 *data = reinterpret_cast<quint32 *>(imageData.data());
++
++ LinearColor skyTopLinear(m_skyTopColor);
++ LinearColor skyHorizonLinear(m_skyHorizonColor);
++ LinearColor groundBottomLinear(m_groundBottomColor);
++ LinearColor groundHorizonLinear(m_groundHorizonColor);
++ LinearColor sunLinear(m_sunColor);
++ sunLinear.r *= m_sunEnergy;
++ sunLinear.g *= m_sunEnergy;
++ sunLinear.b *= m_sunEnergy;
++
++ QVector3D sun(0, 0, -1);
++
++ sun = QQuaternion::fromAxisAndAngle(QVector3D(1, 0, 0), m_sunLatitude) * sun;
++ sun = QQuaternion::fromAxisAndAngle(QVector3D(0, 1, 0), m_sunLongitude) * sun;
++ sun.normalize();
++
++ auto clamp = [](float value, float min, float max) {
++ if (value < min)
++ return min;
++ else if (value > max)
++ return max;
++ return value;
++ };
++
++ auto ease = [](float x, float c) {
++ if (x < 0.0f)
++ x = 0.0f;
++ else if (x > 1.0f)
++ x = 1.0f;
++ if (c > 0.0f) {
++ if (c < 1.0f) {
++ return 1.0f - qPow(1.0f - x, 1.0f / c);
++ } else {
++ return qPow(x, c);
++ }
++ } else if (c < 0.0f) {
++ if (x < 0.5f) {
++ return qPow(x * 2.0f, -c) * 0.5f;
++ } else {
++ return (1.0f - qPow(1.0f - (x - 0.5f) * 2.0f, -c)) * 0.5f + 0.5f;
++ }
++ } else
++ return 0.0f;
++ };
++
++ for (int i = 0; i < width; i++) {
++
++ float u = float(i) / (width - 1);
++ float phi = u * 2.0 * M_PI;
++
++ for (int j = 0; j < height; j++) {
++ float v = float(j) / (height - 1);
++ float theta = v * M_PI;
++
++ QVector3D normal(qSin(phi) * qSin(theta) * -1.0,
++ qCos(theta),
++ qCos(phi) * qSin(theta) * -1.0);
++ normal.normalize();
++ float vAngle = qAcos(clamp(normal.y(), -1.0, 1.0));
++ LinearColor color;
++
++ if (normal.y() < 0) {
++ // Ground color
++ float c = (vAngle - (M_PI * 0.5f)) / (M_PI * 0.5f);
++ color = groundHorizonLinear.interpolate(groundBottomLinear, ease(c, m_groundCurve));
++ color.r *= m_groundEnergy;
++ color.g *= m_groundEnergy;
++ color.b *= m_groundEnergy;
++ } else {
++ // Sky color
++ float c = vAngle / (M_PI * 0.5f);
++ color = skyHorizonLinear.interpolate(skyTopLinear, ease(1.0 - c, m_skyCurve));
++ color.r *= m_skyEnergy;
++ color.g *= m_skyEnergy;
++ color.b *= m_skyEnergy;
++
++ float sunAngle = qRadiansToDegrees(qAcos(clamp(QVector3D::dotProduct(sun, normal), -1.0f, 1.0f)));
++ if (sunAngle < m_sunAngleMin) {
++ color = color.blend(sunLinear);
++ } else if (sunAngle < m_sunAngleMax) {
++ float c2 = (sunAngle - m_sunAngleMin) / (m_sunAngleMax - m_sunAngleMin);
++ c2 = ease(c2, m_sunCurve);
++ color = color.blend(sunLinear).interpolate(color, c2);
++ }
++ }
++
++ // Write from bottom to top
++ if (isRGBE) {
++ data[(height - j - 1) * width + i] = color.toRGBE8();
++ } else {
++ // RGBA16F
++ const int offset = ((height - j - 1) * width + i) * 2;
++ qfloat16 *fData = reinterpret_cast<qfloat16 *>(data + offset);
++ float pixel[4] = {color.r, color.g, color.b, color.a };
++ qFloatToFloat16(fData, pixel, 4);
++ }
++ }
++ }
++
++ return imageData;
++ }
++
++ void ProceduralSkyTextureData::scheduleTextureUpdate()
++ {
++ generateRGBA16FTexture();
++ }
++
++ ProceduralSkyTextureData::LinearColor::LinearColor(const QColor &color)
++ {
++ const float red = color.redF();
++ const float green = color.greenF();
++ const float blue = color.blueF();
++ const float alpha = color.alphaF();
++
++ r = red < 0.04045 ? red * (1.0 / 12.92) : qPow((red + 0.055) * (1.0 / (1 + 0.055)), 2.4),
++ g = green < 0.04045 ? green * (1.0 / 12.92) : qPow((green + 0.055) * (1.0 / (1 + 0.055)), 2.4),
++ b = blue < 0.04045 ? blue * (1.0 / 12.92) : qPow((blue + 0.055) * (1.0 / (1 + 0.055)), 2.4),
++ a = alpha;
++ }
++
++ ProceduralSkyTextureData::LinearColor ProceduralSkyTextureData::LinearColor::interpolate(const ProceduralSkyTextureData::LinearColor &color, float value) const
++ {
++ LinearColor copy = *this;
++
++ copy.r += (value * (color.r - r));
++ copy.g += (value * (color.g - g));
++ copy.b += (value * (color.b - b));
++ copy.a += (value * (color.a - a));
++
++ return copy;
++ }
++
++ ProceduralSkyTextureData::LinearColor ProceduralSkyTextureData::LinearColor::blend(const ProceduralSkyTextureData::LinearColor &color) const
++ {
++ LinearColor copy;
++ float sa = 1.0 - color.a;
++ copy.a = a * sa + color.a;
++ if (copy.a == 0) {
++ return LinearColor();
++ } else {
++ copy.r = (r * a * sa + color.r * color.a) / copy.a;
++ copy.g = (g * a * sa + color.g * color.a) / copy.a;
++ copy.b = (b * a * sa + color.b * color.a) / copy.a;
++ }
++ return copy;
++ }
++
++ quint32 ProceduralSkyTextureData::LinearColor::toRGBA8() const
++ {
++ return (quint32(lrintf(r)) & 0xFF) |
++ ((quint32(lrintf(g)) & 0xFF) << 8) |
++ ((quint32(lrintf(b)) & 0xFF) << 16) |
++ ((quint32(lrintf(a)) & 0xFF) << 24);
++ }
++
++ quint32 ProceduralSkyTextureData::LinearColor::toRGBE8() const
++ {
++ float v = 0.0f;
++ int exp = 0;
++
++ v = r;
++ if (g > v)
++ v = g;
++ if (b > v)
++ v = b;
++
++ v = frexp(v, &exp) * 256.0f / v;
++ quint32 result = 0;
++ quint8 *components = reinterpret_cast<quint8*>(&result);
++ components[0] = quint8(r * v);
++ components[1] = quint8(g * v);
++ components[2] = quint8(b * v);
++ components[3] = quint8(exp + 128);
++ return result;
++ }
++
++ quint32 ProceduralSkyTextureData::LinearColor::toRGBE9995() const
++ {
++ const float pow2to9 = 512.0f;
++ const float B = 15.0f;
++ const float N = 9.0f;
++
++ float sharedExp = 65408.000f;
++
++ float cRed = qMax(0.0f, qMin(sharedExp, r));
++ float cGreen = qMax(0.0f, qMin(sharedExp, g));
++ float cBlue = qMax(0.0f, qMin(sharedExp, b));
++
++ float cMax = qMax(cRed, qMax(cGreen, cBlue));
++
++ float expp = qMax(-B - 1.0f, floor(std::log(cMax) / M_LN2)) + 1.0f + B;
++
++ float sMax = (float)floor((cMax / qPow(2.0f, expp - B - N)) + 0.5f);
++
++ float exps = expp + 1.0f;
++
++ if (0.0 <= sMax && sMax < pow2to9) {
++ exps = expp;
++ }
++
++ float sRed = qFloor((cRed / pow(2.0f, exps - B - N)) + 0.5f);
++ float sGreen = qFloor((cGreen / pow(2.0f, exps - B - N)) + 0.5f);
++ float sBlue = qFloor((cBlue / pow(2.0f, exps - B - N)) + 0.5f);
++
++ return (quint32(lrintf(sRed)) & 0x1FF) |
++ ((quint32(lrintf(sGreen)) & 0x1FF) << 9) |
++ ((quint32(lrintf(sBlue)) & 0x1FF) << 18) |
++ ((quint32(lrintf(exps)) & 0x1F) << 27);
++ }
++
++ QT_END_NAMESPACE