order independent smooth min for calculating color blending factor

Hi,

I am working on a simple raymarching shader the goal of which is to blend both colors and geometry of up to 64 spheres both by smooth union and subtraction operations.

My approach is basic:

  1. Whenever the scene is changed I check bounds intersection for each unique sphere and find the list of all others spheres that might have a blending impact on it
  2. I then create new MaterialPropertyBlock and pass to the raymarching shader all positions and colors of these spheres
  3. In the shader itself I loop trough these values and find the final sdf result

            inline float sminExp(float a, float b, float k)
            {
                float res = exp2(-k * a) + exp2(-k * b);
                return -log2(res) / k;
            }

            inline float DistanceFunction(float3 wpos)
            {
                float m = 0;

                float r = _Rs[0];
                float4 localPos = float4(wpos, 0.0) - _WorldPoses[0];
                float d = Sphere(localPos, r);

                m = d;
                for (int i = 1; i < _Objects_N; i++)
                {
                    r = _Rs[i];
                    localPos = float4(wpos, 0.0) - _WorldPoses[i];
                    d = Sphere(localPos, r);
                    m = sminExp(d, m, _Smooth).x;
                }

                r = _NegRs[0];
                localPos = float4(wpos, 0.0) - _NegWorldPoses[0];
                float negm = Sphere(localPos, r);

                for (int i = 1; i < _NegObjects_N; i++)
                {
                    r = _NegRs[i];
                    localPos = float4(wpos, 0.0) - _NegWorldPoses[i];

                    d = Sphere(localPos, r);
                    negm = sminExp(d, negm, _Smooth);
                }

                m = SmoothSub(negm, m, _Smooth * .5);

                return m;
            }
            inline float2 smin2(float a, float b, float k)
            {
                float h = max(k - abs(a - b), 0.0) / k;
                float m = h * h * 0.5;
                float s = m * k * (1.0 / 2.0);
                return (a < b) ? float2(a - s, m) : float2(b - s, m - 1.0);
            }

            inline void PostEffect(float3 rmpos, inout SurfaceData o)
            {
                float L = 0;
                float dNorms[32];

                float4 finalColor = _Colors[0];
                float m = 0;
                float r = _Rs[0];
                float4 localPos = float4(rmpos, 0.0) - _WorldPoses[0];
                float d = Sphere(localPos, r);
                m = d;

                for (int i = 1; i < _Objects_N; i++)
                {
                    r = _Rs[i];
                    localPos = float4(rmpos, 0.0) - _WorldPoses[i];
                    d = Sphere(localPos, r);

                    float2 sm = smin2(m, d, _Smooth);
                    m = sm.x;
                    finalColor = lerp(finalColor, _Colors[i], abs(sm.y));
                }

                o.albedo = finalColor;
            }

But I have 2 major problems with it:

  1. To make such approach work the smooth min function has to be order independent and I was successfully able to find one in this smooth min article written by talented Inigo Quilez. But unfortunately I couldn’t find the float2 match to calculate the order independent color factor as well (not polynomial, but exp based one). All examples I find in Shadertoy, even the ones that utilize exp smooth min, do not have order independent color blending. So where can I find a proper order independent smooth min blending factor function to properly mix the colors in my clustered approach?

  2. For 10+ spheres per material the performance gets very poor, perhaps it is because of the loops? I feel like there are practically no other way except to pass each of up to 64 spheres (10-20 normal scenario) and carefully calculate sdf value for them all in one place. I tried dynamic camera resolution feature, but it alters the overlay UI, so I couldn’t stick with it. Perhaps there are others ways to handle the multiple spheres sdfs calculations in a more efficient manner? The biggest bottleneck in my approach is these 2 loops I have.

Look forward for any suggestions or external links, thanks!

Here is an elegant solution proposed by Inigo Quilez:

vec2 smin( in float a, in float b, in float k )
{
    float f1 = exp2( -k*a );
    float f2 = exp2( -k*b );
    return vec2(-log2(f1+f2)/k,f2);
}

Colors are blending together in chaotic order with the same result for each sphere material.

And shadertoy example in action: https://www.shadertoy.com/view/slBSWV

3 Likes

Thank you so much for sharing this! The same blending problem has been driving me nuts the past few days; I’d also been scouring ShaderToy / was on the verge of asking the community there for help when I stumbled on this.

What would we do without iq, eh?