Journal

dev-log · WIP · ship notes — not the CV

← Journal

Parallax Occlusion Mapping: Height March, Flat Silhouette

The silhouette is still the quad. FLAT | BUMP | POM graze, then the XOR lie — sil_xor≈0.575, MAE=0 face-on.

Share

The previous note focused on the UV ellipse covered by a single pixel, establishing our baseline for texture sampling. This entry does not introduce a new sampler; instead, it shifts focus to the geometric illusion of depth. By utilizing a height field, we can offset UV coordinates along the tangent-space view ray to convince the eye that a flat, two-triangle quad is actually deeply carved brick. The combination of interior parallax and an optional height-field self-shadowing routine closely mimics true volumetric depth. However, this approach has a strict physical limitation: the outline of the mesh cannot change. Ultimately, the silhouette is still the quad.

Same brick wall, raking light, three shaders. Left FLAT: painted card, mortar wells fully visible. Middle BUMP: grooves shade, grout albedo stays in the wells, bricks do not slide. Right POM N=32: interior bricks slide, mortar hides. Photograph only — no UV-error metric.
Same brick wall, raking light, three shaders. Left FLAT: painted card, mortar wells fully visible. Middle BUMP: grooves shade, grout albedo stays in the wells, bricks do not slide. Right POM N=32: interior bricks slide, mortar hides. Photograph only — no UV-error metric.

Consider the same brick wall under identical lighting conditions across three different shader implementations. On the left, FLAT rendering uses standard geometric n\mathbf{n} and base UV u0\mathbf{u}_0 to produce what is effectively a painted card where mortar wells remain fully visible regardless of angle. In the middle, BUMP mapping applies height-derived normals at the geometric UV. While the grooves receive realistic shading, the bricks themselves do not exhibit perspective shift or "walk" as the view angle changes. On the right, POM (Parallax Occlusion Mapping) utilizes an N=32N=32 roof-hull march. Here, interior bricks slide correctly in perspective, and mortar realistically hides in the wells just as it should. Note that this visual comparison is strictly a photograph; do not attempt to derive a formal UV-error number from this frame.

Bump vs POM, harder graze. Grain and grout slide on the right. Caption: not real displacement. Photograph only.
Bump vs POM, harder graze. Grain and grout slide on the right. Caption: not real displacement. Photograph only.

The visual takeaway here serves as the core thesis of this post: when comparing bump mapping against POM at a severe raking graze, the depth is striking, but it is unequivocally not real displacement.

Unique artifact. Left: CPU displaced grid, floor authorship, proud-brick profile. Middle: POM on the roof quad. Right: coverage XOR in red. sil_xor≈0.575, AABB span≈0.1415. The silhouette is still the quad.
Unique artifact. Left: CPU displaced grid, floor authorship, proud-brick profile. Middle: POM on the roof quad. Right: coverage XOR in red. sil_xor≈0.575, AABB span≈0.1415. The silhouette is still the quad.

We can precisely measure this structural lie. On the left, proud bricks generated by a CPU-displaced grid physically extend the mesh's outline. In the middle, the POM quad remains a fundamentally flat card. The red highlight on the right illustrates the coverage discrepancy: sil=Cdisp xor Cquad\mathrm{sil}=C_{\mathrm{disp}}\ \mathrm{xor}\ C_{\mathrm{quad}}. That calculated fractional difference is ≈0.575\approx 0.575. The AABB span of ≈0.141\approx 0.141 serves as our mathematical proof of extrusion, verifying that the displaced grid genuinely left the basal plane. While interior pixels can register as a highly accurate POM hit, the actual screen-space coverage cannot organically grow beyond the base triangles.

