Blood in a top-down shooter

Started by
7 comments, last by Strewya 9 years, 4 months ago

I wasn't sure if this should go into a graphics section or game programming, so a flip of a coin decided to put it into general.

I'm working on a top down shooter game, and i'm trying to get blood splatter to be drawn at the locations where monsters get killed. The blood should stay on the ground forever, essentially covering the ground entirely after enough time has passed and enough monsters died. The camera is focused on the character (camera is z=-50, game stuff is at z=0), and the map itself is slightly bigger than what the camera sees, so it scrolls a bit until it reaches the edge of the playable area where it stops following the character on that axis.

My idea was to draw all the blood that was spawned in the current frame to a texture, and then draw that texture with alpha blending enabled after the background image, so it would be like a transparent blood-filled layer over the background image.

I can draw all the blood so it looks as i want it if i keep all the blood splatter information (world pos and a sprite index) in a vector, and draw them after the background. Image here. However, this is not going to work in the long run, seeing i want to have the ground covered in blood after a while.

If i set the render target to be a texture, draw all the blood generated in the current frame onto the texture, then setting the render target back to the backbuffer and drawing the background and then the texture over it, i get a weird result where the blood doesn't get spawned at the location the monster was at (problem 1) and the new blood overwrites the previously drawn blood (problem 2). Image before monster death and image after, both issues can be seen.
I'm probably missunderstanding how the render to texture process really works, which is causing my issues.

First off, is this a good way to do blood at all? I've read about decals a bit today, and am not sure if that's the way i should be going for, or if i can get decent results with this.
If this approach might work, then any ideas on how to setup this flow to get good results?

Some info.
Backbuffer is 1200x900, background image is 1200x900, drawn on a quad bigger than what the camera sees so the camera can scroll with the character, and i tried making the texture render target a wide array of values, with the results all being the same.
My transparency blend state is initialized with:


D3D11_BLEND_DESC blendDesc;
ZeroMemory(&blendDesc, sizeof(D3D11_BLEND_DESC));

blendDesc.AlphaToCoverageEnable = false;

D3D11_RENDER_TARGET_BLEND_DESC& rtbd = blendDesc.RenderTarget[0];
rtbd.BlendEnable = true;
rtbd.SrcBlend = D3D11_BLEND_SRC_ALPHA;
rtbd.DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
rtbd.BlendOp = D3D11_BLEND_OP_ADD;
rtbd.SrcBlendAlpha = D3D11_BLEND_ONE;
rtbd.DestBlendAlpha = D3D11_BLEND_ZERO;
rtbd.BlendOpAlpha = D3D11_BLEND_OP_ADD;
rtbd.RenderTargetWriteMask = D3D10_COLOR_WRITE_ENABLE_ALL;

HRESULT hr = m_dev->CreateBlendState(&blendDesc, &m_transparency);


I appreciate any help, suggestions and critique! Thanks.

devstropo.blogspot.com - Random stuff about my gamedev hobby

Advertisement

At its basic level what you're talking about is a decal system, which is sounds like you've already figured out, but its used for all kinds of things like graffiti and bullet-holes. In many games, these effects eventually fade, and are typically limited to some maximum amount for the same reason you are encountering -- eventually, you just can't keep everything around if you render it every frame -- or rather, you can, provided you have budgeted sufficiently for it, but which might not be practical to maintain at your desired framerate/art quality/number of objects.

If you really want to maintain it on a massive scale, what you probably want is something closer to a very large virtual texture map system, as was used in Rage/idTech5 -- basically, you have a huge logical texture that's made up of many smaller physical textures (as in, a texture in ram on the GPU) and the textures are essentially a very large unwrapping of level geometry, then, you spatter into this texture. Those textures are used to overlay the spattering when a) the area corresponding to that physical texture is visible, and b) when there's spatter or other texels drawn into it.

Of course, this consumes memory and requires some work to perform the splatter (mapping back to the unwrapped faces) but after that its largely fire-and-forget, and your budgetary unit is now a whole section of geometry and all its splatters, instead of each individual splatter.

I don't know whether thats the most efficient way to do things, but I think it would be a reasonable solution. New GPUs and 3D APIs even have direct support for these kinds of virtual texturing schemes. You could, of course, adopt virtual texturing whole hog, using it for all your texture data, and then draw your spatters directly into it too, which would save you the overhead of additional textures.

throw table_exception("(? ???)? ? ???");


(problem 1) and the new blood overwrites the previously drawn blood (problem 2). Image before monster death and image after, both issues can be seen.
I'm probably missunderstanding how the render to texture process really works, which is causing my issues.

For problem 2, it looks like you just haven't set the alpha blending states properly when drawing the blood to your rendertarget. I think the problem might be this:


rtbd.SrcBlendAlpha = D3D11_BLEND_ONE;
rtbd.DestBlendAlpha = D3D11_BLEND_ZERO;

You're basically ignoring the alpha value that's already in your rendertarget (so when drawing a new patch of blood, the transparent areas of that sprite will now "erase" any blood already there). You might try D3D11_BLEND_INV_SRC_ALPHA for DestBlendAlpha too. (It's possible you might need to use pre-multiplied alpha everywhere in order for this to work properly though - not sure, I'd need to think about it more).

As for problem 1, I'm not sure. It just sounds like you're drawing it in the wrong place :P.


First off, is this a good way to do blood at all? I've read about decals a bit today, and am not sure if that's the way i should be going for, or if i can get decent results with this.

