Skip to content

Fix parallax mapping correctness and promote its example - #9207

Merged
mvaligursky merged 1 commit into
mainfrom
mv-parallax-fixes
Aug 21, 2026
Merged

Fix parallax mapping correctness and promote its example#9207
mvaligursky merged 1 commit into
mainfrom
mv-parallax-fixes

Conversation

@mvaligursky

@mvaligursky mvaligursky commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

StandardMaterial#heightMap was a single-tap offset mapping implementation with a viewDirT.z += 0.42 fudge added in 2016 and never revisited. Several parts of it were broken, and the parts that worked were about 4x too weak to see.

Changes:

  • The parallax offset now reaches the opacity map. getParallax() ran after getOpacity(), so the opacity map sampled un-offset uvs while every other map of the material was offset (GLSL and WGSL).
  • A height map with no normal map now works. hasTBN excluded useHeights, so no tangent frame was generated and the offset was silently zero. LIT_HEIGHTS now also selects the derivative-based frame, and the frame's uv falls back to the height map's uv channel.
  • The uv used to build the tangent frame no longer includes the parallax offset, which was a circular dependency - that frame is what the offset is computed from. _getUvSourceExpression takes a new allowParallaxOffset argument for this.
  • dUvOffset is no longer referenced by passes which do not compute it. It is declared under FORWARD_PASS only, so any other pass with a height map enabled would emit a shader that fails to compile. The renderer does not reach that combination today, so this is an invariant rather than a live bug.
  • The offset is now real offset limiting (Welsh 2004), height * normalize(viewDirT).xy, replacing the viewDirT.z += 0.42 fudge which both understated the effect and skewed it as the view direction changed.
  • New unit tests in test/scene/shader-lib/standard-parallax.test.mjs covering the four fixes above, each checked to fail against the unfixed source.

API Changes:

  • heightMapFactor scales the offset by 0.1 rather than 0.025, so the default factor of 1 displaces the texture by up to 5% of a uv tile instead of ~1.25%. Existing content using a height map will show a noticeably stronger effect - divide the previous value by 4 to keep the old look. Useful values are now roughly 0 to 2.
  • StandardMaterial#heightMap and #heightMapFactor docs now state that mid-grey is the level of the original geometry, that the offset applies to every other map of the material (so the height map needs the same tiling and offset as those maps), and what the factor means in uv units.

Examples:

  • materials/parallax-mapping - promoted out of test/ and un-hidden. Four shapes in a 2x2 grid compare diffuse only, normal map, height map alone, and normal map + height map, with a shape selector (box or sphere) so the offset is exercised on differently-oriented tangent frames, and a slider driving heightMapFactor. Thumbnails regenerated.

#7821 could not be reproduced - the shader compile failure theorised in that report is not reachable through the renderer, as noted above - but the parallax path it exercises is fixed here, so it is closed by this change too.

Parallax occlusion mapping (#155) is not part of this change.

Fixes #8689
Fixes #7821
Closes #5209

@github-actions

Copy link
Copy Markdown

Build size report

This PR changes the size of the minified bundles.

Bundle Minified Gzip Brotli
playcanvas.min.js 2371.6 KB (+0.2 KB, +0.01%) 609.4 KB (+0.0 KB, +0.01%) 473.2 KB (−0.1 KB, −0.02%)
playcanvas.min.mjs 2368.9 KB (+0.2 KB, +0.01%) 608.3 KB (+0.0 KB, +0.01%) 472.7 KB (−0.0 KB, −0.01%)

@mvaligursky
mvaligursky merged commit 59ffaff into main Aug 21, 2026
10 checks passed
@mvaligursky
mvaligursky deleted the mv-parallax-fixes branch August 21, 2026 08:24
@willeastcott

Copy link
Copy Markdown
Contributor

If parallax mapping was broken, why not just upgrade it to something more modern like displacement mapping?

@mvaligursky

Copy link
Copy Markdown
Contributor Author

If parallax mapping was broken, why not just upgrade it to something more modern like displacement mapping?

Working on that in a follow up PR, as an optional (and more expensive) option. But this is a valid option for lower end devices.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants