Files
EVEngine/test/atmosphere_volume.cpp

188 lines
7.3 KiB
C++

#include "graphics/AtmosphereVolume.h"
#include "graphics/AtmosphereClipmap.h"
#include "graphics/FogVolume.h"
#include "graphics/VolumeDensityGraph.h"
#include "graphics/SparseVolumeTexture.h"
#include "zeroerr/unittest.h"
#include "zeroerr/assert.h"
#include <cmath>
using eve::graphics::AtmosphereVolume;
using eve::graphics::AtmosphereClipmap;
using eve::graphics::FogVolume;
using eve::graphics::VolumetricLight;
using eve::graphics::VolumeDensityGraph;
using eve::graphics::SparseVolumeTexture;
TEST_CASE("atmosphereVolume.logarithmicSlicesRoundTrip") {
AtmosphereVolume volume;
volume.resize(4, 3, 32);
volume.setDepthRange(0.1f, 1000.f);
for (int z = 0; z < volume.getDepth(); ++z)
CHECK(volume.sliceForDistance(volume.sliceDistance(z)) == z);
}
TEST_CASE("atmosphereVolume.uniformBeerLambert") {
AtmosphereVolume volume;
volume.resize(1, 1, 64);
volume.setDepthRange(1.f, 101.f);
for (int z = 0; z < volume.getDepth(); ++z) {
volume.at(0, 0, z).extinction = 0.02f;
volume.at(0, 0, z).scattering = glm::vec3(0.02f);
}
volume.integrate(glm::vec3(1.f));
const float actual = volume.integratedAt(0, 0, 63).a;
const float expected = std::exp(-0.02f * (volume.sliceDistance(63) - 1.f));
CHECK(std::fabs(actual - expected) < 1e-5f);
}
TEST_CASE("atmosphereVolume.heightFogFallsOffUpward") {
AtmosphereVolume volume;
volume.resize(2, 8, 4);
volume.injectHeightFog(0.2f, glm::vec3(0.8f), 0.f, 0.5f, 0.f, 8.f);
CHECK(volume.at(0, 0, 0).extinction > volume.at(0, 7, 0).extinction);
}
TEST_CASE("atmosphereVolume.frustumHeightFogUsesReconstructedWorldHeight") {
AtmosphereVolume volume;
volume.resize(1, 2, 4);
volume.setDepthRange(0.1f, 1.f);
glm::mat4 invViewProj(1.f);
invViewProj[1] = glm::vec4(0.f, 0.f, 1.f, 0.f);
invViewProj[2] = glm::vec4(0.f, 1.f, 0.f, 0.f);
volume.injectHeightFogFrustum(0.2f, glm::vec3(0.8f), 0.f, 2.f, -2.f, 2.f, invViewProj);
CHECK(std::fabs(volume.at(0, 0, 0).extinction - volume.at(0, 1, 0).extinction) < 1e-6f);
CHECK(volume.at(0, 0, 0).extinction > volume.at(0, 0, 3).extinction);
}
TEST_CASE("atmosphereVolume.directionalShadowReducesScattering") {
AtmosphereVolume lit;
lit.resize(1, 1, 8);
lit.setDepthRange(1.f, 20.f);
for (int z = 0; z < 8; ++z) {
lit.at(0, 0, z).extinction = 0.1f;
lit.at(0, 0, z).scattering = glm::vec3(0.08f);
}
AtmosphereVolume shadowed = lit;
for (int z = 0; z < 8; ++z) shadowed.setLightVisibility(0, 0, z, 0.f);
lit.integrate(glm::vec3(1.f));
shadowed.integrate(glm::vec3(1.f));
CHECK(lit.integratedAt(0, 0, 7).r > shadowed.integratedAt(0, 0, 7).r);
}
TEST_CASE("atmosphereVolume.temporalHistoryRejectsLightingDiscontinuity") {
AtmosphereVolume current;
current.resize(2, 1, 2);
current.setDepthRange(1.f, 4.f);
for (int z = 0; z < 2; ++z) {
current.at(0, 0, z).emissive = glm::vec3(2.f);
current.at(1, 0, z).emissive = glm::vec3(0.2f);
}
current.integrate(glm::vec3(0.f));
AtmosphereVolume history = current;
history.clear();
history.integrate(glm::vec3(0.f));
CHECK(current.blendHistory(history, 0.9f, 0.2f) == 4);
CHECK(current.integratedAt(0, 0, 1).r > 0.f);
}
TEST_CASE("atmosphereVolume.localVolumesAddAndCarveMedia") {
AtmosphereVolume volume;
volume.resize(8, 8, 8);
volume.injectHeightFog(0.1f, glm::vec3(1.f), 0.f, 0.f, -4.f, 4.f);
FogVolume add;
add.setShape("sphere");
add.setSize(4.f, 4.f, 4.f);
add.setExtinction(0.2f);
add.setEdgeFalloff(0.f);
volume.injectLocalVolume(add, glm::vec3(-4.f), glm::vec3(4.f));
CHECK(volume.at(4, 4, 4).extinction > volume.at(0, 0, 0).extinction);
FogVolume carve = add;
carve.setExtinction(-1.f);
volume.injectLocalVolume(carve, glm::vec3(-4.f), glm::vec3(4.f));
CHECK(volume.at(4, 4, 4).extinction == 0.f);
CHECK(volume.at(0, 0, 0).extinction > 0.f);
}
TEST_CASE("fogVolume.noiseIsDeterministicAndSpatiallyVaried") {
FogVolume volume;
volume.setShape("box");
volume.setSize(20.f, 20.f, 20.f);
volume.setExtinction(1.f);
volume.setEdgeFalloff(0.f);
volume.setNoise(1.f, 0.7f, 42);
const float first = volume.sampleExtinction(glm::vec3(0.25f, 0.5f, 0.75f));
CHECK(first == volume.sampleExtinction(glm::vec3(0.25f, 0.5f, 0.75f)));
CHECK(std::fabs(first - volume.sampleExtinction(glm::vec3(1.75f, 0.5f, 0.75f))) > 1e-4f);
volume.setNoise(0.f, 0.7f, 42);
CHECK(std::fabs(volume.sampleExtinction(glm::vec3(0.25f, 0.5f, 0.75f)) - 1.f) < 1e-6f);
}
TEST_CASE("atmosphereVolume.localLightAndTransparentDepthQuery") {
AtmosphereVolume volume;
volume.resize(4, 4, 16);
volume.setDepthRange(0.5f, 30.f);
volume.injectHeightFog(0.04f, glm::vec3(0.8f), 0.f, 0.f, -4.f, 4.f);
VolumetricLight light;
light.position = glm::vec3(0.f);
light.radius = 6.f;
light.color = glm::vec3(1.f, 0.2f, 0.1f);
volume.integrateLocalLights({light}, glm::vec3(-8.f), glm::vec3(8.f), glm::vec3(0.02f));
const glm::vec4 nearFog = volume.sampleIntegrated(0.5f, 0.5f, 1.f);
const glm::vec4 farFog = volume.sampleIntegrated(0.5f, 0.5f, 20.f);
CHECK(farFog.a < nearFog.a);
CHECK(farFog.r > farFog.b);
}
TEST_CASE("atmosphereClipmap.snapsCentersAndChoosesFinestLevel") {
AtmosphereClipmap clipmap;
clipmap.configure(3, 8, 8.f);
clipmap.updateCenter(glm::vec3(1.1f, 0.f, 0.f));
const glm::vec3 firstCenter = clipmap.getLevel(0).center;
clipmap.updateCenter(glm::vec3(1.9f, 0.f, 0.f));
CHECK(clipmap.getLevel(0).center == firstCenter);
CHECK(clipmap.levelForPosition(glm::vec3(4.f, 0.f, 0.f)) == 0);
CHECK(clipmap.levelForPosition(glm::vec3(12.f, 0.f, 0.f)) == 1);
CHECK(clipmap.levelForPosition(glm::vec3(28.f, 0.f, 0.f)) == 2);
CHECK(clipmap.levelForPosition(glm::vec3(80.f, 0.f, 0.f)) == -1);
}
TEST_CASE("volumeDensityGraph.composesShapeNoiseAndHeight") {
VolumeDensityGraph graph;
const int sphere = graph.addNode(VolumeDensityGraph::Op::sphere, -1, -1,
glm::vec4(0.f, 0.f, 0.f, 4.f));
const int noise = graph.addNode(VolumeDensityGraph::Op::noise, -1, -1,
glm::vec4(2.f, 0.2f, 0.f, 7.f));
const int shaped = graph.addNode(VolumeDensityGraph::Op::multiply, sphere, noise);
graph.setOutput(shaped);
CHECK(graph.evaluate(glm::vec3(0.f), 1.f) > 0.f);
CHECK(graph.evaluate(glm::vec3(10.f), 1.f) == 0.f);
AtmosphereVolume volume;
volume.resize(8, 8, 8);
volume.injectDensityGraph(graph, glm::vec3(-4.f), glm::vec3(4.f), 0.2f,
glm::vec3(0.8f), 1.f);
CHECK(volume.at(4, 4, 4).extinction > 0.f);
}
TEST_CASE("sparseVolumeTexture.allocatesOnlyOccupiedBricksAndInjects") {
VolumeDensityGraph graph;
graph.addNode(VolumeDensityGraph::Op::sphere, -1, -1, glm::vec4(0.f, 0.f, 0.f, 2.f));
SparseVolumeTexture sparse;
sparse.resize(32, 32, 32, 8);
sparse.bake(graph, glm::vec3(-16.f), glm::vec3(16.f), 0.2f, glm::vec3(0.7f));
CHECK(sparse.getBrickCount() > 0);
CHECK(sparse.getBrickCount() < 64);
CHECK(sparse.sample(0.f, 0.f, 0.f).extinction == 0.f);
CHECK(sparse.sample(0.5f, 0.5f, 0.5f).extinction > 0.f);
AtmosphereVolume volume;
volume.resize(16, 16, 16);
volume.injectSparseVolume(sparse);
CHECK(volume.at(8, 8, 8).extinction > 0.f);
CHECK(volume.at(0, 0, 0).extinction == 0.f);
}