Our hero configuration locks the scale at s=0.08s=0.08 with a bias of 00, utilizing a fixed step count of N=32N=32. The height map format is R32F, executed on Mesa 25.0.7 llvmpipe. When viewed directly face-on, the Mean Absolute Error between bump mapping and POM is perfectly zero: MAE(bump, POM) =0=\mathbf{0}. We measured analytic UV error on a spherical indent interior at a 55∘55^\circ angle, demonstrating a clear convergence: stepping from N=4N=4 at 4.31×10−54.31\times 10^{-5} →\to N=32N=32 yields 8.24×10−78.24\times 10^{-7}. At a grazing angle, the silhouette XOR (comparing the displaced floor against the POM roof quad) is ≈0.575\approx 0.575. Throughout this testing run, internal validation remained pristine: 28 pass / 0 fail.


How it presents

The primary stimulus for these tests is a CPU-authored running-bond brick texture, sized at 102421024^2 POT, utilizing REPEAT wrapping. It adheres strictly to the roof convention, where a height of h=1h=1 is fully flush with the bounding mesh, and h=0h=0 represents the deepest carved recess. For our rigorous scientific instruments, we employ a spherical indent (set to CLAMP_TO_EDGE) alongside a 1-D cosine ridge. The wall material itself is procedural clay, heavily layered with per-brick families, grog, and stria to ensure the UV sliding behavior has enough high-frequency detail to remain visible. It is deliberately neither a photo scan nor a standard PBR brick.

FLAT | BUMP | POM at a moderate angle (ang≈22°). Interior parallax already slides before the graze hero. Photograph only.
FLAT | BUMP | POM at a moderate angle (ang≈22°). Interior parallax already slides before the graze hero. Photograph only.

As shown above, interior parallax successfully creates the illusion of sliding geometry well before reaching the extreme grazing angles of the hero shot. This remains a photograph-only observation.

The rendering behavior strictly adheres to two regimes that must never be mixed:

  1. Face-on (v^z≈1\hat{v}_z \approx 1, vxy≈0\mathbf{v}_{xy}\approx\mathbf{0}): The calculated UV offset is ∼0\sim 0. In this orientation, bump mapping, offset mapping, steep parallax, and POM must match perfectly, sharing both the same normals and the exact same UV coordinates. Our scientific baseline confirms MAE(bump, POM) =0=\mathbf{0}. If a so-called "POM" implementation somehow looks more 3-D from a dead-on perspective, either the TBN sign is inverted, or an erroneous residual offset is being applied.

  2. Graze (v^z→0+\hat{v}_z\to 0^+): This angle is the primary focus of the article. Here, interior parallax delivers the highly sought-after volumetric "wow" factor, while the silhouette's disagreement with the mathematically displaced mesh reveals the inherent lie of the technique. Utilizing too few layer iterations in this regime directly results in texture swimming.

It is important to remember that a software rasterizer does not inherently have to animate to show flaws. Texture swimming at a completely locked pose manifests as visible banding and skipped mortar wells. Furthermore, the silhouette failure at a locked pose becomes painfully obvious as a perfectly straight polygonal edge resting right next to a seemingly complex, bumpy brick profile.

Fundamentally, standard height maps cannot represent overhangs, as they are strictly defined as 2D functions h(u,v)h(u,v). Any self-occlusion observed in these renders strictly means "this specific ridge visually blocks that well from this specific ray direction," rather than representing a volumetric cave or true undercut.


Why: TBN, δuv\boldsymbol{\delta}_{uv}, POM lerp

TBN and tangent-space view

To correctly warp our UVs, we first require an orthonormal tangent frame evaluated at the fragment, with its columns defined in world space. We strictly lock the handedness to a right-handed coordinate system, enforcing n=t×b\mathbf{n}=\mathbf{t}\times\mathbf{b}.

T=[t b n],vw=e−p,v=T⊤vw,v^=v/∥v∥.T=\bigl[\mathbf{t}\ \mathbf{b}\ \mathbf{n}\bigr], \qquad \mathbf{v}_w=\mathbf{e}-\mathbf{p}, \qquad \mathbf{v}=T^\top\mathbf{v}_w, \qquad \hat{\mathbf{v}}=\mathbf{v}/\lVert\mathbf{v}\rVert.

The scientific TBN matrix evaluated here is strictly CPU-generated, derived either directly from the quad or the displaced-grid vertex. This ensures it is the exact same matrix provided to the shader as vertex attributes. You must not trust an llvmpipe dFdx calculation of the world position to act as your precision science TBN.

