Strange behaviours in Node Material normals

Hi

I found some strangeness working with the node materials.
The normal map / perturbNormal Block seems not to work as intended.
Some digging revealed the following problems:

  • Normal default color (127, 127, 255) does not align with empty normal.
    This seems to be a general normal map problem that also occurs with PBR Material.
    The color (127, 127, 255) is converted to (0.498, 0.498, 1.0) instead of (0.5, 0.5, 1.0) as well as in the color picker as with the sampled texture.
    Seems to be somewhat related to the rounding problematic mentioned in Wrong albedo colors. But I am surprised that the texture sampler does the same.

  • The normal texture level somehow rotates the normal instead of smoothing/boosting it.
    This works well in the PBR Material but not in the Node Material.

  • The strength of the perturbNormal block rotates the normal on very low values. Shaded color goes to black on 0.
    I tried this as a replacement for the texture level but it doesn’t to the job either.

  • The perturbNormal Block can only be once in the material. Leads to redefinitions in the shader otherwise.
    I tried this as a way to stack normals (like with detail textures) but I get errors when I have more than one perturbNormal block. Is there a better way to do this or can this be solved somehow?

Overall the color rounding problem seems to be a general one. But it is way less noticeable in PBR Material as the texture level works as intended.

I followed the playground examples and documentation and made a playground to test and verify: https://playground.babylonjs.com/#D6G4UD#7
Note that the generated material code already has the wrong color value in line 122 as it was made with the color picker.

Did I get something wrong in the node tree or is there another solution? Otherwise I would be glad if I could at least get the level/strength right.
Help is greatly appreciated.

Here’s a fix for the strength parameter:

The parameter was passed as 1/strength to the shader instead of plain strength.

Regarding the unperturbed normal to use in the node material, you should use a (0.5, 0.5, 1) Vector3 instead of a Color3, as 0.5 can’t be generated when dividing an integer between 0 and 255 by 255.

The texture level property can’t be used as a bump strength value in the NME case because this property is used in a special way in the bump code and not as the regular level value when used by a standard texture. That’s something the PBR code knows how to handle, however the node material does not “know” that the texture is really a bump texture, so the level property is not used as expected in this case. That’s why there’s the strength property in the PerturbNormal block in the first place.

Finally, I don’t know if reusing the PerturbNormal block is expected to work or not, I will let @Deltakosh answer this one. I think there may be some complications in allowing this…

Note:

It is not a rounding problem, 127/255 = 0.4980, so it is the expected value.

1 Like

Can you share a simple repro with a simple NME of what you want to do with PerturbNormal?

Thank you for the quick fix!

It is not a rounding problem, 127/255 = 0.4980, so it is the expected value.

I see your point but looking at the value range of 8bit, wouldn’t there be some valid arithmetic fix for that? Something like (127 + 0.5)/255 = 0.5?
I guess the real problem is that an 8 bit value isn’t balanced around zero because it has an even amount of values. But that would at least fix the pointing-up direction of the normal applying to the r and g channels.

I think I’ll try to add 1/512 after the conversion and see if it’s close enough for normals. If it works it would at lest be a fix for me.

@Deltakosh
I’ll try to make a playground example for the PerturbNormal later. :+1:
Or is there a specific reason you need a repo?

Sorry, I misread repro as repo.

Here is a playground with a node graph of what I had in mind:
https://playground.babylonjs.com/#D6G4UD#8

The node material doesn’t compile with the error:

Shader compilation error: FRAGMENT SHADER ERROR: 0:627: ‘uvOffset’ : redefinition ERROR: 0:628: ‘normalScale’ : redefinition ERROR: 0:629: ‘TBNUV’ : redefinition ERROR: 0:630: ‘TBN’ : redefinition

I would expect the second perturbNormal to apply the rotation of the second normal map to the normal that was already rotated by the first normal map. At least if I get the shader code of perturbNormal correct.

Ha gotcha! The perturbNormal is supposed to be used once only per node
But I see your need
Let me check what I can do :smiley:

1 Like

Why not using a Vector3 with the right values instead of a Color3? A normal map is not a texture with colors, it is a texture of vectors (the normals), so in any case using a Vector3 is more sensible to me than using a Color3 (behind the scene both are a vec3 in the shader code, anyway).

Good point. But that would require me to use float PNGs instead of 8bit/channel PNG/jpg, wouldn’t it? Last time I tested they were way bigger. I know this can solve the problem with banding for small value changes but I have to try to reduce bandwidth.

On the other hand I think I just could ignore it because most materials I use have a normal map and the shift is uniform. I just noticed this because of the comparison and never before :sweat_smile:

should be good in next nightly :slight_smile:

Wait… do you mean @Evgeni_Popov’s fix or the multiple usable perturbNormal, too?

One last stupid question: I guess there is no way of getting the level from the texture and color without the level applied from a texture node? Because I use the level extensively already :expressionless:.
The only alternative I could think of is feeding the level as a separate input, multiplying the color by 1/level_input and feeding the level_input into the perturbNormal strength.
I think this would work but is a bit of a detour.

Yes, I was speaking about the constant Color3 your are using to hold the (0,0,1) normal, but for the texture you can definitely keep a 8bits per component texture as everyone is doing, you won’t see any artifact because of that.

The texture level is a standard property that is passed to the shader code and is used as a multiply factor to the texel value whatever the texture, you can’t change this.

@GreyWorks In fact we are not going to allow multiple PerturbBlock blocks in a material (@Deltakosh is going to make this block unique so that a single instance can be added to the graph) because there’s no need for it and it leads to duplicate shader code. Instead, you should use the NormalBlend block which is designed for what you want to do: blending two normal maps.

Okay. I will try that. :+1:

First I thought the NormalBlend doesn’t do what I need it to do at all but then I noticed something.
The reflection of the first input is symmetrical on bot axes. Like the values are treated absolute.
Seems to me like a bug, too. Or is there a reason for that behavior?

https://playground.babylonjs.com/#D6G4UD#10

It seems there’s a bug in the NormalBlend block, here’s the PR:

1 Like

I will need to test if normalBlend does exactly what I need it to do but it looks good so far.
All the used nodes are behaving like you would expect now.
Thank you for the quick fixes. :+1: