Game Development Community

Terrain Blending error fix

by Clark Fagot · 01/21/2003 (12:35 pm) · 36 comments

This little nugget is something that came about due to simple experimentation. The entire time we have been developing ThinkTanks we have tried -- with little success -- to forget that those ugly lines were there. One day I decided to try a few things to get rid of them and stumbled upon this simple solution.

If your terrain has lines that look like this:

www.bravetree.com/GarageGames/blendbugBefore.jpg
But you would like it to look like this:

www.bravetree.com/GarageGames/blendbugAfter.jpg
Then simply add the following function to terrData.cc just before initMMXBlender:

// This routine takes 256x256 bitmap and makes
// sure that each 128 x 128 grid has borders that
// match adjacent 128 x 128 grids.  This fixes a
// terrain blending error/defect.
void tweakTerrainBmp(GBitmap * pBitmap)
{
   AssertFatal(pBitmap->getWidth()==256 &&
               pBitmap->getHeight()==256 &&
               pBitmap->getFormat()==GBitmap::RGB,
               "Doh!  This routine was written for fixed bitmap size, must update.");
   U8 * bits = pBitmap->getWritableBits();
   S32 w = pBitmap->getWidth();
   S32 h = pBitmap->getHeight();

   S32 offset1 = -1;
   S32 offset2 = 0;
   S32 base;
   for (base=0; base<256; base += 128)
   {
      S32 top1 = (base + offset1 + 256) % 256;
      top1 *= 3;
      S32 top2 = (base + offset2 + 256) % 256;
      top2 *= 3;

      // slide down column
      for (S32 i=0; i<256; i++)
      {
         for (S32 j=0; j<3; j++)
         {
            U8 val1 = bits[top1 + i*256*3 + j];
            U8 val2 = bits[top2 + i*256*3 + j];
            U8 val = (val1 >> 1) + (val2 >> 1);
            if (val1 & val2 & 1)
               ++val;
            bits[top1 + i*256*3 + j] = bits[top2 + i*256*3 + j] = val;
         }
      }
   }

   for (base=0; base<256; base += 128)
   {
      S32 left1 = (base + offset1 + 256) % 256;
      left1 *= 256 * 3;
      S32 left2 = (base + offset2 + 256) % 256;
      left2 *= 256 * 3;

      // slide across row
      for (S32 i=0; i<256; i++)
      {
         for (S32 j=0; j<3; j++)
         {
            U8 val1 = bits[left1 + i*3 + j];
            U8 val2 = bits[left2 + i*3 + j];
            U8 val = (val1 >> 1) + (val2 >> 1);
            if (val1 & val2 & 1)
               ++val;
            bits[left1+i*3 + j] = bits[left2 + i*3 + j] = val;
         }
      }
   }
}

Then call it just before the line "pBitmap->extrudeMipLevels();" like so: 
   tweakTerrainBmp(pBitmap);
Page «Previous 1 2
#1
01/21/2003 (1:36 pm)
Smoooooth!
I'll be trying this out as soon as I get home. That ugly terrain line was a bit annoying.
#2
01/21/2003 (1:46 pm)
Ahh, the copy and paste is not friendly for IE. :D

Anyways, I tried it, works great!

Excellent work!
#3
01/21/2003 (2:11 pm)
Clark,
This enhancement worked very well. Thank you for all the work you have done and allowing the community to have access to it.

Bryan
#4
01/21/2003 (2:46 pm)
OMG! Finally!! Those annoying lines are gone!! :))
Thank you SO much Clark for sharing all your improvements with the community!!! :D
#5
01/21/2003 (3:05 pm)
Thanks guys. We liked getting rid of those lines too. Might add, this only touches up the first mip level. One might modify it to work after the bitmap has been extruded and touch up all mip levels in a similar way. Didn't need that for ThinkTanks but you might need it if you end up viewing the terrain from a distance more.
#6
01/21/2003 (3:47 pm)
Very, very nice Clark--as always -->U da man
#7
01/21/2003 (6:07 pm)
Wonderful, Clark - just applied to to Trajectory Zone. We only had one level that seemed to have a problem with it, and even then you had to be standing in just the right place (jagged terrain, so, it's harder to see the texture error.) This cleaned it right up. Thanks!
#8
01/21/2003 (7:17 pm)
@Desmond - hey D. Send me some email. I want to throw something by you. My emails haven't been making it to you.
#9
01/21/2003 (7:57 pm)
Woohoo! It works, it works!! Muhahaha! :)
#10
01/21/2003 (7:59 pm)
Sweet.. Ive always hated those annoying lines
#11
01/22/2003 (6:50 am)
ack! win98 cut and paste is goofy trying to copy from here to my textpad ,but it works well copying to word 2000.

I always wondered if anyone was going to fix them lines, :)

great work!
#12
01/22/2003 (8:50 am)
Moist and Delicious!!!

-J
#13
01/22/2003 (9:05 am)
Thanks Clark! We definately appreciate your and BraveTree's contributions! :-)

