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>
11#ifdef QT_QUICK3D_HAS_LIGHTMAPPER
12#include <QtCore/qfuture.h>
13#include <QtCore/qfileinfo.h>
14#include <QtConcurrent/qtconcurrentrun.h>
15#include <QRandomGenerator>
17#include <embree3/rtcore.h>
36#ifdef QT_QUICK3D_HAS_LIGHTMAPPER
38struct QSSGLightmapperPrivate
50 unsigned int geomId = RTC_INVALID_GEOMETRY_ID;
59 float normalStrength = 0.0f;
71 quint32 positionOffset = UINT_MAX;
73 quint32 normalOffset = UINT_MAX;
77 quint32 lightmapUVOffset = UINT_MAX;
79 quint32 tangentOffset = UINT_MAX;
81 quint32 binormalOffset = UINT_MAX;
98 float cosInnerConeAngle;
99 float constantAttenuation;
100 float linearAttenuation;
101 float quadraticAttenuation;
105 RTCDevice rdev =
nullptr;
106 RTCScene rscene =
nullptr;
108 struct LightmapEntry {
114 bool isValid()
const {
return !worldPos.
isNull() && !normal.
isNull(); }
119 Lightmap(
const QSize &pixelSize) : pixelSize(pixelSize) {
120 entries.resize(pixelSize.
width() * pixelSize.
height());
125 bool hasBaseColorTransparency =
false;
131 inline const LightmapEntry &texelForLightmapUV(
unsigned int geomId,
float u,
float v)
const
134 const Lightmap &hitLightmap(lightmaps[geomLightmapMap[geomId]]);
135 u =
qBound(0.0f, u, 1.0f);
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);
144 return hitLightmap.entries[
x +
y *
w];
147 bool commitGeometry();
148 bool prepareLightmaps();
149 void computeDirectLight();
150 void computeIndirectLight();
152 bool storeLightmaps();
156static const int LM_SEAM_BLEND_ITER_COUNT = 4;
159 :
d(new QSSGLightmapperPrivate)
165 _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
166 _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON);
176 _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_OFF);
177 _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_OFF);
183 d->bakedLightingModels.clear();
184 d->subMeshInfos.clear();
185 d->drawInfos.clear();
187 d->lightmaps.clear();
188 d->geomLightmapMap.clear();
189 d->subMeshOpacityMap.clear();
192 rtcReleaseScene(
d->rscene);
196 rtcReleaseDevice(
d->rdev);
200 d->bakingControl.cancelled =
false;
205 d->options = options;
210 d->outputCallback = callback;
215 d->bakedLightingModels.append(
model);
216 return d->bakedLightingModels.size() - 1;
219static void embreeErrFunc(
void *, RTCError
error,
const char *
str)
224static const unsigned int NORMAL_SLOT = 0;
225static const unsigned int LIGHTMAP_UV_SLOT = 1;
227static void embreeFilterFunc(
const RTCFilterFunctionNArguments *
args)
229 RTCHit *hit =
reinterpret_cast<RTCHit *
>(
args->hit);
230 QSSGLightmapperPrivate *
d =
static_cast<QSSGLightmapperPrivate *
>(
args->geometryUserPtr);
231 RTCGeometry geom = rtcGetGeometry(
d->rscene, hit->geomID);
234 rtcInterpolate0(geom, hit->primID, hit->u, hit->v, RTC_BUFFER_TYPE_VERTEX_ATTRIBUTE, LIGHTMAP_UV_SLOT, &hit->u, 2);
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));
243 const float alpha = opacity * texel.baseColor.w();
249 if (alpha < d->options.opacityThreshold)
254bool QSSGLightmapperPrivate::commitGeometry()
256 if (bakedLightingModels.
isEmpty()) {
263 geomPrepTimer.
start();
265 const auto &bufferManager(
renderer->contextInterface()->bufferManager());
267 const int bakedLightingModelCount = bakedLightingModels.
size();
268 subMeshInfos.
resize(bakedLightingModelCount);
269 drawInfos.
resize(bakedLightingModelCount);
271 for (
int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
273 if (lm.renderables.isEmpty()) {
275 arg(lm.model->debugObjectName));
278 if (lm.model->skin || lm.model->skeleton) {
280 arg(lm.model->debugObjectName));
284 subMeshInfos[lmIdx].
reserve(lm.renderables.size());
286 Q_ASSERT(
handle.obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset
287 ||
handle.obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset);
293 if (
handle.obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset) {
326 DrawInfo &drawInfo(drawInfos[lmIdx]);
329 if (lm.model->geometry)
330 mesh = bufferManager->loadMeshData(lm.model->geometry);
332 mesh = bufferManager->loadMeshData(lm.model->meshPath);
336 arg(lm.model->debugObjectName));
345 arg(lm.model->debugObjectName));
349 arg(lm.model->debugObjectName).
352 if (lm.model->hasLightmap())
353 drawInfo.meshWithLightmapUV = mesh;
358 drawInfo.lightmapSize = mesh.
subsets().first().lightmapSizeHint;
359 if (drawInfo.lightmapSize.isEmpty()) {
361 arg(lm.model->debugObjectName));
362 drawInfo.lightmapSize =
QSize(1024, 1024);
369 if (drawInfo.vertexData.isEmpty()) {
373 if (drawInfo.indexData.isEmpty()) {
379 case QSSGMesh::Mesh::ComponentType::UnsignedInt16:
382 case QSSGMesh::Mesh::ComponentType::UnsignedInt32:
388 arg(lm.model->debugObjectName));
394 drawInfo.positionOffset = vbe.offset;
397 drawInfo.normalOffset = vbe.offset;
400 drawInfo.uvOffset = vbe.offset;
403 drawInfo.lightmapUVOffset = vbe.offset;
406 drawInfo.tangentOffset = vbe.offset;
409 drawInfo.binormalOffset = vbe.offset;
414 if (!(drawInfo.positionOffset != UINT_MAX && drawInfo.normalOffset != UINT_MAX)) {
416 arg(lm.model->debugObjectName));
425 arg(lm.model->debugObjectName));
429 if (drawInfo.lightmapUVOffset == UINT_MAX) {
431 arg(lm.model->debugObjectName));
437 arg(lm.model->debugObjectName));
442 if (drawInfo.uvOffset != UINT_MAX) {
445 arg(lm.model->debugObjectName));
450 if (drawInfo.tangentOffset != UINT_MAX) {
453 arg(lm.model->debugObjectName));
457 if (drawInfo.binormalOffset != UINT_MAX) {
460 arg(lm.model->debugObjectName));
468 const quint16 *
s =
reinterpret_cast<const quint16 *
>(drawInfo.indexData.constData());
469 size_t sz = drawInfo.indexData.size() / 2;
473 drawInfo.indexData = newIndexData;
478 char *vertexBase = drawInfo.vertexData.data();
479 const qsizetype sz = drawInfo.vertexData.size();
481 char *posPtr = vertexBase +
offset + drawInfo.positionOffset;
482 float *fPosPtr =
reinterpret_cast<float *
>(posPtr);
484 char *normalPtr = vertexBase +
offset + drawInfo.normalOffset;
485 float *fNormalPtr =
reinterpret_cast<float *
>(normalPtr);
486 QVector3D normal(fNormalPtr[0], fNormalPtr[1], fNormalPtr[2]);
489 *fPosPtr++ =
pos.x();
490 *fPosPtr++ =
pos.y();
491 *fPosPtr++ =
pos.z();
492 *fNormalPtr++ = normal.
x();
493 *fNormalPtr++ = normal.
y();
494 *fNormalPtr++ = normal.
z();
504 if (!sl.light->m_bakingEnabled)
508 light.indirectOnly = !sl.light->m_fullyBaked;
509 light.direction = sl.direction;
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);
516 if (sl.light->type == QSSGRenderLight::Type::PointLight
517 || sl.light->type == QSSGRenderLight::Type::SpotLight)
519 light.worldPos = sl.light->getGlobalPos();
520 if (sl.light->type == QSSGRenderLight::Type::SpotLight) {
521 light.type = Light::Spot;
524 qMin(sl.light->m_innerConeAngle, sl.light->m_coneAngle)));
526 light.type = Light::Point;
532 light.type = Light::Directional;
540 rdev = rtcNewDevice(
nullptr);
546 rtcSetDeviceErrorFunction(rdev, embreeErrFunc,
nullptr);
548 rscene = rtcNewScene(rdev);
550 unsigned int geomId = 1;
552 for (
int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
559 if (!lm.model->castsShadows)
562 const DrawInfo &drawInfo(drawInfos[lmIdx]);
563 const char *vbase = drawInfo.vertexData.constData();
564 const quint32 *ibase =
reinterpret_cast<const quint32 *
>(drawInfo.indexData.constData());
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)
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);
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);
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);
595 rtcCommitGeometry(geom);
596 rtcSetGeometryIntersectFilterFunction(geom, embreeFilterFunc);
597 rtcSetGeometryUserData(geom,
this);
598 rtcAttachGeometryByID(rscene, geom, geomId);
599 subMeshInfo.geomId = geomId++;
600 rtcReleaseGeometry(geom);
604 rtcCommitScene(rscene);
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;
612 const unsigned int geomIdBasedMapSize = geomId;
615 geomLightmapMap.
fill(-1, geomIdBasedMapSize);
616 subMeshOpacityMap.
fill(0.0f, geomIdBasedMapSize);
618 for (
int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
620 if (!lm.model->castsShadows)
622 for (SubMeshInfo &subMeshInfo : subMeshInfos[lmIdx])
623 subMeshOpacityMap[subMeshInfo.geomId] = subMeshInfo.opacity;
630bool QSSGLightmapperPrivate::prepareLightmaps()
648 const int bakedLightingModelCount = bakedLightingModels.
size();
650 Q_ASSERT(subMeshInfos.
size() == bakedLightingModelCount);
652 for (
int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
654 rasterizeTimer.
start();
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;
668 if (!vbuf->create()) {
673 if (!ibuf->create()) {
681 cb->resourceUpdate(resUpd);
685 if (!positionData->create()) {
691 if (!normalData->create()) {
697 if (!baseColorData->create()) {
703 if (!emissionData->create()) {
723 std::unique_ptr<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor());
724 rt->setRenderPassDescriptor(rpDesc.get());
731 const int subMeshCount = subMeshInfos[lmIdx].
size();
733 const int totalUbufSize = alignedUbufSize * subMeshCount;
735 if (!ubuf->create()) {
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));
762 ubuf->endFullDynamicBufferUpdateForCurrentFrame();
773 ps->
setShaderStages(shaderPipeline->cbeginStages(), shaderPipeline->cendStages());
786 for (
int subMeshIdx = 0; subMeshIdx != subMeshCount; ++subMeshIdx) {
787 const SubMeshInfo &subMeshInfo(subMeshInfos[lmIdx][subMeshIdx]);
799 if (hasTangentAndBinormal)
803 const auto &lmUvRastShaderPipeline =
renderer->getRhiLightmapUVRasterizationShader(shaderVariant);
804 if (!lmUvRastShaderPipeline) {
811 if (hasTangentAndBinormal) {
821 subMeshIdx * alignedUbufSize,
UBUF_SIZE);
824 if (subMeshInfo.baseColorMap) {
827 toRhi(subMeshInfo.baseColorNode->m_magFilterType),
829 toRhi(subMeshInfo.baseColorNode->m_horizontalTilingMode),
830 toRhi(subMeshInfo.baseColorNode->m_verticalTilingMode),
837 if (subMeshInfo.emissiveMap) {
840 toRhi(subMeshInfo.emissiveNode->m_magFilterType),
842 toRhi(subMeshInfo.emissiveNode->m_horizontalTilingMode),
843 toRhi(subMeshInfo.emissiveNode->m_verticalTilingMode),
850 if (subMeshInfo.normalMap) {
851 if (!hasUV0 || !hasTangentAndBinormal) {
853 "but the mesh does not provide all three of UV0, tangent, and binormal; "
854 "expect incorrect results").
arg(subMeshIdx));
858 toRhi(subMeshInfo.normalMapNode->m_magFilterType),
860 toRhi(subMeshInfo.normalMapNode->m_horizontalTilingMode),
861 toRhi(subMeshInfo.normalMapNode->m_verticalTilingMode),
871 if (!pipeline->
create()) {
880 pipeline = setupPipeline(lmUvRastShaderPipeline.get(), srb, inputLayout);
882 if (!pipeline->
create()) {
895 bool hadViewport =
false;
898 for (
int subMeshIdx = 0; subMeshIdx != subMeshCount; ++subMeshIdx) {
899 const SubMeshInfo &subMeshInfo(subMeshInfos[lmIdx][subMeshIdx]);
900 cb->setGraphicsPipeline(ps[subMeshIdx]);
905 cb->setShaderResources();
907 cb->drawIndexed(subMeshInfo.count, 1, subMeshInfo.offset);
908 cb->setGraphicsPipeline(psLine[subMeshIdx]);
909 cb->setShaderResources();
910 cb->drawIndexed(subMeshInfo.count, 1, subMeshInfo.offset);
920 resUpd->
readBackTexture({ baseColorData.get() }, &baseColorReadResult);
930 Lightmap lightmap(outputSize);
934 if (posReadResult.
data.
size() < lightmap.entries.size() * 16) {
938 if (normalReadResult.
data.
size() < lightmap.entries.size() * 16) {
942 if (baseColorReadResult.
data.
size() < lightmap.entries.size() * 16) {
946 if (emissionReadResult.
data.
size() < lightmap.entries.size() * 16) {
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]);
958 float x = *lmPosPtr++;
959 float y = *lmPosPtr++;
960 float z = *lmPosPtr++;
970 float r = *lmBaseColorPtr++;
971 float g = *lmBaseColorPtr++;
972 float b = *lmBaseColorPtr++;
973 float a = *lmBaseColorPtr++;
976 lightmap.hasBaseColorTransparency =
true;
978 r = *lmEmissionPtr++;
979 g = *lmEmissionPtr++;
980 b = *lmEmissionPtr++;
984 if (!lmPix.isValid())
989 arg(lightmap.entries.size() - unusedEntries).
990 arg(lightmap.entries.size()).
991 arg(lm.model->debugObjectName).
994 lightmaps.append(lightmap);
996 for (
const SubMeshInfo &subMeshInfo :
std::as_const(subMeshInfos[lmIdx]))
997 geomLightmapMap[subMeshInfo.geomId] = lightmaps.
size() - 1;
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;
1022 bool intersect(RTCScene
scene)
1024 RTCIntersectContext
ctx;
1025 rtcInitIntersectContext(&
ctx);
1026 rtcIntersect1(
scene, &
ctx, &rayhit);
1027 return rayhit.hit.geomID != RTC_INVALID_GEOMETRY_ID;
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);
1045void QSSGLightmapperPrivate::computeDirectLight()
1049 fullDirectLightTimer.
start();
1051 const int bakedLightingModelCount = bakedLightingModels.
size();
1052 Q_ASSERT(lightmaps.size() == bakedLightingModelCount);
1056 for (
int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
1058 Lightmap &lightmap(lightmaps[lmIdx]);
1063 directLightTimer.
start();
1065 const int lightCount = lights.
size();
1066 for (LightmapEntry &lmPix : lightmap.entries) {
1067 if (!lmPix.isValid())
1071 if (options.useAdaptiveBias)
1072 worldPos += vectorSign(lmPix.normal) * vectorAbs(worldPos * 0.0000002f);
1075 for (
int i = 0;
i < lightCount; ++
i) {
1076 const Light &light(lights[
i]);
1079 float dist = std::numeric_limits<float>::infinity();
1080 float attenuation = 1.0f;
1081 if (light.type == Light::Directional) {
1082 lightWorldPos = worldPos - light.direction;
1084 lightWorldPos = light.worldPos;
1086 attenuation = 1.0f / (light.constantAttenuation
1087 + light.linearAttenuation *
dist
1088 + light.quadraticAttenuation *
dist *
dist);
1089 if (light.type == Light::Spot) {
1091 light.direction.normalized());
1092 if (spotAngle > light.cosConeAngle) {
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;
1113 RayHit ray(worldPos, L, options.bias,
dist);
1114 const bool lightReachable = !ray.intersect(rscene);
1115 if (lightReachable) {
1117 lmPix.directLight += light.color * energy;
1119 if (!light.indirectOnly)
1120 lmPix.allLight += light.color * energy;
1126 arg(lm.model->debugObjectName).
1139static inline float uniformRand()
1145 return float(
state) / float(UINT32_MAX);
1148static inline QVector3D cosWeightedHemisphereSample()
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);
1157void QSSGLightmapperPrivate::computeIndirectLight()
1161 fullIndirectLightTimer.
start();
1163 const int bakedLightingModelCount = bakedLightingModels.
size();
1165 for (
int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
1167 if (!bakedLightingModels[lmIdx].
model->hasLightmap())
1171 Lightmap &lightmap(lightmaps[lmIdx]);
1174 arg(lm.model->debugObjectName).
1175 arg(lightmap.entries.size()));
1177 indirectLightTimer.
start();
1184 int wgSizePerGroup =
qMax(1, options.indirectLightWorkgroupSize);
1185 int wgCount = options.indirectLightSamples / wgSizePerGroup;
1186 if (options.indirectLightSamples % wgSizePerGroup)
1192 arg(lm.model->debugObjectName).
1193 arg(lm.model->lightmapKey));
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())
1203 for (
int wgIdx = 0; wgIdx < wgCount; ++wgIdx) {
1204 const int beginIdx = wgIdx * wgSizePerGroup;
1205 const int endIdx =
qMin(beginIdx + wgSizePerGroup, options.indirectLightSamples);
1209 for (
int sampleIdx = beginIdx; sampleIdx < endIdx; ++sampleIdx) {
1215 for (
int bounce = 0; bounce < options.indirectLightBounces; ++bounce) {
1216 if (options.useAdaptiveBias)
1220 const QVector3D sample = cosWeightedHemisphereSample();
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());
1236 const float pdf = NdotL / float(
M_PI);
1242 if (!ray.intersect(rscene))
1246 const LightmapEntry &hitEntry = texelForLightmapUV(ray.rayhit.hit.geomID,
1256 const QVector3D brdf = hitEntry.baseColor.toVector3D() / float(
M_PI);
1259 sampleResult += throughput * hitEntry.emission;
1260 throughput *= brdf * NdotL / pdf;
1261 sampleResult += throughput * hitEntry.directLight;
1265 const float p =
qMax(
qMax(throughput.x(), throughput.y()), throughput.z());
1266 if (
p < uniformRand())
1274 normal = hitEntry.normal;
1277 wgResult += sampleResult;
1284 for (
const auto &
future : wg)
1287 lmPix.allLight += totalIndirect * options.indirectLightFactor / options.indirectLightSamples;
1290 if (texelsDone % 10000 == 0)
1292 arg(lightmap.entries.size() - texelsDone));
1298 arg(lm.model->debugObjectName).
1299 arg(lm.model->lightmapKey).
1308 std::array<QVector3D, 2>
pos;
1309 std::array<QVector3D, 2> normal;
1327 std::array<QVector2D, 2> uv;
1332 std::array<std::array<QVector2D, 2>, 2> uv;
1337 if (
a.x() ==
b.x()) {
1339 return a.z() <
b.z();
1341 return a.y() <
b.y();
1343 return a.x() <
b.x();
1346static inline float floatSign(
float f)
1348 return f > 0.0f ? 1.0f : (
f < 0.0f ? -1.0f : 0.0f);
1353 return QVector2D(std::floor(
v.x()), std::floor(
v.y()));
1360 const float lengthSquared =
n.lengthSquared();
1362 const float d = (
n.x() *
p.x() +
n.y() *
p.y()) / lengthSquared;
1371 const QSize &lightmapPixelSize)
1376 const float lineLength =
line[0].distanceToPoint(
line[1]);
1384 const QVector2D tStep(1.0f / std::abs(
dir.x()), 1.0f / std::abs(
dir.y()));
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());
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());
1398 float *fpW =
reinterpret_cast<float *
>(writeBuf.
data());
1399 const float *fpR =
reinterpret_cast<const float *
>(readBuf.
constData());
1405 const float t =
line[0].distanceToPoint(point) / lineLength;
1406 const QVector2D uvInterp = uvFrom * (1.0 -
t) + uvTo *
t;
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();
1418 if (pixel != endPixel) {
1419 if (nextT.x() < nextT.y()) {
1420 pixel.
setX(pixel.
x() + pixelStep.x());
1421 nextT.setX(nextT.x() + tStep.x());
1423 pixel.
setY(pixel.
y() + pixelStep.y());
1424 nextT.setY(nextT.y() + tStep.y());
1432bool QSSGLightmapperPrivate::postProcess()
1436 const int bakedLightingModelCount = bakedLightingModels.
size();
1439 for (
int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
1441 postProcessTimer.
start();
1445 if (!lm.model->hasLightmap())
1448 Lightmap &lightmap(lightmaps[lmIdx]);
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;
1461 const QRhiViewport viewport(0, 0,
float(lightmap.pixelSize.width()),
float(lightmap.pixelSize.height()));
1464 if (!lightmapTex->create()) {
1470 if (!dilatedLightmapTex->create()) {
1476 std::unique_ptr<QRhiRenderPassDescriptor> rpDescDilate(rtDilate->newCompatibleRenderPassDescriptor());
1477 rtDilate->setRenderPassDescriptor(rpDescDilate.get());
1478 if (!rtDilate->create()) {
1489 renderer->rhiQuadRenderer()->prepareQuad(rhiCtx, resUpd);
1490 const auto &lmDilatePipeline =
renderer->getRhiLightmapDilateShader();
1491 if (!lmDilatePipeline) {
1501 resUpd->
readBackTexture({ dilatedLightmapTex.get() }, &dilateReadResult);
1502 cb->resourceUpdate(resUpd);
1507 lightmap.imageFP32 = dilateReadResult.
data;
1512 const DrawInfo &drawInfo(drawInfos[lmIdx]);
1513 const char *vbase = drawInfo.vertexData.constData();
1514 const quint32 *ibase =
reinterpret_cast<const quint32 *
>(drawInfo.indexData.constData());
1519 for (SubMeshInfo &subMeshInfo : subMeshInfos[lmIdx])
1520 assembledVertexCount += subMeshInfo.
count;
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);
1533 src =
reinterpret_cast<const float *
>(vbase + idx * drawInfo.vertexStride + drawInfo.normalOffset);
1538 src =
reinterpret_cast<const float *
>(vbase + idx * drawInfo.vertexStride + drawInfo.lightmapUVOffset);
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] };
1553 for (
int i = 0;
i < 3; ++
i) {
1555 int i1 = (
i + 1) % 3;
1556 if (vectorLessThan(triVert[i1], triVert[i0]))
1560 { triVert[i0], triVert[i1] },
1561 { triNorm[i0], triNorm[i1] }
1563 const EdgeUV edgeUV = { { triUV[i0], triUV[i1] } };
1565 if (
it == edgeUVMap.
end()) {
1569 seams.
append(SeamUV({ { edgeUV.uv,
it->uv } }));
1575 qDebug() <<
"lm:" << seams.
size() <<
"UV seams in" << lm.model;
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);
1592 arg(lm.model->debugObjectName).
1593 arg(lm.model->lightmapKey).
1600bool QSSGLightmapperPrivate::storeLightmaps()
1602 const int bakedLightingModelCount = bakedLightingModels.
size();
1605 for (
int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
1608 if (!lm.model->hasLightmap())
1616 if (!lm.model->lightmapLoadPath.startsWith(
QStringLiteral(
":/")))
1617 outputFolder = lm.model->lightmapLoadPath;
1623 listContents +=
'\n';
1625 const Lightmap &lightmap(lightmaps[lmIdx]);
1627 if (SaveEXR(
reinterpret_cast<const float *
>(lightmap.imageFP32.constData()),
1628 lightmap.pixelSize.width(), lightmap.pixelSize.height(),
1629 4,
false, fns.
constData(),
nullptr) < 0)
1636 arg(lm.model->debugObjectName).
1639 const DrawInfo &bakeModelDrawInfo(drawInfos[lmIdx]);
1640 if (bakeModelDrawInfo.meshWithLightmapUV.isValid()) {
1644 bakeModelDrawInfo.meshWithLightmapUV.save(&
f);
1651 arg(lm.model->debugObjectName).
1660 arg(listFile.fileName()));
1663 listFile.write(listContents);
1693 if (msg.has_value())
1702 outputCallback(
type, msg, &bakingControl);
1713 if (
d->bakedLightingModels.isEmpty()) {
1718 if (!
d->commitGeometry()) {
1723 if (!
d->prepareLightmaps()) {
1728 if (
d->bakingControl.cancelled) {
1733 d->computeDirectLight();
1735 if (
d->bakingControl.cancelled) {
1740 if (
d->options.indirectLightEnabled)
1741 d->computeIndirectLight();
1743 if (
d->bakingControl.cancelled) {
1748 if (!
d->postProcess()) {
1753 if (
d->bakingControl.cancelled) {
1758 if (!
d->storeLightmaps()) {
1797 qWarning(
"Qt Quick 3D was built without the lightmapper; cannot bake lightmaps");
1806 if (!
model.lightmapLoadPath.isEmpty()) {
char * data()
\macro QT_NO_CAST_FROM_BYTEARRAY
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
qint64 elapsed() const noexcept
Returns the number of milliseconds since this QElapsedTimer was last started.
void start() noexcept
Starts this timer.
\inmodule QtCore \reentrant
QString absoluteFilePath() const
Returns an absolute path including the file name.
iterator find(const Key &key)
Returns an iterator pointing to the item with the key in the hash.
iterator end() noexcept
Returns an \l{STL-style iterators}{STL-style iterator} pointing to the imaginary item after the last ...
iterator insert(const Key &key, const T &value)
Inserts a new item with the key and a value of value.
qsizetype size() const noexcept
QList< T > & fill(parameter_type t, qsizetype size=-1)
bool isEmpty() const noexcept
void reserve(qsizetype size)
void resize(qsizetype size)
void append(parameter_type t)
The QMatrix4x4 class represents a 4x4 transformation matrix in 3D space.
QPoint map(const QPoint &point) const
Maps point by multiplying this matrix by point.
quint32 generate()
Generates a 32-bit random quantity and returns it.
static Q_DECL_CONST_FUNCTION QRandomGenerator * global()
\threadsafe
QPair< QRhiBuffer *, quint32 > VertexInput
Synonym for QPair<QRhiBuffer *, quint32>.
IndexFormat
Specifies the index data type.
void setTargetBlends(std::initializer_list< TargetBlend > list)
Sets the list of render target blend settings.
void setDepthWrite(bool enable)
Controls the writing out of depth data into the depth buffer based on enable.
void setShaderResourceBindings(QRhiShaderResourceBindings *srb)
Associates with srb describing the resource binding layout and the resources (QRhiBuffer,...
void setDepthOp(CompareOp op)
Sets the depth comparison function op.
void setVertexInputLayout(const QRhiVertexInputLayout &layout)
Specifies the vertex input layout.
void setShaderStages(std::initializer_list< QRhiShaderStage > list)
Sets the list of shader stages.
void setRenderPassDescriptor(QRhiRenderPassDescriptor *desc)
Associates with the specified QRhiRenderPassDescriptor desc.
void setTopology(Topology t)
Sets the primitive topology t.
virtual bool create()=0
Creates the corresponding native graphics resources.
void setDepthTest(bool enable)
Enables or disables depth testing based on enable.
void setPolygonMode(PolygonMode mode)
Sets the polygon mode.
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...
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...
void readBackTexture(const QRhiReadbackDescription &rb, QRhiReadbackResult *result)
Enqueues a texture-to-host copy operation as described by rb.
void setDepthStencilBuffer(QRhiRenderBuffer *renderBuffer)
Sets the renderBuffer for depth-stencil.
void setColorAttachments(std::initializer_list< QRhiColorAttachment > list)
Sets the list of color attachments.
QRhiBuffer * newBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, quint32 size)
bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags={}) const
int ubufAligned(int v) const
int resourceLimit(ResourceLimit limit) const
bool isYUpInFramebuffer() const
bool isFeatureSupported(QRhi::Feature feature) const
QRhiRenderBuffer * newRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize, int sampleCount=1, QRhiRenderBuffer::Flags flags={}, QRhiTexture::Format backingFormatHint=QRhiTexture::UnknownFormat)
QRhi::FrameOpResult finish()
Waits for any work on the graphics queue (where applicable) to complete, then executes all deferred o...
QRhiTextureRenderTarget * newTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, QRhiTextureRenderTarget::Flags flags={})
QRhiGraphicsPipeline * newGraphicsPipeline()
QRhiTexture * newTexture(QRhiTexture::Format format, const QSize &pixelSize, int sampleCount=1, QRhiTexture::Flags flags={})
QRhiResourceUpdateBatch * nextResourceUpdateBatch()
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 createLightmapUVChannel(uint lightmapBaseResolution)
bool hasLightmapUVChannel() const
VertexBuffer vertexBuffer() const
IndexBuffer indexBuffer() const
QVector< Subset > subsets() const
LightmapUVRasterizationShaderMode
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)
QRhiSampler * sampler(const QSSGRhiSamplerDescription &samplerDescription)
constexpr int height() const noexcept
Returns the height.
constexpr int width() const noexcept
Returns the width.
\macro QT_RESTRICTED_CAST_FROM_ASCII
QByteArray toUtf8() const &
const_iterator cbegin() const noexcept
const_iterator cend() const noexcept
The QVector2D class represents a vector or vertex in 2D space.
constexpr float y() const noexcept
Returns the y coordinate of this point.
constexpr float x() const noexcept
Returns the x coordinate of this point.
float distanceToPoint(QVector2D point) const noexcept
constexpr void setY(float y) noexcept
Sets the y coordinate of this point to the given finite y coordinate.
constexpr void setX(float x) noexcept
Sets the x coordinate of this point to the given finite x coordinate.
The QVector3D class represents a vector or vertex in 3D space.
constexpr bool isNull() const noexcept
Returns true if the x, y, and z coordinates are set to 0.0, otherwise returns false.
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...
QVector3D normalized() const noexcept
Returns the normalized unit vector form of this vector.
constexpr float y() const noexcept
Returns the y coordinate of this point.
constexpr float x() const noexcept
Returns the x coordinate of this point.
static constexpr float dotProduct(QVector3D v1, QVector3D v2) noexcept
Returns the dot product of v1 and v2.
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.
constexpr float z() const noexcept
Returns the z coordinate of this point.
The QVector4D class represents a vector or vertex in 4D space.
qDeleteAll(list.begin(), list.end())
QSet< QString >::iterator it
Combined button and popup list for selecting options.
auto run(QThreadPool *pool, Function &&f, Args &&...args)
constexpr Initialization Uninitialized
Q_DECL_CONSTEXPR float translateConstantAttenuation(float attenuation)
Q_DECL_CONSTEXPR float translateQuadraticAttenuation(float attenuation)
Q_DECL_CONSTEXPR float translateLinearAttenuation(float attenuation)
QVector3D Q_QUICK3DUTILS_EXPORT transform(const QMatrix3x3 &m, const QVector3D &v)
static const int UBUF_SIZE
DBusConnection const char DBusError * error
size_t qHash(const QFileSystemWatcherPathKey &key, size_t seed=0)
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
bool qFuzzyIsNull(qfloat16 f) noexcept
constexpr float qDegreesToRadians(float degrees)
constexpr const T & qMin(const T &a, const T &b)
constexpr const T & qBound(const T &min, const T &val, const T &max)
constexpr const T & qMax(const T &a, const T &b)
static QList< QNetworkInterfacePrivate * > postProcess(QList< QNetworkInterfacePrivate * > list)
constexpr T qAbs(const T &t)
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]
GLenum GLuint GLenum GLsizei length
GLenum GLenum GLsizei count
GLenum GLuint GLintptr offset
GLfloat GLfloat GLfloat GLfloat h
GLint GLenum GLboolean normalized
GLfloat GLfloat GLfloat alpha
QT_BEGIN_NAMESPACE typedef void(* Callback)(QQmlNotifierEndpoint *, void **)
static qreal position(const QQuickItem *item, QQuickAnchors::Anchor anchorLine)
static Q_CONSTINIT QBasicAtomicInteger< unsigned > seed
bool operator==(const QRandomGenerator &rng1, const QRandomGenerator &rng2)
QRhiSampler::Filter toRhi(QSSGRenderTextureFilterOp op)
SSL_CTX int(* cb)(SSL *ssl, unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg)
#define QStringLiteral(str)
QSqlQueryModel * model
[16]
QFuture< void > future
[5]
std::uniform_real_distribution dist(1, 2.5)
[2]
QFileInfo info(fileName)
[8]
view viewport() -> scroll(dx, dy, deviceRect)
QSvgRenderer * renderer
[0]
\inmodule QtCore \reentrant
static const char * getLightmapUVAttrName()
static const char * getNormalAttrName()
static const char * getTexBinormalAttrName()
static const char * getPositionAttrName()
static const char * getTexTanAttrName()
static const char * getUV0AttrName()
ComponentType componentType
QSSGRenderImage * colorMap
QSSGRenderImage * normalMap
QSSGRenderImage * emissiveMap
const QMatrix4x4 & globalTransform
const QSSGRhiShaderPipeline * shaderPipeline
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