7#include <QtQuick3DRuntimeRender/private/qssgrenderer_p.h>
8#include <QtQuick3DRuntimeRender/private/qssgrhiquadrenderer_p.h>
9#include <QtQuick3DRuntimeRender/private/qssglayerrenderdata_p.h>
10#include "../qssgrendercontextcore.h"
11#include <QtQuick3DUtils/private/qssgutils_p.h>
13#ifdef QT_QUICK3D_HAS_LIGHTMAPPER
14#include <QtCore/qfuture.h>
15#include <QtCore/qfileinfo.h>
16#include <QtConcurrent/qtconcurrentrun.h>
17#include <QRandomGenerator>
19#include <embree3/rtcore.h>
20#include <QtQuick3DRuntimeRender/private/qssglightmapio_p.h>
23#include <QWaitCondition>
25#include <QTemporaryFile>
27#include <QOffscreenSurface>
28#include <QOpenGLContext>
34using namespace Qt::StringLiterals;
49#ifdef QT_QUICK3D_HAS_LIGHTMAPPER
51static constexpr int GAUSS_HALF_KERNEL_SIZE = 3;
52static constexpr int DIRECT_MAP_UPSCALE_FACTOR = 4;
53static constexpr int MAX_TILE_SIZE = 1024;
54static constexpr quint32 PIXEL_VOID = 0;
55static constexpr quint32 PIXEL_UNSET = -1;
56static constexpr char KEY_SCENE_METADATA[] =
"_qt_scene_metadata";
58static void floodFill(quint32 *maskUintPtr,
const int rows,
const int cols)
60 quint32 targetColor = 1;
61 QList<std::array<
int, 2>> stack;
62 stack.reserve(rows * cols);
63 for (
int y0 = 0; y0 < rows; y0++) {
64 for (
int x0 = 0; x0 < cols; x0++) {
66 stack.push_back({ x0, y0 });
67 while (!stack.empty()) {
68 const auto [x, y] = stack.takeLast();
69 const int idx = cols * y + x;
70 const quint32 value = maskUintPtr[idx];
73 if (value != PIXEL_UNSET)
77 maskUintPtr[idx] = targetColor;
82 stack.push_back({ x + 1, y });
84 stack.push_back({ x - 1, y });
86 stack.push_back({ x, y + 1 });
88 stack.push_back({ x, y - 1 });
94 }
while (targetColor == PIXEL_VOID || targetColor == PIXEL_UNSET);
100static QString formatDuration(quint64 milliseconds,
bool showMilliseconds =
true)
102 const quint64 partMilliseconds = milliseconds % 1000;
103 const quint64 partSeconds = (milliseconds / 1000) % 60;
104 const quint64 partMinutes = (milliseconds / 60000) % 60;
105 const quint64 partHours = (milliseconds / 3600000) % 60;
108 return showMilliseconds
109 ? QStringLiteral(
"%1h %2m %3s %4ms").arg(partHours).arg(partMinutes).arg(partSeconds).arg(partMilliseconds)
110 : QStringLiteral(
"%1h %2m %3s").arg(partHours).arg(partMinutes).arg(partSeconds);
112 if (partMinutes > 0) {
113 return showMilliseconds ? QStringLiteral(
"%1m %2s %3ms").arg(partMinutes).arg(partSeconds).arg(partMilliseconds)
114 : QStringLiteral(
"%1m %2s").arg(partMinutes).arg(partSeconds);
116 if (partSeconds > 0) {
117 return showMilliseconds ? QStringLiteral(
"%1s %2ms").arg(partSeconds).arg(partMilliseconds)
118 : QStringLiteral(
"%1s").arg(partSeconds);
120 return showMilliseconds ? QStringLiteral(
"%1ms").arg(partMilliseconds) : QStringLiteral(
"0s");
129struct ProgressTracker
131 void initBake(quint32 numIndirectSamples, quint32 numIndirectBounces)
134 const double direct = 2;
135 const double indirect = numIndirectSamples * numIndirectBounces;
136 const double denoise = 1;
137 const double combined = direct + indirect + denoise;
139 fractionDirect = qMax(direct / combined, 0.02);
140 fractionDenoise = qMax(denoise / combined, 0.02);
141 fractionIndirect = qMax(1.0 - fractionDirect - fractionDenoise, 0.0);
148 fractionIndirect = 0;
151 void setTotalDirectTiles(quint32 totalDirectTilesNew)
153 totalDirectTiles = totalDirectTilesNew;
156 void setStage(Stage stageNew)
158 if (stage == stageNew)
161 if (stage == Stage::Indirect)
162 indirectTimer.start();
165 double getEstimatedTimeRemaining()
167 double estimatedTimeRemaining = -1.0;
168 if (stage == Stage::Indirect && indirectTimer.isValid()) {
169 double totalElapsed = indirectTimer.elapsed();
170 double fullEstimate =
static_cast<
double>(totalElapsed) / progressIndirect;
171 estimatedTimeRemaining = (1.0 - progressIndirect) * fullEstimate;
173 return estimatedTimeRemaining;
181 void directTileDone()
183 Q_ASSERT(stage == Stage::Direct);
185 progress = (fractionDirect * directTilesDone) / qMax(1u, totalDirectTiles);
188 void denoisedModelDone(
int i,
int n)
190 Q_ASSERT(stage == Stage::Denoise);
191 progress = fractionDirect + fractionIndirect + (fractionDenoise *
double(i) / n);
194 void indirectTexelDone(quint64 i, quint64 n)
196 Q_ASSERT(stage == Stage::Indirect);
197 progressIndirect =
double(i) / n;
198 progress = fractionDirect + (fractionIndirect * progressIndirect);
202 double fractionDirect = 0;
203 double fractionIndirect = 0;
204 double fractionDenoise = 0;
206 double progressIndirect = 0;
207 quint32 totalDirectTiles = 0;
208 quint32 directTilesDone = 0;
209 Stage stage = Stage::Direct;
210 QElapsedTimer indirectTimer;
213struct QSSGLightmapperPrivate
215 explicit QSSGLightmapperPrivate() =
default;
217 QSSGLightmapperOptions options;
219 QVector<QSSGBakedLightingModel> bakedLightingModels;
220 QRhi::Implementation rhiBackend = QRhi::Null;
221 std::unique_ptr<QSSGRenderContextInterface> rhiCtxInterface;
222 std::unique_ptr<QSSGRenderer> renderer;
225 QWaitCondition initCondition;
228 QSSGLightmapper::Callback outputCallback;
229 QSSGLightmapper::BakingControl bakingControl;
230 QElapsedTimer totalTimer;
235 unsigned int geomId = RTC_INVALID_GEOMETRY_ID;
237 QSSGRenderImage *baseColorNode =
nullptr;
238 QRhiTexture *baseColorMap =
nullptr;
239 QVector3D emissiveFactor;
240 QSSGRenderImage *emissiveNode =
nullptr;
241 QRhiTexture *emissiveMap =
nullptr;
242 QSSGRenderImage *normalMapNode =
nullptr;
243 QRhiTexture *normalMap =
nullptr;
244 float normalStrength = 0.0f;
245 float opacity = 0.0f;
247 using SubMeshInfoList = QVector<SubMeshInfo>;
248 QVector<SubMeshInfoList> subMeshInfos;
252 QByteArray vertexData;
253 quint32 vertexStride;
254 QByteArray indexData;
255 QRhiCommandBuffer::IndexFormat indexFormat = QRhiCommandBuffer::IndexUInt32;
256 quint32 positionOffset = UINT_MAX;
257 QRhiVertexInputAttribute::Format positionFormat = QRhiVertexInputAttribute::Float;
258 quint32 normalOffset = UINT_MAX;
259 QRhiVertexInputAttribute::Format normalFormat = QRhiVertexInputAttribute::Float;
260 quint32 uvOffset = UINT_MAX;
261 QRhiVertexInputAttribute::Format uvFormat = QRhiVertexInputAttribute::Float;
262 quint32 lightmapUVOffset = UINT_MAX;
263 QRhiVertexInputAttribute::Format lightmapUVFormat = QRhiVertexInputAttribute::Float;
264 quint32 tangentOffset = UINT_MAX;
265 QRhiVertexInputAttribute::Format tangentFormat = QRhiVertexInputAttribute::Float;
266 quint32 binormalOffset = UINT_MAX;
267 QRhiVertexInputAttribute::Format binormalFormat = QRhiVertexInputAttribute::Float;
271 QVector<DrawInfo> drawInfos;
272 QVector<QByteArray> meshes;
285 float cosInnerConeAngle;
286 float constantAttenuation;
287 float linearAttenuation;
288 float quadraticAttenuation;
290 QVector<Light> lights;
292 RTCDevice rdev =
nullptr;
293 RTCScene rscene =
nullptr;
295 struct RasterResult {
296 bool success =
false;
299 QByteArray worldPositions;
301 QByteArray baseColors;
302 QByteArray emissions;
310 bool isValid()
const {
return !worldPos.isNull() && !normal.isNull(); }
313 QVector<QVector<ModelTexel>> modelTexels;
314 QVector<
bool> modelHasBaseColorTransparency;
315 quint32 emissiveModelCount = 0;
316 QVector<quint32> numValidTexels;
318 QVector<
int> geomLightmapMap;
319 QVector<
float> subMeshOpacityMap;
321 bool denoiseOnly =
false;
323 double totalProgress = 0;
324 qint64 estimatedTimeRemaining = -1;
325 quint64 indirectTexelsTotal = 0;
326 quint64 indirectTexelsDone = 0;
328 inline const ModelTexel &texelForLightmapUV(
unsigned int geomId,
float u,
float v)
const
331 const int modelIdx = geomLightmapMap[geomId];
332 QSize texelSize = drawInfos[modelIdx].lightmapSize;
333 u = qBound(0.0f, u, 1.0f);
335 v = 1.0f - qBound(0.0f, v, 1.0f);
337 const int w = texelSize.width();
338 const int h = texelSize.height();
339 const int x = qBound(0,
int(w * u), w - 1);
340 const int y = qBound(0,
int(h * v), h - 1);
341 const int texelIdx = x + y * w;
343 return modelTexels[modelIdx][texelIdx];
346 bool userCancelled();
347 void sendOutputInfo(QSSGLightmapper::BakingStatus type,
348 std::optional<QString> msg,
349 bool outputToConsole =
true,
350 bool outputConsoleTimeRemanining =
false);
351 void updateStage(
const QString &newStage);
352 bool commitGeometry();
353 bool prepareLightmaps();
354 bool verifyLights()
const;
355 QVector<QVector3D> computeDirectLight(
int lmIdx);
356 QVector<QVector3D> computeIndirectLight(
int lmIdx,
359 bool storeMeshes(QSharedPointer<QSSGLightmapWriter> tempFile);
361 RasterResult rasterizeLightmap(
int lmIdx,
363 QVector2D minUVRegion = QVector2D(0, 0),
364 QVector2D maxUVRegion = QVector2D(1, 1));
366 bool storeSceneMetadata(QSharedPointer<QSSGLightmapWriter> writer);
367 bool storeMetadata(
int lmIdx, QSharedPointer<QSSGLightmapWriter> tempFile);
368 bool storeScale(
int lmIdx, QSharedPointer<QSSGLightmapWriter> tempFile);
369 bool storeDirectLightData(
int lmIdx,
const QVector<QVector3D> &directLight, QSharedPointer<QSSGLightmapWriter> tempFile);
370 bool storeIndirectLightData(
int lmIdx,
const QVector<QVector3D> &indirectLight, QSharedPointer<QSSGLightmapWriter> tempFile);
371 bool storeMaskImage(
int lmIdx, QSharedPointer<QSSGLightmapWriter> tempFile);
373 bool denoiseLightmaps();
375 QVector3D sampleDirectLight(QVector3D worldPos, QVector3D normal,
bool allLight)
const;
376 QByteArray dilate(
const QSize &pixelSize,
const QByteArray &image);
378 QString stage = QStringLiteral(
"Initializing");
380 ProgressTracker progressTracker;
381 qint64 bakeStartTime = 0;
387class TimerThread :
public QThread {
390 TimerThread(QObject *parent =
nullptr)
391 : QThread(parent), intervalMs(1000), stopped(
false) {}
398 void setInterval(
int ms) {
402 void setCallback(
const std::function<
void()>& func) {
411 void run() override {
418 if (elapsed >= intervalMs && callback) {
427 std::function<
void()> callback;
428 std::atomic<
bool> stopped;
431static const int LM_SEAM_BLEND_ITER_COUNT = 4;
433QSSGLightmapper::QSSGLightmapper() : d(
new QSSGLightmapperPrivate())
436 _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
437 _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON);
441QSSGLightmapper::~QSSGLightmapper()
447 _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_OFF);
448 _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_OFF);
452void QSSGLightmapper::reset()
454 d->bakedLightingModels.clear();
455 d->subMeshInfos.clear();
456 d->drawInfos.clear();
459 d->modelHasBaseColorTransparency.clear();
460 d->emissiveModelCount = 0;
463 d->geomLightmapMap.clear();
464 d->subMeshOpacityMap.clear();
467 rtcReleaseScene(d->rscene);
471 rtcReleaseDevice(d->rdev);
475 d->bakingControl.cancelled =
false;
476 d->totalProgress = 0.0;
477 d->estimatedTimeRemaining = -1;
480void QSSGLightmapper::setOptions(
const QSSGLightmapperOptions &options)
482 d->options = options;
485void QSSGLightmapper::setOutputCallback(Callback callback)
487 d->outputCallback = callback;
490qsizetype QSSGLightmapper::add(
const QSSGBakedLightingModel &model)
492 d->bakedLightingModels.append(model);
493 return d->bakedLightingModels.size() - 1;
496void QSSGLightmapper::setRhiBackend(QRhi::Implementation backend)
498 d->rhiBackend = backend;
501void QSSGLightmapper::setDenoiseOnly(
bool value)
503 d->denoiseOnly = value;
506static void embreeErrFunc(
void *, RTCError error,
const char *str)
508 qWarning(
"lm: Embree error: %d: %s", error, str);
511static const unsigned int NORMAL_SLOT = 0;
512static const unsigned int LIGHTMAP_UV_SLOT = 1;
514static void embreeFilterFunc(
const RTCFilterFunctionNArguments *args)
516 RTCHit *hit =
reinterpret_cast<RTCHit *>(args->hit);
517 QSSGLightmapperPrivate *d =
static_cast<QSSGLightmapperPrivate *>(args->geometryUserPtr);
518 RTCGeometry geom = rtcGetGeometry(d->rscene, hit->geomID);
521 rtcInterpolate0(geom, hit->primID, hit->u, hit->v, RTC_BUFFER_TYPE_VERTEX_ATTRIBUTE, LIGHTMAP_UV_SLOT, &hit->u, 2);
523 const float opacity = d->subMeshOpacityMap[hit->geomID];
524 const int modelIdx = d->geomLightmapMap[hit->geomID];
525 if (opacity < 1.0f || d->modelHasBaseColorTransparency[modelIdx]) {
526 const QSSGLightmapperPrivate::ModelTexel &texel(d->texelForLightmapUV(hit->geomID, hit->u, hit->v));
531 const float alpha = opacity * texel.baseColor.w();
537 if (alpha < d->options.opacityThreshold)
542static QByteArray meshToByteArray(
const QSSGMesh::Mesh &mesh)
545 QBuffer buffer(&meshData);
546 buffer.open(QIODevice::WriteOnly);
553static QVector3D extractScale(
const QMatrix4x4 &transform)
555 Q_ASSERT(transform.isAffine());
558 const QVector4D col0 = transform.column(0);
559 const QVector4D col1 = transform.column(1);
560 const QVector4D col2 = transform.column(2);
562 const float scaleX = QVector3D(col0[0], col0[1], col0[2]).length();
563 const float scaleY = QVector3D(col1[0], col1[1], col1[2]).length();
564 const float scaleZ = QVector3D(col2[0], col2[1], col2[2]).length();
566 return QVector3D(scaleX, scaleY, scaleZ);
570static QMatrix4x4 constructScaleMatrix(
const QVector3D &scale)
573 QMatrix4x4 scaleMatrix;
574 scaleMatrix.data()[0 * 4 + 0] = scale.x();
575 scaleMatrix.data()[1 * 4 + 1] = scale.y();
576 scaleMatrix.data()[2 * 4 + 2] = scale.z();
580bool QSSGLightmapperPrivate::commitGeometry()
582 if (bakedLightingModels.isEmpty()) {
583 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"No models with usedInBakedLighting, cannot bake"));
587 sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral(
"Preparing geometry..."));
588 QElapsedTimer geomPrepTimer;
589 geomPrepTimer.start();
591 const auto &bufferManager(renderer->contextInterface()->bufferManager());
593 const int bakedLightingModelCount = bakedLightingModels.size();
594 subMeshInfos.resize(bakedLightingModelCount);
595 drawInfos.resize(bakedLightingModelCount);
596 modelTexels.resize(bakedLightingModelCount);
597 modelHasBaseColorTransparency.resize(bakedLightingModelCount,
false);
599 for (
int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
600 const QSSGBakedLightingModel &lm(bakedLightingModels[lmIdx]);
601 if (lm.renderables.isEmpty()) {
602 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"No submeshes, model %1 cannot be lightmapped").
603 arg(lm.model->lightmapKey));
606 if (lm.model->skin || lm.model->skeleton) {
607 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Skinned models not supported: %1").
608 arg(lm.model->lightmapKey));
612 subMeshInfos[lmIdx].reserve(lm.renderables.size());
613 for (
const QSSGRenderableObjectHandle &handle : std::as_const(lm.renderables)) {
614 Q_ASSERT(handle.obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset
615 || handle.obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset);
616 QSSGSubsetRenderable *renderableObj =
static_cast<QSSGSubsetRenderable *>(handle.obj);
618 info.offset = renderableObj->subset.offset;
619 info.count = renderableObj->subset.count;
620 info.opacity = renderableObj->opacity;
621 if (handle.obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset) {
622 const QSSGRenderDefaultMaterial *defMat =
static_cast<
const QSSGRenderDefaultMaterial *>(&renderableObj->material);
623 info.baseColor = defMat->color;
624 info.emissiveFactor = defMat->emissiveColor;
625 if (defMat->colorMap) {
626 info.baseColorNode = defMat->colorMap;
627 QSSGRenderImageTexture texture = bufferManager->loadRenderImage(defMat->colorMap);
628 info.baseColorMap = texture.m_texture;
630 if (defMat->emissiveMap) {
631 info.emissiveNode = defMat->emissiveMap;
632 QSSGRenderImageTexture texture = bufferManager->loadRenderImage(defMat->emissiveMap);
633 info.emissiveMap = texture.m_texture;
635 if (defMat->normalMap) {
636 info.normalMapNode = defMat->normalMap;
637 QSSGRenderImageTexture texture = bufferManager->loadRenderImage(defMat->normalMap);
638 info.normalMap = texture.m_texture;
639 info.normalStrength = defMat->bumpAmount;
642 info.baseColor = QVector4D(1.0f, 1.0f, 1.0f, 1.0f);
643 info.emissiveFactor = QVector3D(0.0f, 0.0f, 0.0f);
645 subMeshInfos[lmIdx].append(info);
648 QMatrix4x4 worldTransform;
649 QMatrix3x3 normalMatrix;
650 QSSGSubsetRenderable *renderableObj =
static_cast<QSSGSubsetRenderable *>(lm.renderables.first().obj);
651 worldTransform = renderableObj->modelContext.globalTransform;
652 normalMatrix = renderableObj->modelContext.normalMatrix;
653 const QVector3D scale = extractScale(worldTransform);
654 const QMatrix4x4 scaleTransform = constructScaleMatrix(scale);
656 DrawInfo &drawInfo(drawInfos[lmIdx]);
657 drawInfo.scale = scale;
660 if (lm.model->geometry)
661 mesh = bufferManager->loadMeshData(lm.model->geometry);
663 mesh = bufferManager->loadMeshData(lm.model->meshPath);
665 if (!mesh.isValid()) {
666 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning,
667 QStringLiteral(
"Failed to load geometry for model %1").arg(lm.model->lightmapKey));
671 QElapsedTimer unwrapTimer;
674 const float texelsPerUnit = lm.model->texelsPerUnit <= 0.0f ? options.texelsPerUnit : lm.model->texelsPerUnit;
675 if (!mesh.createLightmapUVChannel(texelsPerUnit, scaleTransform)) {
676 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Failed to do lightmap UV unwrapping for model %1").
677 arg(lm.model->lightmapKey));
680 sendOutputInfo(QSSGLightmapper::BakingStatus::Info,
681 QStringLiteral(
"Lightmap UV unwrap done for model %1 in %2")
682 .arg(lm.model->lightmapKey, formatDuration(unwrapTimer.elapsed())));
684 if (lm.model->hasLightmap()) {
685 QByteArray meshData = meshToByteArray(mesh);
689 for (
int i = 0; i < meshes.size(); ++i) {
690 if (meshData == meshes[i]) {
697 meshes.push_back(meshData);
698 meshIndex = meshes.size() - 1;
700 drawInfo.meshIndex = meshIndex;
703 drawInfo.lightmapSize = mesh.subsets().constFirst().lightmapSizeHint;
704 drawInfo.vertexData = mesh.vertexBuffer().data;
705 drawInfo.vertexStride = mesh.vertexBuffer().stride;
706 drawInfo.indexData = mesh.indexBuffer().data;
708 if (drawInfo.vertexData.isEmpty()) {
709 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"No vertex data for model %1").arg(lm.model->lightmapKey));
712 if (drawInfo.indexData.isEmpty()) {
713 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"No index data for model %1").arg(lm.model->lightmapKey));
717 switch (mesh.indexBuffer().componentType) {
718 case QSSGMesh::Mesh::ComponentType::UnsignedInt16:
719 drawInfo.indexFormat = QRhiCommandBuffer::IndexUInt16;
721 case QSSGMesh::Mesh::ComponentType::UnsignedInt32:
722 drawInfo.indexFormat = QRhiCommandBuffer::IndexUInt32;
725 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Unknown index component type %1 for model %2").
726 arg(
int(mesh.indexBuffer().componentType)).
727 arg(lm.model->lightmapKey));
731 const auto vertexBuffer = mesh.vertexBuffer();
732 for (
const QSSGMesh::Mesh::VertexBufferEntry &vbe : vertexBuffer.entries) {
733 if (vbe.name == QSSGMesh::MeshInternal::getPositionAttrName()) {
734 drawInfo.positionOffset = vbe.offset;
735 drawInfo.positionFormat = QSSGRhiHelpers::toVertexInputFormat(QSSGRenderComponentType(vbe.componentType), vbe.componentCount);
736 }
else if (vbe.name == QSSGMesh::MeshInternal::getNormalAttrName()) {
737 drawInfo.normalOffset = vbe.offset;
738 drawInfo.normalFormat = QSSGRhiHelpers::toVertexInputFormat(QSSGRenderComponentType(vbe.componentType), vbe.componentCount);
739 }
else if (vbe.name == QSSGMesh::MeshInternal::getUV0AttrName()) {
740 drawInfo.uvOffset = vbe.offset;
741 drawInfo.uvFormat = QSSGRhiHelpers::toVertexInputFormat(QSSGRenderComponentType(vbe.componentType), vbe.componentCount);
742 }
else if (vbe.name == QSSGMesh::MeshInternal::getLightmapUVAttrName()) {
743 drawInfo.lightmapUVOffset = vbe.offset;
744 drawInfo.lightmapUVFormat = QSSGRhiHelpers::toVertexInputFormat(QSSGRenderComponentType(vbe.componentType), vbe.componentCount);
745 }
else if (vbe.name == QSSGMesh::MeshInternal::getTexTanAttrName()) {
746 drawInfo.tangentOffset = vbe.offset;
747 drawInfo.tangentFormat = QSSGRhiHelpers::toVertexInputFormat(QSSGRenderComponentType(vbe.componentType), vbe.componentCount);
748 }
else if (vbe.name == QSSGMesh::MeshInternal::getTexBinormalAttrName()) {
749 drawInfo.binormalOffset = vbe.offset;
750 drawInfo.binormalFormat = QSSGRhiHelpers::toVertexInputFormat(QSSGRenderComponentType(vbe.componentType), vbe.componentCount);
754 if (!(drawInfo.positionOffset != UINT_MAX && drawInfo.normalOffset != UINT_MAX)) {
755 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Could not figure out position and normal attribute offsets for model %1").
756 arg(lm.model->lightmapKey));
761 if (!(drawInfo.positionFormat == QRhiVertexInputAttribute::Float3
762 && drawInfo.normalFormat == QRhiVertexInputAttribute::Float3))
764 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Position or normal attribute format is not as expected (float3) for model %1").
765 arg(lm.model->lightmapKey));
769 if (drawInfo.lightmapUVOffset == UINT_MAX) {
770 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Could not figure out lightmap UV attribute offset for model %1").
771 arg(lm.model->lightmapKey));
775 if (drawInfo.lightmapUVFormat != QRhiVertexInputAttribute::Float2) {
776 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Lightmap UV attribute format is not as expected (float2) for model %1").
777 arg(lm.model->lightmapKey));
782 if (drawInfo.uvOffset != UINT_MAX) {
783 if (drawInfo.uvFormat != QRhiVertexInputAttribute::Float2) {
784 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"UV0 attribute format is not as expected (float2) for model %1").
785 arg(lm.model->lightmapKey));
790 if (drawInfo.tangentOffset != UINT_MAX) {
791 if (drawInfo.tangentFormat != QRhiVertexInputAttribute::Float3) {
792 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Tangent attribute format is not as expected (float3) for model %1").
793 arg(lm.model->lightmapKey));
797 if (drawInfo.binormalOffset != UINT_MAX) {
798 if (drawInfo.binormalFormat != QRhiVertexInputAttribute::Float3) {
799 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Binormal attribute format is not as expected (float3) for model %1").
800 arg(lm.model->lightmapKey));
805 if (drawInfo.indexFormat == QRhiCommandBuffer::IndexUInt16) {
806 drawInfo.indexFormat = QRhiCommandBuffer::IndexUInt32;
807 QByteArray newIndexData(drawInfo.indexData.size() * 2, Qt::Uninitialized);
808 const quint16 *s =
reinterpret_cast<
const quint16 *>(drawInfo.indexData.constData());
809 size_t sz = drawInfo.indexData.size() / 2;
810 quint32 *p =
reinterpret_cast<quint32 *>(newIndexData.data());
813 drawInfo.indexData = newIndexData;
818 char *vertexBase = drawInfo.vertexData.data();
819 const qsizetype sz = drawInfo.vertexData.size();
820 for (qsizetype offset = 0; offset < sz; offset += drawInfo.vertexStride) {
821 char *posPtr = vertexBase + offset + drawInfo.positionOffset;
822 float *fPosPtr =
reinterpret_cast<
float *>(posPtr);
823 QVector3D pos(fPosPtr[0], fPosPtr[1], fPosPtr[2]);
824 char *normalPtr = vertexBase + offset + drawInfo.normalOffset;
825 float *fNormalPtr =
reinterpret_cast<
float *>(normalPtr);
826 QVector3D normal(fNormalPtr[0], fNormalPtr[1], fNormalPtr[2]);
827 pos = worldTransform.map(pos);
828 normal = QSSGUtils::mat33::transform(normalMatrix, normal).normalized();
829 *fPosPtr++ = pos.x();
830 *fPosPtr++ = pos.y();
831 *fPosPtr++ = pos.z();
832 *fNormalPtr++ = normal.x();
833 *fNormalPtr++ = normal.y();
834 *fNormalPtr++ = normal.z();
839 rdev = rtcNewDevice(
nullptr);
841 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Failed to create Embree device"));
845 rtcSetDeviceErrorFunction(rdev, embreeErrFunc,
nullptr);
847 rscene = rtcNewScene(rdev);
849 unsigned int geomId = 1;
851 for (
int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
852 const QSSGBakedLightingModel &lm(bakedLightingModels[lmIdx]);
858 if (!lm.model->castsShadows)
861 const DrawInfo &drawInfo(drawInfos[lmIdx]);
862 const char *vbase = drawInfo.vertexData.constData();
863 const quint32 *ibase =
reinterpret_cast<
const quint32 *>(drawInfo.indexData.constData());
865 for (SubMeshInfo &subMeshInfo : subMeshInfos[lmIdx]) {
866 RTCGeometry geom = rtcNewGeometry(rdev, RTC_GEOMETRY_TYPE_TRIANGLE);
867 rtcSetGeometryVertexAttributeCount(geom, 2);
868 quint32 *ip =
static_cast<quint32 *>(rtcSetNewGeometryBuffer(geom, RTC_BUFFER_TYPE_INDEX, 0, RTC_FORMAT_UINT3, 3 *
sizeof(uint32_t), subMeshInfo.count / 3));
869 for (quint32 i = 0; i < subMeshInfo.count; ++i)
871 float *vp =
static_cast<
float *>(rtcSetNewGeometryBuffer(geom, RTC_BUFFER_TYPE_VERTEX, 0, RTC_FORMAT_FLOAT3, 3 *
sizeof(
float), subMeshInfo.count));
872 for (quint32 i = 0; i < subMeshInfo.count; ++i) {
873 const quint32 idx = *(ibase + subMeshInfo.offset + i);
874 const float *src =
reinterpret_cast<
const float *>(vbase + idx * drawInfo.vertexStride + drawInfo.positionOffset);
879 vp =
static_cast<
float *>(rtcSetNewGeometryBuffer(geom, RTC_BUFFER_TYPE_VERTEX_ATTRIBUTE, NORMAL_SLOT, RTC_FORMAT_FLOAT3, 3 *
sizeof(
float), subMeshInfo.count));
880 for (quint32 i = 0; i < subMeshInfo.count; ++i) {
881 const quint32 idx = *(ibase + subMeshInfo.offset + i);
882 const float *src =
reinterpret_cast<
const float *>(vbase + idx * drawInfo.vertexStride + drawInfo.normalOffset);
887 vp =
static_cast<
float *>(rtcSetNewGeometryBuffer(geom, RTC_BUFFER_TYPE_VERTEX_ATTRIBUTE, LIGHTMAP_UV_SLOT, RTC_FORMAT_FLOAT2, 2 *
sizeof(
float), subMeshInfo.count));
888 for (quint32 i = 0; i < subMeshInfo.count; ++i) {
889 const quint32 idx = *(ibase + subMeshInfo.offset + i);
890 const float *src =
reinterpret_cast<
const float *>(vbase + idx * drawInfo.vertexStride + drawInfo.lightmapUVOffset);
894 rtcCommitGeometry(geom);
895 rtcSetGeometryIntersectFilterFunction(geom, embreeFilterFunc);
896 rtcSetGeometryUserData(geom,
this);
897 rtcAttachGeometryByID(rscene, geom, geomId);
898 subMeshInfo.geomId = geomId++;
899 rtcReleaseGeometry(geom);
903 rtcCommitScene(rscene);
906 rtcGetSceneBounds(rscene, &bounds);
907 QVector3D lowerBound(bounds.lower_x, bounds.lower_y, bounds.lower_z);
908 QVector3D upperBound(bounds.upper_x, bounds.upper_y, bounds.upper_z);
909 qDebug() <<
"[lm] Bounds in world space for raytracing scene:" << lowerBound << upperBound;
911 const unsigned int geomIdBasedMapSize = geomId;
914 geomLightmapMap.fill(-1, geomIdBasedMapSize);
915 subMeshOpacityMap.fill(0.0f, geomIdBasedMapSize);
917 for (
int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
918 const QSSGBakedLightingModel &lm(bakedLightingModels[lmIdx]);
919 if (!lm.model->castsShadows)
921 for (SubMeshInfo &subMeshInfo : subMeshInfos[lmIdx])
922 subMeshOpacityMap[subMeshInfo.geomId] = subMeshInfo.opacity;
925 sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral(
"Geometry ready. Time taken: %1").arg(formatDuration(geomPrepTimer.elapsed())));
929QSSGLightmapperPrivate::RasterResult QSSGLightmapperPrivate::rasterizeLightmap(
int lmIdx, QSize outputSize, QVector2D minUVRegion, QVector2D maxUVRegion)
931 QSSGLightmapperPrivate::RasterResult result;
933 QSSGRhiContext *rhiCtx = rhiCtxInterface->rhiContext().get();
934 QRhi *rhi = rhiCtx->rhi();
935 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
937 const DrawInfo &bakeModelDrawInfo(drawInfos[lmIdx]);
938 const bool hasUV0 = bakeModelDrawInfo.uvOffset != UINT_MAX;
939 const bool hasTangentAndBinormal = bakeModelDrawInfo.tangentOffset != UINT_MAX
940 && bakeModelDrawInfo.binormalOffset != UINT_MAX;
942 QRhiVertexInputLayout inputLayout;
943 inputLayout.setBindings({ QRhiVertexInputBinding(bakeModelDrawInfo.vertexStride) });
945 std::unique_ptr<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, bakeModelDrawInfo.vertexData.size()));
946 if (!vbuf->create()) {
947 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Failed to create vertex buffer"));
950 std::unique_ptr<QRhiBuffer> ibuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::IndexBuffer, bakeModelDrawInfo.indexData.size()));
951 if (!ibuf->create()) {
952 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Failed to create index buffer"));
955 QRhiResourceUpdateBatch *resUpd = rhi->nextResourceUpdateBatch();
956 resUpd->uploadStaticBuffer(vbuf.get(), bakeModelDrawInfo.vertexData.constData());
957 resUpd->uploadStaticBuffer(ibuf.get(), bakeModelDrawInfo.indexData.constData());
958 QRhiTexture *dummyTexture = rhiCtx->dummyTexture({}, resUpd);
959 cb->resourceUpdate(resUpd);
961 std::unique_ptr<QRhiTexture> positionData(rhi->newTexture(QRhiTexture::RGBA32F, outputSize, 1,
962 QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
963 if (!positionData->create()) {
964 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Failed to create FP32 texture for positions"));
967 std::unique_ptr<QRhiTexture> normalData(rhi->newTexture(QRhiTexture::RGBA32F, outputSize, 1,
968 QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
969 if (!normalData->create()) {
970 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Failed to create FP32 texture for normals"));
973 std::unique_ptr<QRhiTexture> baseColorData(rhi->newTexture(QRhiTexture::RGBA32F, outputSize, 1,
974 QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
975 if (!baseColorData->create()) {
976 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Failed to create FP32 texture for base color"));
979 std::unique_ptr<QRhiTexture> emissionData(rhi->newTexture(QRhiTexture::RGBA32F, outputSize, 1,
980 QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
981 if (!emissionData->create()) {
982 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Failed to create FP32 texture for emissive color"));
986 std::unique_ptr<QRhiRenderBuffer> ds(rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, outputSize));
988 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Failed to create depth-stencil buffer"));
992 QRhiColorAttachment posAtt(positionData.get());
993 QRhiColorAttachment normalAtt(normalData.get());
994 QRhiColorAttachment baseColorAtt(baseColorData.get());
995 QRhiColorAttachment emissionAtt(emissionData.get());
996 QRhiTextureRenderTargetDescription rtDesc;
997 rtDesc.setColorAttachments({ posAtt, normalAtt, baseColorAtt, emissionAtt });
998 rtDesc.setDepthStencilBuffer(ds.get());
1000 std::unique_ptr<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(rtDesc));
1001 std::unique_ptr<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor());
1002 rt->setRenderPassDescriptor(rpDesc.get());
1003 if (!rt->create()) {
1004 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Failed to create texture render target"));
1008 static const int UBUF_SIZE = 64;
1009 const int subMeshCount = subMeshInfos[lmIdx].size();
1010 const int alignedUbufSize = rhi->ubufAligned(UBUF_SIZE);
1011 const int totalUbufSize = alignedUbufSize * subMeshCount;
1012 std::unique_ptr<QRhiBuffer> ubuf(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, totalUbufSize));
1013 if (!ubuf->create()) {
1014 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Failed to create uniform buffer of size %1").arg(totalUbufSize));
1021 qint32 flipY = rhi->isYUpInFramebuffer() ? 0 : 1;
1022 if (rhi->isYUpInNDC())
1025 char *ubufData = ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
1026 for (
int subMeshIdx = 0; subMeshIdx != subMeshCount; ++subMeshIdx) {
1027 const SubMeshInfo &subMeshInfo(subMeshInfos[lmIdx][subMeshIdx]);
1028 qint32 hasBaseColorMap = subMeshInfo.baseColorMap ? 1 : 0;
1029 qint32 hasEmissiveMap = subMeshInfo.emissiveMap ? 1 : 0;
1030 qint32 hasNormalMap = subMeshInfo.normalMap && hasTangentAndBinormal ? 1 : 0;
1031 const float minRegionU = minUVRegion.x();
1032 const float minRegionV = minUVRegion.y();
1033 const float maxRegionU = maxUVRegion.x();
1034 const float maxRegionV = maxUVRegion.y();
1035 char *p = ubufData + subMeshIdx * alignedUbufSize;
1036 memcpy(p, &subMeshInfo.baseColor, 4 *
sizeof(
float));
1037 memcpy(p + 16, &subMeshInfo.emissiveFactor, 3 *
sizeof(
float));
1038 memcpy(p + 28, &flipY,
sizeof(qint32));
1039 memcpy(p + 32, &hasBaseColorMap,
sizeof(qint32));
1040 memcpy(p + 36, &hasEmissiveMap,
sizeof(qint32));
1041 memcpy(p + 40, &hasNormalMap,
sizeof(qint32));
1042 memcpy(p + 44, &subMeshInfo.normalStrength,
sizeof(
float));
1043 memcpy(p + 48, &minRegionU,
sizeof(
float));
1044 memcpy(p + 52, &minRegionV,
sizeof(
float));
1045 memcpy(p + 56, &maxRegionU,
sizeof(
float));
1046 memcpy(p + 60, &maxRegionV,
sizeof(
float));
1048 ubuf->endFullDynamicBufferUpdateForCurrentFrame();
1050 auto setupPipeline = [rhi, &rpDesc](QSSGRhiShaderPipeline *shaderPipeline,
1051 QRhiShaderResourceBindings *srb,
1052 const QRhiVertexInputLayout &inputLayout)
1054 QRhiGraphicsPipeline *ps = rhi->newGraphicsPipeline();
1055 ps->setTopology(QRhiGraphicsPipeline::Triangles);
1056 ps->setDepthTest(
true);
1057 ps->setDepthWrite(
true);
1058 ps->setDepthOp(QRhiGraphicsPipeline::Less);
1059 ps->setShaderStages(shaderPipeline->cbeginStages(), shaderPipeline->cendStages());
1060 ps->setTargetBlends({ {}, {}, {}, {} });
1061 ps->setRenderPassDescriptor(rpDesc.get());
1062 ps->setVertexInputLayout(inputLayout);
1063 ps->setShaderResourceBindings(srb);
1067 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
1068 QVector<QRhiGraphicsPipeline *> ps;
1071 QVector<QRhiGraphicsPipeline *> psLine;
1073 for (
int subMeshIdx = 0; subMeshIdx != subMeshCount; ++subMeshIdx) {
1074 const SubMeshInfo &subMeshInfo(subMeshInfos[lmIdx][subMeshIdx]);
1075 QVarLengthArray<QRhiVertexInputAttribute, 6> vertexAttrs;
1076 vertexAttrs << QRhiVertexInputAttribute(0, 0, bakeModelDrawInfo.positionFormat, bakeModelDrawInfo.positionOffset)
1077 << QRhiVertexInputAttribute(0, 1, bakeModelDrawInfo.normalFormat, bakeModelDrawInfo.normalOffset)
1078 << QRhiVertexInputAttribute(0, 2, bakeModelDrawInfo.lightmapUVFormat, bakeModelDrawInfo.lightmapUVOffset);
1083 QSSGBuiltInRhiShaderCache::LightmapUVRasterizationShaderMode shaderVariant = QSSGBuiltInRhiShaderCache::LightmapUVRasterizationShaderMode::Default;
1085 shaderVariant = QSSGBuiltInRhiShaderCache::LightmapUVRasterizationShaderMode::Uv;
1086 if (hasTangentAndBinormal)
1087 shaderVariant = QSSGBuiltInRhiShaderCache::LightmapUVRasterizationShaderMode::UvTangent;
1090 const auto &shaderCache = renderer->contextInterface()->shaderCache();
1091 const auto &lmUvRastShaderPipeline = shaderCache->getBuiltInRhiShaders().getRhiLightmapUVRasterizationShader(shaderVariant);
1092 if (!lmUvRastShaderPipeline) {
1093 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Failed to load shaders"));
1098 vertexAttrs << QRhiVertexInputAttribute(0, 3, bakeModelDrawInfo.uvFormat, bakeModelDrawInfo.uvOffset);
1099 if (hasTangentAndBinormal) {
1100 vertexAttrs << QRhiVertexInputAttribute(0, 4, bakeModelDrawInfo.tangentFormat, bakeModelDrawInfo.tangentOffset);
1101 vertexAttrs << QRhiVertexInputAttribute(0, 5, bakeModelDrawInfo.binormalFormat, bakeModelDrawInfo.binormalOffset);
1105 inputLayout.setAttributes(vertexAttrs.cbegin(), vertexAttrs.cend());
1107 QSSGRhiShaderResourceBindingList bindings;
1108 bindings.addUniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, ubuf.get(),
1109 subMeshIdx * alignedUbufSize, UBUF_SIZE);
1110 QRhiSampler *dummySampler = rhiCtx->sampler({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
1111 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
1112 if (subMeshInfo.baseColorMap) {
1113 const bool mipmapped = subMeshInfo.baseColorMap->flags().testFlag(QRhiTexture::MipMapped);
1114 QRhiSampler *sampler = rhiCtx->sampler({ QSSGRhiHelpers::toRhi(subMeshInfo.baseColorNode->m_minFilterType),
1115 QSSGRhiHelpers::toRhi(subMeshInfo.baseColorNode->m_magFilterType),
1116 mipmapped ? QSSGRhiHelpers::toRhi(subMeshInfo.baseColorNode->m_mipFilterType) : QRhiSampler::None,
1117 QSSGRhiHelpers::toRhi(subMeshInfo.baseColorNode->m_horizontalTilingMode),
1118 QSSGRhiHelpers::toRhi(subMeshInfo.baseColorNode->m_verticalTilingMode),
1119 QSSGRhiHelpers::toRhi(subMeshInfo.baseColorNode->m_depthTilingMode)
1121 bindings.addTexture(1, QRhiShaderResourceBinding::FragmentStage, subMeshInfo.baseColorMap, sampler);
1123 bindings.addTexture(1, QRhiShaderResourceBinding::FragmentStage, dummyTexture, dummySampler);
1125 if (subMeshInfo.emissiveMap) {
1126 const bool mipmapped = subMeshInfo.emissiveMap->flags().testFlag(QRhiTexture::MipMapped);
1127 QRhiSampler *sampler = rhiCtx->sampler({ QSSGRhiHelpers::toRhi(subMeshInfo.emissiveNode->m_minFilterType),
1128 QSSGRhiHelpers::toRhi(subMeshInfo.emissiveNode->m_magFilterType),
1129 mipmapped ? QSSGRhiHelpers::toRhi(subMeshInfo.emissiveNode->m_mipFilterType) : QRhiSampler::None,
1130 QSSGRhiHelpers::toRhi(subMeshInfo.emissiveNode->m_horizontalTilingMode),
1131 QSSGRhiHelpers::toRhi(subMeshInfo.emissiveNode->m_verticalTilingMode),
1132 QSSGRhiHelpers::toRhi(subMeshInfo.emissiveNode->m_depthTilingMode)
1134 bindings.addTexture(2, QRhiShaderResourceBinding::FragmentStage, subMeshInfo.emissiveMap, sampler);
1136 bindings.addTexture(2, QRhiShaderResourceBinding::FragmentStage, dummyTexture, dummySampler);
1138 if (subMeshInfo.normalMap) {
1139 const bool mipmapped = subMeshInfo.normalMap->flags().testFlag(QRhiTexture::MipMapped);
1140 QRhiSampler *sampler = rhiCtx->sampler({ QSSGRhiHelpers::toRhi(subMeshInfo.normalMapNode->m_minFilterType),
1141 QSSGRhiHelpers::toRhi(subMeshInfo.normalMapNode->m_magFilterType),
1142 mipmapped ? QSSGRhiHelpers::toRhi(subMeshInfo.normalMapNode->m_mipFilterType) : QRhiSampler::None,
1143 QSSGRhiHelpers::toRhi(subMeshInfo.normalMapNode->m_horizontalTilingMode),
1144 QSSGRhiHelpers::toRhi(subMeshInfo.normalMapNode->m_verticalTilingMode),
1145 QSSGRhiHelpers::toRhi(subMeshInfo.normalMapNode->m_depthTilingMode)
1147 bindings.addTexture(3, QRhiShaderResourceBinding::FragmentStage, subMeshInfo.normalMap, sampler);
1149 bindings.addTexture(3, QRhiShaderResourceBinding::FragmentStage, dummyTexture, dummySampler);
1151 QRhiShaderResourceBindings *srb = rhiCtxD->srb(bindings);
1153 QRhiGraphicsPipeline *pipeline = setupPipeline(lmUvRastShaderPipeline.get(), srb, inputLayout);
1154 if (!pipeline->create()) {
1155 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Failed to create graphics pipeline (mesh %1 submesh %2)").
1162 ps.append(pipeline);
1163 pipeline = setupPipeline(lmUvRastShaderPipeline.get(), srb, inputLayout);
1164 pipeline->setPolygonMode(QRhiGraphicsPipeline::Line);
1165 if (!pipeline->create()) {
1166 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Failed to create graphics pipeline with line fill mode (mesh %1 submesh %2)").
1173 psLine.append(pipeline);
1176 QRhiCommandBuffer::VertexInput vertexBuffers = { vbuf.get(), 0 };
1177 const QRhiViewport viewport(0, 0,
float(outputSize.width()),
float(outputSize.height()));
1178 bool hadViewport =
false;
1180 cb->beginPass(rt.get(), Qt::black, { 1.0f, 0 });
1181 for (
int subMeshIdx = 0; subMeshIdx != subMeshCount; ++subMeshIdx) {
1182 const SubMeshInfo &subMeshInfo(subMeshInfos[lmIdx][subMeshIdx]);
1183 cb->setGraphicsPipeline(ps[subMeshIdx]);
1185 cb->setViewport(viewport);
1188 cb->setShaderResources();
1189 cb->setVertexInput(0, 1, &vertexBuffers, ibuf.get(), 0, QRhiCommandBuffer::IndexUInt32);
1190 cb->drawIndexed(subMeshInfo.count, 1, subMeshInfo.offset);
1191 cb->setGraphicsPipeline(psLine[subMeshIdx]);
1192 cb->setShaderResources();
1193 cb->drawIndexed(subMeshInfo.count, 1, subMeshInfo.offset);
1196 resUpd = rhi->nextResourceUpdateBatch();
1197 QRhiReadbackResult posReadResult;
1198 QRhiReadbackResult normalReadResult;
1199 QRhiReadbackResult baseColorReadResult;
1200 QRhiReadbackResult emissionReadResult;
1201 resUpd->readBackTexture({ positionData.get() }, &posReadResult);
1202 resUpd->readBackTexture({ normalData.get() }, &normalReadResult);
1203 resUpd->readBackTexture({ baseColorData.get() }, &baseColorReadResult);
1204 resUpd->readBackTexture({ emissionData.get() }, &emissionReadResult);
1205 cb->endPass(resUpd);
1213 const int numPixels = outputSize.width() * outputSize.height();
1215 result.worldPositions.resize(numPixels);
1216 result.normals.resize(numPixels);
1217 result.baseColors.resize(numPixels);
1218 result.emissions.resize(numPixels);
1222 if (posReadResult.data.size() < numPixels * 16) {
1223 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Position data is smaller than expected"));
1226 if (normalReadResult.data.size() < numPixels * 16) {
1227 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Normal data is smaller than expected"));
1230 if (baseColorReadResult.data.size() < numPixels * 16) {
1231 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Base color data is smaller than expected"));
1234 if (emissionReadResult.data.size() < numPixels * 16) {
1235 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Emission data is smaller than expected"));
1239 result.success =
true;
1240 result.width = outputSize.width();
1241 result.height = outputSize.height();
1242 result.worldPositions = posReadResult.data;
1243 result.normals = normalReadResult.data;
1244 result.baseColors = baseColorReadResult.data;
1245 result.emissions = emissionReadResult.data;
1250bool QSSGLightmapperPrivate::prepareLightmaps()
1252 QRhi *rhi = rhiCtxInterface->rhiContext()->rhi();
1254 if (!rhi->isTextureFormatSupported(QRhiTexture::RGBA32F)) {
1255 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"FP32 textures not supported, cannot bake"));
1258 if (rhi->resourceLimit(QRhi::MaxColorAttachments) < 4) {
1259 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Multiple render targets not supported, cannot bake"));
1262 if (!rhi->isFeatureSupported(QRhi::NonFillPolygonMode)) {
1263 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Line polygon mode not supported, cannot bake"));
1267 sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral(
"Preparing lightmaps..."));
1268 QElapsedTimer lightmapPrepTimer;
1269 lightmapPrepTimer.start();
1270 const int bakedLightingModelCount = bakedLightingModels.size();
1271 Q_ASSERT(drawInfos.size() == bakedLightingModelCount);
1272 Q_ASSERT(subMeshInfos.size() == bakedLightingModelCount);
1274 numValidTexels.resize(bakedLightingModelCount);
1276 for (
int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
1277 QElapsedTimer rasterizeTimer;
1278 rasterizeTimer.start();
1280 const QSSGBakedLightingModel &lm(bakedLightingModels[lmIdx]);
1281 const QSize lightmapSize = drawInfos[lmIdx].lightmapSize;
1283 const int w = lightmapSize.width();
1284 const int h = lightmapSize.height();
1285 const int numPixels = w * h;
1287 int unusedEntries = 0;
1288 QVector<ModelTexel> &texels = modelTexels[lmIdx];
1289 texels.resize(numPixels);
1292 constexpr int maxTileSize = MAX_TILE_SIZE;
1293 const int numTilesX = (w + maxTileSize - 1) / maxTileSize;
1294 const int numTilesY = (h + maxTileSize - 1) / maxTileSize;
1296 bool isEmissive =
false;
1299 for (
int tileY = 0; tileY < numTilesY; ++tileY) {
1300 for (
int tileX = 0; tileX < numTilesX; ++tileX) {
1302 const int startX = tileX * maxTileSize;
1303 const int startY = tileY * maxTileSize;
1305 const int tileWidth = qMin(maxTileSize, w - startX);
1306 const int tileHeight = qMin(maxTileSize, h - startY);
1308 const int endX = startX + tileWidth;
1309 const int endY = startY + tileHeight;
1311 const float minU = startX /
double(w);
1312 const float maxV = 1.0 - startY /
double(h);
1313 const float maxU = endX /
double(w);
1314 const float minV = 1.0 - endY /
double(h);
1316 QSSGLightmapperPrivate::RasterResult raster = rasterizeLightmap(lmIdx,
1317 QSize(tileWidth, tileHeight),
1318 QVector2D(minU, minV),
1319 QVector2D(maxU, maxV));
1320 if (!raster.success)
1323 QVector4D *worldPositions =
reinterpret_cast<QVector4D *>(raster.worldPositions.data());
1324 QVector4D *normals =
reinterpret_cast<QVector4D *>(raster.normals.data());
1325 QVector4D *baseColors =
reinterpret_cast<QVector4D *>(raster.baseColors.data());
1326 QVector4D *emissions =
reinterpret_cast<QVector4D *>(raster.emissions.data());
1328 for (
int y = startY; y < endY; ++y) {
1329 const int ySrc = y - startY;
1330 Q_ASSERT(ySrc < tileHeight);
1331 for (
int x = startX; x < endX; ++x) {
1332 const int xSrc = x - startX;
1333 Q_ASSERT(xSrc < tileWidth);
1335 const int dstPixelI = y * w + x;
1336 const int srcPixelI = ySrc * tileWidth + xSrc;
1338 ModelTexel &lmPix(texels[dstPixelI]);
1340 lmPix.worldPos = worldPositions[srcPixelI].toVector3D();
1341 lmPix.normal = normals[srcPixelI].toVector3D();
1342 if (lmPix.isValid())
1343 ++numValidTexels[lmIdx];
1345 lmPix.baseColor = baseColors[srcPixelI];
1346 if (lmPix.baseColor[3] < 1.0f)
1347 modelHasBaseColorTransparency[lmIdx] =
true;
1349 lmPix.emission = emissions[srcPixelI].toVector3D();
1350 if (!isEmissive && !qFuzzyIsNull(lmPix.emission.length()))
1358 ++emissiveModelCount;
1360 sendOutputInfo(QSSGLightmapper::BakingStatus::Info,
1362 "Successfully rasterized %1/%2 lightmap texels for model %3, lightmap size %4 in %5")
1363 .arg(QString::number(texels.size() - unusedEntries),
1364 QString::number(texels.size()),
1365 lm.model->lightmapKey,
1366 QStringLiteral(
"(%1, %2)").arg(QString::number(w), QString::number(h)),
1367 formatDuration(rasterizeTimer.elapsed())));
1368 for (
const SubMeshInfo &subMeshInfo : std::as_const(subMeshInfos[lmIdx])) {
1369 if (!lm.model->castsShadows)
1371 geomLightmapMap[subMeshInfo.geomId] = lmIdx;
1375 sendOutputInfo(QSSGLightmapper::BakingStatus::Info,
1376 QStringLiteral(
"Lightmaps ready. Time taken: %1")
1377 .arg(formatDuration(lightmapPrepTimer.elapsed())));
1381bool QSSGLightmapperPrivate::verifyLights()
const {
1383 return !lights.empty() || emissiveModelCount > 0;
1386bool QSSGLightmapper::setupLights(
const QSSGRenderer &renderer)
1388 QSSGLayerRenderData *renderData = QSSGRendererPrivate::getCurrentRenderData(renderer);
1390 qWarning() <<
"lm: No render data, cannot bake lightmaps";
1394 if (d->bakedLightingModels.isEmpty()) {
1395 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info,
1396 QStringLiteral(
"No models provided, cannot bake lightmaps"));
1403 auto lights =
static_cast<QSSGSubsetRenderable *>(d->bakedLightingModels.first().renderables.first().obj)->lights;
1404 for (
const QSSGShaderLight &sl : lights) {
1405 if (!sl.light->m_bakingEnabled)
1408 QSSGLightmapperPrivate::Light light;
1409 light.indirectOnly = !sl.light->m_fullyBaked;
1410 light.direction = sl.direction;
1412 const float brightness = sl.light->m_brightness;
1413 light.color = QVector3D(sl.light->m_diffuseColor.x() * brightness,
1414 sl.light->m_diffuseColor.y() * brightness,
1415 sl.light->m_diffuseColor.z() * brightness);
1417 if (sl.light->type == QSSGRenderLight::Type::PointLight
1418 || sl.light->type == QSSGRenderLight::Type::SpotLight) {
1419 const QMatrix4x4 lightGlobalTransform = renderData->getGlobalTransform(*sl.light);
1420 light.worldPos = QSSGRenderNode::getGlobalPos(lightGlobalTransform);
1421 if (sl.light->type == QSSGRenderLight::Type::SpotLight) {
1422 light.type = QSSGLightmapperPrivate::Light::Spot;
1423 light.cosConeAngle = qCos(qDegreesToRadians(sl.light->m_coneAngle));
1424 light.cosInnerConeAngle = qCos(
1425 qDegreesToRadians(qMin(sl.light->m_innerConeAngle, sl.light->m_coneAngle)));
1427 light.type = QSSGLightmapperPrivate::Light::Point;
1429 light.constantAttenuation = QSSGUtils::aux::translateConstantAttenuation(
1430 sl.light->m_constantFade);
1431 light.linearAttenuation = QSSGUtils::aux::translateLinearAttenuation(
1432 sl.light->m_linearFade);
1433 light.quadraticAttenuation = QSSGUtils::aux::translateQuadraticAttenuation(
1434 sl.light->m_quadraticFade);
1436 light.type = QSSGLightmapperPrivate::Light::Directional;
1439 d->lights.append(light);
1442 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info,
1443 QStringLiteral(
"Total lights registered: %1").arg(d->lights.size()));
1451 RayHit(
const QVector3D &org,
const QVector3D &dir,
float tnear = 0.0f,
float tfar = std::numeric_limits<
float>::infinity()) {
1452 rayhit.ray.org_x = org.x();
1453 rayhit.ray.org_y = org.y();
1454 rayhit.ray.org_z = org.z();
1455 rayhit.ray.dir_x = dir.x();
1456 rayhit.ray.dir_y = dir.y();
1457 rayhit.ray.dir_z = dir.z();
1458 rayhit.ray.tnear = tnear;
1459 rayhit.ray.tfar = tfar;
1460 rayhit.hit.u = 0.0f;
1461 rayhit.hit.v = 0.0f;
1462 rayhit.hit.geomID = RTC_INVALID_GEOMETRY_ID;
1467 bool intersect(RTCScene scene)
1469 RTCIntersectContext ctx;
1470 rtcInitIntersectContext(&ctx);
1471 rtcIntersect1(scene, &ctx, &rayhit);
1472 return rayhit.hit.geomID != RTC_INVALID_GEOMETRY_ID;
1476static inline QVector3D vectorSign(
const QVector3D &v)
1478 return QVector3D(v.x() < 1.0f ? -1.0f : 1.0f,
1479 v.y() < 1.0f ? -1.0f : 1.0f,
1480 v.z() < 1.0f ? -1.0f : 1.0f);
1483static inline QVector3D vectorAbs(
const QVector3D &v)
1485 return QVector3D(std::abs(v.x()),
1491QList<QVector3D> applyGaussianBlur(
const QList<QVector3D>& image,
const QList<quint32>& mask,
int width,
int height,
float sigma) {
1493 constexpr int halfKernelSize = GAUSS_HALF_KERNEL_SIZE;
1494 constexpr int kernelSize = halfKernelSize * 2 + 1;
1497 double kernel[kernelSize][kernelSize];
1498 double mean = halfKernelSize;
1499 for (
int y = 0; y < kernelSize; ++y) {
1500 for (
int x = 0; x < kernelSize; ++x) {
1501 kernel[y][x] = exp(-0.5 * (pow((x - mean) / sigma, 2.0) + pow((y - mean) / sigma, 2.0))) / (2 * M_PI * sigma * sigma);
1504 sum += kernel[y][x];
1509 for (
int x = 0; x < kernelSize; ++x)
1510 for (
int y = 0; y < kernelSize; ++y)
1511 kernel[y][x] /= sum;
1514 QList<QVector3D> output(image.size(), QVector3D(0, 0, 0));
1517 for (
int y = 0; y < height; ++y) {
1518 for (
int x = 0; x < width; ++x) {
1519 const int centerIdx = y * width + x;
1520 const quint32 maskID = mask[centerIdx];
1521 if (maskID == PIXEL_VOID)
1524 QVector3D blurredPixel(0, 0, 0);
1525 float weightSum = 0.0f;
1528 for (
int ky = -halfKernelSize; ky <= halfKernelSize; ++ky) {
1529 for (
int kx = -halfKernelSize; kx <= halfKernelSize; ++kx) {
1532 if (px < 0 || px >= width || py < 0 || py >= height)
1535 int idx = py * width + px;
1536 if (mask[idx] != maskID)
1539 double weight = kernel[ky + halfKernelSize][kx + halfKernelSize];
1540 blurredPixel += image[idx] * weight;
1541 weightSum += weight;
1546 if (weightSum > 0.0f)
1547 blurredPixel /= weightSum;
1549 output[centerIdx] = blurredPixel;
1558 std::array<QVector3D, 2> pos;
1559 std::array<QVector3D, 2> normal;
1562inline bool operator==(
const Edge &a,
const Edge &b)
1564 return qFuzzyCompare(a.pos[0], b.pos[0]) && qFuzzyCompare(a.pos[1], b.pos[1])
1565 && qFuzzyCompare(a.normal[0], b.normal[0]) && qFuzzyCompare(a.normal[1], b.normal[1]);
1568inline size_t qHash(
const Edge &e, size_t seed) Q_DECL_NOTHROW
1570 return qHash(e.pos[0].x(), seed) ^ qHash(e.pos[0].y()) ^ qHash(e.pos[0].z()) ^ qHash(e.pos[1].x())
1571 ^ qHash(e.pos[1].y()) ^ qHash(e.pos[1].z());
1576 std::array<QVector2D, 2> uv;
1582 std::array<std::array<QVector2D, 2>, 2> uv;
1585static inline bool vectorLessThan(
const QVector3D &a,
const QVector3D &b)
1587 if (a.x() == b.x()) {
1589 return a.z() < b.z();
1591 return a.y() < b.y();
1593 return a.x() < b.x();
1596static inline float floatSign(
float f)
1598 return f > 0.0f ? 1.0f : (f < 0.0f ? -1.0f : 0.0f);
1601static inline QVector2D flooredVec(
const QVector2D &v)
1603 return QVector2D(std::floor(v.x()), std::floor(v.y()));
1606static inline QVector2D projectPointToLine(
const QVector2D &point,
const std::array<QVector2D, 2> &line)
1608 const QVector2D p = point - line[0];
1609 const QVector2D n = line[1] - line[0];
1610 const float lengthSquared = n.lengthSquared();
1611 if (!qFuzzyIsNull(lengthSquared)) {
1612 const float d = (n.x() * p.x() + n.y() * p.y()) / lengthSquared;
1613 return d <= 0.0f ? line[0] : (d >= 1.0f ? line[1] : line[0] + n * d);
1618static void blendLine(
const QVector2D &from,
1619 const QVector2D &to,
1620 const QVector2D &uvFrom,
1621 const QVector2D &uvTo,
1622 const float *readBuf,
1624 const QSize &lightmapPixelSize,
1625 const int stride = 4)
1627 const QVector2D size(lightmapPixelSize.width(), lightmapPixelSize.height());
1628 const std::array<QVector2D, 2> line = { QVector2D(from.x(), 1.0f - from.y()) * size, QVector2D(to.x(), 1.0f - to.y()) * size };
1629 const float lineLength = line[0].distanceToPoint(line[1]);
1630 if (qFuzzyIsNull(lineLength))
1633 const QVector2D startPixel = flooredVec(line[0]);
1634 const QVector2D endPixel = flooredVec(line[1]);
1636 const QVector2D dir = (line[1] - line[0]).normalized();
1637 const QVector2D tStep(1.0f / std::abs(dir.x()), 1.0f / std::abs(dir.y()));
1638 const QVector2D pixelStep(floatSign(dir.x()), floatSign(dir.y()));
1640 QVector2D nextT(std::fmod(line[0].x(), 1.0f), std::fmod(line[0].y(), 1.0f));
1641 if (pixelStep.x() == 1.0f)
1642 nextT.setX(1.0f - nextT.x());
1643 if (pixelStep.y() == 1.0f)
1644 nextT.setY(1.0f - nextT.y());
1646 if (!qFuzzyIsNull(dir.x()))
1647 nextT.setX(nextT.x() / std::abs(dir.x()));
1649 nextT.setX(std::numeric_limits<
float>::max());
1651 if (!qFuzzyIsNull(dir.y()))
1652 nextT.setY(nextT.y() / std::abs(dir.y()));
1654 nextT.setY(std::numeric_limits<
float>::max());
1656 QVector2D pixel = startPixel;
1658 const auto clampedXY = [s = lightmapPixelSize](QVector2D xy) -> std::array<
int, 2> {
1659 return { qBound(0,
int(xy.x()), s.width() - 1), qBound(0,
int(xy.y()), s.height() - 1) };
1662 while (startPixel.distanceToPoint(pixel) < lineLength + 1.0f) {
1663 const QVector2D point = projectPointToLine(pixel + QVector2D(0.5f, 0.5f), line);
1664 const float t = line[0].distanceToPoint(point) / lineLength;
1665 const QVector2D uvInterp = uvFrom * (1.0 - t) + uvTo * t;
1666 const auto sampledPixelXY = clampedXY(flooredVec(QVector2D(uvInterp.x(), 1.0f - uvInterp.y()) * size));
1667 const int sampOfs = (sampledPixelXY[0] + sampledPixelXY[1] * lightmapPixelSize.width()) * stride;
1668 const QVector3D sampledColor(readBuf[sampOfs], readBuf[sampOfs + 1], readBuf[sampOfs + 2]);
1669 const auto pixelXY = clampedXY(pixel);
1670 const int pixOfs = (pixelXY[0] + pixelXY[1] * lightmapPixelSize.width()) * stride;
1671 QVector3D currentColor(writeBuf[pixOfs], writeBuf[pixOfs + 1], writeBuf[pixOfs + 2]);
1672 currentColor = currentColor * 0.6f + sampledColor * 0.4f;
1673 writeBuf[pixOfs] = currentColor.x();
1674 writeBuf[pixOfs + 1] = currentColor.y();
1675 writeBuf[pixOfs + 2] = currentColor.z();
1677 if (pixel != endPixel) {
1678 if (nextT.x() < nextT.y()) {
1679 pixel.setX(pixel.x() + pixelStep.x());
1680 nextT.setX(nextT.x() + tStep.x());
1682 pixel.setY(pixel.y() + pixelStep.y());
1683 nextT.setY(nextT.y() + tStep.y());
1691QVector3D QSSGLightmapperPrivate::sampleDirectLight(QVector3D worldPos, QVector3D normal,
bool allLight)
const
1693 QVector3D directLight = QVector3D(0.f, 0.f, 0.f);
1695 if (options.useAdaptiveBias)
1696 worldPos += vectorSign(normal) * vectorAbs(worldPos * 0.0000002f);
1699 for (
const Light &light : lights) {
1700 if (light.indirectOnly && !allLight)
1703 QVector3D lightWorldPos;
1704 float dist = std::numeric_limits<
float>::infinity();
1705 float attenuation = 1.0f;
1706 if (light.type == Light::Directional) {
1707 lightWorldPos = worldPos - light.direction;
1709 lightWorldPos = light.worldPos;
1710 dist = (worldPos - lightWorldPos).length();
1712 / (light.constantAttenuation + light.linearAttenuation * dist + light.quadraticAttenuation * dist * dist);
1713 if (light.type == Light::Spot) {
1714 const float spotAngle = QVector3D::dotProduct((worldPos - lightWorldPos).normalized(), light.direction.normalized());
1715 if (spotAngle > light.cosConeAngle) {
1717 const float edge0 = light.cosConeAngle;
1718 const float edge1 = light.cosInnerConeAngle;
1719 const float x = spotAngle;
1720 const float t = qBound(0.0f, (x - edge0) / (edge1 - edge0), 1.0f);
1721 const float spotFactor = t * t * (3.0f - 2.0f * t);
1722 attenuation *= spotFactor;
1729 const QVector3D L = (lightWorldPos - worldPos).normalized();
1730 const float energy = qMax(0.0f, QVector3D::dotProduct(normal, L)) * attenuation;
1731 if (qFuzzyIsNull(energy))
1735 RayHit ray(worldPos, L, options.bias, dist);
1736 const bool lightReachable = !ray.intersect(rscene);
1737 if (lightReachable) {
1738 directLight += light.color * energy;
1745QByteArray QSSGLightmapperPrivate::dilate(
const QSize &pixelSize,
const QByteArray &image)
1747 QSSGRhiContext *rhiCtx = rhiCtxInterface->rhiContext().get();
1748 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
1749 QRhi *rhi = rhiCtx->rhi();
1750 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
1752 const QRhiViewport viewport(0, 0,
float(pixelSize.width()),
float(pixelSize.height()));
1754 std::unique_ptr<QRhiTexture> lightmapTex(rhi->newTexture(QRhiTexture::RGBA32F, pixelSize));
1755 if (!lightmapTex->create()) {
1756 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Failed to create FP32 texture for postprocessing"));
1759 std::unique_ptr<QRhiTexture> dilatedLightmapTex(
1760 rhi->newTexture(QRhiTexture::RGBA32F, pixelSize, 1, QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
1761 if (!dilatedLightmapTex->create()) {
1762 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning,
1763 QStringLiteral(
"Failed to create FP32 dest. texture for postprocessing"));
1766 QRhiTextureRenderTargetDescription rtDescDilate(dilatedLightmapTex.get());
1767 std::unique_ptr<QRhiTextureRenderTarget> rtDilate(rhi->newTextureRenderTarget(rtDescDilate));
1768 std::unique_ptr<QRhiRenderPassDescriptor> rpDescDilate(rtDilate->newCompatibleRenderPassDescriptor());
1769 rtDilate->setRenderPassDescriptor(rpDescDilate.get());
1770 if (!rtDilate->create()) {
1771 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning,
1772 QStringLiteral(
"Failed to create postprocessing texture render target"));
1775 QRhiResourceUpdateBatch *resUpd = rhi->nextResourceUpdateBatch();
1776 QRhiTextureSubresourceUploadDescription lightmapTexUpload(image.constData(), image.size());
1777 resUpd->uploadTexture(lightmapTex.get(), QRhiTextureUploadDescription({ 0, 0, lightmapTexUpload }));
1778 QSSGRhiShaderResourceBindingList bindings;
1779 QRhiSampler *nearestSampler = rhiCtx->sampler(
1780 { QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None, QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
1781 bindings.addTexture(0, QRhiShaderResourceBinding::FragmentStage, lightmapTex.get(), nearestSampler);
1782 renderer->rhiQuadRenderer()->prepareQuad(rhiCtx, resUpd);
1783 const auto &shaderCache = renderer->contextInterface()->shaderCache();
1784 const auto &lmDilatePipeline = shaderCache->getBuiltInRhiShaders().getRhiLightmapDilateShader();
1785 if (!lmDilatePipeline) {
1786 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Failed to load shaders"));
1789 QSSGRhiGraphicsPipelineState dilatePs;
1790 dilatePs.viewport = viewport;
1791 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(dilatePs, lmDilatePipeline.get());
1792 renderer->rhiQuadRenderer()->recordRenderQuadPass(rhiCtx, &dilatePs, rhiCtxD->srb(bindings), rtDilate.get(), QSSGRhiQuadRenderer::UvCoords);
1793 resUpd = rhi->nextResourceUpdateBatch();
1794 QRhiReadbackResult dilateReadResult;
1795 resUpd->readBackTexture({ dilatedLightmapTex.get() }, &dilateReadResult);
1796 cb->resourceUpdate(resUpd);
1801 return dilateReadResult.data;
1804QVector<QVector3D> QSSGLightmapperPrivate::computeDirectLight(
int lmIdx)
1806 const QSSGBakedLightingModel &lm(bakedLightingModels[lmIdx]);
1812 if (!lm.model->castsShadows)
1815 const DrawInfo &drawInfo(drawInfos[lmIdx]);
1816 const char *vbase = drawInfo.vertexData.constData();
1817 const quint32 *ibase =
reinterpret_cast<
const quint32 *>(drawInfo.indexData.constData());
1819 const QSize sz = drawInfo.lightmapSize;
1820 const int w = sz.width();
1821 const int h = sz.height();
1822 constexpr int padding = GAUSS_HALF_KERNEL_SIZE;
1823 const int numPixelsFinal = w * h;
1825 QVector<QVector3D> grid(numPixelsFinal);
1826 QVector<quint32> mask(numPixelsFinal, PIXEL_VOID);
1829 const QVector<ModelTexel>& texels = modelTexels[lmIdx];
1830 for (
int pixelI = 0; pixelI < numPixelsFinal; ++pixelI) {
1831 const auto &entry = texels[pixelI];
1832 if (!entry.isValid())
1834 mask[pixelI] = PIXEL_UNSET;
1835 grid[pixelI] = sampleDirectLight(entry.worldPos, entry.normal,
false);
1838 if (std::all_of(grid.begin(), grid.end(), [](
const QVector3D &v) {
return v.isNull(); })) {
1842 floodFill(
reinterpret_cast<quint32 *>(mask.data()), h, w);
1845 constexpr int maxTileSize = MAX_TILE_SIZE / DIRECT_MAP_UPSCALE_FACTOR;
1846 const int numTilesX = (w + maxTileSize - 1) / maxTileSize;
1847 const int numTilesY = (h + maxTileSize - 1) / maxTileSize;
1850 for (
int tileY = 0; tileY < numTilesY; ++tileY) {
1851 for (
int tileX = 0; tileX < numTilesX; ++tileX) {
1853 const int startX = tileX * maxTileSize;
1854 const int startY = tileY * maxTileSize;
1856 const int tileWidth = qMin(maxTileSize, w - startX);
1857 const int tileHeight = qMin(maxTileSize, h - startY);
1859 const int currentTileWidth = tileWidth + 2 * padding;
1860 const int currentTileHeight = tileHeight + 2 * padding;
1862 const int wExp = currentTileWidth * DIRECT_MAP_UPSCALE_FACTOR;
1863 const int hExp = currentTileHeight * DIRECT_MAP_UPSCALE_FACTOR;
1864 const int numPixelsExpanded = wExp * hExp;
1866 QVector<quint32> maskTile(numPixelsExpanded, PIXEL_VOID);
1867 QVector<QVector3D> gridTile(numPixelsExpanded);
1870 const int pixelStartX = startX - padding;
1871 const int pixelStartY = startY - padding;
1872 const int pixelEndX = startX + tileWidth + padding;
1873 const int pixelEndY = startY + tileHeight + padding;
1875 const float minU = pixelStartX /
double(w);
1876 const float maxV = 1.0 - pixelStartY /
double(h);
1877 const float maxU = pixelEndX /
double(w);
1878 const float minV = 1.0 - pixelEndY /
double(h);
1881 QByteArray worldPositionsBuffer;
1882 QByteArray normalsBuffer;
1884 QSSGLightmapperPrivate::RasterResult raster = rasterizeLightmap(lmIdx,
1886 QVector2D(minU, minV),
1887 QVector2D(maxU, maxV));
1888 if (!raster.success)
1890 Q_ASSERT(raster.width * raster.height == numPixelsExpanded);
1891 worldPositionsBuffer = raster.worldPositions;
1892 normalsBuffer = raster.normals;
1895 QVector4D *worldPositions =
reinterpret_cast<QVector4D *>(worldPositionsBuffer.data());
1896 QVector4D *normals =
reinterpret_cast<QVector4D *>(normalsBuffer.data());
1898 for (
int pixelI = 0; pixelI < numPixelsExpanded; ++pixelI) {
1899 QVector3D position = worldPositions[pixelI].toVector3D();
1900 QVector3D normal = normals[pixelI].toVector3D();
1901 if (normal.isNull()) {
1902 maskTile[pixelI] = PIXEL_VOID;
1906 maskTile[pixelI] = PIXEL_UNSET;
1907 gridTile[pixelI] += sampleDirectLight(position, normal,
false);
1910 floodFill(
reinterpret_cast<quint32 *>(maskTile.data()), hExp, wExp);
1911 gridTile = applyGaussianBlur(gridTile, maskTile, wExp, hExp, 3.f);
1913 const int endX = qMin(w, startX + tileWidth);
1914 const int endY = qMin(h, startY + tileHeight);
1918 for (
int y = startY; y < endY; ++y) {
1919 const int ySrc = (padding + y - startY) * DIRECT_MAP_UPSCALE_FACTOR;
1920 Q_ASSERT(ySrc < hExp);
1921 for (
int x = startX; x < endX; ++x) {
1922 const int xSrc = (padding + x - startX) * DIRECT_MAP_UPSCALE_FACTOR;
1923 Q_ASSERT(xSrc < wExp);
1925 if (mask[y * w + x] == PIXEL_VOID)
1928 const int dstPixelI = y * w + x;
1931 for (
int sY = 0; sY < DIRECT_MAP_UPSCALE_FACTOR; ++sY) {
1932 for (
int sX = 0; sX < DIRECT_MAP_UPSCALE_FACTOR; ++sX) {
1933 int srcPixelI = (ySrc + sY) * wExp + (xSrc + sX);
1934 Q_ASSERT(srcPixelI < numPixelsExpanded);
1935 if (maskTile[srcPixelI] == PIXEL_VOID)
1937 average += gridTile[srcPixelI];
1945 grid[dstPixelI] = average / hits;
1950 progressTracker.directTileDone();
1954 QHash<Edge, EdgeUV> edgeUVMap;
1955 QVector<SeamUV> seams;
1957 for (SubMeshInfo &subMeshInfo : subMeshInfos[lmIdx]) {
1958 QVector<std::array<quint32, 3>> triangles;
1959 QVector<QVector3D> positions;
1960 QVector<QVector3D> normals;
1961 QVector<QVector2D> uvs;
1963 triangles.reserve(subMeshInfo.count / 3);
1964 positions.reserve(subMeshInfo.count);
1965 normals.reserve(subMeshInfo.count);
1966 uvs.reserve(subMeshInfo.count);
1968 for (quint32 i = 0; i < subMeshInfo.count / 3; ++i)
1969 triangles.push_back({ i * 3, i * 3 + 1, i * 3 + 2 });
1971 for (quint32 i = 0; i < subMeshInfo.count; ++i) {
1972 const quint32 idx = *(ibase + subMeshInfo.offset + i);
1973 const float *src =
reinterpret_cast<
const float *>(vbase + idx * drawInfo.vertexStride + drawInfo.positionOffset);
1977 positions.push_back(QVector3D(x, y, z));
1980 for (quint32 i = 0; i < subMeshInfo.count; ++i) {
1981 const quint32 idx = *(ibase + subMeshInfo.offset + i);
1982 const float *src =
reinterpret_cast<
const float *>(vbase + idx * drawInfo.vertexStride + drawInfo.normalOffset);
1986 normals.push_back(QVector3D(x, y, z));
1989 for (quint32 i = 0; i < subMeshInfo.count; ++i) {
1990 const quint32 idx = *(ibase + subMeshInfo.offset + i);
1991 const float *src =
reinterpret_cast<
const float *>(vbase + idx * drawInfo.vertexStride + drawInfo.lightmapUVOffset);
1994 uvs.push_back(QVector2D(x, 1.0f - y));
1997 for (
const auto [i0, i1, i2] : std::as_const(triangles)) {
1998 const QVector3D triVert[3] = { positions[i0], positions[i1], positions[i2] };
1999 const QVector3D triNorm[3] = { normals[i0], normals[i1], normals[i2] };
2000 const QVector2D triUV[3] = { uvs[i0], uvs[i1], uvs[i2] };
2002 for (
int i = 0; i < 3; ++i) {
2004 int i1 = (i + 1) % 3;
2005 if (vectorLessThan(triVert[i1], triVert[i0]))
2008 const Edge e = { { triVert[i0], triVert[i1] }, { triNorm[i0], triNorm[i1] } };
2009 const EdgeUV edgeUV = { { triUV[i0], triUV[i1] } };
2010 auto it = edgeUVMap.find(e);
2011 if (it == edgeUVMap.end()) {
2012 edgeUVMap.insert(e, edgeUV);
2013 }
else if (!qFuzzyCompare(it->uv[0], edgeUV.uv[0]) || !qFuzzyCompare(it->uv[1], edgeUV.uv[1])) {
2015 std::array<QVector2D, 2> eUV = {QVector2D(edgeUV.uv[0][0], 1.0f - edgeUV.uv[0][1]), QVector2D(edgeUV.uv[1][0], 1.0f - edgeUV.uv[1][1])};
2016 std::array<QVector2D, 2> itUV = {QVector2D(it->uv[0][0], 1.0f - it->uv[0][1]), QVector2D(it->uv[1][0], 1.0f - it->uv[1][1])};
2018 seams.append(SeamUV({ { eUV, itUV } }));
2029 QByteArray workBuf(grid.size() *
sizeof(QVector3D), Qt::Uninitialized);
2030 for (
int blendIter = 0; blendIter < LM_SEAM_BLEND_ITER_COUNT; ++blendIter) {
2031 memcpy(workBuf.data(), grid.constData(), grid.size() *
sizeof(QVector3D));
2032 for (
int seamIdx = 0, end = seams.size(); seamIdx != end; ++seamIdx) {
2033 const SeamUV &seam(seams[seamIdx]);
2034 blendLine(seam.uv[0][0],
2038 reinterpret_cast<
const float *>(workBuf.data()),
2039 reinterpret_cast<
float *>(grid.data()),
2042 blendLine(seam.uv[1][0],
2046 reinterpret_cast<
const float *>(workBuf.data()),
2047 reinterpret_cast<
float *>(grid.data()),
2058static inline float uniformRand(quint32 &state)
2060 state ^= state << 13;
2061 state ^= state >> 17;
2062 state ^= state << 5;
2063 return float(state) /
float(UINT32_MAX);
2066static inline QVector3D cosWeightedHemisphereSample(quint32 &state)
2068 const float r1 = uniformRand(state);
2069 const float r2 = uniformRand(state) * 2.0f *
float(M_PI);
2070 const float sqr1 = std::sqrt(r1);
2071 const float sqr1m = std::sqrt(1.0f - r1);
2072 return QVector3D(sqr1 * std::cos(r2), sqr1 * std::sin(r2), sqr1m);
2075QVector<QVector3D> QSSGLightmapperPrivate::computeIndirectLight(
int lmIdx,
int wgCount,
int wgSizePerGroup)
2077 const QVector<ModelTexel>& texels = modelTexels[lmIdx];
2078 QVector<QVector3D> result;
2079 result.resize(texels.size());
2081 QVector<QFuture<QVector3D>> wg(wgCount);
2083 for (
int i = 0; i < texels.size(); ++i) {
2084 const ModelTexel& lmPix = texels[i];
2085 if (!lmPix.isValid())
2088 ++indirectTexelsDone;
2089 for (
int wgIdx = 0; wgIdx < wgCount; ++wgIdx) {
2090 const int beginIdx = wgIdx * wgSizePerGroup;
2091 const int endIdx = qMin(beginIdx + wgSizePerGroup, options.indirectLightSamples);
2093 wg[wgIdx] = QtConcurrent::run([
this, wgIdx, beginIdx, endIdx, &lmPix] {
2095 quint32 state = QRandomGenerator(wgIdx).generate();
2096 for (
int sampleIdx = beginIdx; sampleIdx < endIdx; ++sampleIdx) {
2097 QVector3D position = lmPix.worldPos;
2098 QVector3D normal = lmPix.normal;
2099 QVector3D throughput(1.0f, 1.0f, 1.0f);
2100 QVector3D sampleResult;
2102 for (
int bounce = 0; bounce < options.indirectLightBounces; ++bounce) {
2103 if (options.useAdaptiveBias)
2104 position += vectorSign(normal) * vectorAbs(position * 0.0000002f);
2107 const QVector3D sample = cosWeightedHemisphereSample(state);
2110 const QVector3D v0 = qFuzzyCompare(qAbs(normal.z()), 1.0f)
2111 ? QVector3D(0.0f, 1.0f, 0.0f)
2112 : QVector3D(0.0f, 0.0f, 1.0f);
2113 const QVector3D tangent = QVector3D::crossProduct(v0, normal).normalized();
2114 const QVector3D bitangent = QVector3D::crossProduct(tangent, normal).normalized();
2115 QVector3D direction(
2116 tangent.x() * sample.x() + bitangent.x() * sample.y() + normal.x() * sample.z(),
2117 tangent.y() * sample.x() + bitangent.y() * sample.y() + normal.y() * sample.z(),
2118 tangent.z() * sample.x() + bitangent.z() * sample.y() + normal.z() * sample.z());
2119 direction.normalize();
2122 const float NdotL = qMax(0.0f, QVector3D::dotProduct(normal, direction));
2123 const float pdf = NdotL /
float(M_PI);
2124 if (qFuzzyIsNull(pdf))
2128 RayHit ray(position, direction, options.bias);
2129 if (!ray.intersect(rscene))
2133 const ModelTexel &hitEntry = texelForLightmapUV(ray.rayhit.hit.geomID,
2138 const bool hitBackFace = QVector3D::dotProduct(hitEntry.normal, direction) > 0.0f;
2143 const QVector3D brdf = hitEntry.baseColor.toVector3D() /
float(M_PI);
2146 sampleResult += throughput * hitEntry.emission;
2147 throughput *= brdf * NdotL / pdf;
2148 QVector3D directLight = sampleDirectLight(hitEntry.worldPos, hitEntry.normal,
true);
2149 sampleResult += throughput * directLight;
2153 const float p = qMax(qMax(throughput.x(), throughput.y()), throughput.z());
2154 if (p < uniformRand(state))
2161 position = hitEntry.worldPos;
2162 normal = hitEntry.normal;
2165 wgResult += sampleResult;
2171 QVector3D totalIndirect;
2172 for (
const auto &future : wg)
2173 totalIndirect += future.result();
2175 result[i] += totalIndirect * options.indirectLightFactor / options.indirectLightSamples;
2177 if (bakingControl.cancelled)
2180 progressTracker.indirectTexelDone(indirectTexelsDone, indirectTexelsTotal);
2186static QString stripQrcPrefix(
const QString &path)
2188 QString result = path;
2189 if (result.startsWith(QStringLiteral(
":/")))
2190 result.remove(0, 2);
2196static bool createDirectory(
const QString &filePath)
2198 QFileInfo fileInfo(filePath);
2199 QString dirPath = fileInfo.path();
2202 if (dir.exists(dirPath))
2205 if (!dir.mkpath(dirPath))
2211static bool isValidSavePath(
const QString &path) {
2212 const QFileInfo info = QFileInfo(path);
2213 if (!info.exists()) {
2214 return QFileInfo(info.dir().path()).isWritable();
2216 return info.isWritable() && !info.isDir();
2219static inline QString indexToMeshKey(
int index)
2221 return QStringLiteral(
"_mesh_%1").arg(index);
2224bool QSSGLightmapperPrivate::storeMeshes(QSharedPointer<QSSGLightmapWriter> writer)
2226 if (!isValidSavePath(outputPath)) {
2227 sendOutputInfo(QSSGLightmapper::BakingStatus::Failed,
2228 QStringLiteral(
"Source path %1 is not a writable location").arg(outputPath));
2232 for (
int i = 0; i < meshes.size(); ++i) {
2233 if (!writer->writeData(indexToMeshKey(i), QSSGLightmapIODataTag::Mesh, meshes[i]))
2240bool QSSGLightmapperPrivate::storeSceneMetadata(QSharedPointer<QSSGLightmapWriter> writer)
2242 QVariantMap metadata;
2244 metadata[QStringLiteral(
"qt_version")] = QString::fromUtf8(QT_VERSION_STR);
2245 metadata[QStringLiteral(
"bake_start_time")] = bakeStartTime;
2246 metadata[QStringLiteral(
"bake_end_time")] = QDateTime::currentMSecsSinceEpoch();
2248 QVariantMap metadata2;
2249 metadata2[QStringLiteral(
"opacityThreshold")] = options.opacityThreshold;
2250 metadata2[QStringLiteral(
"bias")] = options.bias;
2251 metadata2[QStringLiteral(
"useAdaptiveBias")] = options.useAdaptiveBias;
2252 metadata2[QStringLiteral(
"indirectLightEnabled")] = options.indirectLightEnabled;
2253 metadata2[QStringLiteral(
"indirectLightSamples")] = options.indirectLightSamples;
2254 metadata2[QStringLiteral(
"indirectLightWorkgroupSize")] = options.indirectLightWorkgroupSize;
2255 metadata2[QStringLiteral(
"indirectLightBounces")] = options.indirectLightBounces;
2256 metadata2[QStringLiteral(
"indirectLightFactor")] = options.indirectLightFactor;
2257 metadata2[QStringLiteral(
"denoiseSigma")] = options.sigma;
2258 metadata2[QStringLiteral(
"texelsPerUnit")] = options.texelsPerUnit;
2260 metadata[QStringLiteral(
"options")] = metadata2;
2261 return writer->writeMap(QString::fromUtf8(KEY_SCENE_METADATA), QSSGLightmapIODataTag::SceneMetadata, metadata);
2264bool QSSGLightmapperPrivate::storeMetadata(
int lmIdx, QSharedPointer<QSSGLightmapWriter> writer)
2266 const QSSGBakedLightingModel &lm(bakedLightingModels[lmIdx]);
2267 const DrawInfo &drawInfo(drawInfos[lmIdx]);
2269 QVariantMap metadata;
2270 metadata[QStringLiteral(
"width")] = drawInfos[lmIdx].lightmapSize.width();
2271 metadata[QStringLiteral(
"height")] = drawInfos[lmIdx].lightmapSize.height();
2272 metadata[QStringLiteral(
"mesh_key")] = indexToMeshKey(drawInfo.meshIndex);
2274 return writer->writeMap(lm.model->lightmapKey, QSSGLightmapIODataTag::Metadata, metadata);
2277bool QSSGLightmapperPrivate::storeScale(
int lmIdx, QSharedPointer<QSSGLightmapWriter> writer)
2279 const QSSGBakedLightingModel &lm(bakedLightingModels[lmIdx]);
2280 const DrawInfo &drawInfo(drawInfos[lmIdx]);
2283 QDataStream stream(&buffer, QIODevice::WriteOnly);
2284 stream << drawInfo.scale;
2285 return writer->writeData(lm.model->lightmapKey, QSSGLightmapIODataTag::OriginalScale, buffer);
2288bool QSSGLightmapperPrivate::storeDirectLightData(
int lmIdx,
const QVector<QVector3D> &directLight, QSharedPointer<QSSGLightmapWriter> writer)
2290 const QSSGBakedLightingModel &lm(bakedLightingModels[lmIdx]);
2291 const int numTexels = modelTexels[lmIdx].size();
2293 QByteArray directFP32(numTexels * 4 *
sizeof(
float), Qt::Uninitialized);
2294 float *directFloatPtr =
reinterpret_cast<
float *>(directFP32.data());
2296 for (
int i = 0; i < numTexels; ++i) {
2297 const auto &lmPix = modelTexels[lmIdx][i];
2298 if (lmPix.isValid()) {
2299 *directFloatPtr++ = directLight[i].x();
2300 *directFloatPtr++ = directLight[i].y();
2301 *directFloatPtr++ = directLight[i].z();
2302 *directFloatPtr++ = 1.0f;
2304 *directFloatPtr++ = 0.0f;
2305 *directFloatPtr++ = 0.0f;
2306 *directFloatPtr++ = 0.0f;
2307 *directFloatPtr++ = 0.0f;
2311 const QByteArray dilated = dilate(drawInfos[lmIdx].lightmapSize, directFP32);
2313 if (dilated.isEmpty())
2316 writer->writeF32Image(lm.model->lightmapKey, QSSGLightmapIODataTag::Texture_Direct, dilated);
2321bool QSSGLightmapperPrivate::storeIndirectLightData(
int lmIdx,
const QVector<QVector3D> &indirectLight, QSharedPointer<QSSGLightmapWriter> writer)
2323 const QSSGBakedLightingModel &lm(bakedLightingModels[lmIdx]);
2324 const int numTexels = modelTexels[lmIdx].size();
2326 QByteArray lightmapFP32(numTexels * 4 *
sizeof(
float), Qt::Uninitialized);
2327 float *lightmapFloatPtr =
reinterpret_cast<
float *>(lightmapFP32.data());
2329 for (
int i = 0; i < numTexels; ++i) {
2330 const auto &lmPix = modelTexels[lmIdx][i];
2331 if (lmPix.isValid()) {
2332 *lightmapFloatPtr++ = indirectLight[i].x();
2333 *lightmapFloatPtr++ = indirectLight[i].y();
2334 *lightmapFloatPtr++ = indirectLight[i].z();
2335 *lightmapFloatPtr++ = 1.0f;
2337 *lightmapFloatPtr++ = 0.0f;
2338 *lightmapFloatPtr++ = 0.0f;
2339 *lightmapFloatPtr++ = 0.0f;
2340 *lightmapFloatPtr++ = 0.0f;
2344 QByteArray dilated = dilate(drawInfos[lmIdx].lightmapSize, lightmapFP32);
2346 if (dilated.isEmpty())
2352 const DrawInfo &drawInfo(drawInfos[lmIdx]);
2353 const char *vbase = drawInfo.vertexData.constData();
2354 const quint32 *ibase =
reinterpret_cast<
const quint32 *>(drawInfo.indexData.constData());
2358 qsizetype assembledVertexCount = 0;
2359 for (SubMeshInfo &subMeshInfo : subMeshInfos[lmIdx])
2360 assembledVertexCount += subMeshInfo.count;
2361 QVector<QVector3D> smPos(assembledVertexCount);
2362 QVector<QVector3D> smNormal(assembledVertexCount);
2363 QVector<QVector2D> smCoord(assembledVertexCount);
2364 qsizetype vertexIdx = 0;
2365 for (SubMeshInfo &subMeshInfo : subMeshInfos[lmIdx]) {
2366 for (quint32 i = 0; i < subMeshInfo.count; ++i) {
2367 const quint32 idx = *(ibase + subMeshInfo.offset + i);
2368 const float *src =
reinterpret_cast<
const float *>(vbase + idx * drawInfo.vertexStride + drawInfo.positionOffset);
2372 smPos[vertexIdx] = QVector3D(x, y, z);
2373 src =
reinterpret_cast<
const float *>(vbase + idx * drawInfo.vertexStride + drawInfo.normalOffset);
2377 smNormal[vertexIdx] = QVector3D(x, y, z);
2378 src =
reinterpret_cast<
const float *>(vbase + idx * drawInfo.vertexStride + drawInfo.lightmapUVOffset);
2381 smCoord[vertexIdx] = QVector2D(x, y);
2386 QHash<Edge, EdgeUV> edgeUVMap;
2387 QVector<SeamUV> seams;
2388 for (vertexIdx = 0; vertexIdx < assembledVertexCount; vertexIdx += 3) {
2389 QVector3D triVert[3] = { smPos[vertexIdx], smPos[vertexIdx + 1], smPos[vertexIdx + 2] };
2390 QVector3D triNorm[3] = { smNormal[vertexIdx], smNormal[vertexIdx + 1], smNormal[vertexIdx + 2] };
2391 QVector2D triUV[3] = { smCoord[vertexIdx], smCoord[vertexIdx + 1], smCoord[vertexIdx + 2] };
2393 for (
int i = 0; i < 3; ++i) {
2395 int i1 = (i + 1) % 3;
2396 if (vectorLessThan(triVert[i1], triVert[i0]))
2400 { triVert[i0], triVert[i1] },
2401 { triNorm[i0], triNorm[i1] }
2403 const EdgeUV edgeUV = { { triUV[i0], triUV[i1] } };
2404 auto it = edgeUVMap.find(e);
2405 if (it == edgeUVMap.end()) {
2406 edgeUVMap.insert(e, edgeUV);
2407 }
else if (!qFuzzyCompare(it->uv[0], edgeUV.uv[0]) || !qFuzzyCompare(it->uv[1], edgeUV.uv[1])) {
2409 seams.append(SeamUV({ { edgeUV.uv, it->uv } }));
2417 QByteArray workBuf(dilated.size(), Qt::Uninitialized);
2418 for (
int blendIter = 0; blendIter < LM_SEAM_BLEND_ITER_COUNT; ++blendIter) {
2419 memcpy(workBuf.data(), dilated.constData(), dilated.size());
2420 for (
int seamIdx = 0, end = seams.size(); seamIdx != end; ++seamIdx) {
2421 const SeamUV &seam(seams[seamIdx]);
2422 blendLine(seam.uv[0][0], seam.uv[0][1],
2423 seam.uv[1][0], seam.uv[1][1],
2424 reinterpret_cast<
const float *>(workBuf.data()),
2425 reinterpret_cast<
float *>(dilated.data()),
2426 drawInfos[lmIdx].lightmapSize);
2427 blendLine(seam.uv[1][0], seam.uv[1][1],
2428 seam.uv[0][0], seam.uv[0][1],
2429 reinterpret_cast<
const float *>(workBuf.data()),
2430 reinterpret_cast<
float *>(dilated.data()),
2431 drawInfos[lmIdx].lightmapSize);
2435 writer->writeF32Image(lm.model->lightmapKey, QSSGLightmapIODataTag::Texture_Indirect, dilated);
2440bool QSSGLightmapperPrivate::storeMaskImage(
int lmIdx, QSharedPointer<QSSGLightmapWriter> writer)
2442 constexpr quint32 PIXEL_VOID = 0;
2443 constexpr quint32 PIXEL_UNSET = -1;
2445 const QSSGBakedLightingModel &lm(bakedLightingModels[lmIdx]);
2446 const int numTexels = modelTexels[lmIdx].size();
2448 QByteArray mask(numTexels *
sizeof(quint32), Qt::Uninitialized);
2449 quint32 *maskUIntPtr =
reinterpret_cast<quint32 *>(mask.data());
2451 for (
int i = 0; i < numTexels; ++i) {
2452 *maskUIntPtr++ = modelTexels[lmIdx][i].isValid() ? PIXEL_UNSET : PIXEL_VOID;
2455 const int rows = drawInfos[lmIdx].lightmapSize.height();
2456 const int cols = drawInfos[lmIdx].lightmapSize.width();
2461 floodFill(
reinterpret_cast<quint32 *>(mask.data()), rows, cols);
2463 writer->writeF32Image(lm.model->lightmapKey, QSSGLightmapIODataTag::Mask, mask);
2468bool QSSGLightmapperPrivate::denoiseLightmaps()
2470 QElapsedTimer denoiseTimer;
2471 denoiseTimer.start();
2474 const QString inPath = QFileInfo(outputPath + QStringLiteral(
".raw")).absoluteFilePath();
2475 QSharedPointer<QSSGLightmapLoader> tmpFile = QSSGLightmapLoader::open(inPath);
2477 sendOutputInfo(QSSGLightmapper::BakingStatus::Error, QStringLiteral(
"Could not read file '%1'").arg(inPath));
2482 const QString outPath = QFileInfo(outputPath).absoluteFilePath();
2483 QSharedPointer<QSSGLightmapWriter> finalFile = QSSGLightmapWriter::open(outPath);
2485 sendOutputInfo(QSSGLightmapper::BakingStatus::Error, QStringLiteral(
"Could not read file '%1'").arg(outPath));
2489 QSet<QString> lightmapKeys;
2490 const auto keys = tmpFile->getKeys();
2491 for (
const auto &[key, tag] : keys) {
2492 if (tag == QSSGLightmapIODataTag::SceneMetadata)
continue;
2494 if (tag != QSSGLightmapIODataTag::Texture_Direct && tag != QSSGLightmapIODataTag::Texture_Indirect
2495 && tag != QSSGLightmapIODataTag::Mask) {
2497 finalFile->writeData(key, tag, tmpFile->readData(key, tag));
2498 }
else if (tag == QSSGLightmapIODataTag::Texture_Direct) {
2499 lightmapKeys.insert(key);
2503 QRhi *rhi = rhiCtxInterface->rhiContext()->rhi();
2505 if (!rhi->isFeatureSupported(QRhi::Compute)) {
2506 qFatal(
"Compute is not supported, denoising disabled");
2510 const int bakedLightingModelCount = lightmapKeys.size();
2511 if (bakedLightingModelCount == 0)
2515 if (QFile f(QStringLiteral(
":/res/rhishaders/nlm_denoise.comp.qsb")); f.open(QIODevice::ReadOnly)) {
2516 shader = QShader::fromSerialized(f.readAll());
2518 qFatal() <<
"Could not find denoise shader";
2521 Q_ASSERT(shader.isValid());
2523 QVariantMap sceneMetadata = tmpFile->readMap(QString::fromUtf8(KEY_SCENE_METADATA), QSSGLightmapIODataTag::SceneMetadata);
2524 sceneMetadata[QStringLiteral(
"denoise_start_time")] = QDateTime::currentMSecsSinceEpoch();
2527 for (
const QString &key : std::as_const(lightmapKeys)) {
2529 auto incrementTracker = QScopeGuard([
this, lmIdx, bakedLightingModelCount]() {
2530 progressTracker.denoisedModelDone(lmIdx + 1, bakedLightingModelCount);
2534 sendOutputInfo(QSSGLightmapper::BakingStatus::Info,
2535 QStringLiteral(
"[%2/%3] denoising '%1'").arg(key).arg(lmIdx + 1).arg(bakedLightingModelCount));
2537 QVariantMap metadata = tmpFile->readMap(key, QSSGLightmapIODataTag::Metadata);
2538 QByteArray indirect = tmpFile->readF32Image(key, QSSGLightmapIODataTag::Texture_Indirect);
2539 QByteArray direct = tmpFile->readF32Image(key, QSSGLightmapIODataTag::Texture_Direct);
2540 QByteArray mask = tmpFile->readU32Image(key, QSSGLightmapIODataTag::Mask);
2542 if (!metadata.contains(QStringLiteral(
"width")) || !metadata.contains(QStringLiteral(
"height"))
2543 || indirect.isEmpty() || direct.isEmpty() || mask.isEmpty()) {
2544 sendOutputInfo(QSSGLightmapper::BakingStatus::Error,
2545 QStringLiteral(
"[%2/%3] Failed to denoise '%1'").arg(key).arg(lmIdx + 1).arg(bakedLightingModelCount));
2549 QRhiCommandBuffer *cb =
nullptr;
2550 cb = rhiCtxInterface->rhiContext()->commandBuffer();
2553 QRhiResourceUpdateBatch *u = rhi->nextResourceUpdateBatch();
2556 const int w = metadata[QStringLiteral(
"width")].toInt();
2557 const int h = metadata[QStringLiteral(
"height")].toInt();
2558 const QSize size(w, h);
2559 const int numPixels = w * h;
2561 Q_ASSERT(qsizetype(numPixels *
sizeof(
float) * 4) == indirect.size());
2562 Q_ASSERT(qsizetype(numPixels *
sizeof(
float) * 4) == direct.size());
2563 Q_ASSERT(qsizetype(numPixels *
sizeof(quint32)) == mask.size());
2565 QScopedPointer<QRhiBuffer> buffIn(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, 3 * numPixels *
sizeof(
float)));
2566 QScopedPointer<QRhiBuffer> buffCount(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, numPixels *
sizeof(quint32)));
2567 QScopedPointer<QRhiBuffer> buffOut(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, 3 * numPixels *
sizeof(quint32)));
2568 QScopedPointer<QRhiTexture> texMask(rhi->newTexture(QRhiTexture::RGBA8, size, 1, QRhiTexture::UsedWithLoadStore));
2571 buffCount->create();
2575 u->uploadTexture(texMask.data(), QImage(
reinterpret_cast<
const uchar *>(mask.constData()), w, h, QImage::Format_RGBA8888));
2579 QByteArray inArray(3 * numPixels *
sizeof(
float), 0);
2580 QByteArray count(numPixels *
sizeof(quint32), 0);
2581 QByteArray outArray(3 * numPixels *
sizeof(
float), 0);
2583 QVector3D* inDst =
reinterpret_cast<QVector3D*>(inArray.data());
2584 const QVector4D* indirectSrc =
reinterpret_cast<
const QVector4D*>(indirect.data());
2585 for (
int i = 0; i < numPixels; ++i) {
2586 inDst[i][0] = indirectSrc[i][0] * 256.f;
2587 inDst[i][1] = indirectSrc[i][1] * 256.f;
2588 inDst[i][2] = indirectSrc[i][2] * 256.f;
2590 u->uploadStaticBuffer(buffIn.data(), inArray);
2591 u->uploadStaticBuffer(buffCount.data(), count);
2592 u->uploadStaticBuffer(buffOut.data(), outArray);
2602 settings.sigma = options.sigma;
2604 settings.height = h;
2606 QScopedPointer<QRhiBuffer> settingsBuffer(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer,
sizeof(settings)));
2607 settingsBuffer->create();
2609 u->updateDynamicBuffer(settingsBuffer.data(), 0,
sizeof(settings), &settings);
2611 QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings());
2614 QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::ComputeStage, settingsBuffer.data()),
2615 QRhiShaderResourceBinding::bufferLoad(1, QRhiShaderResourceBinding::ComputeStage, buffIn.data()),
2616 QRhiShaderResourceBinding::imageLoad(2, QRhiShaderResourceBinding::ComputeStage, texMask.data(), 0),
2617 QRhiShaderResourceBinding::bufferLoadStore(3, QRhiShaderResourceBinding::ComputeStage, buffOut.data()),
2618 QRhiShaderResourceBinding::bufferLoadStore(4, QRhiShaderResourceBinding::ComputeStage, buffCount.data())
2622 QScopedPointer<QRhiComputePipeline> pipeline(rhi->newComputePipeline());
2623 pipeline->setShaderStage({ QRhiShaderStage::Compute, shader });
2624 pipeline->setShaderResourceBindings(srb.data());
2627 cb->beginComputePass(u);
2628 cb->setComputePipeline(pipeline.data());
2629 cb->setShaderResources();
2630 constexpr int local_size_x = 8;
2631 constexpr int local_size_y = 8;
2632 constexpr int local_size_z = 1;
2633 cb->dispatch((w + local_size_x - 1) / local_size_x, (h + local_size_y - 1) / local_size_y, local_size_z);
2635 u = rhi->nextResourceUpdateBatch();
2640 QByteArray outCount;
2642 QRhiReadbackResult readResultOut;
2643 readResultOut.completed = [&] {
2644 outOut = readResultOut.data;
2645 Q_ASSERT(outOut.size() == qsizetype(numPixels *
sizeof(quint32) * 3));
2647 QRhiReadbackResult readResultCount;
2648 readResultCount.completed = [&] {
2649 outCount = readResultCount.data;
2650 Q_ASSERT(outCount.size() == qsizetype(numPixels *
sizeof(quint32)));
2653 u->readBackBuffer(buffOut.get(), 0, 3 * numPixels *
sizeof(quint32), &readResultOut);
2654 u->readBackBuffer(buffCount.get(), 0, numPixels *
sizeof(quint32), &readResultCount);
2656 cb->endComputePass(u);
2660 final.resize(indirect.size());
2661 memcpy(final.data(), indirect.data(), indirect.size());
2663 QVector4D* res =
reinterpret_cast<QVector4D*>(final.data());
2664 quint32* ptrRGB =
reinterpret_cast<quint32*>(outOut.data());
2665 quint32* ptrCount =
reinterpret_cast<quint32*>(outCount.data());
2666 for (
int y = 0; y < h; ++y) {
2667 for (
int x = 0; x < w; ++x) {
2668 const int idxDst = y * w + x;
2669 const int idxDst1 = 3 * idxDst;
2670 Q_ASSERT(idxDst1 < numPixels * 3);
2671 quint32 cnt = ptrCount[idxDst];
2673 float r = (ptrRGB[idxDst1] / 256.f) / 1000.f;
2674 float g = (ptrRGB[idxDst1 + 1] / 256.f) / 1000.f;
2675 float b = (ptrRGB[idxDst1 + 2] / 256.f) / 1000.f;
2677 res[idxDst][0] = r / cnt;
2678 res[idxDst][1] = g / cnt;
2679 res[idxDst][2] = b / cnt;
2684 std::array<
float, 4> *imagePtr =
reinterpret_cast<std::array<
float, 4>*>(
const_cast<
char*>(final.data()));
2685 std::array<
float, 4> *directPtr =
reinterpret_cast<std::array<
float, 4>*>(
const_cast<
char*>(direct.data()));
2686 for (
int i = 0; i < numPixels; ++i) {
2687 imagePtr[i][0] += directPtr[i][0];
2688 imagePtr[i][1] += directPtr[i][1];
2689 imagePtr[i][2] += directPtr[i][2];
2691 Q_ASSERT(imagePtr[i][3] == directPtr[i][3]);
2692 Q_ASSERT(imagePtr[i][3] == 1.f || imagePtr[i][3] == 0.f);
2695 finalFile->writeF32Image(key, QSSGLightmapIODataTag::Texture_Final, final);
2698 sceneMetadata[QStringLiteral(
"denoise_end_time")] = QDateTime::currentMSecsSinceEpoch();
2699 auto optionsMap = sceneMetadata[QStringLiteral(
"options")].toMap();
2700 optionsMap[QStringLiteral(
"denoiseSigma")] = options.sigma;
2701 sceneMetadata[QStringLiteral(
"options")] = optionsMap;
2703 finalFile->writeMap(QString::fromUtf8(KEY_SCENE_METADATA), QSSGLightmapIODataTag::SceneMetadata, sceneMetadata);
2705 if (!finalFile->close()) {
2706 sendOutputInfo(QSSGLightmapper::BakingStatus::Error, QStringLiteral(
"Could not save file '%1'").arg(outPath));
2714bool QSSGLightmapperPrivate::userCancelled()
2716 if (bakingControl.cancelled) {
2717 sendOutputInfo(QSSGLightmapper::BakingStatus::Cancelled,
2718 QStringLiteral(
"Cancelled by user"));
2720 return bakingControl.cancelled;
2723void QSSGLightmapperPrivate::sendOutputInfo(QSSGLightmapper::BakingStatus type, std::optional<QString> msg,
bool outputToConsole,
bool outputConsoleTimeRemanining)
2725 if (outputToConsole) {
2726 QString consoleMessage;
2730 case QSSGLightmapper::BakingStatus::None:
2732 case QSSGLightmapper::BakingStatus::Info:
2733 consoleMessage = QStringLiteral(
"[lm] Info");
2735 case QSSGLightmapper::BakingStatus::Error:
2736 consoleMessage = QStringLiteral(
"[lm] Error");
2738 case QSSGLightmapper::BakingStatus::Warning:
2739 consoleMessage = QStringLiteral(
"[lm] Warning");
2741 case QSSGLightmapper::BakingStatus::Cancelled:
2742 consoleMessage = QStringLiteral(
"[lm] Cancelled");
2744 case QSSGLightmapper::BakingStatus::Failed:
2745 consoleMessage = QStringLiteral(
"[lm] Failed");
2747 case QSSGLightmapper::BakingStatus::Complete:
2748 consoleMessage = QStringLiteral(
"[lm] Complete");
2752 if (msg.has_value())
2753 consoleMessage.append(QStringLiteral(
": ") + msg.value());
2754 else if (outputConsoleTimeRemanining) {
2755 const QString timeRemaining = estimatedTimeRemaining >= 0 ? formatDuration(estimatedTimeRemaining,
false)
2756 : QStringLiteral(
"Estimating...");
2757 consoleMessage.append(QStringLiteral(
": Time remaining: ") + timeRemaining);
2760 if (type == QSSGLightmapper::BakingStatus::Error || type == QSSGLightmapper::BakingStatus::Warning)
2761 qWarning() << consoleMessage;
2763 qInfo() << consoleMessage;
2766 if (outputCallback) {
2767 QVariantMap payload;
2768 payload[QStringLiteral(
"status")] = (
int)type;
2769 payload[QStringLiteral(
"stage")] = stage;
2770 payload[QStringLiteral(
"message")] = msg.value_or(QString());
2771 payload[QStringLiteral(
"totalTimeRemaining")] = estimatedTimeRemaining;
2772 payload[QStringLiteral(
"totalProgress")] = totalProgress;
2773 outputCallback(payload, &bakingControl);
2777void QSSGLightmapperPrivate::updateStage(
const QString &newStage)
2779 if (newStage == stage)
2783 if (outputCallback) {
2784 QVariantMap payload;
2785 payload[QStringLiteral(
"stage")] = stage;
2786 outputCallback(payload, &bakingControl);
2790bool QSSGLightmapper::bake()
2792 d->totalTimer.start();
2793 d->bakeStartTime = QDateTime::currentMSecsSinceEpoch();
2795 d->updateStage(QStringLiteral(
"Preparing"));
2796 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral(
"Preparing for bake..."));
2798 if (!isValidSavePath(d->outputPath)) {
2799 d->updateStage(QStringLiteral(
"Failed"));
2800 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed,
2801 QStringLiteral(
"Source path %1 is not a writable location").arg(d->outputPath));
2805 if (d->bakedLightingModels.isEmpty()) {
2806 d->updateStage(QStringLiteral(
"Failed"));
2807 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed, QStringLiteral(
"No Models to bake"));
2813 if (!d->commitGeometry()) {
2814 d->updateStage(QStringLiteral(
"Failed"));
2815 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed, QStringLiteral(
"Baking failed"));
2820 d->initMutex.lock();
2821 d->initCondition.wakeAll();
2822 d->initMutex.unlock();
2824 if (d->userCancelled()) {
2825 d->updateStage(QStringLiteral(
"Cancelled"));
2830 const int bakedLightingModelCount = d->bakedLightingModels.size();
2833 quint32 numDirectTiles = 0;
2834 for (
int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
2835 QSSGBakedLightingModel &lm = d->bakedLightingModels[lmIdx];
2838 if (!lm.model->hasLightmap())
2840 if (!lm.model->castsShadows)
2843 const auto &drawInfo = d->drawInfos[lmIdx];
2844 const QSize sz = drawInfo.lightmapSize;
2845 const int w = sz.width();
2846 const int h = sz.height();
2847 constexpr int maxTileSize = MAX_TILE_SIZE / DIRECT_MAP_UPSCALE_FACTOR;
2848 const int numTilesX = (w + maxTileSize - 1) / maxTileSize;
2849 const int numTilesY = (h + maxTileSize - 1) / maxTileSize;
2851 numDirectTiles += numTilesX * numTilesY;
2854 d->progressTracker.initBake(d->options.indirectLightSamples, d->options.indirectLightBounces);
2855 d->progressTracker.setTotalDirectTiles(numDirectTiles);
2859 if (!d->prepareLightmaps()) {
2860 d->updateStage(QStringLiteral(
"Failed"));
2861 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed, QStringLiteral(
"Baking failed"));
2865 if (d->userCancelled()) {
2866 d->updateStage(QStringLiteral(
"Cancelled"));
2870 if (!d->verifyLights()) {
2871 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed,
2872 QStringLiteral(
"Did not find any lights with baking enabled or any "
2873 "emissive models in the scene."));
2877 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info,
2878 QStringLiteral(
"Total emissive models registered: %1")
2879 .arg(d->emissiveModelCount));
2885 const int wgSizePerGroup = qMax(1, d->options.indirectLightWorkgroupSize);
2886 const int wgCount = (d->options.indirectLightSamples / wgSizePerGroup) + (d->options.indirectLightSamples % wgSizePerGroup ? 1: 0);
2888 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral(
"Sample count: %1, Workgroup size: %2, Max bounces: %3, Multiplier: %4").
2889 arg(d->options.indirectLightSamples).
2890 arg(wgSizePerGroup).
2891 arg(d->options.indirectLightBounces).
2892 arg(d->options.indirectLightFactor));
2896 QSharedPointer<QTemporaryFile> workFile = QSharedPointer<QTemporaryFile>::create(QDir::tempPath() +
"/qt_lightmapper_work_file_XXXXXX"_L1);
2898 QElapsedTimer timer;
2903 d->updateStage(QStringLiteral(
"Storing Metadata"));
2904 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral(
"Storing metadata..."));
2905 auto writer = QSSGLightmapWriter::open(workFile);
2907 for (
int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
2908 if (d->userCancelled()) {
2909 d->updateStage(QStringLiteral(
"Cancelled"));
2912 QSSGBakedLightingModel &lm = d->bakedLightingModels[lmIdx];
2913 if (!lm.model->hasLightmap())
2916 if (!d->storeMetadata(lmIdx, writer)) {
2917 d->updateStage(QStringLiteral(
"Failed"));
2918 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed,
2919 QStringLiteral(
"[%1/%2] Failed to store metadata for '%3'")
2921 .arg(bakedLightingModelCount)
2922 .arg(lm.model->lightmapKey));
2928 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral(
"Storing scale..."));
2929 for (
int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
2930 if (d->userCancelled()) {
2931 d->updateStage(QStringLiteral(
"Cancelled"));
2934 QSSGBakedLightingModel &lm = d->bakedLightingModels[lmIdx];
2935 if (!lm.model->hasLightmap())
2938 if (!d->storeScale(lmIdx, writer)) {
2939 d->updateStage(QStringLiteral(
"failed"));
2940 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed,
2941 QStringLiteral(
"[%1/%2] Failed to store scale for '%3'")
2943 .arg(bakedLightingModelCount)
2944 .arg(lm.model->lightmapKey));
2950 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral(
"Storing mask images..."));
2951 for (
int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
2952 if (d->userCancelled()) {
2953 d->updateStage(QStringLiteral(
"Cancelled"));
2956 QSSGBakedLightingModel &lm = d->bakedLightingModels[lmIdx];
2957 if (!lm.model->hasLightmap())
2960 if (!d->storeMaskImage(lmIdx, writer)) {
2961 d->updateStage(QStringLiteral(
"Failed"));
2962 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed,
2963 QStringLiteral(
"[%1/%2] Failed to store mask for '%3'")
2965 .arg(bakedLightingModelCount)
2966 .arg(lm.model->lightmapKey));
2970 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info,
2971 QStringLiteral(
"Took %1").arg(formatDuration(timer.restart())));
2973 if (d->userCancelled()) {
2974 d->updateStage(QStringLiteral(
"Cancelled"));
2980 d->updateStage(QStringLiteral(
"Computing Direct Light"));
2981 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral(
"Computing direct light..."));
2982 for (
int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
2983 if (d->userCancelled()) {
2984 d->updateStage(QStringLiteral(
"Cancelled"));
2987 QSSGBakedLightingModel &lm = d->bakedLightingModels[lmIdx];
2988 if (!lm.model->hasLightmap())
2992 const QVector<QVector3D> directLight = d->computeDirectLight(lmIdx);
2993 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info,
2994 QStringLiteral(
"[%1/%2] '%3' took %4")
2995 .arg(QString::number(lmIdx + 1),
2996 QString::number(bakedLightingModelCount),
2997 lm.model->lightmapKey,
2998 formatDuration(timer.elapsed())));
3000 if (directLight.empty()) {
3001 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed,
3002 QStringLiteral(
"[%1/%2] Failed to compute for '%3'")
3004 .arg(bakedLightingModelCount)
3005 .arg(lm.model->lightmapKey));
3009 if (!d->storeDirectLightData(lmIdx, directLight, writer)) {
3010 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed,
3011 QStringLiteral(
"[%1/%2] Failed to store data for '%3'")
3013 .arg(bakedLightingModelCount)
3014 .arg(lm.model->lightmapKey));
3019 if (d->userCancelled()) {
3020 d->updateStage(QStringLiteral(
"Cancelled"));
3026 if (d->options.indirectLightEnabled) {
3027 d->indirectTexelsTotal = std::accumulate(d->numValidTexels.begin(), d->numValidTexels.end(), quint64(0));
3028 d->updateStage(QStringLiteral(
"Computing Indirect Light"));
3029 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info,
3030 QStringLiteral(
"Computing indirect light..."));
3031 d->progressTracker.setStage(Stage::Indirect);
3032 for (
int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
3033 if (d->userCancelled()) {
3034 d->updateStage(QStringLiteral(
"Cancelled"));
3037 QSSGBakedLightingModel &lm = d->bakedLightingModels[lmIdx];
3038 if (!lm.model->hasLightmap())
3042 const QVector<QVector3D> indirectLight = d->computeIndirectLight(lmIdx, wgCount, wgSizePerGroup);
3043 if (indirectLight.empty()) {
3044 d->updateStage(QStringLiteral(
"Failed"));
3045 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed,
3046 QStringLiteral(
"[%1/%2] Failed to compute '%3'")
3048 .arg(bakedLightingModelCount)
3049 .arg(lm.model->lightmapKey));
3053 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info,
3054 QStringLiteral(
"[%1/%2] '%3' took %4")
3055 .arg(QString::number(lmIdx + 1),
3056 QString::number(bakedLightingModelCount),
3057 lm.model->lightmapKey,
3058 formatDuration(timer.elapsed())));
3060 if (d->userCancelled()) {
3061 d->updateStage(QStringLiteral(
"Cancelled"));
3065 if (!d->storeIndirectLightData(lmIdx, indirectLight, writer)) {
3066 d->updateStage(QStringLiteral(
"Failed"));
3067 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed,
3068 QStringLiteral(
"[%1/%2] Failed to store data for '%3'")
3070 .arg(bakedLightingModelCount)
3071 .arg(lm.model->lightmapKey));
3079 if (!d->storeMeshes(writer)) {
3080 d->updateStage(QStringLiteral(
"Failed"));
3081 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed, QStringLiteral(
"Failed to store meshes"));
3085 if (d->userCancelled()) {
3086 d->updateStage(QStringLiteral(
"Cancelled"));
3092 d->updateStage(QStringLiteral(
"Storing Scene Metadata"));
3093 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral(
"Storing scene metadata..."));
3094 if (!d->storeSceneMetadata(writer)) {
3095 d->updateStage(QStringLiteral(
"Failed"));
3096 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed,
3097 QStringLiteral(
"Failed to store scene metadata"));
3102 if (!writer->close()) {
3103 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Error,
3104 QStringLiteral(
"Failed to save temp file to %1").arg(workFile->fileName()));
3108 const QString tmpPath = QFileInfo(d->outputPath).absoluteFilePath() +
".raw"_L1;
3109 QFile::remove(tmpPath);
3110 if (!workFile->copy(tmpPath)) {
3111 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Error,
3112 QStringLiteral(
"Failed to copy temp file to %1").arg(tmpPath));
3116 if (d->userCancelled()) {
3117 d->updateStage(QStringLiteral(
"Cancelled"));
3123 d->progressTracker.setStage(Stage::Denoise);
3124 d->updateStage(QStringLiteral(
"Denoising"));
3125 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral(
"Denoising..."));
3127 if (!d->denoiseLightmaps()) {
3128 d->updateStage(QStringLiteral(
"Failed"));
3129 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed, QStringLiteral(
"Denoising failed"));
3132 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral(
"Took %1").arg(formatDuration(timer.elapsed())));
3134 if (d->userCancelled()) {
3135 d->updateStage(QStringLiteral(
"Cancelled"));
3141 d->totalProgress = 1.0;
3142 d->estimatedTimeRemaining = -1;
3143 d->updateStage(QStringLiteral(
"Done"));
3144 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info,
3145 QStringLiteral(
"Baking took %1").arg(formatDuration(d->totalTimer.elapsed())));
3146 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Complete, std::nullopt);
3150bool QSSGLightmapper::denoise() {
3153 d->initMutex.lock();
3154 d->initCondition.wakeAll();
3155 d->initMutex.unlock();
3157 QElapsedTimer totalTimer;
3160 d->progressTracker.initDenoise();
3161 d->progressTracker.setStage(Stage::Denoise);
3162 d->updateStage(
"Denoising"_L1);
3163 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral(
"Denoise starting..."));
3165 if (!d->denoiseLightmaps()) {
3166 d->updateStage(
"Failed"_L1);
3167 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed, QStringLiteral(
"Denoising failed"));
3171 d->totalProgress = 1;
3172 d->updateStage(
"Done"_L1);
3173 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral(
"Denoising took %1 ms").arg(totalTimer.elapsed()));
3174 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Complete, std::nullopt);
3178void QSSGLightmapper::run(QOffscreenSurface *fallbackSurface)
3180 auto releaseMainThread = qScopeGuard([&] {
3181 d->initMutex.lock();
3182 d->initCondition.wakeAll();
3183 d->initMutex.unlock();
3186 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info,
3187 QStringLiteral(
"Total models registered: %1").arg(d->bakedLightingModels.size()));
3189 if (d->bakedLightingModels.isEmpty()) {
3190 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed, QStringLiteral(
"No Models to bake"));
3194 d->outputPath = stripQrcPrefix(d->options.source);
3196 if (!createDirectory(d->outputPath)) {
3197 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed, QStringLiteral(
"Failed to create output directory"));
3201 if (!isValidSavePath(d->outputPath)) {
3202 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed,
3203 QStringLiteral(
"Source path %1 is not a writable location").arg(d->outputPath));
3207 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral(
"Source path: %1").arg(d->outputPath));
3208 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral(
"Output path: %1").arg(d->outputPath));
3210 const QRhi::Flags flags = QRhi::EnableTimestamps | QRhi::EnableDebugMarkers;
3211#if QT_CONFIG(vulkan)
3212 std::unique_ptr<QVulkanInstance> vulkanInstance;
3214 std::unique_ptr<QRhi> rhi;
3216 switch (d->rhiBackend) {
3217 case QRhi::Vulkan: {
3218#if QT_CONFIG(vulkan)
3219 vulkanInstance = std::make_unique<QVulkanInstance>();
3220 vulkanInstance->create();
3221 QRhiVulkanInitParams params;
3222 params.inst = vulkanInstance.get();
3223 rhi = std::unique_ptr<QRhi>(QRhi::create(d->rhiBackend, ¶ms, flags));
3227 case QRhi::OpenGLES2: {
3228#if QT_CONFIG(opengl)
3229 QRhiGles2InitParams params;
3230 if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL) {
3232 params.format.setProfile(QSurfaceFormat::CoreProfile);
3233 params.format.setVersion(4, 3);
3236 params.format.setVersion(3, 1);
3238 params.fallbackSurface = fallbackSurface;
3239 rhi = std::unique_ptr<QRhi>(QRhi::create(d->rhiBackend, ¶ms, flags));
3244#if defined(Q_OS_WIN)
3245 QRhiD3D11InitParams params;
3246 rhi = std::unique_ptr<QRhi>(QRhi::create(d->rhiBackend, ¶ms, flags));
3251#if defined(Q_OS_WIN)
3252 QRhiD3D12InitParams params;
3253 rhi = std::unique_ptr<QRhi>(QRhi::create(d->rhiBackend, ¶ms, flags));
3259 QRhiMetalInitParams params;
3260 rhi = std::unique_ptr<QRhi>(QRhi::create(d->rhiBackend, ¶ms, flags));
3265 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed, QStringLiteral(
"QRhi backend is null"));
3268 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed, QStringLiteral(
"Failed to initialize QRhi"));
3273 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Failed to create QRhi, cannot bake"));
3277 if (!rhi->isTextureFormatSupported(QRhiTexture::RGBA32F)) {
3278 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"FP32 textures not supported, cannot bake"));
3281 if (rhi->resourceLimit(QRhi::MaxColorAttachments) < 4) {
3282 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Multiple render targets not supported, cannot bake"));
3285 if (!rhi->isFeatureSupported(QRhi::NonFillPolygonMode)) {
3286 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Line polygon mode not supported, cannot bake"));
3290 if (!rhi->isFeatureSupported(QRhi::Compute)) {
3291 qFatal(
"Compute is not supported, cannot bake");
3295 d->rhiCtxInterface = std::
3296 unique_ptr<QSSGRenderContextInterface>(
new QSSGRenderContextInterface(rhi.get()));
3297 d->renderer = std::unique_ptr<QSSGRenderer>(
new QSSGRenderer());
3299 QSSGRendererPrivate::setRenderContextInterface(*d->renderer, d->rhiCtxInterface.get());
3301 QRhiCommandBuffer *cb;
3302 rhi->beginOffscreenFrame(&cb);
3304 QSSGRhiContext *rhiCtx = d->rhiCtxInterface->rhiContext().get();
3305 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
3306 rhiCtxD->setCommandBuffer(cb);
3308 d->rhiCtxInterface->bufferManager()->setRenderContextInterface(d->rhiCtxInterface.get());
3310 constexpr int timerIntervalMs = 100;
3311 TimerThread timerThread;
3312 timerThread.setInterval(timerIntervalMs);
3314 constexpr int consoleOutputInterval = 5000 / timerIntervalMs;
3315 int timeoutsSinceOutput = consoleOutputInterval - 1;
3316 timerThread.setCallback([&]() {
3317 d->totalProgress = d->progressTracker.getProgress();
3318 d->estimatedTimeRemaining = d->progressTracker.getEstimatedTimeRemaining();
3319 bool outputToConsole = timeoutsSinceOutput == consoleOutputInterval - 1;
3320 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, std::nullopt, outputToConsole, outputToConsole);
3321 timeoutsSinceOutput = (timeoutsSinceOutput + 1) % consoleOutputInterval;
3323 timerThread.start();
3325 if (d->denoiseOnly) {
3331 rhi->endOffscreenFrame();
3334 d->renderer.reset();
3335 d->rhiCtxInterface.reset();
3338void QSSGLightmapper::waitForInit()
3340 d->initMutex.lock();
3341 d->initCondition.wait(&d->initMutex);
3342 d->initMutex.unlock();
3387 qWarning(
"Qt Quick 3D was built without the lightmapper; cannot bake lightmaps");
3408#include "qssglightmapper.moc"
void setRhiBackend(QRhi::Implementation backend)
bool setupLights(const QSSGRenderer &renderer)
qsizetype add(const QSSGBakedLightingModel &model)
void setOptions(const QSSGLightmapperOptions &options)
std::function< void(const QVariantMap &payload, BakingControl *)> Callback
void setDenoiseOnly(bool value)
void run(QOffscreenSurface *fallbackSurface)
void setOutputCallback(Callback callback)
friend class QSSGRenderer
Combined button and popup list for selecting options.