23

My approach is to calculate two tangent vectors parallel to axis X and Y respectively. Then calculate the cross product to find the normal vector.

The tangent vector is given by the line that crosses the middle point on the two nearest segments as is shown in the following picture.

enter image description here

I was wondering whether there is a more direct calculation, or less expensive in terms of CPU cycles.

rraallvv
  • 2,875
  • 6
  • 30
  • 67

1 Answers1

72

You can actually calculate it without a cross product, by using the "finite difference method" (or at least I think it is called in this way).

Actually it is fast enough that I use it to calculate the normals on the fly in a vertex shader.

  // # P.xy store the position for which we want to calculate the normals
  // # height() here is a function that return the height at a point in the terrain

  // read neightbor heights using an arbitrary small offset
  vec3 off = vec3(1.0, 1.0, 0.0);
  float hL = height(P.xy - off.xz);
  float hR = height(P.xy + off.xz);
  float hD = height(P.xy - off.zy);
  float hU = height(P.xy + off.zy);

  // deduce terrain normal
  N.x = hL - hR;
  N.y = hD - hU;
  N.z = 2.0;
  N = normalize(N);
Gigi
  • 4,953
  • 24
  • 25
  • 1
    Too bad I can't give you more than +1: I doubled my FPS by using your simple algorithm! – Zac Jul 05 '13 at 13:56
  • Very nice, works perfectly for my pre calculations on the CPU – Paul Spain Dec 08 '16 at 00:20
  • Very nice! Can you extend your solution to 3D textures? – qantik Apr 11 '17 at 20:56
  • 2
    Is there somewhere a mathematical explaination for this? – j00hi Feb 02 '18 at 18:53
  • That works flawlessly! Just a note: numbers in the "off" vector needs to be tweaked for your specific case (I had to use a slightly bigger offset to make it work in my case) – Michele May 16 '18 at 14:06
  • not entirely sure why, but I had to use N.z =~ .06. Also, for unity users, z and y are flipped. Other than that it produces pretty decent results :) – Bas Smit Jul 09 '19 at 14:15
  • 2
    A more expensive, but slightly more accurate version here https://stackoverflow.com/a/21660173/175592 – Bas Smit Jul 09 '19 at 14:44
  • 2
    ok that makes sense, N.z has to be 2 times your offset, so in my case I use .03 as offset giving N.z = .06 – Bas Smit Jul 10 '19 at 09:09
  • 3
    Consider your surface: z = h(x, y), where h is the height map. So, z - h(x,y) = 0 This is an equation is a contour at g=0 for the following function: g(x,y,z) = z - f(x,y) The gradient of g points in the direction of max increase. It is also orthogonal to the contour plane. So it is the normal. To find the discrete gradient, center at (x,y), and take the difference of its neighbors. grad_x = f(x+1,y) - f(x-1,y) / 2 grad_y = f(x,y+1) - f(x,y-1) / 2 grad_z = (z+1) - (z-1) / 2 = 2/2 = 1 The whole thing can be scaled without changing direction so multiply by 2. Then normalize. Tada! – Kookei Jan 20 '21 at 08:43
  • Elegant solution, just tested it. Works like a charm. – Siniša Jun 06 '21 at 13:47