Comparison of Two Textures: heightmap and normal map data

If you are a new Irrlicht Engine user, and have a newbie-question, this is the forum for you. You may also post general programming questions here.
dlangdev
Posts: 1324
Joined: Tue Aug 07, 2007 7:28 pm
Location: Beaverton OR
Contact:

Comparison of Two Textures: heightmap and normal map data

Post by dlangdev »

Hi,

Just wondering if the following code make sense when setting up textures for parallax view.

The first method used when a colormap and height map texture is available.

colormap: rockwall.bmp
Image

heightmap: rockwall_height.bmp
Image

code:

Code: Select all

video::ITexture* colorMap = driver->getTexture("../../media/rockwall.bmp");
video::ITexture* normalMap = driver->getTexture("../../media/rockwall_height.bmp");
		
driver->makeNormalMapTexture(normalMap, 9.0f);
scene::IMesh* tangentMesh = smgr->getMeshManipulator()->createMeshWithTangents(
			roomMesh->getMesh(0));
			
room = smgr->addMeshSceneNode(tangentMesh);
room->setMaterialTexture(0,	colorMap);
room->setMaterialTexture(1,	normalMap);
room->getMaterial(0).SpecularColor.set(0,0,0,0);
room->setMaterialFlag(video::EMF_FOG_ENABLE, true);
room->setMaterialType(video::EMT_PARALLAX_MAP_SOLID); 
room->getMaterial(0).MaterialTypeParam = 0.02f; // adjust height for parallax effect
// drop mesh because we created it with a create.. call.
tangentMesh->drop();
here's the output...

parallax:
Image

bump:
Image

The second method uses two textures: a colormap and a normal map.

colormap: rockwall.bmp
Image

normalmap: rockwall_nomalMapped.bmp
Image

Code: Select all

video::ITexture* colorMap = driver->getTexture("../../media/rockwall.bmp");
video::ITexture* normalMap = driver->getTexture("../../media/rockwall_nomalMapped.bmp");

scene::IMesh* tangentMesh = smgr->getMeshManipulator()->createMeshWithTangents(
			roomMesh->getMesh(0));
			
room = smgr->addMeshSceneNode(tangentMesh);
room->setMaterialTexture(0,	colorMap);
room->setMaterialTexture(1,	normalMap);
room->getMaterial(0).SpecularColor.set(0,0,0,0);
room->setMaterialFlag(video::EMF_FOG_ENABLE, true);
room->setMaterialType(video::EMT_PARALLAX_MAP_SOLID); 
room->getMaterial(0).MaterialTypeParam = 0.02f; // adjust height for parallax effect
// drop mesh because we created it with a create.. call.
tangentMesh->drop();
The code I skipped was this one...

Code: Select all

driver->makeNormalMapTexture(normalMap, 9.0f);
since the normal map is already loaded, there's no need to generate it.

And here's the output...

Image

What do you think? Does that make sense?

Thanks.
Image
arras
Posts: 1622
Joined: Mon Apr 05, 2004 8:35 am
Location: Slovakia
Contact:

Post by arras »

I would say yes it do. Difference is in quality of your bump map and normal map. From that bump map you probably not gona create normal map of similar quality as the one you have.
dlangdev
Posts: 1324
Joined: Tue Aug 07, 2007 7:28 pm
Location: Beaverton OR
Contact:

Post by dlangdev »

Thanks Arras,

One thing I noticed was the second method lost the parallax effect. It's probably because a height map is required to produce the effect.

That would only mean hitting two birds with one stone, I can now implement two methods to cover/solve interesting problems.

Also, it would be nice for Irrlicht to implement a fin at the edges so that the pixels can stretch to a height defined by the heightmap. Notice the edge is flat and adding a fin in there would solve the flat edge effect.
Image
BlindSide
Admin
Posts: 2821
Joined: Thu Dec 08, 2005 9:09 am
Location: NZ!

Post by BlindSide »

If you read the method description for "makeNormalMapTexture" closely, it would say that it encodes the heightmap data inside the (invisible :P ) alpha channel. Therefore, there is some unseen data being lost in just using the normal map directly.

Cheers
ShadowMapping for Irrlicht!: Get it here
Need help? Come on the IRC!: #irrlicht on irc://irc.freenode.net
dlangdev
Posts: 1324
Joined: Tue Aug 07, 2007 7:28 pm
Location: Beaverton OR
Contact:

Post by dlangdev »

Is there a tool for processing a colormap into a heightmap? I can't seem to find one at the moment. What I have at the moment is the Nvidia normalmap tool, though I haven't used all the features in there, it might have a checkbox for setting heightmap data.
Image
dlangdev
Posts: 1324
Joined: Tue Aug 07, 2007 7:28 pm
Location: Beaverton OR
Contact:

