Computing Normals for a displaced material

Disclosure: [cross-posted on GameDev.SE][1], but I am confused about the ddx/ddy nodes so applicable here

I am currently rendering a displacement texture and, separately, a normal texture for my ocean. Is there a clever way where I can actually just compute the normal in the final material. I.E. the un-displaced normal is (0, 0, 1) then by using perhaps [ddx/ddy][2] work out the real normal? It seems like [this question is trying a similar thing][3]. I don’t really understand the ddx/ddy nodes/functions but they seem at least related.

Using the ddx, ddy nodes what I tried was computing for each given point:

dx = ddx(WorldPosition.x)
dy = ddy(WorldPosition.y)
dz/dx = ddx(height) / dx
dz/dy = ddy(height) / dy

Then I used these values to rotate my base normal of (0, 0, 1) to the final normal. It kinda looks like I’m on the right track but I not 100% calculation at a couple of points. Further, it isn’t quite right as the normal appears to change as I move the camera around (using the world normal buffer visualisation).

Calculation:

let Tan(theta) = dz / dx
let Tan(phi) = dz / dy
then plugging these values into the rotation matrix R_theta, R_phi:
R_theta = ( 1      0          0     )
          ( 0 cos(theta) -sin(theta))
          ( 0 sin(theta)  cos(theta))

R_phi = ( cos(phi)   0       sin(phi))
        (     0      1           0   )
        (-sin(phi)   0       cos(phi))

I compute theta and phi by applying atan to dz/dx and dz/dy respectively.

finally multiplying R_thea * R_phi * n to get:
    sin(phi)
    -sin(theta)cos(phi)
    cos(theta)cos(phi)

Is what I am trying to do possible and what am I doing wrong? Specifically:

  • Am I using the ddx, ddy nodes correctly to compute the change in height over x and y
  • How come the normal appears to change as I move the camera
  • Does the fact there is horizontal displacement in my ocean matter

[1]:
[2]: #ddx
[3]:

As usual was some what over-thinking the problem. I don’t need to use the screen space normals (which is what I would have got with the ddx/ddy nodes) and can instead just compute a pair of tangents and cross them for the world space normal.

To do this I did the following:

For each point, using a small margin in UV space units (e.g if you have a 512x512 texture for the displacement, 1/512 is approx one pixel above).

TopLeft = Texture[uv + (-small, -small)]
TopRight = Texture[uv + (small, -small)] + (2.0 * small * world unit converter, 0, 0)
BottomLeft = Texture[uv + (-small, small)] + (0, 2.0 * small * world unit converter, 0)

v1 = TopRight - TopLeft
v2 = BottomLeft - TopLeft

n = cross(v1, v2)

I.E. the top right point is the world space position after going along the small distance and then transforming by the displacement stored in the texture. This then gives you two roughly tangential vectors, so crossing is roughly the normal.