It is currently Sat Aug 22, 2020 4:27 am


All times are UTC




Post new topic Reply to topic  [ 13 posts ]  Go to page 1, 2  Next
Author Message
 Post subject: How to use LowPassFilter
PostPosted: Tue Aug 20, 2013 7:29 pm 

Joined: Tue Aug 20, 2013 6:57 pm
Posts: 3
Hello, I recently began looking into PolyVox as means to allow 3D terrain editing in my game engine. I've gone through the example projects that come with the distribution and generally understand what is going on.

I noticed that the marching cubes mesh generation produces a stepped, blocky looking mesh. Which, from my understanding of marching cubes, is to be expected. Looking through the source code for the OpenGL example I ran across a commented out section of code:
Code:
//I've removed this smoothing because it doesn't really make sense to apply a low pass filter to a volume with material values.
//I could implement the mathematical operators for MaterialDensityPair in such a way that they ignores the materials but this
//seems to be setting a bad example. Users can add this operators in their own classes if they want smoothing.
//RawVolume<MaterialDensityPair44> tempVolume(PolyVox::Region(0,0,0,128, 128, 128));
//LowPassFilter< LargeVolume<MaterialDensityPair44>, RawVolume<MaterialDensityPair44> > pass1(&volData, PolyVox::Region(Vector3DInt32(62, 62, 62), Vector3DInt32(126, 126, 126)), &tempVolume, PolyVox::Region(Vector3DInt32(62, 62, 62), Vector3DInt32(126, 126, 126)), 3);
//pass1.executeSAT();
//LowPassFilter< RawVolume<MaterialDensityPair44>, LargeVolume<MaterialDensityPair44> > pass2(&tempVolume, PolyVox::Region(Vector3DInt32(62, 62, 62), Vector3DInt32(126, 126, 126)), &volData, PolyVox::Region(Vector3DInt32(62, 62, 62), Vector3DInt32(126, 126, 126)), 3);
//pass2.executeSAT();

Which seems to be the solution to the blocky mesh problem. Uncommenting the code resulted in some compiler errors because LowPassFilter as been updated since the code was originally commented out. Namely, LowPassFilter now requires an accumulation type, which from my deductive work means it needs a Density<T> type. I tried giving it both Density8 and Density16, but in both cases got a compiler error basically saying that it can't convert MaterialDensityPair44 to Density8 or Density16.

Now this isn't really surprising given the comment above talking about implementing a custom MaterialDensityPair that has operators that can handle the conversion, but I've been unable to find an example that shows how that might be done and what considerations should be observed when doing so. If anyone could shed some light on how to properly use LowPassFilter, it'd be much appreciated.


Top
Offline Profile  
Reply with quote  
 Post subject: Re: How to use LowPassFilter
PostPosted: Wed Aug 21, 2013 6:50 am 
Developer
User avatar

Joined: Sun May 04, 2008 6:35 pm
Posts: 1827
Hi, you're basically right about what's going on. I suggest we start with an example which does work and then go from there. Take a look at the BasicExample, and replace the CubicSurfaceExtractor with the Marching Cubes one:

Code:
//Create a surface extractor. Comment out one of the following two lines to decide which type gets created.
//CubicSurfaceExtractorWithNormals< SimpleVolume<uint8_t> > surfaceExtractor(&volData, volData.getEnclosingRegion(), &mesh);
MarchingCubesSurfaceExtractor< SimpleVolume<uint8_t> > surfaceExtractor(&volData, volData.getEnclosingRegion(), &mesh);


This will give you a jagged mesh as you have seen yourself, but but working with a simple 'uint8_t' voxel type. We can now use the LowPassFilter after we have generated the volume:

Code:
//Create an empty volume and then place a sphere in it
SimpleVolume<uint8_t> volData(PolyVox::Region(Vector3DInt32(0,0,0), Vector3DInt32(63, 63, 63)));
createSphereInVolume(volData, 30);

// Smooth
RawVolume<uint8_t> tempVolume(volData.getEnclosingRegion());
LowPassFilter< SimpleVolume<uint8_t>, RawVolume<uint8_t>, int16_t > pass1(&volData, PolyVox::Region(volData.getEnclosingRegion()), &tempVolume, PolyVox::Region(volData.getEnclosingRegion()), 3);
pass1.executeSAT();
LowPassFilter< RawVolume<uint8_t>, SimpleVolume<uint8_t>, int16_t > pass2(&tempVolume, PolyVox::Region(volData.getEnclosingRegion()), &volData, PolyVox::Region(volData.getEnclosingRegion()), 3);
pass2.executeSAT();


Note that you need a couple more headers:

Code:
#include "PolyVoxCore/CubicSurfaceExtractorWithNormals.h"
#include "PolyVoxCore/LowPassFilter.h"
#include "PolyVoxCore/MarchingCubesSurfaceExtractor.h"
#include "PolyVoxCore/RawVolume.h"
#include "PolyVoxCore/SurfaceMesh.h"
#include "PolyVoxCore/SimpleVolume.h"


I put the full code here for you: http://pastebin.com/EShTTtBR

This should now give you a much smoother volume. It's important to note that another (probably better) approach is to generate smooth volume data in the first place, rather then generating jagged data and then smoothing it. In this example of generating a sphere you could set the voxel value to the inverse of it's distance from the center, rather than thresholding that distance and always writing 0 or 255. But sometimes the LowPassFilter is useful when you can't easily generate such smooth data.

Anyway, what goes wrong when we try to use MaterialDensityPair44? The basic problem is that there is no intuitive way to sum or average materials. If you have two materials identifiers such as 7 and 42, it does not make sense to compute the mathematical average. The resulting value may not be an integer, might not be a valid identifier, and will in no sense be the 'visual average' of the two input materials.

For this reason the MaterialDensityPair44 class does not define mathematical operators such as +, /, etc. You can see these are defined in the Density class (there may be more...):

Code:
// For densities we can supply mathematical operators which behave in an intuitive way.
// In particular the ability to add and subtract densities is important in order to
// apply an averaging filter. The ability to divide by an integer is also needed for
// this same purpose.
Density<Type>& operator+=(const Density<Type>& rhs)
{
   m_uDensity += rhs.m_uDensity;
   return *this;
}

Density<Type>& operator-=(const Density<Type>& rhs)
{
   m_uDensity -= rhs.m_uDensity;
   return *this;
}

Density<Type>& operator/=(uint32_t rhs)
{
   m_uDensity /= rhs;
   return *this;
}


You would need to implement these operators for the MaterialDensityPair44 class, and decide what they should do to material values (ignore them? take the max?). There might be others - my quick test indicated that the LowPassFilter assigns '0' at some point and expects an implicit conversion.

Anyway, I think overall you can see it's a conceptual problem rather than a PolyVox-specific one. But if you decide how it should behave then you should be able to implement the desired behaviour. Let me know if you have more questions about this.


Top
Offline Profile  
Reply with quote  
 Post subject: Re: How to use LowPassFilter
PostPosted: Wed Aug 21, 2013 4:52 pm 

Joined: Tue Aug 20, 2013 6:57 pm
Posts: 3
Thanks for the reply.

That makes sense regarding the material smoothing issue.

I tried your code and it almost worked but I was getting compiler errors in LowPassFilter.inl on line 43:
Quote:
error C2589: '(' : illegal token on right side of '::'
error C2059: syntax error : '::'

the code for the line is:
Code:
m_uKernelSize = std::max(m_uKernelSize, static_cast<uint32_t>(3)); //For release builds

I'm using PolyVox version 0.2.1 on Windows 7.

I did some googling and ran across this stackoverflow post:
http://stackoverflow.com/questions/2789481/problem-calling-stdmax
where the top answer had the solution.

I just had to change the line of code to:
Code:
m_uKernelSize = (std::max)(m_uKernelSize, static_cast<uint32_t>(3)); //For release builds

and it worked. This cross-platform gotcha is probably something to be aware of in the future.

