You can generate each the normal map, pixel by pixel, by computing the respective the X and Y gradients from the height map using a Sobel operator.
Sampling the heightmap with a clamping mode produces an output like such:
Or with a wrapping mode, produces what was shown in your comment:
(Including the errors around the border)
And for good measure, here's the tool I wrote to generate those images. Using SDL and plain C, I called it "hbm2tnm.c" (Height Bump Map to Tangent Normal Map)
#include <stdio.h>
#include <math.h>
#include <SDL/SDL.h>
#ifdef NO_SDL_IMAGE
#define IMG_Load SDL_LoadBMP
#else
#include <SDL/SDL_image.h>
#endif
double extrusion = 2.0;
SDL_Surface* load32(const char* fn) {
SDL_Surface* p_textureSurface = IMG_Load(fn);
if (!p_textureSurface) return 0;
SDL_Surface* p_textureBGRA = SDL_CreateRGBSurface(SDL_SWSURFACE, p_textureSurface->w, p_textureSurface->h, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
SDL_SetAlpha(p_textureSurface, 0, 0);
SDL_SetAlpha(p_textureBGRA, 0, 0);
SDL_BlitSurface(p_textureSurface, 0, p_textureBGRA, 0);
SDL_FreeSurface(p_textureSurface);
return p_textureBGRA;
}
double getPixel(SDL_Surface* surf, int wrap, int x, int y) {
unsigned char* in32 = (unsigned char*)surf->pixels;
if (x < 0) x = wrap ? (x + surf->w) : 0;
if (y < 0) y = wrap ? (y + surf->h) : 0;
if (x >= surf->w) x = wrap ? (x - surf->w) : (surf->w - 1);
if (y >= surf->h) y = wrap ? (y - surf->h) : (surf->h - 1);
int idx = x + y * surf->w;
return (in32[idx * 4 + 0] + in32[idx * 4 + 1] + in32[idx * 4 + 2]) / (256.0 * 3.0);
}
void ComputeGradientMap(SDL_Surface* nmap, SDL_Surface* hmap, int wrap) {
int x,y;
unsigned char* out32 = (unsigned char*)nmap->pixels;
for(y = 0; y < hmap->h; y++) {
for(x = 0; x < hmap->w; x++) {
double center = getPixel(hmap, wrap, x, y);
double up = getPixel(hmap, wrap, x, y - 1);
double down = getPixel(hmap, wrap, x, y + 1);
double left = getPixel(hmap, wrap, x - 1, y);
double right = getPixel(hmap, wrap, x + 1, y);
double upleft = getPixel(hmap, wrap, x - 1, y - 1);
double upright = getPixel(hmap, wrap, x + 1, y - 1);
double downleft = getPixel(hmap, wrap, x - 1, y + 1);
double downright = getPixel(hmap, wrap, x + 1, y + 1);
double vert = (down - up) * 2.0 + downright + downleft - upright - upleft;
double horiz = (right - left) * 2.0 + upright + downright - upleft - downleft;
double depth = 1.0 / extrusion;
double scale = 127.0 / sqrt(vert*vert + horiz*horiz + depth*depth);
unsigned char r = 128 - horiz * scale;
unsigned char g = 128 + vert * scale;
unsigned char b = 128 + depth * scale;
int idx = x + y * nmap->w;
out32[idx * 4 + 0] = b;
out32[idx * 4 + 1] = g;
out32[idx * 4 + 2] = r;
out32[idx * 4 + 3] = 255;
}
}
}
int main(int argc, const char** argv) {
if (argc <= 1) {
printf("Usage: %s <input image> [clamp output image] [wrap output image] [extrusion = %.1f]\n", argc ? argv[0] : "hbm2tnm", extrusion);
return 1;
}
const char* out_clamp = "normal_clamp.bmp";
const char* out_wrap = "normal_wrap.bmp";
if (argc >= 3) out_clamp = argv[2];
if (argc >= 4) out_wrap = argv[3];
if (argc >= 5) extrusion = atof(argv[4]);
SDL_Surface* hmap = load32(argv[1]);
if (!hmap) {
printf("Failed to load: %s\n", argv[1]);
return 2;
}
SDL_Surface* nmap = SDL_CreateRGBSurface(SDL_SWSURFACE, hmap->w, hmap->h, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
ComputeGradientMap(nmap, hmap, 1);
SDL_SaveBMP(nmap, out_wrap);
ComputeGradientMap(nmap, hmap, 0);
SDL_SaveBMP(nmap, out_clamp);
return 0;
}