Skip to main content
GameDev.net gamedev.net
🔒 Locked

Convert Normal Map to grayscale Bump Map?

Started by Kuroyume0161 Feb 2, 2005 at 9:32 PM 42 replies 32.1k views
Original Post
Kuroyume0161
Kuroyume0161
Yep, that's what I need to do. No rendering issues, no OpenGL or 3rd-party libs, apis, or dlls. This is to be done programmatically, preferably in C++. Just need to convert an RGB image representing a Normal map (in this case, an embossed-style with R and G represent the vectors, B is unutilized) into a Grayscale image representing a Bump map. Now I have been told, for better or worse, to 'perform an integration over U and V' to get back as much of a bump map as can be had from a normal map. This, to someone who only basically comprehends Calculus (and has never once had to use it in twenty year's programming), is information that I cannot use. I want examples, source, pseudo-source, references, anything but a vague reference to using integral calculus. And I definitely don't want to read a paper full of partial derivatives and lemmas and intregations. There are no 'calculus' libraries in the standard C++ libraries. Plus, this is not about putting the map onto a 3D object. It's strictly a 2D-2D conversion. I have found nothing pertinent online and certainly have no books relative to the topic - although my library contains a hefty set of 3D computer graphics programming books (the tomes). Thank you for any information leading to a means to perform this task! Robert
Ysaneya
Ysaneya
Not exactly what you want, but there's an interesting tool to do that this page:
http://www.zarria.net/ (Displacement Map Creator).

Maybe you can try to contact the author to ask him how he did it.

Y.
Eelco
Eelco
what i think should give quite decent results:

loop trough each row in your normalmap. summate the x-component and put it in a heightmap.
loop trough each collumn in your normalmap. summate the y-component and put it in a heightmap.

these two outputs should be similar. average them to get your final output. this output can contain values < 0, and its range isnt bounded either. however, enfocing that is trivial.

see, integration isnt that hard. a higher order integration sceme might yield better results, as would calculating the actual slope instead of just the vector component, but give it a try. its 5 minutes to code, and i dont think it will be bad at all.
Kuroyume0161
Kuroyume0161
Prior to asking this question, I did do a 'google' search of this forum, but without results. A more cumbersome page by page search found a couple of similar threads (at approximately page 35).

My problem (and for this type of thing, I guess it is) is that 'No college' equals no Computer Science major and no Calculus. All of my programming and math beyond HS is self-taught. What I know of Calculus is basic. I understand basic integration and derivation and orders. But that's about it. As soon as people mention double integrals, I glaze over. Sorry to say, I'm no David Eberly. ;)

I do have MathCad Pro at my disposal (an older version which I just dusted off for just this occassion). This could be used to load in the normal map image and apply whatever maths and check the results.

So, what do you recommend for an integral equation? Is this simply determining the area under a 'slice', which we'll call a pixel?

I do appreciate that anybody takes the time to explain this to me 'like I'm a five year old', because when it comes to this math, that's how I feel...

And, Anonymous Poster, why did you even bother posting? Not everybody is/was a Math major. As a matter of fact, I was an Art major. Yet I still managed to graduate in the top 5% of my 900 student class. Thinky about that.

Robert
Eelco
Eelco
Quote:
Original post by Kuroyume0161
I do appreciate that anybody takes the time to explain this to me 'like I'm a five year old', because when it comes to this math, that's how I feel...

could you be more specific about what you dont understand about the approach i suggested? or did you try it and it didnt work as desired?

oh shit i just realized its flawed. easy to solve though.
attempt2:
for ix = each pixel in row[0] startheight = startheight + vectorcomponentx[ix,0] height[ix,0] = startheight for iy = each pixel in collumn[ix]  height[ix,iy+1] = height[ix,iy] + vectorcomponenty[ix,iy]

optionally do the same starting from collumn[0], which will produce similar result, and average to minimize errors. then again scale to the desired range.

im quite sure this will work.
Kuroyume0161
Kuroyume0161
I tried something similar, but the results were horrid. Instead of using vectors, I just used the values on the Red or Green plane of the bitmap (128=median) separately to determine changes in value as changes in height. So, if the Red value at some (x,y) was greater than the previous, I added one to that in the heightmap. If the Red value was less than the previous, I subtracted one from it in the heightmap. And so on.

