220 if (!camera || !layer || !
_device) {
224 const auto sortStart = std::chrono::high_resolution_clock::now();
227 if (!programLibrary) {
228 spdlog::error(
"ProgramLibrary is not initialized. Forward rendering requires ProgramLibrary.");
234 programLibrary->setSkyCubemapAvailable(
_scene &&
_scene->skybox() !=
nullptr);
246 bool hasLocalShadows =
false;
247 bool hasOmniShadows =
false;
249 if (lc && lc->enabled() && lc->castShadows() &&
251 Light* sceneLight = lc->light();
252 if (sceneLight && sceneLight->
shadowMap()) {
254 hasOmniShadows =
true;
256 hasLocalShadows =
true;
261 programLibrary->setLocalShadowsEnabled(hasLocalShadows);
262 programLibrary->setOmniShadowsEnabled(hasOmniShadows);
267 bool hasAreaLights =
false;
270 hasAreaLights =
true;
274 programLibrary->setAreaLightsEnabled(hasAreaLights);
279 const bool clusteredEnabled =
_scene &&
_scene->clusteredLightingEnabled();
280 programLibrary->setClusteredLightingEnabled(clusteredEnabled);
285 programLibrary->setSsaoEnabled(
_device->ssaoForwardTexture() !=
nullptr);
289 const bool atmosphereEnabled =
_scene &&
_scene->atmosphereEnabled();
290 programLibrary->setAtmosphereEnabled(atmosphereEnabled);
291 _device->setAtmosphereEnabled(atmosphereEnabled);
292 if (atmosphereEnabled) {
293 _device->setAtmosphereUniforms(
_scene->atmosphereUniformData(),
_scene->atmosphereUniformSize());
297 if (clusteredEnabled && !_worldClusters) {
298 _worldClusters = std::make_unique<WorldClusters>();
303 auto* cameraNode = camera->
node().get();
304 const auto cameraPosition = cameraNode ? cameraNode->position() :
Vector3{};
305 const auto viewMatrix = cameraNode ? cameraNode->worldTransform().inverse() :
Matrix4::identity();
306 const auto activeTarget = renderTarget ? renderTarget : camera->
renderTarget().get();
307 const int targetWidth = std::max(activeTarget ? activeTarget->width() :
_device->size().first, 1);
308 const int targetHeight = std::max(activeTarget ? activeTarget->height() :
_device->size().second, 1);
310 const auto clamp01 = [](
const float v) {
311 return std::clamp(v, 0.0f, 1.0f);
314 const float rectXNorm = clamp01(rect.
getX());
315 const float rectYNorm = clamp01(rect.
getY());
316 const float rectWNorm = clamp01(rect.
getZ());
317 const float rectHNorm = clamp01(rect.
getW());
318 const float rectTopNorm = clamp01(rectYNorm + rectHNorm);
320 int viewportX =
static_cast<int>(rectXNorm *
static_cast<float>(targetWidth));
322 int viewportY = targetHeight -
static_cast<int>(rectTopNorm *
static_cast<float>(targetHeight));
323 viewportX = std::clamp(viewportX, 0, std::max(targetWidth - 1, 0));
324 viewportY = std::clamp(viewportY, 0, std::max(targetHeight - 1, 0));
325 int viewportW = std::max(1,
static_cast<int>(rectWNorm *
static_cast<float>(targetWidth)));
326 int viewportH = std::max(1,
static_cast<int>(rectHNorm *
static_cast<float>(targetHeight)));
327 viewportW = std::clamp(viewportW, 1, targetWidth - viewportX);
328 viewportH = std::clamp(viewportH, 1, targetHeight - viewportY);
331 const float sxNorm = clamp01(scissorRect.
getX());
332 const float syNorm = clamp01(scissorRect.
getY());
333 const float swNorm = clamp01(scissorRect.
getZ());
334 const float shNorm = clamp01(scissorRect.
getW());
335 const float sTopNorm = clamp01(syNorm + shNorm);
336 int scissorX =
static_cast<int>(sxNorm *
static_cast<float>(targetWidth));
337 int scissorY = targetHeight -
static_cast<int>(sTopNorm *
static_cast<float>(targetHeight));
338 scissorX = std::clamp(scissorX, 0, std::max(targetWidth - 1, 0));
339 scissorY = std::clamp(scissorY, 0, std::max(targetHeight - 1, 0));
340 int scissorW = std::max(1,
static_cast<int>(swNorm *
static_cast<float>(targetWidth)));
341 int scissorH = std::max(1,
static_cast<int>(shNorm *
static_cast<float>(targetHeight)));
342 scissorW = std::clamp(scissorW, 1, targetWidth - scissorX);
343 scissorH = std::clamp(scissorH, 1, targetHeight - scissorY);
347 camera->
setAspectRatio(
static_cast<float>(viewportW) /
static_cast<float>(viewportH));
352 if (candidate && candidate->camera() == camera) {
353 cameraComponent = candidate;
359 float jitterX = 0.0f;
360 float jitterY = 0.0f;
361 const float jitter = std::max(camera->
jitter(), 0.0f);
363 const auto& offset = haltonSequence[
static_cast<size_t>(
_device->renderVersion() % haltonSequence.size())];
364 jitterX = jitter * (offset[0] * 2.0f - 1.0f) /
static_cast<float>(viewportW);
365 jitterY = jitter * (offset[1] * 2.0f - 1.0f) /
static_cast<float>(viewportH);
369 const auto viewProjection = projMatrix * viewMatrix;
373 const float oldVx =
_device->vx();
374 const float oldVy =
_device->vy();
375 const float oldVw =
_device->vw();
376 const float oldVh =
_device->vh();
377 const int oldSx =
_device->sx();
378 const int oldSy =
_device->sy();
379 const int oldSw =
_device->sw();
380 const int oldSh =
_device->sh();
383 static_cast<float>(viewportX),
384 static_cast<float>(viewportY),
385 static_cast<float>(viewportW),
386 static_cast<float>(viewportH)
388 _device->setScissor(scissorX, scissorY, scissorW, scissorH);
392 std::vector<ForwardDrawEntry*> drawEntries;
393 drawEntries.reserve(256);
395 const auto appendMeshInstance = [&](
MeshInstance* meshInstance) {
396 if (!meshInstance || !meshInstance->visible()) {
400 auto* mesh = meshInstance->mesh();
405 auto vertexBuffer = mesh->getVertexBuffer();
410 auto* entry = drawEntryPool.
allocate();
411 entry->meshInstance = meshInstance;
412 entry->material = meshInstance->material() ? meshInstance->material() : defaultMaterial.get();
413 if (!entry->material) {
416 if (entry->material->transparent() != transparent) {
420 const bool isSkyboxMaterial = entry->material->isSkybox();
421 const auto worldBounds = meshInstance->aabb();
423 _numDrawCallsCulled++;
427 entry->vertexBuffer = vertexBuffer;
428 entry->indexBuffer = mesh->getIndexBuffer();
429 entry->primitive = mesh->getPrimitive();
430 entry->sortKey = makeOpaqueSortKey(meshInstance);
432 auto* node = meshInstance->node();
433 if (node && !isSkyboxMaterial) {
434 const auto delta = worldBounds.center() - cameraPosition;
435 entry->distanceToCameraSq = delta.lengthSquared();
437 entry->distanceToCameraSq = 0.0f;
440 drawEntries.push_back(entry);
444 if (!renderComponent || !renderComponent->enabled()) {
451 if (renderComponent->entity() && !renderComponent->entity()->enabled()) {
455 const auto& componentLayers = renderComponent->layers();
456 if (std::find(componentLayers.begin(), componentLayers.end(), layer->
id()) == componentLayers.end()) {
460 for (
auto* meshInstance : renderComponent->meshInstances()) {
461 appendMeshInstance(meshInstance);
466 appendMeshInstance(meshInstance);
471 std::stable_sort(drawEntries.begin(), drawEntries.end(),
472 [](
const ForwardDrawEntry* a,
const ForwardDrawEntry* b) {
473 if (a->distanceToCameraSq == b->distanceToCameraSq) {
474 return a->sortKey < b->sortKey;
476 return a->distanceToCameraSq > b->distanceToCameraSq;
480 std::stable_sort(drawEntries.begin(), drawEntries.end(),
481 [](
const ForwardDrawEntry* a,
const ForwardDrawEntry* b) {
482 if (a->sortKey != b->sortKey) {
483 return a->sortKey < b->sortKey;
485 return a->distanceToCameraSq < b->distanceToCameraSq;
489 const auto sortEnd = std::chrono::high_resolution_clock::now();
490 _sortTime +=
static_cast<int>(std::chrono::duration_cast<std::chrono::milliseconds>(sortEnd - sortStart).count());
495 const auto ambientColor = _scene ? _scene->ambientLight() : Color(0.0f, 0.0f, 0.0f, 1.0f);
496 const auto fogParams = _scene ? _scene->fog() : FogParams{};
497 std::vector<LightDispatchEntry> directionalLights;
498 std::vector<LightDispatchEntry> localLights;
499 directionalLights.reserve(4);
500 localLights.reserve(8);
501 ShadowParams shadowParams{};
503 auto toRadians = [](
const float degrees) {
504 return degrees * (std::numbers::pi_v<float> / 180.0f);
507 auto makeGpuLight = [&](
const LightComponent* lightComponent, GpuLightData& lightData) {
508 if (!lightComponent) {
512 switch (lightComponent->type()) {
513 case LightType::LIGHTTYPE_DIRECTIONAL:
514 lightData.type = GpuLightType::Directional;
516 case LightType::LIGHTTYPE_SPOT:
517 lightData.type = GpuLightType::Spot;
519 case LightType::LIGHTTYPE_AREA_RECT:
520 lightData.type = GpuLightType::AreaRect;
521 lightData.areaHalfWidth = lightComponent->areaWidth() * 0.5f;
522 lightData.areaHalfHeight = lightComponent->areaHeight() * 0.5f;
525 const auto& wt = lightComponent->entity()->worldTransform();
526 Vector3 right(wt.getElement(0, 0), wt.getElement(1, 0), wt.getElement(2, 0));
527 if (right.lengthSquared() > 1e-8f) {
528 lightData.areaRight = right.normalized();
532 case LightType::LIGHTTYPE_OMNI:
533 case LightType::LIGHTTYPE_POINT:
535 lightData.type = GpuLightType::Point;
539 lightData.position = lightComponent->position();
540 lightData.direction = lightComponent->direction();
541 if (lightData.direction.lengthSquared() > 1e-8f) {
542 lightData.direction = lightData.direction.normalized();
544 lightData.direction = Vector3(0.0f, -1.0f, 0.0f);
546 lightData.color = lightComponent->color();
547 lightData.intensity = std::max(lightComponent->intensity(), 0.0f);
548 lightData.range = std::max(lightComponent->range(), 1e-4f);
549 lightData.innerConeCos = std::cos(toRadians(std::max(lightComponent->innerConeAngle(), 0.0f) * 0.5f));
550 lightData.outerConeCos = std::cos(toRadians(std::max(lightComponent->outerConeAngle(), 0.0f) * 0.5f));
551 if (lightData.innerConeCos < lightData.outerConeCos) {
552 lightData.innerConeCos = lightData.outerConeCos;
554 lightData.falloffModeLinear = lightComponent->falloffMode() == LightFalloff::LIGHTFALLOFF_LINEAR;
555 lightData.castShadows = lightComponent->castShadows();
558 for (
const auto* lightComponent : LightComponent::instances()) {
559 if (!lightComponent || !lightComponent->enabled()) {
562 if (layer && !lightComponent->rendersLayer(layer->id())) {
565 if (cameraComponent && layer && !cameraComponent->rendersLayer(layer->id())) {
569 GpuLightData lightData{};
570 makeGpuLight(lightComponent, lightData);
571 if (lightData.intensity <= 0.0f) {
575 if (lightData.castShadows && !shadowParams.enabled &&
576 lightData.type == GpuLightType::Directional) {
577 shadowParams.enabled =
true;
578 shadowParams.normalBias = lightComponent->shadowNormalBias();
579 shadowParams.strength = lightComponent->shadowStrength();
582 Light* sceneLight = lightComponent->light();
583 if (sceneLight && sceneLight->shadowMap()) {
584 shadowParams.shadowMap = sceneLight->shadowMap()->shadowTexture();
587 shadowParams.numCascades = sceneLight->numCascades();
588 shadowParams.cascadeBlend = sceneLight->cascadeBlend();
589 std::memcpy(shadowParams.shadowMatrixPalette,
590 sceneLight->shadowMatrixPalette().data(),
591 sizeof(shadowParams.shadowMatrixPalette));
592 std::memcpy(shadowParams.shadowCascadeDistances,
593 sceneLight->shadowCascadeDistances().data(),
594 sizeof(shadowParams.shadowCascadeDistances));
597 LightRenderData* rd = sceneLight->getRenderData(camera, 0);
598 if (rd && rd->shadowCamera && rd->shadowCamera->node()) {
599 shadowParams.viewProjection = rd->shadowCamera->projectionMatrix()
600 * rd->shadowCamera->node()->worldTransform().inverse();
606 shadowParams.bias = 0.0001f;
614 if (lightData.castShadows && lightData.type != GpuLightType::Directional) {
615 Light* sceneLight = lightComponent->light();
616 if (sceneLight && sceneLight->shadowMap() &&
617 shadowParams.localShadowCount < ShadowParams::kMaxLocalShadows) {
619 const int shadowIdx = shadowParams.localShadowCount;
620 lightData.shadowMapIndex = shadowIdx;
622 const bool isOmni = (sceneLight->type() == LightType::LIGHTTYPE_OMNI);
623 auto& ls = shadowParams.localShadows[shadowIdx];
624 ls.shadowMap = sceneLight->shadowMap()->shadowTexture();
630 Matrix4 rangePack = Matrix4::identity();
631 rangePack.setElement(0, 0, sceneLight->range());
632 ls.viewProjection = rangePack;
634 ls.viewProjection = sceneLight->shadowViewProjection();
636 ls.bias = sceneLight->shadowBias();
637 ls.normalBias = sceneLight->normalBias();
638 ls.intensity = sceneLight->shadowIntensity();
640 shadowParams.localShadowCount++;
644 lightData.castShadows =
false;
648 LightDispatchEntry dispatchEntry{};
649 dispatchEntry.light = lightData;
650 dispatchEntry.mask = lightComponent->mask();
652 if (dispatchEntry.light.type == GpuLightType::Directional) {
653 directionalLights.push_back(dispatchEntry);
655 localLights.push_back(dispatchEntry);
664 Vector3 skyDomeCenter(0, 0, 0);
666 if (_scene && _scene->sky() && _scene->sky()->type() != SKYTYPE_INFINITE && _scene->sky()->type() != SKYTYPE_ATMOSPHERE) {
667 skyDomeCenter = _scene->sky()->centerWorldPos();
670 _device->setEnvironmentUniforms(_scene ? _scene->envAtlas() :
nullptr,
671 _scene ? _scene->skyboxIntensity() : 1.0f,
672 static_cast<float>(_scene ? _scene->skyboxMip() : 0),
673 skyDomeCenter, isDome,
674 _scene ? _scene->skybox() :
nullptr);
678 if (clusteredEnabled && _worldClusters) {
680 std::vector<ClusterLightData> clusterLocalLights;
681 clusterLocalLights.reserve(localLights.size());
683 for (
const auto& dispatchEntry : localLights) {
684 const auto& ld = dispatchEntry.light;
686 if (ld.type == GpuLightType::AreaRect)
continue;
687 ClusterLightData lcd;
688 lcd.position = ld.position;
689 lcd.direction = ld.direction;
690 lcd.color = ld.color;
691 lcd.intensity = ld.intensity;
692 lcd.range = ld.range;
693 lcd.innerConeAngle = std::acos(std::clamp(ld.innerConeCos, -1.0f, 1.0f))
694 * (360.0f / std::numbers::pi_v<float>);
695 lcd.outerConeAngle = std::acos(std::clamp(ld.outerConeCos, -1.0f, 1.0f))
696 * (360.0f / std::numbers::pi_v<float>);
697 lcd.isSpot = (ld.type == GpuLightType::Spot);
698 lcd.falloffModeLinear = ld.falloffModeLinear;
699 clusterLocalLights.push_back(lcd);
706 BoundingBox cameraBounds(cameraPosition, Vector3(50.0f, 50.0f, 50.0f));
708 _worldClusters->update(clusterLocalLights, cameraBounds);
711 if (_worldClusters->lightCount() > 0) {
712 _device->setClusterBuffers(
713 _worldClusters->lightData(), _worldClusters->lightDataSize(),
714 _worldClusters->cellData(), _worldClusters->cellDataSize());
717 const auto& bMin = _worldClusters->boundsMin();
718 const auto bRange = _worldClusters->boundsRange();
719 const auto cellsBySize = _worldClusters->cellsCountByBoundsSize();
720 const auto& cfg = _worldClusters->config();
722 const float boundsMinArr[3] = {bMin.getX(), bMin.getY(), bMin.getZ()};
723 const float boundsRangeArr[3] = {bRange.getX(), bRange.getY(), bRange.getZ()};
724 const float cellsBySizeArr[3] = {cellsBySize.getX(), cellsBySize.getY(), cellsBySize.getZ()};
726 _device->setClusterGridParams(boundsMinArr, boundsRangeArr, cellsBySizeArr,
727 cfg.cellsX, cfg.cellsY, cfg.cellsZ, cfg.maxLightsPerCell,
728 _worldClusters->lightCount());
736 std::vector<GpuLightData> cachedGpuLights;
737 cachedGpuLights.reserve(8);
740 auto buildFilteredLights = [&](uint32_t mask, std::vector<GpuLightData>& out) {
743 for (
const auto& dispatchEntry : directionalLights) {
744 if ((dispatchEntry.mask & mask) == 0u)
continue;
745 out.push_back(dispatchEntry.light);
746 if (out.size() >= 8)
break;
750 for (
const auto& dispatchEntry : localLights) {
751 if (out.size() >= 8)
break;
752 if (dispatchEntry.light.type != GpuLightType::AreaRect)
continue;
753 if ((dispatchEntry.mask & mask) == 0u)
continue;
754 out.push_back(dispatchEntry.light);
758 if (!clusteredEnabled) {
759 for (
const auto& dispatchEntry : localLights) {
760 if (out.size() >= 8)
break;
761 if ((dispatchEntry.mask & mask) == 0u)
continue;
762 if (dispatchEntry.light.type == GpuLightType::AreaRect)
continue;
763 out.push_back(dispatchEntry.light);
769 buildFilteredLights(MASK_AFFECT_DYNAMIC, cachedGpuLights);
772 const Material* lastCullMaterial =
nullptr;
773 CullMode cachedCullMode = CullMode::CULLFACE_BACK;
775 for (
const auto* entry : drawEntries) {
776 const Material* boundMaterial = entry->material ? entry->material : defaultMaterial.get();
777 const bool isDynBatch = entry->meshInstance && entry->meshInstance->isDynamicBatch();
778 programLibrary->bindMaterial(_device, boundMaterial, transparent, isDynBatch);
781 const uint32_t drawLightMask = (entry->meshInstance ? entry->meshInstance->mask() : MASK_AFFECT_DYNAMIC);
782 if (drawLightMask != cachedLightMask) {
783 buildFilteredLights(drawLightMask, cachedGpuLights);
784 cachedLightMask = drawLightMask;
789 const bool drawReceivesShadow = (!entry->meshInstance || entry->meshInstance->receiveShadow());
790 if (drawReceivesShadow) {
791 _device->setLightingUniforms(ambientColor, cachedGpuLights, cameraPosition,
true,
792 (_scene ? _scene->exposure() : 1.0f), fogParams, shadowParams,
793 (_scene ? _scene->toneMapping() : 0));
795 ShadowParams noShadow;
796 noShadow.enabled =
false;
797 _device->setLightingUniforms(ambientColor, cachedGpuLights, cameraPosition,
true,
798 (_scene ? _scene->exposure() : 1.0f), fogParams, noShadow,
799 (_scene ? _scene->toneMapping() : 0));
804 if (boundMaterial != lastCullMaterial) {
805 cachedCullMode = resolveMaterialCullMode(boundMaterial);
806 lastCullMaterial = boundMaterial;
808 const auto cullMode = applyNodeScaleFlip(cachedCullMode,
809 entry->meshInstance ? entry->meshInstance->node() :
nullptr);
810 _device->setCullMode(cullMode);
812 _device->setVertexBuffer(entry->vertexBuffer, 0);
816 const auto& instData = entry->meshInstance ? entry->meshInstance->instancingData() : MeshInstance::InstancingData{};
818 if (instData.indirectArgsBuffer && instData.indirectSlot >= 0 && instData.compactedVertexBuffer) {
822 _device->setVertexBuffer(instData.compactedVertexBuffer, 5);
823 _device->setIndirectDrawBuffer(instData.indirectArgsBuffer);
825 _device->setTransformUniforms(viewProjection, Matrix4::identity());
826 _device->draw(entry->primitive, entry->indexBuffer, 0, instData.indirectSlot,
true,
true);
827 }
else if (instData.vertexBuffer && instData.count > 0) {
829 _device->setVertexBuffer(instData.vertexBuffer, 5);
832 if (boundMaterial && boundMaterial->isSkybox()) {
833 if (_scene && _scene->sky() && _scene->sky()->type() != SKYTYPE_INFINITE && _scene->sky()->type() != SKYTYPE_ATMOSPHERE) {
834 modelMatrix = entry->meshInstance && entry->meshInstance->node()
835 ? entry->meshInstance->node()->worldTransform()
836 : Matrix4::identity();
838 modelMatrix = Matrix4::translation(cameraPosition.getX(), cameraPosition.getY(), cameraPosition.getZ());
841 modelMatrix = Matrix4::identity();
843 _device->setTransformUniforms(viewProjection, modelMatrix);
844 _device->draw(entry->primitive, entry->indexBuffer, instData.count, -1,
true,
true);
845 }
else if (isDynBatch) {
850 auto* sbi = entry->meshInstance->skinBatchInstance();
852 _device->setDynamicBatchPalette(sbi->paletteData(), sbi->paletteSizeBytes());
854 _device->setTransformUniforms(viewProjection, Matrix4::identity());
855 _device->draw(entry->primitive, entry->indexBuffer, 1, -1,
true,
true);
859 if (boundMaterial && boundMaterial->isSkybox()) {
860 if (_scene && _scene->sky() && _scene->sky()->type() != SKYTYPE_INFINITE && _scene->sky()->type() != SKYTYPE_ATMOSPHERE) {
861 modelMatrix = entry->meshInstance && entry->meshInstance->node()
862 ? entry->meshInstance->node()->worldTransform()
863 : Matrix4::identity();
865 modelMatrix = Matrix4::translation(cameraPosition.getX(), cameraPosition.getY(), cameraPosition.getZ());
868 modelMatrix = (entry->meshInstance && entry->meshInstance->node())
869 ? entry->meshInstance->node()->worldTransform()
870 : Matrix4::identity();
872 _device->setTransformUniforms(viewProjection, modelMatrix);
873 _device->draw(entry->primitive, entry->indexBuffer, 1, -1,
true,
true);
879 _device->setViewport(oldVx, oldVy, oldVw, oldVh);
880 _device->setScissor(oldSx, oldSy, oldSw, oldSh);