So having successfully run this code and viewed the results it is definitely smoother. I tried out your suggestion of making the density values not be so binary and that definitely resulted in smoother results. Though, the inverse distance approach you mentioned ended up with a sphere with half the radius due to the threshold value being at 127 from my observation. When I only adjusted the values of the fringe voxels by replacing
Code:
//If the current voxel is less than 'radius' units from the center then we make it solid.
if(fDistToCenter <= fRadius)
{
   uVoxelValue = 255;
}

with
Code:
//If the current voxel is less than 'radius' units from the center then we make it solid.
if(fDistToCenter <= fRadius)
{
   float fThreshold = fRadius - fDistToCenter;
   
   if (fThreshold < 1.0f)
   {
      //Our new voxel value
      uVoxelValue = (uint8_t)ceil(fThreshold * 255);
   }
   else
   {
      uVoxelValue = 255;
   }
}

I ended up with a much smoother result that retained the same radius.

I have a couple of questions now that I have the smoothing up and working:
    Why do we perform two LowPassFilter passes instead of one?
    What's the difference between LowPassFilter::execute() and LowPassFilter::executeSAT()?
    How might I change the threshold value for a surface extractor if I wanted to customize it?


Top
Offline Profile  
Reply with quote  
 Post subject: Re: How to use LowPassFilter
PostPosted: Thu Aug 22, 2013 8:01 am 
Developer
User avatar

Joined: Sun May 04, 2008 6:35 pm
Posts: 1827
BlazeCell wrote:
I just had to change the line of code to:
Code:
m_uKernelSize = (std::max)(m_uKernelSize, static_cast<uint32_t>(3)); //For release builds

and it worked. This cross-platform gotcha is probably something to be aware of in the future.


Thanks, but that actual line of code is gone in the develop version of PolyVox and hopefully we've got all the others wrapped in brackets.

BlazeCell wrote:
Why do we perform two LowPassFilter passes instead of one?


You don't have to, it's just habit (and gives more blurring). The LowPassFilter always blurs from a source to a destination (it cannot blur in-place), so if you want the original version blurred then you have to copy the blurred result back over it. In most cases you do want the original version blurred, though in our basic example here we could have just run the surface extractor on the other volume. Doing the second blur pass (with source and destination swapped) was really just an alternative to copying the data back over to the source.

BlazeCell wrote:
What's the difference between LowPassFilter::execute() and LowPassFilter::executeSAT()?


executeSAT() is a version which makes use of Summed Area Tables (see section 8.5) to make the performance of the algorithm independent of the kernel size. Compared to execute(), it will in theory be slower for small kernels and faster for larger kernels. I don't remember how well it was running in practice.

BlazeCell wrote:
How might I change the threshold value for a surface extractor if I wanted to customize it?


You can create an instance of DefaultMarchingCubesController, set the appropriate threshold, and pass that instance to your MarchingCubesSurfaceExtractor.


Top
Offline Profile  
Reply with quote  
 Post subject: Re: How to use LowPassFilter
PostPosted: Thu Aug 22, 2013 7:03 pm 

Joined: Tue Aug 20, 2013 6:57 pm
Posts: 3
Thank you for your replies. I feel like I now have a much better grasp on what's going on.


Top
Offline Profile  
Reply with quote  
 Post subject: Re: How to use LowPassFilter
PostPosted: Fri Nov 22, 2013 10:24 am 

Joined: Fri Sep 20, 2013 10:16 am
Posts: 15
Hey, I have a question about using LowPassFilter on a MaterialDensityPair88 LargeVolume.

Would it be useful to create a temporary LargeVolume<uint8_t> and copy my Density values from my original volume inside. Then running the LowPassFilter from my LargeVolume<uint8_t> to a RawVolume<uint8_t> and afterwards replace my density in my original Volume?

In short:
LargeVolume<MaterialDensity> --> LargeVolume<uint8_t> --LowPassFilter--> RawVolume<uint8_t>
--> LargeVolume<MaterialDensity>

I am worried it would take too much time, first to extract, then using the Filter and then writing back.