The problem with the approach, among others, is that you must be absolutely sure about which basis direction the lighting is considered from (e.g.: Red = 0, Green = -90) on the UV plane. The other problem is the unbounded nature of the approach.

Would be more generalized to be able to do it irrespective of the lighting bases.

Will test your pseudo-code and see what happens.

Thanks, Eelco!

Robert
Eelco
Eelco
Quote:
Original post by Kuroyume0161
I tried something similar, but the results were horrid. Instead of using vectors, I just used the values on the Red or Green plane of the bitmap (128=median) separately to determine changes in value as changes in height.

wrong. not the change in value is the change in height: the value itself is the change in height.

Quote:

So, if the Red value at some (x,y) was greater than the previous, I added one to that in the heightmap. If the Red value was less than the previous, I subtracted one from it in the heightmap. And so on.

why substract one? the vectorcomponents, or colorchannels, however you choose to interpret them, directly correspond to the change in height in that location.

Quote:

The problem with the approach, among others, is that you must be absolutely sure about which basis direction the lighting is considered from (e.g.: Red = 0, Green = -90) on the UV plane.
??
Quote:

The other problem is the unbounded nature of the approach.

there exists no bounded solution to this problem. all ranges are valid solutions. derivatives (the normals) contain only relative information by nature.

Quote:

Would be more generalized to be able to do it irrespective of the lighting bases.

i really dont get what you mean by lighting basis, but i can assure you lights have nothing to do with the solution of this problem.

Quote:

Will test your pseudo-code and see what happens.

Thanks, Eelco!

Robert

im curious to know how it will perform.

note that this is a first order integration scheme. hence it will only give accurate results when the input data is sufficiently cooperative, ie continuous and non-conservative. also, the slope is only linear with the vectorcomponents for small values.

improvements would be higher order integration schemes that are more accurate on small features. also, its not that hard to calculate the slope more accurately. the true slope would be calculated like this if im not mistaken (assuming red & green are unsigned bytes):

float vx = ((float)red)/127.5 - 1;float vy = ((float)green)/127.5 - 1;float vz = sqrt(1 - vx*vx + vy*vy);slopex = vx / vz;slopey = vy / vz;


higher order integration is a little more complex. lets see first how this works out. if its not good enough, we can give it a try.
Kuroyume0161
Kuroyume0161
No, I wasn't just saying that, for instance, values of Red larger than the median were higher and values lower than the median are lower. No. No. NO!

I was using the changes to determine if the surface was increasing or decreasing in height dependent on the light source (this makes a difference on whether brighter colors are increasing or decreasing in height - sloping upwards or downwards, relatively). Yes, there is a light source. Methods used to create Normal maps from geometry use... LIGHTS!

Normal Maps 1
Normal Maps 2

Heck, even one the links in an older thread points a paper (PDF) on taking a photograph and using it to determine the surface normals in order to extract height information (using the LIGHTing to determine the surface normals).

I agree that the image represents the normal of the surface at each pixel, but the analogy to three mutually perpendicular light sources is a direct and valid one. It may or may not play any role in the solution, but it has helped me understand what Normal maps represent. There is no understanding without representation (symbolically, analogously, or whatever).

I will try the code! :)

Robert
Eelco
Eelco
Quote:
Original post by Kuroyume0161
No, I wasn't just saying that, for instance, values of Red larger than the median were higher and values lower than the median are lower. No. No. NO!

lower than what? i really cant follow you.

Quote:

I was using the changes to determine if the surface was increasing or decreasing in height dependent on the light source (this makes a difference on whether brighter colors are increasing or decreasing in height - sloping upwards or downwards, relatively). Yes, there is a light source. Methods used to create Normal maps from geometry use... LIGHTS!

i dont know what methods you refer to, but converting a heightmap to a normalmap has absolutely nothing to do with lights, and unsurprisingly, neither has the inverse operation.

Quote:

Heck, even one the links in an older thread points a paper (PDF) on taking a photograph and using it to determine the surface normals in order to extract height information (using the LIGHTing to determine the surface normals).

yeah. lighting has to be taken into account to calculate normals from an image. however, i thought you were trying to create a heightmap from a normalmap, no? this are two seperate procedures.

Quote:

I agree that the image represents the normal of the surface at each pixel, but the analogy to three mutually perpendicular light sources is a direct and valid one.

ehm, not that im aware of.
Quote:

