point PP;
float F = /* some bump function goes here */
PP = P + F * normalize(N);
Nf = calculatenormal(PP);
This code snippet computes a new position PP by displacing it in the direction of the normal by a certain amount F. The value of F represents the bump height and is determined by some mathematical function.
Implementing this function on programmable graphics hardware is tricky, since each vertex and fragment is processed independently, i.e. we do not have neighbor / connectivity information. However, we can make use of the screen-space partial derivative operators (DDX, DDY) in NVIDIA NV30 fragment programs. We first express dP/du and dP/dv in terms of screen-space x and y. Given this system of equations,
dP/dx = dP/du * du/dx + dP/dv * dv/dx
dP/dy = dP/du * du/dy + dP/dv * dv/dy
we solve for dP/du and dP/dv and obtain
dP/du = (dP/dx * dv/dy - dP/dy * dv/dx) / k
dP/dv = (dP/dy * du/dx - dP/dx * du/dy) / k
where k = du/dx * dv/dy - dv/dx * du/dy. We have expressed dP/du and dP/dv in terms of partial derivatives with respect to screen-space x and y. Each of these partial derivatives can be evaluated using the NV30 DDX or DDY fragment program instructions.
Finally, we take the cross product of dP/du and dP/dv and normalize the result. Note that while the value k above is needed to obtain correct values of dP/du and dP/dv, it is not needed to compute the normal!
The first problem arises because of inaccurate partial derivative estimation. To obtain an accurate normal, we require accurate estimates of dP/du and dP/dv. This assumes that du and dv are small (infinitesimally small). To compute dP/du and dP/dv, we must first compute the partials of u and v with respect to x and y. However, a small change in x and y (pixel-size) may result in a large change in u and/or v. This leads to an inaccurate estimation of the partial derivatives of P with respect to u and/or v, which results in a "noisy" normal with popping artifacts.
The second problem is that we can compute only face normals. In other words, the resulting normals from our implementation are not smoothly interpolated across the primitive. The reason is that we are computing the normals by taking the cross product of two tangent vectors. These tangent vectors are not the true surface tangent vectors, but instead are obtained indirectly from P, u and v linearly interpolated across the face of a triangle. Hence dP/du and dP/dv lie in the plane of the triangle, so all normals computed for all points in a given triangle will be the same. Thus the result is a face normal.
Note that the second problem is a non-issue for the REYES architecture (used in Pixar's prman) because surfaces are finely diced until each micropolygon is less than a pixel on a side, so normals are effectively recalculated on a per-pixel basis, which leads to a smoother appearance.
The normals for the spheres in the top row are computed using our calculatenormal() implementation. Notice that we can compute only face normals, and the overall appearance becomes smoother only if we dice the sphere more finely. In contrast, the normals for the spheres in the bottom row are obtained from the true vertex surface normals by linear interpolation during triangle rasterization.
Dicing: 1 | Dicing: 5 | Dicing: 10 |