Post by dlangdev »

It looks like there's a way of generating height data from a colormap by converting color values to grayscale.

The code in c# shown below:

Code: Select all

public Bitmap ConvertToGrayscale(Bitmap source)

{

  Bitmap bm = new Bitmap(source.Width,source.Height);

  for(int y=0;y<bm.Height;y++)

  {

    for(int x=0;x<bm.Width;x++)

    {

      Color c=source.GetPixel(x,y);

      int luma = (int)(c.R*0.3 + c.G*0.59+ c.B*0.11);

      bm.SetPixel(x,y,Color.FromArgb(luma,luma,luma));

    }

  }

  return bm;

}

Image
Image

Reference: http://www.bobpowell.net/grayscale.htm
Image
Halifax
Posts: 1424
Joined: Sun Apr 29, 2007 10:40 pm
Location: $9D95

Post by Halifax »

I thought that heightmaps were luminance based though, so even though you may convert it to grayscale, the luminance may not be what you are looking for. And without that it may screw up your heightmap shader.
TheQuestion = 2B || !2B
dlangdev
Posts: 1324
Joined: Tue Aug 07, 2007 7:28 pm
Location: Beaverton OR
Contact:

Post by dlangdev »

Halifax wrote:I thought that heightmaps were luminance based though, so even though you may convert it to grayscale, the luminance may not be what you are looking for. And without that it may screw up your heightmap shader.
That is correct, it should take luminance into consideration. It wasn't pasted in the thread, though the matrix formula for luminance was mentioned in the reference.

At the moment, I'm playing around with CNullDriver.h/cpp, I've cloned makeNormalMapTexture() to makeGrayScaleTexture() for setting up the grayscale texture using a colormap as input, the output is a new texture containing grayscale data. Better if it simply passes two textures instead of one, that would make it even simpler.

I'm also looking into adding fractal/perlin textures later, for example makeFractalTexture() or makePerlinTexture(). Then move to code makeColorRampTexture() later.

I have yet to learn A1R5G5B5 and A8R8G8B8, though. I'm definitely a newbie into this. So, don't hold your breath on this one as it might take some time to get this done.
Image
dlangdev
Posts: 1324
Joined: Tue Aug 07, 2007 7:28 pm
Location: Beaverton OR
Contact:

Post by dlangdev »

Hi there,

Please look at the code posted below. It's calculating luminance value using a simple formula.

Not sure exactly if this code will cover it, though.

I'm pasting the partially completed code, please look into it and comment.

The code compiles on my dev machine and I was able to walk through the code in debug mode.

What the code is doing the following:

1) It receives the colormap.
2) Creates the destination texture --> grayscale.
3) Walks the colormap.
4) For each color, calculate height/luminance.
5) Store luminance to alpha.
6) Save color to grayscale map.

Code: Select all

