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, QStringLiteral(
"Lightmap UV unwrap done for model %1 in %2").
681 arg(lm.model->lightmapKey).
682 arg(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().first().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 for (
const QSSGMesh::Mesh::VertexBufferEntry &vbe : mesh.vertexBuffer().entries) {
732 if (vbe.name == QSSGMesh::MeshInternal::getPositionAttrName()) {
733 drawInfo.positionOffset = vbe.offset;
734 drawInfo.positionFormat = QSSGRhiHelpers::toVertexInputFormat(QSSGRenderComponentType(vbe.componentType), vbe.componentCount);
735 }
else if (vbe.name == QSSGMesh::MeshInternal::getNormalAttrName()) {
736 drawInfo.normalOffset = vbe.offset;
737 drawInfo.normalFormat = QSSGRhiHelpers::toVertexInputFormat(QSSGRenderComponentType(vbe.componentType), vbe.componentCount);
738 }
else if (vbe.name == QSSGMesh::MeshInternal::getUV0AttrName()) {
739 drawInfo.uvOffset = vbe.offset;
740 drawInfo.uvFormat = QSSGRhiHelpers::toVertexInputFormat(QSSGRenderComponentType(vbe.componentType), vbe.componentCount);
741 }
else if (vbe.name == QSSGMesh::MeshInternal::getLightmapUVAttrName()) {
742 drawInfo.lightmapUVOffset = vbe.offset;
743 drawInfo.lightmapUVFormat = QSSGRhiHelpers::toVertexInputFormat(QSSGRenderComponentType(vbe.componentType), vbe.componentCount);
744 }
else if (vbe.name == QSSGMesh::MeshInternal::getTexTanAttrName()) {
745 drawInfo.tangentOffset = vbe.offset;
746 drawInfo.tangentFormat = QSSGRhiHelpers::toVertexInputFormat(QSSGRenderComponentType(vbe.componentType), vbe.componentCount);
747 }
else if (vbe.name == QSSGMesh::MeshInternal::getTexBinormalAttrName()) {
748 drawInfo.binormalOffset = vbe.offset;
749 drawInfo.binormalFormat = QSSGRhiHelpers::toVertexInputFormat(QSSGRenderComponentType(vbe.componentType), vbe.componentCount);
753 if (!(drawInfo.positionOffset != UINT_MAX && drawInfo.normalOffset != UINT_MAX)) {
754 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Could not figure out position and normal attribute offsets for model %1").
755 arg(lm.model->lightmapKey));
760 if (!(drawInfo.positionFormat == QRhiVertexInputAttribute::Float3
761 && drawInfo.normalFormat == QRhiVertexInputAttribute::Float3))
763 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Position or normal attribute format is not as expected (float3) for model %1").
764 arg(lm.model->lightmapKey));
768 if (drawInfo.lightmapUVOffset == UINT_MAX) {
769 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Could not figure out lightmap UV attribute offset for model %1").
770 arg(lm.model->lightmapKey));
774 if (drawInfo.lightmapUVFormat != QRhiVertexInputAttribute::Float2) {
775 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Lightmap UV attribute format is not as expected (float2) for model %1").
776 arg(lm.model->lightmapKey));
781 if (drawInfo.uvOffset != UINT_MAX) {
782 if (drawInfo.uvFormat != QRhiVertexInputAttribute::Float2) {
783 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"UV0 attribute format is not as expected (float2) for model %1").
784 arg(lm.model->lightmapKey));
789 if (drawInfo.tangentOffset != UINT_MAX) {
790 if (drawInfo.tangentFormat != QRhiVertexInputAttribute::Float3) {
791 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Tangent attribute format is not as expected (float3) for model %1").
792 arg(lm.model->lightmapKey));
796 if (drawInfo.binormalOffset != UINT_MAX) {
797 if (drawInfo.binormalFormat != QRhiVertexInputAttribute::Float3) {
798 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Binormal attribute format is not as expected (float3) for model %1").
799 arg(lm.model->lightmapKey));
804 if (drawInfo.indexFormat == QRhiCommandBuffer::IndexUInt16) {
805 drawInfo.indexFormat = QRhiCommandBuffer::IndexUInt32;
806 QByteArray newIndexData(drawInfo.indexData.size() * 2, Qt::Uninitialized);
807 const quint16 *s =
reinterpret_cast<
const quint16 *>(drawInfo.indexData.constData());
808 size_t sz = drawInfo.indexData.size() / 2;
809 quint32 *p =
reinterpret_cast<quint32 *>(newIndexData.data());
812 drawInfo.indexData = newIndexData;
817 char *vertexBase = drawInfo.vertexData.data();
818 const qsizetype sz = drawInfo.vertexData.size();
819 for (qsizetype offset = 0; offset < sz; offset += drawInfo.vertexStride) {
820 char *posPtr = vertexBase + offset + drawInfo.positionOffset;
821 float *fPosPtr =
reinterpret_cast<
float *>(posPtr);
822 QVector3D pos(fPosPtr[0], fPosPtr[1], fPosPtr[2]);
823 char *normalPtr = vertexBase + offset + drawInfo.normalOffset;
824 float *fNormalPtr =
reinterpret_cast<
float *>(normalPtr);
825 QVector3D normal(fNormalPtr[0], fNormalPtr[1], fNormalPtr[2]);
826 pos = worldTransform.map(pos);
827 normal = QSSGUtils::mat33::transform(normalMatrix, normal).normalized();
828 *fPosPtr++ = pos.x();
829 *fPosPtr++ = pos.y();
830 *fPosPtr++ = pos.z();
831 *fNormalPtr++ = normal.x();
832 *fNormalPtr++ = normal.y();
833 *fNormalPtr++ = normal.z();
838 rdev = rtcNewDevice(
nullptr);
840 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Failed to create Embree device"));
844 rtcSetDeviceErrorFunction(rdev, embreeErrFunc,
nullptr);
846 rscene = rtcNewScene(rdev);
848 unsigned int geomId = 1;
850 for (
int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
851 const QSSGBakedLightingModel &lm(bakedLightingModels[lmIdx]);
857 if (!lm.model->castsShadows)
860 const DrawInfo &drawInfo(drawInfos[lmIdx]);
861 const char *vbase = drawInfo.vertexData.constData();
862 const quint32 *ibase =
reinterpret_cast<
const quint32 *>(drawInfo.indexData.constData());
864 for (SubMeshInfo &subMeshInfo : subMeshInfos[lmIdx]) {
865 RTCGeometry geom = rtcNewGeometry(rdev, RTC_GEOMETRY_TYPE_TRIANGLE);
866 rtcSetGeometryVertexAttributeCount(geom, 2);
867 quint32 *ip =
static_cast<quint32 *>(rtcSetNewGeometryBuffer(geom, RTC_BUFFER_TYPE_INDEX, 0, RTC_FORMAT_UINT3, 3 *
sizeof(uint32_t), subMeshInfo.count / 3));
868 for (quint32 i = 0; i < subMeshInfo.count; ++i)
870 float *vp =
static_cast<
float *>(rtcSetNewGeometryBuffer(geom, RTC_BUFFER_TYPE_VERTEX, 0, RTC_FORMAT_FLOAT3, 3 *
sizeof(
float), subMeshInfo.count));
871 for (quint32 i = 0; i < subMeshInfo.count; ++i) {
872 const quint32 idx = *(ibase + subMeshInfo.offset + i);
873 const float *src =
reinterpret_cast<
const float *>(vbase + idx * drawInfo.vertexStride + drawInfo.positionOffset);
878 vp =
static_cast<
float *>(rtcSetNewGeometryBuffer(geom, RTC_BUFFER_TYPE_VERTEX_ATTRIBUTE, NORMAL_SLOT, RTC_FORMAT_FLOAT3, 3 *
sizeof(
float), subMeshInfo.count));
879 for (quint32 i = 0; i < subMeshInfo.count; ++i) {
880 const quint32 idx = *(ibase + subMeshInfo.offset + i);
881 const float *src =
reinterpret_cast<
const float *>(vbase + idx * drawInfo.vertexStride + drawInfo.normalOffset);
886 vp =
static_cast<
float *>(rtcSetNewGeometryBuffer(geom, RTC_BUFFER_TYPE_VERTEX_ATTRIBUTE, LIGHTMAP_UV_SLOT, RTC_FORMAT_FLOAT2, 2 *
sizeof(
float), subMeshInfo.count));
887 for (quint32 i = 0; i < subMeshInfo.count; ++i) {
888 const quint32 idx = *(ibase + subMeshInfo.offset + i);
889 const float *src =
reinterpret_cast<
const float *>(vbase + idx * drawInfo.vertexStride + drawInfo.lightmapUVOffset);
893 rtcCommitGeometry(geom);
894 rtcSetGeometryIntersectFilterFunction(geom, embreeFilterFunc);
895 rtcSetGeometryUserData(geom,
this);
896 rtcAttachGeometryByID(rscene, geom, geomId);
897 subMeshInfo.geomId = geomId++;
898 rtcReleaseGeometry(geom);
902 rtcCommitScene(rscene);
905 rtcGetSceneBounds(rscene, &bounds);
906 QVector3D lowerBound(bounds.lower_x, bounds.lower_y, bounds.lower_z);
907 QVector3D upperBound(bounds.upper_x, bounds.upper_y, bounds.upper_z);
908 qDebug() <<
"[lm] Bounds in world space for raytracing scene:" << lowerBound << upperBound;
910 const unsigned int geomIdBasedMapSize = geomId;
913 geomLightmapMap.fill(-1, geomIdBasedMapSize);
914 subMeshOpacityMap.fill(0.0f, geomIdBasedMapSize);
916 for (
int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
917 const QSSGBakedLightingModel &lm(bakedLightingModels[lmIdx]);
918 if (!lm.model->castsShadows)
920 for (SubMeshInfo &subMeshInfo : subMeshInfos[lmIdx])
921 subMeshOpacityMap[subMeshInfo.geomId] = subMeshInfo.opacity;
924 sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral(
"Geometry ready. Time taken: %1").arg(formatDuration(geomPrepTimer.elapsed())));
928QSSGLightmapperPrivate::RasterResult QSSGLightmapperPrivate::rasterizeLightmap(
int lmIdx, QSize outputSize, QVector2D minUVRegion, QVector2D maxUVRegion)
930 QSSGLightmapperPrivate::RasterResult result;
932 QSSGRhiContext *rhiCtx = rhiCtxInterface->rhiContext().get();
933 QRhi *rhi = rhiCtx->rhi();
934 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
936 const DrawInfo &bakeModelDrawInfo(drawInfos[lmIdx]);
937 const bool hasUV0 = bakeModelDrawInfo.uvOffset != UINT_MAX;
938 const bool hasTangentAndBinormal = bakeModelDrawInfo.tangentOffset != UINT_MAX
939 && bakeModelDrawInfo.binormalOffset != UINT_MAX;
941 QRhiVertexInputLayout inputLayout;
942 inputLayout.setBindings({ QRhiVertexInputBinding(bakeModelDrawInfo.vertexStride) });
944 std::unique_ptr<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, bakeModelDrawInfo.vertexData.size()));
945 if (!vbuf->create()) {
946 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Failed to create vertex buffer"));
949 std::unique_ptr<QRhiBuffer> ibuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::IndexBuffer, bakeModelDrawInfo.indexData.size()));
950 if (!ibuf->create()) {
951 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Failed to create index buffer"));
954 QRhiResourceUpdateBatch *resUpd = rhi->nextResourceUpdateBatch();
955 resUpd->uploadStaticBuffer(vbuf.get(), bakeModelDrawInfo.vertexData.constData());
956 resUpd->uploadStaticBuffer(ibuf.get(), bakeModelDrawInfo.indexData.constData());
957 QRhiTexture *dummyTexture = rhiCtx->dummyTexture({}, resUpd);
958 cb->resourceUpdate(resUpd);
960 std::unique_ptr<QRhiTexture> positionData(rhi->newTexture(QRhiTexture::RGBA32F, outputSize, 1,
961 QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
962 if (!positionData->create()) {
963 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Failed to create FP32 texture for positions"));
966 std::unique_ptr<QRhiTexture> normalData(rhi->newTexture(QRhiTexture::RGBA32F, outputSize, 1,
967 QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
968 if (!normalData->create()) {
969 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Failed to create FP32 texture for normals"));
972 std::unique_ptr<QRhiTexture> baseColorData(rhi->newTexture(QRhiTexture::RGBA32F, outputSize, 1,
973 QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
974 if (!baseColorData->create()) {
975 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Failed to create FP32 texture for base color"));
978 std::unique_ptr<QRhiTexture> emissionData(rhi->newTexture(QRhiTexture::RGBA32F, outputSize, 1,
979 QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
980 if (!emissionData->create()) {
981 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Failed to create FP32 texture for emissive color"));
985 std::unique_ptr<QRhiRenderBuffer> ds(rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, outputSize));
987 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Failed to create depth-stencil buffer"));
991 QRhiColorAttachment posAtt(positionData.get());
992 QRhiColorAttachment normalAtt(normalData.get());
993 QRhiColorAttachment baseColorAtt(baseColorData.get());
994 QRhiColorAttachment emissionAtt(emissionData.get());
995 QRhiTextureRenderTargetDescription rtDesc;
996 rtDesc.setColorAttachments({ posAtt, normalAtt, baseColorAtt, emissionAtt });
997 rtDesc.setDepthStencilBuffer(ds.get());
999 std::unique_ptr<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(rtDesc));
1000 std::unique_ptr<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor());
1001 rt->setRenderPassDescriptor(rpDesc.get());
1002 if (!rt->create()) {
1003 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Failed to create texture render target"));
1007 static const int UBUF_SIZE = 64;
1008 const int subMeshCount = subMeshInfos[lmIdx].size();
1009 const int alignedUbufSize = rhi->ubufAligned(UBUF_SIZE);
1010 const int totalUbufSize = alignedUbufSize * subMeshCount;
1011 std::unique_ptr<QRhiBuffer> ubuf(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, totalUbufSize));
1012 if (!ubuf->create()) {
1013 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Failed to create uniform buffer of size %1").arg(totalUbufSize));
1020 qint32 flipY = rhi->isYUpInFramebuffer() ? 0 : 1;
1021 if (rhi->isYUpInNDC())
1024 char *ubufData = ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
1025 for (
int subMeshIdx = 0; subMeshIdx != subMeshCount; ++subMeshIdx) {
1026 const SubMeshInfo &subMeshInfo(subMeshInfos[lmIdx][subMeshIdx]);
1027 qint32 hasBaseColorMap = subMeshInfo.baseColorMap ? 1 : 0;
1028 qint32 hasEmissiveMap = subMeshInfo.emissiveMap ? 1 : 0;
1029 qint32 hasNormalMap = subMeshInfo.normalMap && hasTangentAndBinormal ? 1 : 0;
1030 const float minRegionU = minUVRegion.x();
1031 const float minRegionV = minUVRegion.y();
1032 const float maxRegionU = maxUVRegion.x();
1033 const float maxRegionV = maxUVRegion.y();
1034 char *p = ubufData + subMeshIdx * alignedUbufSize;
1035 memcpy(p, &subMeshInfo.baseColor, 4 *
sizeof(
float));
1036 memcpy(p + 16, &subMeshInfo.emissiveFactor, 3 *
sizeof(
float));
1037 memcpy(p + 28, &flipY,
sizeof(qint32));
1038 memcpy(p + 32, &hasBaseColorMap,
sizeof(qint32));
1039 memcpy(p + 36, &hasEmissiveMap,
sizeof(qint32));
1040 memcpy(p + 40, &hasNormalMap,
sizeof(qint32));
1041 memcpy(p + 44, &subMeshInfo.normalStrength,
sizeof(
float));
1042 memcpy(p + 48, &minRegionU,
sizeof(
float));
1043 memcpy(p + 52, &minRegionV,
sizeof(
float));
1044 memcpy(p + 56, &maxRegionU,
sizeof(
float));
1045 memcpy(p + 60, &maxRegionV,
sizeof(
float));
1047 ubuf->endFullDynamicBufferUpdateForCurrentFrame();
1049 auto setupPipeline = [rhi, &rpDesc](QSSGRhiShaderPipeline *shaderPipeline,
1050 QRhiShaderResourceBindings *srb,
1051 const QRhiVertexInputLayout &inputLayout)
1053 QRhiGraphicsPipeline *ps = rhi->newGraphicsPipeline();
1054 ps->setTopology(QRhiGraphicsPipeline::Triangles);
1055 ps->setDepthTest(
true);
1056 ps->setDepthWrite(
true);
1057 ps->setDepthOp(QRhiGraphicsPipeline::Less);
1058 ps->setShaderStages(shaderPipeline->cbeginStages(), shaderPipeline->cendStages());
1059 ps->setTargetBlends({ {}, {}, {}, {} });
1060 ps->setRenderPassDescriptor(rpDesc.get());
1061 ps->setVertexInputLayout(inputLayout);
1062 ps->setShaderResourceBindings(srb);
1066 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
1067 QVector<QRhiGraphicsPipeline *> ps;
1070 QVector<QRhiGraphicsPipeline *> psLine;
1072 for (
int subMeshIdx = 0; subMeshIdx != subMeshCount; ++subMeshIdx) {
1073 const SubMeshInfo &subMeshInfo(subMeshInfos[lmIdx][subMeshIdx]);
1074 QVarLengthArray<QRhiVertexInputAttribute, 6> vertexAttrs;
1075 vertexAttrs << QRhiVertexInputAttribute(0, 0, bakeModelDrawInfo.positionFormat, bakeModelDrawInfo.positionOffset)
1076 << QRhiVertexInputAttribute(0, 1, bakeModelDrawInfo.normalFormat, bakeModelDrawInfo.normalOffset)
1077 << QRhiVertexInputAttribute(0, 2, bakeModelDrawInfo.lightmapUVFormat, bakeModelDrawInfo.lightmapUVOffset);
1082 QSSGBuiltInRhiShaderCache::LightmapUVRasterizationShaderMode shaderVariant = QSSGBuiltInRhiShaderCache::LightmapUVRasterizationShaderMode::Default;
1084 shaderVariant = QSSGBuiltInRhiShaderCache::LightmapUVRasterizationShaderMode::Uv;
1085 if (hasTangentAndBinormal)
1086 shaderVariant = QSSGBuiltInRhiShaderCache::LightmapUVRasterizationShaderMode::UvTangent;
1089 const auto &shaderCache = renderer->contextInterface()->shaderCache();
1090 const auto &lmUvRastShaderPipeline = shaderCache->getBuiltInRhiShaders().getRhiLightmapUVRasterizationShader(shaderVariant);
1091 if (!lmUvRastShaderPipeline) {
1092 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Failed to load shaders"));
1097 vertexAttrs << QRhiVertexInputAttribute(0, 3, bakeModelDrawInfo.uvFormat, bakeModelDrawInfo.uvOffset);
1098 if (hasTangentAndBinormal) {
1099 vertexAttrs << QRhiVertexInputAttribute(0, 4, bakeModelDrawInfo.tangentFormat, bakeModelDrawInfo.tangentOffset);
1100 vertexAttrs << QRhiVertexInputAttribute(0, 5, bakeModelDrawInfo.binormalFormat, bakeModelDrawInfo.binormalOffset);
1104 inputLayout.setAttributes(vertexAttrs.cbegin(), vertexAttrs.cend());
1106 QSSGRhiShaderResourceBindingList bindings;
1107 bindings.addUniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, ubuf.get(),
1108 subMeshIdx * alignedUbufSize, UBUF_SIZE);
1109 QRhiSampler *dummySampler = rhiCtx->sampler({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
1110 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
1111 if (subMeshInfo.baseColorMap) {
1112 const bool mipmapped = subMeshInfo.baseColorMap->flags().testFlag(QRhiTexture::MipMapped);
1113 QRhiSampler *sampler = rhiCtx->sampler({ QSSGRhiHelpers::toRhi(subMeshInfo.baseColorNode->m_minFilterType),
1114 QSSGRhiHelpers::toRhi(subMeshInfo.baseColorNode->m_magFilterType),
1115 mipmapped ? QSSGRhiHelpers::toRhi(subMeshInfo.baseColorNode->m_mipFilterType) : QRhiSampler::None,
1116 QSSGRhiHelpers::toRhi(subMeshInfo.baseColorNode->m_horizontalTilingMode),
1117 QSSGRhiHelpers::toRhi(subMeshInfo.baseColorNode->m_verticalTilingMode),
1118 QSSGRhiHelpers::toRhi(subMeshInfo.baseColorNode->m_depthTilingMode)
1120 bindings.addTexture(1, QRhiShaderResourceBinding::FragmentStage, subMeshInfo.baseColorMap, sampler);
1122 bindings.addTexture(1, QRhiShaderResourceBinding::FragmentStage, dummyTexture, dummySampler);
1124 if (subMeshInfo.emissiveMap) {
1125 const bool mipmapped = subMeshInfo.emissiveMap->flags().testFlag(QRhiTexture::MipMapped);
1126 QRhiSampler *sampler = rhiCtx->sampler({ QSSGRhiHelpers::toRhi(subMeshInfo.emissiveNode->m_minFilterType),
1127 QSSGRhiHelpers::toRhi(subMeshInfo.emissiveNode->m_magFilterType),
1128 mipmapped ? QSSGRhiHelpers::toRhi(subMeshInfo.emissiveNode->m_mipFilterType) : QRhiSampler::None,
1129 QSSGRhiHelpers::toRhi(subMeshInfo.emissiveNode->m_horizontalTilingMode),
1130 QSSGRhiHelpers::toRhi(subMeshInfo.emissiveNode->m_verticalTilingMode),
1131 QSSGRhiHelpers::toRhi(subMeshInfo.emissiveNode->m_depthTilingMode)
1133 bindings.addTexture(2, QRhiShaderResourceBinding::FragmentStage, subMeshInfo.emissiveMap, sampler);
1135 bindings.addTexture(2, QRhiShaderResourceBinding::FragmentStage, dummyTexture, dummySampler);
1137 if (subMeshInfo.normalMap) {
1138 const bool mipmapped = subMeshInfo.normalMap->flags().testFlag(QRhiTexture::MipMapped);
1139 QRhiSampler *sampler = rhiCtx->sampler({ QSSGRhiHelpers::toRhi(subMeshInfo.normalMapNode->m_minFilterType),
1140 QSSGRhiHelpers::toRhi(subMeshInfo.normalMapNode->m_magFilterType),
1141 mipmapped ? QSSGRhiHelpers::toRhi(subMeshInfo.normalMapNode->m_mipFilterType) : QRhiSampler::None,
1142 QSSGRhiHelpers::toRhi(subMeshInfo.normalMapNode->m_horizontalTilingMode),
1143 QSSGRhiHelpers::toRhi(subMeshInfo.normalMapNode->m_verticalTilingMode),
1144 QSSGRhiHelpers::toRhi(subMeshInfo.normalMapNode->m_depthTilingMode)
1146 bindings.addTexture(3, QRhiShaderResourceBinding::FragmentStage, subMeshInfo.normalMap, sampler);
1148 bindings.addTexture(3, QRhiShaderResourceBinding::FragmentStage, dummyTexture, dummySampler);
1150 QRhiShaderResourceBindings *srb = rhiCtxD->srb(bindings);
1152 QRhiGraphicsPipeline *pipeline = setupPipeline(lmUvRastShaderPipeline.get(), srb, inputLayout);
1153 if (!pipeline->create()) {
1154 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Failed to create graphics pipeline (mesh %1 submesh %2)").
1161 ps.append(pipeline);
1162 pipeline = setupPipeline(lmUvRastShaderPipeline.get(), srb, inputLayout);
1163 pipeline->setPolygonMode(QRhiGraphicsPipeline::Line);
1164 if (!pipeline->create()) {
1165 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Failed to create graphics pipeline with line fill mode (mesh %1 submesh %2)").
1172 psLine.append(pipeline);
1175 QRhiCommandBuffer::VertexInput vertexBuffers = { vbuf.get(), 0 };
1176 const QRhiViewport viewport(0, 0,
float(outputSize.width()),
float(outputSize.height()));
1177 bool hadViewport =
false;
1179 cb->beginPass(rt.get(), Qt::black, { 1.0f, 0 });
1180 for (
int subMeshIdx = 0; subMeshIdx != subMeshCount; ++subMeshIdx) {
1181 const SubMeshInfo &subMeshInfo(subMeshInfos[lmIdx][subMeshIdx]);
1182 cb->setGraphicsPipeline(ps[subMeshIdx]);
1184 cb->setViewport(viewport);
1187 cb->setShaderResources();
1188 cb->setVertexInput(0, 1, &vertexBuffers, ibuf.get(), 0, QRhiCommandBuffer::IndexUInt32);
1189 cb->drawIndexed(subMeshInfo.count, 1, subMeshInfo.offset);
1190 cb->setGraphicsPipeline(psLine[subMeshIdx]);
1191 cb->setShaderResources();
1192 cb->drawIndexed(subMeshInfo.count, 1, subMeshInfo.offset);
1195 resUpd = rhi->nextResourceUpdateBatch();
1196 QRhiReadbackResult posReadResult;
1197 QRhiReadbackResult normalReadResult;
1198 QRhiReadbackResult baseColorReadResult;
1199 QRhiReadbackResult emissionReadResult;
1200 resUpd->readBackTexture({ positionData.get() }, &posReadResult);
1201 resUpd->readBackTexture({ normalData.get() }, &normalReadResult);
1202 resUpd->readBackTexture({ baseColorData.get() }, &baseColorReadResult);
1203 resUpd->readBackTexture({ emissionData.get() }, &emissionReadResult);
1204 cb->endPass(resUpd);
1212 const int numPixels = outputSize.width() * outputSize.height();
1214 result.worldPositions.resize(numPixels);
1215 result.normals.resize(numPixels);
1216 result.baseColors.resize(numPixels);
1217 result.emissions.resize(numPixels);
1221 if (posReadResult.data.size() < numPixels * 16) {
1222 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Position data is smaller than expected"));
1225 if (normalReadResult.data.size() < numPixels * 16) {
1226 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Normal data is smaller than expected"));
1229 if (baseColorReadResult.data.size() < numPixels * 16) {
1230 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Base color data is smaller than expected"));
1233 if (emissionReadResult.data.size() < numPixels * 16) {
1234 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Emission data is smaller than expected"));
1238 result.success =
true;
1239 result.width = outputSize.width();
1240 result.height = outputSize.height();
1241 result.worldPositions = posReadResult.data;
1242 result.normals = normalReadResult.data;
1243 result.baseColors = baseColorReadResult.data;
1244 result.emissions = emissionReadResult.data;
1249bool QSSGLightmapperPrivate::prepareLightmaps()
1251 QRhi *rhi = rhiCtxInterface->rhiContext()->rhi();
1253 if (!rhi->isTextureFormatSupported(QRhiTexture::RGBA32F)) {
1254 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"FP32 textures not supported, cannot bake"));
1257 if (rhi->resourceLimit(QRhi::MaxColorAttachments) < 4) {
1258 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Multiple render targets not supported, cannot bake"));
1261 if (!rhi->isFeatureSupported(QRhi::NonFillPolygonMode)) {
1262 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Line polygon mode not supported, cannot bake"));
1266 sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral(
"Preparing lightmaps..."));
1267 QElapsedTimer lightmapPrepTimer;
1268 lightmapPrepTimer.start();
1269 const int bakedLightingModelCount = bakedLightingModels.size();
1270 Q_ASSERT(drawInfos.size() == bakedLightingModelCount);
1271 Q_ASSERT(subMeshInfos.size() == bakedLightingModelCount);
1273 numValidTexels.resize(bakedLightingModelCount);
1275 for (
int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
1276 QElapsedTimer rasterizeTimer;
1277 rasterizeTimer.start();
1279 const QSSGBakedLightingModel &lm(bakedLightingModels[lmIdx]);
1280 const QSize lightmapSize = drawInfos[lmIdx].lightmapSize;
1282 const int w = lightmapSize.width();
1283 const int h = lightmapSize.height();
1284 const int numPixels = w * h;
1286 int unusedEntries = 0;
1287 QVector<ModelTexel> &texels = modelTexels[lmIdx];
1288 texels.resize(numPixels);
1291 constexpr int maxTileSize = MAX_TILE_SIZE;
1292 const int numTilesX = (w + maxTileSize - 1) / maxTileSize;
1293 const int numTilesY = (h + maxTileSize - 1) / maxTileSize;
1295 bool isEmissive =
false;
1298 for (
int tileY = 0; tileY < numTilesY; ++tileY) {
1299 for (
int tileX = 0; tileX < numTilesX; ++tileX) {
1301 const int startX = tileX * maxTileSize;
1302 const int startY = tileY * maxTileSize;
1304 const int tileWidth = qMin(maxTileSize, w - startX);
1305 const int tileHeight = qMin(maxTileSize, h - startY);
1307 const int endX = startX + tileWidth;
1308 const int endY = startY + tileHeight;
1310 const float minU = startX /
double(w);
1311 const float maxV = 1.0 - startY /
double(h);
1312 const float maxU = endX /
double(w);
1313 const float minV = 1.0 - endY /
double(h);
1315 QSSGLightmapperPrivate::RasterResult raster = rasterizeLightmap(lmIdx,
1316 QSize(tileWidth, tileHeight),
1317 QVector2D(minU, minV),
1318 QVector2D(maxU, maxV));
1319 if (!raster.success)
1322 QVector4D *worldPositions =
reinterpret_cast<QVector4D *>(raster.worldPositions.data());
1323 QVector4D *normals =
reinterpret_cast<QVector4D *>(raster.normals.data());
1324 QVector4D *baseColors =
reinterpret_cast<QVector4D *>(raster.baseColors.data());
1325 QVector4D *emissions =
reinterpret_cast<QVector4D *>(raster.emissions.data());
1327 for (
int y = startY; y < endY; ++y) {
1328 const int ySrc = y - startY;
1329 Q_ASSERT(ySrc < tileHeight);
1330 for (
int x = startX; x < endX; ++x) {
1331 const int xSrc = x - startX;
1332 Q_ASSERT(xSrc < tileWidth);
1334 const int dstPixelI = y * w + x;
1335 const int srcPixelI = ySrc * tileWidth + xSrc;
1337 ModelTexel &lmPix(texels[dstPixelI]);
1339 lmPix.worldPos = worldPositions[srcPixelI].toVector3D();
1340 lmPix.normal = normals[srcPixelI].toVector3D();
1341 if (lmPix.isValid())
1342 ++numValidTexels[lmIdx];
1344 lmPix.baseColor = baseColors[srcPixelI];
1345 if (lmPix.baseColor[3] < 1.0f)
1346 modelHasBaseColorTransparency[lmIdx] =
true;
1348 lmPix.emission = emissions[srcPixelI].toVector3D();
1349 if (!isEmissive && !qFuzzyIsNull(lmPix.emission.length()))
1357 ++emissiveModelCount;
1359 sendOutputInfo(QSSGLightmapper::BakingStatus::Info,
1361 "Successfully rasterized %1/%2 lightmap texels for model %3, lightmap size %4 in %5")
1362 .arg(texels.size() - unusedEntries)
1364 .arg(lm.model->lightmapKey)
1365 .arg(QStringLiteral(
"(%1, %2)").arg(w).arg(h))
1366 .arg(formatDuration(rasterizeTimer.elapsed())));
1367 for (
const SubMeshInfo &subMeshInfo : std::as_const(subMeshInfos[lmIdx])) {
1368 if (!lm.model->castsShadows)
1370 geomLightmapMap[subMeshInfo.geomId] = lmIdx;
1374 sendOutputInfo(QSSGLightmapper::BakingStatus::Info,
1375 QStringLiteral(
"Lightmaps ready. Time taken: %1")
1376 .arg(formatDuration(lightmapPrepTimer.elapsed())));
1380bool QSSGLightmapperPrivate::verifyLights()
const {
1382 return !lights.empty() || emissiveModelCount > 0;
1385bool QSSGLightmapper::setupLights(
const QSSGRenderer &renderer)
1387 QSSGLayerRenderData *renderData = QSSGRendererPrivate::getCurrentRenderData(renderer);
1389 qWarning() <<
"lm: No render data, cannot bake lightmaps";
1393 if (d->bakedLightingModels.isEmpty()) {
1394 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info,
1395 QStringLiteral(
"No models provided, cannot bake lightmaps"));
1402 auto lights =
static_cast<QSSGSubsetRenderable *>(d->bakedLightingModels.first().renderables.first().obj)->lights;
1403 for (
const QSSGShaderLight &sl : lights) {
1404 if (!sl.light->m_bakingEnabled)
1407 QSSGLightmapperPrivate::Light light;
1408 light.indirectOnly = !sl.light->m_fullyBaked;
1409 light.direction = sl.direction;
1411 const float brightness = sl.light->m_brightness;
1412 light.color = QVector3D(sl.light->m_diffuseColor.x() * brightness,
1413 sl.light->m_diffuseColor.y() * brightness,
1414 sl.light->m_diffuseColor.z() * brightness);
1416 if (sl.light->type == QSSGRenderLight::Type::PointLight
1417 || sl.light->type == QSSGRenderLight::Type::SpotLight) {
1418 const QMatrix4x4 lightGlobalTransform = renderData->getGlobalTransform(*sl.light);
1419 light.worldPos = QSSGRenderNode::getGlobalPos(lightGlobalTransform);
1420 if (sl.light->type == QSSGRenderLight::Type::SpotLight) {
1421 light.type = QSSGLightmapperPrivate::Light::Spot;
1422 light.cosConeAngle = qCos(qDegreesToRadians(sl.light->m_coneAngle));
1423 light.cosInnerConeAngle = qCos(
1424 qDegreesToRadians(qMin(sl.light->m_innerConeAngle, sl.light->m_coneAngle)));
1426 light.type = QSSGLightmapperPrivate::Light::Point;
1428 light.constantAttenuation = QSSGUtils::aux::translateConstantAttenuation(
1429 sl.light->m_constantFade);
1430 light.linearAttenuation = QSSGUtils::aux::translateLinearAttenuation(
1431 sl.light->m_linearFade);
1432 light.quadraticAttenuation = QSSGUtils::aux::translateQuadraticAttenuation(
1433 sl.light->m_quadraticFade);
1435 light.type = QSSGLightmapperPrivate::Light::Directional;
1438 d->lights.append(light);
1441 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info,
1442 QStringLiteral(
"Total lights registered: %1").arg(d->lights.size()));
1450 RayHit(
const QVector3D &org,
const QVector3D &dir,
float tnear = 0.0f,
float tfar = std::numeric_limits<
float>::infinity()) {
1451 rayhit.ray.org_x = org.x();
1452 rayhit.ray.org_y = org.y();
1453 rayhit.ray.org_z = org.z();
1454 rayhit.ray.dir_x = dir.x();
1455 rayhit.ray.dir_y = dir.y();
1456 rayhit.ray.dir_z = dir.z();
1457 rayhit.ray.tnear = tnear;
1458 rayhit.ray.tfar = tfar;
1459 rayhit.hit.u = 0.0f;
1460 rayhit.hit.v = 0.0f;
1461 rayhit.hit.geomID = RTC_INVALID_GEOMETRY_ID;
1466 bool intersect(RTCScene scene)
1468 RTCIntersectContext ctx;
1469 rtcInitIntersectContext(&ctx);
1470 rtcIntersect1(scene, &ctx, &rayhit);
1471 return rayhit.hit.geomID != RTC_INVALID_GEOMETRY_ID;
1475static inline QVector3D vectorSign(
const QVector3D &v)
1477 return QVector3D(v.x() < 1.0f ? -1.0f : 1.0f,
1478 v.y() < 1.0f ? -1.0f : 1.0f,
1479 v.z() < 1.0f ? -1.0f : 1.0f);
1482static inline QVector3D vectorAbs(
const QVector3D &v)
1484 return QVector3D(std::abs(v.x()),
1490QList<QVector3D> applyGaussianBlur(
const QList<QVector3D>& image,
const QList<quint32>& mask,
int width,
int height,
float sigma) {
1492 constexpr int halfKernelSize = GAUSS_HALF_KERNEL_SIZE;
1493 constexpr int kernelSize = halfKernelSize * 2 + 1;
1496 double kernel[kernelSize][kernelSize];
1497 double mean = halfKernelSize;
1498 for (
int y = 0; y < kernelSize; ++y) {
1499 for (
int x = 0; x < kernelSize; ++x) {
1500 kernel[y][x] = exp(-0.5 * (pow((x - mean) / sigma, 2.0) + pow((y - mean) / sigma, 2.0))) / (2 * M_PI * sigma * sigma);
1503 sum += kernel[y][x];
1508 for (
int x = 0; x < kernelSize; ++x)
1509 for (
int y = 0; y < kernelSize; ++y)
1510 kernel[y][x] /= sum;
1513 QList<QVector3D> output(image.size(), QVector3D(0, 0, 0));
1516 for (
int y = 0; y < height; ++y) {
1517 for (
int x = 0; x < width; ++x) {
1518 const int centerIdx = y * width + x;
1519 const quint32 maskID = mask[centerIdx];
1520 if (maskID == PIXEL_VOID)
1523 QVector3D blurredPixel(0, 0, 0);
1524 float weightSum = 0.0f;
1527 for (
int ky = -halfKernelSize; ky <= halfKernelSize; ++ky) {
1528 for (
int kx = -halfKernelSize; kx <= halfKernelSize; ++kx) {
1531 if (px < 0 || px >= width || py < 0 || py >= height)
1534 int idx = py * width + px;
1535 if (mask[idx] != maskID)
1538 double weight = kernel[ky + halfKernelSize][kx + halfKernelSize];
1539 blurredPixel += image[idx] * weight;
1540 weightSum += weight;
1545 if (weightSum > 0.0f)
1546 blurredPixel /= weightSum;
1548 output[centerIdx] = blurredPixel;
1557 std::array<QVector3D, 2> pos;
1558 std::array<QVector3D, 2> normal;
1561inline bool operator==(
const Edge &a,
const Edge &b)
1563 return qFuzzyCompare(a.pos[0], b.pos[0]) && qFuzzyCompare(a.pos[1], b.pos[1])
1564 && qFuzzyCompare(a.normal[0], b.normal[0]) && qFuzzyCompare(a.normal[1], b.normal[1]);
1567inline size_t qHash(
const Edge &e, size_t seed) Q_DECL_NOTHROW
1569 return qHash(e.pos[0].x(), seed) ^ qHash(e.pos[0].y()) ^ qHash(e.pos[0].z()) ^ qHash(e.pos[1].x())
1570 ^ qHash(e.pos[1].y()) ^ qHash(e.pos[1].z());
1575 std::array<QVector2D, 2> uv;
1581 std::array<std::array<QVector2D, 2>, 2> uv;
1584static inline bool vectorLessThan(
const QVector3D &a,
const QVector3D &b)
1586 if (a.x() == b.x()) {
1588 return a.z() < b.z();
1590 return a.y() < b.y();
1592 return a.x() < b.x();
1595static inline float floatSign(
float f)
1597 return f > 0.0f ? 1.0f : (f < 0.0f ? -1.0f : 0.0f);
1600static inline QVector2D flooredVec(
const QVector2D &v)
1602 return QVector2D(std::floor(v.x()), std::floor(v.y()));
1605static inline QVector2D projectPointToLine(
const QVector2D &point,
const std::array<QVector2D, 2> &line)
1607 const QVector2D p = point - line[0];
1608 const QVector2D n = line[1] - line[0];
1609 const float lengthSquared = n.lengthSquared();
1610 if (!qFuzzyIsNull(lengthSquared)) {
1611 const float d = (n.x() * p.x() + n.y() * p.y()) / lengthSquared;
1612 return d <= 0.0f ? line[0] : (d >= 1.0f ? line[1] : line[0] + n * d);
1617static void blendLine(
const QVector2D &from,
1618 const QVector2D &to,
1619 const QVector2D &uvFrom,
1620 const QVector2D &uvTo,
1621 const float *readBuf,
1623 const QSize &lightmapPixelSize,
1624 const int stride = 4)
1626 const QVector2D size(lightmapPixelSize.width(), lightmapPixelSize.height());
1627 const std::array<QVector2D, 2> line = { QVector2D(from.x(), 1.0f - from.y()) * size, QVector2D(to.x(), 1.0f - to.y()) * size };
1628 const float lineLength = line[0].distanceToPoint(line[1]);
1629 if (qFuzzyIsNull(lineLength))
1632 const QVector2D startPixel = flooredVec(line[0]);
1633 const QVector2D endPixel = flooredVec(line[1]);
1635 const QVector2D dir = (line[1] - line[0]).normalized();
1636 const QVector2D tStep(1.0f / std::abs(dir.x()), 1.0f / std::abs(dir.y()));
1637 const QVector2D pixelStep(floatSign(dir.x()), floatSign(dir.y()));
1639 QVector2D nextT(std::fmod(line[0].x(), 1.0f), std::fmod(line[0].y(), 1.0f));
1640 if (pixelStep.x() == 1.0f)
1641 nextT.setX(1.0f - nextT.x());
1642 if (pixelStep.y() == 1.0f)
1643 nextT.setY(1.0f - nextT.y());
1645 if (!qFuzzyIsNull(dir.x()))
1646 nextT.setX(nextT.x() / std::abs(dir.x()));
1648 nextT.setX(std::numeric_limits<
float>::max());
1650 if (!qFuzzyIsNull(dir.y()))
1651 nextT.setY(nextT.y() / std::abs(dir.y()));
1653 nextT.setY(std::numeric_limits<
float>::max());
1655 QVector2D pixel = startPixel;
1657 const auto clampedXY = [s = lightmapPixelSize](QVector2D xy) -> std::array<
int, 2> {
1658 return { qBound(0,
int(xy.x()), s.width() - 1), qBound(0,
int(xy.y()), s.height() - 1) };
1661 while (startPixel.distanceToPoint(pixel) < lineLength + 1.0f) {
1662 const QVector2D point = projectPointToLine(pixel + QVector2D(0.5f, 0.5f), line);
1663 const float t = line[0].distanceToPoint(point) / lineLength;
1664 const QVector2D uvInterp = uvFrom * (1.0 - t) + uvTo * t;
1665 const auto sampledPixelXY = clampedXY(flooredVec(QVector2D(uvInterp.x(), 1.0f - uvInterp.y()) * size));
1666 const int sampOfs = (sampledPixelXY[0] + sampledPixelXY[1] * lightmapPixelSize.width()) * stride;
1667 const QVector3D sampledColor(readBuf[sampOfs], readBuf[sampOfs + 1], readBuf[sampOfs + 2]);
1668 const auto pixelXY = clampedXY(pixel);
1669 const int pixOfs = (pixelXY[0] + pixelXY[1] * lightmapPixelSize.width()) * stride;
1670 QVector3D currentColor(writeBuf[pixOfs], writeBuf[pixOfs + 1], writeBuf[pixOfs + 2]);
1671 currentColor = currentColor * 0.6f + sampledColor * 0.4f;
1672 writeBuf[pixOfs] = currentColor.x();
1673 writeBuf[pixOfs + 1] = currentColor.y();
1674 writeBuf[pixOfs + 2] = currentColor.z();
1676 if (pixel != endPixel) {
1677 if (nextT.x() < nextT.y()) {
1678 pixel.setX(pixel.x() + pixelStep.x());
1679 nextT.setX(nextT.x() + tStep.x());
1681 pixel.setY(pixel.y() + pixelStep.y());
1682 nextT.setY(nextT.y() + tStep.y());
1690QVector3D QSSGLightmapperPrivate::sampleDirectLight(QVector3D worldPos, QVector3D normal,
bool allLight)
const
1692 QVector3D directLight = QVector3D(0.f, 0.f, 0.f);
1694 if (options.useAdaptiveBias)
1695 worldPos += vectorSign(normal) * vectorAbs(worldPos * 0.0000002f);
1698 for (
const Light &light : lights) {
1699 if (light.indirectOnly && !allLight)
1702 QVector3D lightWorldPos;
1703 float dist = std::numeric_limits<
float>::infinity();
1704 float attenuation = 1.0f;
1705 if (light.type == Light::Directional) {
1706 lightWorldPos = worldPos - light.direction;
1708 lightWorldPos = light.worldPos;
1709 dist = (worldPos - lightWorldPos).length();
1711 / (light.constantAttenuation + light.linearAttenuation * dist + light.quadraticAttenuation * dist * dist);
1712 if (light.type == Light::Spot) {
1713 const float spotAngle = QVector3D::dotProduct((worldPos - lightWorldPos).normalized(), light.direction.normalized());
1714 if (spotAngle > light.cosConeAngle) {
1716 const float edge0 = light.cosConeAngle;
1717 const float edge1 = light.cosInnerConeAngle;
1718 const float x = spotAngle;
1719 const float t = qBound(0.0f, (x - edge0) / (edge1 - edge0), 1.0f);
1720 const float spotFactor = t * t * (3.0f - 2.0f * t);
1721 attenuation *= spotFactor;
1728 const QVector3D L = (lightWorldPos - worldPos).normalized();
1729 const float energy = qMax(0.0f, QVector3D::dotProduct(normal, L)) * attenuation;
1730 if (qFuzzyIsNull(energy))
1734 RayHit ray(worldPos, L, options.bias, dist);
1735 const bool lightReachable = !ray.intersect(rscene);
1736 if (lightReachable) {
1737 directLight += light.color * energy;
1744QByteArray QSSGLightmapperPrivate::dilate(
const QSize &pixelSize,
const QByteArray &image)
1746 QSSGRhiContext *rhiCtx = rhiCtxInterface->rhiContext().get();
1747 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
1748 QRhi *rhi = rhiCtx->rhi();
1749 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
1751 const QRhiViewport viewport(0, 0,
float(pixelSize.width()),
float(pixelSize.height()));
1753 std::unique_ptr<QRhiTexture> lightmapTex(rhi->newTexture(QRhiTexture::RGBA32F, pixelSize));
1754 if (!lightmapTex->create()) {
1755 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Failed to create FP32 texture for postprocessing"));
1758 std::unique_ptr<QRhiTexture> dilatedLightmapTex(
1759 rhi->newTexture(QRhiTexture::RGBA32F, pixelSize, 1, QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
1760 if (!dilatedLightmapTex->create()) {
1761 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning,
1762 QStringLiteral(
"Failed to create FP32 dest. texture for postprocessing"));
1765 QRhiTextureRenderTargetDescription rtDescDilate(dilatedLightmapTex.get());
1766 std::unique_ptr<QRhiTextureRenderTarget> rtDilate(rhi->newTextureRenderTarget(rtDescDilate));
1767 std::unique_ptr<QRhiRenderPassDescriptor> rpDescDilate(rtDilate->newCompatibleRenderPassDescriptor());
1768 rtDilate->setRenderPassDescriptor(rpDescDilate.get());
1769 if (!rtDilate->create()) {
1770 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning,
1771 QStringLiteral(
"Failed to create postprocessing texture render target"));
1774 QRhiResourceUpdateBatch *resUpd = rhi->nextResourceUpdateBatch();
1775 QRhiTextureSubresourceUploadDescription lightmapTexUpload(image.constData(), image.size());
1776 resUpd->uploadTexture(lightmapTex.get(), QRhiTextureUploadDescription({ 0, 0, lightmapTexUpload }));
1777 QSSGRhiShaderResourceBindingList bindings;
1778 QRhiSampler *nearestSampler = rhiCtx->sampler(
1779 { QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None, QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
1780 bindings.addTexture(0, QRhiShaderResourceBinding::FragmentStage, lightmapTex.get(), nearestSampler);
1781 renderer->rhiQuadRenderer()->prepareQuad(rhiCtx, resUpd);
1782 const auto &shaderCache = renderer->contextInterface()->shaderCache();
1783 const auto &lmDilatePipeline = shaderCache->getBuiltInRhiShaders().getRhiLightmapDilateShader();
1784 if (!lmDilatePipeline) {
1785 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Failed to load shaders"));
1788 QSSGRhiGraphicsPipelineState dilatePs;
1789 dilatePs.viewport = viewport;
1790 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(dilatePs, lmDilatePipeline.get());
1791 renderer->rhiQuadRenderer()->recordRenderQuadPass(rhiCtx, &dilatePs, rhiCtxD->srb(bindings), rtDilate.get(), QSSGRhiQuadRenderer::UvCoords);
1792 resUpd = rhi->nextResourceUpdateBatch();
1793 QRhiReadbackResult dilateReadResult;
1794 resUpd->readBackTexture({ dilatedLightmapTex.get() }, &dilateReadResult);
1795 cb->resourceUpdate(resUpd);
1800 return dilateReadResult.data;
1803QVector<QVector3D> QSSGLightmapperPrivate::computeDirectLight(
int lmIdx)
1805 const QSSGBakedLightingModel &lm(bakedLightingModels[lmIdx]);
1811 if (!lm.model->castsShadows)
1814 const DrawInfo &drawInfo(drawInfos[lmIdx]);
1815 const char *vbase = drawInfo.vertexData.constData();
1816 const quint32 *ibase =
reinterpret_cast<
const quint32 *>(drawInfo.indexData.constData());
1818 const QSize sz = drawInfo.lightmapSize;
1819 const int w = sz.width();
1820 const int h = sz.height();
1821 constexpr int padding = GAUSS_HALF_KERNEL_SIZE;
1822 const int numPixelsFinal = w * h;
1824 QVector<QVector3D> grid(numPixelsFinal);
1825 QVector<quint32> mask(numPixelsFinal, PIXEL_VOID);
1828 const QVector<ModelTexel>& texels = modelTexels[lmIdx];
1829 for (
int pixelI = 0; pixelI < numPixelsFinal; ++pixelI) {
1830 const auto &entry = texels[pixelI];
1831 if (!entry.isValid())
1833 mask[pixelI] = PIXEL_UNSET;
1834 grid[pixelI] = sampleDirectLight(entry.worldPos, entry.normal,
false);
1837 if (std::all_of(grid.begin(), grid.end(), [](
const QVector3D &v) {
return v.isNull(); })) {
1841 floodFill(
reinterpret_cast<quint32 *>(mask.data()), h, w);
1844 constexpr int maxTileSize = MAX_TILE_SIZE / DIRECT_MAP_UPSCALE_FACTOR;
1845 const int numTilesX = (w + maxTileSize - 1) / maxTileSize;
1846 const int numTilesY = (h + maxTileSize - 1) / maxTileSize;
1849 for (
int tileY = 0; tileY < numTilesY; ++tileY) {
1850 for (
int tileX = 0; tileX < numTilesX; ++tileX) {
1852 const int startX = tileX * maxTileSize;
1853 const int startY = tileY * maxTileSize;
1855 const int tileWidth = qMin(maxTileSize, w - startX);
1856 const int tileHeight = qMin(maxTileSize, h - startY);
1858 const int currentTileWidth = tileWidth + 2 * padding;
1859 const int currentTileHeight = tileHeight + 2 * padding;
1861 const int wExp = currentTileWidth * DIRECT_MAP_UPSCALE_FACTOR;
1862 const int hExp = currentTileHeight * DIRECT_MAP_UPSCALE_FACTOR;
1863 const int numPixelsExpanded = wExp * hExp;
1865 QVector<quint32> maskTile(numPixelsExpanded, PIXEL_VOID);
1866 QVector<QVector3D> gridTile(numPixelsExpanded);
1869 const int pixelStartX = startX - padding;
1870 const int pixelStartY = startY - padding;
1871 const int pixelEndX = startX + tileWidth + padding;
1872 const int pixelEndY = startY + tileHeight + padding;
1874 const float minU = pixelStartX /
double(w);
1875 const float maxV = 1.0 - pixelStartY /
double(h);
1876 const float maxU = pixelEndX /
double(w);
1877 const float minV = 1.0 - pixelEndY /
double(h);
1880 QByteArray worldPositionsBuffer;
1881 QByteArray normalsBuffer;
1883 QSSGLightmapperPrivate::RasterResult raster = rasterizeLightmap(lmIdx,
1885 QVector2D(minU, minV),
1886 QVector2D(maxU, maxV));
1887 if (!raster.success)
1889 Q_ASSERT(raster.width * raster.height == numPixelsExpanded);
1890 worldPositionsBuffer = raster.worldPositions;
1891 normalsBuffer = raster.normals;
1894 QVector4D *worldPositions =
reinterpret_cast<QVector4D *>(worldPositionsBuffer.data());
1895 QVector4D *normals =
reinterpret_cast<QVector4D *>(normalsBuffer.data());
1897 for (
int pixelI = 0; pixelI < numPixelsExpanded; ++pixelI) {
1898 QVector3D position = worldPositions[pixelI].toVector3D();
1899 QVector3D normal = normals[pixelI].toVector3D();
1900 if (normal.isNull()) {
1901 maskTile[pixelI] = PIXEL_VOID;
1905 maskTile[pixelI] = PIXEL_UNSET;
1906 gridTile[pixelI] += sampleDirectLight(position, normal,
false);
1909 floodFill(
reinterpret_cast<quint32 *>(maskTile.data()), hExp, wExp);
1910 gridTile = applyGaussianBlur(gridTile, maskTile, wExp, hExp, 3.f);
1912 const int endX = qMin(w, startX + tileWidth);
1913 const int endY = qMin(h, startY + tileHeight);
1917 for (
int y = startY; y < endY; ++y) {
1918 const int ySrc = (padding + y - startY) * DIRECT_MAP_UPSCALE_FACTOR;
1919 Q_ASSERT(ySrc < hExp);
1920 for (
int x = startX; x < endX; ++x) {
1921 const int xSrc = (padding + x - startX) * DIRECT_MAP_UPSCALE_FACTOR;
1922 Q_ASSERT(xSrc < wExp);
1924 if (mask[y * w + x] == PIXEL_VOID)
1927 const int dstPixelI = y * w + x;
1930 for (
int sY = 0; sY < DIRECT_MAP_UPSCALE_FACTOR; ++sY) {
1931 for (
int sX = 0; sX < DIRECT_MAP_UPSCALE_FACTOR; ++sX) {
1932 int srcPixelI = (ySrc + sY) * wExp + (xSrc + sX);
1933 Q_ASSERT(srcPixelI < numPixelsExpanded);
1934 if (maskTile[srcPixelI] == PIXEL_VOID)
1936 average += gridTile[srcPixelI];
1944 grid[dstPixelI] = average / hits;
1949 progressTracker.directTileDone();
1953 QHash<Edge, EdgeUV> edgeUVMap;
1954 QVector<SeamUV> seams;
1956 for (SubMeshInfo &subMeshInfo : subMeshInfos[lmIdx]) {
1957 QVector<std::array<quint32, 3>> triangles;
1958 QVector<QVector3D> positions;
1959 QVector<QVector3D> normals;
1960 QVector<QVector2D> uvs;
1962 triangles.reserve(subMeshInfo.count / 3);
1963 positions.reserve(subMeshInfo.count);
1964 normals.reserve(subMeshInfo.count);
1965 uvs.reserve(subMeshInfo.count);
1967 for (quint32 i = 0; i < subMeshInfo.count / 3; ++i)
1968 triangles.push_back({ i * 3, i * 3 + 1, i * 3 + 2 });
1970 for (quint32 i = 0; i < subMeshInfo.count; ++i) {
1971 const quint32 idx = *(ibase + subMeshInfo.offset + i);
1972 const float *src =
reinterpret_cast<
const float *>(vbase + idx * drawInfo.vertexStride + drawInfo.positionOffset);
1976 positions.push_back(QVector3D(x, y, z));
1979 for (quint32 i = 0; i < subMeshInfo.count; ++i) {
1980 const quint32 idx = *(ibase + subMeshInfo.offset + i);
1981 const float *src =
reinterpret_cast<
const float *>(vbase + idx * drawInfo.vertexStride + drawInfo.normalOffset);
1985 normals.push_back(QVector3D(x, y, z));
1988 for (quint32 i = 0; i < subMeshInfo.count; ++i) {
1989 const quint32 idx = *(ibase + subMeshInfo.offset + i);
1990 const float *src =
reinterpret_cast<
const float *>(vbase + idx * drawInfo.vertexStride + drawInfo.lightmapUVOffset);
1993 uvs.push_back(QVector2D(x, 1.0f - y));
1996 for (
auto [i0, i1, i2] : triangles) {
1997 const QVector3D triVert[3] = { positions[i0], positions[i1], positions[i2] };
1998 const QVector3D triNorm[3] = { normals[i0], normals[i1], normals[i2] };
1999 const QVector2D triUV[3] = { uvs[i0], uvs[i1], uvs[i2] };
2001 for (
int i = 0; i < 3; ++i) {
2003 int i1 = (i + 1) % 3;
2004 if (vectorLessThan(triVert[i1], triVert[i0]))
2007 const Edge e = { { triVert[i0], triVert[i1] }, { triNorm[i0], triNorm[i1] } };
2008 const EdgeUV edgeUV = { { triUV[i0], triUV[i1] } };
2009 auto it = edgeUVMap.find(e);
2010 if (it == edgeUVMap.end()) {
2011 edgeUVMap.insert(e, edgeUV);
2012 }
else if (!qFuzzyCompare(it->uv[0], edgeUV.uv[0]) || !qFuzzyCompare(it->uv[1], edgeUV.uv[1])) {
2014 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])};
2015 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])};
2017 seams.append(SeamUV({ { eUV, itUV } }));
2028 QByteArray workBuf(grid.size() *
sizeof(QVector3D), Qt::Uninitialized);
2029 for (
int blendIter = 0; blendIter < LM_SEAM_BLEND_ITER_COUNT; ++blendIter) {
2030 memcpy(workBuf.data(), grid.constData(), grid.size() *
sizeof(QVector3D));
2031 for (
int seamIdx = 0, end = seams.size(); seamIdx != end; ++seamIdx) {
2032 const SeamUV &seam(seams[seamIdx]);
2033 blendLine(seam.uv[0][0],
2037 reinterpret_cast<
const float *>(workBuf.data()),
2038 reinterpret_cast<
float *>(grid.data()),
2041 blendLine(seam.uv[1][0],
2045 reinterpret_cast<
const float *>(workBuf.data()),
2046 reinterpret_cast<
float *>(grid.data()),
2057static inline float uniformRand(quint32 &state)
2059 state ^= state << 13;
2060 state ^= state >> 17;
2061 state ^= state << 5;
2062 return float(state) /
float(UINT32_MAX);
2065static inline QVector3D cosWeightedHemisphereSample(quint32 &state)
2067 const float r1 = uniformRand(state);
2068 const float r2 = uniformRand(state) * 2.0f *
float(M_PI);
2069 const float sqr1 = std::sqrt(r1);
2070 const float sqr1m = std::sqrt(1.0f - r1);
2071 return QVector3D(sqr1 * std::cos(r2), sqr1 * std::sin(r2), sqr1m);
2074QVector<QVector3D> QSSGLightmapperPrivate::computeIndirectLight(
int lmIdx,
int wgCount,
int wgSizePerGroup)
2076 const QVector<ModelTexel>& texels = modelTexels[lmIdx];
2077 QVector<QVector3D> result;
2078 result.resize(texels.size());
2080 QVector<QFuture<QVector3D>> wg(wgCount);
2082 for (
int i = 0; i < texels.size(); ++i) {
2083 const ModelTexel& lmPix = texels[i];
2084 if (!lmPix.isValid())
2087 ++indirectTexelsDone;
2088 for (
int wgIdx = 0; wgIdx < wgCount; ++wgIdx) {
2089 const int beginIdx = wgIdx * wgSizePerGroup;
2090 const int endIdx = qMin(beginIdx + wgSizePerGroup, options.indirectLightSamples);
2092 wg[wgIdx] = QtConcurrent::run([
this, wgIdx, beginIdx, endIdx, &lmPix] {
2094 quint32 state = QRandomGenerator(wgIdx).generate();
2095 for (
int sampleIdx = beginIdx; sampleIdx < endIdx; ++sampleIdx) {
2096 QVector3D position = lmPix.worldPos;
2097 QVector3D normal = lmPix.normal;
2098 QVector3D throughput(1.0f, 1.0f, 1.0f);
2099 QVector3D sampleResult;
2101 for (
int bounce = 0; bounce < options.indirectLightBounces; ++bounce) {
2102 if (options.useAdaptiveBias)
2103 position += vectorSign(normal) * vectorAbs(position * 0.0000002f);
2106 const QVector3D sample = cosWeightedHemisphereSample(state);
2109 const QVector3D v0 = qFuzzyCompare(qAbs(normal.z()), 1.0f)
2110 ? QVector3D(0.0f, 1.0f, 0.0f)
2111 : QVector3D(0.0f, 0.0f, 1.0f);
2112 const QVector3D tangent = QVector3D::crossProduct(v0, normal).normalized();
2113 const QVector3D bitangent = QVector3D::crossProduct(tangent, normal).normalized();
2114 QVector3D direction(
2115 tangent.x() * sample.x() + bitangent.x() * sample.y() + normal.x() * sample.z(),
2116 tangent.y() * sample.x() + bitangent.y() * sample.y() + normal.y() * sample.z(),
2117 tangent.z() * sample.x() + bitangent.z() * sample.y() + normal.z() * sample.z());
2118 direction.normalize();
2121 const float NdotL = qMax(0.0f, QVector3D::dotProduct(normal, direction));
2122 const float pdf = NdotL /
float(M_PI);
2123 if (qFuzzyIsNull(pdf))
2127 RayHit ray(position, direction, options.bias);
2128 if (!ray.intersect(rscene))
2132 const ModelTexel &hitEntry = texelForLightmapUV(ray.rayhit.hit.geomID,
2137 const bool hitBackFace = QVector3D::dotProduct(hitEntry.normal, direction) > 0.0f;
2142 const QVector3D brdf = hitEntry.baseColor.toVector3D() /
float(M_PI);
2145 sampleResult += throughput * hitEntry.emission;
2146 throughput *= brdf * NdotL / pdf;
2147 QVector3D directLight = sampleDirectLight(hitEntry.worldPos, hitEntry.normal,
true);
2148 sampleResult += throughput * directLight;
2152 const float p = qMax(qMax(throughput.x(), throughput.y()), throughput.z());
2153 if (p < uniformRand(state))
2160 position = hitEntry.worldPos;
2161 normal = hitEntry.normal;
2164 wgResult += sampleResult;
2170 QVector3D totalIndirect;
2171 for (
const auto &future : wg)
2172 totalIndirect += future.result();
2174 result[i] += totalIndirect * options.indirectLightFactor / options.indirectLightSamples;
2176 if (bakingControl.cancelled)
2179 progressTracker.indirectTexelDone(indirectTexelsDone, indirectTexelsTotal);
2185static QString stripQrcPrefix(
const QString &path)
2187 QString result = path;
2188 if (result.startsWith(QStringLiteral(
":/")))
2189 result.remove(0, 2);
2195static bool createDirectory(
const QString &filePath)
2197 QFileInfo fileInfo(filePath);
2198 QString dirPath = fileInfo.path();
2201 if (dir.exists(dirPath))
2204 if (!dir.mkpath(dirPath))
2210static bool isValidSavePath(
const QString &path) {
2211 const QFileInfo info = QFileInfo(path);
2212 if (!info.exists()) {
2213 return QFileInfo(info.dir().path()).isWritable();
2215 return info.isWritable() && !info.isDir();
2218static inline QString indexToMeshKey(
int index)
2220 return QStringLiteral(
"_mesh_%1").arg(index);
2223bool QSSGLightmapperPrivate::storeMeshes(QSharedPointer<QSSGLightmapWriter> writer)
2225 if (!isValidSavePath(outputPath)) {
2226 sendOutputInfo(QSSGLightmapper::BakingStatus::Failed,
2227 QStringLiteral(
"Source path %1 is not a writable location").arg(outputPath));
2231 for (
int i = 0; i < meshes.size(); ++i) {
2232 if (!writer->writeData(indexToMeshKey(i), QSSGLightmapIODataTag::Mesh, meshes[i]))
2239bool QSSGLightmapperPrivate::storeSceneMetadata(QSharedPointer<QSSGLightmapWriter> writer)
2241 QVariantMap metadata;
2243 metadata[QStringLiteral(
"qt_version")] = QString::fromUtf8(QT_VERSION_STR);
2244 metadata[QStringLiteral(
"bake_start_time")] = bakeStartTime;
2245 metadata[QStringLiteral(
"bake_end_time")] = QDateTime::currentMSecsSinceEpoch();
2247 QVariantMap metadata2;
2248 metadata2[QStringLiteral(
"opacityThreshold")] = options.opacityThreshold;
2249 metadata2[QStringLiteral(
"bias")] = options.bias;
2250 metadata2[QStringLiteral(
"useAdaptiveBias")] = options.useAdaptiveBias;
2251 metadata2[QStringLiteral(
"indirectLightEnabled")] = options.indirectLightEnabled;
2252 metadata2[QStringLiteral(
"indirectLightSamples")] = options.indirectLightSamples;
2253 metadata2[QStringLiteral(
"indirectLightWorkgroupSize")] = options.indirectLightWorkgroupSize;
2254 metadata2[QStringLiteral(
"indirectLightBounces")] = options.indirectLightBounces;
2255 metadata2[QStringLiteral(
"indirectLightFactor")] = options.indirectLightFactor;
2256 metadata2[QStringLiteral(
"denoiseSigma")] = options.sigma;
2257 metadata2[QStringLiteral(
"texelsPerUnit")] = options.texelsPerUnit;
2259 metadata[QStringLiteral(
"options")] = metadata2;
2260 return writer->writeMap(QString::fromUtf8(KEY_SCENE_METADATA), QSSGLightmapIODataTag::SceneMetadata, metadata);
2263bool QSSGLightmapperPrivate::storeMetadata(
int lmIdx, QSharedPointer<QSSGLightmapWriter> writer)
2265 const QSSGBakedLightingModel &lm(bakedLightingModels[lmIdx]);
2266 const DrawInfo &drawInfo(drawInfos[lmIdx]);
2268 QVariantMap metadata;
2269 metadata[QStringLiteral(
"width")] = drawInfos[lmIdx].lightmapSize.width();
2270 metadata[QStringLiteral(
"height")] = drawInfos[lmIdx].lightmapSize.height();
2271 metadata[QStringLiteral(
"mesh_key")] = indexToMeshKey(drawInfo.meshIndex);
2273 return writer->writeMap(lm.model->lightmapKey, QSSGLightmapIODataTag::Metadata, metadata);
2276bool QSSGLightmapperPrivate::storeScale(
int lmIdx, QSharedPointer<QSSGLightmapWriter> writer)
2278 const QSSGBakedLightingModel &lm(bakedLightingModels[lmIdx]);
2279 const DrawInfo &drawInfo(drawInfos[lmIdx]);
2282 QDataStream stream(&buffer, QIODevice::WriteOnly);
2283 stream << drawInfo.scale;
2284 return writer->writeData(lm.model->lightmapKey, QSSGLightmapIODataTag::OriginalScale, buffer);
2287bool QSSGLightmapperPrivate::storeDirectLightData(
int lmIdx,
const QVector<QVector3D> &directLight, QSharedPointer<QSSGLightmapWriter> writer)
2289 const QSSGBakedLightingModel &lm(bakedLightingModels[lmIdx]);
2290 const int numTexels = modelTexels[lmIdx].size();
2292 QByteArray directFP32(numTexels * 4 *
sizeof(
float), Qt::Uninitialized);
2293 float *directFloatPtr =
reinterpret_cast<
float *>(directFP32.data());
2295 for (
int i = 0; i < numTexels; ++i) {
2296 const auto &lmPix = modelTexels[lmIdx][i];
2297 if (lmPix.isValid()) {
2298 *directFloatPtr++ = directLight[i].x();
2299 *directFloatPtr++ = directLight[i].y();
2300 *directFloatPtr++ = directLight[i].z();
2301 *directFloatPtr++ = 1.0f;
2303 *directFloatPtr++ = 0.0f;
2304 *directFloatPtr++ = 0.0f;
2305 *directFloatPtr++ = 0.0f;
2306 *directFloatPtr++ = 0.0f;
2310 const QByteArray dilated = dilate(drawInfos[lmIdx].lightmapSize, directFP32);
2312 if (dilated.isEmpty())
2315 writer->writeF32Image(lm.model->lightmapKey, QSSGLightmapIODataTag::Texture_Direct, dilated);
2320bool QSSGLightmapperPrivate::storeIndirectLightData(
int lmIdx,
const QVector<QVector3D> &indirectLight, QSharedPointer<QSSGLightmapWriter> writer)
2322 const QSSGBakedLightingModel &lm(bakedLightingModels[lmIdx]);
2323 const int numTexels = modelTexels[lmIdx].size();
2325 QByteArray lightmapFP32(numTexels * 4 *
sizeof(
float), Qt::Uninitialized);
2326 float *lightmapFloatPtr =
reinterpret_cast<
float *>(lightmapFP32.data());
2328 for (
int i = 0; i < numTexels; ++i) {
2329 const auto &lmPix = modelTexels[lmIdx][i];
2330 if (lmPix.isValid()) {
2331 *lightmapFloatPtr++ = indirectLight[i].x();
2332 *lightmapFloatPtr++ = indirectLight[i].y();
2333 *lightmapFloatPtr++ = indirectLight[i].z();
2334 *lightmapFloatPtr++ = 1.0f;
2336 *lightmapFloatPtr++ = 0.0f;
2337 *lightmapFloatPtr++ = 0.0f;
2338 *lightmapFloatPtr++ = 0.0f;
2339 *lightmapFloatPtr++ = 0.0f;
2343 QByteArray dilated = dilate(drawInfos[lmIdx].lightmapSize, lightmapFP32);
2345 if (dilated.isEmpty())
2351 const DrawInfo &drawInfo(drawInfos[lmIdx]);
2352 const char *vbase = drawInfo.vertexData.constData();
2353 const quint32 *ibase =
reinterpret_cast<
const quint32 *>(drawInfo.indexData.constData());
2357 qsizetype assembledVertexCount = 0;
2358 for (SubMeshInfo &subMeshInfo : subMeshInfos[lmIdx])
2359 assembledVertexCount += subMeshInfo.count;
2360 QVector<QVector3D> smPos(assembledVertexCount);
2361 QVector<QVector3D> smNormal(assembledVertexCount);
2362 QVector<QVector2D> smCoord(assembledVertexCount);
2363 qsizetype vertexIdx = 0;
2364 for (SubMeshInfo &subMeshInfo : subMeshInfos[lmIdx]) {
2365 for (quint32 i = 0; i < subMeshInfo.count; ++i) {
2366 const quint32 idx = *(ibase + subMeshInfo.offset + i);
2367 const float *src =
reinterpret_cast<
const float *>(vbase + idx * drawInfo.vertexStride + drawInfo.positionOffset);
2371 smPos[vertexIdx] = QVector3D(x, y, z);
2372 src =
reinterpret_cast<
const float *>(vbase + idx * drawInfo.vertexStride + drawInfo.normalOffset);
2376 smNormal[vertexIdx] = QVector3D(x, y, z);
2377 src =
reinterpret_cast<
const float *>(vbase + idx * drawInfo.vertexStride + drawInfo.lightmapUVOffset);
2380 smCoord[vertexIdx] = QVector2D(x, y);
2385 QHash<Edge, EdgeUV> edgeUVMap;
2386 QVector<SeamUV> seams;
2387 for (vertexIdx = 0; vertexIdx < assembledVertexCount; vertexIdx += 3) {
2388 QVector3D triVert[3] = { smPos[vertexIdx], smPos[vertexIdx + 1], smPos[vertexIdx + 2] };
2389 QVector3D triNorm[3] = { smNormal[vertexIdx], smNormal[vertexIdx + 1], smNormal[vertexIdx + 2] };
2390 QVector2D triUV[3] = { smCoord[vertexIdx], smCoord[vertexIdx + 1], smCoord[vertexIdx + 2] };
2392 for (
int i = 0; i < 3; ++i) {
2394 int i1 = (i + 1) % 3;
2395 if (vectorLessThan(triVert[i1], triVert[i0]))
2399 { triVert[i0], triVert[i1] },
2400 { triNorm[i0], triNorm[i1] }
2402 const EdgeUV edgeUV = { { triUV[i0], triUV[i1] } };
2403 auto it = edgeUVMap.find(e);
2404 if (it == edgeUVMap.end()) {
2405 edgeUVMap.insert(e, edgeUV);
2406 }
else if (!qFuzzyCompare(it->uv[0], edgeUV.uv[0]) || !qFuzzyCompare(it->uv[1], edgeUV.uv[1])) {
2408 seams.append(SeamUV({ { edgeUV.uv, it->uv } }));
2416 QByteArray workBuf(dilated.size(), Qt::Uninitialized);
2417 for (
int blendIter = 0; blendIter < LM_SEAM_BLEND_ITER_COUNT; ++blendIter) {
2418 memcpy(workBuf.data(), dilated.constData(), dilated.size());
2419 for (
int seamIdx = 0, end = seams.size(); seamIdx != end; ++seamIdx) {
2420 const SeamUV &seam(seams[seamIdx]);
2421 blendLine(seam.uv[0][0], seam.uv[0][1],
2422 seam.uv[1][0], seam.uv[1][1],
2423 reinterpret_cast<
const float *>(workBuf.data()),
2424 reinterpret_cast<
float *>(dilated.data()),
2425 drawInfos[lmIdx].lightmapSize);
2426 blendLine(seam.uv[1][0], seam.uv[1][1],
2427 seam.uv[0][0], seam.uv[0][1],
2428 reinterpret_cast<
const float *>(workBuf.data()),
2429 reinterpret_cast<
float *>(dilated.data()),
2430 drawInfos[lmIdx].lightmapSize);
2434 writer->writeF32Image(lm.model->lightmapKey, QSSGLightmapIODataTag::Texture_Indirect, dilated);
2439bool QSSGLightmapperPrivate::storeMaskImage(
int lmIdx, QSharedPointer<QSSGLightmapWriter> writer)
2441 constexpr quint32 PIXEL_VOID = 0;
2442 constexpr quint32 PIXEL_UNSET = -1;
2444 const QSSGBakedLightingModel &lm(bakedLightingModels[lmIdx]);
2445 const int numTexels = modelTexels[lmIdx].size();
2447 QByteArray mask(numTexels *
sizeof(quint32), Qt::Uninitialized);
2448 quint32 *maskUIntPtr =
reinterpret_cast<quint32 *>(mask.data());
2450 for (
int i = 0; i < numTexels; ++i) {
2451 *maskUIntPtr++ = modelTexels[lmIdx][i].isValid() ? PIXEL_UNSET : PIXEL_VOID;
2454 const int rows = drawInfos[lmIdx].lightmapSize.height();
2455 const int cols = drawInfos[lmIdx].lightmapSize.width();
2460 floodFill(
reinterpret_cast<quint32 *>(mask.data()), rows, cols);
2462 writer->writeF32Image(lm.model->lightmapKey, QSSGLightmapIODataTag::Mask, mask);
2467bool QSSGLightmapperPrivate::denoiseLightmaps()
2469 QElapsedTimer denoiseTimer;
2470 denoiseTimer.start();
2473 const QString inPath = QFileInfo(outputPath + QStringLiteral(
".raw")).absoluteFilePath();
2474 QSharedPointer<QSSGLightmapLoader> tmpFile = QSSGLightmapLoader::open(inPath);
2476 sendOutputInfo(QSSGLightmapper::BakingStatus::Error, QStringLiteral(
"Could not read file '%1'").arg(inPath));
2481 const QString outPath = QFileInfo(outputPath).absoluteFilePath();
2482 QSharedPointer<QSSGLightmapWriter> finalFile = QSSGLightmapWriter::open(outPath);
2484 sendOutputInfo(QSSGLightmapper::BakingStatus::Error, QStringLiteral(
"Could not read file '%1'").arg(outPath));
2488 QSet<QString> lightmapKeys;
2489 for (
const auto &[key, tag] : tmpFile->getKeys()) {
2490 if (tag == QSSGLightmapIODataTag::SceneMetadata)
continue;
2492 if (tag != QSSGLightmapIODataTag::Texture_Direct && tag != QSSGLightmapIODataTag::Texture_Indirect
2493 && tag != QSSGLightmapIODataTag::Mask) {
2495 finalFile->writeData(key, tag, tmpFile->readData(key, tag));
2496 }
else if (tag == QSSGLightmapIODataTag::Texture_Direct) {
2497 lightmapKeys.insert(key);
2501 QRhi *rhi = rhiCtxInterface->rhiContext()->rhi();
2503 if (!rhi->isFeatureSupported(QRhi::Compute)) {
2504 qFatal(
"Compute is not supported, denoising disabled");
2508 const int bakedLightingModelCount = lightmapKeys.size();
2509 if (bakedLightingModelCount == 0)
2513 if (QFile f(QStringLiteral(
":/res/rhishaders/nlm_denoise.comp.qsb")); f.open(QIODevice::ReadOnly)) {
2514 shader = QShader::fromSerialized(f.readAll());
2516 qFatal() <<
"Could not find denoise shader";
2519 Q_ASSERT(shader.isValid());
2521 QVariantMap sceneMetadata = tmpFile->readMap(QString::fromUtf8(KEY_SCENE_METADATA), QSSGLightmapIODataTag::SceneMetadata);
2522 sceneMetadata[QStringLiteral(
"denoise_start_time")] = QDateTime::currentMSecsSinceEpoch();
2525 for (
const QString &key : lightmapKeys) {
2527 auto incrementTracker = QScopeGuard([
this, lmIdx, bakedLightingModelCount]() {
2528 progressTracker.denoisedModelDone(lmIdx + 1, bakedLightingModelCount);
2532 sendOutputInfo(QSSGLightmapper::BakingStatus::Info,
2533 QStringLiteral(
"[%2/%3] denoising '%1'").arg(key).arg(lmIdx + 1).arg(bakedLightingModelCount));
2535 QVariantMap metadata = tmpFile->readMap(key, QSSGLightmapIODataTag::Metadata);
2536 QByteArray indirect = tmpFile->readF32Image(key, QSSGLightmapIODataTag::Texture_Indirect);
2537 QByteArray direct = tmpFile->readF32Image(key, QSSGLightmapIODataTag::Texture_Direct);
2538 QByteArray mask = tmpFile->readU32Image(key, QSSGLightmapIODataTag::Mask);
2540 if (!metadata.contains(QStringLiteral(
"width")) || !metadata.contains(QStringLiteral(
"height"))
2541 || indirect.isEmpty() || direct.isEmpty() || mask.isEmpty()) {
2542 sendOutputInfo(QSSGLightmapper::BakingStatus::Error,
2543 QStringLiteral(
"[%2/%3] Failed to denoise '%1'").arg(key).arg(lmIdx + 1).arg(bakedLightingModelCount));
2547 QRhiCommandBuffer *cb =
nullptr;
2548 cb = rhiCtxInterface->rhiContext()->commandBuffer();
2551 QRhiResourceUpdateBatch *u = rhi->nextResourceUpdateBatch();
2554 const int w = metadata[QStringLiteral(
"width")].toInt();
2555 const int h = metadata[QStringLiteral(
"height")].toInt();
2556 const QSize size(w, h);
2557 const int numPixels = w * h;
2559 Q_ASSERT(qsizetype(numPixels *
sizeof(
float) * 4) == indirect.size());
2560 Q_ASSERT(qsizetype(numPixels *
sizeof(
float) * 4) == direct.size());
2561 Q_ASSERT(qsizetype(numPixels *
sizeof(quint32)) == mask.size());
2563 QScopedPointer<QRhiBuffer> buffIn(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, 3 * numPixels *
sizeof(
float)));
2564 QScopedPointer<QRhiBuffer> buffCount(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, numPixels *
sizeof(quint32)));
2565 QScopedPointer<QRhiBuffer> buffOut(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, 3 * numPixels *
sizeof(quint32)));
2566 QScopedPointer<QRhiTexture> texMask(rhi->newTexture(QRhiTexture::RGBA8, size, 1, QRhiTexture::UsedWithLoadStore));
2569 buffCount->create();
2573 u->uploadTexture(texMask.data(), QImage(
reinterpret_cast<
const uchar *>(mask.constData()), w, h, QImage::Format_RGBA8888));
2577 QByteArray inArray(3 * numPixels *
sizeof(
float), 0);
2578 QByteArray count(numPixels *
sizeof(quint32), 0);
2579 QByteArray outArray(3 * numPixels *
sizeof(
float), 0);
2581 QVector3D* inDst =
reinterpret_cast<QVector3D*>(inArray.data());
2582 const QVector4D* indirectSrc =
reinterpret_cast<
const QVector4D*>(indirect.data());
2583 for (
int i = 0; i < numPixels; ++i) {
2584 inDst[i][0] = indirectSrc[i][0] * 256.f;
2585 inDst[i][1] = indirectSrc[i][1] * 256.f;
2586 inDst[i][2] = indirectSrc[i][2] * 256.f;
2588 u->uploadStaticBuffer(buffIn.data(), inArray);
2589 u->uploadStaticBuffer(buffCount.data(), count);
2590 u->uploadStaticBuffer(buffOut.data(), outArray);
2600 settings.sigma = options.sigma;
2602 settings.height = h;
2604 QScopedPointer<QRhiBuffer> settingsBuffer(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer,
sizeof(settings)));
2605 settingsBuffer->create();
2607 u->updateDynamicBuffer(settingsBuffer.data(), 0,
sizeof(settings), &settings);
2609 QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings());
2612 QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::ComputeStage, settingsBuffer.data()),
2613 QRhiShaderResourceBinding::bufferLoad(1, QRhiShaderResourceBinding::ComputeStage, buffIn.data()),
2614 QRhiShaderResourceBinding::imageLoad(2, QRhiShaderResourceBinding::ComputeStage, texMask.data(), 0),
2615 QRhiShaderResourceBinding::bufferLoadStore(3, QRhiShaderResourceBinding::ComputeStage, buffOut.data()),
2616 QRhiShaderResourceBinding::bufferLoadStore(4, QRhiShaderResourceBinding::ComputeStage, buffCount.data())
2620 QScopedPointer<QRhiComputePipeline> pipeline(rhi->newComputePipeline());
2621 pipeline->setShaderStage({ QRhiShaderStage::Compute, shader });
2622 pipeline->setShaderResourceBindings(srb.data());
2625 cb->beginComputePass(u);
2626 cb->setComputePipeline(pipeline.data());
2627 cb->setShaderResources();
2628 constexpr int local_size_x = 8;
2629 constexpr int local_size_y = 8;
2630 constexpr int local_size_z = 1;
2631 cb->dispatch((w + local_size_x - 1) / local_size_x, (h + local_size_y - 1) / local_size_y, local_size_z);
2633 u = rhi->nextResourceUpdateBatch();
2638 QByteArray outCount;
2640 QRhiReadbackResult readResultOut;
2641 readResultOut.completed = [&] {
2642 outOut = readResultOut.data;
2643 Q_ASSERT(outOut.size() == qsizetype(numPixels *
sizeof(quint32) * 3));
2645 QRhiReadbackResult readResultCount;
2646 readResultCount.completed = [&] {
2647 outCount = readResultCount.data;
2648 Q_ASSERT(outCount.size() == qsizetype(numPixels *
sizeof(quint32)));
2651 u->readBackBuffer(buffOut.get(), 0, 3 * numPixels *
sizeof(quint32), &readResultOut);
2652 u->readBackBuffer(buffCount.get(), 0, numPixels *
sizeof(quint32), &readResultCount);
2654 cb->endComputePass(u);
2658 final.resize(indirect.size());
2659 memcpy(final.data(), indirect.data(), indirect.size());
2661 QVector4D* res =
reinterpret_cast<QVector4D*>(final.data());
2662 quint32* ptrRGB =
reinterpret_cast<quint32*>(outOut.data());
2663 quint32* ptrCount =
reinterpret_cast<quint32*>(outCount.data());
2664 for (
int y = 0; y < h; ++y) {
2665 for (
int x = 0; x < w; ++x) {
2666 const int idxDst = y * w + x;
2667 const int idxDst1 = 3 * idxDst;
2668 Q_ASSERT(idxDst1 < numPixels * 3);
2669 quint32 cnt = ptrCount[idxDst];
2671 float r = (ptrRGB[idxDst1] / 256.f) / 1000.f;
2672 float g = (ptrRGB[idxDst1 + 1] / 256.f) / 1000.f;
2673 float b = (ptrRGB[idxDst1 + 2] / 256.f) / 1000.f;
2675 res[idxDst][0] = r / cnt;
2676 res[idxDst][1] = g / cnt;
2677 res[idxDst][2] = b / cnt;
2682 std::array<
float, 4> *imagePtr =
reinterpret_cast<std::array<
float, 4>*>(
const_cast<
char*>(final.data()));
2683 std::array<
float, 4> *directPtr =
reinterpret_cast<std::array<
float, 4>*>(
const_cast<
char*>(direct.data()));
2684 for (
int i = 0; i < numPixels; ++i) {
2685 imagePtr[i][0] += directPtr[i][0];
2686 imagePtr[i][1] += directPtr[i][1];
2687 imagePtr[i][2] += directPtr[i][2];
2689 Q_ASSERT(imagePtr[i][3] == directPtr[i][3]);
2690 Q_ASSERT(imagePtr[i][3] == 1.f || imagePtr[i][3] == 0.f);
2693 finalFile->writeF32Image(key, QSSGLightmapIODataTag::Texture_Final, final);
2696 sceneMetadata[QStringLiteral(
"denoise_end_time")] = QDateTime::currentMSecsSinceEpoch();
2697 auto optionsMap = sceneMetadata[QStringLiteral(
"options")].toMap();
2698 optionsMap[QStringLiteral(
"denoiseSigma")] = options.sigma;
2699 sceneMetadata[QStringLiteral(
"options")] = optionsMap;
2701 finalFile->writeMap(QString::fromUtf8(KEY_SCENE_METADATA), QSSGLightmapIODataTag::SceneMetadata, sceneMetadata);
2703 if (!finalFile->close()) {
2704 sendOutputInfo(QSSGLightmapper::BakingStatus::Error, QStringLiteral(
"Could not save file '%1'").arg(outPath));
2712bool QSSGLightmapperPrivate::userCancelled()
2714 if (bakingControl.cancelled) {
2715 sendOutputInfo(QSSGLightmapper::BakingStatus::Cancelled,
2716 QStringLiteral(
"Cancelled by user"));
2718 return bakingControl.cancelled;
2721void QSSGLightmapperPrivate::sendOutputInfo(QSSGLightmapper::BakingStatus type, std::optional<QString> msg,
bool outputToConsole,
bool outputConsoleTimeRemanining)
2723 if (outputToConsole) {
2724 QString consoleMessage;
2728 case QSSGLightmapper::BakingStatus::None:
2730 case QSSGLightmapper::BakingStatus::Info:
2731 consoleMessage = QStringLiteral(
"[lm] Info");
2733 case QSSGLightmapper::BakingStatus::Error:
2734 consoleMessage = QStringLiteral(
"[lm] Error");
2736 case QSSGLightmapper::BakingStatus::Warning:
2737 consoleMessage = QStringLiteral(
"[lm] Warning");
2739 case QSSGLightmapper::BakingStatus::Cancelled:
2740 consoleMessage = QStringLiteral(
"[lm] Cancelled");
2742 case QSSGLightmapper::BakingStatus::Failed:
2743 consoleMessage = QStringLiteral(
"[lm] Failed");
2745 case QSSGLightmapper::BakingStatus::Complete:
2746 consoleMessage = QStringLiteral(
"[lm] Complete");
2750 if (msg.has_value())
2751 consoleMessage.append(QStringLiteral(
": ") + msg.value());
2752 else if (outputConsoleTimeRemanining) {
2753 const QString timeRemaining = estimatedTimeRemaining >= 0 ? formatDuration(estimatedTimeRemaining,
false)
2754 : QStringLiteral(
"Estimating...");
2755 consoleMessage.append(QStringLiteral(
": Time remaining: ") + timeRemaining);
2758 if (type == QSSGLightmapper::BakingStatus::Error || type == QSSGLightmapper::BakingStatus::Warning)
2759 qWarning() << consoleMessage;
2761 qInfo() << consoleMessage;
2764 if (outputCallback) {
2765 QVariantMap payload;
2766 payload[QStringLiteral(
"status")] = (
int)type;
2767 payload[QStringLiteral(
"stage")] = stage;
2768 payload[QStringLiteral(
"message")] = msg.value_or(QString());
2769 payload[QStringLiteral(
"totalTimeRemaining")] = estimatedTimeRemaining;
2770 payload[QStringLiteral(
"totalProgress")] = totalProgress;
2771 outputCallback(payload, &bakingControl);
2775void QSSGLightmapperPrivate::updateStage(
const QString &newStage)
2777 if (newStage == stage)
2781 if (outputCallback) {
2782 QVariantMap payload;
2783 payload[QStringLiteral(
"stage")] = stage;
2784 outputCallback(payload, &bakingControl);
2788bool QSSGLightmapper::bake()
2790 d->totalTimer.start();
2791 d->bakeStartTime = QDateTime::currentMSecsSinceEpoch();
2793 d->updateStage(QStringLiteral(
"Preparing"));
2794 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral(
"Preparing for bake..."));
2796 if (!isValidSavePath(d->outputPath)) {
2797 d->updateStage(QStringLiteral(
"Failed"));
2798 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed,
2799 QStringLiteral(
"Source path %1 is not a writable location").arg(d->outputPath));
2803 if (d->bakedLightingModels.isEmpty()) {
2804 d->updateStage(QStringLiteral(
"Failed"));
2805 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed, QStringLiteral(
"No Models to bake"));
2811 if (!d->commitGeometry()) {
2812 d->updateStage(QStringLiteral(
"Failed"));
2813 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed, QStringLiteral(
"Baking failed"));
2818 d->initMutex.lock();
2819 d->initCondition.wakeAll();
2820 d->initMutex.unlock();
2822 if (d->userCancelled()) {
2823 d->updateStage(QStringLiteral(
"Cancelled"));
2828 const int bakedLightingModelCount = d->bakedLightingModels.size();
2831 quint32 numDirectTiles = 0;
2832 for (
int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
2833 QSSGBakedLightingModel &lm = d->bakedLightingModels[lmIdx];
2836 if (!lm.model->hasLightmap())
2838 if (!lm.model->castsShadows)
2841 const auto &drawInfo = d->drawInfos[lmIdx];
2842 const QSize sz = drawInfo.lightmapSize;
2843 const int w = sz.width();
2844 const int h = sz.height();
2845 constexpr int maxTileSize = MAX_TILE_SIZE / DIRECT_MAP_UPSCALE_FACTOR;
2846 const int numTilesX = (w + maxTileSize - 1) / maxTileSize;
2847 const int numTilesY = (h + maxTileSize - 1) / maxTileSize;
2849 numDirectTiles += numTilesX * numTilesY;
2852 d->progressTracker.initBake(d->options.indirectLightSamples, d->options.indirectLightBounces);
2853 d->progressTracker.setTotalDirectTiles(numDirectTiles);
2857 if (!d->prepareLightmaps()) {
2858 d->updateStage(QStringLiteral(
"Failed"));
2859 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed, QStringLiteral(
"Baking failed"));
2863 if (d->userCancelled()) {
2864 d->updateStage(QStringLiteral(
"Cancelled"));
2868 if (!d->verifyLights()) {
2869 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed,
2870 QStringLiteral(
"Did not find any lights with baking enabled or any "
2871 "emissive models in the scene."));
2875 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info,
2876 QStringLiteral(
"Total emissive models registered: %1")
2877 .arg(d->emissiveModelCount));
2883 const int wgSizePerGroup = qMax(1, d->options.indirectLightWorkgroupSize);
2884 const int wgCount = (d->options.indirectLightSamples / wgSizePerGroup) + (d->options.indirectLightSamples % wgSizePerGroup ? 1: 0);
2886 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral(
"Sample count: %1, Workgroup size: %2, Max bounces: %3, Multiplier: %4").
2887 arg(d->options.indirectLightSamples).
2888 arg(wgSizePerGroup).
2889 arg(d->options.indirectLightBounces).
2890 arg(d->options.indirectLightFactor));
2894 QSharedPointer<QTemporaryFile> workFile = QSharedPointer<QTemporaryFile>::create(QDir::tempPath() +
"/qt_lightmapper_work_file_XXXXXX"_L1);
2896 QElapsedTimer timer;
2901 d->updateStage(QStringLiteral(
"Storing Metadata"));
2902 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral(
"Storing metadata..."));
2903 auto writer = QSSGLightmapWriter::open(workFile);
2905 for (
int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
2906 if (d->userCancelled()) {
2907 d->updateStage(QStringLiteral(
"Cancelled"));
2910 QSSGBakedLightingModel &lm = d->bakedLightingModels[lmIdx];
2911 if (!lm.model->hasLightmap())
2914 if (!d->storeMetadata(lmIdx, writer)) {
2915 d->updateStage(QStringLiteral(
"Failed"));
2916 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed,
2917 QStringLiteral(
"[%1/%2] Failed to store metadata for '%3'")
2919 .arg(bakedLightingModelCount)
2920 .arg(lm.model->lightmapKey));
2926 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral(
"Storing scale..."));
2927 for (
int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
2928 if (d->userCancelled()) {
2929 d->updateStage(QStringLiteral(
"Cancelled"));
2932 QSSGBakedLightingModel &lm = d->bakedLightingModels[lmIdx];
2933 if (!lm.model->hasLightmap())
2936 if (!d->storeScale(lmIdx, writer)) {
2937 d->updateStage(QStringLiteral(
"failed"));
2938 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed,
2939 QStringLiteral(
"[%1/%2] Failed to store scale for '%3'")
2941 .arg(bakedLightingModelCount)
2942 .arg(lm.model->lightmapKey));
2948 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral(
"Storing mask images..."));
2949 for (
int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
2950 if (d->userCancelled()) {
2951 d->updateStage(QStringLiteral(
"Cancelled"));
2954 QSSGBakedLightingModel &lm = d->bakedLightingModels[lmIdx];
2955 if (!lm.model->hasLightmap())
2958 if (!d->storeMaskImage(lmIdx, writer)) {
2959 d->updateStage(QStringLiteral(
"Failed"));
2960 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed,
2961 QStringLiteral(
"[%1/%2] Failed to store mask for '%3'")
2963 .arg(bakedLightingModelCount)
2964 .arg(lm.model->lightmapKey));
2968 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info,
2969 QStringLiteral(
"Took %1").arg(formatDuration(timer.restart())));
2971 if (d->userCancelled()) {
2972 d->updateStage(QStringLiteral(
"Cancelled"));
2978 d->updateStage(QStringLiteral(
"Computing Direct Light"));
2979 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral(
"Computing direct light..."));
2980 for (
int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
2981 if (d->userCancelled()) {
2982 d->updateStage(QStringLiteral(
"Cancelled"));
2985 QSSGBakedLightingModel &lm = d->bakedLightingModels[lmIdx];
2986 if (!lm.model->hasLightmap())
2990 const QVector<QVector3D> directLight = d->computeDirectLight(lmIdx);
2991 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info,
2992 QStringLiteral(
"[%1/%2] '%3' took %4")
2994 .arg(bakedLightingModelCount)
2995 .arg(lm.model->lightmapKey)
2996 .arg(formatDuration(timer.elapsed())));
2998 if (directLight.empty()) {
2999 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed,
3000 QStringLiteral(
"[%1/%2] Failed to compute for '%3'")
3002 .arg(bakedLightingModelCount)
3003 .arg(lm.model->lightmapKey));
3007 if (!d->storeDirectLightData(lmIdx, directLight, writer)) {
3008 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed,
3009 QStringLiteral(
"[%1/%2] Failed to store data for '%3'")
3011 .arg(bakedLightingModelCount)
3012 .arg(lm.model->lightmapKey));
3017 if (d->userCancelled()) {
3018 d->updateStage(QStringLiteral(
"Cancelled"));
3024 if (d->options.indirectLightEnabled) {
3025 d->indirectTexelsTotal = std::accumulate(d->numValidTexels.begin(), d->numValidTexels.end(), quint64(0));
3026 d->updateStage(QStringLiteral(
"Computing Indirect Light"));
3027 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info,
3028 QStringLiteral(
"Computing indirect light..."));
3029 d->progressTracker.setStage(Stage::Indirect);
3030 for (
int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
3031 if (d->userCancelled()) {
3032 d->updateStage(QStringLiteral(
"Cancelled"));
3035 QSSGBakedLightingModel &lm = d->bakedLightingModels[lmIdx];
3036 if (!lm.model->hasLightmap())
3040 const QVector<QVector3D> indirectLight = d->computeIndirectLight(lmIdx, wgCount, wgSizePerGroup);
3041 if (indirectLight.empty()) {
3042 d->updateStage(QStringLiteral(
"Failed"));
3043 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed,
3044 QStringLiteral(
"[%1/%2] Failed to compute '%3'")
3046 .arg(bakedLightingModelCount)
3047 .arg(lm.model->lightmapKey));
3051 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info,
3052 QStringLiteral(
"[%1/%2] '%3' took %4")
3054 .arg(bakedLightingModelCount)
3055 .arg(lm.model->lightmapKey)
3056 .arg(formatDuration(timer.elapsed())));
3058 if (d->userCancelled()) {
3059 d->updateStage(QStringLiteral(
"Cancelled"));
3063 if (!d->storeIndirectLightData(lmIdx, indirectLight, writer)) {
3064 d->updateStage(QStringLiteral(
"Failed"));
3065 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed,
3066 QStringLiteral(
"[%1/%2] Failed to store data for '%3'")
3068 .arg(bakedLightingModelCount)
3069 .arg(lm.model->lightmapKey));
3077 if (!d->storeMeshes(writer)) {
3078 d->updateStage(QStringLiteral(
"Failed"));
3079 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed, QStringLiteral(
"Failed to store meshes"));
3083 if (d->userCancelled()) {
3084 d->updateStage(QStringLiteral(
"Cancelled"));
3090 d->updateStage(QStringLiteral(
"Storing Scene Metadata"));
3091 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral(
"Storing scene metadata..."));
3092 if (!d->storeSceneMetadata(writer)) {
3093 d->updateStage(QStringLiteral(
"Failed"));
3094 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed,
3095 QStringLiteral(
"Failed to store scene metadata"));
3100 if (!writer->close()) {
3101 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Error,
3102 QStringLiteral(
"Failed to save temp file to %1").arg(workFile->fileName()));
3106 const QString tmpPath = QFileInfo(d->outputPath).absoluteFilePath() +
".raw"_L1;
3107 QFile::remove(tmpPath);
3108 if (!workFile->copy(tmpPath)) {
3109 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Error,
3110 QStringLiteral(
"Failed to copy temp file to %1").arg(tmpPath));
3114 if (d->userCancelled()) {
3115 d->updateStage(QStringLiteral(
"Cancelled"));
3121 d->progressTracker.setStage(Stage::Denoise);
3122 d->updateStage(QStringLiteral(
"Denoising"));
3123 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral(
"Denoising..."));
3125 if (!d->denoiseLightmaps()) {
3126 d->updateStage(QStringLiteral(
"Failed"));
3127 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed, QStringLiteral(
"Denoising failed"));
3130 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral(
"Took %1").arg(formatDuration(timer.elapsed())));
3132 if (d->userCancelled()) {
3133 d->updateStage(QStringLiteral(
"Cancelled"));
3139 d->totalProgress = 1.0;
3140 d->estimatedTimeRemaining = -1;
3141 d->updateStage(QStringLiteral(
"Done"));
3142 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info,
3143 QStringLiteral(
"Baking took %1").arg(formatDuration(d->totalTimer.elapsed())));
3144 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Complete, std::nullopt);
3148bool QSSGLightmapper::denoise() {
3151 d->initMutex.lock();
3152 d->initCondition.wakeAll();
3153 d->initMutex.unlock();
3155 QElapsedTimer totalTimer;
3158 d->progressTracker.initDenoise();
3159 d->progressTracker.setStage(Stage::Denoise);
3160 d->updateStage(
"Denoising"_L1);
3161 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral(
"Denoise starting..."));
3163 if (!d->denoiseLightmaps()) {
3164 d->updateStage(
"Failed"_L1);
3165 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed, QStringLiteral(
"Denoising failed"));
3169 d->totalProgress = 1;
3170 d->updateStage(
"Done"_L1);
3171 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral(
"Denoising took %1 ms").arg(totalTimer.elapsed()));
3172 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Complete, std::nullopt);
3176void QSSGLightmapper::run(QOffscreenSurface *fallbackSurface)
3178 auto releaseMainThread = qScopeGuard([&] {
3179 d->initMutex.lock();
3180 d->initCondition.wakeAll();
3181 d->initMutex.unlock();
3184 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info,
3185 QStringLiteral(
"Total models registered: %1").arg(d->bakedLightingModels.size()));
3187 if (d->bakedLightingModels.isEmpty()) {
3188 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed, QStringLiteral(
"No Models to bake"));
3192 d->outputPath = stripQrcPrefix(d->options.source);
3194 if (!createDirectory(d->outputPath)) {
3195 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed, QStringLiteral(
"Failed to create output directory"));
3199 if (!isValidSavePath(d->outputPath)) {
3200 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed,
3201 QStringLiteral(
"Source path %1 is not a writable location").arg(d->outputPath));
3205 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral(
"Source path: %1").arg(d->outputPath));
3206 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral(
"Output path: %1").arg(d->outputPath));
3208 const QRhi::Flags flags = QRhi::EnableTimestamps | QRhi::EnableDebugMarkers;
3209#if QT_CONFIG(vulkan)
3210 std::unique_ptr<QVulkanInstance> vulkanInstance;
3212 std::unique_ptr<QRhi> rhi;
3214 switch (d->rhiBackend) {
3215 case QRhi::Vulkan: {
3216#if QT_CONFIG(vulkan)
3217 vulkanInstance = std::make_unique<QVulkanInstance>();
3218 vulkanInstance->create();
3219 QRhiVulkanInitParams params;
3220 params.inst = vulkanInstance.get();
3221 rhi = std::unique_ptr<QRhi>(QRhi::create(d->rhiBackend, ¶ms, flags));
3225 case QRhi::OpenGLES2: {
3226#if QT_CONFIG(opengl)
3227 QRhiGles2InitParams params;
3228 if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL) {
3230 params.format.setProfile(QSurfaceFormat::CoreProfile);
3231 params.format.setVersion(4, 3);
3234 params.format.setVersion(3, 1);
3236 params.fallbackSurface = fallbackSurface;
3237 rhi = std::unique_ptr<QRhi>(QRhi::create(d->rhiBackend, ¶ms, flags));
3242#if defined(Q_OS_WIN)
3243 QRhiD3D11InitParams params;
3244 rhi = std::unique_ptr<QRhi>(QRhi::create(d->rhiBackend, ¶ms, flags));
3249#if defined(Q_OS_WIN)
3250 QRhiD3D12InitParams params;
3251 rhi = std::unique_ptr<QRhi>(QRhi::create(d->rhiBackend, ¶ms, flags));
3257 QRhiMetalInitParams params;
3258 rhi = std::unique_ptr<QRhi>(QRhi::create(d->rhiBackend, ¶ms, flags));
3263 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed, QStringLiteral(
"QRhi backend is null"));
3266 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed, QStringLiteral(
"Failed to initialize QRhi"));
3271 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Failed to create QRhi, cannot bake"));
3275 if (!rhi->isTextureFormatSupported(QRhiTexture::RGBA32F)) {
3276 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"FP32 textures not supported, cannot bake"));
3279 if (rhi->resourceLimit(QRhi::MaxColorAttachments) < 4) {
3280 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Multiple render targets not supported, cannot bake"));
3283 if (!rhi->isFeatureSupported(QRhi::NonFillPolygonMode)) {
3284 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral(
"Line polygon mode not supported, cannot bake"));
3288 if (!rhi->isFeatureSupported(QRhi::Compute)) {
3289 qFatal(
"Compute is not supported, cannot bake");
3293 d->rhiCtxInterface = std::
3294 unique_ptr<QSSGRenderContextInterface>(
new QSSGRenderContextInterface(rhi.get()));
3295 d->renderer = std::unique_ptr<QSSGRenderer>(
new QSSGRenderer());
3297 QSSGRendererPrivate::setRenderContextInterface(*d->renderer, d->rhiCtxInterface.get());
3299 QRhiCommandBuffer *cb;
3300 rhi->beginOffscreenFrame(&cb);
3302 QSSGRhiContext *rhiCtx = d->rhiCtxInterface->rhiContext().get();
3303 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
3304 rhiCtxD->setCommandBuffer(cb);
3306 d->rhiCtxInterface->bufferManager()->setRenderContextInterface(d->rhiCtxInterface.get());
3308 constexpr int timerIntervalMs = 100;
3309 TimerThread timerThread;
3310 timerThread.setInterval(timerIntervalMs);
3312 constexpr int consoleOutputInterval = 5000 / timerIntervalMs;
3313 int timeoutsSinceOutput = consoleOutputInterval - 1;
3314 timerThread.setCallback([&]() {
3315 d->totalProgress = d->progressTracker.getProgress();
3316 d->estimatedTimeRemaining = d->progressTracker.getEstimatedTimeRemaining();
3317 bool outputToConsole = timeoutsSinceOutput == consoleOutputInterval - 1;
3318 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, std::nullopt, outputToConsole, outputToConsole);
3319 timeoutsSinceOutput = (timeoutsSinceOutput + 1) % consoleOutputInterval;
3321 timerThread.start();
3323 if (d->denoiseOnly) {
3329 rhi->endOffscreenFrame();
3332 d->renderer.reset();
3333 d->rhiCtxInterface.reset();
3336void QSSGLightmapper::waitForInit()
3338 d->initMutex.lock();
3339 d->initCondition.wait(&d->initMutex);
3340 d->initMutex.unlock();
3385 qWarning(
"Qt Quick 3D was built without the lightmapper; cannot bake lightmaps");
3406#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.