Anyone ever get smooth scrolling tiles working?
BlitzMax Forums/BlitzMax Programming/Anyone ever get smooth scrolling tiles working?
| ||
See this old thread: http://www.blitzbasic.com/Community/posts.php?topic=86549 Basically I'm talking about the fact that if you draw a bunch of tiles in BlitzMax and then scroll them at floating point coords (or zoom in on them), the joins will look jerky as the move across pixel boundaries. OR if you add a 1 pixel blank border (popular technique with sprites to avoid jerking edges) you'll get dark lines showing up in between then. (see my code sample about halfway down that thread above) [edit]Found an even older thread of mine with images. http://www.blitzbasic.com/Community/posts.php?topic=66157#739230 This is because each tile doesn't know about its neighbours and so their edges are antialiased to the background instead of the neighbouring tiles. If you were able to draw all the tiles at integer coords and *then* draw the whole lot in one go (as if it was a texture) at floating point coords/zooomed in, that would work fine because the antialiasing would be applied to the image as a whole. However, it's too slow to render to a texture first before outputting, so that solution is out. Back in the day indiepath kept going on about using a mesh (like in 3D programming) to do it but none ever materialised in BMax. Or is there a way to use something like a viewport to show a portion of a bigger tiled area grabbed from a floating point source coordinate? Or a projection matrix solution using something like: D3D7GraphicsDriver().Direct3DDevice7().SetTransform(D3DTS_PROJECTION,matrix) Then there's TileMax. Did _Skully ever solve this issue (can't see it mentioned in the feature list). Any info/suggestions you may have are most welcome. |
| ||
I didn't know this was a problem. I've never encountered it myself. Are you talking about the little black lines that can show up between tiles when they are drawn at float coords? It seems the simplest solution would be to use the center handle on your tiles and set the scale to small amount over 1.0. |
| ||
Yes I am talking about those black lines when drawn at floating point coords (if using a filtered image with a 1 pixel border - I updated the original post to add more detail) I haven't tried boosting the scale but I'm not sure that's entirely viable. |
| ||
Each DrawImage has a glBegin\glEnd or BeginScene\EndScene pair. This means that each tile drawn costs one draw call. Imagine a full map. The "right" way to render a tile map would be to use a canvas texture and generating a single mesh for all the tiles, then rendering this mesh with a single call. Since the vertices are also joined (they're separate vertices, but at the same position), this should also solve the rasterisation problem. Can you implement this? Do you need example code? |
| ||
No I cannot implement it, it's beyond my current knowledge. Anyone done that sort of thing in BlitzMax? |
| ||
I never had a problem with this but when I view your example I do see what you're saying. A couple of other solutions might be rounding your FP coordinates down to integer in software, or batching the tilemap into one or more larger images and then moving and drawing that as you wish. That's obviously going to be much faster than 64x64 or however many individual draw calls. Not to minimize your concern or anything like that but I'm questioning whether this would even be noticeable to the vast majority of users. When I ran your example I couldn't see what you were talking about even though it had been pointed out to me, until I looked very closely. The example that flashes a little bit is more noticeable. This isn't fixing the underlying problem but my gut feeling is that if you're drawing a large number of tiles every frame, you might want to find ways of not doing that, for performance reasons irrespective of anything else. How exactly to do that is something I'm still kicking around but I'm thinking for me it's going to involve rendering to pixmaps-- lots of blast scars and that sort of thing and when you are drawing all those every frame things can get real slow real fast. |
| ||
I don't want to round the FP down to int, that's what most people do and it looks jerky. Yes I do want to batch the tilemap into a larger image but I don't know how to do that with a mesh or whatever. There's no point in using render to texture stuff as far as I know because it's supposed to be very slow. Also, drawing a screen of tiles on a modern PC is extremely trivial in terms of GPU power. Drawing thousands of particles is the sort of thing that needs to be optimised. As for if people will notice, yeah that's valid for sure. There are lots of successful indie games with awful scrolling/framerate that sell fine. But I'm still interested to know if it's possible in BMax for my own satisfaction at least. I know it's possible in Unity and other engines where it's all drawn as 3D geometry for example. |
| ||
When I perform scrolling I keep the logic as floating points but only render as integers. |
| ||
Yeah for sure I'd do that if I wanted to render at int coords but I want to try and render at float coords and sort this out once and for all. Considering another approach. Using a projection maxtrix. Draw the tiles at int coords, then project the whole lot at a floating point end coord. I've currently got this projection matrix code that I was using for zooming in on a screen (works well), but do you know if it can be modified to apply an x,y coord (using floats) somewhere in that matrix? If D3D7GraphicsDriver().Direct3DDevice7() = Null glMatrixMode GL_PROJECTION glLoadIdentity glOrtho 0, Self.width, Self.height, 0, -1, 1 glMatrixMode GL_MODELVIEW Else Local matrix:Float[] = .. [.. 2.0 / Self.width , 0.0, 0.0, 0.0, .. 0.0, -2.0 / Self.height, 0.0, 0.0, .. 0.0, 0.0, 1.0, 0.0, .. -1-( 1.0 / Self.width ), 1+( 1.0 / Self.height ), 1.0, 1.0 .. ] D3D7GraphicsDriver().Direct3DDevice7().SetTransform(D3DTS_PROJECTION,matrix) EndIf Though admitedly if I do that I have to make sure I can revert the matrix back to normal and draw the HUD over the top, if that's possible... Hmm scratch that because of course you need to call SetTransform before you start drawing the tiles, so they'll no longer be at int coords as the matrix will have changed them. |
| ||
Well then it seems the only way is to use a 3D mesh, whatever that is, and make sure the graphics card knows to render all the tiles at once and thus calculate the join anti-aliasing correctly. There's nothing to do that in BlitzMax, but it does have access to certain DX/GL commands and it's possible to add more commands (via C++) so it's gotta be doable. Something like this: http://www.riemers.net/eng/Tutorials/DirectX/Csharp/Series1/tut13.php and this (it's gotta work with DX9 and not use DX11 shaders to render the mesh) https://msdn.microsoft.com/en-us/library/windows/desktop/bb153263%28v=vs.85%29.aspx |
| ||
How jerky can it possibly be if you are rounding to int? How large is your screen resolution? Drawing screen-aligned textured quads as a display list should be trivial but I don't know if I'm buying it that rounding to int would not be smooth, or at any rate that the lack of smoothness would be noticeable at all unless you're looking at the screen with an electron microscope or something. Pixels these days are easily sub-millimeter, it looks on my screen like there are 2 per millimeter. |
| ||
Just checked some of my previous games scrolling code. For The Bloobles I did use Ints and did the following: For Local x% = xstart To xend For Local y% = ystart To yend c = tiles[x, y] tile = c.CellType If tile > 0 Then DrawImage TilesImage.Image, Int(x * TILE_SIZE - Game.ScrollX), Int(y * TILE_SIZE - Game.ScrollY), tile Next Next But the movement was always at a set speed. For the map in LoS:COTD, I used floats as it looked a lot better. Heres a cut-down version of the map code and I've made so that you can switch between Ints and Floats using the spacebar: https://dl.dropboxusercontent.com/u/35103024/bmx/Scrolling.zip |
| ||
Hi therevills, thanks for sharing that code and exe. Yeah the floats do look a lot better in it (jerkiness is gone as expected). However, you are still getting artefacts at the 128pixel joins, although they are quite minor due to the image type you've used (more on that below). For example, find the 3rd vertical blank line in from the left edge of the map, then scroll it slowly. You'll see that black line "shimmers" even though the other lines do not. That's because it falls on a 128 pixel boundary and the issue I've been discussing crops up. There are a few other smaller examples but that's the most obvious. So if I'm making a tile based games which has lots of square block graphics e.g. a castle, they will often have vertical and horizontal lines on the edges of the textures. When I draw the tilemap at a subpixel coordinate, I'll get loads of those shimmering lines everywhere, which I want to avoid. You are lucky that for that map the tile size you chose doesn't fall on very many lines as this issue is less pronounced on more "organic" looking textures. In fact I modified your image to move it left 10 pixels and changed the tile size to 82 pixels and now I get shimmering joins on many of the vertical lines. |
| ||
Anyway after discussion with some other devs, I think the solution must be to use some render to texture (render target) code like this: http://www.blitzmax.com/codearcs/codearcs.php?code=2222 That's only OpenGL though and I haven't tested it yet for a tile engine. I'll need a DX version but I just can't find one on these forums (there are references to one by indiepath that doesn't with new BMax). Also I found an old thread elsewhere from TonyG saying he couldn't get a DX8+ render to texture working because "BlitzMax uses the TEXTUREMANAGE flag which means I couldn't set videomemory. Took out the texturemanage and it all worked OK." However, I'm pretty wary of doing that. Like where does BlitzMax use that flag? Just on creating textures, or when setting up DX? It could be pretty important! Like what if it causes alt+tab issues, or maybe I'm just being paranoid - but I've learned not to assume stuff will just work and be safe after all these years coding :-) |
| ||
IMO the entire graphics engine should be doing this. I thought it was already. You could also do it with fixed pipeline quads rendered in 3d but orthographic, with or without even using displaylists. That would be extremely simple and supported on everything and if you were doing, say, 32x32 tiles the speed would be perfectly fine. |
| ||
Yeah if the whole of BMax's backbuffer was a texture, you rendered on that, and then it was copied onto the main backbuffer (scaled as needed and at floating point coords if required) that would make all the projection matrix and virtual resolution stuff way simpler and would anti-alias/filter the entire texture nicely. Like building up a single image in photoshop and scaling it afterwards. Currently you have to draw each sprite individually and if you've set up a projection matrix at the start, it means each sprite is scaled and drawn at floating point coords which yields a less cohesive final image. |
| ||
Think of all the other stuff you could do as well, like postprocessing and color cycling effects in shaders. Seems like it would offer a gigantic speed boost as well as all the other benefits. I would be willing to help with any sort of development along those lines. |
| ||
Yep, you could do all sorts of cool stuff afterwards. Sorta fancy effects I see in other games. Well the main thing is to convert that openGL render to texture stuff (link above) to DirectX. Then we can just make a texture and swap the drawing code to it (though that might be tricky, dunno), then draw that whole thing on the backbuffer at the end. Though I wonder if any of Brucey's mods has a DX version? (hmm doesn't look like it: https://code.google.com/p/maxmods/) I'm also in contact with Indiepath who made a DX version years ago that stopped working with BMax updates, so I'll see if anything comes of that. |
| ||
Why do render to texture at all? If it's a tilemap, why not just texture your bitmaps to individual quads in a mesh? Render to texture would probably be better and easier because it would be more transparent for people used to 2D but weren't you saying it was slow? If it was just individual quads, you could have some of them missing to let a background show through. You could also use the OpenGL vertex lighting and coloring. |
| ||
No I was wrong about it being slow. I got confused with pixmaps. A mesh might work, but it might also still have the join lines issue. Can't tell until I see it. If I'm going to spend my time working on one, I'd rather do the render target one as I know that will work. |
| ||
A mesh might work, but it might also still have the join lines issue. That's conceivably possible but wouldn't someone have noticed in 25 years of OpenGL? I will see if I can throw a demo together this week, been about 3 years since I touched OpenGL to any extent but I've been meaning to get back at it... my tendency to be a retro-luddite is bad. |
| ||
Maybe it could work with textured polygons : ex(there many threads about textured poly) [a /posts.php?topic=100299#1183143[/a] If each tile is drawn on a polygon, the antialiasing should be 'cut' on polygon's limits. There could be artifacts but no dark lines. Quite tedious to do tough. And I wonder about the performance with many polygons drawn 'by hand'. |
| ||
It's certainly not too bad for say 32x32 and that's a pretty large tilegrid. |
| ||
On monkey with a atlas sheet it would give the desired effect as it uses one texture for tile drawing operations and changes the UV coordinates of the image you want to draw, if you want to get rid of it you have to turn it off but it can be useful for these sort of problems. |
| ||
Do you really need anti-aliasing on tiles? Here is the example from the other thread, but with FILTEREDIMAGE disabled on the second image.Strict Graphics 800,600,0 Local x# = 0 Local y#=50 Local speed#=0.23 Local size=64 Local tiles1:TImage = LoadImage("tiles1.png") Local tiles2:TImage = LoadImage("tiles1.png",MASKEDIMAGE) While Not KeyHit(KEY_Escape) And Not AppTerminate() SetBlend AlphaBlend Cls 'rectangles SetColor 200,200,200 DrawRect x,y,size,size SetColor 200,100,100 DrawRect x+20,y+20,20,20 SetColor 100,100,100 DrawRect x+size,y,size,size 'two images with FILTEREDIMAGE set SetColor 255,255,255 DrawImage tiles1,x,y+100 DrawImage tiles1,x+size,y+100 'two images with FILTEREDIMAGE removed DrawImage tiles2,x,y+200 DrawImage tiles2,x+size,y+200 'move x:+speed DrawText x,0,0 Flip 1 Wend |
| ||
@TomToad: Try this and you'll see the issue (note I've dropped size to 32 pixels so modify if need be). The joins (and edges) are perfect but that's because they are clamped to integer coords by the graphics driver, and so when moving slowly they all jiggle badly. If you add a 1 pixel blank border to those images (so they are 34x34) and draw those (but keep size variable at 32), you'll see the middle set (the filtered set) has nice smooth outer lines (no jiggling), but there's a black join line in the middle. The unfiltered set still jiggle badly. Strict Graphics 800,600,0 Local x# = 0 Local y#=50 Local speed#=0.23 Local size=32 Local tiles1:TImage = LoadImage("testimage1.png") Local tiles2:TImage = LoadImage("testimage2.png") Local tiles3:TImage = LoadImage("testimage1.png",MASKEDIMAGE) Local tiles4:TImage = LoadImage("testimage2.png",MASKEDIMAGE) While Not KeyHit(KEY_Escape) And Not AppTerminate() SetBlend AlphaBlend Cls 'rectangles SetColor 200,200,200 DrawRect x,y,size,size SetColor 200,100,100 DrawRect x+20,y+20,20,20 SetColor 100,100,100 DrawRect x+size,y,size,size 'two images with FILTEREDIMAGE set SetColor 255,255,255 DrawImage tiles1,x,y+100 DrawImage tiles2,x+size,y+100 'two images with FILTEREDIMAGE removed DrawImage tiles3,x,y+200 DrawImage tiles4,x+size,y+200 'move x:+speed DrawText x,0,0 Flip 1 Wend |
| ||
@zoqfotpik someone sent me a demo using a mesh but it exhibited join lines. So until I see one where it works, I'm ruling out that as a possible solution. @Pingus I don't think that will solve it. The issue is that we want both adjacent tiles to know about each other and anti-alias along the join as if they were actually one large image. Drawing them separately (at floating point coords), they won't know about each other, which is why there's a join line in the first place. Also cutting anti-aliasing at polygon edge will result in jiggling on integer boundaries. Unless I'm misunderstanding you. @EdzUp A texture atlas won't solve the issue I'm talking about. Whether you render a filtered tile from a single image, or an atlas it doesn't matter. The graphics driver will still sample the pixels around the tile you are using for the anti-aliasing. In a single tile, it'll sample blank space (result in a black join), in an atlas it'll sample the adjacent tile and use its colours as part of the anti-alias result. I've tested this and seen it in action. Basically folks, we need render to texture for DX and OpenGL in BMax and it'll make this problem go away and allow us to do totally rad things. |
| ||
In fact dmaz already suggested the same thing... 8 years ago :-) http://www.blitzbasic.com/Community/posts.php?topic=66157#739279 IF the drawpoly can be drawn at floating coordinates (antialiased subpixel), this should work. |
| ||
Yeah GA it is way below my threshold of detail that I notice but I would love such a thing for other reasons, like the postprocessing thing. It would be easy to add in hooks for postprocessing shaders. |
| ||
@Pingus Nope the mesh doesn't work (based on my limited test). It doesn't anti-alias the joins properly. Like I said, someone sent me a demo and it didn't work. It's because when the mesh is drawn at a floating point coord the edge anti-aliasing looks at the pixels OUTSIDE the original tile size (say 32x32) and uses those (which come from the neighboring tile in the atlas). I proved this in the demo I was sent by drawing over the neighboring tile in the atlas with bright red and I could see that was what the mesh was using for anti-aliasing. What all that means is maybe it's possible to make a 34x34 tile where the edge of the 32x32 tile is repeated in the outer border. Then when drawing in the mesh, use UV coords of the inner 32x32 square and it'll use a better colour for the anti-aliased join. Still not ideal though as the destination background colour will come through due to the join having an alpha value, and then you draw the adjacent tile on top of that (again with some alpha on the join) which results in a 3 colour blend. So it's not as accurate as if you just got the 2 adjacent tiles and blended those two colours for the join (based on their floating point coords used as weight). However, I'm willing to be proved wrong! :-) Still I say, render to texture is the way to go. |
| ||
What if there was a 1 pixel transparent border around the texture? You said this " if you add a 1 pixel blank border " but it's not clear if you meant transparent. I presume you did. |
| ||
Yeah sorry transparent is what I meant. However, you can still run into issues because paint programs like photoshop give the transparent pixels a white colour which can be taken into account during rendering edges scaled or at sub-pixel coords. You can definge images like that (to remove the colouring) but I've found that just goes back to jiggly floating point scrolling as if it were at int coords, very strange. |
| ||
Maybe rendering to a pixmap then drawing that might be the only way, I would have thought a quad sheet would have worked where each quad is a image then when rendered it would use its neighbours but I haven't run tests on that. |
| ||
Well you can't do drawimage on a pixmap can you? Only poke and peek pixels as far as I knew (after you've called LockImage() on it) |
| ||
@ Grey Alien -- One brute-force method I've used is to tape the joints between 2 tiles -- like a builder tapes the ugly seam between 2 sheets of dry-wall. Offline or during startup, place 2 adjacent tiles side-by-side using integer coordinates and GrabImage of several pixels from each tile. During dynamic floating-point run, tape over the gaps. Seems to mask flicker at different zoom levels. I've used this for side-scrolling of long backgrounds sliced into 512 pixel chunks where drawing 4 pixel tapes doesn't effect overhead much. |
| ||
Ha that's a novel solution. Thought I'm still looking for a render to texture solution... Pretty sure I've searched these whole forums now. Found bits but nothing 100% |