-Eric Forhan
Trajectory Zone
MRT
#14
01/25/2003 (7:17 am)
Terrific job! Although it seemed like a tiny problem, I must admitt those "seams" in the terrain were a bit distracting. I just rebuilt my engine and checked the same spot were I saw lines before and.... THE LINE WAS GONE!!!!

HOORAY!!!

a must in the next HEAD or CVS
#15
01/25/2003 (1:27 pm)
If you don't mind, I'd like to improve it to use any texture size and make it work on all mip map levels if you're not going to any time soon. But, great job - you've got most of the gruntwork done! :D
#16
01/25/2003 (3:13 pm)
Well, I'm done already :)
I havn't tested it thoroughly on different texture sizes, but it should all work.

Also, you have to call the function AFTER it extrudes the mip levels. The origional code also works when called after mip level extrusion. Anyway, here it is:
void tweakTerrainBmp(GBitmap * pBitmap)
{
	AssertFatal(pBitmap->getWidth() == pBitmap->getHeight() &&
		pBitmap->getFormat()==GBitmap::RGB,
		"This should have been caught before, but this is an invalid terrain texture.");
	S32 size = pBitmap->getWidth();
	for (int k = 0; k < pBitmap->getNumMipLevels() - 4; k++)
	{
		U8 * bits = pBitmap->getWritableBits(k);
		S32 offset1 = -1;
		S32 offset2 = 0;
		S32 base;
		for (base=0; base<size; base += size / 2)
		{
			S32 top1 = (base + offset1 + size) % size;
			top1 *= 3;
			S32 top2 = (base + offset2 + size) % size;
			top2 *= 3;
			// slide down column
			for (S32 i=0; i<size; i++)
			{
				for (S32 j=0; j<3; j++)
				{
					U8 val1 = bits[top1 + i*size*3 + j];
					U8 val2 = bits[top2 + i*size*3 + j];
					U8 val = (val1 >> 1) + (val2 >> 1);
					if (val1 & val2 & 1)
						++val;
					bits[top1 + i*size*3 + j] = bits[top2 + i*size*3 + j] = val;
				}
			}
		}
		for (base=0; base<size; base += size / 2)
		{
			Con::printf("base: %i", base);
			S32 left1 = (base + offset1 + size) % size;
			left1 *= size * 3;
			S32 left2 = (base + offset2 + size) % size;
			left2 *= size * 3;
			// slide across row
			for (S32 i=0; i<size; i++)
			{
				for (S32 j=0; j<3; j++)
				{
					U8 val1 = bits[left1 + i*3 + j];
					U8 val2 = bits[left2 + i*3 + j];
					U8 val = (val1 >> 1) + (val2 >> 1);
					if (val1 & val2 & 1)
						++val;
					bits[left1+i*3 + j] = bits[left2 + i*3 + j] = val;
				}
			}
		}
		size /= 2;
	}
}
then:
pBitmap->extrudeMipLevels();
tweakTerrainBmp(pBitmap);

Hopefully that'll do it. I'll be checking on this.
Also, I'd like to point out that this code doesn't work exactly as it should. If you look closely, the colors don't match, but this could just be because of the geometry. Take a look at these:

example1
example2

The (small) crosshair is on the separation line.
#17
01/25/2003 (3:34 pm)
@Chris - cool beans. Nice to see you expand on that. The separation line you point out is interesting -- I had never zoomed in that much. Of course, the texture is pretty washed out when zoomed in that much anyway, so the line in that case is the least of your worries. I'd be interested in seeing someone play around with this some more to see if one can get rid of this last line. One could average over two rows/columns instead of just one to see if that made a difference, but even if that worked one probably wouldn't want to do it (you would probably start to see the distortion on the source texures).
#18
01/26/2003 (12:39 pm)
I think the problem is in the geometry because the texture doesn't blend with itself passed the edge of the shape, so unless the colors of the textures are exactly the same, there will always be that line. But, if you make 2 texels in a row the same color, you'd get a line of a different kind... I don't think there is really a good way of doing it without a lot of work. I think what you did is as good as it's gonna get.

Edit: On second thought, there is something you can do using the texture border (different from the edge texel) to blend the together correctly. I'll read up on it and report back
#19
01/27/2003 (2:11 pm)
According to my book, it IS possible. You have to load textures that are size 2^n+2. That allows for a 1 pixel wide border around it. Apparently, if you load the texture, copy the approapriate wrapping to the borders for the base and mip level textures, then set the wrapping for the texture to GL_CLAMP, simple linear texture filtering will make the unnoticable transition that we're all looking for. Someone that's better at coding that me has to do it though :(
#20
02/02/2003 (11:53 am)
Chris,

I've tried the first patch which works great, makes an incredible difference. I've tried the code you've included but it results in this error:

GBitmap::getWritableBits: mip level out of range: (1, 1)

This was used compiling HEAD as of Feb 01, 2003.
Page «Previous 1 2