Bump map looks ok on phong material, has stair step effect on physical material

Here’s a scene with a phong material where the bump map looks ok:

Here’s the scene switched to a physical material where I’m trying to achieve a similar look, but using a bump map results in a weird stair stepping of the bump map, like a topological map:

Close up:

stair-steps

If I increase the bumpMapScale, it gets worse, light seems to bounce off of the vertical cliff faces of the stair steps:

Here’s the bump map (not sure if uploading here changes the quality or data):

Any ideas?

Do you think it is possible to put a fiddle together so we can have a closer look to the bump mapping artifacts with MeshPhysicalMaterial? A simple plane mesh with some lights should already be sufficient.

2 Likes

I’m not yet able to reproduce using the same image assets in this pen:

EDIT! The pen does have the problem! I had to adjust the camera zoom, mesh size, and lighting, to make it more apparent.

It seems that, depending on the mesh size and other parameters, there’s like a modulo that affects the density of the topological stair stepping:

(Note, view it in a new tab, in a bigger window. Hard to see the problem in the small embed.)

Here’s a screenshot of the pen:

Apparently, if you find/replace MeshPhysicalMaterial to MeshPhongMaterial, the problem persists!

There seem to be some parameters, I’m not sure what, that make it have more or less stair stepping.

In my particular case, over at LUME - 3D HTML elements, the phong material does not show the stair stepping due to its current parameters, while using a physical material does.

To reproduce with my demo:

  • open Chrome/Edge devtools
  • click the element picker at very top-left of devtools
  • click on the wall outside of the picture frame
    • this selects the <lume-plane> element, which underneath has a .three property containing a THREE.Mesh with a THREE.PlaneGeometry.
  • find the has="phong-material plane-geometry" and change it to has="physical-material plane-geometry"
    • this changes the mesh to having a THREE.MeshPhysicalMaterial
  • double click any of the attributes, and in the input box append roughness="0.1" to make it shine again while it has the physical material.

At this point, you will have the stair steps (easier to see when you zoom in). If you switch back to has="phong-material plane-geometry", the problem seems to go away.

It seems the problem hasn’t actually gone away, but in the configuration of that picture frame demo, the problem is not very visible when using the phong material.

In the codepen pen, if you modify the size of the plane (try making it bigger and smaller) the stair steps are more or less visible.

1 Like

I updated the previous comment, the pen did have the issue, I just needed to adjust some parameters (camera position, mesh size, lighting) to make the stair steps noticeable.

1 Like

I’ve noticed this before too in my stuff… Would love to get to the bottom of it…
Visually it looks like the bump texture isn’t being filtered incorrectly or something when you’re up close… but it does show Some kind of filtering but they are in a weird direction! and there is weird acne on them… This has been around for as long as I can remember…

You have to really zoom in… But I would expect there to be a smooth gradient between neighboring bump samples… but the light direction seems to shift right at the the edges of the pixels… and there is a weird sort of shadow acne too…

Almost as if the bump map is being sampled with the same filter as the possibly different sized diffuse texture or something…

image

1 Like

Could this be related to precision? Looks like each “level” of the stair step is rounded to some interval, as if the values perhaps are not fully discrete. Is it the data in the bump map perhaps? Or a general problem with any bump map?

1 Like

Here’s a zoom-in on one particular smaller brick in the bump map image, and it looks pretty smooth:

So it seems that something in the shader is limited in precision, or being rounded with a certain step size, or something.

1 Like

I feel like I’ve been seeing this forever but just assumed it was a consequence of how bumpmapping worked. Precision probs could explain the weird banding/fuzz, but I don’t understand why the normal at the corners between samples could shift so drastically. I would expect it all to just be blurry but smooth lighting differences when zoomed up… Almost like filtering is disabled for bumpmaps?

1 Like

I’m trying to investigate this issue in more detail tomorrow. But what I have noticed so far is that the issue is not related to MeshPhysicalMaterial. The same pattern occurs with MeshPhongMaterial. And it also appears with older versions of three.js. So this isn’t a regression.

I need to clarify if this is an issue with the bump map itself or a general issue in the bump map shader. TBH, bump maps are not that common like normals maps so this could be an oversight. Or maybe the intended result. Using a lower bump map scale (around 0.2) makes the appearance much better.

3 Likes

fwiw, Standard and Physical material do the same thing for bumpmapping:

