6
$\begingroup$

I would like to export a normal map texture itself, what is a correct way to make it stronger or weaker?

I found that weakening effect can be accomplished by mixing texture with #8080ff color using MixRGB node, but I am not sure it is the best/proper way to do it.

$\endgroup$
8
$\begingroup$

First of all, to understand how the normal directions are mapped to colours, see this excellent answer in gamedev.sa. Basically, the blue value is

  • B = cos(alpha) × 0.5 + 0.5

and the red and green are the corresponding

  • R or G = sin(alpha·(x or y)) × 0.5 + 0.5

values for both directions, with alpha being the angle away from the surface normal. You might notice that it's not immediately trivial to make it stronger or weaker, the standard transformations (saturation, contrast, brightness) on their own will not do what you want.

A quick and dirty way ...

... to strengthen the map would be to, for example, subtract 0.1 from the red and green channels and then multiply the red and green channels by a factor of 1/(1 - 2*0.1) = 1.25. The problem with this is that you can clip colors and that you end up with a normal map that is technically wrong because the normals will be longer than 1. Basically, this stretches the normal in the x and y directions. For normal maps that are far too weak because they were created from a very smooth surface, this method might work fine. Do not use it on anything where steep angles are already in the map.

Setup: dirty scaling setup Be careful here to make sure you work in linear image coordinates without any gamma correction.

Done right

Per the formulae above, you get the angle in a range from 0 to 1 this way:

  • angle = 2/pi × arccos((B-0.5)×2)
  • angle_x = 2/pi × arcsin((R-0.5)×2)
  • angle_y = 2/pi × arcsin((G-0.5)×2)

We can then transform this value in a way that retains its range (for example via a gamma-node) and re-apply

  • B = pi/2 × cos(angle)×0.5+0.5
  • R = pi/2 × sin(angle_x)×0.5+0.5
  • G = pi/2 × sin(angle_y)×0.5+0.5

This looks something like this: good normal strengthening. You still have to watch your image gamma.

Clipping is not a problem any more here and even strong strengthening should not create any problems. You bend the normals away from the centre while retaining their length.

Notes:

  • In this setup, a smaller gamma value creates a stronger normal map, while gamma = 1.0 doesn't change the map.

  • The values of 0.637 are actually 2/pi, while 1.570 is pi/2 (enter the values as "2/pi" etc., python will understand what you mean).

  • Interestingly, Doc:2.4/Bump_and_Normal suggests that Blender uses the full range of the blue channel for the range of 0.0 to 1.0, which makes the ((B-0.5)×2) and ()×0.5+0.5 transformations unnecessary for this channel. I can't find a more current reference, but I assume this is still true in the current versions. So I might have to change the second setup accordingly.

$\endgroup$
  • $\begingroup$ Julian, thanks for sharing the setup. It's somewhat easier than what I used. Jeez, one would think that blender would have a simple node for that kind of thing by now, since lots of people seem to have that kind of problems with normal maps. It would also be nice to have a node that mixes two normal maps into one... $\endgroup$ – metaphor_set Apr 24 '16 at 22:10
  • $\begingroup$ i did not see in the link you provided how did you come with RG and B values $\endgroup$ – dimus Apr 29 '16 at 23:08
  • 1
    $\begingroup$ @dimus The relevant quote is: "If a normal is (x, y, z), the corresponding pixel in the normal map will have each of x, y, and z mapped from the range (-1,1) to (0,255) to get the red, green, and blue components respectively." -- Only that in the general case, we don't use 255 steps but only the generalized range from 0 to 1, as we don't know what the bit-depth of the image is (and don't need to). $\endgroup$ – JulianHzg Apr 30 '16 at 9:11
5
$\begingroup$

I converted @JulianHzg answer into OSL script which allows to do all calculations in one node:

shader norm (
    color norm = color(0.5,0.5,1.0),
    float Fac = 1.0,
    output color norm2 = 1
) {
    color v1 = (norm-0.5)*2;
    v1[0] = asin(v1[0]);
    v1[1] = asin(v1[1]);
    v1[2] = acos(v1[2]);
    v1 *= Fac;
    v1[0] = sin(v1[0]);
    v1[1] = sin(v1[1]);
    v1[2] = cos(v1[2]);
    //normalize to the 1 unit length
    v1 /= length(v1);
    norm2 = v1/2 + 0.5;
}

Before using OSL Script node don't forget to switch on OSL checkbox:

enter image description here

To generate a "weakened" or "strengthened" normal map I use the following node setup:

enter image description here

And then bake it as Emit on a texture in Raw format, with Float set.

enter image description here

Finaly save it as 16 bit png with Save as Render checked

enter image description here

The resulting normal map will have a "neutral" color set at #8080FF as required by game engines.

As a "bonus" here is a version of the script for mixing two normal maps together with a control over relative strength of each map:

  shader norm (
    color norm1 = color(0.5, 0.5, 1.0),
    float Strength1 = 1.0,
    color norm2 = color(0.5, 0.5, 1.0),
    float Strength2 = 1.0,
    output color norm3 = 1
) {
    vector v1 = (norm1 - 0.5) * 2;
    v1[0] = asin(v1[0]);
    v1[1] = asin(v1[1]);
    v1[2] = acos(v1[2]);
    v1 *= Strength1;

    vector v2 = (norm2 - 0.5) * 2;
    v2[0] = asin(v2[0]);
    v2[1] = asin(v2[1]);
    v2[2] = acos(v2[2]);
    v2 *= Strength2;

    vector v3 = v1 + v2;
    v3[0] = sin(v3[0]);
    v3[1] = sin(v3[1]);
    v3[2] = cos(v3[2]);
    v3 /= length(v3);
    norm3 = v3/2 + 0.5;
}

enter image description here

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.