Files
EVEngine/test/graphics_pbr_surface.cpp

163 lines
7.5 KiB
C++

#include <array>
#include <limits>
#include <memory>
#include <vector>
#include "graphics/Canvas.h"
#include "graphics/Graphics.h"
#include "graphics/Light.h"
#include "graphics/Material.h"
#include "graphics/Mesh.h"
#include "graphics/PbrSurface.h"
#include "image/ImageData.h"
#include "zeroerr/assert.h"
#include "zeroerr/unittest.h"
using namespace eve::graphics;
TEST_CASE("graphics.pbrSurface.atomicValidationAndAbsentBackend") {
Material material;
PbrSurface surface;
surface.specularFactor = .25f;
REQUIRE(material.setPbrSurface(surface).ok());
material.setShadingModel("unlit");
REQUIRE(!material.getReceiveLight());
material.setShadingModel("pbr");
REQUIRE(material.getReceiveLight());
surface.specularFactor = 2;
REQUIRE(!material.setPbrSurface(surface).ok());
REQUIRE_EQ(material.pbrSurface().specularFactor, .25f);
surface.specularFactor = .5f;
surface.textures[0].rotation = std::numeric_limits<float>::quiet_NaN();
REQUIRE(!validatePbrSurface(surface).ok());
auto* gfx = Graphics::create();
REQUIRE(gfx->Graphics::setMesh3DPbrSurface(nullptr).ok());
surface = PbrSurface{};
REQUIRE(!gfx->Graphics::setMesh3DPbrSurface(&surface).ok());
}
TEST_CASE("graphics.pbrSurface.extensionFactorsAndUvChangePixels") {
auto* gfx = Graphics::create();
REQUIRE(gfx != nullptr);
gfx->initHeadless(128, 128);
auto* canvas = gfx->newCanvas(128, 128);
REQUIRE(canvas != nullptr);
const float positions[] = {-.9f, -.9f, .5f, .9f, -.9f, .5f, .9f, .9f, .5f, -.9f, .9f, .5f};
const float normals[] = {0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1};
const float uv[] = {.1f, .1f, .1f, .1f, .1f, .1f, .1f, .1f};
const float uv1[] = {.9f, .1f, .9f, .1f, .9f, .1f, .9f, .1f};
const uint32_t indices[] = {0, 2, 1, 0, 3, 2};
auto* mesh = gfx->newMeshFromArrays(positions, normals, uv, 4, indices, 6);
REQUIRE(mesh != nullptr);
REQUIRE(mesh->setTexcoordSet(1, uv1).ok());
gfx->setMesh3DViewProj(glm::mat4(1));
gfx->setMesh3DView(glm::mat4(1));
gfx->setMesh3DCameraPos({.35f, .2f, 2});
Lighting3DPack lighting{};
lighting.count = 1;
lighting.ambient = {.15f, .15f, .15f, 0};
lighting.lights[0].posRadius = {.3f, .2f, 1, 0};
lighting.lights[0].color = {2, 2, 2, 0};
auto render = [&](const PbrSurface& s) {
gfx->begin3DFrameToCanvas(canvas);
gfx->setMesh3DLighting(lighting);
gfx->setMesh3DMaterial(.15f, .4f);
gfx->setMesh3DSurface(SurfaceMode::Opaque, BlendMode::Opaque, true, true, .5f, "cutoff");
REQUIRE(gfx->setMesh3DPbrSurface(&s).ok());
gfx->drawMesh(mesh, glm::mat4(1), nullptr, Color(.2f, .15f, .1f, 1));
REQUIRE(gfx->setMesh3DPbrSurface(nullptr).ok());
gfx->end3DFrameToCanvas();
std::unique_ptr<eve::image::ImageData> pixels(canvas->newImageData());
REQUIRE(pixels != nullptr);
const auto* data = static_cast<const unsigned char*>(pixels->getData());
return std::vector<unsigned char>(data, data + 128 * 128 * 4);
};
auto difference = [](const auto& a, const auto& b) {
size_t changed = 0;
for (size_t i = 0; i < a.size(); i += 4)
if (std::abs(int(a[i]) - int(b[i])) + std::abs(int(a[i + 1]) - int(b[i + 1])) +
std::abs(int(a[i + 2]) - int(b[i + 2])) >
5)
++changed;
return changed;
};
PbrSurface base;
const auto reference = render(base);
for (int extension = 0; extension < 6; extension++) {
auto s = base;
if (extension == 0) s.specularFactor = 0;
if (extension == 1) {
s.anisotropyStrength = 1;
s.anisotropyRotation = .6f;
}
if (extension == 2) {
s.clearcoatFactor = 1;
s.clearcoatRoughness = .2f;
}
if (extension == 3) {
s.emissive = {.15f, .02f, .01f};
s.emissiveStrength = 3;
}
if (extension == 4) s.ior = 2.5f;
if (extension == 5) s.unlit = true;
auto pixels = render(s);
std::fprintf(stderr, "PBR extension %d changed %zu pixels\n", extension, difference(reference, pixels));
REQUIRE(difference(reference, pixels) > 100);
}
const uint8_t colors[] = {255, 0, 0, 255, 0, 255, 0, 255};
auto* texture = gfx->newTexture(2, 1, colors);
REQUIRE(texture != nullptr);
auto s = base;
s.unlit = true;
s.textures[0].texture = texture;
s.textures[0].minFilter = 9728;
s.textures[0].magFilter = 9728;
// Begin with the vertex UV0 path so the first UV1 draw also exercises buffer growth.
auto red = render(s);
s.textures[0].texcoord = 1;
const auto firstGreen = render(s);
s.textures[0].texcoord = 0;
REQUIRE_EQ(difference(red, render(s)), size_t(0));
s.textures[0].texcoord = 1;
auto green = render(s);
const size_t center = (64 * 128 + 64) * 4;
std::fprintf(stderr, "PBR UV0 RGB=%u,%u,%u UV1 RGB=%u,%u,%u changed=%zu\n", unsigned(red[center]),
unsigned(red[center + 1]), unsigned(red[center + 2]), unsigned(green[center]),
unsigned(green[center + 1]), unsigned(green[center + 2]), difference(red, green));
s.textures[0].texcoord = 0;
s.textures[0].offset = {.8f, 0};
auto transformed = render(s);
std::fprintf(stderr, "PBR first UV1 RGB=%u,%u,%u transformed UV0 RGB=%u,%u,%u\n", unsigned(firstGreen[center]),
unsigned(firstGreen[center + 1]), unsigned(firstGreen[center + 2]), unsigned(transformed[center]),
unsigned(transformed[center + 1]), unsigned(transformed[center + 2]));
REQUIRE(difference(red, green) > 1000);
REQUIRE_EQ(difference(firstGreen, green), size_t(0));
REQUIRE_EQ(difference(green, transformed), size_t(0));
// Every frame owns its parameter and descriptor snapshot; repeated changes must stay deterministic.
s.textures[0].offset = {0, 0};
REQUIRE_EQ(difference(red, render(s)), size_t(0));
REQUIRE_EQ(difference(reference, render(base)), size_t(0));
// Channel semantics are observable: each texture role is compared against an absent binding.
const uint8_t sample[] = {30, 190, 90, 80};
auto* map = gfx->newTexture(1, 1, sample);
REQUIRE(map != nullptr);
for (size_t role = 0; role < 11; role++) {
auto plain = base;
plain.emissive = {.2f, .2f, .2f};
plain.clearcoatFactor = 1;
plain.clearcoatRoughness = .5f;
plain.anisotropyStrength = .8f;
const auto without = render(plain);
plain.textures[role].texture = map;
auto with = render(plain);
std::fprintf(stderr, "PBR texture %zu changed %zu pixels\n", role, difference(without, with));
REQUIRE(difference(without, with) > 100);
}
const uint16_t joints[] = {203, 0, 0, 0, 203, 0, 0, 0, 203, 0, 0, 0, 203, 0, 0, 0};
const float weights[] = {1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0};
REQUIRE(gfx->setMeshSkinningData(mesh, joints, weights, 4));
std::vector<float> palette(204 * 16, 0);
for (int j = 0; j < 204; j++)
for (int k : {0, 5, 10, 15}) palette[j * 16 + k] = 1;
palette[203 * 16 + 12] = .3f;
REQUIRE(mesh->setSkinPalette(palette.data(), 204).ok());
REQUIRE(difference(reference, render(base)) > 100);
}