Not logged in, Join Here! or Log In Below:  
News Articles Search    

Submitted by Maxim Stepin, posted on April 18, 2002

Image Description, by Maxim Stepin

One day I realized that implementing texture magnification by using simple bilinear filter is just not good enough. Very often, a texture contains several color-uniform areas with apparent borders between them and these borders get blurred as the rest of the texture. Just start any modern FPS game, move a character very close to a wall with some sign on it, and if the texture resolution is not high enough, you_ll experience a "bad eyes" effect - everything looks too blurry, you don't see any sharp borders anymore.

In order to solve this problem, I developed my own "smart" texture filter. It keeps borders between color-uniform areas look sharp regardless of texture magnification level and, at the same time, keeps interiors of those areas as smooth as bilinear filter does.

The idea is to use independent interpolation function for each group of four adjacent texels. I developed a set of such functions - most of them describe how border(s) intersect the interval between these texels_ centers. To determine, which function to use for each group of four texels, some additional information needs to be stored along with a texture. In my demo, I used 8 additional bits per texel for that purpose. Because these interpolation functions describe pretty much a sub-texel level of the texture, on preprocessing (analyzing) stage I had to use a much bigger version of the same texture. For example, for this demo I analyzed 2048x1024 image to get the final 512x128 texture with correct interpolation function information.

You can find the demo and full source code at I used software rendering for obvious reasons, so please don_t expect very high speed. Artwork is inspired by M.C. Esher drawings.

Fell free to ask any questions. Comments and suggestions are very welcomed.

Maxim Stepin.

Image of the Day Gallery


Message Center / Reader Comments: ( To Participate in the Discussion, Join the Community )
Archive Notice: This thread is old and no longer active. It is here for reference purposes. This thread was created on an older version of the flipcode forums, before the site closed in 2005. Please keep that in mind as you view this thread, as many of the topics and opinions may be outdated.

April 18, 2002, 05:20 PM

>> But it looks like impractical in its curent form(8bits of xtra info., preprocessing, need for a bigger texture etc.).

I agree. At this stage I wanted to create an "eye candy", even if it's impractical in some areas. Now when I'm satisfied with quality, I'll try to make a practical shift, hoping not to lose much of a quality.

I tried to analize the original texture (without the big version). The result was not so good as in this demo, but in some cases it could be acceptable. The basic difference - border angles were limited to 0, 45, 90, 135.

Maxim Stepin.


April 18, 2002, 05:42 PM

I'm not sure about this alpha testing thing... Is it for multitexturing?
I have only one texture. And black outline is not actually black (0,0,0), but contains texels with different shades of dark gray. And my method smoothes those texels too. (you may need to increase your monitor brightness to see that)

I could actually create the same texture without those black outlines - and still keep sharp borders between lizards. Even in places when three lizards are meet.
How alpha testing thing could handle that? By creating one texture for each lizard? Maybe, but that's sounds tricky to me.

Maxim Stepin.


April 18, 2002, 06:02 PM

3-rd post:

love that alpha channel


April 18, 2002, 06:23 PM

Actually, the way the alpha testing works is that it goes to render the texture using regular filtering. But you can set the alpha test value -- that is, you can tell it at what alpha value to stop drawing pixels to the buffer. So if you had the black line in your lizard texture with an alpha of 1.0 and all the other pixels had an alpha of 0.0, and then you set your alpha test to only accept values equal to or greater than, say, 0.8, then only the black line gets rendered. And since it's filtered, the alpha value is interpolated along with the colors. So this results in a somewhat smooth-looking black line that has a sharp, crisp edge because the moment the alpha value drops below your test threshold (in this case, 0.8), it stops drawing pixels to the buffer.

The reason you'd have to do it in two passes (in in a second texture stage) is because the alpha tested render would obviously cull out the desired colors since their alpha values would be too low. So you'd want to render it again (or in another texture stage) without alpha testing to get the original colors. The problem would be that you'd probably still have some bleeding since the non-alpha tested render would still exibit interpolated texels. So you might have to use two textures -- one with the black line and the alpha mask values for alpha testing, and the other with just the colors you want without the black border.

Although using a single texture might be worth a try... it might still look alright.


April 18, 2002, 06:25 PM

Don't get me wrong -- alpha testing still probably wouldn't look quite as good as your technique.


April 18, 2002, 06:46 PM

Max, show us this effect with another texture


April 18, 2002, 06:55 PM

Don't get me wrong -- I actually saw that alpha testing tecnique. :)

In one early Serious Sam tech demo - there was a coridor with rooms, each room showed some techinque. There was a half-transparent object in one room, with that tricky border between solid and transparent areas.

The problem is - my texture might look like it contain 2 major parts - 1) black lines and 2) colored lizards. But that's my bad choice of texture - not a filtering feature. If I remove black lines, my methods still will be able to show sharp borders btw lizards. While alpha testing tecnique will fail - I can assign 1.0 to one lizard, and 0.0 to another, but what value should I assing to a third lizard? :) That's where the problem is.

Maxim Stepin.


April 18, 2002, 07:14 PM

I believe that I should. :) I see some confusion because of black outlines. I'll make something without them. Something like "DANGER" or "ACHTUNG" signs - my favorites. :)


April 18, 2002, 07:30 PM

Don't let the famous General get you !


April 18, 2002, 08:27 PM

Awesome work, man. It's refreshing to see an example of innovation for a change! :)


April 18, 2002, 08:46 PM

I thought up a roundabout approach you might be able to use with a pixel shader.

Make an extra texture map of the same size and store the normal line equation of the edge in tangent space per texel, and use alpha to indicate if there is an edge at all.

In the pixel shader get the tangent space coordinate of the pixel, point sample the texture with the normal equation and use the distance formula for point to line (which is pretty simple if you have the normal line equation) to get the distance of the pixel's centre to the edge. We also have the normal of the edge in the normal line equation (surprising huh? :) which we use to offset the texture coordinate for the color texture to get the texel directly opposite the edge, we lerp that with the texel sampled without the offset based on the distance. Finally we also do a normal bilinear sampling of the color texture and use a conditional operation based on alpha to select between it and the edge based version ... could this work?

(There's some problems with the precision of the variables for the normal line equation, but if the range of the texture coordinates is small enough that should work out ... ie subdivide the texture, alternatively you might be able to think up a way to store the equation and determine the pixel centre's coordinate in relationship to each texel to ensure a workable range.)


April 18, 2002, 09:13 PM

-P3mobile 733/1.3ghz
-gf2go 32mb
-256mb ram

I get 60 while zoomed out, then the framerate drops from a constant 60 to about 30, and remains at 30 no matter how far in I go.
The question I was asking refers to the fact that no matter what mode I am in (1,2,3), I always have the same fps at the different levels.
I assume one algorithm should be more cpu intensive than the others - I was wondering which one it was.


April 18, 2002, 09:48 PM

That's strange numbers. I'm sure I understand you correctly - are you zooming in or zooming out?

Anyway, at my machine (P3-600, GeForce1 DDR) the results are:
mode 1: 40fps.
mode 2: 33fps.
mode 3: 30fps.

That's all is starting position.
In "extreme zoom in" position I'm getting about one third of these numbers.

Maxim Stepin.


April 18, 2002, 10:39 PM

Original big texture:


April 18, 2002, 10:44 PM

Looks very nice. :)

If a conditional is keeping you from implementing it in hardware, you can always work around that ;) But you might have to calculate both smart filter and bilinear each time.. hehe.
Then use a multiply to figure out which one you want to use.

smart_result * (1 & use_smart) + bilinear_result * (1 & !use_smart)

where use_smart is the alpha bits from your texture. If xxxxx_result can be a function pointer then this should only take a smidgen longer than your if statement ;). If you use the lowest alpha bit, losing a little precision, then you can easily do:

use_smart = texture_rgba_value >> 24
smart_filter_type = bilinear_alpha_value = use_smart >> 1

Your alpha setup gets a bit trickier. But if you can't use an if, then you probably can't use function pointers.. so you'd have to run both routines every time ;)


April 18, 2002, 10:49 PM

Ack I was soooo totally not thinking straight, all you need to know is if the pixel is near enough to an edge that the bilinear footpring would cross it. So you just need a texture which when point sampled will give you either a 1 or a 0 depending if the four surrounding texels of the sampling location in the colour texture are non edge pixels, if they are you use a bilinear filtered representation of the texture if not a point sampled.


April 18, 2002, 11:10 PM

Or better yet

smart_result * (1 & use_smart) + bilinear_result * !(1 & use_smart)

Bit more optimizable.. Am I wasting my time thinking about this? =)


April 19, 2002, 01:23 AM

Very interesting approach.
I'm just not sure whether it will be possible to fit all that radius+tangent information into 8 bit.
Besides, take a look at my web page, there is a picture of 14 types of interpolation functions I use. First seven of them could me discribed using your technique. But not the last seven, I'm afraid.

