1284 lines
42 KiB
C++
1284 lines
42 KiB
C++
#include "Fixtures.h"
|
|
#include "SceneColor.h"
|
|
#include "zeroerr/assert.h"
|
|
#include "zeroerr/unittest.h"
|
|
|
|
#include <SDL2/SDL.h>
|
|
#include <assimp/matrix4x4.h>
|
|
#include <assimp/mesh.h>
|
|
#include <assimp/scene.h>
|
|
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "graphics/AmbientOcclusion.h"
|
|
#include "graphics/AntiAliasing.h"
|
|
#include "graphics/Canvas.h"
|
|
#include "graphics/ClipSpace.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/Shadow.h"
|
|
#include "graphics/Texture.h"
|
|
#include "graphics/Volumetric.h"
|
|
#include "graphics/Water.h"
|
|
#include "graphics/Waterfall.h"
|
|
#include "medialoader/model/ModelLoader.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 {
|
|
|
|
float luma(const Color &c) { return 0.2126f * c.r + 0.7152f * c.g + 0.0722f * c.b; }
|
|
|
|
|
|
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;
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
float meanLumaRegion(Graphics* gfx, int x0, int y0, int x1, int y1, int step = 4, bool sceneLinear = false) {
|
|
double sum = 0.0;
|
|
int n = 0;
|
|
for (int y = y0; y < y1; y += step) {
|
|
for (int x = x0; x < x1; x += step) {
|
|
const auto pixel = gfx->getPixel(x, y);
|
|
sum += luma(sceneLinear ? testSceneLinearColor(pixel) : pixel);
|
|
++n;
|
|
}
|
|
}
|
|
return n ? float(sum / n) : 0.f;
|
|
}
|
|
|
|
float meanGreenRegion(Graphics *gfx, int x0, int y0, int x1, int y1, int step = 4) {
|
|
double sum = 0.0;
|
|
int n = 0;
|
|
for (int y = y0; y < y1; y += step) {
|
|
for (int x = x0; x < x1; x += step) {
|
|
sum += gfx->getPixel(x, y).g;
|
|
++n;
|
|
}
|
|
}
|
|
return n ? float(sum / n) : 0.f;
|
|
}
|
|
|
|
float meanRedRegion(Graphics *gfx, int x0, int y0, int x1, int y1, int step = 4) {
|
|
double sum = 0.0;
|
|
int n = 0;
|
|
for (int y = y0; y < y1; y += step) {
|
|
for (int x = x0; x < x1; x += step) {
|
|
sum += gfx->getPixel(x, y).r;
|
|
++n;
|
|
}
|
|
}
|
|
return n ? float(sum / n) : 0.f;
|
|
}
|
|
|
|
/** Minimal unit cube OBJ (outward CCW for RH Y-up). */
|
|
static const char kCubeObj[] =
|
|
"v -0.5 -0.5 0.5\nv 0.5 -0.5 0.5\nv 0.5 0.5 0.5\nv -0.5 0.5 0.5\n"
|
|
"vn 0 0 1\nf 1//1 2//1 3//1\nf 1//1 3//1 4//1\n"
|
|
"v 0.5 -0.5 -0.5\nv -0.5 -0.5 -0.5\nv -0.5 0.5 -0.5\nv 0.5 0.5 -0.5\n"
|
|
"vn 0 0 -1\nf 5//2 6//2 7//2\nf 5//2 7//2 8//2\n"
|
|
"v 0.5 -0.5 0.5\nv 0.5 -0.5 -0.5\nv 0.5 0.5 -0.5\nv 0.5 0.5 0.5\n"
|
|
"vn 1 0 0\nf 9//3 10//3 11//3\nf 9//3 11//3 12//3\n"
|
|
"v -0.5 -0.5 -0.5\nv -0.5 -0.5 0.5\nv -0.5 0.5 0.5\nv -0.5 0.5 -0.5\n"
|
|
"vn -1 0 0\nf 13//4 14//4 15//4\nf 13//4 15//4 16//4\n"
|
|
"v -0.5 0.5 0.5\nv 0.5 0.5 0.5\nv 0.5 0.5 -0.5\nv -0.5 0.5 -0.5\n"
|
|
"vn 0 1 0\nf 17//5 18//5 19//5\nf 17//5 19//5 20//5\n"
|
|
"v -0.5 -0.5 -0.5\nv 0.5 -0.5 -0.5\nv 0.5 -0.5 0.5\nv -0.5 -0.5 0.5\n"
|
|
"vn 0 -1 0\nf 21//6 22//6 23//6\nf 21//6 23//6 24//6\n";
|
|
|
|
Mesh *loadCube(Graphics *gfx) {
|
|
medialoader::ModelLoader loader;
|
|
auto scene = loader.loadFromMemory(kCubeObj, sizeof(kCubeObj) - 1, ".obj");
|
|
REQUIRE(!scene.empty());
|
|
REQUIRE(scene->mNumMeshes >= 1);
|
|
Mesh *m = gfx->newMeshFromAssimp(*scene->mMeshes[0]);
|
|
REQUIRE(m != nullptr);
|
|
return m;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("RenderScenes.orientation.worldYMapsToScreenUp") {
|
|
// Green sphere above origin, red below — top of framebuffer must read greener.
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx, 320, 240);
|
|
resetScene3D();
|
|
gfx->setScreenReadbackEnabled(true);
|
|
gfx->setBackgroundColor(Color(0.02f, 0.02f, 0.03f, 1.f));
|
|
|
|
Mesh *sphere = gfx->newMeshSphere(20, 12);
|
|
auto *top = Renderable3D::create();
|
|
top->setMesh(sphere);
|
|
top->setTexture(makeSolid(gfx, 40, 220, 40));
|
|
top->setPosition(0.f, 1.05f, 0.f);
|
|
top->setScale(0.55f, 0.55f, 0.55f);
|
|
top->setMetallic(0.f);
|
|
top->setRoughness(0.85f);
|
|
|
|
auto *bot = Renderable3D::create();
|
|
bot->setMesh(sphere);
|
|
bot->setTexture(makeSolid(gfx, 220, 40, 40));
|
|
bot->setPosition(0.f, -1.05f, 0.f);
|
|
bot->setScale(0.55f, 0.55f, 0.55f);
|
|
bot->setMetallic(0.f);
|
|
bot->setRoughness(0.85f);
|
|
|
|
auto *cam = Camera3D::createCamera();
|
|
cam->setEye(0.f, 0.f, 4.2f);
|
|
cam->setTarget(0.f, 0.f, 0.f);
|
|
cam->setAmbient(0.35f, 0.35f, 0.35f);
|
|
cam->data()->nearZ = 0.05f;
|
|
cam->data()->farZ = 40.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();
|
|
warmPresent(gfx, 3);
|
|
|
|
const int w = gfx->getWidth();
|
|
const int h = gfx->getHeight();
|
|
const float topG = meanGreenRegion(gfx, w / 4, 0, (w * 3) / 4, h / 3);
|
|
const float botR = meanRedRegion(gfx, w / 4, (h * 2) / 3, (w * 3) / 4, h);
|
|
const float topR = meanRedRegion(gfx, w / 4, 0, (w * 3) / 4, h / 3);
|
|
const float botG = meanGreenRegion(gfx, w / 4, (h * 2) / 3, (w * 3) / 4, h);
|
|
|
|
std::printf("RenderScenes.orientation topG=%.3f topR=%.3f botR=%.3f botG=%.3f\n", topG, topR,
|
|
botR, botG);
|
|
// Upside-down regression: top must prefer green, bottom prefer red.
|
|
REQUIRE(topG > topR + 0.04f);
|
|
REQUIRE(botR > botG + 0.04f);
|
|
REQUIRE(topG > botG + 0.04f);
|
|
REQUIRE(botR > topR + 0.04f);
|
|
|
|
win->close();
|
|
}
|
|
|
|
TEST_CASE("RenderScenes.lighting.ambientHonorsCamera") {
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx, 280, 200);
|
|
resetScene3D();
|
|
gfx->setScreenReadbackEnabled(true);
|
|
gfx->setBackgroundColor(Color(0.f, 0.f, 0.f, 1.f));
|
|
|
|
auto *cube = Renderable3D::create();
|
|
cube->setMesh(loadCube(gfx));
|
|
cube->setTexture(makeSolid(gfx, 240, 240, 240));
|
|
cube->setMetallic(0.f);
|
|
cube->setRoughness(0.9f);
|
|
|
|
auto *cam = Camera3D::createCamera();
|
|
cam->setEye(1.6f, 1.2f, 2.4f);
|
|
cam->setTarget(0.f, 0.f, 0.f);
|
|
cam->data()->nearZ = 0.05f;
|
|
cam->data()->farZ = 30.f;
|
|
|
|
// No lights — only ambient contributes for dielectrics.
|
|
RenderSystem3D::setDirectionalLight(0.f, 1.f, 0.f, 0.f, 0.f, 0.f);
|
|
addHud();
|
|
|
|
cam->setAmbient(0.02f, 0.02f, 0.02f);
|
|
warmPresent(gfx, 2);
|
|
const Color darkPx = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
|
|
const float dark = luma(darkPx);
|
|
|
|
cam->setAmbient(0.55f, 0.55f, 0.55f);
|
|
warmPresent(gfx, 2);
|
|
const Color brightPx = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
|
|
const float bright = luma(brightPx);
|
|
|
|
std::printf("RenderScenes.ambient dark=%.4f bright=%.4f\n", dark, bright);
|
|
// Without the old shader ambient floor, low ambient stays dark and tracks the camera.
|
|
REQUIRE(dark < 0.18f);
|
|
REQUIRE(bright > dark + 0.12f);
|
|
REQUIRE(bright > 0.25f);
|
|
|
|
win->close();
|
|
}
|
|
|
|
TEST_CASE("RenderScenes.lighting.pointDoesNotBreakOrientation") {
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx, 320, 240);
|
|
resetScene3D();
|
|
gfx->setScreenReadbackEnabled(true);
|
|
gfx->setBackgroundColor(Color(0.01f, 0.01f, 0.02f, 1.f));
|
|
|
|
Mesh *sphere = gfx->newMeshSphere(20, 12);
|
|
auto *top = Renderable3D::create();
|
|
top->setMesh(sphere);
|
|
top->setTexture(makeSolid(gfx, 30, 220, 30));
|
|
top->setPosition(0.f, 1.15f, 0.f);
|
|
top->setScale(0.6f, 0.6f, 0.6f);
|
|
top->setMetallic(0.f);
|
|
top->setRoughness(0.9f);
|
|
|
|
auto *bot = Renderable3D::create();
|
|
bot->setMesh(sphere);
|
|
bot->setTexture(makeSolid(gfx, 220, 30, 30));
|
|
bot->setPosition(0.f, -1.15f, 0.f);
|
|
bot->setScale(0.6f, 0.6f, 0.6f);
|
|
bot->setMetallic(0.f);
|
|
bot->setRoughness(0.9f);
|
|
|
|
auto *cam = Camera3D::createCamera();
|
|
cam->setEye(0.f, 0.f, 4.5f);
|
|
cam->setTarget(0.f, 0.f, 0.f);
|
|
cam->setAmbient(0.2f, 0.2f, 0.2f);
|
|
|
|
auto *pt = Light3D::createLight("point");
|
|
pt->setPosition(0.f, 0.f, 2.0f);
|
|
pt->setColor(1.f, 1.f, 1.f, 6.f);
|
|
pt->setRadius(8.f);
|
|
pt->setEnabled(true);
|
|
|
|
addHud();
|
|
warmPresent(gfx, 3);
|
|
|
|
const int w = gfx->getWidth();
|
|
const int h = gfx->getHeight();
|
|
// Sample near expected sphere centers rather than whole thirds.
|
|
const Color topPx = gfx->getPixel(w / 2, h / 5);
|
|
const Color botPx = gfx->getPixel(w / 2, (h * 4) / 5);
|
|
std::printf("RenderScenes.pointOrient top=(%.2f,%.2f) bot=(%.2f,%.2f)\n", topPx.r, topPx.g,
|
|
botPx.r, botPx.g);
|
|
REQUIRE(topPx.g > topPx.r + 0.05f);
|
|
REQUIRE(botPx.r > botPx.g + 0.05f);
|
|
|
|
win->close();
|
|
}
|
|
|
|
TEST_CASE("RenderScenes.shadow.dirShadowDarkensWithoutFlip") {
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx, 360, 270);
|
|
resetScene3D();
|
|
gfx->setScreenReadbackEnabled(true);
|
|
gfx->setBackgroundColor(Color(0.05f, 0.06f, 0.07f, 1.f));
|
|
|
|
Mesh *sphere = gfx->newMeshSphere(18, 12);
|
|
auto *ground = Renderable3D::create();
|
|
ground->setMesh(sphere);
|
|
ground->setTexture(makeSolid(gfx, 180, 180, 185));
|
|
ground->setPosition(0.f, -0.7f, 0.f);
|
|
ground->setScale(3.5f, 0.08f, 3.5f);
|
|
ground->setMetallic(0.f);
|
|
ground->setRoughness(0.95f);
|
|
ground->setCastShadow(false);
|
|
ground->setReceiveShadow(true);
|
|
|
|
auto *occluder = Renderable3D::create();
|
|
occluder->setMesh(sphere);
|
|
occluder->setTexture(makeSolid(gfx, 40, 200, 40));
|
|
occluder->setPosition(0.f, 0.55f, 0.f);
|
|
occluder->setScale(0.55f, 0.55f, 0.55f);
|
|
occluder->setCastShadow(true);
|
|
occluder->setReceiveShadow(true);
|
|
|
|
auto *cam = Camera3D::createCamera();
|
|
cam->setEye(2.8f, 2.2f, 3.6f);
|
|
cam->setTarget(0.f, 0.f, 0.f);
|
|
cam->setAmbient(0.08f, 0.08f, 0.09f);
|
|
|
|
auto *sun = Light3D::createLight("dir");
|
|
sun->setDirection(0.35f, 1.f, 0.25f);
|
|
sun->setColor(1.f, 0.98f, 0.94f, 2.8f);
|
|
sun->setCastShadow(false);
|
|
sun->setEnabled(true);
|
|
|
|
addHud();
|
|
warmPresent(gfx, 2);
|
|
const int w = gfx->getWidth();
|
|
const int h = gfx->getHeight();
|
|
// Occluder sits above ground — with correct Y, green should dominate the upper half.
|
|
const float topG0 = meanGreenRegion(gfx, w / 5, 0, (w * 4) / 5, h / 2);
|
|
const float mid0 = meanLumaRegion(gfx, w / 3, h / 2, (w * 2) / 3, (h * 5) / 6);
|
|
|
|
sun->setCastShadow(true);
|
|
sun->setShadowStrength(1.f);
|
|
warmPresent(gfx, 3);
|
|
const float topG1 = meanGreenRegion(gfx, w / 5, 0, (w * 4) / 5, h / 2);
|
|
const float mid1 = meanLumaRegion(gfx, w / 3, h / 2, (w * 2) / 3, (h * 5) / 6);
|
|
|
|
std::printf("RenderScenes.shadow topG0=%.3f topG1=%.3f mid0=%.3f mid1=%.3f\n", topG0, topG1,
|
|
mid0, mid1);
|
|
REQUIRE(topG1 > 0.05f); // orientation still upright
|
|
REQUIRE(mid1 + 0.01f < mid0); // ground umbra darkens
|
|
REQUIRE(std::fabs(topG1 - topG0) < 0.25f); // occluder itself not nuked
|
|
|
|
win->close();
|
|
}
|
|
|
|
TEST_CASE("RenderScenes.effects.gbufferAoDoesNotInvertOrientation") {
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx, 320, 240);
|
|
resetScene3D();
|
|
gfx->setScreenReadbackEnabled(true);
|
|
gfx->setBackgroundColor(Color(0.03f, 0.03f, 0.04f, 1.f));
|
|
|
|
Mesh *sphere = gfx->newMeshSphere(16, 10);
|
|
auto *top = Renderable3D::create();
|
|
top->setMesh(sphere);
|
|
top->setTexture(makeSolid(gfx, 40, 210, 40));
|
|
top->setPosition(0.f, 1.0f, 0.f);
|
|
top->setScale(0.5f, 0.5f, 0.5f);
|
|
|
|
auto *bot = Renderable3D::create();
|
|
bot->setMesh(sphere);
|
|
bot->setTexture(makeSolid(gfx, 210, 40, 40));
|
|
bot->setPosition(0.f, -1.0f, 0.f);
|
|
bot->setScale(0.5f, 0.5f, 0.5f);
|
|
|
|
auto *cam = Camera3D::createCamera();
|
|
cam->setEye(0.f, 0.f, 4.0f);
|
|
cam->setTarget(0.f, 0.f, 0.f);
|
|
cam->setAmbient(0.2f, 0.2f, 0.22f);
|
|
|
|
auto *sun = Light3D::createLight("dir");
|
|
sun->setDirection(0.3f, 1.f, 0.2f);
|
|
sun->setColor(1.f, 1.f, 1.f, 1.5f);
|
|
sun->setEnabled(true);
|
|
|
|
RenderControl *rc = gfx->getRenderControl();
|
|
REQUIRE(rc != nullptr);
|
|
rc->enable("gbuffer");
|
|
rc->compile();
|
|
REQUIRE(rc->isCompiled());
|
|
REQUIRE(rc->hasPass("gbuffer"));
|
|
|
|
addHud();
|
|
warmPresent(gfx, 3);
|
|
|
|
const int w = gfx->getWidth();
|
|
const int h = gfx->getHeight();
|
|
REQUIRE(meanGreenRegion(gfx, w / 4, 0, (w * 3) / 4, h / 3) >
|
|
meanRedRegion(gfx, w / 4, 0, (w * 3) / 4, h / 3) + 0.03f);
|
|
|
|
// Disable gbuffer so later cases are not polluted.
|
|
rc->disable("gbuffer");
|
|
rc->compile();
|
|
win->close();
|
|
}
|
|
|
|
TEST_CASE("RenderScenes.mesh.assimpNodeTransformBaked") {
|
|
// Parent node translates +Y by 2 — baked upload must move verts with it.
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx, 280, 200);
|
|
resetScene3D();
|
|
gfx->setScreenReadbackEnabled(true);
|
|
gfx->setBackgroundColor(Color(0.02f, 0.02f, 0.03f, 1.f));
|
|
|
|
medialoader::ModelLoader loader;
|
|
auto scene = loader.loadFromMemory(kCubeObj, sizeof(kCubeObj) - 1, ".obj");
|
|
REQUIRE(!scene.empty());
|
|
REQUIRE(scene->mNumMeshes >= 1);
|
|
|
|
aiMatrix4x4 world;
|
|
world.a4 = 0.f;
|
|
world.b4 = 2.f; // +Y translation
|
|
world.c4 = 0.f;
|
|
Mesh *mesh = gfx->newMeshFromAssimp(*scene->mMeshes[0], world);
|
|
REQUIRE(mesh != nullptr);
|
|
|
|
auto *ent = Renderable3D::create();
|
|
ent->setMesh(mesh);
|
|
ent->setTexture(makeSolid(gfx, 40, 220, 40));
|
|
ent->setMetallic(0.f);
|
|
ent->setRoughness(0.8f);
|
|
|
|
auto *cam = Camera3D::createCamera();
|
|
// Aim at the baked +Y location; identity-local cube at origin would miss the frustum center.
|
|
cam->setEye(0.f, 2.f, 4.0f);
|
|
cam->setTarget(0.f, 2.f, 0.f);
|
|
cam->setAmbient(0.25f, 0.25f, 0.25f);
|
|
|
|
auto *sun = Light3D::createLight("dir");
|
|
sun->setDirection(0.2f, 1.f, 0.3f);
|
|
sun->setColor(1.f, 1.f, 1.f, 1.4f);
|
|
sun->setEnabled(true);
|
|
|
|
addHud();
|
|
warmPresent(gfx, 3);
|
|
|
|
const int w = gfx->getWidth();
|
|
const int h = gfx->getHeight();
|
|
const float mid = meanLumaRegion(gfx, w / 3, h / 3, (w * 2) / 3, (h * 2) / 3);
|
|
REQUIRE(mid > 0.08f);
|
|
|
|
// Control: upload without bake, keep camera on +Y — center should be mostly background.
|
|
resetScene3D();
|
|
Mesh *local = gfx->newMeshFromAssimp(*scene->mMeshes[0]);
|
|
auto *miss = Renderable3D::create();
|
|
miss->setMesh(local);
|
|
miss->setTexture(makeSolid(gfx, 40, 220, 40));
|
|
auto *cam2 = Camera3D::createCamera();
|
|
cam2->setEye(0.f, 2.f, 4.0f);
|
|
cam2->setTarget(0.f, 2.f, 0.f);
|
|
cam2->setAmbient(0.25f, 0.25f, 0.25f);
|
|
auto *sun2 = Light3D::createLight("dir");
|
|
sun2->setDirection(0.2f, 1.f, 0.3f);
|
|
sun2->setColor(1.f, 1.f, 1.f, 1.4f);
|
|
sun2->setEnabled(true);
|
|
addHud();
|
|
warmPresent(gfx, 2);
|
|
const float midMiss = meanLumaRegion(gfx, w / 3, h / 3, (w * 2) / 3, (h * 2) / 3);
|
|
std::printf("RenderScenes.nodeBake mid=%.3f midMiss=%.3f\n", mid, midMiss);
|
|
REQUIRE(mid > midMiss + 0.04f);
|
|
|
|
win->close();
|
|
}
|
|
|
|
TEST_CASE("RenderScenes.clipSpace.perspectiveVulkanFlipsY") {
|
|
const glm::mat4 gl = glm::perspectiveRH_ZO(1.0f, 1.0f, 0.1f, 100.f);
|
|
const glm::mat4 vk = perspectiveVulkanRH_ZO(1.0f, 1.0f, 0.1f, 100.f);
|
|
CHECK(std::fabs(gl[1][1] + vk[1][1]) < 1e-5f);
|
|
CHECK(vk[1][1] < 0.f);
|
|
CHECK(gl[1][1] > 0.f);
|
|
}
|
|
|
|
TEST_CASE("RenderScenes.pick.screenToRayMatchesUprightProjection") {
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx, 200, 150);
|
|
resetScene3D();
|
|
|
|
auto *cam = Camera3D::createCamera();
|
|
cam->setEye(0.f, 0.f, 5.f);
|
|
cam->setTarget(0.f, 0.f, 0.f);
|
|
cam->setUp(0.f, 1.f, 0.f);
|
|
cam->setFov(60.f);
|
|
|
|
// Top-center pixel should ray toward +Y (world up), not -Y.
|
|
cam->screenToRay(100.f, 10.f, 200.f, 150.f);
|
|
REQUIRE(cam->getScreenRayDirY() > 0.05f);
|
|
|
|
// Bottom-center pixel should ray toward -Y.
|
|
cam->screenToRay(100.f, 140.f, 200.f, 150.f);
|
|
REQUIRE(cam->getScreenRayDirY() < -0.05f);
|
|
|
|
win->close();
|
|
}
|
|
|
|
TEST_CASE("RenderScenes.studio.threePointBrightensVsAmbient") {
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx, 360, 270);
|
|
resetScene3D();
|
|
gfx->setScreenReadbackEnabled(true);
|
|
gfx->setBackgroundColor(Color(0.02f, 0.02f, 0.03f, 1.f));
|
|
|
|
Mesh *sphere = gfx->newMeshSphere(20, 12);
|
|
auto *subj = Renderable3D::create();
|
|
subj->setMesh(sphere);
|
|
subj->setTexture(makeSolid(gfx, 210, 210, 215));
|
|
subj->setMetallic(0.15f);
|
|
subj->setRoughness(0.45f);
|
|
|
|
auto *cam = Camera3D::createCamera();
|
|
cam->setEye(0.f, 0.6f, 3.2f);
|
|
cam->setTarget(0.f, 0.f, 0.f);
|
|
cam->setAmbient(0.04f, 0.04f, 0.05f);
|
|
|
|
addHud();
|
|
warmPresent(gfx, 2);
|
|
const float ambOnly = luma(gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2));
|
|
|
|
auto *key = Light3D::createLight("dir");
|
|
key->setDirection(0.55f, 0.85f, 0.35f);
|
|
key->setColor(1.f, 0.97f, 0.92f, 2.4f);
|
|
key->setEnabled(true);
|
|
|
|
auto *fill = Light3D::createLight("point");
|
|
fill->setPosition(-1.6f, 0.4f, 1.2f);
|
|
fill->setColor(0.55f, 0.65f, 1.f, 5.5f);
|
|
fill->setRadius(5.f);
|
|
fill->setEnabled(true);
|
|
|
|
auto *rim = Light3D::createLight("point");
|
|
rim->setPosition(0.2f, 1.2f, -1.8f);
|
|
rim->setColor(1.f, 0.75f, 0.55f, 4.5f);
|
|
rim->setRadius(5.f);
|
|
rim->setEnabled(true);
|
|
|
|
warmPresent(gfx, 3);
|
|
const float lit = luma(gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2));
|
|
std::printf("RenderScenes.threePoint amb=%.3f lit=%.3f\n", ambOnly, lit);
|
|
REQUIRE(lit > ambOnly + 0.05f);
|
|
|
|
// Orientation still upright: sample above/below center after lighting.
|
|
auto *topMark = Renderable3D::create();
|
|
topMark->setMesh(sphere);
|
|
topMark->setTexture(makeSolid(gfx, 40, 220, 40));
|
|
topMark->setPosition(0.f, 1.35f, 0.f);
|
|
topMark->setScale(0.35f, 0.35f, 0.35f);
|
|
warmPresent(gfx, 2);
|
|
const Color topPx = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 5);
|
|
REQUIRE(topPx.g + 0.02f > topPx.r);
|
|
|
|
win->close();
|
|
}
|
|
|
|
TEST_CASE("RenderScenes.ibl.metalBrighterThanDielectric") {
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx, 360, 240);
|
|
resetScene3D();
|
|
gfx->setScreenReadbackEnabled(true);
|
|
gfx->setBackgroundColor(Color(0.01f, 0.01f, 0.02f, 1.f));
|
|
|
|
Mesh *sphere = gfx->newMeshSphere(24, 14);
|
|
auto *metal = Renderable3D::create();
|
|
metal->setMesh(sphere);
|
|
metal->setTexture(makeSolid(gfx, 220, 220, 225));
|
|
metal->setPosition(-0.85f, 0.f, 0.f);
|
|
metal->setScale(0.55f, 0.55f, 0.55f);
|
|
metal->setMetallic(1.f);
|
|
metal->setRoughness(0.12f);
|
|
|
|
auto *diel = Renderable3D::create();
|
|
diel->setMesh(sphere);
|
|
diel->setTexture(makeSolid(gfx, 220, 220, 225));
|
|
diel->setPosition(0.85f, 0.f, 0.f);
|
|
diel->setScale(0.55f, 0.55f, 0.55f);
|
|
diel->setMetallic(0.f);
|
|
diel->setRoughness(0.55f);
|
|
|
|
// Face-colored env so metals pick up tinted specular.
|
|
const uint8_t faceRgb[6][3] = {
|
|
{240, 180, 120}, {100, 140, 220}, {250, 250, 255}, {30, 30, 35}, {200, 210, 230},
|
|
{180, 160, 140},
|
|
};
|
|
const int face = 12;
|
|
std::vector<uint8_t> faces(size_t(face) * size_t(face) * 4u * 6u);
|
|
for (int f = 0; f < 6; ++f) {
|
|
uint8_t *dst = faces.data() + size_t(f) * size_t(face) * size_t(face) * 4u;
|
|
for (int i = 0; i < face * face; ++i) {
|
|
dst[i * 4 + 0] = faceRgb[f][0];
|
|
dst[i * 4 + 1] = faceRgb[f][1];
|
|
dst[i * 4 + 2] = faceRgb[f][2];
|
|
dst[i * 4 + 3] = 255;
|
|
}
|
|
}
|
|
Texture *env = gfx->newCubemap(face, faces.data());
|
|
|
|
auto *cam = Camera3D::createCamera();
|
|
cam->setEye(0.f, 0.35f, 3.4f);
|
|
cam->setTarget(0.f, 0.f, 0.f);
|
|
cam->setAmbient(0.03f, 0.03f, 0.03f);
|
|
cam->setEnvMap(env);
|
|
cam->setEnvIntensity(1.5f);
|
|
|
|
auto *sun = Light3D::createLight("dir");
|
|
sun->setDirection(0.25f, 1.f, 0.2f);
|
|
sun->setColor(0.4f, 0.4f, 0.45f, 0.6f);
|
|
sun->setEnabled(true);
|
|
|
|
addHud();
|
|
warmPresent(gfx, 3);
|
|
|
|
const int w = gfx->getWidth();
|
|
const int h = gfx->getHeight();
|
|
const float metalL = meanLumaRegion(gfx, w / 8, h / 3, w * 3 / 8, h * 2 / 3, 4, true);
|
|
const float dielL = meanLumaRegion(gfx, w * 5 / 8, h / 3, w * 7 / 8, h * 2 / 3, 4, true);
|
|
std::printf("RenderScenes.ibl metalL=%.3f dielL=%.3f\n", metalL, dielL);
|
|
REQUIRE(metalL > dielL + 0.02f);
|
|
|
|
win->close();
|
|
}
|
|
|
|
TEST_CASE("RenderScenes.unlit.ignoresDirectionalLight") {
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx, 280, 200);
|
|
resetScene3D();
|
|
gfx->setScreenReadbackEnabled(true);
|
|
gfx->setBackgroundColor(Color(0.f, 0.f, 0.f, 1.f));
|
|
|
|
Material *unlit = gfx->newMaterial();
|
|
unlit->setShadingModel("unlit");
|
|
unlit->setAlbedoTexture(makeSolid(gfx, 180, 40, 40));
|
|
unlit->setReceiveLight(false);
|
|
|
|
auto *ent = Renderable3D::create();
|
|
ent->setMesh(loadCube(gfx));
|
|
ent->setMaterial(unlit);
|
|
ent->setReceiveLight(false);
|
|
|
|
auto *cam = Camera3D::createCamera();
|
|
cam->setEye(1.8f, 1.4f, 2.6f);
|
|
cam->setTarget(0.f, 0.f, 0.f);
|
|
cam->setAmbient(0.2f, 0.2f, 0.2f);
|
|
|
|
addHud();
|
|
warmPresent(gfx, 2);
|
|
const float before = luma(gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2));
|
|
|
|
auto *sun = Light3D::createLight("dir");
|
|
sun->setDirection(0.3f, 1.f, 0.4f);
|
|
sun->setColor(1.f, 1.f, 1.f, 4.f);
|
|
sun->setEnabled(true);
|
|
warmPresent(gfx, 2);
|
|
const float after = luma(gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2));
|
|
|
|
std::printf("RenderScenes.unlit before=%.3f after=%.3f\n", before, after);
|
|
REQUIRE(before > 0.05f);
|
|
// Unlit must not jump with a strong directional light.
|
|
REQUIRE(std::fabs(after - before) < 0.08f);
|
|
|
|
delete unlit;
|
|
win->close();
|
|
}
|
|
|
|
TEST_CASE("RenderScenes.shadow.pointAndDirCoexist") {
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx, 360, 270);
|
|
resetScene3D();
|
|
gfx->setScreenReadbackEnabled(true);
|
|
gfx->setBackgroundColor(Color(0.04f, 0.04f, 0.05f, 1.f));
|
|
|
|
Mesh *sphere = gfx->newMeshSphere(16, 10);
|
|
auto *ground = Renderable3D::create();
|
|
ground->setMesh(sphere);
|
|
ground->setTexture(makeSolid(gfx, 170, 170, 175));
|
|
ground->setPosition(0.f, -0.75f, 0.f);
|
|
ground->setScale(3.2f, 0.08f, 3.2f);
|
|
ground->setMetallic(0.f);
|
|
ground->setRoughness(0.95f);
|
|
ground->setCastShadow(false);
|
|
ground->setReceiveShadow(true);
|
|
|
|
auto *occ = Renderable3D::create();
|
|
occ->setMesh(sphere);
|
|
occ->setTexture(makeSolid(gfx, 40, 200, 40));
|
|
occ->setPosition(0.f, 0.45f, 0.f);
|
|
occ->setScale(0.5f, 0.5f, 0.5f);
|
|
occ->setCastShadow(true);
|
|
|
|
auto *cam = Camera3D::createCamera();
|
|
cam->setEye(2.6f, 2.0f, 3.4f);
|
|
cam->setTarget(0.f, 0.f, 0.f);
|
|
cam->setAmbient(0.06f, 0.06f, 0.07f);
|
|
|
|
auto *sun = Light3D::createLight("dir");
|
|
sun->setDirection(0.4f, 1.f, 0.2f);
|
|
sun->setColor(1.f, 0.98f, 0.94f, 2.4f);
|
|
sun->setCastShadow(true);
|
|
sun->setShadowStrength(1.f);
|
|
sun->setEnabled(true);
|
|
|
|
auto *pt = Light3D::createLight("point");
|
|
pt->setPosition(-1.2f, 1.0f, 1.0f);
|
|
pt->setColor(0.4f, 0.55f, 1.f, 6.f);
|
|
pt->setRadius(5.f);
|
|
pt->setEnabled(true);
|
|
|
|
addHud();
|
|
warmPresent(gfx, 3);
|
|
|
|
const int w = gfx->getWidth();
|
|
const int h = gfx->getHeight();
|
|
// Green occluder still reads in the upper half (orientation) while ground is lit.
|
|
REQUIRE(meanGreenRegion(gfx, w / 5, 0, (w * 4) / 5, h / 2) > 0.04f);
|
|
REQUIRE(meanLumaRegion(gfx, w / 4, h / 2, (w * 3) / 4, h) > 0.05f);
|
|
|
|
win->close();
|
|
}
|
|
|
|
TEST_CASE("RenderScenes.winding.insideClosedRoomVisible") {
|
|
// Colored left/right walls viewed from inside — verifies upright orientation + visibility.
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx, 320, 240);
|
|
resetScene3D();
|
|
gfx->setScreenReadbackEnabled(true);
|
|
gfx->setBackgroundColor(Color(0.02f, 0.02f, 0.03f, 1.f));
|
|
|
|
Mesh *cube = loadCube(gfx);
|
|
auto *back = Renderable3D::create();
|
|
back->setMesh(cube);
|
|
back->setTexture(makeSolid(gfx, 180, 180, 185));
|
|
back->setPosition(0.f, 1.f, -1.4f);
|
|
back->setScale(2.2f, 2.0f, 0.08f);
|
|
back->setRoughness(0.9f);
|
|
|
|
auto *left = Renderable3D::create();
|
|
left->setMesh(cube);
|
|
left->setTexture(makeSolid(gfx, 210, 35, 35));
|
|
left->setPosition(-1.1f, 1.f, 0.f);
|
|
left->setScale(0.08f, 2.0f, 2.2f);
|
|
left->setRoughness(0.9f);
|
|
|
|
auto *right = Renderable3D::create();
|
|
right->setMesh(cube);
|
|
right->setTexture(makeSolid(gfx, 35, 190, 35));
|
|
right->setPosition(1.1f, 1.f, 0.f);
|
|
right->setScale(0.08f, 2.0f, 2.2f);
|
|
right->setRoughness(0.9f);
|
|
|
|
auto *floor = Renderable3D::create();
|
|
floor->setMesh(cube);
|
|
floor->setTexture(makeSolid(gfx, 140, 140, 145));
|
|
floor->setPosition(0.f, 0.f, 0.f);
|
|
floor->setScale(2.2f, 0.08f, 2.2f);
|
|
floor->setRoughness(0.95f);
|
|
|
|
auto *cam = Camera3D::createCamera();
|
|
cam->setEye(0.f, 1.0f, 1.5f);
|
|
cam->setTarget(0.f, 1.0f, 0.f);
|
|
cam->setAmbient(0.3f, 0.3f, 0.3f);
|
|
|
|
auto *sun = Light3D::createLight("dir");
|
|
sun->setDirection(0.15f, 1.f, 0.1f);
|
|
sun->setColor(1.f, 1.f, 1.f, 1.4f);
|
|
sun->setEnabled(true);
|
|
|
|
addHud();
|
|
warmPresent(gfx, 3);
|
|
|
|
const int w = gfx->getWidth();
|
|
const int h = gfx->getHeight();
|
|
const Color L = gfx->getPixel(w / 6, h / 2);
|
|
const Color R = gfx->getPixel((w * 5) / 6, h / 2);
|
|
std::printf("RenderScenes.room L=(%.2f,%.2f) R=(%.2f,%.2f)\n", L.r, L.g, R.r, R.g);
|
|
REQUIRE(L.r > L.g + 0.05f);
|
|
REQUIRE(R.g > R.r + 0.05f);
|
|
// Floor toward bottom of frame (upright).
|
|
const Color bot = gfx->getPixel(w / 2, (h * 5) / 6);
|
|
REQUIRE(luma(bot) > 0.05f);
|
|
|
|
win->close();
|
|
}
|
|
|
|
TEST_CASE("RenderScenes.gallery.cylinderAndSphereMaterials") {
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx, 400, 280);
|
|
resetScene3D();
|
|
gfx->setScreenReadbackEnabled(true);
|
|
gfx->setBackgroundColor(Color(0.05f, 0.055f, 0.06f, 1.f));
|
|
|
|
Mesh *sphere = gfx->newMeshSphere(18, 12);
|
|
Mesh *cyl = gfx->newMeshCylinder(18, 1, true);
|
|
|
|
const float metals[3] = {0.f, 0.5f, 1.f};
|
|
const float roughs[3] = {0.2f, 0.5f, 0.85f};
|
|
for (int i = 0; i < 3; ++i) {
|
|
auto *s = Renderable3D::create();
|
|
s->setMesh(sphere);
|
|
s->setTexture(makeSolid(gfx, 200, 200, 205));
|
|
s->setPosition(-1.2f + float(i) * 1.2f, 0.55f, 0.f);
|
|
s->setScale(0.4f, 0.4f, 0.4f);
|
|
s->setMetallic(metals[i]);
|
|
s->setRoughness(roughs[i]);
|
|
|
|
auto *c = Renderable3D::create();
|
|
c->setMesh(cyl);
|
|
c->setTexture(makeSolid(gfx, uint8_t(80 + i * 50), 160, uint8_t(200 - i * 40)));
|
|
c->setPosition(-1.2f + float(i) * 1.2f, -0.35f, 0.f);
|
|
c->setScale(0.28f, 0.35f, 0.28f);
|
|
c->setMetallic(0.05f);
|
|
c->setRoughness(0.7f);
|
|
}
|
|
|
|
auto *cam = Camera3D::createCamera();
|
|
cam->setEye(0.f, 1.2f, 4.2f);
|
|
cam->setTarget(0.f, 0.1f, 0.f);
|
|
cam->setAmbient(0.1f, 0.1f, 0.11f);
|
|
|
|
auto *sun = Light3D::createLight("dir");
|
|
sun->setDirection(0.35f, 1.f, 0.4f);
|
|
sun->setColor(1.f, 0.98f, 0.95f, 2.2f);
|
|
sun->setEnabled(true);
|
|
|
|
addHud();
|
|
warmPresent(gfx, 3);
|
|
|
|
const float mid = meanLumaRegion(gfx, 40, 40, 360, 240);
|
|
REQUIRE(mid > 0.04f);
|
|
// Top row (spheres) and bottom row (cylinders) both contribute.
|
|
const int w = gfx->getWidth();
|
|
const int h = gfx->getHeight();
|
|
REQUIRE(meanLumaRegion(gfx, w / 6, h / 8, (w * 5) / 6, h * 2 / 5) > 0.03f);
|
|
REQUIRE(meanLumaRegion(gfx, w / 6, h * 3 / 5, (w * 5) / 6, (h * 7) / 8) > 0.03f);
|
|
|
|
win->close();
|
|
}
|
|
|
|
TEST_CASE("RenderScenes.isolation.effectsDoNotFlipOrientation") {
|
|
// Cycle shadow + gbuffer features; upright green/red markers must survive.
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx, 320, 240);
|
|
resetScene3D();
|
|
gfx->setScreenReadbackEnabled(true);
|
|
gfx->setBackgroundColor(Color(0.02f, 0.02f, 0.03f, 1.f));
|
|
|
|
Mesh *sphere = gfx->newMeshSphere(18, 12);
|
|
auto *top = Renderable3D::create();
|
|
top->setMesh(sphere);
|
|
top->setTexture(makeSolid(gfx, 40, 220, 40));
|
|
top->setPosition(0.f, 1.05f, 0.f);
|
|
top->setScale(0.5f, 0.5f, 0.5f);
|
|
top->setCastShadow(true);
|
|
|
|
auto *bot = Renderable3D::create();
|
|
bot->setMesh(sphere);
|
|
bot->setTexture(makeSolid(gfx, 220, 40, 40));
|
|
bot->setPosition(0.f, -1.05f, 0.f);
|
|
bot->setScale(0.5f, 0.5f, 0.5f);
|
|
bot->setCastShadow(true);
|
|
|
|
auto *ground = Renderable3D::create();
|
|
ground->setMesh(sphere);
|
|
ground->setTexture(makeSolid(gfx, 90, 90, 95));
|
|
ground->setPosition(0.f, -1.7f, 0.f);
|
|
ground->setScale(2.5f, 0.08f, 2.5f);
|
|
ground->setReceiveShadow(true);
|
|
ground->setCastShadow(false);
|
|
|
|
auto *cam = Camera3D::createCamera();
|
|
cam->setEye(0.f, 0.2f, 4.4f);
|
|
cam->setTarget(0.f, 0.f, 0.f);
|
|
cam->setAmbient(0.15f, 0.15f, 0.16f);
|
|
|
|
auto *sun = Light3D::createLight("dir");
|
|
sun->setDirection(0.3f, 1.f, 0.25f);
|
|
sun->setColor(1.f, 1.f, 1.f, 2.0f);
|
|
sun->setEnabled(true);
|
|
|
|
RenderControl *rc = gfx->getRenderControl();
|
|
REQUIRE(rc != nullptr);
|
|
|
|
auto assertUpright = [&](const char *phase) {
|
|
warmPresent(gfx, 2);
|
|
const int w = gfx->getWidth();
|
|
const int h = gfx->getHeight();
|
|
const Color t = gfx->getPixel(w / 2, h / 5);
|
|
const Color b = gfx->getPixel(w / 2, (h * 4) / 5);
|
|
std::printf("RenderScenes.isolation[%s] topG=%.2f botR=%.2f\n", phase, t.g, b.r);
|
|
REQUIRE(t.g > t.r + 0.04f);
|
|
REQUIRE(b.r > b.g + 0.04f);
|
|
};
|
|
|
|
sun->setCastShadow(false);
|
|
rc->disable("gbuffer");
|
|
rc->compile();
|
|
assertUpright("lit");
|
|
|
|
sun->setCastShadow(true);
|
|
sun->setShadowStrength(1.f);
|
|
assertUpright("shadow");
|
|
|
|
rc->enable("gbuffer");
|
|
rc->compile();
|
|
assertUpright("gbuffer");
|
|
|
|
rc->disable("gbuffer");
|
|
sun->setCastShadow(false);
|
|
rc->compile();
|
|
assertUpright("reset");
|
|
|
|
win->close();
|
|
}
|
|
|
|
TEST_CASE("RenderScenes.clustered.manyPointsBrightenWithoutFlip") {
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx, 320, 240);
|
|
resetScene3D();
|
|
gfx->setScreenReadbackEnabled(true);
|
|
gfx->setBackgroundColor(Color(0.01f, 0.01f, 0.015f, 1.f));
|
|
|
|
Mesh *sphere = gfx->newMeshSphere(14, 10);
|
|
auto *ground = Renderable3D::create();
|
|
ground->setMesh(sphere);
|
|
ground->setTexture(makeSolid(gfx, 160, 160, 165));
|
|
ground->setPosition(0.f, -0.6f, 0.f);
|
|
ground->setScale(3.0f, 0.08f, 3.0f);
|
|
ground->setRoughness(0.95f);
|
|
|
|
auto *marker = Renderable3D::create();
|
|
marker->setMesh(sphere);
|
|
marker->setTexture(makeSolid(gfx, 40, 210, 40));
|
|
marker->setPosition(0.f, 0.7f, 0.f);
|
|
marker->setScale(0.45f, 0.45f, 0.45f);
|
|
|
|
auto *cam = Camera3D::createCamera();
|
|
cam->setEye(0.f, 1.5f, 4.0f);
|
|
cam->setTarget(0.f, 0.f, 0.f);
|
|
cam->setAmbient(0.02f, 0.02f, 0.025f);
|
|
|
|
// >8 lights triggers clustered path in RenderSystem3D.
|
|
for (int i = 0; i < 12; ++i) {
|
|
const float a = float(i) / 12.f * 6.2831853f;
|
|
auto *pt = Light3D::createLight("point");
|
|
pt->setPosition(std::cos(a) * 1.4f, 0.55f, std::sin(a) * 1.4f);
|
|
pt->setColor(0.7f + 0.3f * std::cos(a), 0.7f, 0.7f + 0.3f * std::sin(a), 3.5f);
|
|
pt->setRadius(3.5f);
|
|
pt->setEnabled(true);
|
|
}
|
|
|
|
addHud();
|
|
warmPresent(gfx, 3);
|
|
|
|
const int w = gfx->getWidth();
|
|
const int h = gfx->getHeight();
|
|
const float mid = meanLumaRegion(gfx, w / 4, h / 4, (w * 3) / 4, (h * 3) / 4);
|
|
REQUIRE(mid > 0.04f);
|
|
const Color top = gfx->getPixel(w / 2, h / 4);
|
|
REQUIRE(top.g + 0.01f > top.r); // green marker stays in upper half
|
|
|
|
win->close();
|
|
}
|
|
|
|
TEST_CASE("RenderScenes.depth.nearOccludesFar") {
|
|
// Red cube in front of a larger green cube — center pixel must read red.
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx, 280, 200);
|
|
resetScene3D();
|
|
gfx->setScreenReadbackEnabled(true);
|
|
gfx->setBackgroundColor(Color(0.02f, 0.02f, 0.03f, 1.f));
|
|
|
|
Mesh *cube = loadCube(gfx);
|
|
auto *farGreen = Renderable3D::create();
|
|
farGreen->setMesh(cube);
|
|
farGreen->setTexture(makeSolid(gfx, 40, 210, 40));
|
|
farGreen->setPosition(0.f, 0.f, -0.6f);
|
|
farGreen->setScale(1.4f, 1.4f, 0.2f);
|
|
farGreen->setRoughness(0.9f);
|
|
|
|
auto *nearRed = Renderable3D::create();
|
|
nearRed->setMesh(cube);
|
|
nearRed->setTexture(makeSolid(gfx, 220, 40, 40));
|
|
nearRed->setPosition(0.f, 0.f, 0.4f);
|
|
nearRed->setScale(0.55f, 0.55f, 0.55f);
|
|
nearRed->setRoughness(0.9f);
|
|
|
|
auto *cam = Camera3D::createCamera();
|
|
cam->setEye(0.f, 0.15f, 3.2f);
|
|
cam->setTarget(0.f, 0.f, 0.f);
|
|
cam->setAmbient(0.3f, 0.3f, 0.3f);
|
|
|
|
auto *sun = Light3D::createLight("dir");
|
|
sun->setDirection(0.2f, 1.f, 0.3f);
|
|
sun->setColor(1.f, 1.f, 1.f, 1.5f);
|
|
sun->setEnabled(true);
|
|
|
|
addHud();
|
|
warmPresent(gfx, 3);
|
|
|
|
const Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
|
|
std::printf("RenderScenes.depth mid=(%.2f,%.2f,%.2f)\n", mid.r, mid.g, mid.b);
|
|
REQUIRE(mid.r > mid.g + 0.05f);
|
|
REQUIRE(mid.r > 0.08f);
|
|
|
|
win->close();
|
|
}
|
|
|
|
TEST_CASE("RenderScenes.scale.nonUniformKeepsUprightMarkers") {
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx, 320, 240);
|
|
resetScene3D();
|
|
gfx->setScreenReadbackEnabled(true);
|
|
gfx->setBackgroundColor(Color(0.02f, 0.02f, 0.03f, 1.f));
|
|
|
|
Mesh *sphere = gfx->newMeshSphere(16, 10);
|
|
auto *top = Renderable3D::create();
|
|
top->setMesh(sphere);
|
|
top->setTexture(makeSolid(gfx, 40, 220, 40));
|
|
top->setPosition(0.f, 1.15f, 0.f);
|
|
top->setScale(0.85f, 0.5f, 0.7f);
|
|
top->setRoughness(0.85f);
|
|
|
|
auto *bot = Renderable3D::create();
|
|
bot->setMesh(sphere);
|
|
bot->setTexture(makeSolid(gfx, 220, 40, 40));
|
|
bot->setPosition(0.f, -1.15f, 0.f);
|
|
bot->setScale(0.5f, 0.85f, 0.7f);
|
|
bot->setRoughness(0.85f);
|
|
|
|
auto *cam = Camera3D::createCamera();
|
|
cam->setEye(0.f, 0.f, 4.4f);
|
|
cam->setTarget(0.f, 0.f, 0.f);
|
|
cam->setAmbient(0.25f, 0.25f, 0.25f);
|
|
|
|
auto *sun = Light3D::createLight("dir");
|
|
sun->setDirection(0.25f, 1.f, 0.3f);
|
|
sun->setColor(1.f, 1.f, 1.f, 1.6f);
|
|
sun->setEnabled(true);
|
|
|
|
addHud();
|
|
warmPresent(gfx, 3);
|
|
|
|
const int w = gfx->getWidth();
|
|
const int h = gfx->getHeight();
|
|
const float topG = meanGreenRegion(gfx, w / 3, 0, (w * 2) / 3, h / 3);
|
|
const float topR = meanRedRegion(gfx, w / 3, 0, (w * 2) / 3, h / 3);
|
|
const float botR = meanRedRegion(gfx, w / 3, (h * 2) / 3, (w * 2) / 3, h);
|
|
const float botG = meanGreenRegion(gfx, w / 3, (h * 2) / 3, (w * 2) / 3, h);
|
|
REQUIRE(topG > topR + 0.03f);
|
|
REQUIRE(botR > botG + 0.03f);
|
|
|
|
win->close();
|
|
}
|
|
|
|
TEST_CASE("RenderScenes.checker.colorGridReadable") {
|
|
// 2x2 tinted cubes — quadrant regions must keep their dominant channel.
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx, 320, 240);
|
|
resetScene3D();
|
|
gfx->setScreenReadbackEnabled(true);
|
|
gfx->setBackgroundColor(Color(0.01f, 0.01f, 0.015f, 1.f));
|
|
|
|
Mesh *cube = loadCube(gfx);
|
|
struct Cell {
|
|
float x, y;
|
|
uint8_t r, g, b;
|
|
};
|
|
const Cell cells[4] = {
|
|
{-1.05f, 1.05f, 220, 40, 40},
|
|
{1.05f, 1.05f, 40, 220, 40},
|
|
{-1.05f, -1.05f, 40, 40, 220},
|
|
{1.05f, -1.05f, 220, 220, 40},
|
|
};
|
|
for (const auto &c : cells) {
|
|
auto *e = Renderable3D::create();
|
|
e->setMesh(cube);
|
|
e->setTexture(makeSolid(gfx, c.r, c.g, c.b));
|
|
e->setPosition(c.x, c.y, 0.f);
|
|
e->setScale(0.7f, 0.7f, 0.7f);
|
|
e->setRoughness(0.9f);
|
|
}
|
|
|
|
auto *cam = Camera3D::createCamera();
|
|
cam->setEye(0.f, 0.f, 4.5f);
|
|
cam->setTarget(0.f, 0.f, 0.f);
|
|
cam->setAmbient(0.35f, 0.35f, 0.35f);
|
|
|
|
auto *sun = Light3D::createLight("dir");
|
|
sun->setDirection(0.2f, 1.f, 0.25f);
|
|
sun->setColor(1.f, 1.f, 1.f, 1.2f);
|
|
sun->setEnabled(true);
|
|
|
|
addHud();
|
|
warmPresent(gfx, 3);
|
|
|
|
const int w = gfx->getWidth();
|
|
const int h = gfx->getHeight();
|
|
const float tlR = meanRedRegion(gfx, w / 8, h / 8, w * 3 / 8, h * 3 / 8);
|
|
const float tlG = meanGreenRegion(gfx, w / 8, h / 8, w * 3 / 8, h * 3 / 8);
|
|
const float trG = meanGreenRegion(gfx, w * 5 / 8, h / 8, w * 7 / 8, h * 3 / 8);
|
|
const float trR = meanRedRegion(gfx, w * 5 / 8, h / 8, w * 7 / 8, h * 3 / 8);
|
|
const float blB = meanLumaRegion(gfx, w / 8, h * 5 / 8, w * 3 / 8, h * 7 / 8); // proxy
|
|
(void)blB;
|
|
double sumB = 0.0;
|
|
int nB = 0;
|
|
for (int y = h * 5 / 8; y < h * 7 / 8; y += 4) {
|
|
for (int x = w / 8; x < w * 3 / 8; x += 4) {
|
|
sumB += gfx->getPixel(x, y).b;
|
|
++nB;
|
|
}
|
|
}
|
|
const float blBlue = nB ? float(sumB / nB) : 0.f;
|
|
const float brR = meanRedRegion(gfx, w * 5 / 8, h * 5 / 8, w * 7 / 8, h * 7 / 8);
|
|
const float brG = meanGreenRegion(gfx, w * 5 / 8, h * 5 / 8, w * 7 / 8, h * 7 / 8);
|
|
std::printf("RenderScenes.checker tlR=%.2f trG=%.2f blB=%.2f brR=%.2f\n", tlR, trG, blBlue,
|
|
brR);
|
|
REQUIRE(tlR > tlG + 0.03f);
|
|
REQUIRE(trG > trR + 0.03f);
|
|
REQUIRE(blBlue > 0.08f);
|
|
REQUIRE(brR > 0.08f);
|
|
REQUIRE(brG > 0.08f);
|
|
|
|
win->close();
|
|
}
|
|
|
|
TEST_CASE("RenderScenes.camera.moveKeepsUprightMarkers") {
|
|
// Moving the active camera must not invert world-Y markers.
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx, 320, 240);
|
|
resetScene3D();
|
|
gfx->setScreenReadbackEnabled(true);
|
|
gfx->setBackgroundColor(Color(0.02f, 0.02f, 0.03f, 1.f));
|
|
|
|
Mesh *sphere = gfx->newMeshSphere(16, 10);
|
|
auto *top = Renderable3D::create();
|
|
top->setMesh(sphere);
|
|
top->setTexture(makeSolid(gfx, 40, 220, 40));
|
|
top->setPosition(0.f, 1.0f, 0.f);
|
|
top->setScale(0.5f, 0.5f, 0.5f);
|
|
|
|
auto *bot = Renderable3D::create();
|
|
bot->setMesh(sphere);
|
|
bot->setTexture(makeSolid(gfx, 220, 40, 40));
|
|
bot->setPosition(0.f, -1.0f, 0.f);
|
|
bot->setScale(0.5f, 0.5f, 0.5f);
|
|
|
|
auto *cam = Camera3D::createCamera();
|
|
cam->setEye(0.f, 0.f, 4.2f);
|
|
cam->setTarget(0.f, 0.f, 0.f);
|
|
cam->setAmbient(0.25f, 0.25f, 0.25f);
|
|
|
|
auto *sun = Light3D::createLight("dir");
|
|
sun->setDirection(0.25f, 1.f, 0.3f);
|
|
sun->setColor(1.f, 1.f, 1.f, 1.5f);
|
|
sun->setEnabled(true);
|
|
|
|
addHud();
|
|
auto assertUpright = [&](const char *tag) {
|
|
warmPresent(gfx, 2);
|
|
const int w = gfx->getWidth();
|
|
const int h = gfx->getHeight();
|
|
const float topG = meanGreenRegion(gfx, w / 3, 0, (w * 2) / 3, h / 3);
|
|
const float topR = meanRedRegion(gfx, w / 3, 0, (w * 2) / 3, h / 3);
|
|
const float botR = meanRedRegion(gfx, w / 3, (h * 2) / 3, (w * 2) / 3, h);
|
|
const float botG = meanGreenRegion(gfx, w / 3, (h * 2) / 3, (w * 2) / 3, h);
|
|
std::printf("RenderScenes.cameraMove[%s] topG=%.2f botR=%.2f\n", tag, topG, botR);
|
|
REQUIRE(topG > topR + 0.03f);
|
|
REQUIRE(botR > botG + 0.03f);
|
|
};
|
|
assertUpright("near");
|
|
|
|
cam->setEye(0.35f, 0.25f, 4.8f);
|
|
cam->setTarget(0.f, 0.f, 0.f);
|
|
assertUpright("orbit");
|
|
|
|
win->close();
|
|
}
|
|
|
|
TEST_CASE("RenderScenes.shadow.receiveOffIgnoresUmbra") {
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx, 360, 270);
|
|
resetScene3D();
|
|
gfx->setScreenReadbackEnabled(true);
|
|
gfx->setBackgroundColor(Color(0.05f, 0.05f, 0.06f, 1.f));
|
|
|
|
Mesh *sphere = gfx->newMeshSphere(16, 10);
|
|
auto *ground = Renderable3D::create();
|
|
ground->setMesh(sphere);
|
|
ground->setTexture(makeSolid(gfx, 180, 180, 185));
|
|
ground->setPosition(0.f, -0.7f, 0.f);
|
|
ground->setScale(3.2f, 0.08f, 3.2f);
|
|
ground->setRoughness(0.95f);
|
|
ground->setCastShadow(false);
|
|
ground->setReceiveShadow(false); // should ignore caster umbra
|
|
|
|
auto *occ = Renderable3D::create();
|
|
occ->setMesh(sphere);
|
|
occ->setTexture(makeSolid(gfx, 40, 200, 40));
|
|
occ->setPosition(0.f, 0.55f, 0.f);
|
|
occ->setScale(0.5f, 0.5f, 0.5f);
|
|
occ->setCastShadow(true);
|
|
|
|
auto *cam = Camera3D::createCamera();
|
|
cam->setEye(2.8f, 2.2f, 3.6f);
|
|
cam->setTarget(0.f, 0.f, 0.f);
|
|
cam->setAmbient(0.08f, 0.08f, 0.09f);
|
|
|
|
auto *sun = Light3D::createLight("dir");
|
|
sun->setDirection(0.35f, 1.f, 0.25f);
|
|
sun->setColor(1.f, 0.98f, 0.94f, 2.8f);
|
|
sun->setCastShadow(true);
|
|
sun->setShadowStrength(1.f);
|
|
sun->setEnabled(true);
|
|
|
|
addHud();
|
|
warmPresent(gfx, 3);
|
|
|
|
const int w = gfx->getWidth();
|
|
const int h = gfx->getHeight();
|
|
const float mid = meanLumaRegion(gfx, w / 3, h / 2, (w * 2) / 3, (h * 5) / 6);
|
|
// Ground stays reasonably bright despite an active shadow caster above it.
|
|
REQUIRE(mid > 0.08f);
|
|
REQUIRE(meanGreenRegion(gfx, w / 5, 0, (w * 4) / 5, h / 2) > 0.04f);
|
|
|
|
win->close();
|
|
}
|
|
|