🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Volume render empty space skip with DDA not woring

Started by
10 comments, last by Jman2 3 years, 9 months ago

Hello,

Im trying to implement an empty space skipping acceleration structure using DDA lien algorithm and an occupancy map. Its simple in theory, pre-process a volume in a 1/8th resolution map and record each 8^3 voxel regions max intensity iso value. Then in the shader sample from the coarse grid using the DDA algorithm, when you encounter an intensity greater than the threshold sample in fine detail starting from the Last t to the current T value along the ray. Theses t values are the intersection boundary points of the voxel.

My issue seems to be how to map the t values into the original volume, you would assume: p = (rayDir * lastT) but thats not correct, see below the original and the empty space skipping version.

The full source code is here:

https://github.com/James-Emmett/Rune_2

Shader specifically is here yuo may only need to look at it to spot the error…:

https://github.com/James-Emmett/Rune_2/blob/master/Demo/Assets/Shaders/Volume/PBR_Volume.hlsl

Libs because Github Removes them (stored in the External folder next to include, and version libs copied next to demo exe) are here:

https://drive.google.com/drive/folders/1DKwf6hTCpSyBMy-I7b0dJQszFeAzjS5r?usp=sharing

Original Version
Empty-Space Skipped version

If anyone spots the error please let me know i have tried everything.

Thanks

Advertisement

Update, so the P calculation is obviously incorrect its a ray so it should be calculated as:

p = rayStart + (lastT * rayDir);

Results are still bad but atleast better:

Its very blocky and crashes compared to the non ES version but its progress.

You can try RenderDoc (or some other gpu debugger) to see why you get funky results. It allows you to “capture” a frame and debug the pixel shader that was run to color any fragment.

Maybe I'll add some suggestions based on problems I encountered when implementing dda for volume rendering:

  • Make sure you hit all samples when moving to the next “block”: skipping or duplicating a sample can seriously impact the final result.
  • There are all kinds of corner cases where code that involves rounding/floor/ceil can result in the incorrect block index.
  • It seems as if you are simply thresholding on the maximum intensity value for empty space skipping. Depending on your transfer function, that could be incorrect. You would have to precompute the effect of the transfer function on each block before you can decide whether it can be skipped (eg. skip the block, if the maximum alpha value of the transfer function being applied to all voxels in the block is below a threshold).

I'll repeat that RenderDoc has been an invaluable tool in fixing these issues ?

@Koen Yes renderDoc is a fantastic tool been using it a while, unfortunately with these things it tends to be a tiny little error that stares you in the face for days on end.

Regarding the simple threshold the idea is to eventually use the transfer alpha min and max, im working up towards it but had little errors in the way. The final empty space skip is based on the paper here: https://www.researchgate.net/publication/337304568_Accelerated_Volume_Rendering_with_Chebyshev_Distance_Maps

So you bake out a chrbyshev distance map to leap multiple empty spaces at a time.

I think the main issues atm are:

  1. I need to correctly get how many samples to take between lastT and t as they vary in size depending on angle of intersection.
  2. Exit the volume in a consistent manner, this should be checking we have hit the last voxel

At the moment im replacing the ray box intersection with a two pass method(render front back faces and calculate ray that way) too see if the ray box intersect has the error.

@Koen Just another follow up, ive printed but the ray.Start and ray.End colors out using the traditional multi-pass method (render local positions front and back to render target reconstruct direction in shader). And it too has this odd error, at least its visual, see what you make of it.

Ray Start
Ray End
Ray Direction

As you can see the direction has kind of a precision error? looks like z fighting kind of not sure why its occurring though, id expect some smoother transition than lots of spikes.

Thanks, for your suggestions so far.

Jman2 said:

  1. I need to correctly get how many samples to take between lastT and t as they vary in size depending on angle of intersection.

You should be able to get the world space position of both start and end points of each ray. If your proxy-cube has been parameterized as uvw (so position going from [0,0,0] to [1,1,1]) you can calculate the real world space positions as

float3 realPosition = lerp(proxyCubeMin, proxyCubeMax, uvw)

From there you can calculate the ray's world space length and calculate how many samples N that ray needs from the desired sample distance.

I parameterized the ray with t, where t is normalized to be in [0,1] for the entire ray. You want N samples, so the loop increment for t becomes

float ray_length = length(end_point - start_point);
float N = ray_length / sample_distance;
float delta_t = 1.0 / max(0.00001, N);

This way you can use the same t and delta_t regardless of whether you are working in uvw (volume texture) space or real world space

Jman2 said:

2. Exit the volume in a consistent manner, this should be checking we have hit the last voxel

If you make sure you parameterize the ray with a normalized t going from 0 to 1 this becomes trivial: stop when t reaches 1.

Jman2 said:

As you can see the direction has kind of a precision error? looks like z fighting kind of not sure why its occurring though, id expect some smoother transition than lots of spikes.

Are you using a floating point format texture to store these values? Are you normalizing the direction before writing it out?

@Koen No too the floating point, however i have made some progress and swapped back to the raybox intersection method (mostly so its single pass). What im doing now is calculating the steps required to process the entire volume or inner voxel region if there is a voxel ≥ threshold. Basically take the length of the end - start, yes 2 length calculations isnt “great” but it at least looks better. However you can see the ear is missing and certain parts get clipped out but as you move they may reappear.

Also if you change the iso threshold it looks like garbage now (note ill fix over saturation one day…:

Garbage Empty Space Skip Version
Original

Edit:

I didnt see your first comment, i did the exact same thing well near enough!

All I can say is: run a debugger and check why a block that shouldn't be skipped is skipped anyway. Debugging this is not trivial, but if you try generating a “simple” case, it is definitely possible! ie. use a small data set and a simple axis-aligned view direction, so you can more easily track where in the data set and empty-space-skipping volume your ray marching loop should be, how it moves from one block to the next, and why blocks are being skipped.

I really think small corner cases (incorrect indices or iteration steps caused by casts, ceil, floor , NaNs,…) in the dda and ray marching iteration are messing up your results. It is also possible that the blocks that currently look more or less correct (so the center of the skull in your pics), could still be completely wrong: because of spatial coherency in the emtpy-space-skipping data, testing one of the neighbour blocks is still quite likely to give a correct result.

This topic is closed to new replies.

Advertisement