Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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>
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;
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;
123 QVector<LightmapEntry> entries;
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
208void QSSGLightmapper::setOutputCallback(Callback callback)
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->lightmapKey));
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->lightmapKey));
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->lightmapKey));
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->lightmapKey));
346 return false;
347 }
348 sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Lightmap UV unwrap done for model %1 in %2 ms").
349 arg(lm.model->lightmapKey).
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->lightmapKey));
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->lightmapKey));
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->lightmapKey));
371 return false;
372 }
373 if (drawInfo.indexData.isEmpty()) {
374 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("No index data for model %1").arg(lm.model->lightmapKey));
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->lightmapKey));
389 break;
390 }
391
392 for (const QSSGMesh::Mesh::VertexBufferEntry &vbe : mesh.vertexBuffer().entries) {
394 drawInfo.positionOffset = vbe.offset;
395 drawInfo.positionFormat = QSSGRhiHelpers::toVertexInputFormat(QSSGRenderComponentType(vbe.componentType), vbe.componentCount);
396 } else if (vbe.name == QSSGMesh::MeshInternal::getNormalAttrName()) {
397 drawInfo.normalOffset = vbe.offset;
398 drawInfo.normalFormat = QSSGRhiHelpers::toVertexInputFormat(QSSGRenderComponentType(vbe.componentType), vbe.componentCount);
399 } else if (vbe.name == QSSGMesh::MeshInternal::getUV0AttrName()) {
400 drawInfo.uvOffset = vbe.offset;
401 drawInfo.uvFormat = QSSGRhiHelpers::toVertexInputFormat(QSSGRenderComponentType(vbe.componentType), vbe.componentCount);
402 } else if (vbe.name == QSSGMesh::MeshInternal::getLightmapUVAttrName()) {
403 drawInfo.lightmapUVOffset = vbe.offset;
404 drawInfo.lightmapUVFormat = QSSGRhiHelpers::toVertexInputFormat(QSSGRenderComponentType(vbe.componentType), vbe.componentCount);
405 } else if (vbe.name == QSSGMesh::MeshInternal::getTexTanAttrName()) {
406 drawInfo.tangentOffset = vbe.offset;
407 drawInfo.tangentFormat = QSSGRhiHelpers::toVertexInputFormat(QSSGRenderComponentType(vbe.componentType), vbe.componentCount);
408 } else if (vbe.name == QSSGMesh::MeshInternal::getTexBinormalAttrName()) {
409 drawInfo.binormalOffset = vbe.offset;
410 drawInfo.binormalFormat = QSSGRhiHelpers::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->lightmapKey));
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->lightmapKey));
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->lightmapKey));
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->lightmapKey));
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->lightmapKey));
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->lightmapKey));
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->lightmapKey));
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 = QSSGUtils::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 = QSSGUtils::aux::translateConstantAttenuation(sl.light->m_constantFade);
529 light.linearAttenuation = QSSGUtils::aux::translateLinearAttenuation(sl.light->m_linearFade);
530 light.quadraticAttenuation = QSSGUtils::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 QVector<QRhiGraphicsPipeline *> ps;
783 // Everything is going to be rendered twice (but note depth testing), first
784 // with polygon mode fill, then line.
785 QVector<QRhiGraphicsPipeline *> psLine;
786
787 for (int subMeshIdx = 0; subMeshIdx != subMeshCount; ++subMeshIdx) {
788 const SubMeshInfo &subMeshInfo(subMeshInfos[lmIdx][subMeshIdx]);
789 QVarLengthArray<QRhiVertexInputAttribute, 6> vertexAttrs;
790 vertexAttrs << QRhiVertexInputAttribute(0, 0, bakeModelDrawInfo.positionFormat, bakeModelDrawInfo.positionOffset)
791 << QRhiVertexInputAttribute(0, 1, bakeModelDrawInfo.normalFormat, bakeModelDrawInfo.normalOffset)
792 << QRhiVertexInputAttribute(0, 2, bakeModelDrawInfo.lightmapUVFormat, bakeModelDrawInfo.lightmapUVOffset);
793
794 // Vertex inputs (just like the sampler uniforms) must match exactly on
795 // the shader and the application side, cannot just leave out or have
796 // unused inputs.
798 if (hasUV0) {
800 if (hasTangentAndBinormal)
802 }
803
804 const auto &shaderCache = renderer->contextInterface()->shaderCache();
805 const auto &lmUvRastShaderPipeline = shaderCache->getBuiltInRhiShaders().getRhiLightmapUVRasterizationShader(shaderVariant);
806 if (!lmUvRastShaderPipeline) {
807 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Failed to load shaders"));
808 return false;
809 }
810
811 if (hasUV0) {
812 vertexAttrs << QRhiVertexInputAttribute(0, 3, bakeModelDrawInfo.uvFormat, bakeModelDrawInfo.uvOffset);
813 if (hasTangentAndBinormal) {
814 vertexAttrs << QRhiVertexInputAttribute(0, 4, bakeModelDrawInfo.tangentFormat, bakeModelDrawInfo.tangentOffset);
815 vertexAttrs << QRhiVertexInputAttribute(0, 5, bakeModelDrawInfo.binormalFormat, bakeModelDrawInfo.binormalOffset);
816 }
817 }
818
819 inputLayout.setAttributes(vertexAttrs.cbegin(), vertexAttrs.cend());
820
823 subMeshIdx * alignedUbufSize, UBUF_SIZE);
826 if (subMeshInfo.baseColorMap) {
827 const bool mipmapped = subMeshInfo.baseColorMap->flags().testFlag(QRhiTexture::MipMapped);
828 QRhiSampler *sampler = rhiCtx->sampler({ QSSGRhiHelpers::toRhi(subMeshInfo.baseColorNode->m_minFilterType),
829 QSSGRhiHelpers::toRhi(subMeshInfo.baseColorNode->m_magFilterType),
830 mipmapped ? QSSGRhiHelpers::toRhi(subMeshInfo.baseColorNode->m_mipFilterType) : QRhiSampler::None,
831 QSSGRhiHelpers::toRhi(subMeshInfo.baseColorNode->m_horizontalTilingMode),
832 QSSGRhiHelpers::toRhi(subMeshInfo.baseColorNode->m_verticalTilingMode),
833 QSSGRhiHelpers::toRhi(subMeshInfo.baseColorNode->m_depthTilingMode)
834 });
835 bindings.addTexture(1, QRhiShaderResourceBinding::FragmentStage, subMeshInfo.baseColorMap, sampler);
836 } else {
837 bindings.addTexture(1, QRhiShaderResourceBinding::FragmentStage, dummyTexture, dummySampler);
838 }
839 if (subMeshInfo.emissiveMap) {
840 const bool mipmapped = subMeshInfo.emissiveMap->flags().testFlag(QRhiTexture::MipMapped);
841 QRhiSampler *sampler = rhiCtx->sampler({ QSSGRhiHelpers::toRhi(subMeshInfo.emissiveNode->m_minFilterType),
842 QSSGRhiHelpers::toRhi(subMeshInfo.emissiveNode->m_magFilterType),
843 mipmapped ? QSSGRhiHelpers::toRhi(subMeshInfo.emissiveNode->m_mipFilterType) : QRhiSampler::None,
844 QSSGRhiHelpers::toRhi(subMeshInfo.emissiveNode->m_horizontalTilingMode),
845 QSSGRhiHelpers::toRhi(subMeshInfo.emissiveNode->m_verticalTilingMode),
846 QSSGRhiHelpers::toRhi(subMeshInfo.emissiveNode->m_depthTilingMode)
847 });
848 bindings.addTexture(2, QRhiShaderResourceBinding::FragmentStage, subMeshInfo.emissiveMap, sampler);
849 } else {
850 bindings.addTexture(2, QRhiShaderResourceBinding::FragmentStage, dummyTexture, dummySampler);
851 }
852 if (subMeshInfo.normalMap) {
853 const bool mipmapped = subMeshInfo.normalMap->flags().testFlag(QRhiTexture::MipMapped);
854 QRhiSampler *sampler = rhiCtx->sampler({ QSSGRhiHelpers::toRhi(subMeshInfo.normalMapNode->m_minFilterType),
855 QSSGRhiHelpers::toRhi(subMeshInfo.normalMapNode->m_magFilterType),
856 mipmapped ? QSSGRhiHelpers::toRhi(subMeshInfo.normalMapNode->m_mipFilterType) : QRhiSampler::None,
857 QSSGRhiHelpers::toRhi(subMeshInfo.normalMapNode->m_horizontalTilingMode),
858 QSSGRhiHelpers::toRhi(subMeshInfo.normalMapNode->m_verticalTilingMode),
859 QSSGRhiHelpers::toRhi(subMeshInfo.normalMapNode->m_depthTilingMode)
860 });
861 bindings.addTexture(3, QRhiShaderResourceBinding::FragmentStage, subMeshInfo.normalMap, sampler);
862 } else {
863 bindings.addTexture(3, QRhiShaderResourceBinding::FragmentStage, dummyTexture, dummySampler);
864 }
865 QRhiShaderResourceBindings *srb = rhiCtxD->srb(bindings);
866
867 QRhiGraphicsPipeline *pipeline = setupPipeline(lmUvRastShaderPipeline.get(), srb, inputLayout);
868 if (!pipeline->create()) {
869 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Failed to create graphics pipeline (mesh %1 submesh %2)").
870 arg(lmIdx).
871 arg(subMeshIdx));
872 qDeleteAll(ps);
873 qDeleteAll(psLine);
874 return false;
875 }
876 ps.append(pipeline);
877 pipeline = setupPipeline(lmUvRastShaderPipeline.get(), srb, inputLayout);
879 if (!pipeline->create()) {
880 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Failed to create graphics pipeline with line fill mode (mesh %1 submesh %2)").
881 arg(lmIdx).
882 arg(subMeshIdx));
883 qDeleteAll(ps);
884 qDeleteAll(psLine);
885 return false;
886 }
887 psLine.append(pipeline);
888 }
889
890 QRhiCommandBuffer::VertexInput vertexBuffers = { vbuf.get(), 0 };
891 const QRhiViewport viewport(0, 0, float(outputSize.width()), float(outputSize.height()));
892 bool hadViewport = false;
893
894 cb->beginPass(rt.get(), Qt::black, { 1.0f, 0 });
895 for (int subMeshIdx = 0; subMeshIdx != subMeshCount; ++subMeshIdx) {
896 const SubMeshInfo &subMeshInfo(subMeshInfos[lmIdx][subMeshIdx]);
897 cb->setGraphicsPipeline(ps[subMeshIdx]);
898 if (!hadViewport) {
899 cb->setViewport(viewport);
900 hadViewport = true;
901 }
902 cb->setShaderResources();
903 cb->setVertexInput(0, 1, &vertexBuffers, ibuf.get(), 0, QRhiCommandBuffer::IndexUInt32);
904 cb->drawIndexed(subMeshInfo.count, 1, subMeshInfo.offset);
905 cb->setGraphicsPipeline(psLine[subMeshIdx]);
906 cb->setShaderResources();
907 cb->drawIndexed(subMeshInfo.count, 1, subMeshInfo.offset);
908 }
909
910 resUpd = rhi->nextResourceUpdateBatch();
911 QRhiReadbackResult posReadResult;
912 QRhiReadbackResult normalReadResult;
913 QRhiReadbackResult baseColorReadResult;
914 QRhiReadbackResult emissionReadResult;
915 resUpd->readBackTexture({ positionData.get() }, &posReadResult);
916 resUpd->readBackTexture({ normalData.get() }, &normalReadResult);
917 resUpd->readBackTexture({ baseColorData.get() }, &baseColorReadResult);
918 resUpd->readBackTexture({ emissionData.get() }, &emissionReadResult);
919 cb->endPass(resUpd);
920
921 // Submit and wait for completion.
922 rhi->finish();
923
924 qDeleteAll(ps);
925 qDeleteAll(psLine);
926
927 Lightmap lightmap(outputSize);
928
929 // The readback results are tightly packed (which is supposed to be ensured
930 // by each rhi backend), so one line is 16 * width bytes.
931 if (posReadResult.data.size() < lightmap.entries.size() * 16) {
932 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Position data is smaller than expected"));
933 return false;
934 }
935 if (normalReadResult.data.size() < lightmap.entries.size() * 16) {
936 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Normal data is smaller than expected"));
937 return false;
938 }
939 if (baseColorReadResult.data.size() < lightmap.entries.size() * 16) {
940 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Base color data is smaller than expected"));
941 return false;
942 }
943 if (emissionReadResult.data.size() < lightmap.entries.size() * 16) {
944 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Emission data is smaller than expected"));
945 return false;
946 }
947 const float *lmPosPtr = reinterpret_cast<const float *>(posReadResult.data.constData());
948 const float *lmNormPtr = reinterpret_cast<const float *>(normalReadResult.data.constData());
949 const float *lmBaseColorPtr = reinterpret_cast<const float *>(baseColorReadResult.data.constData());
950 const float *lmEmissionPtr = reinterpret_cast<const float *>(emissionReadResult.data.constData());
951 int unusedEntries = 0;
952 for (qsizetype i = 0, ie = lightmap.entries.size(); i != ie; ++i) {
953 LightmapEntry &lmPix(lightmap.entries[i]);
954
955 float x = *lmPosPtr++;
956 float y = *lmPosPtr++;
957 float z = *lmPosPtr++;
958 lmPosPtr++;
959 lmPix.worldPos = QVector3D(x, y, z);
960
961 x = *lmNormPtr++;
962 y = *lmNormPtr++;
963 z = *lmNormPtr++;
964 lmNormPtr++;
965 lmPix.normal = QVector3D(x, y, z);
966
967 float r = *lmBaseColorPtr++;
968 float g = *lmBaseColorPtr++;
969 float b = *lmBaseColorPtr++;
970 float a = *lmBaseColorPtr++;
971 lmPix.baseColor = QVector4D(r, g, b, a);
972 if (a < 1.0f)
973 lightmap.hasBaseColorTransparency = true;
974
975 r = *lmEmissionPtr++;
976 g = *lmEmissionPtr++;
977 b = *lmEmissionPtr++;
978 lmEmissionPtr++;
979 lmPix.emission = QVector3D(r, g, b);
980
981 if (!lmPix.isValid())
982 ++unusedEntries;
983 }
984
985 sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Successfully rasterized %1/%2 lightmap texels for model %3, lightmap size %4 in %5 ms").
986 arg(lightmap.entries.size() - unusedEntries).
987 arg(lightmap.entries.size()).
988 arg(lm.model->lightmapKey).
989 arg(QStringLiteral("(%1, %2)").arg(outputSize.width()).arg(outputSize.height())).
990 arg(rasterizeTimer.elapsed()));
991 lightmaps.append(lightmap);
992
993 for (const SubMeshInfo &subMeshInfo : std::as_const(subMeshInfos[lmIdx])) {
994 if (!lm.model->castsShadows) // only matters if it's in the raytracer scene
995 continue;
996 geomLightmapMap[subMeshInfo.geomId] = lightmaps.size() - 1;
997 }
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->lightmapKey).
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->lightmapKey).
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").
1192 arg(lm.model->lightmapKey));
1193 sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Sample count: %1, Workgroup size: %2, Max bounces: %3, Multiplier: %4").
1194 arg(options.indirectLightSamples).
1195 arg(wgSizePerGroup).
1196 arg(options.indirectLightBounces).
1197 arg(options.indirectLightFactor));
1198 for (LightmapEntry &lmPix : lightmap.entries) {
1199 if (!lmPix.isValid())
1200 continue;
1201
1202 for (int wgIdx = 0; wgIdx < wgCount; ++wgIdx) {
1203 const int beginIdx = wgIdx * wgSizePerGroup;
1204 const int endIdx = qMin(beginIdx + wgSizePerGroup, options.indirectLightSamples);
1205
1206 wg[wgIdx] = QtConcurrent::run([this, beginIdx, endIdx, &lmPix] {
1207 QVector3D wgResult;
1208 for (int sampleIdx = beginIdx; sampleIdx < endIdx; ++sampleIdx) {
1209 QVector3D position = lmPix.worldPos;
1210 QVector3D normal = lmPix.normal;
1211 QVector3D throughput(1.0f, 1.0f, 1.0f);
1212 QVector3D sampleResult;
1213
1214 for (int bounce = 0; bounce < options.indirectLightBounces; ++bounce) {
1215 if (options.useAdaptiveBias)
1216 position += vectorSign(normal) * vectorAbs(position * 0.0000002f);
1217
1218 // get a sample using a cosine-weighted hemisphere sampler
1219 const QVector3D sample = cosWeightedHemisphereSample();
1220
1221 // transform to the point's local coordinate system
1222 const QVector3D v0 = qFuzzyCompare(qAbs(normal.z()), 1.0f)
1223 ? QVector3D(0.0f, 1.0f, 0.0f)
1224 : QVector3D(0.0f, 0.0f, 1.0f);
1225 const QVector3D tangent = QVector3D::crossProduct(v0, normal).normalized();
1226 const QVector3D bitangent = QVector3D::crossProduct(tangent, normal).normalized();
1228 tangent.x() * sample.x() + bitangent.x() * sample.y() + normal.x() * sample.z(),
1229 tangent.y() * sample.x() + bitangent.y() * sample.y() + normal.y() * sample.z(),
1230 tangent.z() * sample.x() + bitangent.z() * sample.y() + normal.z() * sample.z());
1231 direction.normalize();
1232
1233 // probability distribution function
1234 const float NdotL = qMax(0.0f, QVector3D::dotProduct(normal, direction));
1235 const float pdf = NdotL / float(M_PI);
1236 if (qFuzzyIsNull(pdf))
1237 break;
1238
1239 // shoot ray, stop if no hit
1240 RayHit ray(position, direction, options.bias);
1241 if (!ray.intersect(rscene))
1242 break;
1243
1244 // see what (sub)mesh and which texel it intersected with
1245 const LightmapEntry &hitEntry = texelForLightmapUV(ray.rayhit.hit.geomID,
1246 ray.rayhit.hit.u,
1247 ray.rayhit.hit.v);
1248
1249 // won't bounce further from a back face
1250 const bool hitBackFace = QVector3D::dotProduct(hitEntry.normal, direction) > 0.0f;
1251 if (hitBackFace)
1252 break;
1253
1254 // the BRDF of a diffuse surface is albedo / PI
1255 const QVector3D brdf = hitEntry.baseColor.toVector3D() / float(M_PI);
1256
1257 // calculate result for this bounce
1258 sampleResult += throughput * hitEntry.emission;
1259 throughput *= brdf * NdotL / pdf;
1260 sampleResult += throughput * hitEntry.directLight;
1261
1262 // stop if we guess there's no point in bouncing further
1263 // (low throughput path wouldn't contribute much)
1264 const float p = qMax(qMax(throughput.x(), throughput.y()), throughput.z());
1265 if (p < uniformRand())
1266 break;
1267
1268 // was not terminated: boost the energy by the probability to be terminated
1269 throughput /= p;
1270
1271 // next bounce starts from the hit's position
1272 position = hitEntry.worldPos;
1273 normal = hitEntry.normal;
1274 }
1275
1276 wgResult += sampleResult;
1277 }
1278 return wgResult;
1279 });
1280 }
1281
1282 QVector3D totalIndirect;
1283 for (const auto &future : wg)
1284 totalIndirect += future.result();
1285
1286 lmPix.allLight += totalIndirect * options.indirectLightFactor / options.indirectLightSamples;
1287
1288 ++texelsDone;
1289 if (texelsDone % 10000 == 0)
1290 sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("%1 texels left").
1291 arg(lightmap.entries.size() - texelsDone));
1292
1293 if (bakingControl.cancelled)
1294 return;
1295 }
1296 sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Indirect lighting computed for model %1 in %2 ms").
1297 arg(lm.model->lightmapKey).
1298 arg(indirectLightTimer.elapsed()));
1299 }
1300
1301 sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Indirect light computation completed in %1 ms").
1302 arg(fullIndirectLightTimer.elapsed()));
1303}
1304
1305struct Edge {
1306 std::array<QVector3D, 2> pos;
1307 std::array<QVector3D, 2> normal;
1308};
1309
1310inline bool operator==(const Edge &a, const Edge &b)
1311{
1312 return qFuzzyCompare(a.pos[0], b.pos[0])
1313 && qFuzzyCompare(a.pos[1], b.pos[1])
1314 && qFuzzyCompare(a.normal[0], b.normal[0])
1315 && qFuzzyCompare(a.normal[1], b.normal[1]);
1316}
1317
1318inline size_t qHash(const Edge &e, size_t seed) Q_DECL_NOTHROW
1319{
1320 return qHash(e.pos[0].x(), seed) ^ qHash(e.pos[0].y()) ^ qHash(e.pos[0].z())
1321 ^ qHash(e.pos[1].x()) ^ qHash(e.pos[1].y()) ^ qHash(e.pos[1].z());
1322}
1323
1324struct EdgeUV {
1325 std::array<QVector2D, 2> uv;
1326 bool seam = false;
1327};
1328
1329struct SeamUV {
1330 std::array<std::array<QVector2D, 2>, 2> uv;
1331};
1332
1333static inline bool vectorLessThan(const QVector3D &a, const QVector3D &b)
1334{
1335 if (a.x() == b.x()) {
1336 if (a.y() == b.y())
1337 return a.z() < b.z();
1338 else
1339 return a.y() < b.y();
1340 }
1341 return a.x() < b.x();
1342}
1343
1344static inline float floatSign(float f)
1345{
1346 return f > 0.0f ? 1.0f : (f < 0.0f ? -1.0f : 0.0f);
1347}
1348
1349static inline QVector2D flooredVec(const QVector2D &v)
1350{
1351 return QVector2D(std::floor(v.x()), std::floor(v.y()));
1352}
1353
1354static inline QVector2D projectPointToLine(const QVector2D &point, const std::array<QVector2D, 2> &line)
1355{
1356 const QVector2D p = point - line[0];
1357 const QVector2D n = line[1] - line[0];
1358 const float lengthSquared = n.lengthSquared();
1359 if (!qFuzzyIsNull(lengthSquared)) {
1360 const float d = (n.x() * p.x() + n.y() * p.y()) / lengthSquared;
1361 return d <= 0.0f ? line[0] : (d >= 1.0f ? line[1] : line[0] + n * d);
1362 }
1363 return line[0];
1364}
1365
1366static void blendLine(const QVector2D &from, const QVector2D &to,
1367 const QVector2D &uvFrom, const QVector2D &uvTo,
1368 const QByteArray &readBuf, QByteArray &writeBuf,
1369 const QSize &lightmapPixelSize)
1370{
1371 const QVector2D size(lightmapPixelSize.width(), lightmapPixelSize.height());
1372 const std::array<QVector2D, 2> line = { QVector2D(from.x(), 1.0f - from.y()) * size,
1373 QVector2D(to.x(), 1.0f - to.y()) * size };
1374 const float lineLength = line[0].distanceToPoint(line[1]);
1375 if (qFuzzyIsNull(lineLength))
1376 return;
1377
1378 const QVector2D startPixel = flooredVec(line[0]);
1379 const QVector2D endPixel = flooredVec(line[1]);
1380
1381 const QVector2D dir = (line[1] - line[0]).normalized();
1382 const QVector2D tStep(1.0f / std::abs(dir.x()), 1.0f / std::abs(dir.y()));
1383 const QVector2D pixelStep(floatSign(dir.x()), floatSign(dir.y()));
1384
1385 QVector2D nextT(std::fmod(line[0].x(), 1.0f), std::fmod(line[0].y(), 1.0f));
1386 if (pixelStep.x() == 1.0f)
1387 nextT.setX(1.0f - nextT.x());
1388 if (pixelStep.y() == 1.0f)
1389 nextT.setY(1.0f - nextT.y());
1390
1391 if (!qFuzzyIsNull(dir.x()))
1392 nextT.setX(nextT.x() / std::abs(dir.x()));
1393 else
1394 nextT.setX(std::numeric_limits<float>::max());
1395
1396 if (!qFuzzyIsNull(dir.y()))
1397 nextT.setY(nextT.y() / std::abs(dir.y()));
1398 else
1399 nextT.setY(std::numeric_limits<float>::max());
1400
1401 float *fpW = reinterpret_cast<float *>(writeBuf.data());
1402 const float *fpR = reinterpret_cast<const float *>(readBuf.constData());
1403
1404 QVector2D pixel = startPixel;
1405
1406 while (startPixel.distanceToPoint(pixel) < lineLength + 1.0f) {
1407 const QVector2D point = projectPointToLine(pixel + QVector2D(0.5f, 0.5f), line);
1408 const float t = line[0].distanceToPoint(point) / lineLength;
1409 const QVector2D uvInterp = uvFrom * (1.0 - t) + uvTo * t;
1410 const QVector2D sampledPixel = flooredVec(QVector2D(uvInterp.x(), 1.0f - uvInterp.y()) * size);
1411
1412 const int sampOfs = (int(sampledPixel.x()) + int(sampledPixel.y()) * lightmapPixelSize.width()) * 4;
1413 const QVector3D sampledColor(fpR[sampOfs], fpR[sampOfs + 1], fpR[sampOfs + 2]);
1414 const int pixOfs = (int(pixel.x()) + int(pixel.y()) * lightmapPixelSize.width()) * 4;
1415 QVector3D currentColor(fpW[pixOfs], fpW[pixOfs + 1], fpW[pixOfs + 2]);
1416 currentColor = currentColor * 0.6f + sampledColor * 0.4f;
1417 fpW[pixOfs] = currentColor.x();
1418 fpW[pixOfs + 1] = currentColor.y();
1419 fpW[pixOfs + 2] = currentColor.z();
1420
1421 if (pixel != endPixel) {
1422 if (nextT.x() < nextT.y()) {
1423 pixel.setX(pixel.x() + pixelStep.x());
1424 nextT.setX(nextT.x() + tStep.x());
1425 } else {
1426 pixel.setY(pixel.y() + pixelStep.y());
1427 nextT.setY(nextT.y() + tStep.y());
1428 }
1429 } else {
1430 break;
1431 }
1432 }
1433}
1434
1435bool QSSGLightmapperPrivate::postProcess()
1436{
1438 QRhi *rhi = rhiCtx->rhi();
1439 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
1440 const int bakedLightingModelCount = bakedLightingModels.size();
1441
1442 sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Post-processing..."));
1443 for (int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
1444 QElapsedTimer postProcessTimer;
1445 postProcessTimer.start();
1446
1447 const QSSGBakedLightingModel &lm(bakedLightingModels[lmIdx]);
1448 // only care about the ones that will store the lightmap image persistently
1449 if (!lm.model->hasLightmap())
1450 continue;
1451
1452 Lightmap &lightmap(lightmaps[lmIdx]);
1453
1454 // Assemble the RGBA32F image from the baker data structures
1455 QByteArray lightmapFP32(lightmap.entries.size() * 4 * sizeof(float), Qt::Uninitialized);
1456 float *lightmapFloatPtr = reinterpret_cast<float *>(lightmapFP32.data());
1457 for (const LightmapEntry &lmPix : std::as_const(lightmap.entries)) {
1458 *lightmapFloatPtr++ = lmPix.allLight.x();
1459 *lightmapFloatPtr++ = lmPix.allLight.y();
1460 *lightmapFloatPtr++ = lmPix.allLight.z();
1461 *lightmapFloatPtr++ = lmPix.isValid() ? 1.0f : 0.0f;
1462 }
1463
1464 // Dilate
1465 const QRhiViewport viewport(0, 0, float(lightmap.pixelSize.width()), float(lightmap.pixelSize.height()));
1466
1467 std::unique_ptr<QRhiTexture> lightmapTex(rhi->newTexture(QRhiTexture::RGBA32F, lightmap.pixelSize));
1468 if (!lightmapTex->create()) {
1469 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Failed to create FP32 texture for postprocessing"));
1470 return false;
1471 }
1472 std::unique_ptr<QRhiTexture> dilatedLightmapTex(rhi->newTexture(QRhiTexture::RGBA32F, lightmap.pixelSize, 1,
1474 if (!dilatedLightmapTex->create()) {
1475 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Failed to create FP32 dest. texture for postprocessing"));
1476 return false;
1477 }
1478 QRhiTextureRenderTargetDescription rtDescDilate(dilatedLightmapTex.get());
1479 std::unique_ptr<QRhiTextureRenderTarget> rtDilate(rhi->newTextureRenderTarget(rtDescDilate));
1480 std::unique_ptr<QRhiRenderPassDescriptor> rpDescDilate(rtDilate->newCompatibleRenderPassDescriptor());
1481 rtDilate->setRenderPassDescriptor(rpDescDilate.get());
1482 if (!rtDilate->create()) {
1483 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Failed to create postprocessing texture render target"));
1484 return false;
1485 }
1487 QRhiTextureSubresourceUploadDescription lightmapTexUpload(lightmapFP32.constData(), lightmapFP32.size());
1488 resUpd->uploadTexture(lightmapTex.get(), QRhiTextureUploadDescription({ 0, 0, lightmapTexUpload }));
1492 bindings.addTexture(0, QRhiShaderResourceBinding::FragmentStage, lightmapTex.get(), nearestSampler);
1493 renderer->rhiQuadRenderer()->prepareQuad(rhiCtx, resUpd);
1494 const auto &shaderCache = renderer->contextInterface()->shaderCache();
1495 const auto &lmDilatePipeline = shaderCache->getBuiltInRhiShaders().getRhiLightmapDilateShader();
1496 if (!lmDilatePipeline) {
1497 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Failed to load shaders"));
1498 return false;
1499 }
1501 dilatePs.viewport = viewport;
1502 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(dilatePs, lmDilatePipeline.get());
1503 renderer->rhiQuadRenderer()->recordRenderQuadPass(rhiCtx, &dilatePs, rhiCtxD->srb(bindings), rtDilate.get(), QSSGRhiQuadRenderer::UvCoords);
1504 resUpd = rhi->nextResourceUpdateBatch();
1505 QRhiReadbackResult dilateReadResult;
1506 resUpd->readBackTexture({ dilatedLightmapTex.get() }, &dilateReadResult);
1507 cb->resourceUpdate(resUpd);
1508
1509 // Submit and wait for completion.
1510 rhi->finish();
1511
1512 lightmap.imageFP32 = dilateReadResult.data;
1513
1514 // Reduce UV seams by collecting all edges (going through all
1515 // triangles), looking for (fuzzy)matching ones, then drawing lines
1516 // with blending on top.
1517 const DrawInfo &drawInfo(drawInfos[lmIdx]);
1518 const char *vbase = drawInfo.vertexData.constData();
1519 const quint32 *ibase = reinterpret_cast<const quint32 *>(drawInfo.indexData.constData());
1520
1521 // topology is Triangles, would be indexed draw - get rid of the index
1522 // buffer, need nothing but triangles afterwards
1523 qsizetype assembledVertexCount = 0;
1524 for (SubMeshInfo &subMeshInfo : subMeshInfos[lmIdx])
1525 assembledVertexCount += subMeshInfo.count;
1526 QVector<QVector3D> smPos(assembledVertexCount);
1527 QVector<QVector3D> smNormal(assembledVertexCount);
1528 QVector<QVector2D> smCoord(assembledVertexCount);
1529 qsizetype vertexIdx = 0;
1530 for (SubMeshInfo &subMeshInfo : subMeshInfos[lmIdx]) {
1531 for (quint32 i = 0; i < subMeshInfo.count; ++i) {
1532 const quint32 idx = *(ibase + subMeshInfo.offset + i);
1533 const float *src = reinterpret_cast<const float *>(vbase + idx * drawInfo.vertexStride + drawInfo.positionOffset);
1534 float x = *src++;
1535 float y = *src++;
1536 float z = *src++;
1537 smPos[vertexIdx] = QVector3D(x, y, z);
1538 src = reinterpret_cast<const float *>(vbase + idx * drawInfo.vertexStride + drawInfo.normalOffset);
1539 x = *src++;
1540 y = *src++;
1541 z = *src++;
1542 smNormal[vertexIdx] = QVector3D(x, y, z);
1543 src = reinterpret_cast<const float *>(vbase + idx * drawInfo.vertexStride + drawInfo.lightmapUVOffset);
1544 x = *src++;
1545 y = *src++;
1546 smCoord[vertexIdx] = QVector2D(x, y);
1547 ++vertexIdx;
1548 }
1549 }
1550
1551 QHash<Edge, EdgeUV> edgeUVMap;
1552 QVector<SeamUV> seams;
1553 for (vertexIdx = 0; vertexIdx < assembledVertexCount; vertexIdx += 3) {
1554 QVector3D triVert[3] = { smPos[vertexIdx], smPos[vertexIdx + 1], smPos[vertexIdx + 2] };
1555 QVector3D triNorm[3] = { smNormal[vertexIdx], smNormal[vertexIdx + 1], smNormal[vertexIdx + 2] };
1556 QVector2D triUV[3] = { smCoord[vertexIdx], smCoord[vertexIdx + 1], smCoord[vertexIdx + 2] };
1557
1558 for (int i = 0; i < 3; ++i) {
1559 int i0 = i;
1560 int i1 = (i + 1) % 3;
1561 if (vectorLessThan(triVert[i1], triVert[i0]))
1562 std::swap(i0, i1);
1563
1564 const Edge e = {
1565 { triVert[i0], triVert[i1] },
1566 { triNorm[i0], triNorm[i1] }
1567 };
1568 const EdgeUV edgeUV = { { triUV[i0], triUV[i1] } };
1569 auto it = edgeUVMap.find(e);
1570 if (it == edgeUVMap.end()) {
1571 edgeUVMap.insert(e, edgeUV);
1572 } else if (!qFuzzyCompare(it->uv[0], edgeUV.uv[0]) || !qFuzzyCompare(it->uv[1], edgeUV.uv[1])) {
1573 if (!it->seam) {
1574 seams.append(SeamUV({ { edgeUV.uv, it->uv } }));
1575 it->seam = true;
1576 }
1577 }
1578 }
1579 }
1580 qDebug() << "lm:" << seams.size() << "UV seams in" << lm.model;
1581
1582 QByteArray workBuf(lightmap.imageFP32.size(), Qt::Uninitialized);
1583 for (int blendIter = 0; blendIter < LM_SEAM_BLEND_ITER_COUNT; ++blendIter) {
1584 memcpy(workBuf.data(), lightmap.imageFP32.constData(), lightmap.imageFP32.size());
1585 for (int seamIdx = 0, end = seams.size(); seamIdx != end; ++seamIdx) {
1586 const SeamUV &seam(seams[seamIdx]);
1587 blendLine(seam.uv[0][0], seam.uv[0][1],
1588 seam.uv[1][0], seam.uv[1][1],
1589 workBuf, lightmap.imageFP32, lightmap.pixelSize);
1590 blendLine(seam.uv[1][0], seam.uv[1][1],
1591 seam.uv[0][0], seam.uv[0][1],
1592 workBuf, lightmap.imageFP32, lightmap.pixelSize);
1593 }
1594 }
1595
1596 sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Post-processing for model %1 done in %2").
1597 arg(lm.model->lightmapKey).
1598 arg(postProcessTimer.elapsed()));
1599 }
1600
1601 return true;
1602}
1603
1604bool QSSGLightmapperPrivate::storeLightmaps()
1605{
1606 const int bakedLightingModelCount = bakedLightingModels.size();
1607 QByteArray listContents;
1608
1609 for (int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
1610 const QSSGBakedLightingModel &lm(bakedLightingModels[lmIdx]);
1611 // only care about the ones that want to store the lightmap image persistently
1612 if (!lm.model->hasLightmap())
1613 continue;
1614
1615 QElapsedTimer writeTimer;
1616 writeTimer.start();
1617
1618 // An empty outputFolder equates to working directory
1619 QString outputFolder;
1620 if (!lm.model->lightmapLoadPath.startsWith(QStringLiteral(":/")))
1621 outputFolder = lm.model->lightmapLoadPath;
1622
1624 const QByteArray fns = fn.toUtf8();
1625
1626 listContents += QFileInfo(fn).absoluteFilePath().toUtf8();
1627 listContents += '\n';
1628
1629 const Lightmap &lightmap(lightmaps[lmIdx]);
1630
1631 if (SaveEXR(reinterpret_cast<const float *>(lightmap.imageFP32.constData()),
1632 lightmap.pixelSize.width(), lightmap.pixelSize.height(),
1633 4, false, fns.constData(), nullptr) < 0)
1634 {
1635 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Failed to write out lightmap"));
1636 return false;
1637 }
1638
1639 sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Lightmap saved for model %1 to %2 in %3 ms").
1640 arg(lm.model->lightmapKey).
1641 arg(fn).
1642 arg(writeTimer.elapsed()));
1643 const DrawInfo &bakeModelDrawInfo(drawInfos[lmIdx]);
1644 if (bakeModelDrawInfo.meshWithLightmapUV.isValid()) {
1645 writeTimer.start();
1648 bakeModelDrawInfo.meshWithLightmapUV.save(&f);
1649 } else {
1650 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Failed to write mesh with lightmap UV data to '%1'").
1651 arg(f.fileName()));
1652 return false;
1653 }
1654 sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Lightmap-compatible mesh saved for model %1 to %2 in %3 ms").
1655 arg(lm.model->lightmapKey).
1656 arg(f.fileName()).
1657 arg(writeTimer.elapsed()));
1658 } // else the mesh had a lightmap uv channel to begin with, no need to save another version of it
1659 }
1660
1663 sendOutputInfo(QSSGLightmapper::BakingStatus::Warning, QStringLiteral("Failed to create lightmap list file %1").
1664 arg(listFile.fileName()));
1665 return false;
1666 }
1667 listFile.write(listContents);
1668
1669 return true;
1670}
1671
1672void QSSGLightmapperPrivate::sendOutputInfo(QSSGLightmapper::BakingStatus type, std::optional<QString> msg)
1673{
1675
1676 switch (type)
1677 {
1679 return;
1681 result = QStringLiteral("[lm] Progress");
1682 break;
1684 result = QStringLiteral("[lm] Error");
1685 break;
1687 result = QStringLiteral("[lm] Warning");
1688 break;
1690 result = QStringLiteral("[lm] Cancelled");
1691 break;
1693 result = QStringLiteral("[lm] Complete");
1694 break;
1695 }
1696
1697 if (msg.has_value())
1698 result.append(QStringLiteral(": ") + msg.value());
1699
1701 qWarning() << result;
1702 else
1703 qDebug() << result;
1704
1705 if (outputCallback)
1706 outputCallback(type, msg, &bakingControl);
1707}
1708
1710{
1711 QElapsedTimer totalTimer;
1712 totalTimer.start();
1713
1714 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Bake starting..."));
1715 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Total models registered: %1").arg(d->bakedLightingModels.size()));
1716
1717 if (d->bakedLightingModels.isEmpty()) {
1718 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Cancelled, QStringLiteral("Cancelled by LightMapper, No Models to bake"));
1719 return false;
1720 }
1721
1722 if (!d->commitGeometry()) {
1723 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Baking failed"));
1724 return false;
1725 }
1726
1727 if (!d->prepareLightmaps()) {
1728 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Baking failed"));
1729 return false;
1730 }
1731
1732 if (d->bakingControl.cancelled) {
1733 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Cancelled, QStringLiteral("Cancelled by user"));
1734 return false;
1735 }
1736
1737 d->computeDirectLight();
1738
1739 if (d->bakingControl.cancelled) {
1740 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Cancelled, QStringLiteral("Cancelled by user"));
1741 return false;
1742 }
1743
1744 if (d->options.indirectLightEnabled)
1745 d->computeIndirectLight();
1746
1747 if (d->bakingControl.cancelled) {
1748 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Cancelled, QStringLiteral("Cancelled by user"));
1749 return false;
1750 }
1751
1752 if (!d->postProcess()) {
1753 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Baking failed"));
1754 return false;
1755 }
1756
1757 if (d->bakingControl.cancelled) {
1758 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Cancelled, QStringLiteral("Cancelled by user"));
1759 return false;
1760 }
1761
1762 if (!d->storeLightmaps()) {
1763 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Baking failed"));
1764 return false;
1765 }
1766
1767 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Progress, QStringLiteral("Baking took %1 ms").arg(totalTimer.elapsed()));
1768 d->sendOutputInfo(QSSGLightmapper::BakingStatus::Complete, std::nullopt);
1769 return true;
1770}
1771
1772#else
1773
1777
1781
1783{
1784}
1785
1789
1791{
1792}
1793
1795{
1796 return 0;
1797}
1798
1800{
1801 qWarning("Qt Quick 3D was built without the lightmapper; cannot bake lightmaps");
1802 return false;
1803}
1804
1805#endif // QT_QUICK3D_HAS_LIGHTMAPPER
1806
1808{
1810 if (!model.lightmapLoadPath.isEmpty()) {
1811 result += model.lightmapLoadPath;
1812 if (!result.endsWith(QLatin1Char('/')))
1813 result += QLatin1Char('/');
1814 }
1815 switch (asset) {
1816 case LightmapAsset::LightmapImage:
1817 result += QStringLiteral("qlm_%1.exr").arg(model.lightmapKey);
1818 break;
1819 case LightmapAsset::MeshWithLightmapUV:
1820 result += QStringLiteral("qlm_%1.mesh").arg(model.lightmapKey);
1821 break;
1822 default:
1823 return QString();
1824 }
1825 return result;
1826}
1827
1829{
1830 QString result = outputFolder;
1831 if (!result.isEmpty() && !result.endsWith(QLatin1Char('/')))
1832 result += QLatin1Char('/');
1833
1834 switch (asset) {
1835 case LightmapAsset::LightmapImage:
1836 result += QStringLiteral("qlm_%1.exr").arg(model.lightmapKey);
1837 break;
1838 case LightmapAsset::MeshWithLightmapUV:
1839 result += QStringLiteral("qlm_%1.mesh").arg(model.lightmapKey);
1840 break;
1841 default:
1842 result += lightmapAssetPathForSave(asset, outputFolder);
1843 break;
1844 }
1845 return result;
1846}
1847
1849{
1850 QString result = outputFolder;
1851 if (!result.isEmpty() && !result.endsWith(QLatin1Char('/')))
1852 result += QLatin1Char('/');
1853
1854 switch (asset) {
1855 case LightmapAsset::LightmapImageList:
1856 result += QStringLiteral("qlm_list.txt");
1857 break;
1858 default:
1859 break;
1860 }
1861 return result;
1862}
1863
\inmodule QtCore
Definition qbytearray.h:57
\inmodule QtCore
void start() noexcept
\typealias QElapsedTimer::Duration Synonym for std::chrono::nanoseconds.
QString absoluteFilePath() const
\inmodule QtCore
Definition qfile.h:93
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
static Q_DECL_CONST_FUNCTION QRandomGenerator * global()
\threadsafe
Definition qrandom.h:275
@ Immutable
Definition qrhi.h:849
@ Dynamic
Definition qrhi.h:851
@ IndexBuffer
Definition qrhi.h:856
@ VertexBuffer
Definition qrhi.h:855
@ UniformBuffer
Definition qrhi.h:857
\inmodule QtGui
Definition qrhi.h:576
\inmodule QtGui
Definition qrhi.h:1651
QPair< QRhiBuffer *, quint32 > VertexInput
Synonym for QPair<QRhiBuffer *, quint32>.
Definition qrhi.h:1680
IndexFormat
Specifies the index data type.
Definition qrhi.h:1653
\inmodule QtGui
Definition qrhi.h:1270
void setTargetBlends(std::initializer_list< TargetBlend > list)
Sets the list of render target blend settings.
Definition qrhi.h:1398
void setDepthWrite(bool enable)
Controls the writing out of depth data into the depth buffer based on enable.
Definition qrhi.h:1414
void setShaderResourceBindings(QRhiShaderResourceBindings *srb)
Associates with srb describing the resource binding layout and the resources (QRhiBuffer,...
Definition qrhi.h:1462
void setDepthOp(CompareOp op)
Sets the depth comparison function op.
Definition qrhi.h:1417
void setVertexInputLayout(const QRhiVertexInputLayout &layout)
Specifies the vertex input layout.
Definition qrhi.h:1459
void setShaderStages(std::initializer_list< QRhiShaderStage > list)
Sets the list of shader stages.
Definition qrhi.h:1446
void setRenderPassDescriptor(QRhiRenderPassDescriptor *desc)
Associates with the specified QRhiRenderPassDescriptor desc.
Definition qrhi.h:1465
void setTopology(Topology t)
Sets the primitive topology t.
Definition qrhi.h:1390
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:1411
void setPolygonMode(PolygonMode mode)
Sets the polygon mode.
Definition qrhi.h:1471
\inmodule QtGui
Definition qrhi.h:1731
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:9011
\inmodule QtGui
Definition qrhi.h:1030
@ ClampToEdge
Definition qrhi.h:1040
\inmodule QtGui
Definition qrhi.h:1214
void setColorAttachments(std::initializer_list< QRhiColorAttachment > list)
Sets the list of color attachments.
Definition qrhi.h:627
\inmodule QtGui
Definition qrhi.h:716
\inmodule QtGui
Definition qrhi.h:895
@ UsedAsTransferSource
Definition qrhi.h:902
@ MipMapped
Definition qrhi.h:900
@ RenderTarget
Definition qrhi.h:898
@ RGBA32F
Definition qrhi.h:926
\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:321
void setBindings(std::initializer_list< QRhiVertexInputBinding > list)
Sets the bindings from the specified list.
Definition qrhi.h:325
void setAttributes(std::initializer_list< QRhiVertexInputAttribute > list)
Sets the attributes from the specified list.
Definition qrhi.h:337
\inmodule QtGui
Definition qrhi.h:85
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:1804
QRhiBuffer * newBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, quint32 size)
Definition qrhi.cpp:10508
bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags={}) const
Definition qrhi.cpp:10102
int ubufAligned(int v) const
Definition qrhi.cpp:9999
int resourceLimit(ResourceLimit limit) const
Definition qrhi.cpp:10121
bool isYUpInFramebuffer() const
Definition qrhi.cpp:10030
bool isYUpInNDC() const
Definition qrhi.cpp:10044
bool isFeatureSupported(QRhi::Feature feature) const
Definition qrhi.cpp:10110
QRhiRenderBuffer * newRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize, int sampleCount=1, QRhiRenderBuffer::Flags flags={}, QRhiTexture::Format backingFormatHint=QRhiTexture::UnknownFormat)
Definition qrhi.cpp:10535
QRhi::FrameOpResult finish()
Waits for any work on the graphics queue (where applicable) to complete, then executes all deferred o...
Definition qrhi.cpp:10936
QRhiTextureRenderTarget * newTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, QRhiTextureRenderTarget::Flags flags={})
Definition qrhi.cpp:10682
QRhiGraphicsPipeline * newGraphicsPipeline()
Definition qrhi.cpp:10466
@ MaxColorAttachments
Definition qrhi.h:1889
QRhiTexture * newTexture(QRhiTexture::Format format, const QSize &pixelSize, int sampleCount=1, QRhiTexture::Flags flags={})
Definition qrhi.cpp:10562
@ NonFillPolygonMode
Definition qrhi.h:1866
QRhiResourceUpdateBatch * nextResourceUpdateBatch()
Definition qrhi.cpp:9252
static QString lightmapAssetPathForSave(const QSSGRenderModel &model, LightmapAsset asset, const QString &outputFolder={})
qsizetype add(const QSSGBakedLightingModel &model)
std::function< void(BakingStatus, std::optional< QString >, BakingControl *) Callback)
void setOptions(const QSSGLightmapperOptions &options)
QSSGLightmapper(QSSGRhiContext *rhiCtx, QSSGRenderer *renderer)
static QString lightmapAssetPathForLoad(const QSSGRenderModel &model, LightmapAsset asset)
void setOutputCallback(Callback callback)
bool isValid() const
Definition qssgmesh_p.h:159
bool createLightmapUVChannel(uint lightmapBaseResolution)
bool hasLightmapUVChannel() const
VertexBuffer vertexBuffer() const
Definition qssgmesh_p.h:140
IndexBuffer indexBuffer() const
Definition qssgmesh_p.h:141
QVector< Subset > subsets() const
Definition qssgmesh_p.h:143
static QSSGRhiContextPrivate * get(QSSGRhiContext *q)
\inmodule QtQuick3D
QRhiCommandBuffer * commandBuffer() const
QRhiTexture * dummyTexture(QRhiTexture::Flags flags, QRhiResourceUpdateBatch *rub, const QSize &size=QSize(64, 64), const QColor &fillColor=Qt::black, int arraySize=0)
QRhi * rhi() const
QRhiSampler * sampler(const QSSGRhiSamplerDescription &samplerDescription)
void addUniformBuffer(int binding, QRhiShaderResourceBinding::StageFlags stage, QRhiBuffer *buf, int offset=0, int size=0)
void addTexture(int binding, QRhiShaderResourceBinding::StageFlags stage, QRhiTexture *tex, QRhiSampler *sampler)
iterator end()
Definition qset.h:140
iterator find(const T &value)
Definition qset.h:159
\inmodule QtCore
Definition qsize.h:25
constexpr int height() const noexcept
Returns the height.
Definition qsize.h:133
constexpr int width() const noexcept
Returns the width.
Definition qsize.h:130
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
QByteArray toUtf8() const &
Definition qstring.h:634
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
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())
QSet< QString >::iterator it
direction
else opt state
[0]
QRhiVertexInputAttribute::Format toVertexInputFormat(QSSGRenderComponentType compType, quint32 numComps)
QRhiSampler::Filter toRhi(QSSGRenderTextureFilterOp op)
Q_DECL_CONSTEXPR float translateLinearAttenuation(float attenuation)
Definition qssgutils_p.h:44
Q_DECL_CONSTEXPR float translateConstantAttenuation(float attenuation)
Definition qssgutils_p.h:42
Q_DECL_CONSTEXPR float translateQuadraticAttenuation(float attenuation)
Definition qssgutils_p.h:46
QVector3D Q_QUICK3DUTILS_EXPORT transform(const QMatrix3x3 &m, const QVector3D &v)
Definition qssgutils.cpp:43
Combined button and popup list for selecting options.
QTCONCURRENT_RUN_NODISCARD auto run(QThreadPool *pool, Function &&f, Args &&...args)
@ black
Definition qnamespace.h:30
constexpr Initialization Uninitialized
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:333
bool qFuzzyIsNull(qfloat16 f) noexcept
Definition qfloat16.h:349
@ None
Definition qhash.cpp:531
#define qDebug
[1]
Definition qlogging.h:164
#define qWarning
Definition qlogging.h:166
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
GLuint color
[2]
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 s
[6]
Definition qopenglext.h:235
GLdouble GLdouble t
Definition qopenglext.h:243
GLint GLenum GLboolean normalized
Definition qopenglext.h:752
GLuint64EXT * result
[6]
GLfloat GLfloat p
[1]
GLfloat GLfloat GLfloat alpha
Definition qopenglext.h:418
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:1220
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QSSGRenderComponentType
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:50
unsigned short quint16
Definition qtypes.h:48
int qint32
Definition qtypes.h:49
ptrdiff_t qsizetype
Definition qtypes.h:165
float vertexData[]
QSqlQueryModel * model
[16]
QFuture< void > future
[5]
future waitForFinished()
std::uniform_real_distribution dist(1, 2.5)
[2]
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)
QHostInfo info
[0]
QJSValueList args
QSvgRenderer * renderer
[0]
Definition parser.h:19
\inmodule QtCore \reentrant
Definition qchar.h:18
\inmodule QtGui
Definition qrhi.h:1723
static const char * getLightmapUVAttrName()
Definition qssgmesh_p.h:396
static const char * getNormalAttrName()
Definition qssgmesh_p.h:393
static const char * getTexBinormalAttrName()
Definition qssgmesh_p.h:398
static const char * getPositionAttrName()
Definition qssgmesh_p.h:392
static const char * getTexTanAttrName()
Definition qssgmesh_p.h:397
static const char * getUV0AttrName()
Definition qssgmesh_p.h:394
static void setShaderPipeline(QSSGRhiGraphicsPipelineState &ps, const QSSGRhiShaderPipeline *pipeline)