Sign in to follow this  

Efficient rendering of a dynamic grid

This topic is 818 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I'm trying to render a grid similar to Starcraft 2's build grid. I want each tile to change its color as efficiently as possible using a single shader for the whole grid.

 

OmFT6.jpg

 

 

On the CPU side, I have a byte[] (C#) which contain the state of each tile (free, occupied, conflict, etc.). These states change each time the player moves a building over the grid or builds it. My idea right now is to send this data to the GPU through a StructuredBuffer, submit a procedural drawcall, and somehow change the color of the tiles based on the data and the vertexID.

 

I have two potential problems with this:

  • There is no 8 bit scalar type in HLSL (like byte). Should I convert my byte[] to a uint[] and pass that to the buffer instead? I don't like the idea of having a duplicate of my data array. Is there a way to "fake" an 8 bit scalar in HLSL?
  • I'm using Unity, and the only way to update a buffer on the CPU side is to set the entire array of data. This could be done frequently (each time the player moves the building to another position). I wonder if there is a more GPU-centric approach to fetch the state of each tile in my grid.

I know this all sounds like premature optimization, but my main goal here is to learn new ways to think about these kinds of problems.

 

Thanks for any ideas.

Share this post


Link to post
Share on other sites
Instead of a structured buffer, can you can use a "regular" buffer with DXGI_FORMAT_R8_UINT? If you use this, then it will work in the shader just as if it were a byte[] array, except that during the load the 8-bit value will be cast to a 32-bit uint (which is fine). If you can't do that with Unity, then you can still do it with a structured buffer. You'll just need to load 4 bytes at a time, and mask and shift the uint result in order to get the byte that you're looking for. Like this:

uint elemIdx = byteIdx / 4;
uint bufferValue = GridBuffer[elemIdx];
uint gridValue = (bufferValue >> (byteIdx % 4)) & 0xFF;
As for updating the buffer, how big is the buffer that you're updating? The drivers are definitely optimized for the case of updating GPU resources from the CPU, and it shouldn't be an issue unless your buffer is really big. Depending on how many tile states you have, you could also possibly reduce your memory requirements by packing your tile state into less than 8 bits.

Share this post


Link to post
Share on other sites

This is way I would look at this, assuming you have your world grid evenly spaced across the map, and the state of each cell flagged as open or closed and the info for whether the current structure can be placed there (refinery) and whether its blocked by a unit, what you really want to draw is an WxH planar grid mesh of quads spaced the same as the world grid, which you can generate once at load time. Each quad would have its own 0.0 -> 1.0 texture space, to place the little faded corner texture on, and you could just change the vertex colors of the cells (quads) to match their state (yellow = neutral, green = ok, red = blocked), the whole thing would then look like a simple blended mesh draw, where you update the vertex colors when needed (when the grid is visible).

 

So you wouldnt exactly draw the cells as you have them stored in the world grid. You just draw a planar grid over top, blended, textured, with vertex colors matching the state based on the selected building/structure and your game logic.

Share this post


Link to post
Share on other sites

@MJP The buffer would typically be 100x100 elements. I'll try packing the states in a uint[], with 16 2-bit states per uint. Then I'll send that to the StructuredBuffer.

 

@NumberXaero Correct me if I'm wrong, but I don't see how storing and updating vertex colors would be more efficient than sending states as a bye[] in a buffer. In both cases I need to update a buffer, which would be larger if I choose vertex colors.

Share this post


Link to post
Share on other sites

You are doing this completely different from what I would do. If you know the size of that projected decal in world space, you can create a projection matrix and modelview matrix much like any shadow mapping or projective texturing.  Then just render to another texture those surrounding terrain cells as a bunch of quads in the texture. Apply that overlay texture that has those "+" shapes in that decal you see.  Then just apply the decal as projective texturing.  Why you would need to update vertex colors and write a shader etc is not only going to be much more of a pain, but also you can see in SC2 they are doing it my way because you have those "+" grids, which are definitely in a texture. You can't get those shapes from a vertex color.

Share this post


Link to post
Share on other sites

@dpadam450 I think we're not talking about the same thing here. My question is not about the appearance of the grid. In fact, I do have a texture with the + shape and everything. What I need is an efficient way to send each tile's state (green, yellow, red) to the shader and THEN use this for shading. Or maybe I misunderstood your solution?

 

Edit: Right now I do have the same exact grid as SC2 visually. What I don't have are the tile colors matching the states (free, obstructed, etc).

Edited by jesta

Share this post


Link to post
Share on other sites

If you want to have a color overlay I would just render the grid separately after you have already rendered the ground. To render the grid, I would just have one repeating texture for the tile and then either use vertex colors or a low res texture to define what each tile color should be.

Share this post


Link to post
Share on other sites

I ended up following MJP's advice, but with very tight packing inside a uint. This creates a very small buffer at the cost of code complexity (lots of bitwise operations). I'll benchmark my implementation against a more naive approach using uints for states to see if there are real benefits with packing.

 

Here's a glimpse of the vertex shader code to unpack the uint buffer:

 

3300c6aeb65ccbb495013e8366ea2578.png

 

Benchmark results to come.

Share this post


Link to post
Share on other sites

Well then you are doing this badly/inefficiently.

If you are already projective texturing. I was suggested not manipulating vertex data and modulating that in the shader, I was saying create a new texture/FBO the size of your "+" texture. Grab the closest 10x10 tiles or whatever, do a for loop drawing green/red quads. Then draw your "+" texture overlayed on top of those red/green quads.  Then just do your projective texturing that you said you already have. Case closed.

Why do you need to send /manipulate vertex data. Are you multiplying all vertices by "0" for all the squares that are outside of the grid? It all seems like a waste if you already have the projective grid texture.

Edited by dpadam450

Share this post


Link to post
Share on other sites

Grab the closest 10x10 tiles or whatever, do a for loop drawing green/red quads.

 

That's the part I don't understand. My grid can be up to 200x200 visible tiles. Are you suggesting I do 40k draw calls?

 

I'm not trying to challenge your idea here, I just genuinely want to understand your approach.

Share this post


Link to post
Share on other sites

Well this is my idea, may or may not be faster, definitely seems a bit lighter as it doesn't modify any terrain data. It is it's own system:

1.) Create the + grid texture (offline, done in photoshop). Load it in. In SC2 this texture represents 10x10 tiles.

