26#include <Qt3DCore/QEntity>
27#include <Qt3DExtras/QCuboidMesh>
28#include <Qt3DRender/QCullFace>
29#include <Qt3DRender/QDepthTest>
30#include <Qt3DRender/QEffect>
31#include <Qt3DRender/QFilterKey>
32#include <Qt3DRender/QGraphicsApiFilter>
33#include <Qt3DRender/QMaterial>
34#include <Qt3DRender/QParameter>
35#include <Qt3DRender/QRenderPass>
36#include <Qt3DRender/QSeamlessCubemap>
37#include <Qt3DRender/QShaderProgram>
38#include <Qt3DRender/QTechnique>
39#include <Qt3DRender/QTextureImage>
40#include <QtConcurrent/QtConcurrentMap>
42#include "moc_qgsskyboxentity.cpp"
44using namespace Qt::StringLiterals;
57 mGl3Technique->graphicsApiFilter()->setApi( Qt3DRender::QGraphicsApiFilter::OpenGL );
60 mGl3Technique->graphicsApiFilter()->setProfile( Qt3DRender::QGraphicsApiFilter::CoreProfile );
68 Qt3DRender::QCullFace *cullFront =
new Qt3DRender::QCullFace();
69 cullFront->setMode( Qt3DRender::QCullFace::Front );
70 Qt3DRender::QDepthTest *depthTest =
new Qt3DRender::QDepthTest();
71 depthTest->setDepthFunction( Qt3DRender::QDepthTest::LessOrEqual );
72 Qt3DRender::QSeamlessCubemap *seamlessCubemap =
new Qt3DRender::QSeamlessCubemap();
86 mMesh->setXYMeshResolution( QSize( 2, 2 ) );
87 mMesh->setXZMeshResolution( QSize( 2, 2 ) );
88 mMesh->setYZMeshResolution( QSize( 2, 2 ) );
90 addComponent(
mMesh );
94#if ENABLE_PANORAMIC_SKYBOX
97QgsPanoramicSkyboxEntity::QgsPanoramicSkyboxEntity(
const QString &texturePath, QNode *parent )
99 , mTexturePath( texturePath )
100 , mLoadedTexture( new
Qt3DRender::QTextureLoader( parent ) )
101 , mGlShader( new
Qt3DRender::QShaderProgram( this ) )
103 mLoadedTexture->setGenerateMipMaps(
false );
104 mGlShader->setVertexShaderCode( Qt3DRender::QShaderProgram::loadSource( QUrl( u
"qrc:/shaders/skybox.vert"_s ) ) );
105 mGlShader->setFragmentShaderCode( Qt3DRender::QShaderProgram::loadSource( QUrl( u
"qrc:/shaders/hdr_skybox.frag"_s ) ) );
114void QgsPanoramicSkyboxEntity::reloadTexture()
116 mLoadedTexture->setSource( QUrl::fromUserInput( mTexturePath ) );
131 bool enableEnvironmentalLighting,
132 Qt3DCore::QNode *parent
135 , mMappingType( mapping )
136 , mSourcePosX( posX )
137 , mSourcePosY( posY )
138 , mSourcePosZ( posZ )
139 , mSourceNegX( negX )
140 , mSourceNegY( negY )
141 , mSourceNegZ( negZ )
142 , mEnableEnvironmentalLighting( enableEnvironmentalLighting )
143 , mGlShader( new
Qt3DRender::QShaderProgram() )
151 QVector3D getCubeMapDirection(
int face,
float u,
float v )
155 case Qt3DRender::QTextureCubeMap::CubeMapFace::CubeMapPositiveX:
156 return QVector3D( 1.0f, -v, -u );
157 case Qt3DRender::QTextureCubeMap::CubeMapFace::CubeMapNegativeX:
158 return QVector3D( -1.0f, -v, u );
159 case Qt3DRender::QTextureCubeMap::CubeMapFace::CubeMapPositiveY:
160 return QVector3D( u, 1.0f, v );
161 case Qt3DRender::QTextureCubeMap::CubeMapFace::CubeMapNegativeY:
162 return QVector3D( u, -1.0f, -v );
163 case Qt3DRender::QTextureCubeMap::CubeMapFace::CubeMapPositiveZ:
164 return QVector3D( u, -v, 1.0f );
165 case Qt3DRender::QTextureCubeMap::CubeMapFace::CubeMapNegativeZ:
166 return QVector3D( -u, -v, -1.0f );
176 FaceData( Qt3DRender::QTextureCubeMap::CubeMapFace faceIndex,
const QImage &image )
177 : faceIndex( faceIndex )
180 Qt3DRender::QTextureCubeMap::CubeMapFace faceIndex;
186 QVector<QVector3D> coeffs = QVector<QVector3D>( 9, QVector3D( 0.0f, 0.0f, 0.0f ) );
187 float totalWeight = 0.0f;
190 SHFaceResult computeFaceSH(
const FaceData &data )
194 constexpr int WIDTH = 32;
195 constexpr int HEIGHT = 32;
197 QImage img = data.image;
202 switch ( img.format() )
204 case QImage::Format_RGBA32FPx4:
205 case QImage::Format_RGBA32FPx4_Premultiplied:
206 case QImage::Format_RGBX32FPx4:
207 case QImage::Format_RGBA16FPx4:
208 case QImage::Format_RGBA16FPx4_Premultiplied:
209 case QImage::Format_RGBX16FPx4:
217 if ( img.format() != QImage::Format_RGBA32FPx4 )
220 img = img.convertToFormat( QImage::Format_RGBA32FPx4 );
223 QImage scaledImage = img.scaled( WIDTH, HEIGHT, Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
224 for (
int y = 0; y < HEIGHT; ++y )
226 const float v = ( (
static_cast< float >( y ) + 0.5f ) /
static_cast< float >( HEIGHT ) ) * 2.0f - 1.0f;
227 const float *line =
reinterpret_cast<const float *
>( scaledImage.constScanLine( y ) );
228 for (
int x = 0; x < WIDTH; ++x )
231 const float u = ( (
static_cast< float >( x ) + 0.5f ) /
static_cast< float >( WIDTH ) ) * 2.0f - 1.0f;
232 const QVector3D cubemapDir = getCubeMapDirection( data.faceIndex, u, v ).normalized();
233 const QVector3D dir( cubemapDir.x(), -cubemapDir.z(), cubemapDir.y() );
236 float weight = 1.0f / std::pow( 1.0f + u * u + v * v, 1.5f );
237 result.totalWeight += weight;
239 QColor color = QColor::fromRgbF( line[x * 4 + 0], line[x * 4 + 1], line[x * 4 + 2], 1 );
244 const QVector3D weightedColor( color.redF() * weight, color.greenF() * weight, color.blueF() * weight );
246 constexpr float Y00 = 0.282095f;
247 constexpr float Y11 = 0.488603f;
248 constexpr float Y22 = 1.092548f;
249 constexpr float Y20 = 0.315392f;
250 constexpr float Y22_2 = 0.546274f;
252 result.coeffs[0] += weightedColor * Y00;
253 result.coeffs[1] += weightedColor * ( Y11 * dir.y() );
254 result.coeffs[2] += weightedColor * ( Y11 * dir.z() );
255 result.coeffs[3] += weightedColor * ( Y11 * dir.x() );
256 result.coeffs[4] += weightedColor * ( Y22 * dir.x() * dir.y() );
257 result.coeffs[5] += weightedColor * ( Y22 * dir.y() * dir.z() );
258 result.coeffs[6] += weightedColor * ( Y20 * ( 3.0f * dir.z() * dir.z() - 1.0f ) );
259 result.coeffs[7] += weightedColor * ( Y22 * dir.x() * dir.z() );
260 result.coeffs[8] += weightedColor * ( Y22_2 * ( dir.x() * dir.x() - dir.y() * dir.y() ) );
267 void reduceSH( SHFaceResult &finalResult,
const SHFaceResult &partialResult )
269 for (
int i = 0; i < 9; ++i )
271 finalResult.coeffs[i] += partialResult.coeffs[i];
273 finalResult.totalWeight += partialResult.totalWeight;
276 QVector<QVector3D> computeSphericalHarmonics(
const QList<FaceData> &faceDataList )
280 QFuture<SHFaceResult> future = QtConcurrent::mappedReduced( faceDataList, computeFaceSH, reduceSH );
281 future.waitForFinished();
282 SHFaceResult combinedResult = future.result();
284 if ( combinedResult.totalWeight > 0 )
286 const float norm =
static_cast< float >( 4.0 / combinedResult.totalWeight );
287 for (
int i = 0; i < 9; ++i )
289 combinedResult.coeffs[i] *= norm;
293 return combinedResult.coeffs;
299 if ( !mEnableEnvironmentalLighting )
305 auto *lightCubeMap =
new Qt3DRender::QTextureCubeMap( envLight );
306 lightCubeMap->setMagnificationFilter( Qt3DRender::QTextureCubeMap::Linear );
307 lightCubeMap->setMinificationFilter( Qt3DRender::QTextureCubeMap::LinearMipMapLinear );
308 lightCubeMap->setGenerateMipMaps(
true );
309 lightCubeMap->setWrapMode( Qt3DRender::QTextureWrapMode( Qt3DRender::QTextureWrapMode::ClampToEdge ) );
312 for (
const QString &texturePath : { mSourcePosX, mSourcePosY, mSourcePosZ, mSourceNegX, mSourceNegY, mSourceNegZ } )
315 maxSize = std::max( maxSize, std::max( size.width(), size.height() ) );
318 const QMap<Qt3DRender::QTextureCubeMap::CubeMapFace, FaceTransformation> faceConfigs = generateFaceTransformation();
319 const QSize faceSize( maxSize, maxSize );
321 QList<FaceData> faceDataList;
322 for (
auto it = faceConfigs.begin(); it != faceConfigs.end(); ++it )
324 const Qt3DRender::QTextureCubeMap::CubeMapFace face = it.key();
325 const FaceTransformation &config = it.value();
327 bool fitsInCache =
false;
330 ( void ) fitsInCache;
331 QImage finalImage = textureSourceImage;
332 if ( finalImage.isNull() )
334 finalImage = QImage( faceSize.width(), faceSize.height(), QImage::Format_RGB32 );
335 finalImage.fill( Qt::white );
337 else if ( config.mirrorHorizontal || config.mirrorVertical )
339 finalImage = finalImage.mirrored( config.mirrorHorizontal, config.mirrorVertical );
342 bool requiresConversionToRgb =
false;
344 lightCubeMap->setFormat( textureFormat );
345 if ( requiresConversionToRgb )
347 finalImage.convertTo( QImage::Format::Format_ARGB32_Premultiplied );
350 if ( finalImage.width() != finalImage.height() )
352 const int maxDimension = std::max( finalImage.width(), finalImage.height() );
353 finalImage = finalImage.scaled( maxDimension, maxDimension, Qt::AspectRatioMode::IgnoreAspectRatio, Qt::SmoothTransformation );
357 textureImage->setFace( face );
358 lightCubeMap->addTextureImage( textureImage );
360 if ( !textureSourceImage.isNull() )
362 faceDataList.append( FaceData( face, finalImage ) );
366 const QVector<QVector3D> shCoeffs = faceDataList.isEmpty() ? QVector<QVector3D>( 9, QVector3D( 0, 0, 0 ) ) : computeSphericalHarmonics( faceDataList );
371 mipLevels =
static_cast<int>( std::floor( std::log2( maxSize ) ) ) + 1;
379void QgsCubeFacesSkyboxEntity::init()
381 mGlShader->setVertexShaderCode( Qt3DRender::QShaderProgram::loadSource( QUrl( u
"qrc:/shaders/skybox.vert"_s ) ) );
382 mGlShader->setFragmentShaderCode( Qt3DRender::QShaderProgram::loadSource( QUrl( u
"qrc:/shaders/skybox.frag"_s ) ) );
388void QgsCubeFacesSkyboxEntity::reloadTexture()
390 auto *newCubeMap =
new Qt3DRender::QTextureCubeMap(
this );
391 newCubeMap->setMagnificationFilter( Qt3DRender::QTextureCubeMap::Linear );
392 newCubeMap->setMinificationFilter( Qt3DRender::QTextureCubeMap::Linear );
393 newCubeMap->setGenerateMipMaps(
false );
394 newCubeMap->setWrapMode( Qt3DRender::QTextureWrapMode( Qt3DRender::QTextureWrapMode::ClampToEdge ) );
398 for (
const QString &texturePath : { mSourcePosX, mSourcePosY, mSourcePosZ, mSourceNegX, mSourceNegY, mSourceNegZ } )
401 maxSize = std::max( maxSize, std::max( size.width(), size.height() ) );
404 QList<Qt3DRender::QAbstractTextureImage *> newFaces;
405 const QMap<Qt3DRender::QTextureCubeMap::CubeMapFace, FaceTransformation> faceConfigs = generateFaceTransformation();
406 const QSize faceSize( maxSize, maxSize );
407 for (
auto it = faceConfigs.begin(); it != faceConfigs.end(); ++it )
409 const Qt3DRender::QTextureCubeMap::CubeMapFace face = it.key();
410 const FaceTransformation &config = it.value();
412 bool fitsInCache =
false;
414 ( void ) fitsInCache;
415 QImage finalImage = textureSourceImage;
416 if ( finalImage.isNull() )
418 finalImage = QImage( faceSize.width(), faceSize.height(), QImage::Format_RGB32 );
419 finalImage.fill( Qt::white );
421 p.begin( &finalImage );
423 uchar pixDataRGB[] = { 150, 150, 150, 255, 100, 100, 100, 255, 100, 100, 100, 255, 150, 150, 150, 255 };
424 const QImage img( pixDataRGB, 2, 2, 8, QImage::Format_ARGB32 );
425 const QPixmap pix = QPixmap::fromImage( img.scaled( 8, 8 ) );
427 checkerBrush.setTexture( pix );
428 p.fillRect( finalImage.rect(), checkerBrush );
431 else if ( config.mirrorHorizontal || config.mirrorVertical )
433 finalImage = finalImage.mirrored( config.mirrorHorizontal, config.mirrorVertical );
436 bool requiresConversionToRgb =
false;
438 newCubeMap->setFormat( textureFormat );
439 if ( requiresConversionToRgb )
441 finalImage.convertTo( QImage::Format::Format_ARGB32_Premultiplied );
444 if ( finalImage.width() != finalImage.height() )
446 const int maxDimension = std::max( finalImage.width(), finalImage.height() );
447 finalImage = finalImage.scaled( maxDimension, maxDimension, Qt::AspectRatioMode::IgnoreAspectRatio, Qt::SmoothTransformation );
450 auto textureImage =
new QgsImageTexture( finalImage, newCubeMap );
451 textureImage->setFace( face );
452 newCubeMap->addTextureImage( textureImage );
453 newFaces.push_back( textureImage );
460 mCubeMap->deleteLater();
462 mCubeMap = newCubeMap;
463 mFacesTextureImages = newFaces;
466QMap<Qt3DRender::QAbstractTexture::CubeMapFace, QgsCubeFacesSkyboxEntity::FaceTransformation> QgsCubeFacesSkyboxEntity::generateFaceTransformation()
const
468 QMap<Qt3DRender::QTextureCubeMap::CubeMapFace, FaceTransformation> faceConfigs;
469 switch ( mMappingType )
472 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapPositiveX] = { mSourcePosX,
false,
false };
473 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapNegativeX] = { mSourceNegX,
false,
false };
474 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapPositiveY] = { mSourcePosY,
false,
false };
475 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapNegativeY] = { mSourceNegY,
false,
false };
476 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapPositiveZ] = { mSourcePosZ,
false,
false };
477 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapNegativeZ] = { mSourceNegZ,
false,
false };
481 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapPositiveX] = { mSourcePosX,
false,
false };
482 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapNegativeX] = { mSourceNegX,
false,
false };
483 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapPositiveY] = { mSourceNegZ,
false,
false };
484 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapNegativeY] = { mSourcePosZ,
false,
false };
485 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapPositiveZ] = { mSourcePosY,
false,
false };
486 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapNegativeZ] = { mSourceNegY,
false,
false };
490 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapPositiveX] = { mSourcePosX,
false,
true };
491 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapNegativeX] = { mSourceNegX,
false,
true };
492 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapPositiveY] = { mSourceNegZ,
false,
true };
493 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapNegativeY] = { mSourcePosZ,
false,
true };
494 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapPositiveZ] = { mSourcePosY,
false,
true };
495 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapNegativeZ] = { mSourceNegY,
false,
true };
499 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapPositiveX] = { mSourcePosY,
true,
false };
500 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapNegativeX] = { mSourceNegY,
true,
false };
501 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapPositiveY] = { mSourcePosX,
true,
false };
502 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapNegativeY] = { mSourceNegX,
true,
false };
503 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapPositiveZ] = { mSourcePosZ,
true,
false };
504 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapNegativeZ] = { mSourceNegZ,
true,
false };
508 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapPositiveX] = { mSourcePosX,
true,
false };
509 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapNegativeX] = { mSourceNegX,
true,
false };
510 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapPositiveY] = { mSourcePosZ,
true,
false };
511 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapNegativeY] = { mSourceNegZ,
true,
false };
512 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapPositiveZ] = { mSourcePosY,
true,
false };
513 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapNegativeZ] = { mSourceNegY,
true,
false };
SkyboxCubeMapping
Skybox texture cube mapping for distinct texture skyboxes.
@ UnrealEngineZUp
Unreal engine standard (+X Forward, +Y Right, +Z Up, Left-handed).
@ NativeZUp
Textures exported for Z-up (+X Right, +Y Forward, +Z Up).
@ LeftHandedYUpMirrored
Left-Handed, Y-Up coordinate systems (e.g., Unity convention +X Right, +Y Top, +Z Forward,...
@ GodotYUp
Godot standard (+X Right, +Y Top, -Z Forward, with vertical flip).
@ OpenGLYUp
Standard OpenGL/WebGL standard (+X Right, +Y Top, -Z Forward).
static Qt3DRender::QAbstractTexture::TextureFormat determineTextureFormat(QImage::Format format, bool isSrgb, bool &requiresConversionToRgb)
Given a QImage format, returns the most appropriate corresponding texture format.
static QColor srgbToLinear(const QColor &color)
Converts a SRGB color to a linear color.
static QgsImageCache * imageCache()
Returns the application's image cache, used for caching resampled versions of raster images.
QgsCubeFacesSkyboxEntity(Qgis::SkyboxCubeMapping mapping, const QString &posX, const QString &posY, const QString &posZ, const QString &negX, const QString &negY, const QString &negZ, bool enableEnvironmentalLighting, Qt3DCore::QNode *parent=nullptr)
Constructs a skybox from 6 different images.
void updateEnvironmentLight(QgsEnvironmentLight *light) const override
Updates the specified environment light to match the skybox settings.
An environment light entity.
@ SpecularMapWithSphericalHarmonics
Specular map, using spherical harmonics for irradiance.
@ Disabled
No environment lighting.
void setMode(Mode mode)
Sets the environment light mode.
void setSpecularMap(Qt3DRender::QTextureCubeMap *specularTexture, int mipLevels)
Sets the specular map texture and available mip levels.
void setSphericalHarmonics(const QVector< QVector3D > &harmonics)
Sets the spherical harmonics for irradiant light.
QSize originalSize(const QString &path, bool blocking=false) const
Returns the original size (in pixels) of the image at the specified path.
QImage pathAsImage(const QString &path, const QSize size, const bool keepAspectRatio, const double opacity, bool &fitsInCache, bool blocking=false, double targetDpi=96, int frameNumber=-1, bool *isMissing=nullptr)
Returns the specified path rendered as an image.
Holds an image that can be used as a texture in the 3D view.
Base class for all skybox types.
QgsSkyboxEntity(QNode *parent=nullptr)
Constructor.
Qt3DRender::QRenderPass * mGl3RenderPass
Qt3DRender::QParameter * mGammaStrengthParameter
Qt3DRender::QFilterKey * mFilterKey
Qt3DRender::QEffect * mEffect
Qt3DExtras::QCuboidMesh * mMesh
Qt3DRender::QParameter * mTextureParameter
Qt3DRender::QMaterial * mMaterial
Qt3DRender::QTechnique * mGl3Technique