How to convert bump map to normal map?

Hi,

I am sure this has been already asked about 1000 times, yet even after two hours of googling, I have not been able to find out straightforward and working solution. Basically, I am searching for a way to use regular grayscale texture sample as a normal map. There is NormalFromHeightmap node which is not working, as it accepts only Texture2D input, not texture sample, rendering it pretty much useless. There is also bump offset, which does not work through normal map slot, but rather through a very odd way which makes material tree very messy, and instead of normal mapping, it does some sort of very expensive parallax occlusion mapping.

So I had to bite the bullet, and tried to make my own normal map converter, which would pretty much offset value of the input height map by specified constant in horizontal, and then vertical direction of UV space, and would add these two offset values as R and G channels on top of the blue background, which could be then used as a normal map. Yet, even something so incredibly simple seems to be impossible struggle for me :frowning:

1 Like

there are filters for photoshop and gimp which will convert bumps to normals or prgrams like crazybump or substance painter

Using the **NormalFromHeightmap **node is the best way to go about doing this. To make a Texture Sampler into a Texture Object, right - click on the Texture Sampler and from the menu that is displayed, select the **Convert To Texture Object **option. The only draw back to this is that you can not easily change a Texture Object when using Material Instances.

1 Like

That only drawback is unfortunately a dealbreaker here, so I am back at square one I am afraid. :frowning:

I am curious… What is the reason NormalFromHeightmap can not work with Texture samplers?

Geodav:
That’s completely missing the point, which is that most of my materials are dynamic often generated from a single texture. Avoidance of duplicate textures for different material channels is a main point here.

To give you a better idea, here’s one of my procedural materials:
5ec125c54e4a3420d66aabb0b349d065513f0220.jpeg
I am using pre-rendered AO maps to generate all sorts of scratching and dirt on my rifle here:
ca218f879ac92603cc01d3af4eae3c1a3226cd3f.jpeg

I am used to this workflow from offline rendering, where I use minimal amount of textures to save memory, and I can also take great advantage of texture tiling. I can then use modifications of the same texture to create scratching, and so on, and I would also like the same texture to create bump mapping. In offline rendering, it is as simple as plugging same texture in bump mapping slot and raising a multiplier. In Unreal Engine, it has to be normal map, which is OK I guess, but there is no option to at least naively generate that normal map. If I convert texture sample to object, I lose ability to instance materials, which is important for me, and ability to use texture coords input to drive texture tiling.

You can easily use TextureParameter2D though.
How expensive is the NormalFromHeightmap node compared to a sampled Normalmap?

This actually doesn’t work… TextureParameter2D outputs V3, not T2D :confused:

The manual way of doing this is to use a Sobel filter.
The shader code looks something like:


float height_pu = tex2D(uv + float4(uPixel, 0, 0, 1), theTexture);
float height_mu = tex2D(uv - float4(uPixel, 0, 0, 1), theTexture);
float height_pv = tex2D(uv + float4(0, vPixel, 0, 1), theTexture);
float height_mv = tex2D(uv - float4(0, vPixel, 0, 1), theTexture);
float du = height_mu - height_pu;
float dv = height_mv - height_pv;
float3 N = normalize(float3(du, dv, 1.0/nScale));


uPixel and vPixel is the size of a pixel – 1/1024.0 if you use a 1024x1024 texture.
nScale is a factor for how “strong” the effect is. 1.0 for a 45-degree maximum effect; 10.0 for a fairly strong effect.

Note that uPixel and vPixel do NOT change values at different MIP maps! They should always represent the size of the largest MIP map level.

Sorry, I can’t build a node graph for this right now, but it should be fairly simple to turn this into the obvious texture sample and add/subtract/divide/normalize nodes.

Whoops, I’m sorry, I meant TextureObjectParameter, which is a parametrized TextureObject
0885e0d5bbebf16fac4fa42eabccfec92d9e7598.jpeg

Sorry but using third party plugin, especially paid one, for something so incredibly essential as using bump map is simply not an option.

Thanks, but I still do not understand how is this going to help me? I do not see any difference to regular texture object. It still does not have coordinates input to allow me to tile the texture, nor does it have any parameters for it right inside the node.

Thank you, this actually seems like something I was looking for. Unfortunately I have no experience with coding, so it’s going to give me hard time converting it to node tree.

I think that this could encourage UE developers to take a step back and look at a bigger picture of making UE4 friendly for new users. Something as trivial as using regular bump map should not be matter of a forum thread with many hit and miss solutions. It should be a no-brainer that doesn’t even require opening manual, let alone participating on a forum.

Right now I am still facing an impossible choice between being able to use texture tiling and take advantage material instancing, but giving up my desired approach of keeping input texture count to minimum, or giving up texture tiling and material instancing to gain ability to generate normal maps out of height maps. That’s kind of decisions I would not like to make.

That being said… I understand this is quite a niche workflow, and may not be worth the time to accommodate for.

2 Likes

A Texture Object is a raw texture and is not sampled.
Use a TextureSample Node and plug your TextureObject into it’s Tex-Input.
Then you can also use scaling etc within the UV input of this texturesample

Oh thanks, now I get it… :slight_smile:

But now I am confused about another thing. I need to generate my normal map out of already tiled texture. Otherwise my normal map won’t match all of my other maps (color, roughness, etc…)

So the texture object goes into Tex slot, TexCoords that does tiling goes into UVs slot, and it’s output goes to appropriate places. But I still can not plug that into NormaFromHeightmap. I can plug only TextureObject in there, and that TextureObject has not yet gone through that tiling process, which happens only after Texture Sample, which cannot go into NormaFromHeightmap :expressionless:

Sorry, I forgot about your NormalFromHeightmap problem:

Use the “Coordinates”-Input to specify Tiling :slight_smile:

3 Likes

Ohhh… Thanks! Not only does it work, it also completely solves all my needs (maybe aside from keeping node tree as tidy and minimal as possible).

I have to say, that’s one hell of a steep learning curve though. :slight_smile:

By the way, is there any limitation or performance hit to using texture object plugged into texture sampler rather that texture straight out of the texture sampler?

To be fair, the way it’s recommended that newbies do this, is “convert to normal map” on import.
Glad you got the normal-from-bump working!

Huh… I had no idea that’s even possible? How, where? Maybe I was looking at wrong places in documentation but I don’t recall seeing any mention of such thing.

Anyway, thanks again to everyone for your awesome patience, and especially to Raildex_ for the solution that does the job! :slight_smile:

Sorry for the late response, was out of town for Christmas / New Years holiday. Sampling from a Normal Map Texture will always be cheaper than using the NormalFromHeightmap as the NormalFromHeightmap is doing a conversion under the hood, so it is going to require more math instructions to calculate and display the final result. When you use a Texture, you forgo these extra calculations and just display the Texture.

I believe that it is because it is a Material Function. If you take all of the Material nodes from the NormalFromHeightmap Material Function and put them into your Material, you should be able to use a regular Texture sampler instead of a Texture Object.