Skip to main content
GameDev.net gamedev.net
🔒 Locked

Map Quad Drawing Logic

Started by hollowMedia Jul 9, 2004 at 12:25 PM 10 replies 2.1k views
Original Post
hollowMedia
hollowMedia
I'm doing a 2D game with a basic level map and based on different texts on VB's, I get different ideas of what is efficient/optimal. If I have an 800x600 screen and about, let's say 8 screens worth of tiles, how would I go about working with it? According to one nvidia doc, 10k-20k is am optimum size for a VB. Other docs say 1k-2k. That in itself would play a huge difference. I was thinking about creating the whole map in a VB, which would allow for about 10 screens at my rate, then moving the camera for viewing. I could use index buffering to determine what I'm rendering. Honestly though, I don't see that as being terribly efficient aside from the fact that I'm only locking and unlocking the vb once. Any ideas on this? I appreciate it, I'm not very well versed on the theory of 3d programming just yet, so I'm piecing a lot of these concepts togther as I go.
[xian]
tomek_zielinski
tomek_zielinski
Todays hardware can display hundreds of thousend of triangles per frame, millions per second.

If you are sometimes playing 3D games you can see huge complicated world with hudreds of thousend faces. And such a game maintains up to 5/10/20/50 Vbufs - so each VB contains tens of thousends of vertices - so it isn't really inefficient - it's rather efficient to keep VB number small.

Then - you are writing 2D tile game. How much triangles do you need ? 100? 200? 1000? Even on old Riva 128 or TNT it won't slow down...
So instead of worrying about it write the core gameplay! D3D will cope with your data well!

www.tmreality.com
hollowMedia
hollowMedia
It's not about what it can do, it's about learning what the best practices are. Sure, having a completed game would be nice, but I'm more interested in learning how/why right now.

[xian]
Wicked Ewok
Wicked Ewok
What is the published date on the paper you read? My guess is that it's pretty old. But in practice, merging things together in one larger VB is a good thing. I would create the whole map in a VB, in fact that's what I'm doing for my own little project. And yes an index buffer would be good, but locking it to change what to draw seems unneccessary to me. What I'll end up doing is dividing the maps into chunks the size of the screen, and putting these into index buffers. Probably will end up with one IB, with multiple offsets for the different chunks, then I would render the chunk most in view, and all touching chunks including diagonals. If you don't optimize the way DIP is called with your IB chunks, you'd get 9 DIP calls, but you can easily bring that down to 3, with no IB locks. 3DIP and no IB locks is better than 1 DIP and an IB lock, or at least from my experience think so.

and oh, in the game of optimization, any small speedup that can be had with just a little thought can and will usually add up to good performance down the line.

[Edited by - Wicked Ewok on July 12, 2004 3:32:47 PM]
-=~''^''~=-.,Wicked,.-=~''^''~=-
hollowMedia
hollowMedia
Thanks!

I am basically trying to replicate the functionality of a previous (DX7) game I had written where the player stayed centered and the map moved along with the player until the map couldn't move any longer. At that point, the player would continue to move to the edge of the screen as needed. The chunked IB idea wouldn't work well with that considering it is not a single direction for movement and offsets wouldn't really work.

My original idea was to create the VB, let the camera handle the rendering, and move the player accordingly. Unfortunately, I don't understand the subtleties of the 2D camera with DirectX, so I really need to gather more information on how that works. If you know of any articles/tutorials that give a better description of this, I'd appreciate it.

Thanks again.
[xian]
SoaringTortoise
SoaringTortoise
I don't think you should treat the 2d any differently from a 3d game in terms of the map creation and rendering. Use a single VB for the map, but choose an appropriate striping method so that you can cull areas that are off-screen. For example:

Create your tiles as chunks of 10x10 tiles, but put them all into a single VB. This way each chunk contains a 10x10 area of the map, but the VB is ordered like this

1 2 3 4 5
6 7 8 910
etc

This would represent a 'world' of 50x20 tiles.

Your view program would then just use an offset and span to draw chunks 1,2,6 and 7 if you're in that area (this is, I think, a pretty common 2d mapping scenario).

Also, use a 3d camera but set the projection to Orthagonal. This will make the view appear like a standard 2D game, but it would simplify your rendering code - you just move the camera and draw any chunk that is nearby.

As for the most efficient. Each card make and version has a different optimal buffer size. The limiting factor is the memory required and the speed of transfering that memory, vs the cost of switching buffers. So, a low-mem card would be happier with smaller buffers, an nvidia geforce3+ would like large and an ATI 9700 would like massive (once again, depending on make and version).

There is an upper limit to the number of vertices you can put in a VB, and that's dependent on the vertex format (16/32 bit), but you shouldn't be reaching that. If you try to stick to around 1k-2k poly's you should satisfy most cards.