For the front face calculation: v^z=v^⋅n>0\hat{v}_z=\hat{\mathbf{v}}\cdot\mathbf{n}>0. If v^z≤0\hat{v}_z\le 0, the ray is pointing away, and we immediately skip the march.

Height convention (roof default)

By default, the geometric quad acts as the outer hull stationed at a normalized height of 11. The sampled texture htex∈[0,1]h_{\mathrm{tex}}\in[0,1] represents the structural elevation of the actual surface: where 1=1= flush with the hull, and 0=0= deeply carved in.

h=clamp(htex+β, 0, 1).h=\mathrm{clamp}(h_{\mathrm{tex}}+\beta,\,0,\,1).

Here, β\beta represents a height bias, which is firmly locked to 00 for the entirety of this run. The scale factor s>0s>0 dictates the physical height range, measured in units of the quad’s uu-edge world length. Our locked hero metric requires s=0.08s=0.08. The required lateral UV travel to account for a unit drop in normalized height is given by:

δuv=v^xyv^z⋅s.\boldsymbol{\delta}_{uv} = \frac{\hat{\mathbf{v}}_{xy}}{\hat{v}_z}\cdot s.

Proud-brick silhouette control: We establish a secondary authored mesh stationed at the floor (height 00), with individual bricks geometrically extruding outward to 11. This uses the exact same scale ss. This secondary geometry forms the literal outline that should organically grow under POM, but mathematically does not.

Handling misses: if the cast ray manages to completely traverse the internal volume without intersecting the height field, we gracefully fallback and sample the floor at t=1t=1.

Ray in the height volume

The ray begins its journey with its origin at the geometric hit location, stationed on the roof where H=1H=1, mapping to initial UV coordinate u0\mathbf{u}_0. The progressing parameter t∈[0,1]t\in[0,1] defines the normalized depth driven into the volume, equating to a direct drop in height:

r(t)=(u0−t δuv,  1−t).\mathbf{r}(t)=\bigl(\mathbf{u}_0 - t\,\boldsymbol{\delta}_{uv},\; 1-t\bigr).

A confirmed hit is registered at the absolute smallest value of t∈[0,1]t\in[0,1] that satisfies the inequality 1−t≤h(u0−tδuv)1-t \le h(\mathbf{u}_0-t\boldsymbol{\delta}_{uv}).

Offset mapping — named knife, not hero

When discussing true parallax mapping (Kaneko-style), which relies on a single sample, the UV perturbation follows:

u′=u0−δuv (1−h(u0)).\mathbf{u}'=\mathbf{u}_0-\boldsymbol{\delta}_{uv}\,(1-h(\mathbf{u}_0)).

Traditional offset limiting (often associated with Welsh) deliberately drops the 1/v^z1/\hat{v}_z term, resulting in a modified vector where δuv∝v^xy s\boldsymbol{\delta}_{uv}\propto\hat{\mathbf{v}}_{xy}\,s. At severe grazing angles, true single-sample offset explodes into visual chaos, whereas the limited offset inherently under-walks the surface. Both historical methods belong documented on the family strip for context, but neither serves as the modern hero technique.

Steep parallax — even layers, first crossing

Steep parallax mapping discretizes the volume into uniform strata:

Δt=1N,ui=u0−iΔt δuv,Hi=1−iΔt,i=0,…,N.\Delta t=\frac{1}{N},\qquad \mathbf{u}_i=\mathbf{u}_0-i\Delta t\,\boldsymbol{\delta}_{uv},\qquad H_i=1-i\Delta t,\qquad i=0,\ldots,N.

A steep hit is defined simply as the smallest layer index i≥1i\ge 1 where the ray plane dips below the surface: Hi≤h(ui)H_i\le h(\mathbf{u}_i). This produces a distinct mathematical staircase in tt. Our hero configuration fixes N=32N=32, while maintaining a strict compile-time cap of Ncap=64N_{\mathrm{cap}}=64.

