Search Unity

Voxel based damage Model - VoidBorn

Discussion in 'General Discussion' started by Universator, May 17, 2020.

  1. Universator

    Universator

    Joined:
    Mar 21, 2018
    Posts:
    5
    Hi guys,

    For my game VoidBorn I'm working on a damage model that utilizes voxels to handle damage calculation.
    I believe that voxels provide an easy way to make vehicles buildable by players and still fun like Lego.

    I've come so far that I have a working damage model already.
    I can display voxels that are damaged and can even show the degree of damage to the player.
    It already works in some way but still feels a bit off and not really intuitive.
    Hopefully someone around here can give me a good hint how I can solve this.

    My model is based on 4 properties: ImpactDamage, AreaDamage, AreaSpread and Penetration.
    Imagine a Fireball that has 0 Impact Damage, 100 AreaDamage, 3 AreaSpread and 0 Penetration.
    This is the projectile I tested on my spaceship here (yes I tried myself on a ship from Homeworld 2)

    Before:
    Before.JPG

    After:
    After.JPG

    As you can see the damage to the ship is easily visible.
    A voxel can have a damage state from 0-100.
    Every voxel can also have specific resistances towards the damage properties.
    Overall I classify voxels into structure and system voxels.
    A structure voxel has no other purpose than to protect the system voxels.
    System voxels provide the actual functionality of the ship.
    Usually a structure voxel has higher hitpoints than a system voxel and some for of resistance against either penetration oder area damage.
    In the given example the ship is covered with a layer of heavy armor voxel.
    On top (the blue patches) are layers of ablative armor.

    Heavy Armor is supposed to provide good protection against every kind of damage at the cost of high density. The ablative armor is meant to soke up area damage and protect surrounding voxels from explosions.
    This is implemented by giving the heavy armor voxel an AreaSpread Reduction of 1, high HP, Penetration reduction of 3 and Impact resistance of 50%.
    The ablative armor has an AreaSpread Reduction of 3, low HP, Penetration reduction 1 and Impact resistance 20%.

    Assume that every system voxel has no reduction other than the basic Penetration reduction of 1.

    Although I only aimed for voxels on the outside of the ship, a lot of system voxels get destroyed early by the fire ball.

    AfterInner.JPG

    The issue in my test is that the fireball is not really stopped by the heavy armor.
    It's explosion gets dampened, yes, but the voxels behind it are still affected by a lot.
    And even though the armor voxel is nearly not damaged the system voxels behind it are heavily damaged or even destroyed.
    I might do a bit of number juggling here but I actually would like to find a fast and easy way to simulate an actual explosion that gets blocked.
    If a voxel completely absorbs the explosion the voxels behind it might be saved but the voxels around look awkwardly untouched.
    What I am looking for is something like this:
    DamageDiscussion-area-ideal.jpg

    The most direct approach would probably be to iterate over all voxels in range and check the line there to see if it is blocked.
    For an explosion with range 3 this would mean to raytrace for 7x7x7 = 343 rays.
    Raytracing in voxels can be really easy with the Bresenham Line algorithm, but I have the feeling I am missing something here.

    Hope you can help or inspire me, I really appreciate any idea here.
     

    Attached Files:

    Last edited: May 17, 2020
  2. Universator

    Universator

    Joined:
    Mar 21, 2018
    Posts:
    5
    As I fail to include images from IMGUR, this post will add further explanation to the damage model I used.

    Penetration Model
    The penetration damage is actually very easily implemented.
    A projectile hits a voxel and I find every voxel on the trajectory line through the ship.
    Then I iterate over the voxels in the line and deal ImpactDamage proportional to the amount of penetration power left to them.
    Everytime I handle voxel I subtract it's Penetration Reduction from the left over penetration power.

    A Penetration of 5 hits 5 voxels in a row with each voxel having a reduction of 1.
    Each consecutive voxel receives power/Penetration * ImpactDamage as ImpactDamage.
    So for a projectile with 100 ImpactDamage and Pentration 5 it looks like this:
    DamageDiscussion-pen.jpg

    If the first voxel has a Penetration Reduction of 2 it would look like this.
    DamageDiscussion-pen-damp.jpg

    For me this looks intuitive enough, but I'm open to improvement.

    Explosion Model
    The explosion model is based on the idea that the initial voxel absorbs the explosion to some degree and prevents damage to systems behind it.
    I have prepared static tables to avoid the calculation distance to the source with square roots as I found that to be very slowly.
    They look a bit like this.
    DamageTable.JPG

    I use those tables to traverse space around a point and deal proportional damage.

    Without absorption:
    DamageDiscussion-area.jpg

    With Absorption of 1:
    DamageDiscussion-area-damp.jpg
     
  3. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,566
    Actually you shouldn't test all of them.

    If you assume that the point of explosion is exactly a point, then...

    upload_2020-5-17_17-50-5.png
    It is a shadowcasting problem.

    Nearby voxel, if it exists, forms a "shadow volume" formed by 4 planes.created the sides of voxel face.
    upload_2020-5-17_17-53-2.png
    Voxel will take damage from explosion only if it doesn't completely lie within the volume.

    For AABB or even OBB "is it within volume" test for a box is fairly simple, as it involves checking if the box is "below" all 4 planes.

    This would allow you to exclude large portion of the voxel array from calculation by verifying six neighboring faces for being "explosion blocker". If you play it clever you won't even need to verify if individual voxels are within the volume, you should be able to find the ones that are partially within, and fill those between those as "shielded".

    upload_2020-5-17_18-0-15.png
    If the explosion is performed at dead center of a voxel, then visibility can be fully precomputed, stored in an array and save you some CPU time.

    Also, I wouldn't use brezenham for anything and would try to walk ray through the grid instead.
     
    Martin_H and Universator like this.
  4. Universator

    Universator

    Joined:
    Mar 21, 2018
    Posts:
    5
    Oh wow, you got me really excited here! :D

    The shadowing is exactly what I need. As voxels should be relatively small compared to impact influence and an uneven impact point leads to awkward effects with my damage maps, I can certainly see the impact point rounded to next voxel center and use the precomputed arrays.


    And actually I use the Bresenham algorithm to walk the rays through the voxels. I just use the version where you check every voxel it traverses instead of those closest to the line. If that is not what you mean maybe you can point me into another direction. I would appreciate any performance improvement I can get.

    Thanks a lot for that suggestion!
     
  5. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,566
    Well, shadowing is relatively easy to implement, as you basically need to form 4 plane equations, where plane will be formed by two vectors - edge of the voxel face, and direction from the point of origin towards the edge:
    upload_2020-5-17_18-57-13.png
    Plane-box overlap test is fairly simple, it is calculating minimum and maximum "distance" between box and plane, where..
    Code (csharp):
    1.  
    2. float minDist = Vector3.Dot(boxCenter - planePoint, planeNormal)
    3.    - Mathf.Abs(Vector3.Dot(boxXAxis * xSize * 0.5, planeNormal))
    4.    - Mathf.Abs(Vector3.Dot(boxYAxis * ySize * 0.5, planeNormal))
    5.    - Mathf.Abs(Vector3.Dot(boxZAxis * zSize * 0.5, planeNormal));
    6. float maxDist = Vector3.Dot(boxCenter - planePoint, planeNormal)
    7.    + Mathf.Abs(Vector3.Dot(boxXAxis * xSize * 0.5, planeNormal))
    8.    + Mathf.Abs(Vector3.Dot(boxYAxis * ySize * 0.5, planeNormal))
    9.    + Mathf.Abs(Vector3.Dot(boxZAxis * zSize * 0.5, planeNormal));
    10.  
    As you can see there are many repeating values, and if the box is axis aligned, many things can be optimized away.

    If both min and max distance are greater than zero, then the box is above the plane.
    If they're both less than zero, then the box is below the plane.
    If one is greater than zero, and other is less than zero, then the plane slices through the box.

    In case of shadow volumes cast by convex objects, the object is completely in the shadow, if for all shadow planes, min and max distances are below zero.

    -----------

    Regarding brezenham, on second though, I think it might be actually fine.

    Long time ago I implemented voxel walker pixel shader, and that operated on floats, however thinking about it, brezenham should produce exact same sequence in the end.
     
    Universator likes this.
  6. Universator

    Universator

    Joined:
    Mar 21, 2018
    Posts:
    5
    Oh hey thanks again, I was already scribbling on the formulas and would arrive eventually at your solution :rolleyes:

    I am now thinking of a fast solution on how to reuse the results for voxels farther away.
    So if the we have a shadow result from the first layer I would like to find a method to determine shadowing results for consecutive layers.

    DamageDiscussion-Shadow-0.jpg
    In this case we find that 1 is blocking and therefore I would like to reuse this result for layers 2 and 3.

    DamageDiscussion-Shadow-1.jpg
    The second image shows that we found B to be blocking and now I would like to look up if 1 is blocked by B.
    I have the feeling there is also an easy solution to the data structure. Any idea?
     
  7. Little_Turtle

    Little_Turtle

    Joined:
    Jan 29, 2017
    Posts:
    9
    Four little sentences:

    1. You can see the explosion as the pressure that it has at a point on a sphere with radius x.
    2. You can project each voxel with a certain offset from point of the explosion onto this sphere (project each point of the voxel against it but since you are doing a game, using a rectangle (or even more simpler a circle) with a similar area onto the sphere would be enough.
    3. Decrease the pressure on every point of that explosion sphere that 'hits' this 'cube area' to 0.
    4. Proceed the cubes one by one based on the offset distance.

    ---
    You can store for the distance vector the position of the rectangle on the sphere (or if you use a circle the center based on the normalized direction of the voxel center point and the radius based on the distance from the point of explosion and the voxels center).

    Remember that you have rotations and 8 quadrants in 3d space. Basically your point of impact / explosion center is the origin of the explosion space.

    Since you are dealing with a sphere, the sphere itself allows you to use the same calculation to get the x projection onto the sphere as needed for the y projection.

    You can store the iteration of the different voxel offsets you should iterate one by one. Again this iteration can be reused for different quadrants and can be compressed using x,y similarities.

    With this system you should a very accurate explosion simulation relying only on tables with almost no complex math to solve on runtime.
     
  8. Universator

    Universator

    Joined:
    Mar 21, 2018
    Posts:
    5
    Oh that sounds exciting aswell. But how do I store these Information while calculating. Do you recommend a sperical texture for that? That sounds very precise yet also very performance expensive. Could you elaborate a bit more on that?

    Do you mean I just collect spherical coordinates together with a radius for every cube that blocks an explosion? Then I would have to compare the projection of the of a voxel in question with all the projections I have found so far to check whether the current projection is overlapped by any of the blocking projections.
    That sounds a lot like neginfinity's suggestion, Maybe a bit faster for the approximation with the radius.
    But I have the feeling my voxels are a bit too big compared to the effects intend to use so that this approximation might lead to unexpected behaviour for the player.
    Nonetheless I definitly might try that out.


    The approach I would go first is I set up look up tables for every voxel around a center.
    For every voxel around the center I keep a list of voxel locations that might block it by a factor.
    These values can be precalculated by either the algorithm negfinity suggested or the system Little_Turtle suggested.
    I will try both for the sake of completeness :D
    Then for an explosion of radius X I prepare a boolean array of [2X+1,2X+1,2X+1] with all false.
    Then I walk my look up tables along and look for every voxel and set the array[X,Y,Z] to
    (voxel != empty && voxel.hp - accumulatedExplosionPressure > 0)
    Where accumulatedExplosionPressure is the explosion pressure at x|y|z relative to the shadowing value sum.

    Now the tricky part: how do I calculate the amount of shadow a voxel receives?
    I find it hard to grasp whether the plane method negfinity described says anything about coverage. And how would I calculate the some of all coverage? The same actually for Little_Turtle's method.
    Am I overthinking this here and a simple full cover check would suffice?
     
  9. Little_Turtle

    Little_Turtle

    Joined:
    Jan 29, 2017
    Posts:
    9
    Its more like a buffer. In the end you should use a simple Array for this (at least I would do). Since you use rectangles on this area (or a circle), you simply sum up all the pressure values and have the aprox amount of pressure (=devestation) the voxel receives. After that zero out all the values.

    Nope. You do not hold a list. It is like a shadow map you play out. You create an array of lets say 32x32 and only work with that array. You should not try to be too clever and work with polygons or triangles and calculating the pressure on the whole face and stuff. You can do it to be ultra-realistic in some sorts. If you really go ultra-realistic, just think about water simulation using particles. You can understand an explosion like a multi bullet shotgun shot and simply emit particles circular that are interacting with your geometry. At some distance just split the particles. You could also think about raytracing using ray bundles for this. But for a game this would all be too labor intensive and computation heavy (depending on the number of explosions per second you want to support).

    Depending on the radius / distance of the voxel you simply increase the array by scaling it up by times two. So for near voxels you take 32x32 (or even lower) at distance X you go 64x64 and distance z you go 128x128. And again remember you can use quadrants which are distinct and do not overlap/interact. This way you can solve the problem pro quadrant and use a transformation of the voxel positions to map the same orientation. This will save a lot of memory.

    Using a binary array is also good but if you need to increase the resolution instead of going 'Antialiasing' you need to scale up the array even more. Do not know if it is worth. Just test it. With doing 'gray' area stuff (e.g. covers 50% of that pressure buffer 'pixel', lets remove only have the pressure then...). But this is an optimization so it depends... try it out.

    Since this is similar to the question how much 'light' of the explosion lits this voxel face, you should be able to find all the math you need for your pre-calculation tables.

    Since you can tweak resolution of your pressure map you should be able to get a fast convincing system going with this.