//! Creates a grayscale map from a colormap texture.
video::ITexture* CNullDriver::makeGrayscaleTexture(video::ITexture* texture)
{
	if (!texture)
		return 0;

	if (texture->getColorFormat() != ECF_A1R5G5B5 &&
		texture->getColorFormat() != ECF_A8R8G8B8 )
	{
		os::Printer::log("Error: Unsupported texture color format for making grayscale map.", ELL_ERROR);
		return 0;
	}

	struct colorBitFields {
		unsigned a :8;
		unsigned r :8;
		unsigned b :8;
		unsigned g :8;
	};
	struct colorBitFields aBitFld;
	int luma;
	core::dimension2d<s32> dim = texture->getSize();
	video::ITexture* grayScaleTexture;
	core::stringc tn = texture->getName();
	tn.append("_grayscale");
	grayScaleTexture = addTexture(dim, tn.c_str() ,texture->getColorFormat());
	if (texture->getColorFormat() == ECF_A8R8G8B8)
	{
		// ECF_A8R8G8B8 version
		s32 *p = (s32*)texture->lock();
		s32 *q = (s32*)grayScaleTexture->lock();
		if (!p)
		{
			os::Printer::log("Could not lock source texture for making grayscale map.", ELL_ERROR);
			return 0;
		}
		if (!q)
		{
			os::Printer::log("Could not lock destination texture for making grayscale map.", ELL_ERROR);
			return 0;
		}
		s32 pitch = texture->getPitch() / 4;
		s32* in = new s32[dim.Height * pitch];
		memcpy(in, p, dim.Height * pitch * 4);
		for (s32 x=0; x<pitch; ++x)
		{
			for (s32 y=0; y<dim.Height; ++y)
			{
				int nsiz = sizeof(p[y*pitch + x]);
				//if ( nsiz == 4 ) {
					aBitFld.r = p[y*pitch + x] & 0xFF000000;
					aBitFld.g = p[y*pitch + x] & 0x00FF0000;
					aBitFld.b = p[y*pitch + x] & 0x0000FF00;
					aBitFld.a = p[y*pitch + x] & 0x000000FF;
				//}
				luma = (int)(aBitFld.r*0.3 + aBitFld.g*0.59+ aBitFld.b*0.11); 
				aBitFld.a = luma;

				//method 1: return unmodded rgb color
				q[y*pitch + x] = video::SColor(
					(s32)aBitFld.a, // store height in alpha
					(s32)aBitFld.r, (s32)aBitFld.g, (s32)aBitFld.b).color;

				//method 2: return modded rgb color
				q[y*pitch + x] = video::SColor(
					(s32)aBitFld.a, // store height in alpha
					(s32)aBitFld.a, (s32)aBitFld.a, (s32)aBitFld.a).color;

			}
		}
		delete [] in;
		texture->unlock();
		grayScaleTexture->unlock();

	}

// more code here taken out for brevity's sake.

	grayScaleTexture->regenerateMipMapLevels();

	return grayScaleTexture;

Image
dlangdev
Posts: 1324
Joined: Tue Aug 07, 2007 7:28 pm
Location: Beaverton OR
Contact:

Post by dlangdev »

Here are the results...

Using the code posted above, it was able to generate grayscale data using only the color data by translating colorscale to grayscale. The image shown below.

With the help of the texture-to-image code, I was able to dump texture data to file. See http://irrlicht.sourceforge.net/phpBB2/ ... magetofile for more details.

The original image...
Image

The grayscale image....

Code: Select all

grayScaleMap = driver->makeGrayscaleTexture(colorMap);
Image

Then the grayscale data passed through makeNormalMapTexture() and here is the result...

Image

What's so cool about this code is it provides simpler texture workflow. With this feature, you can easily make bump maps by simply using your original images, the code will convert them on-the-fly.

That's probably it for this one, though. I hope someone picks this up and runs with it.

I'll post the cleaned-up code later. But you know well you can easily figure the code out from here.
Image
dlangdev
Posts: 1324
Joined: Tue Aug 07, 2007 7:28 pm
Location: Beaverton OR
Contact:

Post by dlangdev »

Hmm, I just noticed there were differences in how the two normal maps were generated.

Here's the nvidia version.

Image

And the version generated by the code posted above...

Image

Anyway, that's not really a big deal, I'm just pointing out the difference between two tools.
Image
Halifax
Posts: 1424
Joined: Sun Apr 29, 2007 10:40 pm
Location: $9D95

Post by Halifax »

Your values seems a little scattered and jittered unlike the nVidia version. It would be cool to see the two normals maps applied to a model in real-time for comparision.
TheQuestion = 2B || !2B
dlangdev
Posts: 1324
Joined: Tue Aug 07, 2007 7:28 pm
Location: Beaverton OR
Contact:

Post by dlangdev »

Halifax wrote:Your values seems a little scattered and jittered unlike the nVidia version.
Yeah, I played around with the values by passing it through one to several filters. The image above came from a one-pass filter, I noticed it only after finishing the testing.
Halifax wrote: It would be cool to see the two normals maps applied to a model in real-time for comparision.
I'll post another set of images later as I'm still cleaning up the code. So far, the code pretty much fills my need for simpler bumpmapping workflow.

To answer that question, the on-the-fly bumpmap looks like the one shown below. Depending on what filters are applied.

Image

So far, the render looks pretty good, though not as good as the heightmap texture found in the demo. I think the heightmap texture from the demo was manually meticulously prepared. I don't have time to work on those kinds of stuff. I'd rather push them to a function and let the code handle it.

By the way, I got the idea from Maya version 2008. It has tons of cool features that covers most of the tedious boring hard work, though.

In due time, they'll eventually get coded/ported over to Irrlicht.
Image
Halifax
Posts: 1424
Joined: Sun Apr 29, 2007 10:40 pm
Location: $9D95

Post by Halifax »

Yeah, your normal map looks pretty good. I'm not sure if this is possible, but if it is, then I would say you might benefit from "scaling" the normal map by 2 or something when it is applied.
TheQuestion = 2B || !2B
dlangdev
Posts: 1324
Joined: Tue Aug 07, 2007 7:28 pm
Location: Beaverton OR
Contact:

Post by dlangdev »

Yup, I did play around with the filters, and was able to get different results.

grayscale:
Image

normal:
Image

output:
Image

Overall, I'm very happy with the results I got, it took a lot of testing time, but the results seeing how they rendered was really worth all the effort.

All that was rendered using the sample demo per pixel lighting code. I took out the heightmap and replaced that with a make gray scale function.

Looks like I won't be needing the Nvidia normal mapping tool any longer.

Huzzzah!
Image
Post Reply