POM — linear search + secant lerp (hero)

After successfully isolating the first height-field crossing occurring between layers i−1i-1 and ii, POM improves precision by interpolating the exact zero of H−hH-h. This assumes both functions maintain linearity with respect to tt across the tiny interval:

t⋆=mix(ti−1, ti, (Hi−1−hi−1)(Hi−1−hi−1)−(Hi−hi)),uhit=u0−t⋆δuv.t^\star = \mathrm{mix}\bigl(t_{i-1},\,t_i,\, \tfrac{(H_{i-1}-h_{i-1})}{(H_{i-1}-h_{i-1})-(H_i-h_i)}\bigr), \qquad \mathbf{u}_{\mathrm{hit}}=\mathbf{u}_0-t^\star\boldsymbol{\delta}_{uv}.

It is critical to understand that this is purely a secant step performed on the bounding samples; it is not an analytically perfect ray–height intersection unless the underlying height map hh happens to be perfectly linear between those two UV coordinates. Relief mapping (which utilizes K=5K=5 binary bisections after identifying the initial crossing) is fully implemented in our backend but is intentionally omitted from the family strip comparison. The simplified N=4N=4 ladder clearly demonstrates how a basic linear search can carelessly skip over a thin mortar well.

Sampling the height inside the march

When implementing the loop, you must not rely on standard texture() calls. Because the iteration count is highly non-uniform across varying fragments, calculating implicit derivatives inside the loop is strictly illegal. Instead, you must explicitly specify the LOD:

hi=textureLod(H, ui, λ)or CPU bilinear on the float pyramid.h_i=\mathrm{textureLod}(H,\,\mathbf{u}_i,\,\lambda) \quad\text{or CPU bilinear on the float pyramid.}

We enforce two tightly locked λ\lambda values for our comparisons:

  • λ=0\lambda=0: This preserves incredibly sharp wells but aggressively aliases when the wall is minified at a distance.

  • λ=λiso=log⁡2ρ\lambda=\lambda_{\mathrm{iso}}=\log_2\rho: This is derived precisely from the geometric UV Jacobian (CPU JJ, utilizing the exact same formula standard mipmaps or anisotropic filtering would use). Under this LOD, relief noticeably melts and self-occlusion details fade out.

Do not falsely claim this calculates an accurate along-ray cone LOD. Following the resolution of uhit\mathbf{u}_{\mathrm{hit}}, we sample the albedo at uhit\mathbf{u}_{\mathrm{hit}}, and we derive the normal by evaluating the central differences of the height map hh exactly at the hit coordinate.

Self-shadow (second march, not a shadow map)

To simulate self-shadowing, we initiate a secondary ray marching from uhit\mathbf{u}_{\mathrm{hit}} directly toward the incoming light vector in tangent space, bounded by the exact same roof/floor volume. If any subsequent sample along this secondary ray dips below the height field before fully exiting the volume, we classify the fragment as being in a height-field shadow. A simple binary result (any hit equals shadow) serves as our default approach. This technique is strictly in-family to parallax mapping; it should never be confused with shadow-map bias, PCF filtering, or standard glPolygonOffset hacks.

Silhouette predicate (the lie, as a formula)

To formally define the inherent visual lie of POM, let Cquad(x)C_{\mathrm{quad}}(\mathbf{x}) represent the absolute screen-space coverage of the base 2-triangle wall. POM can only shade fragments where Cquad=1C_{\mathrm{quad}}=1. Conversely, let Cdisp(x)C_{\mathrm{disp}}(\mathbf{x}) represent the true rasterized coverage of a highly tessellated, physically displaced grid possessing the exact same height data.

CPOM=Cquad,sil(x)=Cdisp(x) xor Cquad(x).C_{\mathrm{POM}}=C_{\mathrm{quad}}, \qquad \mathrm{sil}(\mathbf{x}) = C_{\mathrm{disp}}(\mathbf{x})\ \mathrm{xor}\ C_{\mathrm{quad}}(\mathbf{x}).

