Sunday, February 22, 2015

Terrain heightmap Bump (Normal) mapping in Ogre


I've looked through a series of web sites on this topic, any number of these ranging in various degrees of technical information, but I've had at times a difficult times retrieving actual code application (outside of glsl open source information on any number of bump mapping techniques).

I did find one particular code example which basically used a forward difference method for approximating first partial derivatives, albeit the code was slightly off I think I managed to correct this, however, and append this in my revision, and secondly, normal mapping which does appear to have some slight technical differences need not require shading illumination in producing a normal map as opposed to the bump map which appears to integrate a given shade computation.

The normal map quite simply is, at least, the view of Ogre normal/heightmap combinations for a given terrain is just that which is an array of surface normals over the terrain's surface, and thus producing a normal map is actually technically a little less expensive computationally speaking...Ogre takes the surface normals and then renders the bump shading from this, if I am not mistaken.  The format that I've found that works is given literally from computational image processing articles (Nvidia being one source site), and is simply given by the surface Gradient vector which is of the form (-partial H(x,y)/partial x, -partial H(x,y)/partial y, 1.0)
where H(x,y) is the partial of the Height map function or using forward difference method.
Is partial H(x,y) / partial x = H(x+1, y)-H(x,y))  and partial H(x,y)/partial y = H(x,y+1) - H(x,y).

There is a scaling factor a that can be added into the gradient which effectively increases or decreases the strength of the Normal maps (outside of the main blue predominant axis) which then modifies the Gradient as
(-a* (H(x+1, y)-H(x,y)) , -a * (H(x,y+1) - H(x,y)), 1.0)

Thus an increase to a > 1 produces greater contributions to the shadow prominence in the rendered bump map shading and this can be used as the normal intensity factorization for Normal mapping.

I've also re scaled my height map values on the difference equations simply dividing the difference values by the total heightmap difference

diff = terrainMaxHeight() - terrainMinHeight on the terrain.

Thus the Gradient (surface normal) vector might look like:

(-a/diff * (H(x+1, y)-H(x,y)) , -a/diff * (H(x,y+1) - H(x,y)), 1.0)

In this way, all possible values to be amplified by the scaling factor a are not scaled with any pre existing value > 1.  You'd want to normalize the gradient vector (compute the normal of the Gradient vector), and then scale to rgb values
Gradvec/2 +.5  .  On my mapping to tweak the sensitivity in the normal mapping routine, I've actually decreased the value of the Grad.z from 1 to some lesser value > 0 which can work since the scaling factor a means that normalization will diminish the value of Grad.z if Grad.x and/or Grad.y are greater than 1.  This in effect leads to ,prior to normalization, a value Grad.z which tends to zero with increasing a>1 post normalization of the Gradient, but with re scaling Grad.z will always be a minimum of .5 regardless, hence the blue field is never completely absent if Grad.z prior to normalization >= 0.
In my case I've chose parameters of Grad.z around .1 and a =1 to around a = 1.5 although you could increase this factor where normals become extremely high.  

Thus the final surface normal vector  prepared for RGB output should look like:

Norm((-a/diff * (H(x+1, y)-H(x,y)) , -a/diff * (H(x,y+1) - H(x,y)), 1.0)) / 2 + .5

where diff = terrainMaxHeight() - terrainMinHeight, and a is the normal intensity factor (user supplied), H(x,y) is the Heightmap value at the given x,y coordinate position.


Ogre's terrain manager is also nice in another respect since the interpolators are stored alongside heightmap data  so that one can at any scale choose points on a given map without having to use the more accurate but expensive procedural method itself in picking a scale value for producing higher resolution maps.  Thus from a 513x513 pixel heightmap I can actually render 1026x1026 and higher maps by using the function getHeightAtTerrainPosition () and supplying a position from (0,1) in both the x and y direction.  Thus to produce a 1026x1026 map for a doubly iterated set of x and y positions I just need take the iterator value and divide by 1026  for either x and y position on both iterator values.  This is also better since recalling a value directly from an fBm method also recalls a heightmap position that is not actually rendered if, for instance, a lower resolution height map instance of such terrain is being used.

Here is an example surface normal map that I constructed alongside original texture.  The normal map is technically set for Ogre specifications being a Normal RGB map coupled with alpha heightmap set (although you can't see the transparency prominence here),




For procedural textures (that are technically not stored as terrain coordinate) data, I haven't researched higher resolution mapping methods, although arguably this probably not needed if one were using much smaller scaling given a tiling series.  If you were interested in this likely you'd want to build interpolating functions for your height map or procedural f(x,y) texturing output, store these coefficients in a given map for fast look ups applying this into your favorite interpolating equation method.  Anyways given the computational expense at doing this you'd avoid the more lengthy and long winded computations found through the higher resolution re scaling method on such texture topology.

By the way my terrain texture above is 3078 x 3078, produced from originally 513x513 heightmap data that were of likely some higher order interpolating method (quadratic or higher)...
see also Bicubic re sampling.  You can also find a c++ implementation of bicubic resampling at
http://www.paulinternet.nl/?page=bicubic.  Thus to set up bicubic interpolation, first you'd simply set up the parray (in the link's algorithm) where any two grid points (on the original two dimension set of point for a given height map) have a 4 tuple/vector coefficient set as follows:
(p0, p0', p1, p1') where p0 = H(x0,y0) and p1 = H(x1,y1), for instance, and p0' and p1' are the first derivatives at such point along the given axis (directional derivatives)...the method above however also computes these directional derivatives that can in turn be supplied as p0' and p1' coefficients (you just need to track and store these on your normal mapping routine).  The link provides further math information if you want details on how this stuff works.

If you wondering about formulation of the surface normal, this is derived from the cross product of the partial derivative vectors of the height map function with respect to x and y.  Technically this is shown with a negative x component, positive y component and positive z component.

\( \partial H / \partial x \) direction vector \(= (1,0,\partial H / \partial x) \)
\( \partial H / \partial y \) direction vector \(= (0,1,\partial H / \partial y) \)

No comments:

Post a Comment

Oblivion

 Between the fascination of an upcoming pandemic ridden college football season, Taylor Swift, and Kim Kardashian, wildfires, crazier weathe...