#include "common/ECS.h" #include "common/ProcgenProbeSink.h" #include "graphics/DiffuseLightProbeRegistry.h" #include "graphics/Canvas.h" #include "graphics/Graphics.h" #include "graphics/Mesh.h" #include "graphics/RenderSystem3D.h" #include "image/ImageData.h" #include #include #include #include #include using namespace eve; using namespace eve::graphics; TEST_CASE("graphics.lightProbe.generatedBatchBlendsAndRemovesAtomically") { ProcgenProbeDesc red; red.sourcePointId = 1; red.type = 1; red.x = -1.f; red.extentX = red.extentY = red.extentZ = 4.f; red.irradianceR = 1.f; red.irradianceG = red.irradianceB = 0.f; ProcgenProbeDesc blue = red; blue.sourcePointId = 2; blue.x = 1.f; blue.irradianceR = 0.f; blue.irradianceB = 1.f; auto& registry = DiffuseLightProbeRegistry::instance(); registry.replaceBatch("light-probe-test", {red, blue}); auto sample = registry.sample(glm::vec3(0.f), 4); REQUIRE(sample.ok()); const glm::vec3 irradiance = glm::vec3(sample.value().coefficients[0]) * 0.2820947918f; CHECK(irradiance.r > 0.49f); CHECK(irradiance.r < 0.51f); CHECK(irradiance.b > 0.49f); CHECK(irradiance.b < 0.51f); CHECK(!registry.sample(glm::vec3(20.f), 4).ok()); registry.removeBatch("light-probe-test"); CHECK(!registry.sample(glm::vec3(0.f), 4).ok()); } TEST_CASE("graphics.lightProbe.proxyVolumeSelectsBracketingGridCell") { std::vector probes; std::uint64_t id = 100; for (float z : {-1.f, 1.f}) for (float y : {-1.f, 1.f}) for (float x : {-2.f, 0.f, 2.f}) { ProcgenProbeDesc probe; probe.sourcePointId = id++; probe.type = 1; probe.x = x; probe.y = y; probe.z = z; probe.extentX = probe.extentY = probe.extentZ = 2.f; probe.irradianceR = (x + 2.f) * 0.25f; probes.push_back(probe); } auto& registry = DiffuseLightProbeRegistry::instance(); registry.replaceBatch("regular-grid-cell-test", probes); auto selected = registry.selectVolume(glm::vec3(0.5f, 0.25f, -0.25f)); REQUIRE(selected.ok()); CHECK(selected.value().trilinearCell); CHECK_EQ(selected.value().count, 8); for (int index = 0; index < selected.value().count; ++index) { const glm::vec3 p(selected.value().positions[static_cast(index)]); CHECK((p.x == 0.f || p.x == 2.f)); CHECK((p.y == -1.f || p.y == 1.f)); CHECK((p.z == -1.f || p.z == 1.f)); } registry.removeBatch("regular-grid-cell-test"); } TEST_CASE("graphics.lightProbe.incompleteProxyGridUsesSparseFallback") { std::vector probes; std::uint64_t id = 200; for (const glm::vec3 position : {glm::vec3(-1.f, -1.f, 0.f), glm::vec3(1.f, -1.f, 0.f), glm::vec3(-1.f, 1.f, 0.f)}) { ProcgenProbeDesc probe; probe.sourcePointId = id++; probe.type = 1; probe.x = position.x; probe.y = position.y; probe.z = position.z; probe.extentX = probe.extentY = probe.extentZ = 2.f; probes.push_back(probe); } auto& registry = DiffuseLightProbeRegistry::instance(); registry.replaceBatch("incomplete-grid-test", probes); auto selected = registry.selectVolume(glm::vec3(0.f)); REQUIRE(selected.ok()); CHECK(!selected.value().trilinearCell); CHECK_EQ(selected.value().count, 3); registry.removeBatch("incomplete-grid-test"); } TEST_CASE("graphics.lightProbe.customProvidedRejectsInvalidAndOwnsValue") { auto* renderable = Renderable3D::create(); REQUIRE(renderable); CHECK(!renderable->setCustomLightProbe(-1.f, 0.f, 0.f).ok()); CHECK(!renderable->setCustomLightProbe(std::numeric_limits::infinity(), 0.f, 0.f).ok()); REQUIRE(renderable->setCustomLightProbe(0.25f, 0.5f, 0.75f).ok()); CHECK(renderable->meshRenderer()->customLightProbe); CHECK(std::abs(renderable->meshRenderer()->customLightProbeSh[0].x * 0.2820947918f - 0.25f) < 1e-5f); REQUIRE(renderable->setCustomLightProbeCoefficient(3, 0.5f, -0.25f, 0.1f).ok()); CHECK(glm::vec3(renderable->meshRenderer()->customLightProbeSh[3]) == glm::vec3(0.5f, -0.25f, 0.1f)); CHECK(!renderable->setCustomLightProbeCoefficient(9, 0.f, 0.f, 0.f).ok()); renderable->clearCustomLightProbe(); CHECK(!renderable->meshRenderer()->customLightProbe); ecs::DestroyEntity(renderable); } TEST_CASE("graphics.lightProbe.customProvidedChangesRenderedPixels") { auto* gfx = Graphics::create(); REQUIRE(gfx); gfx->initHeadless(96, 96); Canvas* canvas = gfx->newCanvas(96, 96); REQUIRE(canvas); const float positions[] = {-0.8f, -0.7f, 0.f, 0.8f, -0.7f, 0.f, 0.f, 0.8f, 0.f}; const float normals[] = {0.f, 0.f, 1.f, 0.f, 0.f, 1.f, 0.f, 0.f, 1.f}; const uint32_t indices[] = {0, 1, 2, 2, 1, 0}; Mesh* mesh = gfx->newMeshFromArrays(positions, nullptr, normals, 3, indices, 6); REQUIRE(mesh); auto* camera = Camera3D::createCamera(); camera->setEye(0.f, 0.f, 3.f); camera->setTarget(0.f, 0.f, 0.f); camera->setAmbient(0.f, 0.f, 0.f); auto* renderable = Renderable3D::create(); renderable->setMeshLod(0, mesh); REQUIRE(renderable->setMeshLodRendererState(0, 0, 1, true, 1, true, 4, 0).ok()); std::array totals{}; for (int pass = 0; pass < 2; ++pass) { REQUIRE(renderable->setCustomLightProbe(pass == 0 ? 1.f : 0.f, 0.f, pass == 0 ? 0.f : 1.f).ok()); RenderSystem3D::renderToCanvas(*gfx, canvas, camera); std::unique_ptr pixels(canvas->newImageData()); REQUIRE(pixels); const auto* data = static_cast(pixels->getData()); unsigned long long red = 0, blue = 0; for (size_t i = 0; i < 96u * 96u * 4u; i += 4) { red += data[i]; blue += data[i + 2]; } if (pass == 0) { totals[0] = red; totals[1] = blue; } else { totals[2] = blue; CHECK(red < totals[0]); } } CHECK(totals[0] > totals[1] + 1000); CHECK(totals[2] > totals[1] + 1000); unsigned long long directionalRed[2]{}; for (int pass = 0; pass < 2; ++pass) { REQUIRE(renderable->setCustomLightProbe(0.45f, 0.f, 0.f).ok()); REQUIRE(renderable->setCustomLightProbeCoefficient(2, pass == 0 ? 0.35f : -0.35f, 0.f, 0.f).ok()); RenderSystem3D::renderToCanvas(*gfx, canvas, camera); std::unique_ptr pixels(canvas->newImageData()); REQUIRE(pixels); const auto* data = static_cast(pixels->getData()); for (size_t i = 0; i < 96u * 96u * 4u; i += 4) directionalRed[pass] += data[i]; } CHECK(directionalRed[0] > directionalRed[1] + 1000); ProcgenProbeDesc leftProbe; leftProbe.sourcePointId = 10; leftProbe.type = 1; leftProbe.x = -0.55f; leftProbe.extentX = leftProbe.extentY = leftProbe.extentZ = 2.f; leftProbe.irradianceR = 1.f; leftProbe.irradianceG = leftProbe.irradianceB = 0.f; ProcgenProbeDesc rightProbe = leftProbe; rightProbe.sourcePointId = 11; rightProbe.x = 0.55f; rightProbe.irradianceR = 0.f; rightProbe.irradianceB = 1.f; DiffuseLightProbeRegistry::instance().replaceBatch("pixel-volume-test", {leftProbe, rightProbe}); REQUIRE(renderable->setMeshLodRendererState(0, 0, 1, true, 1, true, 2, 0).ok()); RenderSystem3D::renderToCanvas(*gfx, canvas, camera); std::unique_ptr volumePixels(canvas->newImageData()); REQUIRE(volumePixels); const auto* volumeData = static_cast(volumePixels->getData()); long long halfDominance[2]{}; for (int y = 0; y < 96; ++y) for (int x = 0; x < 96; ++x) { const size_t offset = static_cast(y * 96 + x) * 4; halfDominance[x >= 48] += static_cast(volumeData[offset]) - volumeData[offset + 2]; } CHECK(std::abs(halfDominance[0]) > 1000); CHECK(std::abs(halfDominance[1]) > 1000); CHECK(halfDominance[0] * halfDominance[1] < 0); DiffuseLightProbeRegistry::instance().removeBatch("pixel-volume-test"); ecs::DestroyEntity(renderable); ecs::DestroyEntity(camera); }