While manipulating gl_FragDepth based on the intersection distance can successfully correct interior depth testing for subsequent post-processing and compositing, it fundamentally cannot expand the rasterized coverage of the mesh. Our default rule is strict: do not write gl_FragDepth, and this current test run firmly abstains from doing so.


Unique artifacts: the slice, then the XOR

Unique artifact. 1-D cosine ridge, view ray, layer planes. N=32 first ridge nested (steep / POM / dense-1D agree). Inset: N=8 skipped well — intentional. CPU science.
Unique artifact. 1-D cosine ridge, view ray, layer planes. N=32 first ridge nested (steep / POM / dense-1D agree). Inset: N=8 skipped well — intentional. CPU science.

The diagram above illustrates the exact mathematical artifact this article was written to investigate. This is pure CPU science evaluation, deliberately untethered from a standard GL photo. It maps a 1-D cosine ridge against the incoming view ray and discrete layer planes.

  • Primary Validation: At N=32N=32, the Steep first-hit, POM secant lerp, and the dense-1D true intersection all mathematically agree on the same first ridge (indicated by the nested rings). The dense-1D resolves at t=0.258t=0.258, POM closely follows at t=0.260t=0.260, and the steep approximation lands at t=0.282t=0.282.

  • Inset Failure: The N=8N=8 inset demonstrates a skipped well, which is entirely intentional. The mathematically true hit should still land on ridge 1; however, both the steep and POM algorithms completely jump the gap and erroneously skip to ridge 2. Providing the algorithm with too few layers is not evidence that "POM is broken"; rather, it is exactly the mechanism behind the texture swimming artifact we observe on the lower rungs of the NN-ladder.

‖Δu‖ turbo on the graze quad. Wells walk farther than brick faces. CPU march.
‖Δu‖ turbo on the graze quad. Wells walk farther than brick faces. CPU march.

First-hit layer index, N=32, same pose as the offset field. Mean first-hit layer ≈9.06.
First-hit layer index, N=32, same pose as the offset field. Mean first-hit layer ≈9.06.

Sphere indent UV-error heatmap, indent interior only. N=4 → 4.31e-5; N=32 → 8.24e-7. Analytic instrument.
Sphere indent UV-error heatmap, indent interior only. N=4 → 4.31e-5; N=32 → 8.24e-7. Analytic instrument.

To rigorously evaluate UV error, we mapped a spherical indent with radius R=0.40R=0.40, perfectly centered at (0.5,0.5,1)(0.5,0.5,1), using wrap clamp rules, evaluating the indent interior only. The generated heat map visualizes the deviation vector magnitude: ∥uhit−uanalytic∥\lVert\mathbf{u}_{\mathrm{hit}}-\mathbf{u}_{\mathrm{analytic}}\rVert. Any region outside the defined rim is considered the flat roof (t=0t=0); therefore, a raw quadratic sphere formula "miss" does not represent a valid height-field hit and is strictly excluded from our average error metrics.


Quote the numbers. Do not quote the brick photographs as UV error.

Operating with a locked scale s=0.08s=0.08, zero bias β=0\beta=0, under Mesa llvmpipe rendering:

row NN angle metric value
POM indent interior 4 55∘55^\circ mean UV err 4.31×10−54.31\times 10^{-5}
POM indent interior 32 55∘55^\circ mean UV err 8.24×10−78.24\times 10^{-7}
same 32 55∘55^\circ p95 UV err 2.27×10−62.27\times 10^{-6}
sil-xor floor vs roof 32 profile sil\mathrm{sil} fraction 0.575
face-on 32 0∘0^\circ MAE(bump, POM) 0
face-on 32 0∘0^\circ sil XOR (not the lie) 0.029
POM field 32 50.7∘50.7^\circ mean first-hit layer 9.06
shadow 32 72∘72^\circ agree vs N=64N=64, same field 0.993

