Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qssglightmapper.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qssglightmapper_p.h"
5#include <QtQuick3DRuntimeRender/private/qssgrenderer_p.h>
6#include <QtQuick3DRuntimeRender/private/qssgrhiquadrenderer_p.h>
7#include <QtQuick3DRuntimeRender/private/qssglayerrenderdata_p.h>
8#include <QtQuick3DRuntimeRender/private/qssgrendercontextcore_p.h>
9#include <QtQuick3DUtils/private/qssgutils_p.h>
10
11#ifdef QT_QUICK3D_HAS_LIGHTMAPPER
12#include <QtCore/qfuture.h>
13#include <QtCore/qfileinfo.h>
14#include <QtConcurrent/qtconcurrentrun.h>
15#include <QRandomGenerator>
16#include <qsimd.h>
17#include <embree3/rtcore.h>
18#include <tinyexr.h>
19#endif
20
22
23// References:
24// https://ndotl.wordpress.com/2018/08/29/baking-artifact-free-lightmaps/
25// https://www.scratchapixel.com/lessons/3d-basic-rendering/global-illumination-path-tracing/
26// https://media.contentapi.ea.com/content/dam/eacom/frostbite/files/gdc2018-precomputedgiobalilluminationinfrostbite.pdf
27// https://therealmjp.github.io/posts/new-blog-series-lightmap-baking-and-spherical-gaussians/
28// https://computergraphics.stackexchange.com/questions/2316/is-russian-roulette-really-the-answer
29// https://computergraphics.stackexchange.com/questions/4664/does-cosine-weighted-hemisphere-sampling-still-require-ndotl-when-calculating-co
30// https://www.rorydriscoll.com/2009/01/07/better-sampling/
31// https://github.com/TheRealMJP/BakingLab
32// https://github.com/candycat1992/LightmapperToy
33// https://github.com/godotengine/
34// https://github.com/jpcy/xatlas
35
36#ifdef QT_QUICK3D_HAS_LIGHTMAPPER
37
38struct QSSGLightmapperPrivate
39{
41 QSSGRhiContext *rhiCtx;
43 QVector<QSSGBakedLightingModel> bakedLightingModels;
44 QSSGLightmapper::Callback outputCallback;
46
47 struct SubMeshInfo {
48 quint32 offset = 0;
49 quint32 count = 0;
50 unsigned int geomId = RTC_INVALID_GEOMETRY_ID;
51 QVector4D baseColor;
52 QSSGRenderImage *baseColorNode = nullptr;
53 QRhiTexture *baseColorMap = nullptr;
54 QVector3D emissiveFactor;
55 QSSGRenderImage *emissiveNode = nullptr;
56 QRhiTexture *emissiveMap = nullptr;
57 QSSGRenderImage *normalMapNode = nullptr;
58 QRhiTexture *normalMap = nullptr;
59 float normalStrength = 0.0f;
60 float opacity = 0.0f;
61 };
62 using SubMeshInfoList = QVector<SubMeshInfo>;
63 QVector<SubMeshInfoList> subMeshInfos;
64
65 struct DrawInfo {
66 QSize lightmapSize;
67 QByteArray vertexData;
68 quint32 vertexStride;
69 QByteArray indexData;
71 quint32 positionOffset = UINT_MAX;
73 quint32 normalOffset = UINT_MAX;
75 quint32 uvOffset = UINT_MAX;
77 quint32 lightmapUVOffset = UINT_MAX;
79 quint32 tangentOffset = UINT_MAX;
81 quint32 binormalOffset = UINT_MAX;
83 QSSGMesh::Mesh meshWithLightmapUV; // only set when model->hasLightmap() == true
84 };
85 QVector<DrawInfo> drawInfos;
86
87 struct Light {
88 enum {
89 Directional,
90 Point,
91 Spot
92 } type;
93 bool indirectOnly;
96 QVector3D worldPos;
97 float cosConeAngle;
98 float cosInnerConeAngle;
99 float constantAttenuation;
100 float linearAttenuation;
101 float quadraticAttenuation;
102 };
103 QVector<Light> lights;
104
105 RTCDevice rdev = nullptr;
106 RTCScene rscene = nullptr;
107
108 struct LightmapEntry {
109 QSize pixelSize;
110 QVector3D worldPos;
111 QVector3D normal;
112 QVector4D baseColor; // static color * texture map value (both linear)
113 QVector3D emission; // static factor * emission map value
114 bool isValid() const { return !worldPos.isNull() && !normal.isNull(); }
115 QVector3D directLight;
116 QVector3D allLight;
117 };
118 struct Lightmap {
119 Lightmap(const QSize &pixelSize) : pixelSize(pixelSize) {
120 entries.resize(pixelSize.width() * pixelSize.height());
121 }
122 QSize pixelSize;
124 QByteArray imageFP32;
125 bool hasBaseColorTransparency = false;
126 };
127 QVector<Lightmap> lightmaps;
128 QVector<int> geomLightmapMap; // [geomId] -> index in lightmaps (NB lightmap is per-model, geomId is per-submesh)
129 QVector<float> subMeshOpacityMap; // [geomId] -> opacity
130
131 inline const LightmapEntry &texelForLightmapUV(unsigned int geomId, float u, float v) const
132 {
133 // find the hit texel in the lightmap for the model to which the submesh with geomId belongs
134 const Lightmap &hitLightmap(lightmaps[geomLightmapMap[geomId]]);
135 u = qBound(0.0f, u, 1.0f);
136 // flip V, CPU-side data is top-left based
137 v = 1.0f - qBound(0.0f, v, 1.0f);
138
139 const int w = hitLightmap.pixelSize.width();
140 const int h = hitLightmap.pixelSize.height();
141 const int x = qBound(0, int(w * u), w - 1);
142 const int y = qBound(0, int(h * v), h - 1);
143
144 return hitLightmap.entries[x + y * w];
145 }
146
147 bool commitGeometry();
148 bool prepareLightmaps();
149 void computeDirectLight();
150 void computeIndirectLight();
151 bool postProcess();
152 bool storeLightmaps();
153 void sendOutputInfo(QSSGLightmapper::BakingStatus type, std::optional<QString> msg);
154};
155
156static const int LM_SEAM_BLEND_ITER_COUNT = 4;
157
159 : d(new QSSGLightmapperPrivate)
160{
161 d->rhiCtx = rhiCtx;
162 d->renderer = renderer;
163
164#ifdef __SSE2__
165 _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
166 _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON);
167#endif
168}
169
171{
172 reset();
173 delete d;
174
175#ifdef __SSE2__
176 _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_OFF);
177 _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_OFF);
178#endif
179}
180
182{
183 d->bakedLightingModels.clear();
184 d->subMeshInfos.clear();
185 d->drawInfos.clear();
186 d->lights.clear();
187 d->lightmaps.clear();
188 d->geomLightmapMap.clear();
189 d->subMeshOpacityMap.clear();
190
191 if (d->rscene) {
192 rtcReleaseScene(d->rscene);
193 d->rscene = nullptr;
194 }
195 if (d->rdev) {
196 rtcReleaseDevice(d->rdev);
197 d->rdev = nullptr;
198 }
199
200 d->bakingControl.cancelled = false;
201}
202
204{
205 d->options = options;
206}
207
209{
210 d->outputCallback = callback;
211}
212
214{
215 d->bakedLightingModels.append(model);
216 return d->bakedLightingModels.size() - 1;
217}
218
219static void embreeErrFunc(void *, RTCError error, const char *str)
220{
221 qWarning("lm: Embree error: %d: %s", error, str);
222}
223
224static const unsigned int NORMAL_SLOT = 0;
225static const unsigned int LIGHTMAP_UV_SLOT = 1;
226
227static void embreeFilterFunc(const RTCFilterFunctionNArguments *args)
228{
229 RTCHit *hit = reinterpret_cast<RTCHit *>(args->hit);
230 QSSGLightmapperPrivate *d = static_cast<QSSGLightmapperPrivate *>(args->geometryUserPtr);
231 RTCGeometry geom = rtcGetGeometry(d->rscene, hit->geomID);
232
233 // convert from barycentric and overwrite u and v in hit with the result
234 rtcInterpolate0(geom, hit->primID, hit->u, hit->v, RTC_BUFFER_TYPE_VERTEX_ATTRIBUTE, LIGHTMAP_UV_SLOT, &hit->u, 2);
235
236 const float opacity = d->subMeshOpacityMap[hit->geomID];
237 if (opacity < 1.0f || d->lightmaps[d->geomLightmapMap[hit->geomID]].hasBaseColorTransparency) {
238 const QSSGLightmapperPrivate::LightmapEntry &texel(d->texelForLightmapUV(hit->geomID, hit->u, hit->v));
239
240 // In addition to material.opacity, take at least the base color (both
241 // the static color and the value from the base color map, if there is
242 // one) into account. Opacity map, alpha cutoff, etc. are ignored.
243 const float alpha = opacity * texel.baseColor.w();
244
245 // Ignore the hit if the alpha is low enough. This is not exactly perfect,
246 // but better than nothing. An object with an opacity lower than the
247 // threshold will act is if it was not there, as far as the intersection is
248 // concerned. So then the object won't cast shadows for example.
249 if (alpha < d->options.opacityThreshold)
250 args->valid[0] = 0;
251 }
252}
253
254bool QSSGLightmapperPrivate::commitGeometry()
255{
256 if (bakedLightingModels.isEmpty()) {
257 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("No models with usedInBakedLighting, cannot bake"));
258 return false;
259 }
260
261 sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Geometry setup..."));
262 QElapsedTimer geomPrepTimer;
263 geomPrepTimer.start();
264
265 const auto &bufferManager(renderer->contextInterface()->bufferManager());
266
267 const int bakedLightingModelCount = bakedLightingModels.size();
268 subMeshInfos.resize(bakedLightingModelCount);
269 drawInfos.resize(bakedLightingModelCount);
270
271 for (int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
272 const QSSGBakedLightingModel &lm(bakedLightingModels[lmIdx]);
273 if (lm.renderables.isEmpty()) {
274 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("No submeshes, model %1 cannot be lightmapped").
275 arg(lm.model->debugObjectName));
276 return false;
277 }
278 if (lm.model->skin || lm.model->skeleton) {
279 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Skinned models not supported: %1").
280 arg(lm.model->debugObjectName));
281 return false;
282 }
283
284 subMeshInfos[lmIdx].reserve(lm.renderables.size());
285 for (const QSSGRenderableObjectHandle &handle : std::as_const(lm.renderables)) {
286 Q_ASSERT(handle.obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset
287 || handle.obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset);
288 QSSGSubsetRenderable *renderableObj = static_cast<QSSGSubsetRenderable *>(handle.obj);
289 SubMeshInfo info;
290 info.offset = renderableObj->subset.offset;
291 info.count = renderableObj->subset.count;
292 info.opacity = renderableObj->opacity;
293 if (handle.obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset) {
294 const QSSGRenderDefaultMaterial *defMat = static_cast<const QSSGRenderDefaultMaterial *>(&renderableObj->material);
295 info.baseColor = defMat->color;
296 info.emissiveFactor = defMat->emissiveColor;
297 if (defMat->colorMap) {
298 info.baseColorNode = defMat->colorMap;
299 QSSGRenderImageTexture texture = bufferManager->loadRenderImage(defMat->colorMap);
300 info.baseColorMap = texture.m_texture;
301 }
302 if (defMat->emissiveMap) {
303 info.emissiveNode = defMat->emissiveMap;
304 QSSGRenderImageTexture texture = bufferManager->loadRenderImage(defMat->emissiveMap);
305 info.emissiveMap = texture.m_texture;
306 }
307 if (defMat->normalMap) {
308 info.normalMapNode = defMat->normalMap;
309 QSSGRenderImageTexture texture = bufferManager->loadRenderImage(defMat->normalMap);
310 info.normalMap = texture.m_texture;
311 info.normalStrength = defMat->bumpAmount;
312 }
313 } else {
314 info.baseColor = QVector4D(1.0f, 1.0f, 1.0f, 1.0f);
315 info.emissiveFactor = QVector3D(0.0f, 0.0f, 0.0f);
316 }
317 subMeshInfos[lmIdx].append(info);
318 }
319
320 QMatrix4x4 worldTransform;
321 QMatrix3x3 normalMatrix;
322 QSSGSubsetRenderable *renderableObj = static_cast<QSSGSubsetRenderable *>(lm.renderables.first().obj);
323 worldTransform = renderableObj->globalTransform;
324 normalMatrix = renderableObj->modelContext.normalMatrix;
325
326 DrawInfo &drawInfo(drawInfos[lmIdx]);
327 QSSGMesh::Mesh mesh;
328
329 if (lm.model->geometry)
330 mesh = bufferManager->loadMeshData(lm.model->geometry);
331 else
332 mesh = bufferManager->loadMeshData(lm.model->meshPath);
333
334 if (!mesh.isValid()) {
335 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Failed to load geometry for model %1").
336 arg(lm.model->debugObjectName));
337 return false;
338 }
339
340 if (!mesh.hasLightmapUVChannel()) {
341 QElapsedTimer unwrapTimer;
342 unwrapTimer.start();
343 if (!mesh.createLightmapUVChannel(lm.model->lightmapBaseResolution)) {
344 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Failed to do lightmap UV unwrapping for model %1").
345 arg(lm.model->debugObjectName));
346 return false;
347 }
348 sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Lightmap UV unwrap done for model %1 in %2 ms").
349 arg(lm.model->debugObjectName).
350 arg(unwrapTimer.elapsed()));
351
352 if (lm.model->hasLightmap())
353 drawInfo.meshWithLightmapUV = mesh;
354 } else {
355 sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Model %1 already has a lightmap UV channel").arg(lm.model->debugObjectName));
356 }
357
358 drawInfo.lightmapSize = mesh.subsets().first().lightmapSizeHint;
359 if (drawInfo.lightmapSize.isEmpty()) {
360 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("No lightmap size hint found for model %1, defaulting to 1024x1024").
361 arg(lm.model->debugObjectName));
362 drawInfo.lightmapSize = QSize(1024, 1024);
363 }
364
365 drawInfo.vertexData = mesh.vertexBuffer().data;
366 drawInfo.vertexStride = mesh.vertexBuffer().stride;
367 drawInfo.indexData = mesh.indexBuffer().data;
368
369 if (drawInfo.vertexData.isEmpty()) {
370 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("No vertex data for model %1").arg(lm.model->debugObjectName));
371 return false;
372 }
373 if (drawInfo.indexData.isEmpty()) {
374 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("No index data for model %1").arg(lm.model->debugObjectName));
375 return false;
376 }
377
378 switch (mesh.indexBuffer().componentType) {
379 case QSSGMesh::Mesh::ComponentType::UnsignedInt16:
380 drawInfo.indexFormat = QRhiCommandBuffer::IndexUInt16;
381 break;
382 case QSSGMesh::Mesh::ComponentType::UnsignedInt32:
383 drawInfo.indexFormat = QRhiCommandBuffer::IndexUInt32;
384 break;
385 default:
386 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Unknown index component type %1 for model %2").
387 arg(int(mesh.indexBuffer().componentType)).
388 arg(lm.model->debugObjectName));
389 break;
390 }
391
392 for (const QSSGMesh::Mesh::VertexBufferEntry &vbe : mesh.vertexBuffer().entries) {
394 drawInfo.positionOffset = vbe.offset;
395 drawInfo.positionFormat = QSSGRhiInputAssemblerState::toVertexInputFormat(QSSGRenderComponentType(vbe.componentType), vbe.componentCount);
396 } else if (vbe.name == QSSGMesh::MeshInternal::getNormalAttrName()) {
397 drawInfo.normalOffset = vbe.offset;
398 drawInfo.normalFormat = QSSGRhiInputAssemblerState::toVertexInputFormat(QSSGRenderComponentType(vbe.componentType), vbe.componentCount);
399 } else if (vbe.name == QSSGMesh::MeshInternal::getUV0AttrName()) {
400 drawInfo.uvOffset = vbe.offset;
401 drawInfo.uvFormat = QSSGRhiInputAssemblerState::toVertexInputFormat(QSSGRenderComponentType(vbe.componentType), vbe.componentCount);
402 } else if (vbe.name == QSSGMesh::MeshInternal::getLightmapUVAttrName()) {
403 drawInfo.lightmapUVOffset = vbe.offset;
404 drawInfo.lightmapUVFormat = QSSGRhiInputAssemblerState::toVertexInputFormat(QSSGRenderComponentType(vbe.componentType), vbe.componentCount);
405 } else if (vbe.name == QSSGMesh::MeshInternal::getTexTanAttrName()) {
406 drawInfo.tangentOffset = vbe.offset;
407 drawInfo.tangentFormat = QSSGRhiInputAssemblerState::toVertexInputFormat(QSSGRenderComponentType(vbe.componentType), vbe.componentCount);
408 } else if (vbe.name == QSSGMesh::MeshInternal::getTexBinormalAttrName()) {
409 drawInfo.binormalOffset = vbe.offset;
410 drawInfo.binormalFormat = QSSGRhiInputAssemblerState::toVertexInputFormat(QSSGRenderComponentType(vbe.componentType), vbe.componentCount);
411 }
412 }
413
414 if (!(drawInfo.positionOffset != UINT_MAX && drawInfo.normalOffset != UINT_MAX)) {
415 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Could not figure out position and normal attribute offsets for model %1").
416 arg(lm.model->debugObjectName));
417 return false;
418 }
419
420 // We will manually access and massage the data, so cannot just work with arbitrary formats.
421 if (!(drawInfo.positionFormat == QRhiVertexInputAttribute::Float3
422 && drawInfo.normalFormat == QRhiVertexInputAttribute::Float3))
423 {
424 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Position or normal attribute format is not as expected (float3) for model %1").
425 arg(lm.model->debugObjectName));
426 return false;
427 }
428
429 if (drawInfo.lightmapUVOffset == UINT_MAX) {
430 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Could not figure out lightmap UV attribute offset for model %1").
431 arg(lm.model->debugObjectName));
432 return false;
433 }
434
435 if (drawInfo.lightmapUVFormat != QRhiVertexInputAttribute::Float2) {
436 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Lightmap UV attribute format is not as expected (float2) for model %1").
437 arg(lm.model->debugObjectName));
438 return false;
439 }
440
441 // UV0 is optional
442 if (drawInfo.uvOffset != UINT_MAX) {
443 if (drawInfo.uvFormat != QRhiVertexInputAttribute::Float2) {
444 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("UV0 attribute format is not as expected (float2) for model %1").
445 arg(lm.model->debugObjectName));
446 return false;
447 }
448 }
449 // tangent and binormal are optional too
450 if (drawInfo.tangentOffset != UINT_MAX) {
451 if (drawInfo.tangentFormat != QRhiVertexInputAttribute::Float3) {
452 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Tangent attribute format is not as expected (float3) for model %1").
453 arg(lm.model->debugObjectName));
454 return false;
455 }
456 }
457 if (drawInfo.binormalOffset != UINT_MAX) {
458 if (drawInfo.binormalFormat != QRhiVertexInputAttribute::Float3) {
459 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Binormal attribute format is not as expected (float3) for model %1").
460 arg(lm.model->debugObjectName));
461 return false;
462 }
463 }
464
465 if (drawInfo.indexFormat == QRhiCommandBuffer::IndexUInt16) {
466 drawInfo.indexFormat = QRhiCommandBuffer::IndexUInt32;
467 QByteArray newIndexData(drawInfo.indexData.size() * 2, Qt::Uninitialized);
468 const quint16 *s = reinterpret_cast<const quint16 *>(drawInfo.indexData.constData());
469 size_t sz = drawInfo.indexData.size() / 2;
470 quint32 *p = reinterpret_cast<quint32 *>(newIndexData.data());
471 while (sz--)
472 *p++ = *s++;
473 drawInfo.indexData = newIndexData;
474 }
475
476 // Bake in the world transform.
477 {
478 char *vertexBase = drawInfo.vertexData.data();
479 const qsizetype sz = drawInfo.vertexData.size();
480 for (qsizetype offset = 0; offset < sz; offset += drawInfo.vertexStride) {
481 char *posPtr = vertexBase + offset + drawInfo.positionOffset;
482 float *fPosPtr = reinterpret_cast<float *>(posPtr);
483 QVector3D pos(fPosPtr[0], fPosPtr[1], fPosPtr[2]);
484 char *normalPtr = vertexBase + offset + drawInfo.normalOffset;
485 float *fNormalPtr = reinterpret_cast<float *>(normalPtr);
486 QVector3D normal(fNormalPtr[0], fNormalPtr[1], fNormalPtr[2]);
487 pos = worldTransform.map(pos);
488 normal = mat33::transform(normalMatrix, normal).normalized();
489 *fPosPtr++ = pos.x();
490 *fPosPtr++ = pos.y();
491 *fPosPtr++ = pos.z();
492 *fNormalPtr++ = normal.x();
493 *fNormalPtr++ = normal.y();
494 *fNormalPtr++ = normal.z();
495 }
496 }
497 } // end loop over models used in the lightmap
498
499 sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Found %1 models for the lightmapped scene").arg(bakedLightingModelCount));
500
501 // All subsets for a model reference the same QSSGShaderLight list,
502 // take the first one, but filter it based on the bake flag.
503 for (const QSSGShaderLight &sl : static_cast<QSSGSubsetRenderable *>(bakedLightingModels.first().renderables.first().obj)->lights) {
504 if (!sl.light->m_bakingEnabled)
505 continue;
506
507 Light light;
508 light.indirectOnly = !sl.light->m_fullyBaked;
509 light.direction = sl.direction;
510
511 const float brightness = sl.light->m_brightness;
512 light.color = QVector3D(sl.light->m_diffuseColor.x() * brightness,
513 sl.light->m_diffuseColor.y() * brightness,
514 sl.light->m_diffuseColor.z() * brightness);
515
516 if (sl.light->type == QSSGRenderLight::Type::PointLight
517 || sl.light->type == QSSGRenderLight::Type::SpotLight)
518 {
519 light.worldPos = sl.light->getGlobalPos();
520 if (sl.light->type == QSSGRenderLight::Type::SpotLight) {
521 light.type = Light::Spot;
522 light.cosConeAngle = qCos(qDegreesToRadians(sl.light->m_coneAngle));
523 light.cosInnerConeAngle = qCos(qDegreesToRadians(
524 qMin(sl.light->m_innerConeAngle, sl.light->m_coneAngle)));
525 } else {
526 light.type = Light::Point;
527 }
528 light.constantAttenuation = aux::translateConstantAttenuation(sl.light->m_constantFade);
529 light.linearAttenuation = aux::translateLinearAttenuation(sl.light->m_linearFade);
530 light.quadraticAttenuation = aux::translateQuadraticAttenuation(sl.light->m_quadraticFade);
531 } else {
532 light.type = Light::Directional;
533 }
534
535 lights.append(light);
536 }
537
538 sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Found %1 lights enabled for baking").arg(lights.size()));
539
540 rdev = rtcNewDevice(nullptr);
541 if (!rdev) {
542 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Failed to create Embree device"));
543 return false;
544 }
545
546 rtcSetDeviceErrorFunction(rdev, embreeErrFunc, nullptr);
547
548 rscene = rtcNewScene(rdev);
549
550 unsigned int geomId = 1;
551
552 for (int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
553 const QSSGBakedLightingModel &lm(bakedLightingModels[lmIdx]);
554
555 // While Light.castsShadow and Model.receivesShadows are irrelevant for
556 // baked lighting (they are effectively ignored, shadows are always
557 // there with baked direct lighting), Model.castsShadows is something
558 // we can and should take into account.
559 if (!lm.model->castsShadows)
560 continue;
561
562 const DrawInfo &drawInfo(drawInfos[lmIdx]);
563 const char *vbase = drawInfo.vertexData.constData();
564 const quint32 *ibase = reinterpret_cast<const quint32 *>(drawInfo.indexData.constData());
565
566 for (SubMeshInfo &subMeshInfo : subMeshInfos[lmIdx]) {
567 RTCGeometry geom = rtcNewGeometry(rdev, RTC_GEOMETRY_TYPE_TRIANGLE);
568 rtcSetGeometryVertexAttributeCount(geom, 2);
569 quint32 *ip = static_cast<quint32 *>(rtcSetNewGeometryBuffer(geom, RTC_BUFFER_TYPE_INDEX, 0, RTC_FORMAT_UINT3, 3 * sizeof(uint32_t), subMeshInfo.count / 3));
570 for (quint32 i = 0; i < subMeshInfo.count; ++i)
571 *ip++ = i;
572 float *vp = static_cast<float *>(rtcSetNewGeometryBuffer(geom, RTC_BUFFER_TYPE_VERTEX, 0, RTC_FORMAT_FLOAT3, 3 * sizeof(float), subMeshInfo.count));
573 for (quint32 i = 0; i < subMeshInfo.count; ++i) {
574 const quint32 idx = *(ibase + subMeshInfo.offset + i);
575 const float *src = reinterpret_cast<const float *>(vbase + idx * drawInfo.vertexStride + drawInfo.positionOffset);
576 *vp++ = *src++;
577 *vp++ = *src++;
578 *vp++ = *src++;
579 }
580 vp = static_cast<float *>(rtcSetNewGeometryBuffer(geom, RTC_BUFFER_TYPE_VERTEX_ATTRIBUTE, NORMAL_SLOT, RTC_FORMAT_FLOAT3, 3 * sizeof(float), subMeshInfo.count));
581 for (quint32 i = 0; i < subMeshInfo.count; ++i) {
582 const quint32 idx = *(ibase + subMeshInfo.offset + i);
583 const float *src = reinterpret_cast<const float *>(vbase + idx * drawInfo.vertexStride + drawInfo.normalOffset);
584 *vp++ = *src++;
585 *vp++ = *src++;
586 *vp++ = *src++;
587 }
588 vp = static_cast<float *>(rtcSetNewGeometryBuffer(geom, RTC_BUFFER_TYPE_VERTEX_ATTRIBUTE, LIGHTMAP_UV_SLOT, RTC_FORMAT_FLOAT2, 2 * sizeof(float), subMeshInfo.count));
589 for (quint32 i = 0; i < subMeshInfo.count; ++i) {
590 const quint32 idx = *(ibase + subMeshInfo.offset + i);
591 const float *src = reinterpret_cast<const float *>(vbase + idx * drawInfo.vertexStride + drawInfo.lightmapUVOffset);
592 *vp++ = *src++;
593 *vp++ = *src++;
594 }
595 rtcCommitGeometry(geom);
596 rtcSetGeometryIntersectFilterFunction(geom, embreeFilterFunc);
597 rtcSetGeometryUserData(geom, this);
598 rtcAttachGeometryByID(rscene, geom, geomId);
599 subMeshInfo.geomId = geomId++;
600 rtcReleaseGeometry(geom);
601 }
602 }
603
604 rtcCommitScene(rscene);
605
606 RTCBounds bounds;
607 rtcGetSceneBounds(rscene, &bounds);
608 QVector3D lowerBound(bounds.lower_x, bounds.lower_y, bounds.lower_z);
609 QVector3D upperBound(bounds.upper_x, bounds.upper_y, bounds.upper_z);
610 qDebug() << "[lm] Bounds in world space for raytracing scene:" << lowerBound << upperBound;
611
612 const unsigned int geomIdBasedMapSize = geomId;
613 // Need fast lookup, hence indexing by geomId here. geomId starts from 1,
614 // meaning index 0 will be unused, but that's ok.
615 geomLightmapMap.fill(-1, geomIdBasedMapSize);
616 subMeshOpacityMap.fill(0.0f, geomIdBasedMapSize);
617
618 for (int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
619 const QSSGBakedLightingModel &lm(bakedLightingModels[lmIdx]);
620 if (!lm.model->castsShadows) // only matters if it's in the raytracer scene
621 continue;
622 for (SubMeshInfo &subMeshInfo : subMeshInfos[lmIdx])
623 subMeshOpacityMap[subMeshInfo.geomId] = subMeshInfo.opacity;
624 }
625
626 sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Geometry setup done. Time taken: %1 ms").arg(geomPrepTimer.elapsed()));
627 return true;
628}
629
630bool QSSGLightmapperPrivate::prepareLightmaps()
631{
632 QRhi *rhi = rhiCtx->rhi();
634 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("FP32 textures not supported, cannot bake"));
635 return false;
636 }
638 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Multiple render targets not supported, cannot bake"));
639 return false;
640 }
642 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Line polygon mode not supported, cannot bake"));
643 return false;
644 }
645
646 sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Preparing lightmaps..."));
648 const int bakedLightingModelCount = bakedLightingModels.size();
649 Q_ASSERT(drawInfos.size() == bakedLightingModelCount);
650 Q_ASSERT(subMeshInfos.size() == bakedLightingModelCount);
651
652 for (int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
653 QElapsedTimer rasterizeTimer;
654 rasterizeTimer.start();
655
656 const QSSGBakedLightingModel &lm(bakedLightingModels[lmIdx]);
657
658 const DrawInfo &bakeModelDrawInfo(drawInfos[lmIdx]);
659 const bool hasUV0 = bakeModelDrawInfo.uvOffset != UINT_MAX;
660 const bool hasTangentAndBinormal = bakeModelDrawInfo.tangentOffset != UINT_MAX
661 && bakeModelDrawInfo.binormalOffset != UINT_MAX;
662 const QSize outputSize = bakeModelDrawInfo.lightmapSize;
663
664 QRhiVertexInputLayout inputLayout;
665 inputLayout.setBindings({ QRhiVertexInputBinding(bakeModelDrawInfo.vertexStride) });
666
667 std::unique_ptr<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, bakeModelDrawInfo.vertexData.size()));
668 if (!vbuf->create()) {
669 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Failed to create vertex buffer"));
670 return false;
671 }
672 std::unique_ptr<QRhiBuffer> ibuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::IndexBuffer, bakeModelDrawInfo.indexData.size()));
673 if (!ibuf->create()) {
674 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Failed to create index buffer"));
675 return false;
676 }
678 resUpd->uploadStaticBuffer(vbuf.get(), bakeModelDrawInfo.vertexData.constData());
679 resUpd->uploadStaticBuffer(ibuf.get(), bakeModelDrawInfo.indexData.constData());
680 QRhiTexture *dummyTexture = rhiCtx->dummyTexture({}, resUpd);
681 cb->resourceUpdate(resUpd);
682
683 std::unique_ptr<QRhiTexture> positionData(rhi->newTexture(QRhiTexture::RGBA32F, outputSize, 1,
685 if (!positionData->create()) {
686 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Failed to create FP32 texture for positions"));
687 return false;
688 }
689 std::unique_ptr<QRhiTexture> normalData(rhi->newTexture(QRhiTexture::RGBA32F, outputSize, 1,
691 if (!normalData->create()) {
692 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Failed to create FP32 texture for normals"));
693 return false;
694 }
695 std::unique_ptr<QRhiTexture> baseColorData(rhi->newTexture(QRhiTexture::RGBA32F, outputSize, 1,
697 if (!baseColorData->create()) {
698 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Failed to create FP32 texture for base color"));
699 return false;
700 }
701 std::unique_ptr<QRhiTexture> emissionData(rhi->newTexture(QRhiTexture::RGBA32F, outputSize, 1,
703 if (!emissionData->create()) {
704 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Failed to create FP32 texture for emissive color"));
705 return false;
706 }
707
708 std::unique_ptr<QRhiRenderBuffer> ds(rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, outputSize));
709 if (!ds->create()) {
710 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Failed to create depth-stencil buffer"));
711 return false;
712 }
713
714 QRhiColorAttachment posAtt(positionData.get());
715 QRhiColorAttachment normalAtt(normalData.get());
716 QRhiColorAttachment baseColorAtt(baseColorData.get());
717 QRhiColorAttachment emissionAtt(emissionData.get());
719 rtDesc.setColorAttachments({ posAtt, normalAtt, baseColorAtt, emissionAtt });
720 rtDesc.setDepthStencilBuffer(ds.get());
721
722 std::unique_ptr<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(rtDesc));
723 std::unique_ptr<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor());
724 rt->setRenderPassDescriptor(rpDesc.get());
725 if (!rt->create()) {
726 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Failed to create texture render target"));
727 return false;
728 }
729
730 static const int UBUF_SIZE = 48;
731 const int subMeshCount = subMeshInfos[lmIdx].size();
732 const int alignedUbufSize = rhi->ubufAligned(UBUF_SIZE);
733 const int totalUbufSize = alignedUbufSize * subMeshCount;
734 std::unique_ptr<QRhiBuffer> ubuf(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, totalUbufSize));
735 if (!ubuf->create()) {
736 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Failed to create uniform buffer of size %1").arg(totalUbufSize));
737 return false;
738 }
739
740 // Must ensure that the final image is identical with all graphics APIs,
741 // regardless of how the Y axis goes in the image and normalized device
742 // coordinate systems.
743 qint32 flipY = rhi->isYUpInFramebuffer() ? 0 : 1;
744 if (rhi->isYUpInNDC())
745 flipY = 1 - flipY;
746
747 char *ubufData = ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
748 for (int subMeshIdx = 0; subMeshIdx != subMeshCount; ++subMeshIdx) {
749 const SubMeshInfo &subMeshInfo(subMeshInfos[lmIdx][subMeshIdx]);
750 qint32 hasBaseColorMap = subMeshInfo.baseColorMap ? 1 : 0;
751 qint32 hasEmissiveMap = subMeshInfo.emissiveMap ? 1 : 0;
752 qint32 hasNormalMap = subMeshInfo.normalMap ? 1 : 0;
753 char *p = ubufData + subMeshIdx * alignedUbufSize;
754 memcpy(p, &subMeshInfo.baseColor, 4 * sizeof(float));
755 memcpy(p + 16, &subMeshInfo.emissiveFactor, 3 * sizeof(float));
756 memcpy(p + 28, &flipY, sizeof(qint32));
757 memcpy(p + 32, &hasBaseColorMap, sizeof(qint32));
758 memcpy(p + 36, &hasEmissiveMap, sizeof(qint32));
759 memcpy(p + 40, &hasNormalMap, sizeof(qint32));
760 memcpy(p + 44, &subMeshInfo.normalStrength, sizeof(float));
761 }
762 ubuf->endFullDynamicBufferUpdateForCurrentFrame();
763
764 auto setupPipeline = [rhi, &rpDesc](QSSGRhiShaderPipeline *shaderPipeline,
766 const QRhiVertexInputLayout &inputLayout)
767 {
770 ps->setDepthTest(true);
771 ps->setDepthWrite(true);
773 ps->setShaderStages(shaderPipeline->cbeginStages(), shaderPipeline->cendStages());
774 ps->setTargetBlends({ {}, {}, {}, {} });
775 ps->setRenderPassDescriptor(rpDesc.get());
776 ps->setVertexInputLayout(inputLayout);
778 return ps;
779 };
780
782 // Everything is going to be rendered twice (but note depth testing), first
783 // with polygon mode fill, then line.
785
786 for (int subMeshIdx = 0; subMeshIdx != subMeshCount; ++subMeshIdx) {
787 const SubMeshInfo &subMeshInfo(subMeshInfos[lmIdx][subMeshIdx]);
789 vertexAttrs << QRhiVertexInputAttribute(0, 0, bakeModelDrawInfo.positionFormat, bakeModelDrawInfo.positionOffset)
790 << QRhiVertexInputAttribute(0, 1, bakeModelDrawInfo.normalFormat, bakeModelDrawInfo.normalOffset)
791 << QRhiVertexInputAttribute(0, 2, bakeModelDrawInfo.lightmapUVFormat, bakeModelDrawInfo.lightmapUVOffset);
792
793 // Vertex inputs (just like the sampler uniforms) must match exactly on
794 // the shader and the application side, cannot just leave out or have
795 // unused inputs.
797 if (hasUV0) {
799 if (hasTangentAndBinormal)
801 }
802
803 const auto &lmUvRastShaderPipeline = renderer->getRhiLightmapUVRasterizationShader(shaderVariant);
804 if (!lmUvRastShaderPipeline) {
805 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Failed to load shaders"));
806 return false;
807 }
808
809 if (hasUV0) {
810 vertexAttrs << QRhiVertexInputAttribute(0, 3, bakeModelDrawInfo.uvFormat, bakeModelDrawInfo.uvOffset);
811 if (hasTangentAndBinormal) {
812 vertexAttrs << QRhiVertexInputAttribute(0, 4, bakeModelDrawInfo.tangentFormat, bakeModelDrawInfo.tangentOffset);
813 vertexAttrs << QRhiVertexInputAttribute(0, 5, bakeModelDrawInfo.binormalFormat, bakeModelDrawInfo.binormalOffset);
814 }
815 }
816
817 inputLayout.setAttributes(vertexAttrs.cbegin(), vertexAttrs.cend());
818
821 subMeshIdx * alignedUbufSize, UBUF_SIZE);
824 if (subMeshInfo.baseColorMap) {
825 const bool mipmapped = subMeshInfo.baseColorMap->flags().testFlag(QRhiTexture::MipMapped);
826 QRhiSampler *sampler = rhiCtx->sampler({ toRhi(subMeshInfo.baseColorNode->m_minFilterType),
827 toRhi(subMeshInfo.baseColorNode->m_magFilterType),
828 mipmapped ? toRhi(subMeshInfo.baseColorNode->m_mipFilterType) : QRhiSampler::None,
829 toRhi(subMeshInfo.baseColorNode->m_horizontalTilingMode),
830 toRhi(subMeshInfo.baseColorNode->m_verticalTilingMode),
832 });
833 bindings.addTexture(1, QRhiShaderResourceBinding::FragmentStage, subMeshInfo.baseColorMap, sampler);
834 } else {
835 bindings.addTexture(1, QRhiShaderResourceBinding::FragmentStage, dummyTexture, dummySampler);
836 }
837 if (subMeshInfo.emissiveMap) {
838 const bool mipmapped = subMeshInfo.emissiveMap->flags().testFlag(QRhiTexture::MipMapped);
839 QRhiSampler *sampler = rhiCtx->sampler({ toRhi(subMeshInfo.emissiveNode->m_minFilterType),
840 toRhi(subMeshInfo.emissiveNode->m_magFilterType),
841 mipmapped ? toRhi(subMeshInfo.emissiveNode->m_mipFilterType) : QRhiSampler::None,
842 toRhi(subMeshInfo.emissiveNode->m_horizontalTilingMode),
843 toRhi(subMeshInfo.emissiveNode->m_verticalTilingMode),
845 });
846 bindings.addTexture(2, QRhiShaderResourceBinding::FragmentStage, subMeshInfo.emissiveMap, sampler);
847 } else {
848 bindings.addTexture(2, QRhiShaderResourceBinding::FragmentStage, dummyTexture, dummySampler);
849 }
850 if (subMeshInfo.normalMap) {
851 if (!hasUV0 || !hasTangentAndBinormal) {
852 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("submesh %1 has a normal map, "
853 "but the mesh does not provide all three of UV0, tangent, and binormal; "
854 "expect incorrect results").arg(subMeshIdx));
855 }
856 const bool mipmapped = subMeshInfo.normalMap->flags().testFlag(QRhiTexture::MipMapped);
857 QRhiSampler *sampler = rhiCtx->sampler({ toRhi(subMeshInfo.normalMapNode->m_minFilterType),
858 toRhi(subMeshInfo.normalMapNode->m_magFilterType),
859 mipmapped ? toRhi(subMeshInfo.normalMapNode->m_mipFilterType) : QRhiSampler::None,
860 toRhi(subMeshInfo.normalMapNode->m_horizontalTilingMode),
861 toRhi(subMeshInfo.normalMapNode->m_verticalTilingMode),
863 });
864 bindings.addTexture(3, QRhiShaderResourceBinding::FragmentStage, subMeshInfo.normalMap, sampler);
865 } else {
866 bindings.addTexture(3, QRhiShaderResourceBinding::FragmentStage, dummyTexture, dummySampler);
867 }
868 QRhiShaderResourceBindings *srb = rhiCtx->srb(bindings);
869
870 QRhiGraphicsPipeline *pipeline = setupPipeline(lmUvRastShaderPipeline.get(), srb, inputLayout);
871 if (!pipeline->create()) {
872 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Failed to create graphics pipeline (mesh %1 submesh %2)").
873 arg(lmIdx).
874 arg(subMeshIdx));
875 qDeleteAll(ps);
876 qDeleteAll(psLine);
877 return false;
878 }
879 ps.append(pipeline);
880 pipeline = setupPipeline(lmUvRastShaderPipeline.get(), srb, inputLayout);
882 if (!pipeline->create()) {
883 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Failed to create graphics pipeline with line fill mode (mesh %1 submesh %2)").
884 arg(lmIdx).
885 arg(subMeshIdx));
886 qDeleteAll(ps);
887 qDeleteAll(psLine);
888 return false;
889 }
890 psLine.append(pipeline);
891 }
892
893 QRhiCommandBuffer::VertexInput vertexBuffers = { vbuf.get(), 0 };
894 const QRhiViewport viewport(0, 0, float(outputSize.width()), float(outputSize.height()));
895 bool hadViewport = false;
896
897 cb->beginPass(rt.get(), Qt::black, { 1.0f, 0 });
898 for (int subMeshIdx = 0; subMeshIdx != subMeshCount; ++subMeshIdx) {
899 const SubMeshInfo &subMeshInfo(subMeshInfos[lmIdx][subMeshIdx]);
900 cb->setGraphicsPipeline(ps[subMeshIdx]);
901 if (!hadViewport) {
902 cb->setViewport(viewport);
903 hadViewport = true;
904 }
905 cb->setShaderResources();
906 cb->setVertexInput(0, 1, &vertexBuffers, ibuf.get(), 0, QRhiCommandBuffer::IndexUInt32);
907 cb->drawIndexed(subMeshInfo.count, 1, subMeshInfo.offset);
908 cb->setGraphicsPipeline(psLine[subMeshIdx]);
909 cb->setShaderResources();
910 cb->drawIndexed(subMeshInfo.count, 1, subMeshInfo.offset);
911 }
912
913 resUpd = rhi->nextResourceUpdateBatch();
914 QRhiReadbackResult posReadResult;
915 QRhiReadbackResult normalReadResult;
916 QRhiReadbackResult baseColorReadResult;
917 QRhiReadbackResult emissionReadResult;
918 resUpd->readBackTexture({ positionData.get() }, &posReadResult);
919 resUpd->readBackTexture({ normalData.get() }, &normalReadResult);
920 resUpd->readBackTexture({ baseColorData.get() }, &baseColorReadResult);
921 resUpd->readBackTexture({ emissionData.get() }, &emissionReadResult);
922 cb->endPass(resUpd);
923
924 // Submit and wait for completion.
925 rhi->finish();
926
927 qDeleteAll(ps);
928 qDeleteAll(psLine);
929
930 Lightmap lightmap(outputSize);
931
932 // The readback results are tightly packed (which is supposed to be ensured
933 // by each rhi backend), so one line is 16 * width bytes.
934 if (posReadResult.data.size() < lightmap.entries.size() * 16) {
935 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Position data is smaller than expected"));
936 return false;
937 }
938 if (normalReadResult.data.size() < lightmap.entries.size() * 16) {
939 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Normal data is smaller than expected"));
940 return false;
941 }
942 if (baseColorReadResult.data.size() < lightmap.entries.size() * 16) {
943 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Base color data is smaller than expected"));
944 return false;
945 }
946 if (emissionReadResult.data.size() < lightmap.entries.size() * 16) {
947 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Emission data is smaller than expected"));
948 return false;
949 }
950 const float *lmPosPtr = reinterpret_cast<const float *>(posReadResult.data.constData());
951 const float *lmNormPtr = reinterpret_cast<const float *>(normalReadResult.data.constData());
952 const float *lmBaseColorPtr = reinterpret_cast<const float *>(baseColorReadResult.data.constData());
953 const float *lmEmissionPtr = reinterpret_cast<const float *>(emissionReadResult.data.constData());
954 int unusedEntries = 0;
955 for (qsizetype i = 0, ie = lightmap.entries.size(); i != ie; ++i) {
956 LightmapEntry &lmPix(lightmap.entries[i]);
957
958 float x = *lmPosPtr++;
959 float y = *lmPosPtr++;
960 float z = *lmPosPtr++;
961 lmPosPtr++;
962 lmPix.worldPos = QVector3D(x, y, z);
963
964 x = *lmNormPtr++;
965 y = *lmNormPtr++;
966 z = *lmNormPtr++;
967 lmNormPtr++;
968 lmPix.normal = QVector3D(x, y, z);
969
970 float r = *lmBaseColorPtr++;
971 float g = *lmBaseColorPtr++;
972 float b = *lmBaseColorPtr++;
973 float a = *lmBaseColorPtr++;
974 lmPix.baseColor = QVector4D(r, g, b, a);
975 if (a < 1.0f)
976 lightmap.hasBaseColorTransparency = true;
977
978 r = *lmEmissionPtr++;
979 g = *lmEmissionPtr++;
980 b = *lmEmissionPtr++;
981 lmEmissionPtr++;
982 lmPix.emission = QVector3D(r, g, b);
983
984 if (!lmPix.isValid())
985 ++unusedEntries;
986 }
987
988 sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Successfully rasterized %1/%2 lightmap texels for model %3 with lightmap size %4 in %5 ms").
989 arg(lightmap.entries.size() - unusedEntries).
990 arg(lightmap.entries.size()).
991 arg(lm.model->debugObjectName).
992 arg(QStringLiteral("(%1, %2)").arg(outputSize.width()).arg(outputSize.height())).
993 arg(rasterizeTimer.elapsed()));
994 lightmaps.append(lightmap);
995
996 for (const SubMeshInfo &subMeshInfo : std::as_const(subMeshInfos[lmIdx]))
997 geomLightmapMap[subMeshInfo.geomId] = lightmaps.size() - 1;
998 }
999
1000 sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Lightmap preparing done"));
1001 return true;
1002}
1003
1004struct RayHit
1005{
1006 RayHit(const QVector3D &org, const QVector3D &dir, float tnear = 0.0f, float tfar = std::numeric_limits<float>::infinity()) {
1007 rayhit.ray.org_x = org.x();
1008 rayhit.ray.org_y = org.y();
1009 rayhit.ray.org_z = org.z();
1010 rayhit.ray.dir_x = dir.x();
1011 rayhit.ray.dir_y = dir.y();
1012 rayhit.ray.dir_z = dir.z();
1013 rayhit.ray.tnear = tnear;
1014 rayhit.ray.tfar = tfar;
1015 rayhit.hit.u = 0.0f;
1016 rayhit.hit.v = 0.0f;
1017 rayhit.hit.geomID = RTC_INVALID_GEOMETRY_ID;
1018 }
1019
1020 RTCRayHit rayhit;
1021
1022 bool intersect(RTCScene scene)
1023 {
1024 RTCIntersectContext ctx;
1025 rtcInitIntersectContext(&ctx);
1026 rtcIntersect1(scene, &ctx, &rayhit);
1027 return rayhit.hit.geomID != RTC_INVALID_GEOMETRY_ID;
1028 }
1029};
1030
1031static inline QVector3D vectorSign(const QVector3D &v)
1032{
1033 return QVector3D(v.x() < 1.0f ? -1.0f : 1.0f,
1034 v.y() < 1.0f ? -1.0f : 1.0f,
1035 v.z() < 1.0f ? -1.0f : 1.0f);
1036}
1037
1038static inline QVector3D vectorAbs(const QVector3D &v)
1039{
1040 return QVector3D(std::abs(v.x()),
1041 std::abs(v.y()),
1042 std::abs(v.z()));
1043}
1044
1045void QSSGLightmapperPrivate::computeDirectLight()
1046{
1047 sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Computing direct lighting..."));
1048 QElapsedTimer fullDirectLightTimer;
1049 fullDirectLightTimer.start();
1050
1051 const int bakedLightingModelCount = bakedLightingModels.size();
1052 Q_ASSERT(lightmaps.size() == bakedLightingModelCount);
1053
1054 QVector<QFuture<void>> futures;
1055
1056 for (int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
1057 const QSSGBakedLightingModel &lm(bakedLightingModels[lmIdx]);
1058 Lightmap &lightmap(lightmaps[lmIdx]);
1059
1060 // direct lighting is relatively fast to calculate, so parallelize per model
1061 futures << QtConcurrent::run([this, &lm, &lightmap] {
1062 QElapsedTimer directLightTimer;
1063 directLightTimer.start();
1064
1065 const int lightCount = lights.size();
1066 for (LightmapEntry &lmPix : lightmap.entries) {
1067 if (!lmPix.isValid())
1068 continue;
1069
1070 QVector3D worldPos = lmPix.worldPos;
1071 if (options.useAdaptiveBias)
1072 worldPos += vectorSign(lmPix.normal) * vectorAbs(worldPos * 0.0000002f);
1073
1074 // 'lights' should have all lights that are either BakeModeIndirect or BakeModeAll
1075 for (int i = 0; i < lightCount; ++i) {
1076 const Light &light(lights[i]);
1077
1078 QVector3D lightWorldPos;
1079 float dist = std::numeric_limits<float>::infinity();
1080 float attenuation = 1.0f;
1081 if (light.type == Light::Directional) {
1082 lightWorldPos = worldPos - light.direction;
1083 } else {
1084 lightWorldPos = light.worldPos;
1085 dist = (worldPos - lightWorldPos).length();
1086 attenuation = 1.0f / (light.constantAttenuation
1087 + light.linearAttenuation * dist
1088 + light.quadraticAttenuation * dist * dist);
1089 if (light.type == Light::Spot) {
1090 const float spotAngle = QVector3D::dotProduct((worldPos - lightWorldPos).normalized(),
1091 light.direction.normalized());
1092 if (spotAngle > light.cosConeAngle) {
1093 // spotFactor = smoothstep(light.cosConeAngle, light.cosInnerConeAngle, spotAngle);
1094 const float edge0 = light.cosConeAngle;
1095 const float edge1 = light.cosInnerConeAngle;
1096 const float x = spotAngle;
1097 const float t = qBound(0.0f, (x - edge0) / (edge1 - edge0), 1.0f);
1098 const float spotFactor = t * t * (3.0f - 2.0f * t);
1099 attenuation *= spotFactor;
1100 } else {
1101 attenuation = 0.0f;
1102 }
1103 }
1104 }
1105
1106 const QVector3D N = lmPix.normal;
1107 const QVector3D L = (lightWorldPos - worldPos).normalized();
1108 const float energy = qMax(0.0f, QVector3D::dotProduct(N, L)) * attenuation;
1109 if (qFuzzyIsNull(energy))
1110 continue;
1111
1112 // trace a ray from this point towards the light, and see if something is hit on the way
1113 RayHit ray(worldPos, L, options.bias, dist);
1114 const bool lightReachable = !ray.intersect(rscene);
1115 if (lightReachable) {
1116 // direct light must always be stored because indirect computation will need it
1117 lmPix.directLight += light.color * energy;
1118 // but we take it into account in the final result only for lights that have BakeModeAll
1119 if (!light.indirectOnly)
1120 lmPix.allLight += light.color * energy;
1121 }
1122 }
1123 }
1124
1125 sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Direct light computed for model %1 in %2 ms").
1126 arg(lm.model->debugObjectName).
1127 arg(directLightTimer.elapsed()));
1128 });
1129 }
1130
1131 for (QFuture<void> &future : futures)
1133
1134 sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Direct light computation completed in %1 ms").
1135 arg(fullDirectLightTimer.elapsed()));
1136}
1137
1138// xorshift rng. this is called a lot -> rand/QRandomGenerator is out of question (way too slow)
1139static inline float uniformRand()
1140{
1141 static thread_local quint32 state = QRandomGenerator::global()->generate();
1142 state ^= state << 13;
1143 state ^= state >> 17;
1144 state ^= state << 5;
1145 return float(state) / float(UINT32_MAX);
1146}
1147
1148static inline QVector3D cosWeightedHemisphereSample()
1149{
1150 const float r1 = uniformRand();
1151 const float r2 = uniformRand() * 2.0f * float(M_PI);
1152 const float sqr1 = std::sqrt(r1);
1153 const float sqr1m = std::sqrt(1.0f - r1);
1154 return QVector3D(sqr1 * std::cos(r2), sqr1 * std::sin(r2), sqr1m);
1155}
1156
1157void QSSGLightmapperPrivate::computeIndirectLight()
1158{
1159 sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Computing indirect lighting..."));
1160 QElapsedTimer fullIndirectLightTimer;
1161 fullIndirectLightTimer.start();
1162
1163 const int bakedLightingModelCount = bakedLightingModels.size();
1164
1165 for (int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
1166 // here we only care about the models that will store the lightmap image persistently
1167 if (!bakedLightingModels[lmIdx].model->hasLightmap())
1168 continue;
1169
1170 const QSSGBakedLightingModel &lm(bakedLightingModels[lmIdx]);
1171 Lightmap &lightmap(lightmaps[lmIdx]);
1172 int texelsDone = 0;
1173 sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Total texels to compute for model %1: %2").
1174 arg(lm.model->debugObjectName).
1175 arg(lightmap.entries.size()));
1176 QElapsedTimer indirectLightTimer;
1177 indirectLightTimer.start();
1178
1179 // indirect lighting is slow, so parallelize per groups of samples,
1180 // e.g. if sample count is 256 and workgroup size is 32, then do up to
1181 // 8 sets in parallel, each calculating 32 samples (how many of the 8
1182 // are really done concurrently that's up to the thread pool to manage)
1183
1184 int wgSizePerGroup = qMax(1, options.indirectLightWorkgroupSize);
1185 int wgCount = options.indirectLightSamples / wgSizePerGroup;
1186 if (options.indirectLightSamples % wgSizePerGroup)
1187 ++wgCount;
1188
1189 QVector<QFuture<QVector3D>> wg(wgCount);
1190
1191 sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Computing indirect lighting for model %1 with key %2").
1192 arg(lm.model->debugObjectName).
1193 arg(lm.model->lightmapKey));
1194 sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Sample count: %1, Workgroup size: %2, Max bounces: %3, Multiplier: %4").
1195 arg(options.indirectLightSamples).
1196 arg(wgSizePerGroup).
1197 arg(options.indirectLightBounces).
1198 arg(options.indirectLightFactor));
1199 for (LightmapEntry &lmPix : lightmap.entries) {
1200 if (!lmPix.isValid())
1201 continue;
1202
1203 for (int wgIdx = 0; wgIdx < wgCount; ++wgIdx) {
1204 const int beginIdx = wgIdx * wgSizePerGroup;
1205 const int endIdx = qMin(beginIdx + wgSizePerGroup, options.indirectLightSamples);
1206
1207 wg[wgIdx] = QtConcurrent::run([this, beginIdx, endIdx, &lmPix] {
1208 QVector3D wgResult;
1209 for (int sampleIdx = beginIdx; sampleIdx < endIdx; ++sampleIdx) {
1210 QVector3D position = lmPix.worldPos;
1211 QVector3D normal = lmPix.normal;
1212 QVector3D throughput(1.0f, 1.0f, 1.0f);
1213 QVector3D sampleResult;
1214
1215 for (int bounce = 0; bounce < options.indirectLightBounces; ++bounce) {
1216 if (options.useAdaptiveBias)
1217 position += vectorSign(normal) * vectorAbs(position * 0.0000002f);
1218
1219 // get a sample using a cosine-weighted hemisphere sampler
1220 const QVector3D sample = cosWeightedHemisphereSample();
1221
1222 // transform to the point's local coordinate system
1223 const QVector3D v0 = qFuzzyCompare(qAbs(normal.z()), 1.0f)
1224 ? QVector3D(0.0f, 1.0f, 0.0f)
1225 : QVector3D(0.0f, 0.0f, 1.0f);
1226 const QVector3D tangent = QVector3D::crossProduct(v0, normal).normalized();
1227 const QVector3D bitangent = QVector3D::crossProduct(tangent, normal).normalized();
1229 tangent.x() * sample.x() + bitangent.x() * sample.y() + normal.x() * sample.z(),
1230 tangent.y() * sample.x() + bitangent.y() * sample.y() + normal.y() * sample.z(),
1231 tangent.z() * sample.x() + bitangent.z() * sample.y() + normal.z() * sample.z());
1232 direction.normalize();
1233
1234 // probability distribution function
1235 const float NdotL = qMax(0.0f, QVector3D::dotProduct(normal, direction));
1236 const float pdf = NdotL / float(M_PI);
1237 if (qFuzzyIsNull(pdf))
1238 break;
1239
1240 // shoot ray, stop if no hit
1241 RayHit ray(position, direction, options.bias);
1242 if (!ray.intersect(rscene))
1243 break;
1244
1245 // see what (sub)mesh and which texel it intersected with
1246 const LightmapEntry &hitEntry = texelForLightmapUV(ray.rayhit.hit.geomID,
1247 ray.rayhit.hit.u,
1248 ray.rayhit.hit.v);
1249
1250 // won't bounce further from a back face
1251 const bool hitBackFace = QVector3D::dotProduct(hitEntry.normal, direction) > 0.0f;
1252 if (hitBackFace)
1253 break;
1254
1255 // the BRDF of a diffuse surface is albedo / PI
1256 const QVector3D brdf = hitEntry.baseColor.toVector3D() / float(M_PI);
1257
1258 // calculate result for this bounce
1259 sampleResult += throughput * hitEntry.emission;
1260 throughput *= brdf * NdotL / pdf;
1261 sampleResult += throughput * hitEntry.directLight;
1262
1263 // stop if we guess there's no point in bouncing further
1264 // (low throughput path wouldn't contribute much)
1265 const float p = qMax(qMax(throughput.x(), throughput.y()), throughput.z());
1266 if (p < uniformRand())
1267 break;
1268
1269 // was not terminated: boost the energy by the probability to be terminated
1270 throughput /= p;
1271
1272 // next bounce starts from the hit's position
1273 position = hitEntry.worldPos;
1274 normal = hitEntry.normal;
1275 }
1276
1277 wgResult += sampleResult;
1278 }
1279 return wgResult;
1280 });
1281 }
1282
1283 QVector3D totalIndirect;
1284 for (const auto &future : wg)
1285 totalIndirect += future.result();
1286
1287 lmPix.allLight += totalIndirect * options.indirectLightFactor / options.indirectLightSamples;
1288
1289 ++texelsDone;
1290 if (texelsDone % 10000 == 0)
1291 sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("%1 texels left").
1292 arg(lightmap.entries.size() - texelsDone));
1293
1294 if (bakingControl.cancelled)
1295 return;
1296 }
1297 sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Indirect lighting computed for model %1 with key %2 in %3 ms").
1298 arg(lm.model->debugObjectName).
1299 arg(lm.model->lightmapKey).
1300 arg(indirectLightTimer.elapsed()));
1301 }
1302
1303 sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Indirect light computation completed in %1 ms").
1304 arg(fullIndirectLightTimer.elapsed()));
1305}
1306
1307struct Edge {
1308 std::array<QVector3D, 2> pos;
1309 std::array<QVector3D, 2> normal;
1310};
1311
1312inline bool operator==(const Edge &a, const Edge &b)
1313{
1314 return qFuzzyCompare(a.pos[0], b.pos[0])
1315 && qFuzzyCompare(a.pos[1], b.pos[1])
1316 && qFuzzyCompare(a.normal[0], b.normal[0])
1317 && qFuzzyCompare(a.normal[1], b.normal[1]);
1318}
1319
1320inline size_t qHash(const Edge &e, size_t seed) Q_DECL_NOTHROW
1321{
1322 return qHash(e.pos[0].x(), seed) ^ qHash(e.pos[0].y()) ^ qHash(e.pos[0].z())
1323 ^ qHash(e.pos[1].x()) ^ qHash(e.pos[1].y()) ^ qHash(e.pos[1].z());
1324}
1325
1326struct EdgeUV {
1327 std::array<QVector2D, 2> uv;
1328 bool seam = false;
1329};
1330
1331struct SeamUV {
1332 std::array<std::array<QVector2D, 2>, 2> uv;
1333};
1334
1335static inline bool vectorLessThan(const QVector3D &a, const QVector3D &b)
1336{
1337 if (a.x() == b.x()) {
1338 if (a.y() == b.y())
1339 return a.z() < b.z();
1340 else
1341 return a.y() < b.y();
1342 }
1343 return a.x() < b.x();
1344}
1345
1346static inline float floatSign(float f)
1347{
1348 return f > 0.0f ? 1.0f : (f < 0.0f ? -1.0f : 0.0f);
1349}
1350
1351static inline QVector2D flooredVec(const QVector2D &v)
1352{
1353 return QVector2D(std::floor(v.x()), std::floor(v.y()));
1354}
1355
1356static inline QVector2D projectPointToLine(const QVector2D &point, const std::array<QVector2D, 2> &line)
1357{
1358 const QVector2D p = point - line[0];
1359 const QVector2D n = line[1] - line[0];
1360 const float lengthSquared = n.lengthSquared();
1361 if (!qFuzzyIsNull(lengthSquared)) {
1362 const float d = (n.x() * p.x() + n.y() * p.y()) / lengthSquared;
1363 return d <= 0.0f ? line[0] : (d >= 1.0f ? line[1] : line[0] + n * d);
1364 }
1365 return line[0];
1366}
1367
1368static void blendLine(const QVector2D &from, const QVector2D &to,
1369 const QVector2D &uvFrom, const QVector2D &uvTo,
1370 const QByteArray &readBuf, QByteArray &writeBuf,
1371 const QSize &lightmapPixelSize)
1372{
1373 const QVector2D size(lightmapPixelSize.width(), lightmapPixelSize.height());
1374 const std::array<QVector2D, 2> line = { QVector2D(from.x(), 1.0f - from.y()) * size,
1375 QVector2D(to.x(), 1.0f - to.y()) * size };
1376 const float lineLength = line[0].distanceToPoint(line[1]);
1377 if (qFuzzyIsNull(lineLength))
1378 return;
1379
1380 const QVector2D startPixel = flooredVec(line[0]);
1381 const QVector2D endPixel = flooredVec(line[1]);
1382
1383 const QVector2D dir = (line[1] - line[0]).normalized();
1384 const QVector2D tStep(1.0f / std::abs(dir.x()), 1.0f / std::abs(dir.y()));
1385 const QVector2D pixelStep(floatSign(dir.x()), floatSign(dir.y()));
1386
1387 QVector2D nextT(std::fmod(line[0].x(), 1.0f), std::fmod(line[0].y(), 1.0f));
1388 if (pixelStep.x() == 1.0f)
1389 nextT.setX(1.0f - nextT.x());
1390 if (pixelStep.y() == 1.0f)
1391 nextT.setY(1.0f - nextT.y());
1392 nextT /= QVector2D(std::abs(dir.x()), std::abs(dir.y()));
1393 if (std::isnan(nextT.x()))
1394 nextT.setX(std::numeric_limits<float>::max());
1395 if (std::isnan(nextT.y()))
1396 nextT.setY(std::numeric_limits<float>::max());
1397
1398 float *fpW = reinterpret_cast<float *>(writeBuf.data());
1399 const float *fpR = reinterpret_cast<const float *>(readBuf.constData());
1400
1401 QVector2D pixel = startPixel;
1402
1403 while (startPixel.distanceToPoint(pixel) < lineLength + 1.0f) {
1404 const QVector2D point = projectPointToLine(pixel + QVector2D(0.5f, 0.5f), line);
1405 const float t = line[0].distanceToPoint(point) / lineLength;
1406 const QVector2D uvInterp = uvFrom * (1.0 - t) + uvTo * t;
1407 const QVector2D sampledPixel = flooredVec(QVector2D(uvInterp.x(), 1.0f - uvInterp.y()) * size);
1408
1409 const int sampOfs = (int(sampledPixel.x()) + int(sampledPixel.y()) * lightmapPixelSize.width()) * 4;
1410 const QVector3D sampledColor(fpR[sampOfs], fpR[sampOfs + 1], fpR[sampOfs + 2]);
1411 const int pixOfs = (int(pixel.x()) + int(pixel.y()) * lightmapPixelSize.width()) * 4;
1412 QVector3D currentColor(fpW[pixOfs], fpW[pixOfs + 1], fpW[pixOfs + 2]);
1413 currentColor = currentColor * 0.6f + sampledColor * 0.4f;
1414 fpW[pixOfs] = currentColor.x();
1415 fpW[pixOfs + 1] = currentColor.y();
1416 fpW[pixOfs + 2] = currentColor.z();
1417
1418 if (pixel != endPixel) {
1419 if (nextT.x() < nextT.y()) {
1420 pixel.setX(pixel.x() + pixelStep.x());
1421 nextT.setX(nextT.x() + tStep.x());
1422 } else {
1423 pixel.setY(pixel.y() + pixelStep.y());
1424 nextT.setY(nextT.y() + tStep.y());
1425 }
1426 } else {
1427 break;
1428 }
1429 }
1430}
1431
1432bool QSSGLightmapperPrivate::postProcess()
1433{
1434 QRhi *rhi = rhiCtx->rhi();
1435 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
1436 const int bakedLightingModelCount = bakedLightingModels.size();
1437
1438 sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Post-processing..."));
1439 for (int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
1440 QElapsedTimer postProcessTimer;
1441 postProcessTimer.start();
1442
1443 const QSSGBakedLightingModel &lm(bakedLightingModels[lmIdx]);
1444 // only care about the ones that will store the lightmap image persistently
1445 if (!lm.model->hasLightmap())
1446 continue;
1447
1448 Lightmap &lightmap(lightmaps[lmIdx]);
1449
1450 // Assemble the RGBA32F image from the baker data structures
1451 QByteArray lightmapFP32(lightmap.entries.size() * 4 * sizeof(float), Qt::Uninitialized);
1452 float *lightmapFloatPtr = reinterpret_cast<float *>(lightmapFP32.data());
1453 for (const LightmapEntry &lmPix : std::as_const(lightmap.entries)) {
1454 *lightmapFloatPtr++ = lmPix.allLight.x();
1455 *lightmapFloatPtr++ = lmPix.allLight.y();
1456 *lightmapFloatPtr++ = lmPix.allLight.z();
1457 *lightmapFloatPtr++ = lmPix.isValid() ? 1.0f : 0.0f;
1458 }
1459
1460 // Dilate
1461 const QRhiViewport viewport(0, 0, float(lightmap.pixelSize.width()), float(lightmap.pixelSize.height()));
1462
1463 std::unique_ptr<QRhiTexture> lightmapTex(rhi->newTexture(QRhiTexture::RGBA32F, lightmap.pixelSize));
1464 if (!lightmapTex->create()) {
1465 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Failed to create FP32 texture for postprocessing"));
1466 return false;
1467 }
1468 std::unique_ptr<QRhiTexture> dilatedLightmapTex(rhi->newTexture(QRhiTexture::RGBA32F, lightmap.pixelSize, 1,
1470 if (!dilatedLightmapTex->create()) {
1471 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Failed to create FP32 dest. texture for postprocessing"));
1472 return false;
1473 }
1474 QRhiTextureRenderTargetDescription rtDescDilate(dilatedLightmapTex.get());
1475 std::unique_ptr<QRhiTextureRenderTarget> rtDilate(rhi->newTextureRenderTarget(rtDescDilate));
1476 std::unique_ptr<QRhiRenderPassDescriptor> rpDescDilate(rtDilate->newCompatibleRenderPassDescriptor());
1477 rtDilate->setRenderPassDescriptor(rpDescDilate.get());
1478 if (!rtDilate->create()) {
1479 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Failed to create postprocessing texture render target"));
1480 return false;
1481 }
1483 QRhiTextureSubresourceUploadDescription lightmapTexUpload(lightmapFP32.constData(), lightmapFP32.size());
1484 resUpd->uploadTexture(lightmapTex.get(), QRhiTextureUploadDescription({ 0, 0, lightmapTexUpload }));
1488 bindings.addTexture(0, QRhiShaderResourceBinding::FragmentStage, lightmapTex.get(), nearestSampler);
1489 renderer->rhiQuadRenderer()->prepareQuad(rhiCtx, resUpd);
1490 const auto &lmDilatePipeline = renderer->getRhiLightmapDilateShader();
1491 if (!lmDilatePipeline) {
1492 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Failed to load shaders"));
1493 return false;
1494 }
1496 dilatePs.viewport = viewport;
1497 dilatePs.shaderPipeline = lmDilatePipeline.get();
1498 renderer->rhiQuadRenderer()->recordRenderQuadPass(rhiCtx, &dilatePs, rhiCtx->srb(bindings), rtDilate.get(), QSSGRhiQuadRenderer::UvCoords);
1499 resUpd = rhi->nextResourceUpdateBatch();
1500 QRhiReadbackResult dilateReadResult;
1501 resUpd->readBackTexture({ dilatedLightmapTex.get() }, &dilateReadResult);
1502 cb->resourceUpdate(resUpd);
1503
1504 // Submit and wait for completion.
1505 rhi->finish();
1506
1507 lightmap.imageFP32 = dilateReadResult.data;
1508
1509 // Reduce UV seams by collecting all edges (going through all
1510 // triangles), looking for (fuzzy)matching ones, then drawing lines
1511 // with blending on top.
1512 const DrawInfo &drawInfo(drawInfos[lmIdx]);
1513 const char *vbase = drawInfo.vertexData.constData();
1514 const quint32 *ibase = reinterpret_cast<const quint32 *>(drawInfo.indexData.constData());
1515
1516 // topology is Triangles, would be indexed draw - get rid of the index
1517 // buffer, need nothing but triangles afterwards
1518 qsizetype assembledVertexCount = 0;
1519 for (SubMeshInfo &subMeshInfo : subMeshInfos[lmIdx])
1520 assembledVertexCount += subMeshInfo.count;
1521 QVector<QVector3D> smPos(assembledVertexCount);
1522 QVector<QVector3D> smNormal(assembledVertexCount);
1523 QVector<QVector2D> smCoord(assembledVertexCount);
1524 qsizetype vertexIdx = 0;
1525 for (SubMeshInfo &subMeshInfo : subMeshInfos[lmIdx]) {
1526 for (quint32 i = 0; i < subMeshInfo.count; ++i) {
1527 const quint32 idx = *(ibase + subMeshInfo.offset + i);
1528 const float *src = reinterpret_cast<const float *>(vbase + idx * drawInfo.vertexStride + drawInfo.positionOffset);
1529 float x = *src++;
1530 float y = *src++;
1531 float z = *src++;
1532 smPos[vertexIdx] = QVector3D(x, y, z);
1533 src = reinterpret_cast<const float *>(vbase + idx * drawInfo.vertexStride + drawInfo.normalOffset);
1534 x = *src++;
1535 y = *src++;
1536 z = *src++;
1537 smNormal[vertexIdx] = QVector3D(x, y, z);
1538 src = reinterpret_cast<const float *>(vbase + idx * drawInfo.vertexStride + drawInfo.lightmapUVOffset);
1539 x = *src++;
1540 y = *src++;
1541 smCoord[vertexIdx] = QVector2D(x, y);
1542 ++vertexIdx;
1543 }
1544 }
1545
1546 QHash<Edge, EdgeUV> edgeUVMap;
1547 QVector<SeamUV> seams;
1548 for (vertexIdx = 0; vertexIdx < assembledVertexCount; vertexIdx += 3) {
1549 QVector3D triVert[3] = { smPos[vertexIdx], smPos[vertexIdx + 1], smPos[vertexIdx + 2] };
1550 QVector3D triNorm[3] = { smNormal[vertexIdx], smNormal[vertexIdx + 1], smNormal[vertexIdx + 2] };
1551 QVector2D triUV[3] = { smCoord[vertexIdx], smCoord[vertexIdx + 1], smCoord[vertexIdx + 2] };
1552
1553 for (int i = 0; i < 3; ++i) {
1554 int i0 = i;
1555 int i1 = (i + 1) % 3;
1556 if (vectorLessThan(triVert[i1], triVert[i0]))
1557 std::swap(i0, i1);
1558
1559 const Edge e = {
1560 { triVert[i0], triVert[i1] },
1561 { triNorm[i0], triNorm[i1] }
1562 };
1563 const EdgeUV edgeUV = { { triUV[i0], triUV[i1] } };
1564 auto it = edgeUVMap.find(e);
1565 if (it == edgeUVMap.end()) {
1566 edgeUVMap.insert(e, edgeUV);
1567 } else if (!qFuzzyCompare(it->uv[0], edgeUV.uv[0]) || !qFuzzyCompare(it->uv[1], edgeUV.uv[1])) {
1568 if (!it->seam) {
1569 seams.append(SeamUV({ { edgeUV.uv, it->uv } }));
1570 it->seam = true;
1571 }
1572 }
1573 }
1574 }
1575 qDebug() << "lm:" << seams.size() << "UV seams in" << lm.model;
1576
1577 QByteArray workBuf(lightmap.imageFP32.size(), Qt::Uninitialized);
1578 for (int blendIter = 0; blendIter < LM_SEAM_BLEND_ITER_COUNT; ++blendIter) {
1579 memcpy(workBuf.data(), lightmap.imageFP32.constData(), lightmap.imageFP32.size());
1580 for (int seamIdx = 0, end = seams.size(); seamIdx != end; ++seamIdx) {
1581 const SeamUV &seam(seams[seamIdx]);
1582 blendLine(seam.uv[0][0], seam.uv[0][1],
1583 seam.uv[1][0], seam.uv[1][1],
1584 workBuf, lightmap.imageFP32, lightmap.pixelSize);
1585 blendLine(seam.uv[1][0], seam.uv[1][1],
1586 seam.uv[0][0], seam.uv[0][1],
1587 workBuf, lightmap.imageFP32, lightmap.pixelSize);
1588 }
1589 }
1590
1591 sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Post-processing for model %1 with key %2 done in %3").
1592 arg(lm.model->debugObjectName).
1593 arg(lm.model->lightmapKey).
1594 arg(postProcessTimer.elapsed()));
1595 }
1596
1597 return true;
1598}
1599
1600bool QSSGLightmapperPrivate::storeLightmaps()
1601{
1602 const int bakedLightingModelCount = bakedLightingModels.size();
1603 QByteArray listContents;
1604
1605 for (int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
1606 const QSSGBakedLightingModel &lm(bakedLightingModels[lmIdx]);
1607 // only care about the ones that want to store the lightmap image persistently
1608 if (!lm.model->hasLightmap())
1609 continue;
1610
1611 QElapsedTimer writeTimer;
1612 writeTimer.start();
1613
1614 // An empty outputFolder equates to working directory
1615 QString outputFolder;
1616 if (!lm.model->lightmapLoadPath.startsWith(QStringLiteral(":/")))
1617 outputFolder = lm.model->lightmapLoadPath;
1618
1620 const QByteArray fns = fn.toUtf8();
1621
1622 listContents += QFileInfo(fn).absoluteFilePath().toUtf8();
1623 listContents += '\n';
1624
1625 const Lightmap &lightmap(lightmaps[lmIdx]);
1626
1627 if (SaveEXR(reinterpret_cast<const float *>(lightmap.imageFP32.constData()),
1628 lightmap.pixelSize.width(), lightmap.pixelSize.height(),
1629 4, false, fns.constData(), nullptr) < 0)
1630 {
1631 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Failed to write out lightmap"));
1632 return false;
1633 }
1634
1635 sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Lightmap saved for model %1 to %2 in %3 ms").
1636 arg(lm.model->debugObjectName).
1637 arg(fn).
1638 arg(writeTimer.elapsed()));
1639 const DrawInfo &bakeModelDrawInfo(drawInfos[lmIdx]);
1640 if (bakeModelDrawInfo.meshWithLightmapUV.isValid()) {
1641 writeTimer.start();
1644 bakeModelDrawInfo.meshWithLightmapUV.save(&f);
1645 } else {
1646 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Failed to write mesh with lightmap UV data to '%1'").
1647 arg(f.fileName()));
1648 return false;
1649 }
1650 sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Lightmap-compatible mesh saved for model %1 to %2 in %3 ms").
1651 arg(lm.model->debugObjectName).
1652 arg(f.fileName()).
1653 arg(writeTimer.elapsed()));
1654 } // else the mesh had a lightmap uv channel to begin with, no need to save another version of it
1655 }
1656
1659 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Failed to create lightmap list file %1").
1660 arg(listFile.fileName()));
1661 return false;
1662 }
1663 listFile.write(listContents);
1664
1665 return true;
1666}
1667
1668void QSSGLightmapperPrivate::sendOutputInfo(QSSGLightmapper::BakingStatus type, std::optional<QString> msg)
1669{
1671
1672 switch (type)
1673 {
1675 return;
1677 result = QStringLiteral("[lm] Progress");
1678 break;
1680 result = QStringLiteral("[lm] Error");
1681 break;
1683 result = QStringLiteral("[lm] Warning");
1684 break;
1686 result = QStringLiteral("[lm] Cancelled");
1687 break;
1689 result = QStringLiteral("[lm] Complete");
1690 break;
1691 }
1692
1693 if (msg.has_value())
1694 result.append(QStringLiteral(": ") + msg.value());
1695
1697 qWarning() << result;
1698 else
1699 qDebug() << result;
1700
1701 if (outputCallback)
1702 outputCallback(type, msg, &bakingControl);
1703}
1704
1706{
1707 QElapsedTimer totalTimer;
1708 totalTimer.start();
1709
1710 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Bake starting..."));
1711 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Total models registered: %1").arg(d->bakedLightingModels.size()));
1712
1713 if (d->bakedLightingModels.isEmpty()) {
1714 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Cancelled, QStringLiteral("Cancelled by LightMapper, No Models to bake"));
1715 return false;
1716 }
1717
1718 if (!d->commitGeometry()) {
1719 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Baking failed"));
1720 return false;
1721 }
1722
1723 if (!d->prepareLightmaps()) {
1724 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Baking failed"));
1725 return false;
1726 }
1727
1728 if (d->bakingControl.cancelled) {
1729 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Cancelled, QStringLiteral("Cancelled by user"));
1730 return false;
1731 }
1732
1733 d->computeDirectLight();
1734
1735 if (d->bakingControl.cancelled) {
1736 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Cancelled, QStringLiteral("Cancelled by user"));
1737 return false;
1738 }
1739
1740 if (d->options.indirectLightEnabled)
1741 d->computeIndirectLight();
1742
1743 if (d->bakingControl.cancelled) {
1744 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Cancelled, QStringLiteral("Cancelled by user"));
1745 return false;
1746 }
1747
1748 if (!d->postProcess()) {
1749 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Baking failed"));
1750 return false;
1751 }
1752
1753 if (d->bakingControl.cancelled) {
1754 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Cancelled, QStringLiteral("Cancelled by user"));
1755 return false;
1756 }
1757
1758 if (!d->storeLightmaps()) {
1759 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Baking failed"));
1760 return false;
1761 }
1762
1763 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Baking took %1 ms").arg(totalTimer.elapsed()));
1764 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Complete, std::nullopt);
1765 return true;
1766}
1767
1768#else
1769
1771{
1772}
1773
1775{
1776}
1777
1779{
1780}
1781
1783{
1784}
1785
1787{
1788}
1789
1791{
1792 return 0;
1793}
1794
1796{
1797 qWarning("Qt Quick 3D was built without the lightmapper; cannot bake lightmaps");
1798 return false;
1799}
1800
1801#endif // QT_QUICK3D_HAS_LIGHTMAPPER
1802
1804{
1806 if (!model.lightmapLoadPath.isEmpty()) {
1807 result += model.lightmapLoadPath;
1808 if (!result.endsWith(QLatin1Char('/')))
1809 result += QLatin1Char('/');
1810 }
1811 switch (asset) {
1813 result += QStringLiteral("qlm_%1.exr").arg(model.lightmapKey);
1814 break;
1816 result += QStringLiteral("qlm_%1.mesh").arg(model.lightmapKey);
1817 break;
1818 default:
1819 return QString();
1820 }
1821 return result;
1822}
1823
1825{
1826 QString result = outputFolder;
1827 if (!result.isEmpty() && !result.endsWith(QLatin1Char('/')))
1828 result += QLatin1Char('/');
1829
1830 switch (asset) {
1832 result += QStringLiteral("qlm_%1.exr").arg(model.lightmapKey);
1833 break;
1835 result += QStringLiteral("qlm_%1.mesh").arg(model.lightmapKey);
1836 break;
1837 default:
1838 result += lightmapAssetPathForSave(asset, outputFolder);
1839 break;
1840 }
1841 return result;
1842}
1843
1845{
1846 QString result = outputFolder;
1847 if (!result.isEmpty() && !result.endsWith(QLatin1Char('/')))
1848 result += QLatin1Char('/');
1849
1850 switch (asset) {
1852 result += QStringLiteral("qlm_list.txt");
1853 default:
1854 break;
1855 }
1856 return result;
1857}
1858
\inmodule QtCore
Definition qbytearray.h:57
char * data()
\macro QT_NO_CAST_FROM_BYTEARRAY
Definition qbytearray.h:534
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:474
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:122
\inmodule QtCore
qint64 elapsed() const noexcept
Returns the number of milliseconds since this QElapsedTimer was last started.
void start() noexcept
Starts this timer.
\inmodule QtCore \reentrant
Definition qfileinfo.h:22
QString absoluteFilePath() const
Returns an absolute path including the file name.
\inmodule QtCore
Definition qfile.h:93
\inmodule QtCore
Definition qhash.h:818
iterator find(const Key &key)
Returns an iterator pointing to the item with the key in the hash.
Definition qhash.h:1258
iterator end() noexcept
Returns an \l{STL-style iterators}{STL-style iterator} pointing to the imaginary item after the last ...
Definition qhash.h:1206
iterator insert(const Key &key, const T &value)
Inserts a new item with the key and a value of value.
Definition qhash.h:1283
Definition qlist.h:74
qsizetype size() const noexcept
Definition qlist.h:386
QList< T > & fill(parameter_type t, qsizetype size=-1)
Definition qlist.h:896
bool isEmpty() const noexcept
Definition qlist.h:390
void reserve(qsizetype size)
Definition qlist.h:746
void resize(qsizetype size)
Definition qlist.h:392
void append(parameter_type t)
Definition qlist.h:441
The QMatrix4x4 class represents a 4x4 transformation matrix in 3D space.
Definition qmatrix4x4.h:25
QPoint map(const QPoint &point) const
Maps point by multiplying this matrix by point.
Definition qmatrix4x4.h:908
quint32 generate()
Generates a 32-bit random quantity and returns it.
Definition qrandom.h:48
static Q_DECL_CONST_FUNCTION QRandomGenerator * global()
\threadsafe
Definition qrandom.h:275
@ Immutable
Definition qrhi.h:837
@ Dynamic
Definition qrhi.h:839
@ IndexBuffer
Definition qrhi.h:844
@ VertexBuffer
Definition qrhi.h:843
@ UniformBuffer
Definition qrhi.h:845
\inmodule QtGui
Definition qrhi.h:568
\inmodule QtGui
Definition qrhi.h:1614
QPair< QRhiBuffer *, quint32 > VertexInput
Synonym for QPair<QRhiBuffer *, quint32>.
Definition qrhi.h:1643
IndexFormat
Specifies the index data type.
Definition qrhi.h:1616
\inmodule QtGui
Definition qrhi.h:1241
void setTargetBlends(std::initializer_list< TargetBlend > list)
Sets the list of render target blend settings.
Definition qrhi.h:1369
void setDepthWrite(bool enable)
Controls the writing out of depth data into the depth buffer based on enable.
Definition qrhi.h:1385
void setShaderResourceBindings(QRhiShaderResourceBindings *srb)
Associates with srb describing the resource binding layout and the resources (QRhiBuffer,...
Definition qrhi.h:1433
void setDepthOp(CompareOp op)
Sets the depth comparison function op.
Definition qrhi.h:1388
void setVertexInputLayout(const QRhiVertexInputLayout &layout)
Specifies the vertex input layout.
Definition qrhi.h:1430
void setShaderStages(std::initializer_list< QRhiShaderStage > list)
Sets the list of shader stages.
Definition qrhi.h:1417
void setRenderPassDescriptor(QRhiRenderPassDescriptor *desc)
Associates with the specified QRhiRenderPassDescriptor desc.
Definition qrhi.h:1436
void setTopology(Topology t)
Sets the primitive topology t.
Definition qrhi.h:1361
virtual bool create()=0
Creates the corresponding native graphics resources.
void setDepthTest(bool enable)
Enables or disables depth testing based on enable.
Definition qrhi.h:1382
void setPolygonMode(PolygonMode mode)
Sets the polygon mode.
Definition qrhi.h:1442
\inmodule QtGui
Definition qrhi.h:1694
void uploadStaticBuffer(QRhiBuffer *buf, quint32 offset, quint32 size, const void *data)
Enqueues updating a region of a QRhiBuffer buf created with the type QRhiBuffer::Immutable or QRhiBuf...
Definition qrhi.cpp:8615
void uploadTexture(QRhiTexture *tex, const QRhiTextureUploadDescription &desc)
Enqueues uploading the image data for one or more mip levels in one or more layers of the texture tex...
Definition qrhi.cpp:8681
void readBackTexture(const QRhiReadbackDescription &rb, QRhiReadbackResult *result)
Enqueues a texture-to-host copy operation as described by rb.
Definition qrhi.cpp:8788
\inmodule QtGui
Definition qrhi.h:1007
@ ClampToEdge
Definition qrhi.h:1017
\inmodule QtGui
Definition qrhi.h:1190
void setDepthStencilBuffer(QRhiRenderBuffer *renderBuffer)
Sets the renderBuffer for depth-stencil.
Definition qrhi.h:632
void setColorAttachments(std::initializer_list< QRhiColorAttachment > list)
Sets the list of color attachments.
Definition qrhi.h:619
\inmodule QtGui
Definition qrhi.h:704
\inmodule QtGui
Definition qrhi.h:883
@ UsedAsTransferSource
Definition qrhi.h:890
@ MipMapped
Definition qrhi.h:888
@ RenderTarget
Definition qrhi.h:886
@ RGBA32F
Definition qrhi.h:914
\inmodule QtGui
Definition qrhi.h:232
Format
Specifies the type of the element data.
Definition qrhi.h:234
\inmodule QtGui
Definition qrhi.h:179
\inmodule QtGui
Definition qrhi.h:313
void setBindings(std::initializer_list< QRhiVertexInputBinding > list)
Sets the bindings from the specified list.
Definition qrhi.h:317
void setAttributes(std::initializer_list< QRhiVertexInputAttribute > list)
Sets the attributes from the specified list.
Definition qrhi.h:329
\inmodule QtGui
Definition qrhi.h:85
\inmodule QtGui
Definition qrhi.h:1767
QRhiBuffer * newBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, quint32 size)
Definition qrhi.cpp:10079
bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags={}) const
Definition qrhi.cpp:9673
int ubufAligned(int v) const
Definition qrhi.cpp:9570
int resourceLimit(ResourceLimit limit) const
Definition qrhi.cpp:9692
bool isYUpInFramebuffer() const
Definition qrhi.cpp:9601
bool isYUpInNDC() const
Definition qrhi.cpp:9615
bool isFeatureSupported(QRhi::Feature feature) const
Definition qrhi.cpp:9681
QRhiRenderBuffer * newRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize, int sampleCount=1, QRhiRenderBuffer::Flags flags={}, QRhiTexture::Format backingFormatHint=QRhiTexture::UnknownFormat)
Definition qrhi.cpp:10106
QRhi::FrameOpResult finish()
Waits for any work on the graphics queue (where applicable) to complete, then executes all deferred o...
Definition qrhi.cpp:10499
QRhiTextureRenderTarget * newTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, QRhiTextureRenderTarget::Flags flags={})
Definition qrhi.cpp:10245
QRhiGraphicsPipeline * newGraphicsPipeline()
Definition qrhi.cpp:10037
@ MaxColorAttachments
Definition qrhi.h:1849
QRhiTexture * newTexture(QRhiTexture::Format format, const QSize &pixelSize, int sampleCount=1, QRhiTexture::Flags flags={})
Definition qrhi.cpp:10133
@ NonFillPolygonMode
Definition qrhi.h:1828
QRhiResourceUpdateBatch * nextResourceUpdateBatch()
Definition qrhi.cpp:8854
static QString lightmapAssetPathForSave(const QSSGRenderModel &model, LightmapAsset asset, const QString &outputFolder={})
qsizetype add(const QSSGBakedLightingModel &model)
void setOptions(const QSSGLightmapperOptions &options)
QSSGLightmapper(QSSGRhiContext *rhiCtx, QSSGRenderer *renderer)
std::function< void(BakingStatus, std::optional< QString >, BakingControl *)> Callback
static QString lightmapAssetPathForLoad(const QSSGRenderModel &model, LightmapAsset asset)
void setOutputCallback(Callback callback)
bool isValid() const
Definition qssgmesh_p.h:116
bool createLightmapUVChannel(uint lightmapBaseResolution)
bool hasLightmapUVChannel() const
VertexBuffer vertexBuffer() const
Definition qssgmesh_p.h:97
IndexBuffer indexBuffer() const
Definition qssgmesh_p.h:98
QVector< Subset > subsets() const
Definition qssgmesh_p.h:100
QRhiTexture * dummyTexture(QRhiTexture::Flags flags, QRhiResourceUpdateBatch *rub, const QSize &size=QSize(64, 64), const QColor &fillColor=Qt::black)
QRhiCommandBuffer * commandBuffer() const
QRhiShaderResourceBindings * srb(const QSSGRhiShaderResourceBindingList &bindings)
QRhi * rhi() const
QRhiSampler * sampler(const QSSGRhiSamplerDescription &samplerDescription)
\inmodule QtCore
Definition qsize.h:25
constexpr int height() const noexcept
Returns the height.
Definition qsize.h:132
constexpr int width() const noexcept
Returns the width.
Definition qsize.h:129
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
QByteArray toUtf8() const &
Definition qstring.h:563
const_iterator cbegin() const noexcept
const_iterator cend() const noexcept
The QVector2D class represents a vector or vertex in 2D space.
Definition qvectornd.h:31
constexpr float y() const noexcept
Returns the y coordinate of this point.
Definition qvectornd.h:502
constexpr float x() const noexcept
Returns the x coordinate of this point.
Definition qvectornd.h:501
float distanceToPoint(QVector2D point) const noexcept
Definition qvectornd.h:546
constexpr void setY(float y) noexcept
Sets the y coordinate of this point to the given finite y coordinate.
Definition qvectornd.h:505
constexpr void setX(float x) noexcept
Sets the x coordinate of this point to the given finite x coordinate.
Definition qvectornd.h:504
The QVector3D class represents a vector or vertex in 3D space.
Definition qvectornd.h:171
constexpr bool isNull() const noexcept
Returns true if the x, y, and z coordinates are set to 0.0, otherwise returns false.
Definition qvectornd.h:665
static QVector3D normal(QVector3D v1, QVector3D v2) noexcept
Returns the unit normal vector of a plane spanned by vectors v1 and v2, which must not be parallel to...
Definition qvectornd.h:782
QVector3D normalized() const noexcept
Returns the normalized unit vector form of this vector.
Definition qvectornd.h:695
constexpr float y() const noexcept
Returns the y coordinate of this point.
Definition qvectornd.h:671
constexpr float x() const noexcept
Returns the x coordinate of this point.
Definition qvectornd.h:670
static constexpr float dotProduct(QVector3D v1, QVector3D v2) noexcept
Returns the dot product of v1 and v2.
Definition qvectornd.h:770
static constexpr QVector3D crossProduct(QVector3D v1, QVector3D v2) noexcept
Returns the cross-product of vectors v1 and v2, which is normal to the plane spanned by v1 and v2.
Definition qvectornd.h:775
constexpr float z() const noexcept
Returns the z coordinate of this point.
Definition qvectornd.h:672
The QVector4D class represents a vector or vertex in 4D space.
Definition qvectornd.h:330
EGLContext ctx
QString str
[2]
qDeleteAll(list.begin(), list.end())
double e
QSet< QString >::iterator it
direction
else opt state
[0]
Combined button and popup list for selecting options.
auto run(QThreadPool *pool, Function &&f, Args &&...args)
@ black
Definition qnamespace.h:29
constexpr Initialization Uninitialized
Q_DECL_CONSTEXPR float translateConstantAttenuation(float attenuation)
Definition qssgutils_p.h:40
Q_DECL_CONSTEXPR float translateQuadraticAttenuation(float attenuation)
Definition qssgutils_p.h:44
Q_DECL_CONSTEXPR float translateLinearAttenuation(float attenuation)
Definition qssgutils_p.h:42
QVector3D Q_QUICK3DUTILS_EXPORT transform(const QMatrix3x3 &m, const QVector3D &v)
Definition qssgutils.cpp:43
static const int UBUF_SIZE
#define Q_DECL_NOTHROW
DBusConnection const char DBusError * error
size_t qHash(const QFileSystemWatcherPathKey &key, size_t seed=0)
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
Definition qfloat16.h:287
bool qFuzzyIsNull(qfloat16 f) noexcept
Definition qfloat16.h:303
@ Repeat
#define qDebug
[1]
Definition qlogging.h:160
#define qWarning
Definition qlogging.h:162
auto qCos(T v)
Definition qmath.h:60
#define M_PI
Definition qmath.h:209
constexpr float qDegreesToRadians(float degrees)
Definition qmath.h:260
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qBound(const T &min, const T &val, const T &max)
Definition qminmax.h:44
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
static QList< QNetworkInterfacePrivate * > postProcess(QList< QNetworkInterfacePrivate * > list)
constexpr T qAbs(const T &t)
Definition qnumeric.h:328
GLboolean GLboolean GLboolean b
GLsizei const GLfloat * v
[13]
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat z
GLuint64 GLenum void * handle
GLint GLint GLint GLint GLint x
[0]
GLfloat GLfloat GLfloat w
[0]
GLboolean GLboolean GLboolean GLboolean a
[7]
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLboolean r
[2]
GLuint GLuint end
GLuint sampler
GLenum GLuint GLenum GLsizei length
GLenum GLenum GLsizei count
GLfloat GLfloat f
GLenum src
GLenum type
GLint GLfloat v0
GLenum GLuint texture
GLenum GLuint GLintptr offset
GLboolean GLboolean g
GLint first
GLfloat n
GLint y
GLfloat GLfloat GLfloat GLfloat h
GLhandleARB obj
[2]
GLdouble GLdouble t
Definition qopenglext.h:243
GLint GLenum GLboolean normalized
Definition qopenglext.h:752
GLuint64EXT * result
[6]
GLdouble s
[6]
Definition qopenglext.h:235
GLfloat GLfloat p
[1]
GLfloat GLfloat GLfloat alpha
Definition qopenglext.h:418
QT_BEGIN_NAMESPACE typedef void(* Callback)(QQmlNotifierEndpoint *, void **)
static qreal position(const QQuickItem *item, QQuickAnchors::Anchor anchorLine)
static Q_CONSTINIT QBasicAtomicInteger< unsigned > seed
Definition qrandom.cpp:196
bool operator==(const QRandomGenerator &rng1, const QRandomGenerator &rng2)
Definition qrandom.cpp:1219
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QSSGRenderComponentType
QRhiSampler::Filter toRhi(QSSGRenderTextureFilterOp op)
SSL_CTX int(*) void arg)
SSL_CTX int(* cb)(SSL *ssl, unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg)
#define QStringLiteral(str)
unsigned int quint32
Definition qtypes.h:45
unsigned short quint16
Definition qtypes.h:43
int qint32
Definition qtypes.h:44
ptrdiff_t qsizetype
Definition qtypes.h:70
QSqlQueryModel * model
[16]
QFuture< void > future
[5]
future waitForFinished()
std::uniform_real_distribution dist(1, 2.5)
[2]
QFileInfo info(fileName)
[8]
QRect r1(100, 200, 11, 16)
[0]
QRect r2(QPoint(100, 200), QSize(11, 16))
QString dir
[11]
QGraphicsScene scene
[0]
view viewport() -> scroll(dx, dy, deviceRect)
QJSValueList args
QSvgRenderer * renderer
[0]
Definition parser.h:19
\inmodule QtCore \reentrant
Definition qchar.h:17
\inmodule QtGui
Definition qrhi.h:1686
QByteArray data
Definition qrhi.h:1690
static const char * getLightmapUVAttrName()
Definition qssgmesh_p.h:353
static const char * getNormalAttrName()
Definition qssgmesh_p.h:350
static const char * getTexBinormalAttrName()
Definition qssgmesh_p.h:355
static const char * getPositionAttrName()
Definition qssgmesh_p.h:349
static const char * getTexTanAttrName()
Definition qssgmesh_p.h:354
static const char * getUV0AttrName()
Definition qssgmesh_p.h:351
ComponentType componentType
Definition qssgmesh_p.h:66
const QMatrix4x4 & globalTransform
const QSSGRhiShaderPipeline * shaderPipeline
static QRhiVertexInputAttribute::Format toVertexInputFormat(QSSGRenderComponentType compType, quint32 numComps)
void addUniformBuffer(int binding, QRhiShaderResourceBinding::StageFlags stage, QRhiBuffer *buf, int offset, int size)
void addTexture(int binding, QRhiShaderResourceBinding::StageFlags stage, QRhiTexture *tex, QRhiSampler *sampler)
const QSSGRenderSubset & subset
const QSSGModelContext & modelContext
const QSSGRenderGraphObject & material