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 const QImage img = data.image;
201 QImage scaledImage = img.scaled( WIDTH, HEIGHT, Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
202 if ( scaledImage.format() != QImage::Format_RGB32 )
206 scaledImage = scaledImage.convertToFormat( QImage::Format_RGB32 );
208 for (
int y = 0; y < HEIGHT; ++y )
210 const float v = ( (
static_cast< float >( y ) + 0.5f ) /
static_cast< float >( HEIGHT ) ) * 2.0f - 1.0f;
211 const QRgb *line =
reinterpret_cast<const QRgb *
>( scaledImage.constScanLine( y ) );
212 for (
int x = 0; x < WIDTH; ++x )
215 const float u = ( (
static_cast< float >( x ) + 0.5f ) /
static_cast< float >( WIDTH ) ) * 2.0f - 1.0f;
216 const QVector3D cubemapDir = getCubeMapDirection( data.faceIndex, u, v ).normalized();
217 const QVector3D dir( cubemapDir.x(), -cubemapDir.z(), cubemapDir.y() );
220 float weight = 1.0f / std::pow( 1.0f + u * u + v * v, 1.5f );
221 result.totalWeight += weight;
224 const QVector3D weightedColor( color.redF() * weight, color.greenF() * weight, color.blueF() * weight );
226 constexpr float Y00 = 0.282095f;
227 constexpr float Y11 = 0.488603f;
228 constexpr float Y22 = 1.092548f;
229 constexpr float Y20 = 0.315392f;
230 constexpr float Y22_2 = 0.546274f;
232 result.coeffs[0] += weightedColor * Y00;
233 result.coeffs[1] += weightedColor * ( Y11 * dir.y() );
234 result.coeffs[2] += weightedColor * ( Y11 * dir.z() );
235 result.coeffs[3] += weightedColor * ( Y11 * dir.x() );
236 result.coeffs[4] += weightedColor * ( Y22 * dir.x() * dir.y() );
237 result.coeffs[5] += weightedColor * ( Y22 * dir.y() * dir.z() );
238 result.coeffs[6] += weightedColor * ( Y20 * ( 3.0f * dir.z() * dir.z() - 1.0f ) );
239 result.coeffs[7] += weightedColor * ( Y22 * dir.x() * dir.z() );
240 result.coeffs[8] += weightedColor * ( Y22_2 * ( dir.x() * dir.x() - dir.y() * dir.y() ) );
247 void reduceSH( SHFaceResult &finalResult,
const SHFaceResult &partialResult )
249 for (
int i = 0; i < 9; ++i )
251 finalResult.coeffs[i] += partialResult.coeffs[i];
253 finalResult.totalWeight += partialResult.totalWeight;
256 QVector<QVector3D> computeSphericalHarmonics(
const QList<FaceData> &faceDataList )
260 QFuture<SHFaceResult> future = QtConcurrent::mappedReduced( faceDataList, computeFaceSH, reduceSH );
261 future.waitForFinished();
262 SHFaceResult combinedResult = future.result();
264 if ( combinedResult.totalWeight > 0 )
266 const float norm =
static_cast< float >( 4.0 / combinedResult.totalWeight );
267 for (
int i = 0; i < 9; ++i )
269 combinedResult.coeffs[i] *= norm;
273 return combinedResult.coeffs;
279 if ( !mEnableEnvironmentalLighting )
285 auto *lightCubeMap =
new Qt3DRender::QTextureCubeMap( envLight );
286 lightCubeMap->setMagnificationFilter( Qt3DRender::QTextureCubeMap::Linear );
287 lightCubeMap->setMinificationFilter( Qt3DRender::QTextureCubeMap::LinearMipMapLinear );
288 lightCubeMap->setGenerateMipMaps(
true );
289 lightCubeMap->setWrapMode( Qt3DRender::QTextureWrapMode( Qt3DRender::QTextureWrapMode::ClampToEdge ) );
290 lightCubeMap->setFormat( Qt3DRender::QAbstractTexture::SRGB8_Alpha8 );
293 for (
const QString &texturePath : { mSourcePosX, mSourcePosY, mSourcePosZ, mSourceNegX, mSourceNegY, mSourceNegZ } )
296 maxSize = std::max( maxSize, std::max( size.width(), size.height() ) );
299 const QMap<Qt3DRender::QTextureCubeMap::CubeMapFace, FaceTransformation> faceConfigs = generateFaceTransformation();
300 const QSize faceSize( maxSize, maxSize );
302 QList<FaceData> faceDataList;
303 for (
auto it = faceConfigs.begin(); it != faceConfigs.end(); ++it )
305 const Qt3DRender::QTextureCubeMap::CubeMapFace face = it.key();
306 const FaceTransformation &config = it.value();
308 bool fitsInCache =
false;
311 ( void ) fitsInCache;
312 QImage finalImage = textureSourceImage;
313 if ( finalImage.isNull() )
315 finalImage = QImage( faceSize.width(), faceSize.height(), QImage::Format_RGB32 );
316 finalImage.fill( Qt::white );
318 else if ( config.mirrorHorizontal || config.mirrorVertical )
320 finalImage = finalImage.mirrored( config.mirrorHorizontal, config.mirrorVertical );
324 textureImage->setFace( face );
325 lightCubeMap->addTextureImage( textureImage );
327 if ( !textureSourceImage.isNull() )
329 faceDataList.append( FaceData( face, finalImage ) );
333 const QVector<QVector3D> shCoeffs = faceDataList.isEmpty() ? QVector<QVector3D>( 9, QVector3D( 0, 0, 0 ) ) : computeSphericalHarmonics( faceDataList );
338 mipLevels =
static_cast<int>( std::floor( std::log2( maxSize ) ) ) + 1;
346void QgsCubeFacesSkyboxEntity::init()
348 mGlShader->setVertexShaderCode( Qt3DRender::QShaderProgram::loadSource( QUrl( u
"qrc:/shaders/skybox.vert"_s ) ) );
349 mGlShader->setFragmentShaderCode( Qt3DRender::QShaderProgram::loadSource( QUrl( u
"qrc:/shaders/skybox.frag"_s ) ) );
355void QgsCubeFacesSkyboxEntity::reloadTexture()
357 auto *newCubeMap =
new Qt3DRender::QTextureCubeMap(
this );
358 newCubeMap->setMagnificationFilter( Qt3DRender::QTextureCubeMap::Linear );
359 newCubeMap->setMinificationFilter( Qt3DRender::QTextureCubeMap::Linear );
360 newCubeMap->setGenerateMipMaps(
false );
361 newCubeMap->setWrapMode( Qt3DRender::QTextureWrapMode( Qt3DRender::QTextureWrapMode::ClampToEdge ) );
362 newCubeMap->setFormat( Qt3DRender::QAbstractTexture::SRGB8_Alpha8 );
366 for (
const QString &texturePath : { mSourcePosX, mSourcePosY, mSourcePosZ, mSourceNegX, mSourceNegY, mSourceNegZ } )
369 maxSize = std::max( maxSize, std::max( size.width(), size.height() ) );
372 QList<Qt3DRender::QAbstractTextureImage *> newFaces;
373 const QMap<Qt3DRender::QTextureCubeMap::CubeMapFace, FaceTransformation> faceConfigs = generateFaceTransformation();
374 const QSize faceSize( maxSize, maxSize );
375 for (
auto it = faceConfigs.begin(); it != faceConfigs.end(); ++it )
377 const Qt3DRender::QTextureCubeMap::CubeMapFace face = it.key();
378 const FaceTransformation &config = it.value();
380 bool fitsInCache =
false;
382 ( void ) fitsInCache;
383 QImage finalImage = textureSourceImage;
384 if ( finalImage.isNull() )
386 finalImage = QImage( faceSize.width(), faceSize.height(), QImage::Format_RGB32 );
387 finalImage.fill( Qt::white );
389 p.begin( &finalImage );
391 uchar pixDataRGB[] = { 150, 150, 150, 255, 100, 100, 100, 255, 100, 100, 100, 255, 150, 150, 150, 255 };
392 const QImage img( pixDataRGB, 2, 2, 8, QImage::Format_ARGB32 );
393 const QPixmap pix = QPixmap::fromImage( img.scaled( 8, 8 ) );
395 checkerBrush.setTexture( pix );
396 p.fillRect( finalImage.rect(), checkerBrush );
399 else if ( config.mirrorHorizontal || config.mirrorVertical )
401 finalImage = finalImage.mirrored( config.mirrorHorizontal, config.mirrorVertical );
404 auto textureImage =
new QgsImageTexture( finalImage, newCubeMap );
405 textureImage->setFace( face );
406 newCubeMap->addTextureImage( textureImage );
407 newFaces.push_back( textureImage );
414 mCubeMap->deleteLater();
416 mCubeMap = newCubeMap;
417 mFacesTextureImages = newFaces;
420QMap<Qt3DRender::QAbstractTexture::CubeMapFace, QgsCubeFacesSkyboxEntity::FaceTransformation> QgsCubeFacesSkyboxEntity::generateFaceTransformation()
const
422 QMap<Qt3DRender::QTextureCubeMap::CubeMapFace, FaceTransformation> faceConfigs;
423 switch ( mMappingType )
426 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapPositiveX] = { mSourcePosX,
false,
false };
427 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapNegativeX] = { mSourceNegX,
false,
false };
428 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapPositiveY] = { mSourcePosY,
false,
false };
429 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapNegativeY] = { mSourceNegY,
false,
false };
430 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapPositiveZ] = { mSourcePosZ,
false,
false };
431 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapNegativeZ] = { mSourceNegZ,
false,
false };
435 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapPositiveX] = { mSourcePosX,
false,
false };
436 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapNegativeX] = { mSourceNegX,
false,
false };
437 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapPositiveY] = { mSourceNegZ,
false,
false };
438 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapNegativeY] = { mSourcePosZ,
false,
false };
439 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapPositiveZ] = { mSourcePosY,
false,
false };
440 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapNegativeZ] = { mSourceNegY,
false,
false };
444 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapPositiveX] = { mSourcePosX,
false,
true };
445 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapNegativeX] = { mSourceNegX,
false,
true };
446 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapPositiveY] = { mSourceNegZ,
false,
true };
447 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapNegativeY] = { mSourcePosZ,
false,
true };
448 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapPositiveZ] = { mSourcePosY,
false,
true };
449 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapNegativeZ] = { mSourceNegY,
false,
true };
453 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapPositiveX] = { mSourcePosY,
true,
false };
454 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapNegativeX] = { mSourceNegY,
true,
false };
455 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapPositiveY] = { mSourcePosX,
true,
false };
456 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapNegativeY] = { mSourceNegX,
true,
false };
457 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapPositiveZ] = { mSourcePosZ,
true,
false };
458 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapNegativeZ] = { mSourceNegZ,
true,
false };
462 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapPositiveX] = { mSourcePosX,
true,
false };
463 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapNegativeX] = { mSourceNegX,
true,
false };
464 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapPositiveY] = { mSourcePosZ,
true,
false };
465 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapNegativeY] = { mSourceNegZ,
true,
false };
466 faceConfigs[Qt3DRender::QTextureCubeMap::CubeMapPositiveZ] = { mSourcePosY,
true,
false };
467 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 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