#ifdef USE_BUMPMAP
	uniform sampler2D bumpMap;
	uniform float bumpScale;
	vec2 dHdxy_fwd() {
		vec2 dSTdx = dFdx( vBumpMapUv );
		vec2 dSTdy = dFdy( vBumpMapUv );
		float Hll = bumpScale * texture2D( bumpMap, vBumpMapUv ).x;
		float dBx = bumpScale * texture2D( bumpMap, vBumpMapUv + dSTdx ).x - Hll;
		float dBy = bumpScale * texture2D( bumpMap, vBumpMapUv + dSTdy ).x - Hll;
		return vec2( dBx, dBy );
	}
	vec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy, float faceDirection ) {
		vec3 vSigmaX = dFdx( surf_pos.xyz );
		vec3 vSigmaY = dFdy( surf_pos.xyz );
		vec3 vN = surf_norm;
		vec3 R1 = cross( vSigmaY, vN );
		vec3 R2 = cross( vN, vSigmaX );
		float fDet = dot( vSigmaX, R1 ) * faceDirection;
		vec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );
		return normalize( abs( fDet ) * surf_norm - vGrad );
	}
#endif
1 Like

If you add bumpScale:100, then you can really see the whole topological map:

Indeed, setting bumpScale to a low value like 0.2 starts to make the issue go away, but then we can’t get as pronounced out-cropping of the bricks as desired:

In the Lume demo, which happens to be looking good on Phong for whatever reasons, the rock outcropping amount is more pronounced as desired:

1 Like

Yeah! Is Lume using threejs? i.e. threejs phong has the same issue afaik… from your codepen but with phong:

It’s like the fractional derivative of the bumpmap UV isn’t right…

I compared the shaders in here: three-shaderlib-skim

And all three Standard/Physical/Phong use the exact same code for bumpmapping.

1 Like

I looked at what Luma is using for threejs… I think its r139… and it’s bumpmap shader chunk looks like this:

#ifdef USE_BUMPMAP
uniform sampler2D bumpMap;
uniform float bumpScale;
vec2 dHdxy_fwd() {
    vec2 dSTdx = dFdx( vUv );
    vec2 dSTdy = dFdy( vUv );
    float Hll = bumpScale * texture2D( bumpMap, vUv ).x;
    float dBx = bumpScale * texture2D( bumpMap, vUv + dSTdx ).x - Hll;
    float dBy = bumpScale * texture2D( bumpMap, vUv + dSTdy ).x - Hll;
    return vec2( dBx, dBy );
}
vec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy, float faceDirection ) {
    vec3 vSigmaX = vec3( dFdx( surf_pos.x ), dFdx( surf_pos.y ), dFdx( surf_pos.z ) );
  vec3 vSigmaY = vec3( dFdy( surf_pos.x ), dFdy( surf_pos.y ), dFdy( surf_pos.z ) );
    vec3 vN = surf_norm;
    vec3 R1 = cross( vSigmaY, vN );
    vec3 R2 = cross( vN, vSigmaX );
    float fDet = dot( vSigmaX, R1 ) * faceDirection;
    vec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );
    return normalize( abs( fDet ) * surf_norm - vGrad );
}


The only difference I can see is:

vec3 vSigmaX = vec3( dFdx( surf_pos.x ), dFdx( surf_pos.y ), dFdx( surf_pos.z ) );
vec3 vSigmaY = vec3( dFdy( surf_pos.x ), dFdy( surf_pos.y ), dFdy( surf_pos.z ) );

vs latest threejs:

vec3 vSigmaX = dFdx( surf_pos.xyz );
vec3 vSigmaY = dFdy( surf_pos.xyz );

Which doesn’t seem like it should affect anything…

<3 Super curious about this!

1 Like

Yeah, Lume is on Three.

Indeed the demo is on 139, but same result with 157 here locally on my end (new release coming soon).

Yeah, in the pen, same issue with physical or with phong.

Not sure what about the Lume demo makes it an issue only with physical, while phong looks good.

After some more investigation, I don’t think this is a bug in the bump map shader. The artifacts you see are a result from the combination of a very bright light which is very close to a large mesh surface using a high bump scale.

The typical range for bumpScale is [0,1] where 1 is considered as the maximum influence a bump map should have. This is not necessarily a good value in all use cases. I would even go for 0.1 or even lower which makes the plane look quite nicely: three.js dev template - module - JSFiddle - Code Playground

I guess the artifacts become more apparent with PBR materials because they implement lighting in a more realistic fashion. That requires to parametrize materials differently. In this case, a lower bump scale should do the trick.

1 Like

Sidenote: Bump mapping is not appropriate if you want a real rough, uneven surface. For this you would need parallax mapping, displacement mapping or even some sort of tessellation.

In this specific case would a normal map be visually better than a bump map and computationally simpler than parallax mapping?

The best I could get with a bump map is by modifying it in this way:

  • increased the contrast/levels, so that the image uses (almost) the full range of values – otherwise it uses just a few levels, that make bump staircased – well, bumps are always staircased, but with more levels the granularity is finer and less noticeable
  • after increasing contrast some image artifacts emerged, so some blurring of low-contrast areas is needed

The bump texture is changed from this to that:

Here is the result:

https://codepen.io/boytchev/pen/KKbGazX?editors=0010

image

4 Likes

I would definitely start with a normal map. TBH, I never use bump maps. They are consider as predecessor of normal maps.

1 Like