geopro/tests/render/test_view_adaptive_lod.cpp

202 lines
7.4 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <gtest/gtest.h>
#include <cmath>
#include "lod/ViewAdaptiveLodPolicy.hpp"
using geopro::render::CameraView;
using geopro::render::LodSelection;
using geopro::render::selectLod;
using geopro::render::VolumeView;
namespace {
// 构造一个规整体nx=ny=nz=512、brick=64每轴 8 块、level0 间距=1、levels=4L0..L3
// 世界范围 [0,512]³origin 0。各 level 维度512/256/128/64间距 1/2/4/8。
VolumeView makeVol(int n = 512, int brick = 64, int levels = 4, double sp = 1.0) {
VolumeView v{};
v.nx = v.ny = v.nz = n;
v.brick = brick;
v.levels = levels;
v.origin[0] = v.origin[1] = v.origin[2] = 0.0;
v.spacing[0] = v.spacing[1] = v.spacing[2] = sp;
v.exagg = 1.0;
return v;
}
// 相机:从 +X 方向看体中心。dist = 相机到中心距离fov/viewportH 控分辨率密度。
CameraView lookFromX(const VolumeView& v, double dist, double fovYDeg = 30.0,
int viewportH = 1080) {
CameraView c{};
const double cx = v.origin[0] + 0.5 * v.nx * v.spacing[0];
const double cy = v.origin[1] + 0.5 * v.ny * v.spacing[1];
const double cz = v.origin[2] + 0.5 * v.nz * v.spacing[2];
c.focal[0] = cx;
c.focal[1] = cy;
c.focal[2] = cz;
c.pos[0] = cx + dist;
c.pos[1] = cy;
c.pos[2] = cz;
c.up[0] = 0;
c.up[1] = 0;
c.up[2] = 1;
c.fovYDeg = fovYDeg;
c.aspect = 1.0;
c.viewportH = viewportH;
return c;
}
} // namespace
// ── empty体完全在视锥外相机背对体→ empty ──────────────────────────────
TEST(ViewAdaptiveLod, VolumeBehindCameraIsEmpty) {
const VolumeView v = makeVol();
CameraView c = lookFromX(v, 1000.0);
// 把焦点设到相机背后:相机仍在 +X 远处,但看向 +X 更远(体在身后)。
c.focal[0] = c.pos[0] + 1000.0; // 视线朝 +X体在 -X 侧 → 视锥外
const LodSelection sel = selectLod(v, c);
EXPECT_TRUE(sel.empty);
}
TEST(ViewAdaptiveLod, VolumeOffToSideIsEmpty) {
const VolumeView v = makeVol();
// 相机看 +Z体在 -X..+X、相机在远 +X焦点正上方→ 体偏出窄视锥。
CameraView c{};
c.pos[0] = 100000;
c.pos[1] = 0;
c.pos[2] = 0;
c.focal[0] = 100000;
c.focal[1] = 0;
c.focal[2] = 100000; // 看 +Z
c.up[0] = 1;
c.up[1] = 0;
c.up[2] = 0;
c.fovYDeg = 5.0;
c.aspect = 1.0;
c.viewportH = 1080;
const LodSelection sel = selectLod(v, c);
EXPECT_TRUE(sel.empty);
}
// ── 远观整体 → 粗层、区间≈全体、各轴 ≤ maxTextureDim ───────────────────────
TEST(ViewAdaptiveLod, FarViewPicksCoarseLevelWholeVolume) {
const VolumeView v = makeVol();
// 很远 + 适中 fov整体入视锥worldPerPixel 大 → 粗层。
const CameraView c = lookFromX(v, 8000.0);
const LodSelection sel = selectLod(v, c);
EXPECT_FALSE(sel.empty);
EXPECT_GT(sel.level, 0); // 远 → 粗(非最细)
// 区间≈全体:覆盖所有块(该 level 块数)。
// 该 level 每轴块数 = ceil(ceil(512/2^L)/64)。
const int dimL = (512 + (1 << sel.level) - 1) >> sel.level;
const int bN = (dimL + v.brick - 1) / v.brick;
EXPECT_EQ(sel.bx0, 0);
EXPECT_EQ(sel.bx1, bN);
EXPECT_EQ(sel.by0, 0);
EXPECT_EQ(sel.by1, bN);
EXPECT_EQ(sel.bz0, 0);
EXPECT_EQ(sel.bz1, bN);
}
// ── 近观局部 → 细层、区间是视锥内小块、各轴 ≤ maxTextureDim ─────────────────
TEST(ViewAdaptiveLod, NearViewPicksFineLevelSmallRegion) {
const VolumeView v = makeVol();
// 贴近 + 窄 fov只看到体内一小块worldPerPixel 小 → 细层(0)、小区间。
const CameraView c = lookFromX(v, 60.0, 20.0, 1080);
const LodSelection sel = selectLod(v, c);
EXPECT_FALSE(sel.empty);
EXPECT_EQ(sel.level, 0); // 近 → 最细
// 区间是子集(不是全 8 块):至少某一垂直于视线的轴被裁小。
const int bNfull = (512 + v.brick - 1) / v.brick; // 8
const bool yShrunk = (sel.by1 - sel.by0) < bNfull;
const bool zShrunk = (sel.bz1 - sel.bz0) < bNfull;
EXPECT_TRUE(yShrunk || zShrunk);
}
// ── 选定层重组区间各轴恒 ≤ maxTextureDim小 maxTextureDim 强制升层/缩区间)──
TEST(ViewAdaptiveLod, RespectsMaxTextureDimAlways) {
const VolumeView v = makeVol();
// 多组视距 × 极小 maxTextureDim断言选定层重组单纹理各轴 ≤ maxTextureDim。
for (double dist : {60.0, 300.0, 1500.0, 8000.0, 40000.0}) {
const CameraView c = lookFromX(v, dist);
const LodSelection sel = selectLod(v, c, /*maxTextureDim=*/64);
if (sel.empty) continue;
// 选定 level 下,区间重组单纹理各轴体素数 = (块数 × brick) 截到该 level 维度。
const int dimL = (512 + (1 << sel.level) - 1) >> sel.level;
auto axisTex = [&](int b0, int b1) {
const int start = b0 * v.brick;
const int end = std::min(b1 * v.brick, dimL);
return end - start;
};
EXPECT_LE(axisTex(sel.bx0, sel.bx1), 64);
EXPECT_LE(axisTex(sel.by0, sel.by1), 64);
EXPECT_LE(axisTex(sel.bz0, sel.bz1), 64);
}
}
TEST(ViewAdaptiveLod, RespectsDefaultMaxTextureDim) {
const VolumeView v = makeVol(/*n=*/4096, /*brick=*/256, /*levels=*/1);
// levels=1只有 level0→ 不能升层,只能靠缩区间满足 16384。4096 < 16384 必满足。
const CameraView c = lookFromX(v, 100000.0);
const LodSelection sel = selectLod(v, c, 16384);
ASSERT_FALSE(sel.empty);
EXPECT_EQ(sel.level, 0);
const int texX = std::min((sel.bx1) * v.brick, 4096) - sel.bx0 * v.brick;
EXPECT_LE(texX, 16384);
}
// ── 视距-层单调:拉近 level 不增 ────────────────────────────────────────────
TEST(ViewAdaptiveLod, LevelMonotonicWithDistance) {
const VolumeView v = makeVol();
int prev = 1 << 30;
for (double dist : {40000.0, 8000.0, 1500.0, 300.0, 60.0}) { // 由远到近
const CameraView c = lookFromX(v, dist);
const LodSelection sel = selectLod(v, c);
if (sel.empty) continue;
EXPECT_LE(sel.level, prev) << "dist=" << dist; // 拉近 level 不增
prev = sel.level;
}
}
// ── 区间半开且合法b0<b1落在块数范围内────────────────────────────────
TEST(ViewAdaptiveLod, IntervalsAreValidHalfOpen) {
const VolumeView v = makeVol();
const CameraView c = lookFromX(v, 1500.0);
const LodSelection sel = selectLod(v, c);
ASSERT_FALSE(sel.empty);
const int dimL = (512 + (1 << sel.level) - 1) >> sel.level;
const int bN = (dimL + v.brick - 1) / v.brick;
EXPECT_GE(sel.bx0, 0);
EXPECT_LT(sel.bx0, sel.bx1);
EXPECT_LE(sel.bx1, bN);
EXPECT_GE(sel.by0, 0);
EXPECT_LT(sel.by0, sel.by1);
EXPECT_LE(sel.by1, bN);
EXPECT_GE(sel.bz0, 0);
EXPECT_LT(sel.bz0, sel.bz1);
EXPECT_LE(sel.bz1, bN);
}
// ── side 视角:从某一侧斜看,仍非空且区间合法 ─────────────────────────────
TEST(ViewAdaptiveLod, ObliqueSideViewNotEmpty) {
const VolumeView v = makeVol();
CameraView c{};
const double cx = 256, cy = 256, cz = 256;
c.pos[0] = cx + 1200;
c.pos[1] = cy + 1200;
c.pos[2] = cz + 800;
c.focal[0] = cx;
c.focal[1] = cy;
c.focal[2] = cz;
c.up[0] = 0;
c.up[1] = 0;
c.up[2] = 1;
c.fovYDeg = 45.0;
c.aspect = 1.6;
c.viewportH = 1080;
const LodSelection sel = selectLod(v, c);
EXPECT_FALSE(sel.empty);
EXPECT_GE(sel.level, 0);
EXPECT_LT(sel.bx0, sel.bx1);
}