If you like, you can create a flexible VB that varies according to the card (which you'll detect at start-up) and builds the VB's according to the card's optimum set up, but that gets really messy and bogs you down.
Always prey on the weak, the timid and the stupid. Otherwise you'll just get your butt kicked
For a tortoise, this is extremely hard to do, but when you get it right... the expression on their faces ...
SoaringTortoise
SoaringTortoise
OH, and using chunks the size of the screen isn't going to be efficent (Wicked Ewok's post). As you scroll towards the edge of the screen you're going to have to draw an entire screen-size chunk to fill the edge of the screen, but you'll see very little of it - too much stuff sent to the video card that isn't useful.

THE most efficient way to handle this would be to dynamically create the VB on the fly using some kind of rectangular culling so that at most you're only drawing at most parts of a redundant triangle per tile. However, IMHO, that's a hassle, and simply using chunk-sizes 1/2 - 1/4 the screen size provides a good balance between unnecessary rendering and hassle. I'm greatly in favour of a *little* performance loss for reduced coding.

Having said all that, most modern cards are exceptionally efficient at culling geometry outside the view frustrum so the performance hit rendering an entire screen-sized chunk is going to be very minimal. On older cards the problem becomes more pronounced.
Always prey on the weak, the timid and the stupid. Otherwise you'll just get your butt kicked
For a tortoise, this is extremely hard to do, but when you get it right... the expression on their faces ...
hollowMedia
hollowMedia
It's going to take me a while to figure all of that out. Yesterday I managed to get a much better grasp on the use of transform matrices. I still don't know the common uses for changing each, but I managed to work with them a lot better.

Good thing I have this math degree here collecting dust.
[xian]
Supernat02
Supernat02
How are you implementing 2D? Are you going to use D3DXSprite objects, inverse view quads, an Orthogonal Projection, or XYZ_RHW vertices? Each comes with its own subtleties.

With D3DXSprites, implementation is much easier, you can still rotate/translate the sprites on screen with the SetTransform function in the D3DXSprite interface, and it will even help with batching by sorting textures for you. You will have to render things in the proper order though. If you want your main character on top of the landscape, render the main character last.

With inverse view quads, you make a basic billboard where the textured quad is always facing the camera. You can then make all quads in the same plane (say z=1), and you can even simplify it further by setting the VIEW transform so that the camera is looking straight down the +Z axis at x,y = 0,0 (I think you just set it to Identity). Then you just create untransformed vertices always setting their z values to 1. With this setup, you can move the camera without having to change the vertex coordinates or calling a SetTransform function. You just select sets of quads to draw based on where the camera is.

An Orthogonal projection is where you specify a view Cube instead of a view Pyramid, as is done in Field Of View perspective. Using the D3DX functions, you can easily setup an ortho projection, only knowing the width, height, near Z, and Far Z clipping planes. What that means is that a cube will be created. Anything inside the cube will be rendered on a percentage basis. Let's say you set Width and Height to 1.0f and 1.0f. If you create a quad in that view cube (i.e. z=anything inside the cube), and you make that quad's vertices go from (-0.5, -0.5, any value), to (0.5, 0.5, any value) (where any value remains the same between vertices of that quad), then you'll end up with a quad rendered to the entire screen. If you change 0.5 to 0.25, it will be centered in the screen, and a .25 unit empty space will be around the box. The benefit of using ortho is that you can assign z values to objects in your 2D game, and that will be their rendering order. The higher the z value, the closer to the front of being drawn they will be (assuming you have Z-Depth testing enabled). This way, you can assign z=2 to all of your tiles, z = 1.5 to all overlay tiles, and z = 1 to the main character who runs on top of the tiles.

With XYZ_RHW, you basically implement a D3DXSprite on your own. I wouldn't recommend it, but it's not too difficult to do.

To do what you were saying, about having the camera stop at the edge and letting the character move to the edge of the screen, you would just simply test for the tile you're on being some distance from the min/max of that column/row, each frame. The easiest way I can think of is to move the camera and the player every frame throughout the tiled world. I assume you won't be transforming the tiles in any way. i.e. You've loaded a vertex buffer with the tiles and their locations in space, so you don't need to move them. When you are a certain distance from the min/max tile, you need to stop moving the camera and the player will just move naturally.

I created an engine that just resets the camera after every tile's width/height of travel and batches the vertex buffer into an index buffer, based on textures on-screen. It allows you to have a virtually unending landscape, so you can roam freely. You would obviously still have boundaries put in anyway. The way I do this is by having an array of unsigned chars (assuming no more than 255 textures for the tiles), I load the array up from a map file created in my map editor. All the file stores is the index of the texture at that array position. Well, I actually store the textures and what index they are too, but that's not important.

// Initial Codeunsigned char Map[1000][1000];LoadMapFile(Map, "Filename");unsigned char NumTextures = 25;CreateVertexBuffer(NumTilesToShow*6);// Every FrameBatch the tiles based on texture, then use a simple algorithm to figure out which indices to put in the index buffer.unsigned int NumTilesToShow = 100; // Shows a 10x10 grid for simplicity.  You would probably calculate this if you were to allow the player to zoom in and out.unsigned int NumTilesFound = 0;unsigned char CurrentTexture = 0;DWORD TempData[NumTilesToShow*6];for (int a=0; ((a < NumTextures) && (NumTilesFound != NumTilesToShow; a++){  for (int x=0; ((x < NumTilesToShow/2) && (NumTilesFound !=  NumTilesToShow)); x++)  {    for (int y=0; ((y < NumTilesToShow/2) && (NumTilesFound != NumTilesToShow)); y++)    if (Map[x][y] == CurrentTexture)    {      AddIndicesToTempData(x,y);      NumTilesFound++;    }  }  LockIndexBuffer(DISCARD);  CopyTempDataToBuffer();  UnlockIndexBuffer();  SetTexture(GetTexture(CurrentTexture));  DrawIndexedPrimitive();  CurrentTexture++;}