It may or may not play any role in the solution, but it has helped me understand what Normal maps represent. There is no understanding without representation (symbolically, analogously, or whatever).

the only thing you need to visualize to understand normals is that they are the vector peripendicular to a tangent plane.
Quote:

I will try the code! :)

Robert

ok.

you seem confused as to what you really want though. i advice you to get that straight before you start typing. you are neither infinite monkeys, nor have infinite time.
superpig
superpig
There is stuff out there about how to do this, but it never hurts to have another.

The concept of three orthogonal lights can be helpful at first, I agree - the color components at each pixel of the normal map telling you the extent to which each light is shining directly at that texel - but it may cause problems when you move from using normal maps in world space (where it all holds true) to using normal maps in tangent space (where it gets more confusing, because the position of the orthogonal lights is defined by the normal at that texel, and yet you're using those lights to encode the normal at the texel, etc...)

Aaanyway. What you're almost looking to do is to convert each texel into a small quad, with a normal equal to that given by the normal map. Then you're aligning the edges of those quads so that they form one seamless surface, and then you're sampling the resultant 3D surface at regular intervals to generate a heightmap. (That's not how I suggest you do it - it's just, conceptually, what the process does).

First off, you need to pick a base height. You've been told correctly with regards to 'performing an integration over U and V' - the problem is that integration like this doesn't produce a single answer, but rather a set of answers from which you want only one - a 'family of functions.' A normal map representing the top of a small hill and one representing the top of Mount Everest could come out looking similar, even though the actual height values would be radically different. So you want to pick a point somewhere (possibly the top left texel) and say, "This point is at height C." (In math terms, 'C' is your 'constant of integration').

Then, starting at that point, you work your way outwards across the map. For each neighbouring texel, you say, "OK. I'm at height C now. The texel I'm looking at is sloping in such a way that when I've crossed the entire texel I'll have gone up/down X units of height. So, my height at that point will be C+X."

I'd continue explaining this - specifically, the "sloping in such a way that" bit - but I've just noticed you say that the blue channel in your normal map is unused. As such, I don't see how you could do this - you can't reconstruct a three dimensional normal with only two components...
Richard "Superpig" Fine - saving pigs from untimely fates - Microsoft DirectX MVP 2006/2007/2008/2009
"Shaders are not meant to do everything. Of course you can try to use it for everything, but it's like playing football using cabbage." - MickeyMouse
Eelco
Eelco
Quote:
Original post by superpig
I'd continue explaining this - specifically, the "sloping in such a way that" bit - but I've just noticed you say that the blue channel in your normal map is unused. As such, I don't see how you could do this - you can't reconstruct a three dimensional normal with only two components...

a normal has only 2 DOF's, since one it taken by the unit length contraint. using this contraint you can recalculate the third component using pythagoras, as shown in my snippet above.
Kuroyume0161
Kuroyume0161
First, superpig (whatta name), thank you for agreeing that I'm not insane about the 'lighting' metaphor/analogy. I've only read this twenty thousand times over the period of several days. Not making this stuff up. :)

I understand the idea of establishing a base-line height at some pixel (the upper-left hand one, for example). And this is exactly what I did in my unworking code. The 0,0 pixel was arbitrarily set to a height of 0.0. And then use of the normals to decide on whether we're sloping up/down. I did this in the X direction (Red channel) and the Y direction (Green channel) and averaged the results (so two positive slopes increased the height, two negative slopes decreased, and all of the combinatorial variation inbetween).

Ah, but you see my problem. There is no Z-axis information stored in the Blue channel. This is why I said "embossing-style" normal map.

The Red channel contains the X-component on the plane.
The Green channel contains the Y-component on the plane.
The Blue channel is 100%, totally BLACK (every pixel = 0).

The Red and Green channels have a median of 128 (this is where the normal component is perpendicular to the plane). Then they vary between 0 and 255 to denote the slope.

But, I've seen normal maps like this reconstructed into height maps. It is a lossy conversion, yes, because information is missing. But it is possible! I don't think it's as much reconstructing a 3D normal as it is converting an incomplete set of 3D normals into an array of 1D vectors (the only requirement here is the vector magnitude off of the plane even if it is a relative and arbitrarily decided range, honto desu nee?).

I'd love to get the person who posted the "perform an integration over U and V" over here to beat some algorithms out of him. None were proferred.

Thanks and let me know if you can consider an approximation considering the missing component!