If you want the blood patches to stay forever, what you're doing now seems like a reasonable way of doing it.

@Ravyne: that seems way over my head for now, and although it sounds logical to me, i don't yet have the knowledge to pull it off in time for my decided time constraint. But thanks for pointing it out, i'll probably take a deeper look at it at some point.


You're basically ignoring the alpha value that's already in your rendertarget (so when drawing a new patch of blood, the transparent areas of that sprite will now "erase" any blood already there). You might try D3D11_BLEND_INV_SRC_ALPHA for DestBlendAlpha too. (It's possible you might need to use pre-multiplied alpha everywhere in order for this to work properly though - not sure, I'd need to think about it more).

Thank you, this fixed it :)

As far as the positions go, i'm thinking it might be something to do with scaling the texture from being a render target to using it as a texture for a quad that covers more than what the camera can see. I'm guessing i need to scale the world positions from being outside of the camera view, to being purely inside, then rendering the splatter onto the texture. Just have to figure out how to do that.

devstropo.blogspot.com - Random stuff about my gamedev hobby

I found a solution to the incorrect coordinates :)

The code is below, if anyone is interested. Basically, the idea is to first translate the world position from (-halfSize, halfSize) to (0,1), then multiply that by the window/viewport size, and calculate the world position from that screen position. The texture dimension needs to be the same as the backbuffer in order for this to work, at least i think.


for(auto splatterPosition : splatters)
{
	splatterPosition.y = -splatterPosition.y;
	//(-halfSize, halfSize) -> (0, 2*halfSize)
	splatterPosition += game.m_playingField.halfSize();
	//(0, 2*halfSize) -> (0, 1)
	splatterPosition /= (2 * game.m_playingField.halfSize());
	//(0, 1) -> (0, windowSize)
	splatterPosition *= {(float)game.m_window->getSizeX(), (float)game.m_window->getSizeY()};
	//(0, windowSize) -> worldPos
	splatterPosition = game.m_graphicsSystem.screenToWorld(splatterPosition, game.m_camera);
	game.m_splatters.emplace_back(splatterPosition, splatterImageIndex);
}

devstropo.blogspot.com - Random stuff about my gamedev hobby

Could you not just store your decals similar to how you store the rest of anything in your game... ie using some sort of partitioning system and only render that which is actually in view? You don't render every object in your game every frame... god I hope you don't... so why does every blood spatter have to be rendered every frame... when it's not in view.

Also could you not setup some sort of bounding box or something to denote the area of a splatter so if a new splatter can be discarded if it occupies the same space?


Could you not just store your decals similar to how you store the rest of anything in your game

I could, but then the vector which holds these would grow indefinitely, and impact performance (as it does on one of my slower machines where i try the game).


ie using some sort of partitioning system and only render that which is actually in view

There is very little map space that isn't in view. Maybe around 20 percent of the map isn't visible. I would just waste time implementing a partitioning system and making sure it all works instead of rendering everything.


You don't render every object in your game every frame... god I hope you don't...

Yes i do. It's a small game, no more than 150 objects cumulative, at least right now. I simply don't need a more complex system when a very simple one suffices.


so why does every blood spatter have to be rendered every frame... when it's not in view.

Well, in my current solution, it's rendered only once to the texture, and another time to the screen, with all other blood splatters which includes correct ordering. It was very simple to implement once i got the math right.


Also could you not setup some sort of bounding box or something to denote the area of a splatter so if a new splatter can be discarded if it occupies the same space?

This wouldn't give me the result i wanted. If you look at the first linked screenshot, you'll see what i want. Discarding blood splatters (which are all differently shaped) would not give a nice looking effect in the end.

I like simplicity in my code, and i try doing it as far as i can until i prove to myself through struggling with the current code that i need something more complex. :) All of your comments are perfectly applicable if i were making a larger game, but this is a very simple game (based on Crimsonland, if you've heard of it), and so i need simple code that works as soon as i type it (modulo bugs).

devstropo.blogspot.com - Random stuff about my gamedev hobby

Ah I totally miss read most your post, must've been more tired than I thought last night. I didn't realize everything was pretty much contained to one screen space.... are you even experiencing a performance hit at all with so little going on in your screen, even if everyone of those blood spatters times 3 were drawn individually?

Yes rendering the splatter to a separate render target and just not clearing it, sounds perfectly reasonable if your play area is that small.

Ah I totally miss read most your post, must've been more tired than I thought last night. I didn't realize everything was pretty much contained to one screen space.... are you even experiencing a performance hit at all with so little going on in your screen, even if everyone of those blood spatters times 3 were drawn individually?

Yes rendering the splatter to a separate render target and just not clearing it, sounds perfectly reasonable if your play area is that small.

I've experienced performance drops only when debugging from visual studio on my low end machine. If i run without a debugger attached, it runs fine, but i'd wager it's just a question of time before it starts slowing down from having to draw a couple of thousand blood splatters, each with it's own draw call (because i just haven't had the need to optimize draw call count yet).

I'm in a semi prototype phase right now, adding some graphics because i was tired of watching black and red circles shooting at each other, and this was part of adding certain graphical effects i know i want in the final thing. In the final thing, i'll want a bit more than 150 objects though, with more effects per object, and at that point, if performance becomes an issue, i'll profile and optimize where needed.

devstropo.blogspot.com - Random stuff about my gamedev hobby

This topic is closed to new replies.

Advertisement