Generating a Normal Map

Started by
51 comments, last by Geometrian 13 years, 6 months ago
Ok so I'm hoping this is possibly a straight forward question, but I can't find any useful information on it anywhere. Basically in a project I'm working on I create a procedural planet. I procedurally generate a landscape texture that is used as the surface for the planet. So these textures that I generate are always random and different. I'm wondering if I could somehow generate a normal map for that texture on the cpu and store it, but I'm unsure on how to create a normal map based off a texture.

Cheers,
Scott.
Advertisement
You can take the difference in x and y direction from your heightfield, from which you can construct two vectors that are (more or less) tangential to the surface at this point. The cross product of the two will give you a vector that is perpendicular to them. Normalizing this vector will give you a normal.

If you want better precision and a smoother overall look, you do that 8 times for all the adjacent height values and average the normals.
Hate to be a pain but could you demonstrate in a simple pseudo code example what you mean. What you said is a little blurry to me.

Cheers.
Google "Sobel Filter" and you're getting warm.
------------------------------Great Little War Game
given a depth map, calculate the xy distance it should be spread apart by every pixel, and how much z distance stands for a change in height.

youve then effectively made "3d" from "2d".

you can then calculate the surface vector by sampling the pixel and the pixel above it and making a vector. do this twice, once to the pixel above, and once to the pixel besides.

then the cross product of these two surface vectors is the normal.

VEC xyzdist;xyzdist.x=1.0f;  //modify these values to get the output youd like.xyzdist.y=1.0f;xyzdist.z=1.0f;for(i=0;i<mapsizex;i++){ for(j=0;j<mapsizey;j++) {  VEC surfacesample0=VEC(i*xyzdist.x,j*xyzdist.y,heightmap[i+(j+1)*mapsizex]*xyzdist.z);  VEC surfacesample1=VEC(i*xyzdist.x,(j+1)*xyzdist.y,heightmap[i+j*mapsizex]*xyzdist.z);  VEC surfacesample2=VEC((i+1)*xyzdist.x,j*xyzdist.y,heightmap[(i+1)+j*mapsizex]*xyzdist.z);  VEC surfacevec0=surfacesample1-surfacesample0;  VEC surfacevec1=surfacesample2-surfacesample0;   surfacevec0=Normalize(surfacevec0);  surfacevec1=Normalize(surfacevec1);   VEC surfacenormal=Cross(surfacevec0,surfacevec1);  surfacenormal=Normalize(surfacenormal);  normaloutput[i+j*mapsizex]=surfacenormal; }}
Your terrain is laid out as a regular grid, which means that you already know 2 components of the tangent/bitangent vectors, they are 0 and 1, respectively (it may actually be 0 and any number, depending on your scale. But it does not really matter as long as you consistenly use the same number, because only the direction is what you're interested in).
So, in pseudocode, for the tangent:
vec3 t(1, 0, height[y][x+1] - height[y][x]);

It's the exact same for the bitangent, only swapping 0 and 1, and using y+1 instead of x+1.

The cross product gives you a vector orthogonal to two (non-zero, non-parallel) vectors. So, cross(t, u) will give you a (non-normal) normal corresponding to the triangle formed by the three points used.
Because your input vectors are not unit length (they are larger, unless the terrain is flat), the length of the resulting normal will not be 1, thus the normal needs to be renormalized for really being a normal.
If you skip this, your lighting will be too bright in steep places.

Now, this will give you the normal for one triangle touching a point. If the terrain is flat, all triangles will have the same normal, but if the terrain is not flat, they will be different. Therefore you may see a more or less disturbing discontinuity from one vertex to the next. Which is the reason why you may want to average several normals. It depends on whether the discontinuity is disturbing or not (or maybe even desired), and on how much computional power you can invest.

You can find a dozen different algorithms for generating terrain normals via google, including an article on this site. Some of them do this "averaging" implicitely.
Thanks for the replys guys I'm doing all my calculations prior to in game, so it's all precalculated before you get into the game. So I've not to worry about how much is going on during run time.

I'm not sure if I explained myself very well. If any of use are familiar with a program called crazy bump I want to produce an image result like that. So no mater the image I put in, I was thinking I might be able to have an outcome like:

image

Using your algorithm rouncED (Not sure if I implemented part of it right, the float samples for the color values - R + G + B). And don't worry about the -1 in the for loops for now.

I'm generating a perlin noise texture thats sphereized so it wraps around the sphere mesh perfectly, it's of random colors for different planets, but for the sakes of what I'm showing use I just made it white and blue, so it's cloud like. But there times where it could be yellow brown and violet possibly etc. This is why I'm not to sure about how to generate a normal map like above. Anyway I implemented the algorithm like such.

Color[] normalArray = new Color[squared * squared];Vector3 xyzdist = new Vector3(1.0f, 1.0f, 1.0f);for (int i = 0; i < squared-1; i++){    for (int j = 0; j < squared-1; j++)    {        float sample1 = (colorArray.R + colorArray.G + colorArray.B);<br>        <span class="cpp-keyword">float</span> sample<span class="cpp-literal"><span class="cpp-number">2</span></span> = (colorArray.R + colorArray.G + colorArray.B);<br>        <span class="cpp-keyword">float</span> sample<span class="cpp-literal"><span class="cpp-number">3</span></span> = (colorArray[(i + <span class="cpp-literal"><span class="cpp-number">1</span></span>) + j * squared].R + colorArray[(i + <span class="cpp-literal"><span class="cpp-number">1</span></span>) + j * squared].G + colorArray[(i + <span class="cpp-literal"><span class="cpp-number">1</span></span>) + j * squared].B);<br>        Vector<span class="cpp-literal"><span class="cpp-number">3</span></span> surfacesample<span class="cpp-literal"><span class="cpp-number">0</span></span> = <span class="vb-function">new</span> Vector<span class="cpp-literal"><span class="cpp-number">3</span></span>(i * xyzdist.X, j * xyzdist.Y, sample<span class="cpp-literal"><span class="cpp-number">1</span></span> * xyzdist.Z);<br>        Vector<span class="cpp-literal"><span class="cpp-number">3</span></span> surfacesample<span class="cpp-literal"><span class="cpp-number">1</span></span> = <span class="vb-function">new</span> Vector<span class="cpp-literal"><span class="cpp-number">3</span></span>(i * xyzdist.X, (j + <span class="cpp-literal"><span class="cpp-number">1</span></span>) * xyzdist.Y,  sample<span class="cpp-literal"><span class="cpp-number">2</span></span> * xyzdist.Z);<br>        Vector<span class="cpp-literal"><span class="cpp-number">3</span></span> surfacesample<span class="cpp-literal"><span class="cpp-number">2</span></span> = <span class="vb-function">new</span> Vector<span class="cpp-literal"><span class="cpp-number">3</span></span>((i + <span class="cpp-literal"><span class="cpp-number">1</span></span>) * xyzdist.X, j * xyzdist.Y,  sample<span class="cpp-literal"><span class="cpp-number">3</span></span> * xyzdist.Z);<br><br>        Vector<span class="cpp-literal"><span class="cpp-number">3</span></span> surfacevec<span class="cpp-literal"><span class="cpp-number">0</span></span> = surfacesample<span class="cpp-literal"><span class="cpp-number">1</span></span> - surfacesample<span class="cpp-literal"><span class="cpp-number">0</span></span>;<br>        Vector<span class="cpp-literal"><span class="cpp-number">3</span></span> surfacevec<span class="cpp-literal"><span class="cpp-number">1</span></span> = surfacesample<span class="cpp-literal"><span class="cpp-number">2</span></span> - surfacesample<span class="cpp-literal"><span class="cpp-number">0</span></span>;<br><br>        surfacevec<span class="cpp-literal"><span class="cpp-number">0</span>.</span>Normalize();<br>        surfacevec<span class="cpp-literal"><span class="cpp-number">1</span>.</span>Normalize();<br><br>        Vector<span class="cpp-literal"><span class="cpp-number">3</span></span> surfacenormal = <span class="vb-function">new</span> Vector<span class="cpp-literal"><span class="cpp-number">3</span></span>();<br>        Vector<span class="cpp-literal"><span class="cpp-number">3</span>.</span>Cross(<span class="cpp-keyword">ref</span> surfacevec<span class="cpp-literal"><span class="cpp-number">0</span></span>, <span class="cpp-keyword">ref</span> surfacevec<span class="cpp-literal"><span class="cpp-number">1</span></span>, <span class="cpp-keyword">out</span> surfacenormal);<br><br>        surfacenormal.Normalize();<br><br>        normalArray = <span class="vb-function">new</span> Color(surfacenormal);<br>    }<br>}<br>texture = <span class="vb-function">new</span> Texture<span class="cpp-literal">2D</span>(Device, squared, squared);<br>texture.SetData(normalArray);<br><br></pre></div><!–ENDSCRIPT–><br><br>Heres the image that was used:<br><br><img src="http://i52.tinypic.com/2rrsyoo.jpg" alt="image" /> <br><br>And heres the map generated off this type of noise image. Obviously it's not the same, but it's the same perlin generation, also I'm just displaying the image that was generated from the input image, im not actually using it as a normalmap yet.<br><br><img src="http://i56.tinypic.com/9h6ko9.jpg" alt="image" /> <br><br>Thanks for the help so far guys :)<br>
Hey great! I told you what to write and here it goes!
Hey that actually almost looks right.
We are almost there...

looks like you might have to invert the z, cause its not looking blue...

so try that, but youve got to remember I only told you how to get world space normals, you see, textures are stored in unsigned format, so you dont get negative values.

to convert unsigned you have to add 1 to it and divide it by two, like this->

red=(normal.x+1)/2;
green=(normal.y+1)/2;
blue=(normal.z+1)/2;

then in the normal mapping shader, you then have to do this.

x=red*2-1;
y=green*2-1;
z=blue*2-1;

to restore the normal back to negative values. (but to just use the normal map as a colour map (to see it look right like that normal map you showed us)) you needent convert to negatives yet.



but either invert the z, or reverse the cross product, in otherwords go "cross(surfacevec2, surfacevec1)" instead of the other way around, like it is now.

Actually try reversing the cross product, it would be more correct than inverting the z.

Dont forget to show me a picture once youve done this!

Awesome work man! You really make me feel like im making a difference, i never get that...
Awesome man, thanks for the help :D! So by doing it to the first way you said, inverting the Z and turning the values into unsigned numbers:

surfacenormal.Z = -surfacenormal.Z;
surfacenormal.X = (surfacenormal.X + 1.0f / 2.0f);
surfacenormal.Y = (surfacenormal.Y + 1.0f / 2.0f);
surfacenormal.Z = (surfacenormal.Z + 1.0f / 2.0f);

normalArray = new Color(surfacenormal);<br><br>I get:<br><br><img src="http://i52.tinypic.com/32zrj1y.jpg" alt="image" /><br><br>And by flipping the cross product the other way around instead of inverting the Z I get:<br><br><img src="http://i55.tinypic.com/2ekiww8.jpg" alt="image" /><br><br>These images seem much more correct now, but would I be wrong in thinking there is maybe a little to much red? I'm not to sure but either way I'll test this out in a shader now and see how it goes. Thanks a heap :)!!!
Ok, I feel a little stupid now. To use this as a normal map in a shader, am I going to need to know the tangents and bi-normals? If so how am I meant to generate the tangents from my diffuse texture and new normal texture?

This topic is closed to new replies.

Advertisement