Robert
superpig
superpig
Quote:
Original post by Eelco
Quote:
Original post by superpig
I'd continue explaining this - specifically, the "sloping in such a way that" bit - but I've just noticed you say that the blue channel in your normal map is unused. As such, I don't see how you could do this - you can't reconstruct a three dimensional normal with only two components...

a normal has only 2 DOF's, since one it taken by the unit length contraint. using this contraint you can recalculate the third component using pythagoras, as shown in my snippet above.
Oh, of course. My bad.
Richard "Superpig" Fine - saving pigs from untimely fates - Microsoft DirectX MVP 2006/2007/2008/2009
"Shaders are not meant to do everything. Of course you can try to use it for everything, but it's like playing football using cabbage." - MickeyMouse
Kuroyume0161
Kuroyume0161
Quote:
Original post by Anonymous Poster
Normals that are perpendicular to the plane, so like being the plane's normal, they get the biggest height.


The problem with that assessment is that it is incorrect. The values of each "color" (for lack of a better word) denote the slope of the surface. Normals perpendicular to the plane (at the mediam 128 value) can be at -1000 meters or 10^10 meters, they just denote surfaces that point directly out of the image plane.

This is why baseline height establishment and slope are crucial to the conversion.

Robert
superpig
superpig
Quote:
Original post by Kuroyume0161
Ah, but you see my problem. There is no Z-axis information stored in the Blue channel. This is why I said "embossing-style" normal map.

The Red channel contains the X-component on the plane.
The Green channel contains the Y-component on the plane.
The Blue channel is 100%, totally BLACK (every pixel = 0).

The Red and Green channels have a median of 128 (this is where the normal component is perpendicular to the plane). Then they vary between 0 and 255 to denote the slope.

But, I've seen normal maps like this reconstructed into height maps. It is a lossy conversion, yes, because information is missing. But it is possible! I don't think it's as much reconstructing a 3D normal as it is converting an incomplete set of 3D normals into an array of 1D vectors (the only requirement here is the vector magnitude off of the plane even if it is a relative and arbitrarily decided range, honto desu nee?).

I'd love to get the person who posted the "perform an integration over U and V" over here to beat some algorithms out of him. None were proferred.

Thanks and let me know if you can consider an approximation considering the missing component!


This is the thing that Eelco explained, and that I missed first time around. All of your normals are normalised, that is, they're vectors of length 1.

For a vector to be of length 1, it stands that (xComponent ^ 2 + yComponent ^ 2 + zComponent ^ 2) == 1 - that's basic pythagoras, which I'd hope you learnt in high school. You know xComponent (call it R) and you know yComponent (call it G). You want to find zComponent (call it B). You know that (R^2 + G^2 + B^2) == 1. As such I expect you can see that B == sqrt(1 - R^2 - G^2) [smile]
Richard "Superpig" Fine - saving pigs from untimely fates - Microsoft DirectX MVP 2006/2007/2008/2009
"Shaders are not meant to do everything. Of course you can try to use it for everything, but it's like playing football using cabbage." - MickeyMouse
Kuroyume0161
Kuroyume0161
Pythagoras? Normalized? Huh?

;0)

I know what a normalized vector is (and Pythagoras). I see where you're going here. So the idea is assume a normalized resultant vector for all three vectors, one of which is missing (Z). Solve to find it.

But, will that give me relative height relationships corresponding to the overall normal map? In other words, will that tell me that the point in the middle which, for illustration, is 128/128 becomes 255 because it is the highest Z-point on the map, from the 128/128 corner point becoming 0 because that is the lowest Z-point on the map?

I've read over Dave Eberly's possible approach several times, but it still makes no more sense to me than if he were using Laplacians for Tensor analysis (which it might actually be!). The math he's throwing about I've seen mainly in Hydrodynamical flow systems with vector fields in Physics. Sorry, that's just beyond me at this stage. I can learn Calculus further, but it would take six months at least to become proficient enough for this stuff.

Several attempts have resulted in nothing useful. Very pretty solid gray or a nice diagonal gradient.

Robert
Eelco
Eelco
Quote:
Original post by Kuroyume0161
But, will that give me relative height relationships corresponding to the overall normal map? In other words, will that tell me that the point in the middle which, for illustration, is 128/128 becomes 255 because it is the highest Z-point on the map, from the 128/128 corner point becoming 0 because that is the lowest Z-point on the map?

not directly. its part of the puzzle though.

Quote:

Several attempts have resulted in nothing useful. Very pretty solid gray or a nice diagonal gradient.

Robert

the solid gray is probably due to failing to scale the result back into 0-255 range.

if you could post your code, i think id be able to help you further.
superpig
superpig
Quote:
Original post by Kuroyume0161
Pythagoras? Normalized? Huh?

;0)

I know what a normalized vector is (and Pythagoras). I see where you're going here. So the idea is assume a normalized resultant vector for all three vectors, one of which is missing (Z). Solve to find it.
Yup, pretty much. ("all three vectors" throws me a bit - did you mean 'components?')

Quote:
But, will that give me relative height relationships corresponding to the overall normal map? In other words, will that tell me that the point in the middle which, for illustration, is 128/128 becomes 255 because it is the highest Z-point on the map, from the 128/128 corner point becoming 0 because that is the lowest Z-point on the map?
No, it won't give you any absolute heights. This is the thing about the constant of integration - given a heightmap that has samples across a 10 meter range, you don't know if the actual height is 0-10 metres above sea level, or 100-110 meters. That information simply isn't present in the normal map, nor can it be retrieved from it.

I assume you're trying to produce a heightmap across some particular range of greyscale values (0-255, right?). One way you *could* go about doing this is to:

a) Assume that the pixel at 0,0 is at height 0
b) Work out the relative heights of all other pixels. The values at each could be positive or negative (you'd want to use something large than 0-255 at this stage - -32000 to +32000 ought to do it, the range of a signed integer).
c) Loop through all your calculated heights to find the minimum and maximum values.
d) Scale+offset all your calculated heights so that the minimum height is mapped to 0 and the maximum height is mapped to 255.

One side-effect of that is that a heightmap ranging from 0 to 1 cm will come out with the same sort of values as one ranging from 0 to 1 meter. If you want to use these heightmaps together, you'll need to pick and scaling + offset value for each map. Because you're using the full range of values for each map, though, you'll get the greatest level of detail (instead of trying to pack your 0..1cm map into the values '0' and '1' [smile]).
Richard "Superpig" Fine - saving pigs from untimely fates - Microsoft DirectX MVP 2006/2007/2008/2009
"Shaders are not meant to do everything. Of course you can try to use it for everything, but it's like playing football using cabbage." - MickeyMouse
Kuroyume0161
Kuroyume0161
Quote:
Original post by superpig
No, it won't give you any absolute heights. This is the thing about the constant of integration - given a heightmap that has samples across a 10 meter range, you don't know if the actual height is 0-10 metres above sea level, or 100-110 meters. That information simply isn't present in the normal map, nor can it be retrieved from it.

I assume you're trying to produce a heightmap across some particular range of greyscale values (0-255, right?). One way you *could* go about doing this is to:

a) Assume that the pixel at 0,0 is at height 0
b) Work out the relative heights of all other pixels. The values at each could be positive or negative (you'd want to use something large than 0-255 at this stage - -32000 to +32000 ought to do it, the range of a signed integer).
c) Loop through all your calculated heights to find the minimum and maximum values.
d) Scale+offset all your calculated heights so that the minimum height is mapped to 0 and the maximum height is mapped to 255.

One side-effect of that is that a heightmap ranging from 0 to 1 cm will come out with the same sort of values as one ranging from 0 to 1 meter. If you want to use these heightmaps together, you'll need to pick and scaling + offset value for each map. Because you're using the full range of values for each map, though, you'll get the greatest level of detail (instead of trying to pack your 0..1cm map into the values '0' and '1' [smile]).


This is basically what I've been trying to do without success. One major issue is when the surface normal is parallel to the image plane (the surface itself is vertical). Just like any Tangent or slope, this value goes to infinity (or is undefined). I'll have to be aware of that possibility and handle specially.

Robert
reltham
reltham
You really need to know the scale used during creation of the normal map in order to get a hieght map back out.

Take the simple case of a 2 texel (2x1) normal map. Say the first normal is pointing near 45 degrees in the x direction and the second normal is pointing straight up. If the scale was very large then the source texels in the height map could have been 0 and 10 (assuming a source range of 0 to 255), but with a small scale the source height map could have been 0 and 150.

Without knowing the scale you can't really reproduce the height map. You can only produce a scaled version of the height map which may not be useful.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.