For clarity, the hero rounding confirms that shifting from N=4N=4 at 4.31×10−54.31\times 10^{-5} →\to N=32N=32 yields a vast improvement to 8.24×10−78.24\times 10^{-7}. The silhouette discrepancy is heavily pronounced at sil_xor≈0.575\mathrm{sil\_xor}\approx\mathbf{0.575}, with the AABB span confirming extrusion at ≈0.141\approx\mathbf{0.141}. We assert mathematically that an s=0⇒Δu=0s=0\Rightarrow\Delta\mathbf{u}=0. It is imperative to remember that this analytic error is derived exclusively from the sphere indent instrument, not the noisy brick texture. Do not attempt to invent a generalized GL UV error metric by squinting at the graze or brick-hero photographs; those specific frames are provided strictly for qualitative photo-only analysis.

Do not invent a subjective “POM quality score.”


Family, then the ladders

Offset (explodes at graze) | steep (first layer) | POM secant lerp. Family rungs. Relief K=5 omitted.
Offset (explodes at graze) | steep (first layer) | POM secant lerp. Family rungs. Relief K=5 omitted.

N∈{4,8,16,32}. N=4 is the swim / missed-well hero; N=32 cleans the same pose.
N∈{4,8,16,32}. N=4 is the swim / missed-well hero; N=32 cleans the same pose.

s∈{0, 0.02, 0.08, 0.25}. s=0 matches bump; s=0.25 swims.
s∈{0, 0.02, 0.08, 0.25}. s=0 matches bump; s=0.25 swims.


Controls

Face-on: POM must not invent depth

bump | POM | offset at v̂_z=1. MAE(bump, POM)=0. Must match.
bump | POM | offset at v̂_z=1. MAE(bump, POM)=0. Must match.

When the view vector perfectly aligns with the surface normal (v^z=1\hat v_z=1) utilizing the exact same roof hull, there should be zero UV deviation. The rigorous science path for this is an orthographic UV-grid calculation yielding MAE, not relying on a perspective photograph. This confirms MAE(bump, POM) =0=\mathbf{0}. Note that the included photograph inherently captures a few microscopic degrees of edge parallax due to perspective projection; it serves as a visual picture of the control environment, not as a secondary quantitative metric. If your POM implementation mysteriously appears more 3-D or distorted here, verifying the TBN handedness should be your immediate first debugging step.

Scale / bias

A scale of s=0s=0 is functionally equivalent to bump mapping, as it dictates absolutely no UV walk. Our strictly locked hero scale of s=0.08s=0.08 strikes the ideal balance, providing highly readable interior parallax across this specific brick wall. Pushing the scale to s=0.25s=0.25 forces the ray to span far too many repeating tiles per unit of depth tt: the initial ray crossing erroneously intersects the wrong brick entirely, rays smash directly into the volume floor, and the entire wall violently swims. Bias remains rigidly locked at 00.

Too few steps

Executing the march with only N=4N=4 layers completely misses deep wells and creates massive staircasing artifacts, serving as a highly visible spatial proxy for swimming. Increasing the sample count to N=32N=32 beautifully cleans up the exact same pose. The earlier march-slice featuring the N=8N=8 inset is a direct mathematical representation of this exact failure mode occurring in 1-D; it is explicitly labeled as an intentional failure so that it cannot be mistakenly read as a broken hero implementation.

Self-occlusion is a second height march

POM | POM + light-march | displaced. Caption: not a shadow map / not bias. Wall panels photograph-only.
POM | POM + light-march | displaced. Caption: not a shadow map / not bias. Wall panels photograph-only.

The image sequence above demonstrates the bounds of self-shadowing. On the left, with only the view-march active, physically hidden wells can erroneously receive full shading if the view hit is successful but the incoming light angle is completely ignored. The middle frame introduces the secondary light-march, which correctly darkens the contact points and deep recesses. The right frame provides the displaced-mesh truth, utilizing real triangulated geometry matching the exact same height field. As the caption firmly states: this is not a shadow map. Our reported shadow agreement of 0.9930.993 is specifically derived by comparing an N=32N=32 march against a denser N=64N=64 march operating on the same underlying height field, not by comparing it against ray-traced geometric triangles. Do not quote this agreement metric as a validation of geometry matching.

