Silverlining’s “stratocumulus” cloud type is rendered via GPU ray-marching instead of particles. This “volumetric” approach avoids all of the artifacts that come with billboard particles, but it comes at a cost. Since volume rendering just renders a slab with a very fancy fragment shader, geometrically there is not much resolution in a volumetric cloud. This means that depth testing doesn’t work the way it should; objects within the clouds won’t be properly obscured by individual clouds; they are only obscured by the cloud layer as a whole.

Fortunately, there is a solution – but it requires a little extra work. If our stratocumulus fragment shader knows the depth of each fragment of your scene somehow, it can compare the depth of each ray march through the cloud layer to that depth, and stop marching if it hits something in your scene.

The trick is getting that scene depth somehow; it requires access to a depth texture representing your scene, and that’s not something SilverLining can produce on its own.

SilverLining’s user functions for OpenGL renderers can help. Let’s imagine your application is populating a depth texture called “depthTexture” in the Resources/Shaders/UserFunctions-frag.glsl file, and a flag indicating that it is present:

uniform sampler2D depthTexture;
uniform bool hasDepthTexture;

This would allow you to develop a getFragDepth function within your user functions; something like this:

float getFragDepth()
{
    float defaultDepth = 1000000.0;

    if (!hasDepthTexture) {
        return defaultDepth;
    }

    vec4 ndcPos;
    ndcPos.xy = ((2.0 * gl_FragCoord.xy) - (2.0 * viewport.xy)) / (viewport.zw) - 1.0;

    if (reverseZ) {
        ndcPos.z = gl_FragCoord.z;
    }
    else {
        ndcPos.z = gl_FragCoord.z * 2.0 - 1.0;
    }

    ndcPos.w = 1.0;

    vec4 clipPos = ndcPos / gl_FragCoord.w;
    vec4 eyePos = invPersMatrix * clipPos;

    vec2 tc = (gl_FragCoord.xy - viewport.xy) / viewport.zw;
    float depth = texture2D(depthTexture, tc).x;

    if (reverseZ) {
        ndcPos.z = depth;
    }
    else {
        ndcPos.z = depth * 2.0 - 1.0;
    }

    float den = (ndcPos.z - (persMatrix[2][2] / persMatrix[2][3]));

    if (den == 0.0) {
        return defaultDepth; // Protect against divide by zero; this happens
    }
    else {
        clipPos.w = persMatrix[3][2] / den;

        clipPos.xyz = ndcPos.xyz * clipPos.w;

        eyePos = invPersMatrix * clipPos;

        float sceneDepth = abs(eyePos.z);

        return sceneDepth;
    }
}

You could then modify our Resources/Shaders/Stratocumulus-frag.glsl15 shader to make use of this function. First, declare it at the top of the shader along with the other user functions it hooks into:

float getFragDepth();

Then, within the shader’s inner loop in the main function, where it iterates through every sample along the ray, you’ll find the logic where we apply fog to cloud. This requires us to compute a “depth” variable, which very conveniently gives us something to compare the result of getFragDepth to.

Since the scene depth does not change within this loop, you can retrieve it once before entering it:

float sceneDepth = getFragDepth();

Then, within the inner loop after “depth” has been computed as part of the fog calculations, you can just do:

if (depth > sceneDepth) {
    break;
}

Et voila! Proper depth testing within our volumetric stratocumulus clouds. We use this same technique in our X-Plane integration, SkyMaxx Pro, and it works quite well. Here’s a shot of “stratocumulus” volumetric clouds intersecting the mountains of Nepal (click for full resolution:)