Files
EVEngine/test/ParallaxMap.cpp
西风逍遥游andCursor Agent 0f14ca97e5 SilPOM vs SSDM: restore edge protrusion, keep mid solid (#399)
* Add SilPOM and SSDM comparison helpers plus demo

Expose silhouette-POM coverage and educational SSDM screen-offset
references on CPU/GLSL, and add a three-panel example that contrasts
classic POM, SilPOM UV clipping, and view-space SSDM-style relief.

Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com>

* Polish SilPOM/SSDM demo camera and UBO layout

Disable auto-orbit by default, use a frontal grazing pose for clearer
side-by-side comparison, shrink procedural textures for faster boot, and
align the compare shader Frame UBO with the full mesh3d layout.

Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com>

* Ignore runtime eve_inspect_cache for SilPOM/SSDM demo

Remove the generated MCP inspect frame artifacts from the example tree
and ignore that cache directory going forward.

Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com>

* Fix SSDM floor occlusion in the SilPOM comparison demo.

Write Vulkan ZO FragDepth without the OpenGL remap, never punch holes on
SSDM misses, seat the cards slightly above the floor, and fade relief at
the chart bottom so parallax no longer looks like the floor covers the wall.

Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com>

* Seal SSDM floor contact while keeping side silhouettes.

Out-of-chart SSDM hits discard on the left/right/top edges for silhouette,
but the bottom edge falls back to POM so the floor cannot show through the
contact line. Also soften rim lighting and fade relief near the chart base.

Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com>

* Switch SilPOM/SSDM demo to two-face dihedral corners.

A crease is where SilPOM chart discard opens gaps while SSDM-style
FragDepth keeps the intersection filled; each face is its own UV chart.

Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com>

* Drop unused flat-card ssdmSample from the corner compare shader.

Corner UV comes from per-face TBN/POM; the old local.xy chart helper
assumed a single XY plane and was dead after the dihedral switch.

Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com>

* Switch SilPOM/SSDM demo to brick cylinders for seam comparison.

Cylinder UV charts wrap in U (seam at 0/1) and open at top/bottom; SilPOM
discards off-chart samples while SSDM wraps U and keeps the seam filled via
FragDepth.

Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com>

* Implement full planar SilPOM and SSDM algorithms.

Replace the teaching approximations with steep POM + soft chart coverage,
horizon trim, height normals, self-shadow, and FragDepth for SilPOM, and
model-space heightfield raymarch through an extruded slab for planar SSDM.

Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com>

* Fix SilPOM mid-panel hollow mortar (镂空).

Horizon-trim + softCoverage discard punched holes through low mortar, and
packing softCoverage into the SSDM hit flag caused false misses. Switch
SilPOM to a solid planar heightfield march with border-only feather,
front-face miss fill, softer brick slopes, and forced texture rebuild.

Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com>

* Fix SilPOM mid-panel 镂空 from horizon discard and cliff tunnels.

SilPOM was discarding mortar via softCoverage×horizonTrim, and packing
softCoverage into the SSDM hit flag caused false misses. Both silhouette
modes now share a solid planar heightfield march with chart-fill on miss,
no coverage discard, softer brick ramps, texture/shader version invalidation,
and sendFloat integer casts so the demo stays alive under persist.

Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com>

* Clarify SilPOM mid-panel no longer uses horizon-trim discard.

Document that SilPOM and SSDM share the solid planar heightfield march;
remaining mid-panel shelling at grazing angles is view-dependent.

Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com>

* Document SilPOM hollow root cause and shared solid march.

Explain that horizon-trim discard caused mortar punch-through, and that
remaining mid-panel shelling at grazing angles is view-dependent.

Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com>

* Restore SilPOM/SSDM edge protrusion without interior 镂空.

SilPOM again uses steep POM with chart-border limb discard (not full-face
horizon trim). SSDM chart-fills front misses only; side misses discard so
brick caps form the extruded silhouette. Sharper brick heights and thicker
slabs bring the edge pop back.

Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com>

* Restore edge protrusion via side-face heightfield hits.

Hollow-fix chartFill on slab sides flattened the silhouette to a
rectangle. Side misses discard again so brick caps extrude; SilPOM keeps a
jagged brick-only side limb while SSDM keeps a continuous thickness
profile. Front mortar stays opaque (no rim height discard). Thicker slabs
and a slightly more oblique default camera make the edge pop visible.

Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com>

* Fix SilPOM/SSDM demo: hard flat bricks, no pillow height.

Soft sine height and soft mortar ramps made every brick a blob. Use binary
flat plateaus at 512² nearest, keep front geometric depth (no cliff shells),
and differentiate SilPOM jagged brick-cap limbs from SSDM continuous sides.

Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com>

* Differentiate SilPOM (POM front) from SSDM march; tame POM grazing.

SilPOM now shades the front with steep POM and only uses side-face
heightfield hits for jagged brick-cap limbs. SSDM keeps the full
geometric march. Grazing fade + linear albedo reduce left-card zebra
from hard cliff sampling; height stays nearest for flat plateaus.

Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com>

* Solid SilPOM front march again; adaptive cliffs; 1-texel bevel.

POM-on-front shredded mid-panel cliff walls versus SSDM. Share the solid
heightfield march for SilPOM/SSDM (side brick-cap discard still differentiates),
add adaptive near-surface steps, and a one-texel mortar bevel to cut POM zebra
without soft pillows.

Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com>

* Tame classic POM relief scale on the left compare card.

Hard cliff heights still zebra under full POM scale; send about half
relief to mode 0 while SilPOM/SSDM keep the full slab thickness.

Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com>

* List silpom-ssdm-compare in the examples overview table.

CI Source quality runs check_examples.py; the new demo directory was
missing from examples/README.md, which fails the example contract.

Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-09-18 11:29:17 +08:00

87 lines
2.9 KiB
C++

#include "zeroerr/assert.h"
#include "zeroerr/unittest.h"
#include <cmath>
#include "graphics/ParallaxMap.h"
using eve::graphics::ParallaxParams;
using eve::graphics::clampParallaxParams;
using eve::graphics::parallaxOffsetUV;
using eve::graphics::silPomCoverage;
using eve::graphics::silPomCoverageSoft;
using eve::graphics::silPomHorizonTrim;
using eve::graphics::ssdmCoverage;
using eve::graphics::ssdmScreenOffset;
TEST_CASE("ParallaxMap.scaleZeroIsIdentity") {
float ou = -1.f, ov = -1.f;
parallaxOffsetUV(0.4f, 0.6f, 0.2f, 0.5f, -0.3f, 0.8f, 0.f, ou, ov);
CHECK(std::fabs(ou - 0.4f) < 1e-6f);
CHECK(std::fabs(ov - 0.6f) < 1e-6f);
}
TEST_CASE("ParallaxMap.heightDisplacesTowardViewXY") {
// Low height → deep → larger displacement along -viewXY.
float ouLo = 0.f, ovLo = 0.f;
float ouHi = 0.f, ovHi = 0.f;
parallaxOffsetUV(0.5f, 0.5f, 0.0f, 0.4f, 0.0f, 0.9f, 0.1f, ouLo, ovLo);
parallaxOffsetUV(0.5f, 0.5f, 1.0f, 0.4f, 0.0f, 0.9f, 0.1f, ouHi, ovHi);
CHECK(ouLo < ouHi); // deeper surface shifts UV more opposite +viewX
CHECK(std::fabs(ovLo - 0.5f) < 1e-5f);
CHECK(std::fabs(ovHi - 0.5f) < 1e-5f);
}
TEST_CASE("ParallaxMap.clampParams") {
ParallaxParams p;
p.scale = 1.f;
p.minLayers = 0.f;
p.maxLayers = 100.f;
clampParallaxParams(p);
CHECK(p.scale == 0.25f);
CHECK(p.minLayers == 1.f);
CHECK(p.maxLayers == 64.f);
p.scale = -1.f;
p.minLayers = 16.f;
p.maxLayers = 8.f;
clampParallaxParams(p);
CHECK(p.scale == 0.f);
CHECK(p.maxLayers == 16.f);
}
TEST_CASE("ParallaxMap.silPomCoverageClipsOutsideChart") {
CHECK(silPomCoverage(0.5f, 0.5f, 0.f) == 1.f);
CHECK(silPomCoverage(0.0f, 0.0f, 0.f) == 1.f);
CHECK(silPomCoverage(1.0f, 1.0f, 0.f) == 1.f);
CHECK(silPomCoverage(-0.01f, 0.5f, 0.f) == 0.f);
CHECK(silPomCoverage(0.5f, 1.01f, 0.f) == 0.f);
// Padding expands the keep region.
CHECK(silPomCoverage(-0.01f, 0.5f, 0.02f) == 1.f);
}
TEST_CASE("ParallaxMap.silPomSoftCoverageAndHorizon") {
CHECK(silPomCoverageSoft(0.5f, 0.5f, 0.05f) == 1.f);
CHECK(silPomCoverageSoft(-0.01f, 0.5f, 0.05f) == 0.f);
const float edge = silPomCoverageSoft(0.02f, 0.5f, 0.05f);
CHECK(edge > 0.f);
CHECK(edge < 1.f);
// Face-on: keep low height. Grazing: clip low height, keep high height.
CHECK(silPomHorizonTrim(0.1f, 1.0f, 0.45f) == 1.f);
CHECK(silPomHorizonTrim(0.1f, 0.05f, 0.45f) == 0.f);
CHECK(silPomHorizonTrim(0.95f, 0.05f, 0.45f) == 1.f);
}
TEST_CASE("ParallaxMap.ssdmOffsetScalesWithHeight") {
float ox0 = 0.f, oy0 = 0.f;
float ox1 = 0.f, oy1 = 0.f;
ssdmScreenOffset(0.0f, 1.0f, 0.0f, 0.05f, ox0, oy0);
ssdmScreenOffset(1.0f, 1.0f, 0.0f, 0.05f, ox1, oy1);
CHECK(std::fabs(ox0) < 1e-6f);
CHECK(std::fabs(oy0) < 1e-6f);
CHECK(std::fabs(ox1 - 0.05f) < 1e-5f);
CHECK(std::fabs(oy1) < 1e-6f);
CHECK(ssdmCoverage(0.9f, 0.5f, 0.15f, 0.f, 0.f) == 0.f);
CHECK(ssdmCoverage(0.9f, 0.5f, 0.05f, 0.f, 0.f) == 1.f);
}