2.) Create an FBO the same size as the + grid texture.

3.) Put mouse in the world, calculate its 2D grid position, grab the 5 tiles left, 5 right etc LOGICALLY (complete cpu side).

4.) Load FBO, do a for loop i = 10, j = 10, draw 100 quads based on the logical can or cant walk.

 ---> to optimize just have a VBO containing 100 quads and update all vertex colors in one update call (instead of updating your other VBO which since vertices in this case aren't consecutive, I assume you have to call re-upload buffer about 10 times.......or are you literally updating the ENTIRE buffer? I hope not).

 

5.) At this point we have updated the color array VBO for just 100 "fake" cells (not all 100x100). We have called draw on this array. So our FBO holds a small 2d image of green and red tiles, a top down image of them.)

6.) In the same VBO draw your + grid texture with blending or whatever. So what you have is the + grid and the colors in 1 texture. Then we want to stamp that top down right onto the terrain. So we use projective texturing. You have the mouse position on the terrain, so you can use projective texturing. The terrain didn't know any extra data, just that an image was projected on it (which it sounds like you already have).

If you don't understand then, oh well, sounds like your way works just fine. And I can't explain it any better. You create a stamp with all that, and stamp it on the terrain via projective texturing, instead of the vertex knowing it has a color.
 

Edited by dpadam450

Share this post


Link to post
Share on other sites

This topic is 818 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this