Height LOD — one sentence of Jacobian

λ=0 aliases vs λ_iso melts. ρ≈3.15, λ_iso≈1.66 from CPU geometric UV Jacobian. Continuity with mipmaps / anisotropic — not a new theorem.
λ=0 aliases vs λ_iso melts. ρ≈3.15, λ_iso≈1.66 from CPU geometric UV Jacobian. Continuity with mipmaps / anisotropic — not a new theorem.

When the wall is minified at a distance, the CPU calculates the geometric UV Jacobian as ρ=N⋅max⁡(∂u/∂x,∂u/∂y)≈3.15\rho=N\cdot\max(\partial u/\partial x,\partial u/\partial y)\approx 3.15, which yields an isotropic LOD of λiso=log⁡2ρ≈1.66\lambda_{\mathrm{iso}}=\log_2\rho\approx 1.66. On the left, clamping to λ=0\lambda=0 preserves the incredibly sharp mortar wells but introduces severe aliasing artifacts. On the right, adopting λiso\lambda_{\mathrm{iso}} causes the high-frequency relief to melt and soften significantly. This ensures perfect continuity with standard mipmaps and anisotropic filtering: it applies an isotropic LOD based entirely on the geometric UV footprint, completely ignoring any complex along-ray cone calculations. This is standard practice, not a new rendering theorem, and we present no extensive Anisotropic Filtering quality tables here.

Offset leaves the tile

REPEAT vs CLAMP vs absdiff at N=4. Periodic brick should walk to the next tile; clamp flattens a rim. Photograph only.
REPEAT vs CLAMP vs absdiff at N=4. Periodic brick should walk to the next tile; clamp flattens a rim. Photograph only.

When a ray pushes past the boundary, a periodic brick texture should naturally walk over to the next repeating tile; utilizing a strict clamp instead flattens the boundary into an ugly rim. This remains a photo-only observation.

CPU brick L0 we own: turbo height + bump-lit preview; sphere indent inset. Authorship, not the theorem.
CPU brick L0 we own: turbo height + bump-lit preview; sphere indent inset. Authorship, not the theorem.


What this box actually measured

To ensure total transparency, the host environment for these evaluations was OSMesa, running Mesa 25.0.7-2+deb13u1, utilizing the llvmpipe driver (LLVM 19.1.7, 256 bits). The framebuffer target was strictly RGBA32F, with height map uploads locked to R32F. The 8-bit fallback path was not hit at any point during testing, and we deliberately disabled sRGB and MSAA. The depth buffer gl_FragDepth was not written. The primary brick texture utilized REPEAT wrapping, while the spherical indent instrument used CLAMP_TO_EDGE. Our automated assertions confirmed 28 pass / 0 fail, mathematically validating that the TBN matrix remained perfectly orthonormal and right-handed, verified that s=0⇒Δu=0s=0\Rightarrow\Delta\mathbf{u}=0, confirmed that the displaced AABB span was properly >0>0 (0.1415), proved the analytic UV error decreased properly as N=32<N=4N=32 < N=4, established the face-on MAE precisely =0=\mathbf{0}, and confirmed the grazing profile sil_xor>0.01\mathrm{sil\_xor}>0.01 (measuring at 0.575).

We can firmly claim: operating on this specific OSMesa / llvmpipe build, a rigorous CPU tangent-space height march applied to an authored field successfully offsets UVs along a perfectly known vector δuv\boldsymbol{\delta}_{uv}. This reduces measurable UV error compared to an analytic sphere as the layer count NN grows, right up until the linear-search Δt\Delta t / lerp model inherently saturates. Implementing a GLSL 330 fragment march constrained by a fixed step cap and relying on textureLod produces what is effectively a photograph of this software rasterizer, and should not be misconstrued as testing a discrete GPU’s dedicated POM hardware unit (because no such hardware unit actually exists). In a perfectly face-on orientation, bump mapping and POM flawlessly match along our scientific evaluation path (MAE =0=0). At a heavy graze, the true physically displaced-grid coverage fiercely disagrees with the visual output of the flat 2-triangle POM quad; this resulting XOR delta completely encapsulates the silhouette lie. Implementing a secondary height-field light-march convincingly darkens mortar wells that the primary view ray fails to hide, but this technique fundamentally remains a ray march, not a hardware shadow map.

We absolutely cannot claim to have evaluated NVIDIA / AMD / Intel hardware POM implementations, nor can we quantify hardware tessellation quality or make sweeping claims that "AAA games do exactly this many taps." We cannot claim that our specific llvmpipe loop performance has any parity with a modern GPU, which is why we present zero millisecond frame timings. We cannot assert that a shader utilizing dFdx/dFdy to build a TBN matrix, or relying on implicit LOD calculations inside a dynamic loop, will exactly match dedicated hardware behavior. We cannot claim that the standard POM secant lerp is a perfect analytic ray–height intersection. We cannot state that writing gl_FragDepth out from the shader magically "fixes" the lack of true displacement—the absolute screen coverage is, and always will be, bottlenecked by the primitive quad. Most importantly, we cannot claim that a height field is actual geometry.

Honesty, short:

  1. Metrics are CPU evaluated exclusively on float height data. The presented graze shots, brick-hero panels, and self-shadow wall comparisons are essentially gorgeous photographs of this specific software rasterizer in action. Do not irresponsibly quote these qualitative images as a quantitative UV error or sil_xor\mathrm{sil\_xor} metric.

  2. Face-on MAE is exclusively the vxy=0\mathbf{v}_{xy}=0 UV-grid control. The accompanying perspective face-on photograph is simply a visual picture of that isolated control environment, absolutely not a second measurable metric.

  3. Analytic UV error is strictly scored inside the spherical indent interior only. The POM secant lerp is fundamentally not a perfect ray–height intersection, making the residual error compared to the true analytic solution fully in scope for this evaluation.

  4. March-slice “analytic” data represents a dense 1-D mathematical first-crossing of our precisely authored cosine ridge, not a perfectly closed-form sphere.

  5. Silhouette XOR inherently requires a profile camera to be measured accurately. The minor face-on XOR of ≈0.029\approx 0.029 is simply edge noise and is not the true architectural lie we are examining. The AABB span being mathematically >0>0 stands as our definitive extrusion proof.

  6. Self-shadow agree simply pits an N=32N=32 march against a heavier N=64N=64 march on the exact same underlying field; it is not a comparison against a physically displaced mesh and it is absolutely not a hardware shadow map.

  7. Height LOD. Evaluated at ρ≈3.15\rho\approx 3.15, yielding λiso≈1.66\lambda_{\mathrm{iso}}\approx 1.66. The resulting relief softening is quite visible but remains functionally modest. This relies entirely on the geometric Jacobian only.

  8. Scale s=0.08s=0.08 is tightly locked to provide readable interior parallax across this specific wall asset. Cranking the scale to s=0.25s=0.25 intentionally hits the swim/miss failure rung for educational purposes.

  9. No gl_FragDepth, no cone-step, no tessellation. The absolute physical coverage of the POM rendering path strictly remains the flat 2-triangle quad.

  10. Brick is CPU procedural, explicitly not a high-fidelity photo scan. Analytic error was measured purely on the mathematical sphere, not on the noisy brick asset.

Bump mapping merely perturbs the normal n\mathbf{n} at the existing geometric UV coordinate. Offset mapping applies a single naive push. POM continuously marches through a mathematical volume. True displacement physically moves the geometric vertices. Only the last technique is physically capable of growing the mesh's silhouette.

Pin the graze 3-up as the definitive presentation. Pin bump-vs-POM as the ultimate lighting hero shot. Pin the silhouette XOR comparison as the stark, mathematical lie of the technique. Pin the 1-D march slice accompanied by the analytic UV-error heatmap as our rigorous science theorem. The foundational formula serves as the caption, and the XOR fraction perfectly encapsulates exactly why the mesh's outline stayed a flat quad.

All journal entries ← Work