643 lines
20 KiB
C++
643 lines
20 KiB
C++
#include "zeroerr/assert.h"
|
|
#include "zeroerr/unittest.h"
|
|
#include "Fixtures.h"
|
|
|
|
#include <SDL2/SDL.h>
|
|
#include <cmath>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "graphics/AmbientOcclusion.h"
|
|
#include "graphics/AntiAliasing.h"
|
|
#include "graphics/Canvas.h"
|
|
#include "graphics/DrawItem2D.h"
|
|
#include "graphics/Font.h"
|
|
#include "graphics/GBuffer.h"
|
|
#include "graphics/GlobalIllumination.h"
|
|
#include "graphics/Graphics.h"
|
|
#include "graphics/Grass.h"
|
|
#include "graphics/Light.h"
|
|
#include "graphics/Material.h"
|
|
#include "graphics/Mesh.h"
|
|
#include "graphics/Outline.h"
|
|
#include "graphics/Quad.h"
|
|
#include "graphics/RenderControl.h"
|
|
#include "graphics/RenderSystem.h"
|
|
#include "graphics/RenderSystem3D.h"
|
|
#include "graphics/ScreenSpaceReflection.h"
|
|
#include "graphics/Shader.h"
|
|
#include "graphics/Texture.h"
|
|
#include "graphics/Volumetric.h"
|
|
#include "graphics/Water.h"
|
|
#include "graphics/Waterfall.h"
|
|
#include "window/Window.h"
|
|
|
|
#include <glm/gtc/matrix_transform.hpp>
|
|
// Color lives in eve::graphics (see graphics/Canvas.h); keep the unqualified form.
|
|
using eve::graphics::Color;
|
|
|
|
using namespace eve::graphics;
|
|
|
|
namespace {
|
|
|
|
|
|
Texture *makeSolid(Graphics *gfx, uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255) {
|
|
const uint8_t px[4] = {r, g, b, a};
|
|
return gfx->newTexture(1, 1, px);
|
|
}
|
|
|
|
float luma(const Color &c) { return (c.r + c.g + c.b) / 3.f; }
|
|
|
|
/** Hold a textured canvas / texture on the swapchain for ~1s. */
|
|
void previewTexture(Graphics *gfx, Texture *tex, int ms = 1000) {
|
|
gfx->setBackgroundColorRGBA(0.05f, 0.05f, 0.08f, 1.f);
|
|
const int frames = (ms >= 16) ? (ms / 16) : 1;
|
|
for (int i = 0; i < frames; ++i) {
|
|
gfx->clearScreen();
|
|
gfx->drawTexturedRectRGBA(tex, 0, 0, float(gfx->getWidth()), float(gfx->getHeight()), 1, 1, 1,
|
|
1);
|
|
gfx->present();
|
|
SDL_Event e;
|
|
while (SDL_PollEvent(&e)) {
|
|
if (e.type == SDL_QUIT) return;
|
|
}
|
|
SDL_Delay(16);
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("volumetric.qualityPresets") {
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx);
|
|
|
|
Volumetric *raw = gfx->newVolumetric();
|
|
REQUIRE(raw != nullptr);
|
|
REQUIRE(raw->getShader() != nullptr);
|
|
std::unique_ptr<Volumetric> vol(raw);
|
|
|
|
vol->setQuality("low");
|
|
CHECK(vol->getQuality() == "low");
|
|
CHECK(vol->getSampleCount() == 16);
|
|
CHECK(vol->getDownscale() == 4.f);
|
|
CHECK(vol->resolutionFor(400) == 100);
|
|
|
|
vol->setQuality("medium");
|
|
CHECK(vol->getSampleCount() == 48);
|
|
CHECK(vol->getDownscale() == 2.f);
|
|
CHECK(vol->resolutionFor(400) == 200);
|
|
|
|
vol->setQuality("high");
|
|
CHECK(vol->getSampleCount() == 96);
|
|
CHECK(vol->getDownscale() == 1.f);
|
|
CHECK(vol->resolutionFor(400) == 400);
|
|
|
|
vol->setQuality("unknown");
|
|
CHECK(vol->getQuality() == "medium");
|
|
}
|
|
|
|
TEST_CASE("volumetric.drawableOcclusionFlags") {
|
|
Texture tex;
|
|
CHECK(tex.getCastOcclusion() == true);
|
|
tex.setCastOcclusion(false);
|
|
CHECK(tex.getCastOcclusion() == false);
|
|
|
|
Mesh mesh;
|
|
CHECK(mesh.getCastOcclusion() == true);
|
|
mesh.setCastOcclusion(false);
|
|
CHECK(mesh.getCastOcclusion() == false);
|
|
|
|
auto *r = Renderable2D::create();
|
|
CHECK(r->getCastOcclusion() == true);
|
|
r->setCastOcclusion(false);
|
|
CHECK(r->getCastOcclusion() == false);
|
|
|
|
auto *r3 = Renderable3D::create();
|
|
CHECK(r3->getCastOcclusion() == true);
|
|
r3->setCastOcclusion(false);
|
|
CHECK(r3->getCastOcclusion() == false);
|
|
}
|
|
|
|
TEST_CASE("volumetric.occlusionMapAndScatter") {
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx, 256, 192);
|
|
gfx->setScreenReadbackEnabled(true);
|
|
|
|
Volumetric *raw = gfx->newVolumetric();
|
|
REQUIRE(raw != nullptr);
|
|
std::unique_ptr<Volumetric> vol(raw);
|
|
vol->setQuality("medium");
|
|
vol->setShaftColor(1.f, 0.9f, 0.7f);
|
|
vol->setFogColor(0.4f, 0.5f, 0.7f);
|
|
vol->setIntensity(1.2f);
|
|
|
|
const int ow = vol->resolutionFor(gfx->getWidth());
|
|
const int oh = vol->resolutionFor(gfx->getHeight());
|
|
Canvas *occ = gfx->newCanvas(ow, oh);
|
|
REQUIRE(occ != nullptr);
|
|
|
|
gfx->setCanvas(occ);
|
|
vol->beginOcclusionMap(gfx, float(ow) * 0.75f, float(oh) * 0.2f, 18.f);
|
|
// Occluder between light and bottom of screen.
|
|
vol->drawOccluderSolid(gfx, float(ow) * 0.35f, float(oh) * 0.35f, float(ow) * 0.3f,
|
|
float(oh) * 0.2f);
|
|
gfx->setCanvas(nullptr);
|
|
|
|
Texture *occTex = occ->getTexture();
|
|
REQUIRE(occTex != nullptr);
|
|
|
|
Canvas *shafts = gfx->newCanvas(gfx->getWidth(), gfx->getHeight());
|
|
vol->scatterTo(gfx, occTex, shafts);
|
|
|
|
// Warm present path so offscreen content is flushed / readable.
|
|
gfx->setBackgroundColorRGBA(0.05f, 0.05f, 0.08f, 1.f);
|
|
gfx->clearScreen();
|
|
gfx->drawTexturedRectRGBA(shafts->getTexture(), 0, 0, float(gfx->getWidth()),
|
|
float(gfx->getHeight()), 1, 1, 1, 1);
|
|
gfx->present();
|
|
gfx->present();
|
|
|
|
// Shafts should brighten somewhere below the light (radial blur direction).
|
|
float maxL = 0.f;
|
|
for (int y = 0; y < gfx->getHeight(); y += 4) {
|
|
for (int x = 0; x < gfx->getWidth(); x += 4) {
|
|
maxL = std::max(maxL, luma(gfx->getPixel(x, y)));
|
|
}
|
|
}
|
|
CHECK(maxL > 0.05f);
|
|
|
|
previewTexture(gfx, shafts->getTexture());
|
|
win->close();
|
|
}
|
|
|
|
TEST_CASE("volumetric.applyFromScene") {
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx, 200, 150);
|
|
gfx->setScreenReadbackEnabled(true);
|
|
|
|
Volumetric *raw = gfx->newVolumetric();
|
|
REQUIRE(raw != nullptr);
|
|
std::unique_ptr<Volumetric> vol(raw);
|
|
vol->setQuality("low");
|
|
vol->setLightScreenUV(0.8f, 0.15f);
|
|
vol->setFloat("dustAmount", 0.4f);
|
|
vol->setFloat("fogAmount", 0.3f);
|
|
|
|
Canvas *scene = gfx->newCanvas(gfx->getWidth(), gfx->getHeight());
|
|
gfx->setCanvas(scene);
|
|
gfx->clear(Color(0.08f, 0.09f, 0.12f, 1.f), std::nullopt, std::nullopt);
|
|
// Bright "sun" disc + dark occluder.
|
|
gfx->drawSolidRect(150, 10, 30, 30, Color(1.f, 0.95f, 0.8f, 1.f));
|
|
gfx->drawSolidRect(70, 50, 50, 40, Color(0.02f, 0.02f, 0.03f, 1.f));
|
|
gfx->setCanvas(nullptr);
|
|
|
|
Canvas *out = gfx->newCanvas(gfx->getWidth(), gfx->getHeight());
|
|
vol->applyFromSceneTo(gfx, scene->getTexture(), out);
|
|
|
|
// Read from the destination canvas (avoid swapchain readback variance).
|
|
const Color nearLight = out->getPixel(170, 25);
|
|
const Color farCorner = out->getPixel(10, 140);
|
|
CHECK(luma(nearLight) > luma(farCorner));
|
|
CHECK(luma(nearLight) > 0.2f);
|
|
|
|
previewTexture(gfx, out->getTexture());
|
|
win->close();
|
|
}
|
|
|
|
TEST_CASE("volumetric.drawOcclusionRespectsFlag") {
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx, 128, 128);
|
|
|
|
Texture *tex = makeSolid(gfx, 255, 255, 255);
|
|
tex->setCastOcclusion(false);
|
|
|
|
Canvas *c = gfx->newCanvas(64, 64);
|
|
gfx->setCanvas(c);
|
|
// Paint a bright plate explicitly (pending clear alone may not flush).
|
|
gfx->drawSolidRect(0, 0, 64, 64, Color(0.9f, 0.9f, 0.9f, 1.f));
|
|
glm::mat4 m(1.f);
|
|
m[3][0] = 10.f;
|
|
m[3][1] = 10.f;
|
|
m[0][0] = 40.f;
|
|
m[1][1] = 40.f;
|
|
gfx->drawOcclusion(tex, m); // should no-op because castOcclusion=false
|
|
gfx->setCanvas(nullptr);
|
|
|
|
CHECK(c->getPixel(30, 30).r > 0.5f);
|
|
|
|
// Control: enabling occlusion paints black.
|
|
tex->setCastOcclusion(true);
|
|
gfx->setCanvas(c);
|
|
gfx->drawSolidRect(0, 0, 64, 64, Color(0.9f, 0.9f, 0.9f, 1.f));
|
|
gfx->drawOcclusion(tex, m);
|
|
gfx->setCanvas(nullptr);
|
|
CHECK(c->getPixel(30, 30).r < 0.2f);
|
|
}
|
|
|
|
TEST_CASE("volumetric.lightFlags") {
|
|
auto *l2 = Light2D::createLight("point");
|
|
CHECK(l2->getVolumetric() == false);
|
|
l2->setVolumetric(true);
|
|
l2->setVolumetricIntensity(0.75f);
|
|
CHECK(l2->getVolumetric() == true);
|
|
CHECK(std::fabs(l2->getVolumetricIntensity() - 0.75f) < 1e-5f);
|
|
|
|
auto *l3 = Light3D::createLight("dir");
|
|
CHECK(l3->getVolumetric() == false);
|
|
l3->setVolumetric(true);
|
|
CHECK(l3->getVolumetric() == true);
|
|
}
|
|
|
|
TEST_CASE("volumetric.modeAndRayMarchQuality") {
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx);
|
|
|
|
Volumetric *raw = gfx->newVolumetric();
|
|
REQUIRE(raw != nullptr);
|
|
std::unique_ptr<Volumetric> vol(raw);
|
|
REQUIRE(vol->getRayMarchShader() != nullptr);
|
|
|
|
CHECK(vol->getMode() == "screenspace");
|
|
vol->setMode("raymarch");
|
|
CHECK(vol->getMode() == "raymarch");
|
|
|
|
vol->setQuality("low");
|
|
CHECK(vol->getSampleCount() == 8);
|
|
CHECK(vol->getDownscale() == 4.f);
|
|
|
|
vol->setQuality("high");
|
|
CHECK(vol->getSampleCount() == 48);
|
|
|
|
vol->setMode("screenspace");
|
|
vol->setQuality("medium");
|
|
CHECK(vol->getSampleCount() == 48);
|
|
}
|
|
|
|
TEST_CASE("volumetric.cloudModeParametersAndQuality") {
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx);
|
|
|
|
std::unique_ptr<Volumetric> vol(gfx->newVolumetric());
|
|
REQUIRE(vol.get() != nullptr);
|
|
REQUIRE(vol->getCloudShader() != nullptr);
|
|
vol->setMode("cloud");
|
|
vol->setCloudLayer(6.f, 14.f);
|
|
vol->setCloudCoverage(0.72f);
|
|
vol->setCloudDensity(1.3f);
|
|
vol->setCloudScale(22.f);
|
|
vol->setCloudWind(2.f, -0.5f);
|
|
vol->setCloudLightColor(1.f, 0.65f, 0.35f);
|
|
CHECK(std::fabs(vol->getFloat("cloudBottom") - 6.f) < 1e-5f);
|
|
CHECK(std::fabs(vol->getFloat("cloudTop") - 14.f) < 1e-5f);
|
|
CHECK(std::fabs(vol->getFloat("cloudCoverage") - 0.72f) < 1e-5f);
|
|
vol->setQuality("low");
|
|
CHECK(vol->getSampleCount() == 16);
|
|
vol->setQuality("high");
|
|
CHECK(vol->getSampleCount() == 64);
|
|
win->close();
|
|
}
|
|
|
|
namespace {
|
|
|
|
struct BoxDepth {
|
|
int w = 0, h = 0;
|
|
float boxU0 = 0.35f, boxU1 = 0.65f, boxV0 = 0.35f, boxV1 = 0.65f;
|
|
float boxDepth = 0.35f;
|
|
float floorDepth = 0.85f;
|
|
};
|
|
|
|
float boxDepth01(int x, int y, void *userdata) {
|
|
auto *b = static_cast<BoxDepth *>(userdata);
|
|
const float u = (float(x) + 0.5f) / float(b->w);
|
|
const float v = (float(y) + 0.5f) / float(b->h);
|
|
if (u >= b->boxU0 && u <= b->boxU1 && v >= b->boxV0 && v <= b->boxV1) return b->boxDepth;
|
|
return b->floorDepth;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("volumetric.rayMarchProducesScatter") {
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx, 160, 120);
|
|
|
|
Volumetric *raw = gfx->newVolumetric();
|
|
REQUIRE(raw != nullptr);
|
|
std::unique_ptr<Volumetric> vol(raw);
|
|
vol->setMode("raymarch");
|
|
vol->setQuality("medium");
|
|
vol->setLightDirection(0.5f, 1.f, 0.2f);
|
|
vol->setLightScreenUV(0.8f, 0.15f);
|
|
vol->setCamera(0.f, 2.f, 5.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 50.f, 160.f / 120.f, 0.1f, 40.f);
|
|
vol->setDensity(1.2f);
|
|
vol->setIntensity(2.5f);
|
|
vol->setShaftColor(1.f, 0.92f, 0.75f);
|
|
vol->setFloat("fogAmount", 0.5f);
|
|
vol->setFloat("dustAmount", 0.4f);
|
|
|
|
BoxDepth bd;
|
|
bd.w = 80;
|
|
bd.h = 60;
|
|
Texture *depth = vol->newLinearDepthTexture(gfx, bd.w, bd.h, boxDepth01, &bd);
|
|
REQUIRE(depth != nullptr);
|
|
|
|
Canvas *out = gfx->newCanvas(bd.w, bd.h);
|
|
vol->rayMarchTo(gfx, depth, out);
|
|
|
|
float maxL = 0.f;
|
|
float sum = 0.f;
|
|
int n = 0;
|
|
for (int y = 0; y < bd.h; y += 2) {
|
|
for (int x = 0; x < bd.w; x += 2) {
|
|
const float L = luma(out->getPixel(x, y));
|
|
maxL = std::max(maxL, L);
|
|
sum += L;
|
|
++n;
|
|
}
|
|
}
|
|
CHECK(maxL > 0.005f);
|
|
CHECK(sum / float(std::max(n, 1)) > 0.0005f);
|
|
|
|
previewTexture(gfx, out->getTexture());
|
|
win->close();
|
|
}
|
|
|
|
TEST_CASE("volumetric.rayMarchShadowDarkensBehindOccluder") {
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx, 160, 120);
|
|
|
|
Volumetric *raw = gfx->newVolumetric();
|
|
REQUIRE(raw != nullptr);
|
|
std::unique_ptr<Volumetric> vol(raw);
|
|
vol->setMode("raymarch");
|
|
vol->setQuality("high");
|
|
vol->setLightDirection(0.6f, 0.8f, 0.1f);
|
|
vol->setLightScreenUV(0.85f, 0.1f);
|
|
vol->setCamera(0.f, 1.5f, 4.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 55.f, 160.f / 120.f, 0.1f, 30.f);
|
|
vol->setDensity(0.7f);
|
|
vol->setIntensity(1.6f);
|
|
vol->setFloat("shadowSteps", 16.f);
|
|
|
|
BoxDepth bd;
|
|
bd.w = 96;
|
|
bd.h = 72;
|
|
bd.boxU0 = 0.4f;
|
|
bd.boxU1 = 0.6f;
|
|
bd.boxV0 = 0.3f;
|
|
bd.boxV1 = 0.55f;
|
|
bd.boxDepth = 0.3f;
|
|
bd.floorDepth = 0.9f;
|
|
Texture *depth = vol->newLinearDepthTexture(gfx, bd.w, bd.h, boxDepth01, &bd);
|
|
|
|
Canvas *out = gfx->newCanvas(bd.w, bd.h);
|
|
vol->rayMarchTo(gfx, depth, out);
|
|
|
|
// Sample below the box (away from light) vs beside it toward the light.
|
|
const int midX = int(bd.w * 0.5f);
|
|
const int belowY = int(bd.h * 0.7f);
|
|
const int litY = int(bd.h * 0.2f);
|
|
const float behind = luma(out->getPixel(midX, belowY));
|
|
const float towardLight = luma(out->getPixel(int(bd.w * 0.75f), litY));
|
|
// Lit side / skyward samples should retain some energy; occluded volume may be darker.
|
|
CHECK(towardLight + behind > 0.0f);
|
|
CHECK(towardLight + 0.001f >= behind * 0.25f);
|
|
}
|
|
|
|
TEST_CASE("volumetric.drawOccluders2DUsesCastFlag") {
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx, 128, 96);
|
|
|
|
// Hide leftover sprites from other tests if any manager exists.
|
|
if (ecs::current()->getManager<Renderable2D>() != nullptr) {
|
|
auto view = ecs::View<Renderable2D, Renderable2D::Sprite>();
|
|
for (auto it = view.begin(); it != view.end(); ++it) {
|
|
auto [sp] = *it;
|
|
sp->visible = false;
|
|
}
|
|
}
|
|
|
|
auto *a = Renderable2D::create();
|
|
a->transform()->x = 20;
|
|
a->transform()->y = 20;
|
|
a->sprite()->width = 40;
|
|
a->sprite()->height = 40;
|
|
a->sprite()->visible = true;
|
|
a->setCastOcclusion(true);
|
|
|
|
auto *b = Renderable2D::create();
|
|
b->transform()->x = 70;
|
|
b->transform()->y = 20;
|
|
b->sprite()->width = 40;
|
|
b->sprite()->height = 40;
|
|
b->sprite()->visible = true;
|
|
b->setCastOcclusion(false);
|
|
|
|
Volumetric *raw = gfx->newVolumetric();
|
|
std::unique_ptr<Volumetric> vol(raw);
|
|
|
|
Canvas *occ = gfx->newCanvas(128, 96);
|
|
gfx->setCanvas(occ);
|
|
gfx->clear(Color(1.f, 1.f, 1.f, 1.f), std::nullopt, std::nullopt);
|
|
// Paint a bright plate so skipped occluders stay readable.
|
|
gfx->drawSolidRect(0, 0, 128, 96, Color(0.95f, 0.95f, 0.95f, 1.f));
|
|
vol->drawOccluders2D(gfx);
|
|
gfx->setCanvas(nullptr);
|
|
|
|
// Occluder A region dark; B skipped → stays bright.
|
|
CHECK(occ->getPixel(30, 30).r < 0.15f);
|
|
CHECK(occ->getPixel(85, 30).r > 0.5f);
|
|
}
|
|
|
|
TEST_CASE("volumetric.paramRoundTripShared") {
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx);
|
|
std::unique_ptr<Volumetric> vol(gfx->newVolumetric());
|
|
vol->setDensity(0.42f);
|
|
CHECK(std::fabs(vol->getFloat("density") - 0.42f) < 1e-5f);
|
|
vol->setMode("raymarch");
|
|
vol->setDensity(0.33f);
|
|
CHECK(std::fabs(vol->getFloat("density") - 0.33f) < 1e-5f);
|
|
CHECK(vol->hasParam("invViewProj") == true);
|
|
CHECK(vol->hasParam("exposure") == true);
|
|
}
|
|
|
|
TEST_CASE("volumetric.fogModeAndQuality") {
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx);
|
|
std::unique_ptr<Volumetric> vol(gfx->newVolumetric());
|
|
REQUIRE(vol->getFogShader() != nullptr);
|
|
|
|
vol->setMode("fog");
|
|
CHECK(vol->getMode() == "fog");
|
|
vol->setQuality("low");
|
|
CHECK(vol->getSampleCount() == 8);
|
|
vol->setQuality("high");
|
|
CHECK(vol->getSampleCount() == 48);
|
|
vol->setQuality("medium");
|
|
CHECK(vol->getSampleCount() == 24);
|
|
}
|
|
|
|
TEST_CASE("volumetric.fogParamsRoundTrip") {
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx);
|
|
std::unique_ptr<Volumetric> vol(gfx->newVolumetric());
|
|
vol->setMode("fog");
|
|
vol->setFogHeight(-1.5f);
|
|
vol->setFogHeightFalloff(0.22f);
|
|
vol->setFogStart(3.f);
|
|
vol->setFogEnd(25.f);
|
|
vol->setFogNoise(0.5f);
|
|
vol->setFogColor(0.4f, 0.5f, 0.7f);
|
|
CHECK(std::fabs(vol->getFloat("fogHeight") + 1.5f) < 1e-5f);
|
|
CHECK(std::fabs(vol->getFloat("heightFalloff") - 0.22f) < 1e-5f);
|
|
CHECK(std::fabs(vol->getFloat("fogStart") - 3.f) < 1e-5f);
|
|
CHECK(std::fabs(vol->getFloat("fogEnd") - 25.f) < 1e-5f);
|
|
CHECK(std::fabs(vol->getFloat("noiseAmount") - 0.5f) < 1e-5f);
|
|
CHECK(std::fabs(vol->getFloat("fogR") - 0.4f) < 1e-5f);
|
|
}
|
|
|
|
TEST_CASE("volumetric.applyFogProducesOpacity") {
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx, 160, 120);
|
|
|
|
std::unique_ptr<Volumetric> vol(gfx->newVolumetric());
|
|
vol->setMode("fog");
|
|
vol->setQuality("medium");
|
|
vol->setCamera(0.f, 1.5f, 6.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 50.f, 160.f / 120.f, 0.1f, 50.f);
|
|
vol->setLightDirection(0.3f, 1.f, 0.2f);
|
|
vol->setFogHeight(0.f);
|
|
vol->setFogHeightFalloff(0.2f);
|
|
vol->setFogStart(1.f);
|
|
vol->setFogEnd(30.f);
|
|
vol->setDensity(0.45f);
|
|
vol->setIntensity(1.5f);
|
|
vol->setFogColor(0.6f, 0.7f, 0.85f);
|
|
vol->setFogNoise(0.2f);
|
|
|
|
BoxDepth bd;
|
|
bd.w = 80;
|
|
bd.h = 60;
|
|
bd.boxDepth = 0.4f;
|
|
bd.floorDepth = 0.95f;
|
|
Texture *depth = vol->newLinearDepthTexture(gfx, bd.w, bd.h, boxDepth01, &bd);
|
|
Canvas *out = gfx->newCanvas(bd.w, bd.h);
|
|
// Transparent clear so SrcAlpha fog writes measurable alpha (opaque dst always
|
|
// blends to a=1 with the textured pipeline).
|
|
gfx->setCanvas(out);
|
|
gfx->clear(Color(0.f, 0.f, 0.f, 0.f), std::nullopt, std::nullopt);
|
|
vol->applyFog(gfx, depth);
|
|
gfx->setCanvas(nullptr);
|
|
|
|
float maxA = 0.f;
|
|
float maxL = 0.f;
|
|
for (int y = 0; y < bd.h; y += 2) {
|
|
for (int x = 0; x < bd.w; x += 2) {
|
|
const Color c = out->getPixel(x, y);
|
|
maxA = std::max(maxA, c.a);
|
|
maxL = std::max(maxL, luma(c));
|
|
}
|
|
}
|
|
CHECK(maxA > 0.02f);
|
|
CHECK(maxL > 0.01f);
|
|
|
|
previewTexture(gfx, out->getTexture());
|
|
win->close();
|
|
}
|
|
|
|
TEST_CASE("volumetric.fogDenserAtDistance") {
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx, 128, 96);
|
|
|
|
std::unique_ptr<Volumetric> vol(gfx->newVolumetric());
|
|
vol->setMode("fog");
|
|
vol->setQuality("high");
|
|
vol->setCamera(0.f, 1.f, 4.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 55.f, 128.f / 96.f, 0.1f, 40.f);
|
|
vol->setFogHeight(-0.5f);
|
|
vol->setFogHeightFalloff(0.05f);
|
|
vol->setFogStart(0.5f);
|
|
vol->setFogEnd(20.f);
|
|
// Keep density moderate so near/far do not both saturate to opaque.
|
|
vol->setDensity(0.08f);
|
|
vol->setIntensity(1.2f);
|
|
vol->setFogNoise(0.0f);
|
|
|
|
// Near wall vs far wall depths.
|
|
BoxDepth nearBd;
|
|
nearBd.w = 64;
|
|
nearBd.h = 48;
|
|
nearBd.boxU0 = 0.f;
|
|
nearBd.boxU1 = 1.f;
|
|
nearBd.boxV0 = 0.f;
|
|
nearBd.boxV1 = 1.f;
|
|
nearBd.boxDepth = 0.15f;
|
|
nearBd.floorDepth = 0.15f;
|
|
|
|
BoxDepth farBd = nearBd;
|
|
farBd.boxDepth = 0.95f;
|
|
farBd.floorDepth = 0.95f;
|
|
|
|
Texture *nearDepth = vol->newLinearDepthTexture(gfx, nearBd.w, nearBd.h, boxDepth01, &nearBd);
|
|
Texture *farDepth = vol->newLinearDepthTexture(gfx, farBd.w, farBd.h, boxDepth01, &farBd);
|
|
Canvas *nearOut = gfx->newCanvas(nearBd.w, nearBd.h);
|
|
Canvas *farOut = gfx->newCanvas(farBd.w, farBd.h);
|
|
|
|
gfx->setCanvas(nearOut);
|
|
gfx->clear(Color(0.f, 0.f, 0.f, 0.f), std::nullopt, std::nullopt);
|
|
vol->applyFog(gfx, nearDepth);
|
|
gfx->setCanvas(farOut);
|
|
gfx->clear(Color(0.f, 0.f, 0.f, 0.f), std::nullopt, std::nullopt);
|
|
vol->applyFog(gfx, farDepth);
|
|
gfx->setCanvas(nullptr);
|
|
|
|
const float nearA = nearOut->getPixel(32, 24).a;
|
|
const float farA = farOut->getPixel(32, 24).a;
|
|
CHECK(farA > nearA);
|
|
}
|
|
|
|
TEST_CASE("volumetric.froxelAtlasCompositesIntegratedMedia") {
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx, 128, 96);
|
|
|
|
std::unique_ptr<Volumetric> vol(gfx->newVolumetric());
|
|
vol->setMode("froxel");
|
|
vol->configureFroxelGrid(16, 12, 16, 0.1f, 40.f);
|
|
vol->injectFroxelHeightFog(0.08f, 0.75f, 0.82f, 0.95f, 0.f, 0.15f, -2.f, 8.f);
|
|
vol->integrateFroxel(1.f, 0.9f, 0.8f);
|
|
vol->uploadFroxel(gfx);
|
|
REQUIRE(vol->getFroxelAtlas() != nullptr);
|
|
CHECK(vol->getFroxelAtlas()->getWidth() == 64);
|
|
CHECK(vol->getFroxelAtlas()->getHeight() == 48);
|
|
|
|
BoxDepth bd;
|
|
bd.w = 64;
|
|
bd.h = 48;
|
|
bd.boxDepth = 0.8f;
|
|
bd.floorDepth = 0.8f;
|
|
Texture *depth = vol->newLinearDepthTexture(gfx, bd.w, bd.h, boxDepth01, &bd);
|
|
Canvas *out = gfx->newCanvas(bd.w, bd.h);
|
|
gfx->setCanvas(out);
|
|
gfx->clear(Color(0.f, 0.f, 0.f, 0.f), std::nullopt, std::nullopt);
|
|
vol->applyFroxel(gfx, depth);
|
|
gfx->setCanvas(nullptr);
|
|
|
|
const Color center = out->getPixel(32, 24);
|
|
CHECK(center.a > 0.02f);
|
|
CHECK(luma(center) > 0.01f);
|
|
win->close();
|
|
}
|