// Copyright (C) 2020 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "dynamictexturedata.h" #include #include #include #include DynamicTextureData::DynamicTextureData() { } static QColor linearInterpolate(const QColor &color1, const QColor &color2, float value) { QColor output; output.setRedF(color1.redF() + (value * (color2.redF() - color1.redF()))); output.setGreenF(color1.greenF() + (value * (color2.greenF() - color1.greenF()))); output.setBlueF(color1.blueF() + (value * (color2.blueF() - color1.blueF()))); return output; } void DynamicTextureData::generateRGBA8Texture() { const int width = 256; const int height = 256; setSize(QSize(width, height)); setFormat(Format::RGBA8); setHasTransparency(false); QByteArray imageData; QByteArray gradientScanline; gradientScanline.resize(width * 4); // RGBA8 for (int x = 0; x < width; ++x) { QColor color = linearInterpolate(QColor(255, 0, 0, 255), QColor(0, 255, 255, 255), x / float(width)); int offset = x * 4; gradientScanline.data()[offset + 0] = char(color.red()); gradientScanline.data()[offset + 1] = char(color.green()); gradientScanline.data()[offset + 2] = char(color.blue()); gradientScanline.data()[offset + 3] = char(255); } for (int y = 0; y < height; ++y) imageData += gradientScanline; setTextureData(imageData); } void DynamicTextureData::generateRGBE8Texture() { const int width = 1024; const int height = 512; setSize(QSize(width, height)); setFormat(Format::RGBE8); setHasTransparency(false); const int dataSize = width * height * 4; // 1 byte per channel QByteArray imageData; imageData.resize(dataSize); generateHDRTexture(width, height, imageData, true); setTextureData(imageData); } void DynamicTextureData::generateRGBA32FTexture() { const int width = 1024; const int height = 512; setSize(QSize(width, height)); setFormat(Format::RGBA32F); setHasTransparency(false); const int dataSize = width * height * 4 * 4; // 4 bytes per channel QByteArray imageData; imageData.resize(dataSize); generateHDRTexture(width, height, imageData, false); setTextureData(imageData); } DynamicTextureData::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; } DynamicTextureData::LinearColor DynamicTextureData::LinearColor::interpolate(const DynamicTextureData::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; } DynamicTextureData::LinearColor DynamicTextureData::LinearColor::blend(const DynamicTextureData::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 DynamicTextureData::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(&result); components[0] = quint8(r * v); components[1] = quint8(g * v); components[2] = quint8(b * v); components[3] = quint8(exp + 128); return result; } void DynamicTextureData::generateHDRTexture(int width, int height, QByteArray &imageData, bool isRGBE) { quint32 *data = reinterpret_cast(imageData.data()); LinearColor skyTopLinear(QColor(68, 129, 235)); LinearColor skyHorizonLinear(QColor(4, 190, 254)); LinearColor groundBottomLinear(QColor(48, 67, 82)); LinearColor groundHorizonLinear(QColor(215, 210, 204)); LinearColor sunLinear(QColor(255, 255, 249)); QVector3D sun(0, 0, -1); sun = QQuaternion::fromAxisAndAngle(QVector3D(1, 0, 0), 28.0) * sun; sun = QQuaternion::fromAxisAndAngle(QVector3D(0, 1, 0), 45.0) * 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, 0.02f)); } else { // Sky color float c = vAngle / (M_PI * 0.5f); color = skyHorizonLinear.interpolate(skyTopLinear, ease(1.0 - c, 0.09f)); float sunAngle = qRadiansToDegrees(qAcos(clamp(QVector3D::dotProduct(sun, normal), -1.0f, 1.0f))); if (sunAngle < 1.0) { color = color.blend(sunLinear); } else if (sunAngle < 100) { float c2 = (sunAngle - 1) / (100 - 1); c2 = ease(c2, 0.05f); color = color.blend(sunLinear).interpolate(color, c2); } } // Write from bottem to top if (isRGBE) { data[(height - j - 1) * width + i] = color.toRGBE8(); } else { const int offset = ((height - j - 1) * width + i) * 4; float *fData = reinterpret_cast(data + offset); fData[0] = color.r; fData[1] = color.g; fData[2] = color.b; fData[3] = color.a; } } } }