Files

299 lines
9.1 KiB
C++

#include "Fixtures.h"
#include "ManualPreview.h"
#include "image/ImageData.h"
#include "zeroerr/assert.h"
#include "zeroerr/unittest.h"
#include <SDL2/SDL.h>
#include <cstdio>
#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"
// Color lives in eve::graphics (see graphics/Canvas.h); keep the unqualified form.
using eve::graphics::Color;
using namespace eve::graphics;
namespace {
void resetScene3D() {
if (ecs::current()->getManager<Renderable3D>() != nullptr) {
auto view = ecs::View<Renderable3D, Renderable3D::MeshRenderer>();
for (auto it = view.begin(); it != view.end(); ++it) {
auto [mr] = *it;
mr->visible = false;
}
}
if (ecs::current()->getManager<Camera3D>() != nullptr) {
auto camView = ecs::View<Camera3D, Camera3D::Data>();
for (auto it = camView.begin(); it != camView.end(); ++it) {
auto [data] = *it;
data->active = false;
data->envMap = nullptr;
data->envIntensity = 0.f;
}
}
if (ecs::current()->getManager<Light3D>() != nullptr) {
auto lightView = ecs::View<Light3D, Light3D::Data>();
for (auto it = lightView.begin(); it != lightView.end(); ++it) {
auto [d] = *it;
d->enabled = false;
}
}
}
Texture *makeSolid(Graphics *gfx, uint8_t r, uint8_t g, uint8_t b) {
const uint8_t px[4] = {r, g, b, 255};
return gfx->newTexture(1, 1, px);
}
void addHud() {
auto *hud = Renderable2D::create();
hud->transform()->x = 0;
hud->transform()->y = 0;
hud->sprite()->width = 2;
hud->sprite()->height = 2;
}
void warmPresent(Graphics *gfx, int frames = 2) {
for (int i = 0; i < frames; ++i) {
RenderSystem3D::render(*gfx);
RenderSystem::render(*gfx);
SDL_Delay(8);
}
}
Mesh *loadCube(Graphics *gfx) {
Mesh *m = gfx->newMeshCube();
REQUIRE(m != nullptr);
return m;
}
void setupCubeScene(Graphics *gfx, Renderable3D *&cube) {
resetScene3D();
cube = Renderable3D::create();
cube->setMesh(loadCube(gfx));
cube->setTexture(makeSolid(gfx, 200, 190, 170));
cube->setMetallic(0.f);
cube->setRoughness(0.85f);
auto *cam = Camera3D::createCamera();
cam->setEye(1.6f, 1.2f, 2.4f);
cam->setTarget(0.f, 0.f, 0.f);
cam->setAmbient(0.3f, 0.3f, 0.3f);
cam->data()->nearZ = 0.05f;
cam->data()->farZ = 30.f;
auto *sun = Light3D::createLight("dir");
sun->setDirection(0.2f, 1.f, 0.4f);
sun->setColor(1.f, 1.f, 1.f, 1.6f);
sun->setEnabled(true);
addHud();
}
} // namespace
TEST_CASE("outline.paramRoundTrip") {
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx);
std::unique_ptr<Outline> outline(gfx->newOutline());
REQUIRE(outline->getShader() != nullptr);
outline->setColor(0.1f, 0.2f, 0.3f);
outline->setWidth(2.f);
outline->setDepthThreshold(0.4f);
outline->setDepthSensitivity(0.01f);
outline->setNormalThreshold(0.5f);
outline->setSoftness(0.3f);
outline->setClip(0.1f, 50.f);
CHECK(std::fabs(outline->getColorR() - 0.1f) < 1e-5f);
CHECK(std::fabs(outline->getColorG() - 0.2f) < 1e-5f);
CHECK(std::fabs(outline->getColorB() - 0.3f) < 1e-5f);
CHECK(std::fabs(outline->getWidth() - 2.f) < 1e-5f);
CHECK(std::fabs(outline->getDepthThreshold() - 0.4f) < 1e-5f);
CHECK(std::fabs(outline->getDepthSensitivity() - 0.01f) < 1e-5f);
CHECK(std::fabs(outline->getNormalThreshold() - 0.5f) < 1e-5f);
CHECK(std::fabs(outline->getSoftness() - 0.3f) < 1e-5f);
CHECK(outline->hasParam("width") == true);
CHECK(outline->hasParam("depthThreshold") == true);
CHECK(outline->hasParam("normalThreshold") == true);
CHECK(outline->hasParam("softness") == true);
CHECK(std::fabs(outline->getFloat("width") - 2.f) < 1e-5f);
outline->setFloat("colorR", 0.9f);
CHECK(std::fabs(outline->getFloat("colorR") - 0.9f) < 1e-5f);
win->close();
}
TEST_CASE("outline.renderControlFeatureToggle") {
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 240, 180);
RenderControl *rc = gfx->getRenderControl();
REQUIRE(rc != nullptr);
rc->disable("ao");
CHECK(rc->supports("outline") == true);
CHECK(rc->isEnabled("outline") == false);
rc->enable("outline");
CHECK(rc->isEnabled("outline") == true);
// Enabling outline implies the gbuffer depth/normal fill.
CHECK(rc->isEnabled("gbuffer") == true);
// Turning the gbuffer off must drop the dependent outline feature.
rc->disable("gbuffer");
CHECK(rc->isEnabled("outline") == false);
rc->enable("gbuffer");
rc->enable("outline");
rc->compile();
Renderable3D *cube = nullptr;
setupCubeScene(gfx, cube);
warmPresent(gfx, 3);
GBuffer *gb = rc->getGBuffer();
REQUIRE(gb != nullptr);
CHECK(gb->isValid());
CHECK(gb->getHwDepthTexture() != nullptr);
CHECK(gb->getNormalTexture() != nullptr);
win->close();
}
TEST_CASE("outline.newMeshCubeAndPipelineConfig") {
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 240, 180);
Mesh *cube = gfx->newMeshCube(1.f);
REQUIRE(cube != nullptr);
// The auto-applied (render-feature) outline must be reachable and configurable.
Outline *pipeline = gfx->pipelineOutline();
REQUIRE(pipeline != nullptr);
pipeline->setColor(0.2f, 0.1f, 0.05f);
pipeline->setWidth(2.5f);
pipeline->setClip(0.05f, 40.f);
CHECK(std::fabs(pipeline->getWidth() - 2.5f) < 1e-5f);
CHECK(std::fabs(pipeline->getColorR() - 0.2f) < 1e-5f);
RenderControl *rc = gfx->getRenderControl();
rc->disable("ao");
rc->enable("outline");
rc->compile();
Renderable3D *cubeEnt = Renderable3D::create();
cubeEnt->setMesh(cube);
cubeEnt->setPosition(0.f, 0.f, 0.f);
cubeEnt->setRoughness(0.7f);
auto *cam = Camera3D::createCamera();
cam->setEye(2.6f, 1.9f, 3.4f);
cam->setTarget(0.f, 0.f, 0.f);
cam->data()->nearZ = 0.05f;
cam->data()->farZ = 40.f;
addHud();
warmPresent(gfx, 2);
CHECK(rc->getGBuffer()->isValid());
win->close();
}
TEST_CASE("outline.previewCube") {
// Compare the outline against the same cube without the effect at fixed poses.
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 420, 360);
gfx->setScreenReadbackEnabled(true);
gfx->setBackgroundColor(Color(0.12f, 0.13f, 0.16f, 1.f));
RenderControl *rc = gfx->getRenderControl();
REQUIRE(rc != nullptr);
rc->disable("ao");
rc->enable("outline");
rc->compile();
Renderable3D *cube = nullptr;
setupCubeScene(gfx, cube);
// Slightly thicker, softer dark outline so the silhouette reads clearly.
Outline *outline = gfx->pipelineOutline();
REQUIRE(outline != nullptr);
outline->setWidth(1.5f);
outline->setColor(0.04f, 0.03f, 0.05f);
outline->setNormalThreshold(0.5f);
outline->setSoftness(0.2f);
for (int pose : {0, 17, 43}) {
cube->setRotation(0.15f * float(pose), float(pose) * 0.02f, 0.f);
rc->disable("outline");
rc->compile();
warmPresent(gfx);
std::unique_ptr<eve::image::ImageData> plain(gfx->newImageData());
REQUIRE(plain != nullptr);
rc->enable("outline");
rc->compile();
warmPresent(gfx);
std::unique_ptr<eve::image::ImageData> outlined(gfx->newImageData());
REQUIRE(outlined != nullptr);
REQUIRE(plain->getWidth() == outlined->getWidth());
REQUIRE(plain->getHeight() == outlined->getHeight());
int darkenedPixels = 0;
for (int y = 0; y < plain->getHeight(); ++y) {
for (int x = 0; x < plain->getWidth(); ++x) {
const auto before = plain->getPixel(x, y);
const auto after = outlined->getPixel(x, y);
if (before.r + before.g + before.b > after.r + after.g + after.b + 0.09f) ++darkenedPixels;
}
}
REQUIRE(darkenedPixels > 8);
}
const int frames = eve::test::manualPreviewEnabled() ? 150 : 0;
for (int i = 0; i < frames; ++i) {
cube->setRotation(0.15f * float(i), float(i) * 0.02f, 0.f);
RenderSystem3D::render(*gfx);
RenderSystem::render(*gfx);
SDL_Event e;
while (SDL_PollEvent(&e)) {
if (e.type == SDL_QUIT) goto done;
}
SDL_Delay(33);
}
done:
win->close();
}