Eliminating Raymarching Banding in Volume Shaders

I recently started messing around with shaders in Unity and thought I’d try creating a raymarch volume shader. I found some success, but I’ve run into a rather ugly problem that I’m not really sure how to approach. As in the title, I am getting this banding in the render:


Increasing the step count helps to a point, this was done at 256 steps, and it becomes such a drain on performance that I would like to decrease the steps rather than increase them.

I would appreciate any insight into this issue. I would like to also understand any solutions as much as possible, so diagrams would also be greatly appreciated. Thanks!

Could you upload this broken shader or send me code in private message ? I will try to fix it.

Have you used domain deformations in this shader ? Sometimes operations like bending / twisting can cause artifacts.
Also you can visit my Git repository, where you can find some raymarch shaders for Unity3D…

Here you have simple example:

https://github.com/przemyslawzaworski/Unity3D-CG-programming/blob/master/raymarching_materials_integration.shader

The code isn’t broken, it’s behaving exactly as it should. Here is a raymarch script that is heavily simplified to get to the root of the problem:

Shader "Unlit/TestRayMarch"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader {
        Tags {
            "Queue" = "Transparent"
        }

        Pass {

            //Transparency Stuff
               ZWrite Off
            Cull Front
                Blend SrcAlpha OneMinusSrcAlpha

                ZTest Off

               CGPROGRAM

               #include "UnityCG.cginc"
               #include "Lighting.cginc"

            #pragma vertex vert           
                #pragma fragment frag


                //Check if the ray intersects with a sphere
                float checkSphere (float3 rayPosition, float3 _Center) {
                    return distance (rayPosition, _Center);
                }


                float4 raymarch (float3 direction, float3 pixelWorldPos, float3 cameraPos) {
                    float3 pos = cameraPos;
                    float4 sampleValue = float4 (1, 1, 1, 0);

                    for (int i = 0; i < 128; i++) {
                        if (checkSphere (pos, mul(unity_ObjectToWorld, float4 (0, 0, 0, 1)).xyz) <= 0.5){
                        sampleValue.a += 0.1;        //This part here, having transparency, causes banding              
                        }

                     pos += direction * 0.1;
                    }

                    return float4 (sampleValue);
                }

            struct vertOutput {
                    float4 pos : SV_POSITION;
                    float3 wPos : TEXCOORD0;
               };

            vertOutput vert(appdata_full v) {
                   vertOutput o;

                o.pos = UnityObjectToClipPos(v.vertex);
                o.wPos = mul(unity_ObjectToWorld, v.vertex).xyz;

                return o;
            }


            half4 frag(vertOutput output) : COLOR {
                return raymarch ( normalize (output.wPos - _WorldSpaceCameraPos), output.wPos, _WorldSpaceCameraPos);
               }

            ENDCG
        }
    }
}

The banding is noticeable because there is an alpha channel that is changing based on ray depth.

I think another approach to solving this would be using a depth texture.

Looks like a precision issue as you have enough steps, or maybe you just need to process it further with dither or such?

In a way, yes it is a precision problem. The step size can be changed and that can help reduce the effect, but it never quite goes away. I suppose one path may be that a dynamic system is needed to balance this out in real time, but it would be messy and kind of complicated.

I’ve also found an academic paper in which this is avoided via a random depth offset. The problem is getting a random number, and it creates a static like effect which isn’t very desirable.

Jittering the ray depth can help a lot but it does created a “static” pattern. You should blur the volume then do a bilateral upsample to keep the sharp edges.

Another option is using old school interleaved sampling. Basically render the volume 4 times at a (slices/4) count with a different initial ray offset.

Then in your final composite pass use an interleaved pattern (dither pattern) to sample from those 4 previous samples.

This will give you a better quality than if you had rendered all those passes together. It will give you a dither pattern but in either case you should do a blur pass.

This is a good article even thought it deals with light all the techniques he uses can be applied.
http://www.alexandre-pestana.com/volumetric-lights/

Ah thanks! It never ceases to amaze me what I don’t find in my own research.