In Blender, you can reduce filtering by going to the Texture->Image Sampling panel, turning off Mip Maps and interpolation, and cranking down the filter size as low as it will go. But you also have to ensure that your object is scaled relative to the viewpoint correctly, so that there is a 1:1 pixel mapping. Also, I notice that in your posted image, on the right, there is a bit of sloppiness in your tile. To be pixel-perfect, or as near to it as can be achieved, you need to ensure that your tiles are correct. A 2:1 tile ratio has cell lines that are exactly 2 pixels horizontally for every 1 pixel vertically; in your tile, there are a couple spots on the cell boundary that don't follow the pattern precisely, and this can have implications when drawing. That is a minor nitpick, though.
Getting the math right for pixel-perfect is the trickiest part of the process. You also have to ensure the quality of the asset creation process. I have found that, for best results, a 2:1 isometric tile hold approximately to this template:
That is zoomed, of course. You can see that all the edge lines hold to the 2:1 line ratio. In the image, the dark zones indicate areas where padding may be placed, in order to mitigate what filtering may take place. Extra padding around the outside may be necessary if the sprite is to be scaled. The padding between the top and the walls should match the top, and not the walls, since those rows of padding will be visible on tiles of the same level placed adjacent to one another, without tiles on top of them.
The main areas where you need to do some calculating are:
1) The orthographic projection matrix, and how it relates to the drawing size of the tile cubes. My personal method is to visualize each tile, as in the template above, as being some number of units in size. Each 2-pixel wide block in the cell lines denotes a unit, so a tile that is 64 pixels wide would denote a 16x16 unit cell. So if I draw my cubes as 16x16xH (height varies, depending), and use integer math for coordinates and locations, then I can ensure that tiles will be drawn on whole-pixel locations, and filtering is reduced. This requires special consideration in calculating the projection matrix.
My method, then, of calculating the orthographic matrix is to divide the screen horizontal resolution by the width, in pixels, of a tile. (64, in the case of the above magnified template). Then I multiply this value by the world size of a tile (16, in my system) and multiply that result by 1.41421356237 (The square root of 2). The reason for this is that, given a camera rotated 45 degrees around the vertical axis, you are viewing a tile not edge-wise, but diagonally. The distance between the corners of a square whose sides are 1x1 is, of course, the square root of 2.
With that calculated value, I multiply by the aspect ratio to get the orthographic height of the viewport, then use those values to set the ortho matrix. To illustrate, here is a bit of code to do just that in OpenGL:
// Get the viewport resolution
glGetIntegerv( GL_VIEWPORT, viewport );
// Set the projection matrix
float screen_tiles=(float)viewport / (float)tile_pixel_width_;
float ortho_height=ortho_width * aspect;
glOrtho(-ortho_width/2, ortho_width/2, -ortho_height/2, ortho_height/2, 0.0f, 1000.0f);
// Set the camera, pointed at (x_,z_), 30 degrees around X and 45 degrees around Z. 100 units (arbitrarily chosen, will probably need to be larger for a bigger map, to
// Prevent far-plane clipping
That is working code from the demo that sets the projection matrix. The member tile_pixel_width_
of that class is set during initialization, to the width (in pixels) of a tile. 64, with the above template. The member tile_world_size_
is set by dividing the tile pixel width by 4, for a 16x16xH unit tile in the case of the above template.
2) Tile height. If you set up a 30 degree camera (the angle needed for a 2:1 projection) and view a cube, you will notice that the cube vertical faces are fore-shortened, due to the angle of the viewpoint. There needs to be careful consideration for the correspondence between the tile sprite and the cube primitive that is drawn. The height of the cube primitive needs to be scaled correctly to correspond with that foreshortening. This can take a little bit of trigonometry, but once you wrap your head around it it's not too complicated. In the case of my template tile above, the tile is a total of 64x48 pixels. (The rest is padding to bring it to 64x64.) That means 32 of the pixels are taken by the top of the tile, leaving 16 for the vertical faces. A bit of trig gives me the totally arbitrarily funky value of ~0.40824829f as the height to scale the unit-sized cube to in order to match the tile template.
With that in place, the rest is pretty simple. I put together a small demo in C++, using the latest RC of SFML 2.0. (I don't use XNA or C#.) But the code should be relatively explanatory. It's a quick, one-off demo meant to show the concepts, rather than a comprehensive prototype, but it works.
And the isometric map code:iso.hiso.cpp
Here are the tiles used for the demo. (They should be in the root directory of the executable.)dirttile.pnggrasstile.pngleftramptile.pngrightramptile.png
The demo sets up a tiny sample map, then enters a loop in which the camera scrolls around endlessly in a circle. Nothing fancy. The tile geometry units are hard-coded, but in a real game I'd use loadable geometry in case I needed a weird tile of some sort. Here is a shot of the demo in action:
If you zoom in, you can see that the pixels are pretty close to pixel-perfect (or, at least they were on the source image; I really can't vouch for imgur's service after they're uploaded). Filtering artifacts might occur (very occasionally) but if you pad your sprites correctly, even on the rare times they happen, they are not that noticeable.
I didn't have time at work to hack in mob sprites, but depth testing IS working correctly, and the sprites would be correctly clipped. (You can tell depth buffering is working, as the tiles are drawn from front (top, nearest corner) to back (bottom, far corner), and yet the back tiles are correctly clipped by the front tiles.
The key to eliminating filtering, whether it's in Blender or in a game, is to ensure that there is exactly 1:1 correspondence between the pixels being drawn and the region of the buffer they are being drawn to. Any change in scale or stretching will result in filtering of some sort, and you will have to plan accordingly. You can use nearest filtering, but there will still be filtering. This is actually the biggest hurdle when trying to do a pixel-perfect translation from 2D to 3D. Although, of course, you also have to worry about filtering if you do traditional 2D with scaling, so there's that.