PhysicalMaterial: Add Heightmap support using Parallax Occlusion Mapping (POM)#569
Open
johnnyshields wants to merge 18 commits intoasny:masterfrom
Open
PhysicalMaterial: Add Heightmap support using Parallax Occlusion Mapping (POM)#569johnnyshields wants to merge 18 commits intoasny:masterfrom
johnnyshields wants to merge 18 commits intoasny:masterfrom
Conversation
…calMaterial Implement height-based displacement for PhysicalMaterial using Parallax Occlusion Mapping with secant refinement. Features include: - First pass = linear layer computation, second pass = secant refinement to blend layers - Distance-based LOD with smooth fade to flat UVs at configurable range - Automatic normal derivation from height gradient when no normal map Parameters: - height_texture: Texture map for height (white=raised, 50% gray=surface, black=lowered) - height_scale: Depth of height texture - height_quality: (VeryLow to VeryHigh) which controls layer count, smoothing, and render distance Performance optimizations: - Short-circuit to not apply POM to distant surfaces - Layer count scales with height_scale (depth) - pre-computated - Layer count and refinement scales with distance - #ifdef to elimate dead code paths - Use textureLod to avoid implicit LOD calculation overhead - Guards against division-by-zero
…hields/three-d into pom-to-physical-material
PhysicalMaterialBase = 0x8020 had bit 5 already set in its value. When the height texture flag (also bit 5) was OR'd onto this base, it had no effect since the bit was already 1. This caused materials with and without height textures to get the same shader ID, leading to shader cache collisions where non-height materials received height-enabled shaders.
johnnyshields
commented
Jan 14, 2026
| HeightQuality::Low => (8, 0, 8.0, 25.0), | ||
| HeightQuality::Medium => (8, 2, 10.0, 50.0), | ||
| HeightQuality::High => (12, 3, 15.0, 50.0), | ||
| HeightQuality::VeryHigh => (16, 4, 15.0, 50.0), |
Author
There was a problem hiding this comment.
These levels can be tuned further, this is just what made sense for me experimentally in basic manual testing.
(Rather than "Quality" we may wish to expose the underlying raw params to users, but many of the Tier-1 rendering engines like Unreal just use "Quality")
johnnyshields
commented
Jan 14, 2026
| PrefilterMaterial = 0x8080, | ||
| PhysicalMaterialBase = 0x8040, // To 0x807F (6 bits: albedo, metallic_roughness, occlusion, normal, emissive, height) | ||
| DeferredPhysicalMaterialBase = 0x8080, // To 0x80BF (6 bits: albedo, metallic_roughness, occlusion, normal, emissive, alpha_cutout) | ||
| PrefilterMaterial = 0x80C0, |
Author
There was a problem hiding this comment.
This shift of bits is needed to accommodate the heightmap. I'm not sure the actual performance impact of going from 5 to 6 bits on PhysicalMaterial, but if this is a regression then we could make my code here a new material (POMPhysicalMaterial) instead.
Author
|
@asny any feedback would be appreciated! 🙇♂️ Please try |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ideas for future PR:
This PR implements height-based displacement for PhysicalMaterial using Parallax Occlusion Mapping with secant refinement. I'd be happy to add this as POMPhysicalMaterial (or ParallaxPhysicalMaterial) if you prefer, but I think there's no harm to add it just to PhysicalMaterial b/c without a heightmap the performance should be basically the same.
These pictures are flat wall meshes with textures that have a texture with a height map and normal map.
You can see how the bricks stick-out at different angles, versus standard normal+AO maps.
Features:
Parameters:
Performance optimizations:
#define USE_HEIGHT_TEXTURE-- if no heightmap is supplied, then USE_HEIGHT_TEXTURE will be false and the code/performance will be identical to the state before this PR.Re:
height_quality, you tend not to see the quality difference except up close and in extreme cases, e.g. sharp metallic edges. The higher quality levels increase the number of layers (reduces "stacked pancake" effect) and the amount of secant smoothing (reduces artifacts). Bricks, concrete, and rough surfaces generally look very good on "Low" quality.