So I'm trying to generate a normal map from a greyscale height map. Unfortunately the result I'm getting doesn't seem quite right.
Here is the height map I'm using to generate the normal map:
Here is my generated normal map:
For reference, here is the normal map that gets generated from http://cpetry.github.io/NormalMap-Online/:
One thing I notice in my normal map is that there seems to be edges. I'm not really sure why I am getting these edges - does anyone know why?
Here's the code that I'm using to generate the normal map:
// Source:
// https://stackoverflow.com/questions/2368728/can-normal-maps-be-generated-from-a-texture
// and
// http://www.catalinzima.com/2008/01/converting-displacement-maps-into-normal-maps/
float32 height(const std::vector<char>& data, const int width, const int height, uint32 x, uint32 z)
{
if (x >= width) x %= width;
while (x < 0) x += width;
if (z >= height) z %= height;
while (z < 0) z += height;
return static_cast<float32>(data[z*width*4 + x*4 + 3]) / 255.0f;
}
uint8 textureCoordinateToRgb(const float32 value)
{
return (value + 1.0) * (255.0 / 2.0);
}
glm::vec3 calculateNormal(const std::vector<char>& data, const int width, const int height, uint32 x, uint32 z)
{
const float32 strength = 8.0f;
// surrounding pixels
float32 tl = height(data, width, height, x-1, z-1); // top left
float32 l = height(data, width, height, x-1, z); // left
float32 bl = height(data, width, height, x-1, z+1); // bottom left
float32 t = height(data, width, height, x, z-1); // top
float32 b = height(data, width, height, x, z+1); // bottom
float32 tr = height(data, width, height, x+1, z-1); // top right
float32 r = height(data, width, height, x+1, z); // right
float32 br = height(data, width, height, x+1, z+1); // bottom right
// sobel filter
const float32 dX = (tr + 2.0 * r + br) - (tl + 2.0 * l + bl);
const float32 dY = (bl + 2.0 * b + br) - (tl + 2.0 * t + tr);
const float32 dZ = 1.0 / strength;
glm::vec3 n(dX, dY, dZ);
n = glm::normalize(n);
return n;
}
void calculateNormals(std::vector<char>& data, const int width, const int height)
{
for (int i = 0; i < width; ++i)
{
for (int j = 0; j < height; ++j)
{
const glm::vec3 n = calculateNormal(data, width, height, i, j);
// convert to rgb
data[j*width*4 + i*4 + 0] = textureCoordinateToRgb(n.x);
data[j*width*4 + i*4 + 1] = textureCoordinateToRgb(n.y);
data[j*width*4 + i*4 + 2] = textureCoordinateToRgb(n.z);
}
}
}
std::unique_ptr<Image> generateFormattedHeightmap(const Image& image)
{
std::vector<char> data;
int width = image.width();
int height = image.height();
data.resize(width*height*4);
if (image.format() == Image::Format::FORMAT_RGB)
{
int j=0;
for (int i=0; i < data.size(); i+=4)
{
data[i] = image.data()[j];
data[i+1] = image.data()[j+1];
data[i+2] = image.data()[j+2];
data[i+3] = (image.data()[j] + image.data()[j+1] + image.data()[j+2]) / 3;
j+=3;
}
}
else if (image.format() == Image::Format::FORMAT_RGBA)
{
for (int i=0; i < image.data().size(); i+=4)
{
data[i] = image.data()[i];
data[i+1] = image.data()[i+1];
data[i+2] = image.data()[i+2];
data[i+3] = (image.data()[i] + image.data()[i+1] + image.data()[i+2]) / 3;
}
}
calculateNormals(data, width, height);
return std::make_unique<Image>(data, width, height, Image::Format::FORMAT_RGBA);
}
Does anyone have any ideas?
float32 height(const std::vector<char>& data, const int width, const int height, uint32 x, uint32 z)
function - casting thechar
to afloat32
would cause the value to be negative. \$\endgroup\$