Linear Sampler for Texture3D

Started by
3 comments, last by Mr_Fox 7 years, 6 months ago

Hey Guys,

Does anyone know how linear sampler works behind the scene? especially when sampling a Texture3D object. Is it fetching 8 texels and interpolate the result? MSDN only says D3D12_FILTER_TYPE_LINEAR will return "the weighter average of a 2x2 area of texels surrounding the desired pixel" which is not super clear to me.

I tried to render a surface by raycasting through a TSDF volume (Texture3D), point sampler and my own linear 'sampler' (point sample 8 nearest voxels then interpolate) gives expected result without artifacts, while LinearSampler and AnisotropicSampler runs faster but generate artifacts(see attachments PointSampler, ManualSampler(trilinear interpolation), LinearSampler).

[attachment=33607:PointSample.PNG][attachment=33608:ManualLinearSample.PNG][attachment=33609:LinearSample.PNG]

also here is the most obvious one:

[attachment=33610:artifactPattern.PNG]

It looks clearly that this artifact related to raycast step size, but why point sampler and manual interpolation don't have this artifact? In fact I double checked, even if I make my ray cast step 10x smaller, the artifact still exist (even worse).

Also another MSDN page at the bottom part states:

During texture sampling, one or more texels are read and combined (this is calling filtering) to produce a single value. Point sampling reads a single texel while linear sampling reads two texels (endpoints) and linearly interpolates a third value between the endpoints.

So it seems samplers will never do 8 texels fetches for correct trilinear interpolation on Texture3D? (anyone?)

in case there are any problems in my manual interpolation I pasted it here:


// f3Idx range (0,0,0) - (resolution.x, resolution.y, resolution.z)
float readVolume(float3 f3Idx)
{
    int3 i3Idx000;
    float3 f3d = modf(f3Idx, i3Idx000) - 0.5f;
#if FILTER_READ == 1
    float fV000 = tex_srvTSDFVol[BUFFER_INDEX(i3Idx000)];
    float fV001 = tex_srvTSDFVol[BUFFER_INDEX(i3Idx000 + uint3(0, 0, 1))];
    float fV010 = tex_srvTSDFVol[BUFFER_INDEX(i3Idx000 + uint3(0, 1, 0))];
    float fV011 = tex_srvTSDFVol[BUFFER_INDEX(i3Idx000 + uint3(0, 1, 1))];
    float fV100 = tex_srvTSDFVol[BUFFER_INDEX(i3Idx000 + uint3(1, 0, 0))];
    float fV101 = tex_srvTSDFVol[BUFFER_INDEX(i3Idx000 + uint3(1, 0, 1))];
    float fV110 = tex_srvTSDFVol[BUFFER_INDEX(i3Idx000 + uint3(1, 1, 0))];
    float fV111 = tex_srvTSDFVol[BUFFER_INDEX(i3Idx000 + uint3(1, 1, 1))];
    return fV000 * (1.f - f3d.x) * (1.f - f3d.y) * (1.f - f3d.z) +
        fV100 * f3d.x * (1.f - f3d.y) * (1.f - f3d.z) +
        fV010 * (1.f - f3d.x) * f3d.y * (1.f - f3d.z) +
        fV001 * (1.f - f3d.x) * (1.f - f3d.y) * f3d.z +
        fV101 * f3d.x * (1.f - f3d.y) * f3d.z +
        fV011 * (1.f - f3d.x) * f3d.y * f3d.z +
        fV110 * f3d.x * f3d.y * (1.f - f3d.z) +
        fV111 * f3d.x * f3d.y * f3d.z;
#elif TEX3D_UAV && FILTER_READ == 2
    return tex_srvTSDFVol.SampleLevel(
        samp_Linear, f3Idx / vParam.u3VoxelReso, 0);
#elif TEX3D_UAV && FILTER_READ == 3
    return tex_srvTSDFVol.SampleLevel(
        samp_Aniso, f3Idx / vParam.u3VoxelReso, 0);
#else
    return tex_srvTSDFVol[BUFFER_INDEX(i3Idx000)];
#endif // !FILTER_READ
} 

So I think I probably need more information to dig into it. Also if anyone knows there exist better ways to do Volume interpolated read, please yell at me.

Thanks

Advertisement

The hardware will be doing Trilinear filtering and taking the weighted average of the 8 texels you'd expect.

I'm not sure if your algorithm is sensitive to this, but be aware that there is a finite amount of values a texture coordinate can take between two texels; much less than you'd expect. I believe you're only guaranteed 8 bits of sub-texel precision, meaning that there can only be 256 different possible linear interpolations between two adjacent texels in a 1D texture. Think of your texture coordinate as getting clamped to the nearest 1/256th of a texel and ask yourself whether that might be a problem?

I'm pretty sure hardware is allowed to give more precision to the interpolation, but not less. Have you tried any other GPUs than the one you're using? Perhaps WARP?

Clearly point sampling the 8 values and doing your own interpolation will yield as precise a result as is possible with the numbers you have available, so this may go some way towards explaining why it works without artifacts.

Adam Miles - Principal Software Development Engineer - Microsoft Xbox Advanced Technology Group

Another thing to consider is mipmaps. If your UVWs vary too much, the GPU will be likely dropping to lower mips. And the results will be as good as the quality of the mip allows.

Thanks Adam and Matias. Another thing I think I should mention is that I only have one mipMap level (so no mipmap) does that matters for linear sampler?

Since my TSDF volume is generated every frame. Creating additional mipmap level will take extra memory and spend more time(am I right?) So after briefly think about it, I decide to not have extra mipmap levels.

Problem solved!!

Thanks Adam, the problem did relate to sampler's uv precision, but it also related to the problematic way I detect surface in TSDF volume:

surface is detected if

lastStepTSDFValue * currentStepTSDFValue < 0

so I will miss surface if either lastStepTSDFValue or currentStepTSDFValue is 0 (which means sample point is right on surface). And I have placed my test surface right in the x = 0.f plane (which means, in terms of Texture3D, after transformation, I will have voxels store 0.0f) , when I use manual linear filter, I will almost never sample the very center of each voxel, so I will almost never get xxxStepTSDFValue == 0.f. However when use linearSampler, due to the precision limit mentioned by Adam, I will have fare amount of samples right on voxel center, thus have some number of xxxStepTSDFValue == 0.f. then the artifact appears.

modify surface detect condition to lastStepTSDFValue * currentStepTSDFValue <= 0 fix the artifacts.

Many thanks!!!

This topic is closed to new replies.

Advertisement