glslshaderfragment-shaderraymarching

Why are my Ray march fragment shader refelction texture lookups slowing my frame rate?


I’ve written a Fragment shader in GLSL, using shader toy. Link : https://www.shadertoy.com/view/wtGSzy

most of it works, but when I enable texture lookups in the reflection function, the performance drops from 60FPS to 5~FPS.

The code in question is on lines 173 - 176

if(SDFObjectToDraw.texChannelID == 0)
    col = texture(iChannel0, uv);
if(SDFObjectToDraw.texChannelID == 1)
           col = texture(iChannel1, uv);

This same code can bee seen in my rayMarch function (lines 274-277) and works fine for colouring my objects. It only causes issues in the reflection function.

My question is, why are my texture lookups, in the reflection code, dropping my performance this much and what can I do to improve it?

/**
 * Return the normalized direction to march in from the eye point for a single pixel.
 * 
 * fieldOfView: vertical field of view in degrees
 * size: resolution of the output image
 * fragCoord: the x,y coordinate of the pixel in the output image
 */
vec3 rayDirection(float fieldOfView, vec2 size, vec2 fragCoord) {
    vec2 xy = fragCoord - size / 2.0;
    float z = size.y / tan(radians(fieldOfView) / 2.0);
    return normalize(vec3(xy, -z));
}


float start = 0.0;
vec3 eye = vec3(0,0,5);
int MAX_MARCHING_STEPS = 255;
float EPSILON = 0.00001;
float end = 10.0;


const uint Shpere = 1u;
const uint Box = 2u;
const uint Plane = 4u;



vec3 lightPos = vec3(-10,0,5);
#define M_PI 3.1415926535897932384626433832795
const int SDF_OBJECT_COUNT = 4;

struct SDFObject
{
    uint Shape;
    vec3 Position;
    float Radius;
    int  texChannelID;
    float Ambiant;
    float Spec;
    float Diff;
    vec3 BoxSize;
    bool isMirror; //quick hack to get refletions working

};
SDFObject SDFObjects[SDF_OBJECT_COUNT] = SDFObject[SDF_OBJECT_COUNT](    
        SDFObject(Shpere, vec3(2,0,-3),1.0,0,0.2,0.2,0.8, vec3(0,0,0),true)
        ,SDFObject(Shpere, vec3(-2,0,-3),1.0,0,0.1,1.0,1.0, vec3(0,0,0),false)
        ,SDFObject(Box, vec3(0,0,-6),0.2,1,0.2,0.2,0.8, vec3(1.0,0.5,0.5),false)
        ,SDFObject(Plane, vec3(0,0,0),1.0,1,0.2,0.2,0.8, vec3(0.0,1.0,0.0),false)
        );

float shereSDF(vec3 p, SDFObject o)
{
    return length(p-o.Position)-o.Radius;
}
float boxSDF(vec3 pointToTest, vec3 boxBoundery, float radius, vec3 boxPos)
{
    vec3 q = abs(pointToTest - boxPos) - boxBoundery;
    return length(max(q,0.0)) + min(max(q.x, max(q.y,q.z)) ,0.0) -radius;
}
float planeSDF(vec3 p, vec4 n, vec3 Pos)
{
    return dot(p-Pos, n.xyz) + n.w;
}





bool IsShadow(vec3 LightPos, vec3 HitPos)
{
    bool isShadow = false;


    vec3 viewRayDirection = normalize(lightPos- HitPos) ;       

    float depth = start;

    vec3 hitpoint;

    for(int i=0; i<MAX_MARCHING_STEPS; i++)
    {
        hitpoint = (HitPos+  depth * viewRayDirection);

        float dist = end;

        for(int j =0; j<SDF_OBJECT_COUNT; j++)
        {
            float distToObjectBeingConsidered;
             if(SDFObjects[j].Shape == Shpere)
                distToObjectBeingConsidered = shereSDF(hitpoint, SDFObjects[j]);
            if(SDFObjects[j].Shape == Box)
                distToObjectBeingConsidered = boxSDF(hitpoint, SDFObjects[j].BoxSize , SDFObjects[j].Radius, SDFObjects[j].Position);
            if(SDFObjects[j].Shape == Plane)
                distToObjectBeingConsidered= planeSDF(hitpoint, vec4(SDFObjects[j].BoxSize, SDFObjects[j].Radius), SDFObjects[j].Position);

            if( distToObjectBeingConsidered < dist)
            {
               dist = distToObjectBeingConsidered;
            }
        }

        if(dist < EPSILON)
        {
            isShadow = true;
        }


        depth += dist;

        if(depth >= end)
        {
           isShadow = false;
        } 

    }

    return isShadow;
}


vec3 MirrorReflection(vec3 inComingRay, vec3 surfNormal, vec3 HitPos, int objectIndexToIgnore)
{
    vec3 returnCol;

    vec3 reflectedRay = reflect(inComingRay, surfNormal);



    vec3 RayDirection = normalize(reflectedRay) ;       

    float depth = start;


    vec3 hitpoint;
    int i;
    for(i=0; i<MAX_MARCHING_STEPS; i++)
    {
        hitpoint = (HitPos+  depth * RayDirection);

        SDFObject SDFObjectToDraw;
        float dist = end;

        for(int j =0; j<SDF_OBJECT_COUNT; j++)
        {
            float distToObjectBeingConsidered;
             if(SDFObjects[j].Shape == Shpere)
                distToObjectBeingConsidered = shereSDF(hitpoint, SDFObjects[j]);
            if(SDFObjects[j].Shape == Box)
                distToObjectBeingConsidered = boxSDF(hitpoint, SDFObjects[j].BoxSize , SDFObjects[j].Radius, SDFObjects[j].Position);
            if(SDFObjects[j].Shape == Plane)
                distToObjectBeingConsidered= planeSDF(hitpoint, vec4(SDFObjects[j].BoxSize, SDFObjects[j].Radius), SDFObjects[j].Position);


           if( distToObjectBeingConsidered < dist && j!= objectIndexToIgnore )// D > 0.0)
            {
               dist = distToObjectBeingConsidered;
               SDFObjectToDraw = SDFObjects[j];
            }
        }

        if(dist < EPSILON)
        {
            vec3 normal =normalize(hitpoint-SDFObjectToDraw.Position);
            float u = 0.5+ (atan(normal.z, normal.x)/(2.0*M_PI));
            float v = 0.5+ (asin(normal.y)/(M_PI));

            vec2 uv =vec2(u,v);            
            vec4 col = vec4(0,0.5,0.5,0);


///>>>>>>>>>>>> THESE LINES ARE broken, WHY?        
            //if(SDFObjectToDraw.texChannelID == 0)
                //col = texture(iChannel0, uv);
            //if(SDFObjectToDraw.texChannelID == 1)
                //col = texture(iChannel1, uv);


            vec3 NormalizedDirToLight = normalize(lightPos-SDFObjectToDraw.Position);
            float theta = dot(normal,NormalizedDirToLight);


            vec3 reflectionOfLight = reflect(NormalizedDirToLight, normal);
            vec3 viewDir = normalize(SDFObjectToDraw.Position);
            float Spec = dot(reflectionOfLight,  viewDir);


            if(IsShadow(lightPos, hitpoint))
            {
                returnCol= (col.xyz*SDFObjectToDraw.Ambiant);
            }
            else
            {
                returnCol= (col.xyz*SDFObjectToDraw.Ambiant) 
                +(col.xyz * max(theta *SDFObjectToDraw.Diff, SDFObjectToDraw.Ambiant));
            }
            break;
        }


        depth += dist;

        if(depth >= end)
        {
            //should look up bg texture here but cant be assed right now
           returnCol = vec3(1.0,0.0,0.0);
           break;
        } 

    }



    return returnCol;//*= (vec3(i+1)/vec3(MAX_MARCHING_STEPS));
}


vec3 rayMarch(vec2 fragCoord)
{
    vec3 viewRayDirection = rayDirection(45.0, iResolution.xy, fragCoord);

    float depth = start;

    vec3 hitpoint;

    vec3 ReturnColour = vec3(0,0,0);

    for(int i=0; i<MAX_MARCHING_STEPS; i++)
    {
        hitpoint = (eye+  depth * viewRayDirection);

        float dist = end;
        SDFObject SDFObjectToDraw;
        int objectInDexToIgnore=-1;

        //find closest objecct to current point
        for(int j =0; j<SDF_OBJECT_COUNT; j++)
        {
            float distToObjectBeingConsidered;

            if(SDFObjects[j].Shape == Shpere)
                distToObjectBeingConsidered = shereSDF(hitpoint, SDFObjects[j]);
            if(SDFObjects[j].Shape == Box)
                distToObjectBeingConsidered = boxSDF(hitpoint, SDFObjects[j].BoxSize , SDFObjects[j].Radius, SDFObjects[j].Position);
            if(SDFObjects[j].Shape == Plane)
                distToObjectBeingConsidered= planeSDF(hitpoint, vec4(SDFObjects[j].BoxSize, SDFObjects[j].Radius), SDFObjects[j].Position);

            if( distToObjectBeingConsidered < dist)
            {
                dist = distToObjectBeingConsidered;
                SDFObjectToDraw = SDFObjects[j];
                objectInDexToIgnore = j;
            }
        }


        //if we are close enough to an objectoto hit it.
        if(dist < EPSILON)
        {
            vec3 normal =normalize(hitpoint-SDFObjectToDraw.Position);
            if(SDFObjectToDraw.isMirror)
            {
                ReturnColour = MirrorReflection( viewRayDirection, normal, hitpoint, objectInDexToIgnore);
            }
            else
            {

                float u = 0.5+ (atan(normal.z, normal.x)/(2.0*M_PI));
                float v = 0.5+ (asin(normal.y)/(M_PI));

                vec2 uv =vec2(u,v);            
                vec4 col;

                if(SDFObjectToDraw.texChannelID == 0)
                    col = texture(iChannel0, uv);
                if(SDFObjectToDraw.texChannelID == 1)
                    col = texture(iChannel1, uv);


                vec3 NormalizedDirToLight = normalize(lightPos-SDFObjectToDraw.Position);
                float theta = dot(normal,NormalizedDirToLight);


                vec3 reflectionOfLight = reflect(NormalizedDirToLight, normal);
                vec3 viewDir = normalize(SDFObjectToDraw.Position);
                float Spec = dot(reflectionOfLight,  viewDir);


                if(IsShadow(lightPos, hitpoint))
                {
                    ReturnColour= (col.xyz*SDFObjectToDraw.Ambiant);
                }
                else
                {
                    ReturnColour= (col.xyz*SDFObjectToDraw.Ambiant) 
                    +(col.xyz * max(theta *SDFObjectToDraw.Diff, SDFObjectToDraw.Ambiant));
                    //+(col.xyz* Spec * SDFObjectToDraw.Spec);
                }
            }

             return ReturnColour;
        }


        depth += dist;

        if(depth >= end)
        {
            float u = fragCoord.x/ iResolution.x;
            float v =  fragCoord.y/ iResolution.y;
            vec4 col = texture(iChannel2, vec2(u,v));
            ReturnColour =col.xyz;
        } 

    }
    return ReturnColour;
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    // Normalized pixel coordinates (from 0 to 1)
    //vec2 uv = fragCoord/iResolution.xy;

    // Time varying pixel color
    //vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4));

    // Output to screen

    lightPos *= cos(iTime+vec3(1.5,2,2));

    //lightPos= vec3(cos(iTime)*2.0,0,0);

    vec3 SDFCol= rayMarch(fragCoord);
    vec3 col = vec3(0);

    //if(SDFVal <=1.0)
      //  col = vec3(1,0,0);

    //col = vec3(SDFVal,0,0);

    col = vec3(0.5,0,0);
    col =  SDFCol;

    fragColor = vec4(col,1.0);
}

Solution

  • [...] This same code can bee seen in my rayMarch function (lines 274-277) and works fine for colouring my objects. [...]

    The "working" texture lookup is executed in a loop in rayMarch. MAX_MARCHING_STEPS is 255, so the lookup is done at most 255 times.

    vec3 rayMarch(vec2 fragCoord)
    {
       // [...]
    
       for(int i=0; i<MAX_MARCHING_STEPS; i++)
       {
           // [...]
    
           if(SDFObjectToDraw.texChannelID == 0)
               col = texture(iChannel0, uv);
           if(SDFObjectToDraw.texChannelID == 1)
               col = texture(iChannel1, uv);
    
           // [...]
       }
    
       // [...]
    }
    

    When you do the lookup in MirrorReflection then the performance breaks down, because it is done in a loop in MirrorReflection and MirrorReflection is called in a loop in rayMarch. In this case the lookup is done up to 255*255 = 65025 times.

    ~65000 texture lookups for a fragment is far to much and cause the break down of performance.

    vec3 MirrorReflection(vec3 inComingRay, vec3 surfNormal, vec3 HitPos, int objectIndexToIgnore)
    {
       // [...]
    
       for(i=0; i<MAX_MARCHING_STEPS; i++)
       {
           // [...]
    
           if(SDFObjectToDraw.texChannelID == 0)
                col = texture(iChannel0, uv);
           if(SDFObjectToDraw.texChannelID == 1)
                col = texture(iChannel1, uv);
    
           // [...]
       }
    
       // [...]
    }
    
    vec3 rayMarch(vec2 fragCoord)
    {
       // [...]
    
       for(int i=0; i<MAX_MARCHING_STEPS; i++)
       {
           // [...]
    
           ReturnColour = MirrorReflection(viewRayDirection, normal, hitpoint, objectInDexToIgnore);
    
           // [...]
       }
    
       // [...]
    }