That is the kind of artifact I see when I change the frame count of my volume texture without updating the shader. My best guess is that this is a rounding error in your 2d space conversion, or a ceil needs to be a floor or something.
I handle the volume lookup with a separate nested function and then call that. Note that to seamlessly sample the volume texture it is actually blending two samples. If you skip that step you will see a very nasty hard edge between Z frames. That part will not be necessary once we have full hardware volume texture support. The reason why is that the hardware uses the usual mip mapping channel to handle Z frame blending. But that also means you lose mip maps.
Here is my single volume lookup code. Maybe you can plug it in and use it.
//** Tex **// Input Texture Object storing Volume Data
//** inPos **// Input float3 for Position, 0-1
//** XYSize **// Input float for num frames in x,y directions
//** NumFrames **// Input float for num total frames
float zframe = ceil( inPos.z * NumFrames );
float zphase = frac( inPos.z * NumFrames );
float2 uv = 0;
float2 zcellxypos = 0;
// convert 1d to 2d index
{
zcellxypos.x = fmod( zframe, XYSize );
zcellxypos.y = floor( zframe / XYSize );
zcellxypos /= XYSize;
}
uv = frac(inPos.xy) / XYSize;
uv += zcellxypos;
float sampleA = dot( Tex.SampleLevel(TexSampler, uv, 0), channel) - offset;
float2 uv2 = frac(inPos.xy) / XYSize;
// bilinear filtering get 1 frame above
{
zcellxypos.x = fmod( (zframe + 1), XYSize );
zcellxypos.y = floor( (zframe + 1) / XYSize );
zcellxypos /= XYSize;
}
uv2 += zcellxypos;
float sampleB = dot( Tex.SampleLevel(TexSampler, uv2, 0), channel) - offset;
//return sampleA;
return saturate( lerp( sampleA, sampleB, zphase ) );
The value ‘offset’ was added last minute so I could try to add variation to instances of the effect using the ray starting position but you can remove it. Also it does a dot product so that various channels could be used. I was trying to channel pack different variations of the volume texture as well for more variation which worked very nicely.
This is the code line to call the nested function. All you have to do is make sure the nested function is plugged into one of the pins of the ray marching function so that the compiler is forced to add the nested function first so that it is number 0:
//Sample the volume texture
float texatray = CustomExpression0 (Parameters, Tex, TexSampler, saturate(CurPos), XYSize, NumFrames, channel, offset);
Saturate may or may not be necessary depending on how you handle your step calculations. I added it just to clean up the edges when using low number of steps.
Here is what my volume baking BP and material look like animated. I apply some animation by using flowmap like distortion on the volume texture itself. The volume texture gets rebaked every frame and then the ray marcher can look up the more complex effects like flowmaps just as a single (well technically double due to the aforementioned reasons) texture lookup: