fix(lod): selectLod 最粗层兜底裁剪,保证返回区间恒不超 maxTextureDim
最粗层 fits||maxLevel 分支原先无条件返回,不校验 fits;合并体最粗层 全可见区间仍 >maxTextureDim 时返回区间会突破单纹理硬上限,C2 重组撞 GL 16384 纹理墙退回慢路。 新增 clampAxisToMaxTexture,在所有返回路径前按可见中心对称裁三轴到 重组单纹理各轴 <= maxTextureDim 的子区间;brick 本身 > maxTextureDim 的退化情形返回单块并由 C2 体素级再裁(契约见 hpp)。补 3 例边界测试 (brick>maxTextureDim、最粗层整卷超限、改写 RespectsMaxTextureDimAlways 为 brick!=maxTextureDim),原有用例全绿。
This commit is contained in:
parent
fa348a2a9f
commit
0da5accebe
|
|
@ -160,6 +160,28 @@ int axisTexture(int b0, int b1, int brick, int dimL) {
|
||||||
return std::max(0, end - start);
|
return std::max(0, end - start);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 把某轴的可见 brick 区间 [b0,b1) 裁到“重组单纹理 ≤ maxTextureDim”的子区间。
|
||||||
|
// 以可见区间中心为锚向两侧对称收缩 brick,宁可概览只显中心部分也绝不超限。
|
||||||
|
// 返回的子区间满足 axisTexture(...) ≤ maxTextureDim;恒保 b0<b1(至少 1 块)。
|
||||||
|
//
|
||||||
|
// 注意:当 brick 自身 > maxTextureDim 时,单块体素数已 > maxTextureDim,brick 粒度
|
||||||
|
// 无法表达;此时返回单块并由 C2 重组时按体素上限再裁(见 hpp 契约),即重组实际
|
||||||
|
// 体素数 = min(单块体素, maxTextureDim),仍恒 ≤ maxTextureDim。
|
||||||
|
void clampAxisToMaxTexture(int& b0, int& b1, int brick, int dimL,
|
||||||
|
int maxTextureDim) {
|
||||||
|
if (axisTexture(b0, b1, brick, dimL) <= maxTextureDim) return;
|
||||||
|
// 该 level 该轴每块 brick 体素,能容纳的最多整块数(至少 1 块)。
|
||||||
|
const int maxBricks = std::max(1, maxTextureDim / std::max(1, brick));
|
||||||
|
const int span = b1 - b0;
|
||||||
|
if (span <= maxBricks) return; // 块数已够小(仅 brick>maxTextureDim 时到这)。
|
||||||
|
// 以可见区间中心为锚,对称取 maxBricks 块。
|
||||||
|
const int center = (b0 + b1) / 2;
|
||||||
|
int newB0 = center - maxBricks / 2;
|
||||||
|
newB0 = std::clamp(newB0, b0, b1 - maxBricks);
|
||||||
|
b0 = newB0;
|
||||||
|
b1 = newB0 + maxBricks;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
LodSelection selectLod(const VolumeView& vol, const CameraView& cam,
|
LodSelection selectLod(const VolumeView& vol, const CameraView& cam,
|
||||||
|
|
@ -216,6 +238,12 @@ LodSelection selectLod(const VolumeView& vol, const CameraView& cam,
|
||||||
const bool fits =
|
const bool fits =
|
||||||
tx <= maxTextureDim && ty <= maxTextureDim && tz <= maxTextureDim;
|
tx <= maxTextureDim && ty <= maxTextureDim && tz <= maxTextureDim;
|
||||||
if (fits || level == maxLevel) {
|
if (fits || level == maxLevel) {
|
||||||
|
// 最粗层兜底:即使全可见区间仍 >maxTextureDim,也必须把区间裁到 ≤maxTextureDim
|
||||||
|
// 的中心子区间——单纹理快路绝不能 >maxTextureDim(撞 GL 16384 纹理墙)。
|
||||||
|
// 任何返回路径都先过 clamp,保证返回区间各轴重组恒 ≤ maxTextureDim。
|
||||||
|
clampAxisToMaxTexture(bx0, bx1, vol.brick, dimX, maxTextureDim);
|
||||||
|
clampAxisToMaxTexture(by0, by1, vol.brick, dimY, maxTextureDim);
|
||||||
|
clampAxisToMaxTexture(bz0, bz1, vol.brick, dimZ, maxTextureDim);
|
||||||
out.level = level;
|
out.level = level;
|
||||||
out.bx0 = bx0;
|
out.bx0 = bx0;
|
||||||
out.bx1 = bx1;
|
out.bx1 = bx1;
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,15 @@ struct LodSelection {
|
||||||
// 这样:远观→粗层(区间≈全体)、近观→细层(区间是视锥内小块),且各轴恒 ≤ maxTextureDim。
|
// 这样:远观→粗层(区间≈全体)、近观→细层(区间是视锥内小块),且各轴恒 ≤ maxTextureDim。
|
||||||
// 视距-层单调:相机拉近 → worldPerPixel 与视距均减 → Lmin 不增 → level 不增。
|
// 视距-层单调:相机拉近 → worldPerPixel 与视距均减 → Lmin 不增 → level 不增。
|
||||||
// 体在视锥外 → empty=true。
|
// 体在视锥外 → empty=true。
|
||||||
|
//
|
||||||
|
// **硬上限保证(架构命脉)**:返回的 brick 区间在选定 level 重组单纹理时,各轴体素数
|
||||||
|
// 恒 ≤ maxTextureDim——即使最粗层全可见区间仍 >maxTextureDim,也按可见中心裁成刚好
|
||||||
|
// ≤maxTextureDim 的中心子区间(宁可概览只显中心部分,绝不超限)。单纹理快路绝不能
|
||||||
|
// >maxTextureDim,否则撞 GL 纹理墙退回慢路。
|
||||||
|
// 退化情形:当 brick 本身 > maxTextureDim(单块体素数已超限,brick 粒度无法表达更小区间)
|
||||||
|
// 时返回单块,C2 重组须按体素上限再裁——重组实际体素数 = min(选定区间体素, maxTextureDim),
|
||||||
|
// 即重组纹理某轴起点对齐 b0*brick、终点取 min(b1*brick, dimL, b0*brick + maxTextureDim),
|
||||||
|
// 仍恒 ≤ maxTextureDim。
|
||||||
LodSelection selectLod(const VolumeView& vol, const CameraView& cam,
|
LodSelection selectLod(const VolumeView& vol, const CameraView& cam,
|
||||||
int maxTextureDim = 16384);
|
int maxTextureDim = 16384);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -113,27 +113,84 @@ TEST(ViewAdaptiveLod, NearViewPicksFineLevelSmallRegion) {
|
||||||
EXPECT_TRUE(yShrunk || zShrunk);
|
EXPECT_TRUE(yShrunk || zShrunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 契约里的“重组单纹理某轴体素数”:起点对齐 b0*brick,终点取
|
||||||
|
// min(b1*brick, dimL, b0*brick + maxTextureDim)(含 brick>maxTextureDim 的体素级再裁)。
|
||||||
|
// 生产代码保证此值恒 ≤ maxTextureDim;测试用同一公式焊死不变量。
|
||||||
|
static int reconstructedAxisTex(int b0, int b1, int brick, int dimL,
|
||||||
|
int maxTextureDim) {
|
||||||
|
const int start = b0 * brick;
|
||||||
|
const int end = std::min({b1 * brick, dimL, start + maxTextureDim});
|
||||||
|
return std::max(0, end - start);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int dimAt(int n, int level) {
|
||||||
|
return (n + (1 << level) - 1) >> level;
|
||||||
|
}
|
||||||
|
|
||||||
// ── 选定层重组区间各轴恒 ≤ maxTextureDim(小 maxTextureDim 强制升层/缩区间)──
|
// ── 选定层重组区间各轴恒 ≤ maxTextureDim(小 maxTextureDim 强制升层/缩区间)──
|
||||||
|
// brick=64、maxTextureDim=128(brick≠maxTextureDim,不靠 64=64 侥幸):允许 2 块/轴,
|
||||||
|
// 多于此须按中心裁掉,真正走多块 clamp 算术(而非边界相等巧合)。
|
||||||
TEST(ViewAdaptiveLod, RespectsMaxTextureDimAlways) {
|
TEST(ViewAdaptiveLod, RespectsMaxTextureDimAlways) {
|
||||||
const VolumeView v = makeVol();
|
const VolumeView v = makeVol(); // brick=64
|
||||||
// 多组视距 × 极小 maxTextureDim,断言选定层重组单纹理各轴 ≤ maxTextureDim。
|
const int kMax = 128; // ≠ brick;容 2 块,逼出多块中心裁剪
|
||||||
for (double dist : {60.0, 300.0, 1500.0, 8000.0, 40000.0}) {
|
for (double dist : {60.0, 300.0, 1500.0, 8000.0, 40000.0}) {
|
||||||
const CameraView c = lookFromX(v, dist);
|
const CameraView c = lookFromX(v, dist);
|
||||||
const LodSelection sel = selectLod(v, c, /*maxTextureDim=*/64);
|
const LodSelection sel = selectLod(v, c, kMax);
|
||||||
if (sel.empty) continue;
|
if (sel.empty) continue;
|
||||||
// 选定 level 下,区间重组单纹理各轴体素数 = (块数 × brick) 截到该 level 维度。
|
const int dimL = dimAt(512, sel.level);
|
||||||
const int dimL = (512 + (1 << sel.level) - 1) >> sel.level;
|
EXPECT_LE(reconstructedAxisTex(sel.bx0, sel.bx1, v.brick, dimL, kMax), kMax);
|
||||||
auto axisTex = [&](int b0, int b1) {
|
EXPECT_LE(reconstructedAxisTex(sel.by0, sel.by1, v.brick, dimL, kMax), kMax);
|
||||||
const int start = b0 * v.brick;
|
EXPECT_LE(reconstructedAxisTex(sel.bz0, sel.bz1, v.brick, dimL, kMax), kMax);
|
||||||
const int end = std::min(b1 * v.brick, dimL);
|
// 区间合法、非空。
|
||||||
return end - start;
|
EXPECT_LT(sel.bx0, sel.bx1);
|
||||||
};
|
EXPECT_LT(sel.by0, sel.by1);
|
||||||
EXPECT_LE(axisTex(sel.bx0, sel.bx1), 64);
|
EXPECT_LT(sel.bz0, sel.bz1);
|
||||||
EXPECT_LE(axisTex(sel.by0, sel.by1), 64);
|
|
||||||
EXPECT_LE(axisTex(sel.bz0, sel.bz1), 64);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── brick > maxTextureDim:单块体素数已超限,仍必须保证重组 ≤ maxTextureDim ──────
|
||||||
|
// brick=128、maxTextureDim=64:brick 粒度无法表达更小区间 → 返回单块 + C2 体素级再裁,
|
||||||
|
// 重组实际体素数 = min(单块, maxTextureDim) = 64 ≤ 64。各视距各轴恒不超限。
|
||||||
|
TEST(ViewAdaptiveLod, BrickLargerThanMaxTextureDimStillClamped) {
|
||||||
|
const VolumeView v = makeVol(/*n=*/512, /*brick=*/128, /*levels=*/4);
|
||||||
|
const int kMax = 64; // < brick=128
|
||||||
|
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, kMax);
|
||||||
|
if (sel.empty) continue;
|
||||||
|
const int dimL = dimAt(512, sel.level);
|
||||||
|
EXPECT_LE(reconstructedAxisTex(sel.bx0, sel.bx1, v.brick, dimL, kMax), kMax);
|
||||||
|
EXPECT_LE(reconstructedAxisTex(sel.by0, sel.by1, v.brick, dimL, kMax), kMax);
|
||||||
|
EXPECT_LE(reconstructedAxisTex(sel.bz0, sel.bz1, v.brick, dimL, kMax), kMax);
|
||||||
|
EXPECT_LT(sel.bx0, sel.bx1); // 恒至少一块
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 最粗层整卷 > maxTextureDim:远观也必须裁成子区间,绝不突破硬上限 ────────────
|
||||||
|
// nx=156544、levels=3 → 最粗层 dim=ceil(156544/4)=39136 > 16384;旧实现在最粗层无条件
|
||||||
|
// 返回会突破上限。修复后远观应裁成 ≤16384 的中心子区间,empty=false。
|
||||||
|
TEST(ViewAdaptiveLod, CoarsestLevelOverflowIsClampedNotBreached) {
|
||||||
|
VolumeView v{};
|
||||||
|
v.nx = v.ny = v.nz = 156544;
|
||||||
|
v.brick = 64;
|
||||||
|
v.levels = 3; // L0..L2;最粗层 dim = ceil(156544/4) = 39136 > 16384
|
||||||
|
v.origin[0] = v.origin[1] = v.origin[2] = 0.0;
|
||||||
|
v.spacing[0] = v.spacing[1] = v.spacing[2] = 1.0;
|
||||||
|
v.exagg = 1.0;
|
||||||
|
// 极远观:worldPerPixel 大 → 选最粗层;整卷入视锥。
|
||||||
|
const CameraView c = lookFromX(v, 5.0e6, 30.0, 1080);
|
||||||
|
const int kMax = 16384;
|
||||||
|
const LodSelection sel = selectLod(v, c, kMax);
|
||||||
|
ASSERT_FALSE(sel.empty);
|
||||||
|
const int dimL = dimAt(v.nx, sel.level);
|
||||||
|
EXPECT_LE(reconstructedAxisTex(sel.bx0, sel.bx1, v.brick, dimL, kMax), kMax);
|
||||||
|
EXPECT_LE(reconstructedAxisTex(sel.by0, sel.by1, v.brick, dimL, kMax), kMax);
|
||||||
|
EXPECT_LE(reconstructedAxisTex(sel.bz0, sel.bz1, v.brick, dimL, kMax), kMax);
|
||||||
|
EXPECT_LT(sel.bx0, sel.bx1);
|
||||||
|
EXPECT_LT(sel.by0, sel.by1);
|
||||||
|
EXPECT_LT(sel.bz0, sel.bz1);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(ViewAdaptiveLod, RespectsDefaultMaxTextureDim) {
|
TEST(ViewAdaptiveLod, RespectsDefaultMaxTextureDim) {
|
||||||
const VolumeView v = makeVol(/*n=*/4096, /*brick=*/256, /*levels=*/1);
|
const VolumeView v = makeVol(/*n=*/4096, /*brick=*/256, /*levels=*/1);
|
||||||
// levels=1(只有 level0)→ 不能升层,只能靠缩区间满足 16384。4096 < 16384 必满足。
|
// levels=1(只有 level0)→ 不能升层,只能靠缩区间满足 16384。4096 < 16384 必满足。
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue