<p>You are on the right track with your post...</p>
<p>Compute two slopes then somehow use those slopes to find a normal. The slopes are the result of finite difference (like central difference)</p>
<p>But why two slopes? Because we can use them to construct two independent vectors whose cross product will become our normal vector.</p>
<p>One vector is in the texture's <span class="math-container">$u$</span> direction and is simply <span class="math-container">$vec3(1,0,slopeU)$</span></p>
<p>The other vector is in the textures <span class="math-container">$v$</span> direction and is <span class="math-container">$vec3(0,1,slopeV)$</span></p>
<p><strong>W</strong>here are these vectors coming from?
Take a look at a 2D cartesian coordinate system such as <a href="https://dqm1v390v3ac1.cloudfront.net/img01_22.jpg" rel="nofollow noreferrer">this one</a>. Now overlay the height map texture over the cartesian coordinate system with the point we are interested in located at the origin. The u direction of the texture lines up with the x direction of the coordinate system. Compute the u direction slope for the heightmap at that point using central differences. This samples a=(-1,0) and b=(1,0) on the cartesian coordinate system. These two points form a line on the x axis. This is the same direction the vector needs to point. Remember that vectors have direction but no location, so as long as we pick a vector that points in the same direction as the slope calculation then it doesn't matter what values we plug into the vector, just that it has the right direction. A natural choice for the vector in the same direction as the slope is (1,0) combined with the slope gives (1,0,slopeU).</p>
<p>With the two directions computed, and the two vectors created:
Take the cross product and normalize, its really that simple.</p>
<p>Both of the vectors are tangent to the height, and they are independent of each other. They can be constructed as <span class="math-container">$(0,1,S_v)$</span> and <span class="math-container">$(1,0,S_u)$</span> because the slopes are very specifically taken in those directions. If you change the directions used to compute the slopes then you have to change the directions used in the vectors to match.</p>
<p>There are a few other minor caveats, like the calculations are assumed to always happen at the origin, and the height map, which usually contains values between 0 and 1 needs to be scaled to its final height.</p>
<p><strong>O</strong>rigin and direction choices:
Yes, the choice to do the calculations at the origin is completely arbitrary. It just makes the math more convenient.</p>
<p>The choice of the (1,0) and (0,1) is not arbitrary. These are chosen because they lie in the exact same direction as the computed slopes. If we chose (12,243,slopeU) the question becomes where does this vector point? Well this vector points very heavily toward the y axis. (the y value of 243 ) so our calculation of the slope along the x (slopeU) axis will be used to compute a slope pointing nearly at the y axis and the end result will give a very skewed value. For this reason, most applications assume a grid spacing of 1 between height map points.</p>
<p><strong>T</strong>heory is all nice, but where the rubber meets the road we need something that can be implemented, and runs in a timely manner. These functions can be implemented naively on the CPU or easily translated to a Compute Shader. They can also be used in real time to compute normals for height mapped geometry like water and terrain. Also the functions come straight out of the book FGED2 which gives brief but good explanation of the math here. This implementation of central difference can also be easily extended to 3 dimensions where it can be used to compute normals for isosurface extraction in real time.</p>
<p>slope in x direction: <span class="math-container">$d_x = \frac{\Delta z}{ \Delta x} = \frac{scale}{2}[h(i+1,j)-h(i-1,j)]$</span></p>
<p>slope in y direction: <span class="math-container">$d_y = \frac{\Delta z}{ \Delta y} = \frac{scale}{2}[h(i,j+1)-h(i,j-1)]$</span></p>
<p>x direction vector: <span class="math-container">$u_x = (1,0,d_x)$</span></p>
<p>y direction vector: <span class="math-container">$u_y = (0,1,d_y)$</span></p>
<p>Final Value: <span class="math-container">$m = normalize(u_x \times u_y ) = \frac{(-d_x,-d_y,1)}{\sqrt{d^2_x+d^2_y+1}}$</span></p>

CC BY-SA 4.0
2021-11-15T12:50:36.390Z
Yes I had seen this bit elsewhere but froze at the vectors themselves that you had mentioned. How and why exactly are the components of the vectors (1,0, slopeU) and (0,1, slopeV)? What is the intuitive reasoning as to how we got those components? I can tell, that for the first one , we get it by doing (dx/dx, dy/dx , df(u,v)/dx) which gives us (1, 0 , slopeU). But why exactly do we do that? Why does it give us the tangent vector?
Hash
CC BY-SA 4.0
2021-11-15T13:40:07.913Z
I added an edit to the answer to help explain where the vectors come from.
pmw1234
CC BY-SA 4.0
2021-11-15T19:22:25.420Z
After having gone through your edit, I have a few more questions. Why must the height map be centered at the origin? The choice seems arbitrary. Is there an actual reason? 

Also in " A natural choice for the vector in the same direction as the slope is (1,0) combined with the slope gives (1,0,slopeU).", why exactly do we combine (1,0) with the slope? I understand we just need the vector to point in the direction we want, but if it was say (12,243,slopeU) or something similar, it would point in a diff direction. Is there a reason we specifically took (1,0)?
Hash
CC BY-SA 4.0
2021-11-16T12:34:20.273Z
I added a bit of explanation for your question above, and added some math showing a sample computation using central differences.
pmw1234
CC BY-SA 4.0
2021-11-16T13:13:58.653Z
It is worth mentioning that this description isn't all that different from @lightxbulb. The major difference is this description uses several constants values which allows some of the math to be simplified. The reason I gave this particular description is because your question very specifically requested how to get normal maps for bump maps. The explanation above is pretty much industry standard with a few variations on the theme.
pmw1234
CC BY-SA 4.0
2021-11-16T16:26:51.473Z
In "The choice of the (1,0) and (0,1) is not arbitrary. These are chosen because they lie in the exact same direction as the computed slopes.", do you mean that (0,1) is picked because (0,1) lies in the same direction as (in this instance) the slope of the surface when y changes by a small amount, or do you mean that (0,1, dy) points in the same direction as the slope of the surface when y changes by a small amount?
Hash
CC BY-SA 4.0
2021-11-16T16:46:51.260Z
They both do, the 2d version (0,1) can only point in directions that lie in the plane, and the 3D version (0,1,dy) is point the same direction as the 2d version, but isn't restricted to the plane.
pmw1234
CC BY-SA 4.0
2021-11-16T16:56:54.033Z
What do you mean by it ((0,1,dy)) not being restricted to a plane? Do you mean that it is parallel to (0,1) but translated up by some specific amount such that it no longer lies on the plane?
Hash
CC BY-SA 4.0
2021-11-16T16:59:29.827Z
Exactly, the slope calc of $d_y$ is a quick and dirty derivative which produces the "change in" height, so the code just plugs that into the 2D vector  to get the 3D sloped vector.
pmw1234
CC BY-SA 4.0
2021-11-17T11:05:02.127Z
But wont adding dy to (0,1) such that it goes from (0,1,0) to giving us (0,1,dy) produce a vector that isn't parallel to (0,1,0)? (0,1,dy) is like (0,1,0) except tilted up rather than translated up right?
Hash
CC BY-SA 4.0
2021-11-17T12:01:58.560Z
and thats how we get a normal ...from the cross product of two tilted orthogonal vectors
pmw1234
CC BY-SA 4.0
2021-11-17T18:57:26.140Z
But you had said earlier in " 2d version (0,1) can only point in directions that lie in the plane, and the 3D version (0,1,dy) is point the same direction as the 2d version, but isn't restricted to the plane" that they werent tilted but rather pointing in the same direction? That is what I am confused by
Hash
CC BY-SA 4.0
2021-11-19T18:29:38.323Z
BTW, see this article for a bit more about the relationship between normal vectors and slopes/derivatives - might be helpful: https://www.reedbeta.com/blog/slope-space-in-brdf-theory/
Nathan Reed
CC BY-SA 4.0
2021-11-22T14:03:03.373Z
@NathanReed Thank you for the article! I did go through it, but I was very quickly left confused, primarily because 1) a lot of math concepts discussed there was new to me 2) paragraphs related to the math concepts I already knew were a bit difficult to intuit (Such as from the slope-space subheading onwards)

Is the post supposed to be understandable/intended towards by people new/fresh to graphics programming or is it intended for people experienced with graphics programming?
Hash
CC BY-SA 4.0
2021-11-15T11:35:12.277Z
2021-11-16T12:29:49.957Z
2021-11-16T12:29:49.957Z
pmw1234
<p>I have rewritten my answer to make it easier to follow and I also fixed some mistakes.</p>
<p>Let <span class="math-container">$S:[0,1]^2 \rightarrow \mathbb{R}^3$</span> be some parameterization of your surface (assume that the derivatives do not become zero anywhere, i.e. it's regular/an immersion). That is, the set of points of your surface is <span class="math-container">$\mathcal{M} = \{S(\beta, \gamma) \,:\, (\beta, \gamma) \in [0,1]^2\}$</span>. Let <span class="math-container">$h:[0,1]^2\rightarrow \mathbb{R}$</span> be the heightmap. The unit normal <span class="math-container">$n:[0,1]^2\rightarrow \mathbb{R}^3$</span> is given by the normalized cross product of the partial derivatives: <span class="math-container">$n = \frac{\partial_\beta S \times \partial_\gamma S}{\|\partial_\beta S \times \partial_\gamma S\|}$</span>. Then we displace the surface with the heightmap along the normal in order to construct a new surface <span class="math-container">$T(\beta, \gamma) = S(\beta, \gamma) + h(u(\beta, \gamma),v(\beta, \gamma))n(\beta, \gamma)$</span>. We want to find the normal of <span class="math-container">$T$</span>:</p>
<p><span class="math-container">$$m = \partial_\beta T \times \partial_\gamma T$$</span>
<span class="math-container">$$\partial_\beta T = \partial_\beta S+(\partial_\beta h)n + h(\partial_\beta n), \quad \partial_\gamma T = \partial_\gamma S+(\partial_\gamma h)n + h(\partial_\gamma n)$$</span>
<span class="math-container">$$ \partial_\beta T \approx \partial_\beta S+(\partial_\beta h)n, \quad \partial_\gamma T \approx \partial_\gamma S+(\partial_\gamma h)n$$</span>
<span class="math-container">$$m\approx (\partial_\beta S+(\partial_\beta h)n)\times(\partial_\gamma S+(\partial_\gamma h)n) = $$</span>
<span class="math-container">$$\require{cancel}\partial_\beta S\times\partial_\gamma S + (\partial_\beta  h)(n\times\partial_\gamma S) + (\partial_\gamma h)(\partial_\beta S\times n) + \cancel{(\partial_\beta h)(\partial_\gamma h)(n\times n)}$$</span></p>
<p>The above expression gives you an approximation of the (not necessarily unit length) normal of the displaced surface <span class="math-container">$T$</span>. This is Blinn's approximation - it assumes that <span class="math-container">$h$</span> is small enough for <span class="math-container">$h(\partial_\beta n)$</span> and <span class="math-container">$h(\partial_\gamma n)$</span> to be ignored.</p>
<p>Let us consider how this can be applied in practice. There you usually have a triangle mesh. Then if we know how to modify the normal for every triangle, we know how to modify it for the whole mesh. Consider a specific triangle with vertices <span class="math-container">$p_0, p_1, p_2 \in \mathbb{R}^3$</span>. We can parametrize the triangle through barycentric coordinates <span class="math-container">$\alpha, \beta, \gamma \geq 0, \alpha + \beta + \gamma = 1$</span>:</p>
<p><span class="math-container">$$S(\beta, \gamma) = \alpha p_0 + \beta p_1 + \gamma p_2 = \\
(1-\beta-\gamma) p_0 + \beta p_1 + \gamma p_2 = p_0 + \beta (p_1-p_0) + \gamma (p_2-p_0).$$</span></p>
<p>We can now compute the partial derivatives of <span class="math-container">$S$</span>:</p>
<p><span class="math-container">$$\partial_{\beta} S = p_1 - p_0, \, \partial_{\gamma} S = p_2 - p_0,$$</span></p>
<p>which turn out to be the edges of the triangle. Then the geometric normal is:</p>
<p><span class="math-container">$$n_g = \partial_{\beta}S \times \partial_\gamma S, \, \hat{n}_g = \frac{n_g}{\|n_g\|}.$$</span></p>
<p>We also need <span class="math-container">$\partial_{\beta} h, \, \partial_\gamma h$</span> in order to be able to evaluate the perturbed normal. Let the texture coordinates at the 3 triangle vertices be <span class="math-container">$uv_0, uv_1, uv_2 \in \mathbb{R}^2$</span>, then the reparametrization from barycentric coordinates to UV coordinates is given by:</p>
<p><span class="math-container">$$uv(\beta, \gamma) = uv_0 + \beta (uv_1-uv_0) + \gamma (uv_2-uv_0).$$</span></p>
<p>We can now compute the partial derivatives:</p>
<p><span class="math-container">$$\partial_\beta h(uv(\beta, \gamma)) = (uv_1-uv_0) \cdot (\partial_u h(uv(\beta, \gamma)),\partial_v h(uv(\beta, \gamma)))$$</span>
<span class="math-container">$$\partial_\gamma h(uv(\beta, \gamma)) = (uv_2-uv_0) \cdot (\partial_u h(uv(\beta, \gamma)),\partial_v h(uv(\beta, \gamma)))$$</span></p>
<p>The finally the perturbed normal is:</p>
<p><span class="math-container">$$m_g = \partial_{\beta}S\times\partial_{\gamma}S + (\partial_{\beta} h)\hat{n}_g\times\partial_{\gamma}S + (\partial_{\gamma}h)\partial_{\beta}S\times \hat{n}_g.$$</span></p>
<p>To compute <span class="math-container">$\partial_u h, \, \partial_v h$</span> you can use finite differences:</p>
<p><span class="math-container">$$\partial_u h(u,v) \approx \frac{h(u+\epsilon,v) - h(u,v)}{\epsilon}, \, \partial_v h(u,v) \approx \frac{h(u, v+\epsilon) - h(u,v)}{\epsilon}$$</span></p>
<p>Here is some code to clarify how this would be implemented:</p>
<pre><code>// Assume the following is given for a triangle
vec3 p0, p1, p2;    // positions of the 3 vertices
vec2 uv0, uv1, uv2; // texture coordinates at the 3 vertices
// epsilon for the finite differences
float eps;
// we want to compute the normal at bary coords (beta, gamma)
float beta, gamma;  // barycentric coordinates


// compute edges
vec3 e1 = p1-p0;
vec3 e2 = p2-p0;

// compute geometric normal
vec3 n_g = cross(e1,e2);
vec3 n_g_n = normalize(n_g);

// compute n x (d_gamma S)
vec3 t_g = -cross(n_g_n, e2);

// compute (d_beta S) x n
vec3 b_g = -cross(e1, n_g_n);

// compute partial derivatives of h
vec2 uv = uv0 + beta * (uv1-uv0) + gamma * (uv2-uv0);
vec2 h_uv = 1/eps * (vec2(h(uv+vec2(eps,0)), h(uv+vec2(0,eps))) - vec2(h(uv)));
float h_beta = dot(uv1-uv0, h_uv);
float h_gamma = dot(uv2-uv0, h_uv);

// compute coefficients
float coef_n = 1.0;
float coef_t = -h_beta;
float coef_b = -h_gamma;

// compute new geometric normal
vec3 m_g = coef_n * n_g + coef_t * t_g + coef_b * b_g;
</code></pre>
<p>Note that this matches how normal mapping is sometimes defined, with a sampled normal <span class="math-container">$(c_n, c_t, c_b) = (1, -\partial_\beta h, -\partial_\gamma h)$</span>:</p>
<p><span class="math-container">$$m_g = c_n (\partial_\beta S \times \partial_\gamma S) + c_t (\partial_\gamma S\times\hat{n}_g) + c_b (\hat{n}_g\times \partial_\beta S).$$</span></p>
<p>You may notice that the cross product arguments have been switched around in the last two term. This is because of how tangent space is defined when doing normal mapping. You will see I have done the same in the code (the minuses involved in <span class="math-container">$t_g, b_g$</span>). The above doesn't take into account shading normals. When considering those, various approaches have been proposed. I can suggest looking into <a href="http://image.diku.dk/projects/media/morten.mikkelsen.08.pdf" rel="nofollow noreferrer">Simulation of Wrinkled Surfaces Revisited by Mikkelsen</a> for more details on that.</p>

CC BY-SA 4.0
2021-11-15T09:53:51.693Z
Admittedly a LOT of the math here is going over my head so I am having trouble understanding it. I recall seeing a few of these equations on a site that explained bump maps : https://pbr-book.org/3ed-2018/Materials/Bump_Mapping
I did have trouble understanding them though. 

Is there a way to understand this without heavy math notations?
Hash
CC BY-SA 4.0
2021-11-15T10:39:19.360Z
@Hash The math is saying: deform every point of your original surface $S$ by moving along its normal by the corresponding value in the heightmap - this yields a displacement surface $T$. You want to find the normal of this displacement surface $T$, so that you can use this normal instead of the actual one. The key statement that I use here is that the normal of a surface is given by the cross product of its partial derivatives (e.g. for a triangle it's the cross product of its edges). For partial derivatives see: https://tutorial.math.lamar.edu/classes/calciii/PartialDerivsIntro.aspx
lightxbulb
CC BY-SA 4.0
2021-11-15T11:32:36.807Z
Yes, I got that bit, but how do we obtain the partial derivatives exactly? To crossproduct, each of the 2 vectors involved in the crossproduct would have 3 components (x,y,z). How would we get these individual components?
Hash
CC BY-SA 4.0
2021-11-15T16:35:23.343Z
@Hash Try the last formula for $m$ in the edit. I am not sure whether this is exactly what they do in practice though. You can look at some implementation to figure out what hack different people use to take into account shading normals.
lightxbulb
CC BY-SA 4.0
2021-11-15T20:20:13.217Z
I find a lot of these math terms rather difficult to parse and understand (mostly due to my lack of familiarity with them). I'll try my level best to read up on the underlying math topics soon, and come back to this in the future to understanding and ask any further doubts
Hash
CC BY-SA 4.0
2021-11-16T09:08:37.597Z
@Hash I updated the answer with some code since I am not sure which part of the math you do not understand. I also removed the ad-hoc solution, since there are better solutions for shading normals.
lightxbulb
CC BY-SA 4.0
2021-11-16T16:54:49.220Z
to be more specific, I have not faced barycentric coordinates before (and what little I saw confused me) (along with the very idea of a surface given by a parametrized equation and partial derivatvives, of which I have only learnt a small bit very recently when researching my question earlier).

 I do understand that attempting to understand this problem requires me to be fresh with at least the idea of barycentric coordinates, so I will try to read up on these concepts and return to this answer
Hash
CC BY-SA 4.0
2021-11-16T19:31:19.970Z
@Hash Are you in a rasterization setting or a ray-tracing one? For ray-tracing you can get the barycentric coordinates directly from you ray-triangle intersection routine (they naturally pop up in the solution). In a rasterization setting you don't typically even need those, as the values you're interested in get interpolated while rasterizing. Also as you can see from the code, there are no partial derivatives that remain except the ones for h, which I approximate through finite differences. The remaining partial derivatives are just $p1−p0$ and $p2−p0$.
lightxbulb
CC BY-SA 4.0
2021-11-17T11:01:39.773Z
Rasterization. It is what I was planning to learn and delve into
Hash
CC BY-SA 4.0
2021-11-17T14:57:05.790Z
@Hash Here's how you can compute the tangent and bitangent: https://learnopengl.com/Advanced-Lighting/Normal-Mapping https://github.com/ssloy/tinyrenderer/wiki/Lesson-6bis:-tangent-space-normal-mapping#let-us-compute-darboux-basis-and-apply-the-perturbation-of-normals After that plug in the $(-\partial_\beta h, -\partial_\gamma h, 1)$.
lightxbulb
CC BY-SA 4.0
2021-11-14T20:53:23.420Z
2021-11-16T09:19:41.743Z
2021-11-16T09:19:41.743Z
lightxbulb
<p>From what I understand, grey-scale bump maps are used to perturb normals on a surface such that the surface appears to have bumps. With a regular tangent normal map, it seems relatively intuitive enough, considering each pixel contains the 'normal' itself so to speak</p>
<p>A bump map only measures changes in height as far as I know. In a software like Blender, I can plug in a grey-scale bump map and get results similar to the bumps produced by a normal map (Considering they more or less produce the same end result - to perturb normals), so certainly, these ARE being converted to normals somehow</p>
<p>The question is how exactly. I've being cracking my head over this for so long and after dozens of searches, can't find exactly what I need</p>
<p>From what I know so far, since you only store height info in a bump map, the only meaningful way to 'extract' normals out of it is via finding the partial derivative along each axis of the bump map, and then.....I'm sorta blank in regards to the rest</p>
<p>From what I know, to find the partial derivative along each axis, you could use the central difference method (or forward difference method), to find how much the function f(u,v) [that returns a pixel representing a height value at the coordinates on the map (u,v)] changes as we move from left to right (for partial derivatives along the x axis or u), and up to down for the y/v axis</p>
<p>So we find how much the function changes as we move along either of the two axes. What do we do next to finally attain the normal? I am sorta lost. I would like some help with the intuition behind it as well rather than just plain equations since I really want to understand them thoroughly</p>
<p>I will also add that I am just delving in the theory now, I haven't done any computer graphics programming YET, so I dont need any API specific help</p>

<texture><normal-mapping>
How are the normals 'derived' from grey scale bump maps?
Hash
# How are the normals 'derived' from grey scale bump maps?

(Asked by: username_0 on 2021-11-14)

From what I understand, grey-scale bump maps are used to perturb normals on a surface such that the surface appears to have bumps. With a regular tangent normal map, it seems relatively intuitive enough, considering each pixel contains the 'normal' itself so to speak

A bump map only measures changes in height as far as I know. In a software like Blender, I can plug in a grey-scale bump map and get results similar to the bumps produced by a normal map (Considering they more or less produce the same end result - to perturb normals), so certainly, these ARE being converted to normals somehow

The question is how exactly. I've being cracking my head over this for so long and after dozens of searches, can't find exactly what I need

From what I know so far, since you only store height info in a bump map, the only meaningful way to 'extract' normals out of it is via finding the partial derivative along each axis of the bump map, and then.....I'm sorta blank in regards to the rest

From what I know, to find the partial derivative along each axis, you could use the central difference method (or forward difference method), to find how much the function f(u,v) [that returns a pixel representing a height value at the coordinates on the map (u,v)] changes as we move from left to right (for partial derivatives along the x axis or u), and up to down for the y/v axis

So we find how much the function changes as we move along either of the two axes. What do we do next to finally attain the normal? I am sorta lost. I would like some help with the intuition behind it as well rather than just plain equations since I really want to understand them thoroughly

I will also add that I am just delving in the theory now, I haven't done any computer graphics programming YET, so I dont need any API specific help

**Tags:** texture, normal-mapping


---

## 2 answers

### Answer by username_1 on 2021-11-15. Score: 1

You are on the right track with your post...

Compute two slopes then somehow use those slopes to find a normal. The slopes are the result of finite difference (like central difference)

But why two slopes? Because we can use them to construct two independent vectors whose cross product will become our normal vector.

One vector is in the texture's $u$ direction and is simply $vec3(1,0,slopeU)$

The other vector is in the textures $v$ direction and is $vec3(0,1,slopeV)$

**W**here are these vectors coming from?
Take a look at a 2D cartesian coordinate system such as [this one](https://dqm1v390v3ac1.cloudfront.net/img01_22.jpg). Now overlay the height map texture over the cartesian coordinate system with the point we are interested in located at the origin. The u direction of the texture lines up with the x direction of the coordinate system. Compute the u direction slope for the heightmap at that point using central differences. This samples a=(-1,0) and b=(1,0) on the cartesian coordinate system. These two points form a line on the x axis. This is the same direction the vector needs to point. Remember that vectors have direction but no location, so as long as we pick a vector that points in the same direction as the slope calculation then it doesn't matter what values we plug into the vector, just that it has the right direction. A natural choice for the vector in the same direction as the slope is (1,0) combined with the slope gives (1,0,slopeU).

With the two directions computed, and the two vectors created:
Take the cross product and normalize, its really that simple.

Both of the vectors are tangent to the height, and they are independent of each other. They can be constructed as $(0,1,S\_v)$ and $(1,0,S\_u)$ because the slopes are very specifically taken in those directions. If you change the directions used to compute the slopes then you have to change the directions used in the vectors to match.

There are a few other minor caveats, like the calculations are assumed to always happen at the origin, and the height map, which usually contains values between 0 and 1 needs to be scaled to its final height.

**O**rigin and direction choices:
Yes, the choice to do the calculations at the origin is completely arbitrary. It just makes the math more convenient.

The choice of the (1,0) and (0,1) is not arbitrary. These are chosen because they lie in the exact same direction as the computed slopes. If we chose (12,243,slopeU) the question becomes where does this vector point? Well this vector points very heavily toward the y axis. (the y value of 243 ) so our calculation of the slope along the x (slopeU) axis will be used to compute a slope pointing nearly at the y axis and the end result will give a very skewed value. For this reason, most applications assume a grid spacing of 1 between height map points.

**T**heory is all nice, but where the rubber meets the road we need something that can be implemented, and runs in a timely manner. These functions can be implemented naively on the CPU or easily translated to a Compute Shader. They can also be used in real time to compute normals for height mapped geometry like water and terrain. Also the functions come straight out of the book FGED2 which gives brief but good explanation of the math here. This implementation of central difference can also be easily extended to 3 dimensions where it can be used to compute normals for isosurface extraction in real time.

slope in x direction: $d\_x = \frac{\Delta z}{ \Delta x} = \frac{scale}{2}[h(i+1,j)-h(i-1,j)]$

slope in y direction: $d\_y = \frac{\Delta z}{ \Delta y} = \frac{scale}{2}[h(i,j+1)-h(i,j-1)]$

x direction vector: $u\_x = (1,0,d\_x)$

y direction vector: $u\_y = (0,1,d\_y)$

Final Value: $m = normalize(u\_x \times u\_y ) = \frac{(-d\_x,-d\_y,1)}{\sqrt{d^2\_x+d^2\_y+1}}$

> **Comments:**
> **username_0**: Yes I had seen this bit elsewhere but froze at the vectors themselves that you had mentioned. How and why exactly are the components of the vectors (1,0, slopeU) and (0,1, slopeV)? What is the intuitive reasoning as to how we got those components? I can tell, that for the first one , we get it by doing (dx/dx, dy/dx , df(u,v)/dx) which gives us (1, 0 , slopeU). But why exactly do we do that? Why does it give us the tangent vector?  
> **username_1**: I added an edit to the answer to help explain where the vectors come from.  
> **username_0**: After having gone through your edit, I have a few more questions. Why must the height map be centered at the origin? The choice seems arbitrary. Is there an actual reason?   Also in " A natural choice for the vector in the same direction as the slope is (1,0) combined with the slope gives (1,0,slopeU).", why exactly do we combine (1,0) with the slope? I understand we just need the vector to point in the direction we want, but if it was say (12,243,slopeU) or something similar, it would point in a diff direction. Is there a reason we specifically took (1,0)?  
> **username_1**: I added a bit of explanation for your question above, and added some math showing a sample computation using central differences.  
> **username_1**: It is worth mentioning that this description isn't all that different from @username_3. The major difference is this description uses several constants values which allows some of the math to be simplified. The reason I gave this particular description is because your question very specifically requested how to get normal maps for bump maps. The explanation above is pretty much industry standard with a few variations on the theme.  
> **username_0**: In "The choice of the (1,0) and (0,1) is not arbitrary. These are chosen because they lie in the exact same direction as the computed slopes.", do you mean that (0,1) is picked because (0,1) lies in the same direction as (in this instance) the slope of the surface when y changes by a small amount, or do you mean that (0,1, dy) points in the same direction as the slope of the surface when y changes by a small amount?  
> **username_1**: They both do, the 2d version (0,1) can only point in directions that lie in the plane, and the 3D version (0,1,dy) is point the same direction as the 2d version, but isn't restricted to the plane.  
> **username_0**: What do you mean by it ((0,1,dy)) not being restricted to a plane? Do you mean that it is parallel to (0,1) but translated up by some specific amount such that it no longer lies on the plane?  
> **username_1**: Exactly, the slope calc of $d_y$ is a quick and dirty derivative which produces the "change in" height, so the code just plugs that into the 2D vector  to get the 3D sloped vector.  
> **username_0**: But wont adding dy to (0,1) such that it goes from (0,1,0) to giving us (0,1,dy) produce a vector that isn't parallel to (0,1,0)? (0,1,dy) is like (0,1,0) except tilted up rather than translated up right?  
> **username_1**: and thats how we get a normal ...from the cross product of two tilted orthogonal vectors  
> **username_0**: But you had said earlier in " 2d version (0,1) can only point in directions that lie in the plane, and the 3D version (0,1,dy) is point the same direction as the 2d version, but isn't restricted to the plane" that they werent tilted but rather pointing in the same direction? That is what I am confused by  
> **username_2**: BTW, see this article for a bit more about the relationship between normal vectors and slopes/derivatives - might be helpful: https://www.reedbeta.com/blog/slope-space-in-brdf-theory/  
> **username_0**: @username_2 Thank you for the article! I did go through it, but I was very quickly left confused, primarily because 1) a lot of math concepts discussed there was new to me 2) paragraphs related to the math concepts I already knew were a bit difficult to intuit (Such as from the slope-space subheading onwards)  Is the post supposed to be understandable/intended towards by people new/fresh to graphics programming or is it intended for people experienced with graphics programming?  

---

### Answer by username_3 on 2021-11-14. Score: 1

I have rewritten my answer to make it easier to follow and I also fixed some mistakes.

Let $S:[0,1]^2 \rightarrow \mathbb{R}^3$ be some parameterization of your surface (assume that the derivatives do not become zero anywhere, i.e. it's regular/an immersion). That is, the set of points of your surface is $\mathcal{M} = \{S(\beta, \gamma) \,:\, (\beta, \gamma) \in [0,1]^2\}$. Let $h:[0,1]^2\rightarrow \mathbb{R}$ be the heightmap. The unit normal $n:[0,1]^2\rightarrow \mathbb{R}^3$ is given by the normalized cross product of the partial derivatives: $n = \frac{\partial\_\beta S \times \partial\_\gamma S}{\|\partial\_\beta S \times \partial\_\gamma S\|}$. Then we displace the surface with the heightmap along the normal in order to construct a new surface $T(\beta, \gamma) = S(\beta, \gamma) + h(u(\beta, \gamma),v(\beta, \gamma))n(\beta, \gamma)$. We want to find the normal of $T$:

$$m = \partial\_\beta T \times \partial\_\gamma T$$
$$\partial\_\beta T = \partial\_\beta S+(\partial\_\beta h)n + h(\partial\_\beta n), \quad \partial\_\gamma T = \partial\_\gamma S+(\partial\_\gamma h)n + h(\partial\_\gamma n)$$
$$ \partial\_\beta T \approx \partial\_\beta S+(\partial\_\beta h)n, \quad \partial\_\gamma T \approx \partial\_\gamma S+(\partial\_\gamma h)n$$
$$m\approx (\partial\_\beta S+(\partial\_\beta h)n)\times(\partial\_\gamma S+(\partial\_\gamma h)n) = $$
$$\require{cancel}\partial\_\beta S\times\partial\_\gamma S + (\partial\_\beta h)(n\times\partial\_\gamma S) + (\partial\_\gamma h)(\partial\_\beta S\times n) + \cancel{(\partial\_\beta h)(\partial\_\gamma h)(n\times n)}$$

The above expression gives you an approximation of the (not necessarily unit length) normal of the displaced surface $T$. This is Blinn's approximation - it assumes that $h$ is small enough for $h(\partial\_\beta n)$ and $h(\partial\_\gamma n)$ to be ignored.

Let us consider how this can be applied in practice. There you usually have a triangle mesh. Then if we know how to modify the normal for every triangle, we know how to modify it for the whole mesh. Consider a specific triangle with vertices $p\_0, p\_1, p\_2 \in \mathbb{R}^3$. We can parametrize the triangle through barycentric coordinates $\alpha, \beta, \gamma \geq 0, \alpha + \beta + \gamma = 1$:

$$S(\beta, \gamma) = \alpha p\_0 + \beta p\_1 + \gamma p\_2 = \\
(1-\beta-\gamma) p\_0 + \beta p\_1 + \gamma p\_2 = p\_0 + \beta (p\_1-p\_0) + \gamma (p\_2-p\_0).$$

We can now compute the partial derivatives of $S$:

$$\partial\_{\beta} S = p\_1 - p\_0, \, \partial\_{\gamma} S = p\_2 - p\_0,$$

which turn out to be the edges of the triangle. Then the geometric normal is:

$$n\_g = \partial\_{\beta}S \times \partial\_\gamma S, \, \hat{n}\_g = \frac{n\_g}{\|n\_g\|}.$$

We also need $\partial\_{\beta} h, \, \partial\_\gamma h$ in order to be able to evaluate the perturbed normal. Let the texture coordinates at the 3 triangle vertices be $uv\_0, uv\_1, uv\_2 \in \mathbb{R}^2$, then the reparametrization from barycentric coordinates to UV coordinates is given by:

$$uv(\beta, \gamma) = uv\_0 + \beta (uv\_1-uv\_0) + \gamma (uv\_2-uv\_0).$$

We can now compute the partial derivatives:

$$\partial\_\beta h(uv(\beta, \gamma)) = (uv\_1-uv\_0) \cdot (\partial\_u h(uv(\beta, \gamma)),\partial\_v h(uv(\beta, \gamma)))$$
$$\partial\_\gamma h(uv(\beta, \gamma)) = (uv\_2-uv\_0) \cdot (\partial\_u h(uv(\beta, \gamma)),\partial\_v h(uv(\beta, \gamma)))$$

The finally the perturbed normal is:

$$m\_g = \partial\_{\beta}S\times\partial\_{\gamma}S + (\partial\_{\beta} h)\hat{n}\_g\times\partial\_{\gamma}S + (\partial\_{\gamma}h)\partial\_{\beta}S\times \hat{n}\_g.$$

To compute $\partial\_u h, \, \partial\_v h$ you can use finite differences:

$$\partial\_u h(u,v) \approx \frac{h(u+\epsilon,v) - h(u,v)}{\epsilon}, \, \partial\_v h(u,v) \approx \frac{h(u, v+\epsilon) - h(u,v)}{\epsilon}$$

Here is some code to clarify how this would be implemented:

```
// Assume the following is given for a triangle
vec3 p0, p1, p2;    // positions of the 3 vertices
vec2 uv0, uv1, uv2; // texture coordinates at the 3 vertices
// epsilon for the finite differences
float eps;
// we want to compute the normal at bary coords (beta, gamma)
float beta, gamma;  // barycentric coordinates

// compute edges
vec3 e1 = p1-p0;
vec3 e2 = p2-p0;

// compute geometric normal
vec3 n_g = cross(e1,e2);
vec3 n_g_n = normalize(n_g);

// compute n x (d_gamma S)
vec3 t_g = -cross(n_g_n, e2);

// compute (d_beta S) x n
vec3 b_g = -cross(e1, n_g_n);

// compute partial derivatives of h
vec2 uv = uv0 + beta * (uv1-uv0) + gamma * (uv2-uv0);
vec2 h_uv = 1/eps * (vec2(h(uv+vec2(eps,0)), h(uv+vec2(0,eps))) - vec2(h(uv)));
float h_beta = dot(uv1-uv0, h_uv);
float h_gamma = dot(uv2-uv0, h_uv);

// compute coefficients
float coef_n = 1.0;
float coef_t = -h_beta;
float coef_b = -h_gamma;

// compute new geometric normal
vec3 m_g = coef_n * n_g + coef_t * t_g + coef_b * b_g;

```

Note that this matches how normal mapping is sometimes defined, with a sampled normal $(c\_n, c\_t, c\_b) = (1, -\partial\_\beta h, -\partial\_\gamma h)$:

$$m\_g = c\_n (\partial\_\beta S \times \partial\_\gamma S) + c\_t (\partial\_\gamma S\times\hat{n}\_g) + c\_b (\hat{n}\_g\times \partial\_\beta S).$$

You may notice that the cross product arguments have been switched around in the last two term. This is because of how tangent space is defined when doing normal mapping. You will see I have done the same in the code (the minuses involved in $t\_g, b\_g$). The above doesn't take into account shading normals. When considering those, various approaches have been proposed. I can suggest looking into [Simulation of Wrinkled Surfaces Revisited by Mikkelsen](http://image.diku.dk/projects/media/morten.mikkelsen.08.pdf) for more details on that.

> **Comments:**
> **username_0**: Admittedly a LOT of the math here is going over my head so I am having trouble understanding it. I recall seeing a few of these equations on a site that explained bump maps : https://pbr-book.org/3ed-2018/Materials/Bump_Mapping I did have trouble understanding them though.   Is there a way to understand this without heavy math notations?  
> **username_3**: @username_0 The math is saying: deform every point of your original surface $S$ by moving along its normal by the corresponding value in the heightmap - this yields a displacement surface $T$. You want to find the normal of this displacement surface $T$, so that you can use this normal instead of the actual one. The key statement that I use here is that the normal of a surface is given by the cross product of its partial derivatives (e.g. for a triangle it's the cross product of its edges). For partial derivatives see: https://tutorial.math.lamar.edu/classes/calciii/PartialDerivsIntro.aspx  
> **username_0**: Yes, I got that bit, but how do we obtain the partial derivatives exactly? To crossproduct, each of the 2 vectors involved in the crossproduct would have 3 components (x,y,z). How would we get these individual components?  
> **username_3**: @username_0 Try the last formula for $m$ in the edit. I am not sure whether this is exactly what they do in practice though. You can look at some implementation to figure out what hack different people use to take into account shading normals.  
> **username_0**: I find a lot of these math terms rather difficult to parse and understand (mostly due to my lack of familiarity with them). I'll try my level best to read up on the underlying math topics soon, and come back to this in the future to understanding and ask any further doubts  
> **username_3**: @username_0 I updated the answer with some code since I am not sure which part of the math you do not understand. I also removed the ad-hoc solution, since there are better solutions for shading normals.  
> **username_0**: to be more specific, I have not faced barycentric coordinates before (and what little I saw confused me) (along with the very idea of a surface given by a parametrized equation and partial derivatvives, of which I have only learnt a small bit very recently when researching my question earlier).   I do understand that attempting to understand this problem requires me to be fresh with at least the idea of barycentric coordinates, so I will try to read up on these concepts and return to this answer  
> **username_3**: @username_0 Are you in a rasterization setting or a ray-tracing one? For ray-tracing you can get the barycentric coordinates directly from you ray-triangle intersection routine (they naturally pop up in the solution). In a rasterization setting you don't typically even need those, as the values you're interested in get interpolated while rasterizing. Also as you can see from the code, there are no partial derivatives that remain except the ones for h, which I approximate through finite differences. The remaining partial derivatives are just $p1−p0$ and $p2−p0$.  
> **username_0**: Rasterization. It is what I was planning to learn and delve into  
> **username_3**: @username_0 Here's how you can compute the tangent and bitangent: https://learnopengl.com/Advanced-Lighting/Normal-Mapping https://github.com/ssloy/tinyrenderer/wiki/Lesson-6bis:-tangent-space-normal-mapping#let-us-compute-darboux-basis-and-apply-the-perturbation-of-normals After that plug in the $(-\partial_\beta h, -\partial_\gamma h, 1)$.  

---