Maxim Stepin.


April 19, 2002, 01:29 AM

>> If xxxxx_result can be a function pointer then ...

Actually there is not just 2 function pointers: smart_result and bilinear_result. Not 2, but 14.

smart_result calculated in switch-case fashion, with 14 "cases". One of the cases - bilinear, and 13 others are some tricky border functions.
Take a look at my web page. There is a picture explaining these 14 cases.

Maxim Stepin.

William Dahlberg

April 19, 2002, 02:31 AM

Great job! This is how texture filtering should be done! Hardware support would be nice though!

Frans Bouma

April 19, 2002, 02:40 AM

Isn't this a voilation of Shortcut's S-Spline patent? (They patented a technique which creates very similar images)


April 19, 2002, 03:10 AM

You really have function pointers??
It's easy then... do the bit of code on the other post (might wanna ignore that crap about optimizing [that would have actually royally screwed things up] ;). If those are function pointers then you can use a kind of FSM like

main_hw_filter( texture ) {
use_smart = texture >> 24
remaining_alpha_bits = use_smart >> 1
pfnFilter = pfnSmart * (1 & use_smart) + pfnBilinear * (1 & !use_smart)
return pfnFilter( remaining_alpha_bits, texture )

pfnBilinear would just do that filter, but pfnSmart would be like

pfnSmart( bits, texture ) {
pfnNextFilter = pfnSmartXXXXXX1 * (1 & bits) + pfnSmartXXXXXXX0 * (1 & !bits)
return pfnNextFilter( bits >> 1, texture )

Those X's should really be in there. It's a hard-coded BSP that finds the proper filter function, as u can see. Of course you have to write out all the pfnSmartXXXXXXX functions =) There will be your 2^7 filters, plus each combination of 1-6X's followed by 1-6 bit values. Using a tree structure with a recursive function would be a lot more compact, but a little trickier (it is left as an exercise! hehe ;). So to just show it's possible, the inline form would be like:

pfnSmartXXX1011( bits, texture ) {
pfnNextFilter = pfnSmartXX11011 * (1 & bits) + pfnSmartXX01011 * (1 & !bits)
return pfnNextFilter( bits >> 1, texture )

They would all be tiny, just like pfnSmart above, and you can make a perl/python/ruby/etc script to spit them all out in like 5 minutes. The ones with no X's left to determine, i.e. pfnSmart0001011 for filter number 11), are where you actually do the work.

A bit nasty, but it gets around that 'if' block ;) Make it a real BSP using the (1 & bits) multiplication test in a recursive function and you have a simple way to do it w/function pointers.


April 19, 2002, 03:11 AM

Gah, forget that, it would break the friggin thing ;)


April 19, 2002, 03:25 AM

Then again you could just make a table of 256 function pointers and index it with (texture_rgba_value >> 24).... I need to get some sleep....


April 19, 2002, 03:54 AM

As far as I can see, no :)
It's based on having a proper enlargement already. His interpolation 'hints' are based on this enlargement, rather than just the small source image (like S-Spline).

I kinda like this idea though! Actually we're currently working on a Pro version of S-Spline in which the algorithm has been improved further, and we included (amongst others) some 'trickery' kinda similar to what this guy is doing here. Still it won't look as good as this, since all we can base our interpolation on is the source image.


April 19, 2002, 04:11 AM

it will be supported


April 19, 2002, 04:13 AM

don't listen to him : he didn't even read the explaination/technique being your filtering. Anyway, it's pretty clever. Well done. As soon as I recieve my GF4, I'll try to implement it in shader (GF2 right now).

Peter Mackay

April 19, 2002, 04:20 AM

I could decide that I'm a flying gyspy wagon, but that doesn't make it true :-)


April 19, 2002, 04:48 AM

I don't think that the need for a bigger texture is a problem as most artist would start in a high resolution anyway.


April 19, 2002, 04:59 AM

This sounds like some slighty different marching cubes algho...

Do you create the lookup indexes for every pixel group and put the result in the 4th channell ??
So to be able to "rebuild" a bigger pixmap by creating new pixel using the 4th value and using a lookup table ??

The bigger texture shoot resembles me some kind of 2d marching cubes (well quads) ... or I'm just completeley wrong ??

This thread contains 118 messages.
First Previous ( To view more messages, select a page: 0 1 2 3 ... out of 3) Next Last
Hosting by Solid Eight Studios, maker of PhotoTangler Collage Maker.