This actually provides a pretty good frame rate for a 64x64 grid. This might be full of holes, I just threw it together from memory, but I hope this gives you some ideas at least.

Now, if you leave the Map array in the vertex buffer with world coordinates (i.e. the Map vertex buffer starts at (-100,-100,1) and goes to (100, 100, 1)), then you can take input from the player and move the camera in that direction. As a simple example, the player moves to the right once button push. You move the camera 0.2 to the right. The player does this 5 more times, you move the camera 1.0 to the right. Now, your camera says "I just went from 1.0 to 1.2...I need to jump back to 0.2". I hope that makes sense...it's difficult to explain. Everytime the camera goes 1 tile length in a direction, it sets itself back to 0 plus any offset from the nearest whole number previously. ALSO, you update your Map array by moving all columns or rows back or forward one, OR you just add an offset of 1 to the x and y values in the code I put above. I have to go, or I'd explain further. Perhaps I can post more code tonight.

Good luck,
Chris
Chris ByersMicrosoft DirectX MVP - 2005
hollowMedia
hollowMedia
First off, I'd like to say thank you. You've provided a great deal of lucidity for concepts that I had mistakenly blended together.

Quote:
Original post by Supernat02
How are you implementing 2D? Are you going to use D3DXSprite objects, inverse view quads, an Orthogonal Projection, or XYZ_RHW vertices? Each comes with its own subtleties.


Well, at this point I was doing multiple things. I was rendering the background as transformed vertices and using an orthogonal projection to keep my coordinates positive. I was also using the Sprite object to create and draw a dynamic menu system that I've done. FWIW I'm using Managed DirectX, but I don't think there are many limitations as a result. After thinking about it, I decided to move away from the transformed vertices so I wouldn't have to manage their location explicitly.

Quote:
With D3DXSprites, implementation is much easier, you can still rotate/translate the sprites on screen with the SetTransform function in the D3DXSprite interface, and it will even help with batching by sorting textures for you. You will have to render things in the proper order though. If you want your main character on top of the landscape, render the main character last.


Since I am familiar with DirectX7 (DirectDraw), I know how that works. I plan on using sprites for all images with static coordinates, but with the background I thought it might be more elegant to move the camera.

Quote:
With inverse view quads, you make a basic billboard where the textured quad is always facing the camera....


This is what I was thinking would be a good idea for the background. I think I was getting caught up in the last part, which was selecting which quads to draw. I believe your sample code illustrates this idea well, thanks again.

Quote:
An Orthogonal projection...


Another idea that I thought was good, but I was blending it with other ideas, which caused some issues.

Quote:
To do what you were saying, about having the camera stop at the edge ...


Yeah, I've implemented this before. I was just justifying why I didn't want to use the "chunked" setup.

Quote:
I created an engine that just resets the camera after every tile's width/height of travel and batches the vertex buffer into an index buffer, based on textures on-screen...


Excellent. I read a ton of articles and a lot of them mention the good/bad practices of VB/IB's. The problem is, they're utilized in ideal situations, which aren't always realistic. Your snippet has shown me enough to get me moving without worrying so much about performance.

Thanks a ton!
[xian]
Wicked Ewok
Wicked Ewok
Quote:
Original post by SoaringTortoise
OH, and using chunks the size of the screen isn't going to be efficent (Wicked Ewok's post). As you scroll towards the edge of the screen you're going to have to draw an entire screen-size chunk to fill the edge of the screen, but you'll see very little of it - too much stuff sent to the video card that isn't useful.

THE most efficient way to handle this would be to dynamically create the VB on the fly using some kind of rectangular culling so that at most you're only drawing at most parts of a redundant triangle per tile. However, IMHO, that's a hassle, and simply using chunk-sizes 1/2 - 1/4 the screen size provides a good balance between unnecessary rendering and hassle. I'm greatly in favour of a *little* performance loss for reduced coding.

Having said all that, most modern cards are exceptionally efficient at culling geometry outside the view frustrum so the performance hit rendering an entire screen-sized chunk is going to be very minimal. On older cards the problem becomes more pronounced.


As you said, hardware vertex fail test is extremely fast, almost free if you've ever tried drawing a million triangles that don't actually get rendered. It is certainly a lot faster than having to use dynamic index buffers if you can use static ones and just offset them. The reason I use screen size chunks is you'll basically have to only use 3 DIP calls, instead of maybe 10+ if you're working with smaller strips of geometry. In my experience, the fewer DIP calls(and with more geometry per call), the faster.
-=~''^''~=-.,Wicked,.-=~''^''~=-

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.