Top
Offline Profile  
Reply with quote  
 Post subject: Re: How to use LowPassFilter
PostPosted: Fri Nov 22, 2013 10:50 pm 
Developer
User avatar

Joined: Sun May 04, 2008 6:35 pm
Posts: 1827
In general I don't think this is a good idea... but the question is why would you want to do this? I assume it's because the MaterialDensityPair88 cannot be used with the LowPassFilter due to the required operators being missing?

The MaterialDensityPair88 is just an example really, so if you have an idea of how you expect the filtering to behave in your particular circumstance then you are welcome to add the required operators (perhaps just ignoring the material values and only operating on the densities). The reason we don't implement this for you is because it's not clear how to filter materials in the general case, but if you have an approach which works for your scenario then you are welcome to use it.


Top
Offline Profile  
Reply with quote  
 Post subject: Re: How to use LowPassFilter
PostPosted: Sat Nov 23, 2013 8:07 am 

Joined: Fri Sep 20, 2013 10:16 am
Posts: 15
At the moment I am using the material to store my colourvalue at a specific point, so i have a value between 0 and 255 which represents an index on a colourtable with the 8bit - colours (about 216 different ones).
I also try to modify my environment dynamically, so I need to smooth things from time to time. Therefore I just need to change the density value, doing the same with the material would have undesireable effects.
So instead of extracting, filtering and writing back, i should modify the lowpassfilter to my needs?


Top
Offline Profile  
Reply with quote  
 Post subject: Re: How to use LowPassFilter
PostPosted: Mon Nov 25, 2013 12:29 pm 
Developer
User avatar

Joined: Sun May 04, 2008 6:35 pm
Posts: 1827
The LowPassFilter performs simple averaging by adding the surrounding voxels together and then dividing by the total number of voxels. It may be enough for you to change the definitaion of 'adding' by modifying the operator+=() which is provided for MaterialDensityPair88. It currently reads as follows:

Code:
MaterialDensityPair<Type, NoOfMaterialBits, NoOfDensityBits>& operator+=(const MaterialDensityPair<Type, NoOfMaterialBits, NoOfDensityBits>& rhs)
{
   m_uDensity += rhs.m_uDensity;

   // What should we do with the material? Conceptually the idea of adding materials makes no sense, but for our
   // purposes we consider the 'sum' of two materials to just be the max. At least this way it is commutative.
   m_uMaterial = (std::max)(m_uMaterial, rhs.m_uMaterial);

   return *this;
}


Perhaps if you remove the line which modifies the material then this gives the desired behaviour?

If you can't get it to work in this way then a custom version of the LowPassFilter may indeed be required.


Top
Offline Profile  
Reply with quote  
 Post subject: Re: How to use LowPassFilter
PostPosted: Tue Nov 26, 2013 9:15 am 

Joined: Fri Sep 20, 2013 10:16 am
Posts: 15
Thanks for the reply.
I removed the line you suggested, but it didnt worked out well. Some of the values disappeared so in the end it smoothed itself to nothing. :)

So I modified the LowPassFilter::execute()
Code:
tSrcVoxel += static_cast<AccumulationType>(srcSampler.peekVoxel1nx1ny1nz().getDensity());
...
tSrcVoxel += static_cast<AccumulationType>(srcSampler.peekVoxel1px1py1pz().getDensity());

tSrcVoxel /= 27;
DstVolumeType::VoxelType newVoxel(srcSampler.peekVoxel0px0py0pz().getMaterial(),tSrcVoxel);
m_pVolDst->setVoxelAt(iSrcX, iSrcY, iSrcZ, voxel);

First I changed that the srcSampler just looks at the Density-value at a point, so tSrcVoxel is just a Density-value and nothing more.
Then I create a new Voxel with the needed parameters and write it to the DestinationVolume.

For my needs it works fine, but I could imagine if you have another VolumeType, where you only want to change the density, it would work too with some small changes.


Top
Offline Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 13 posts ]  Go to page 1, 2  Next

All times are UTC


Who is online

Users browsing this forum: No registered users and 3 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
cron
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group
Theme created StylerBB.net