Board index FlightGear Development Effects and shaders

Revisiting clouds

An exciting "new" option in FlightGear, that includes reflections, lightmaps, the particle system etc.. A lot is yet to be discovered/implemented!

Re: Revisiting clouds

Postby Thorsten » Thu Sep 20, 2018 12:28 pm

I don't think a cloud shape is as simple as that.


You're doing 32 samples sunward for the whole sky - I don't know your true spatial resolution (I'd be interested), but it's not unrealistic to take clouds 30 km away in low light to cast a shade, so you'd have a km-sized resolution scale.

At this resolution, a cloud shape is very simple.

You need to have some kind of noise function to modulate the density in the height axis.


At a spatial resolution of 50 m which the texture provides, I think I can basically just store the value - the resolution is an overkill for shading already.

The current technique runs really fast on a low sample count, and I don't think you'll be able to further decrease the amount of work done per sample, not without compromising how good the clouds look.


Unless I misunderstood what you're doing, I think you can - you need resolution for how clouds look, but not for how they shade. The latter we can solve with no noise, just a single texture lookup per sample I bet - so in fact you'll probably be able to increase the number of shade samples.
Thorsten
 
Posts: 10635
Joined: Mon Nov 02, 2009 8:33 am

Re: Revisiting clouds

Postby Icecode GL » Thu Sep 20, 2018 12:35 pm

You're doing 32 samples sunward for the whole sky - I don't know your true spatial resolution (I'd be interested), but it's not unrealistic to take clouds 30 km away in low light to cast a shade, so you'd have a km-sized resolution scale.


I'm not, it's 32 samples in the view ray. I'm doing like 4 or 6 sunwards.
Icecode GL
 
Posts: 506
Joined: Thu Aug 12, 2010 12:17 pm
Location: Spain
Callsign: icecode
Version: GIT
OS: Arch Linux

Re: Revisiting clouds

Postby Thorsten » Thu Sep 20, 2018 12:38 pm

So you're even less sensitive to the true shape of the clouds...
Thorsten
 
Posts: 10635
Joined: Mon Nov 02, 2009 8:33 am

Re: Revisiting clouds

Postby Icecode GL » Fri Sep 21, 2018 8:23 am

It looks good enough, feel free to tune the values yourself. In the end this is about perfectly tuning a lot of arbitrary parameters as an artist rather than about solving a physics problem, that only gets you so far.

Here is the code:

Code: Select all
#version 120

#define MAX_MARCHING_STEPS 256
#define CLOUD_START_ALT    2200.0
#define CLOUD_END_ALT      4400.0

#define CLOUD_DENSITY           0.04
#define CLOUD_EROSION_STRENGTH  0.2

uniform sampler2D color_tex;
uniform sampler2D noise_base_tex;
uniform sampler2D noise_erosion_tex;

uniform vec2 fg_BufferSize;
uniform vec3 fg_CameraPositionCart;
uniform vec3 fg_CameraPositionGeod;

uniform mat4 fg_ViewMatrixInverse;
uniform mat4 fg_ProjectionMatrixInverse;

////////////////////////////////////////////////////////////////////////////////

float Noise2D(in vec2 coord, in float wavelength);
float Noise3D(in vec3 coord, in float wavelength);

////////////////////////////////////////////////////////////////////////////////

float map(float s, float a1, float a2, float b1, float b2)
{
    return b1+(s-a1)*(b2-b1)/(a2-a1);
}

vec3 getAmbientLight(float altitude)
{
    // TODO: Actually return the cloud ambient light for this altitude
    return mix(vec3(39.0, 67.0, 87.0) * (1.5/255.0),
               vec3(149.0, 167.0, 200.0) * (1.5/255.0),
               altitude);
}

// Henyey-Greenstein phase function is used instead of the much more complicated
// Mie phase function to approximate the angular distribution of scattered light
float HenyeyGreenstein(float costheta, float g)
{
    float gg = g * g;
    return (1.0 - gg) * pow(1.0 + gg - 2.0 * g * costheta, -1.5) * 0.25;
}

// Placeholder function to read a tiled 2D texture as a 3D texture
float sampleNoiseTexture(sampler2D tex, vec3 p, float size)
{
    p = abs(p);
    vec2 coord = vec2((mod(p.x, size) + floor(mod(p.z, size)) * size) / (size * size),
                      mod(p.y, size) / size);
    float a = texture2D(tex, coord).r;

    coord = vec2((mod(p.x, size) + floor(mod(p.z + 1.0, size)) * size) / (size * size),
                 mod(p.y, size) / size);
    float b = texture2D(tex, coord).r;
    return mix(a, b, fract(p.z));
}

// Get the cloud density in a given sky position and altitude
float getDensity(vec3 pos, float alt)
{
    // Get the low frequency noise that defines the base shape of the cloud
    float lowFreqNoise = sampleNoiseTexture(noise_base_tex, pos / 50.0, 128.0);

    // Modulate the noise by a coverage value (this could be a CPU generated
    // weather texture instead)
    float coverage =
        Noise3D(pos, 10000.0) * 0.625 +
        Noise3D(pos, 5000.0) * 0.500 +
        Noise3D(pos, 2500.0) * 0.125;
    coverage = smoothstep(0.6, 1.0, coverage);
    float baseCloud = map(lowFreqNoise, 1.0 - coverage, 1.0, 0.0, 1.0);
    baseCloud *= coverage;

    // Get the height signal
    float heightSignal = smoothstep(0.0, 0.1, alt) * (1.0 - smoothstep(0.6, 1.0, alt));
    baseCloud *= heightSignal;

    // Apply some erosion to add details
    float highFreqNoise = sampleNoiseTexture(noise_erosion_tex, pos / 50.0, 32.0);
    float density = map(baseCloud, highFreqNoise * CLOUD_EROSION_STRENGTH, 1.0, 0.0, 1.0);

    return density * CLOUD_DENSITY;
}

float shadowing(vec3 pos, float alt, vec3 lightDir)
{
    float stepDelta = 10.0;
    float t = stepDelta;
    float shadow = 1.0;

    for(int i = 0; i < 6; ++i) {
        vec3 samplePoint = pos + lightDir * t;

        float density = getDensity(samplePoint, alt);
        shadow *= exp(-density * stepDelta);

        stepDelta *= 1.6;
        t += stepDelta;
    }

    return shadow;
}

vec3 raySphereIntersection(vec3 cameraPos, vec3 direction, float altitude)
{
    float radius = length(cameraPos) - fg_CameraPositionGeod.z + altitude;
    float a = dot(direction, direction) * 2.0;
    float b = dot(direction, cameraPos) * 2.0;
    float c = dot(cameraPos, cameraPos) - radius * radius;
    float discriminant = b * b - 2.0 * a * c;
    float t = max(0.0, (-b + sqrt(discriminant)) / a);
    vec3 intersection = cameraPos + direction * t;
    return intersection;
}

vec4 rayMarch(vec3 eye, vec3 dir)
{
    vec3 entry;
    float dist;
    // TODO: make this actually work
    if (fg_CameraPositionGeod.z < CLOUD_START_ALT) {
        // We are below the clouds

        // Discard fragments below the horizon
        if (dot(normalize(eye), dir) < 0.0)
            return vec4(0.0, 0.0, 0.0, 1.0);

        entry = raySphereIntersection(eye, dir, CLOUD_START_ALT);
        vec3 exit  = raySphereIntersection(eye, dir, CLOUD_END_ALT);
        dist = distance(entry, exit);
    } else if (fg_CameraPositionGeod.z > CLOUD_END_ALT) {
        // We are over the clouds
        entry = raySphereIntersection(eye, dir, CLOUD_END_ALT);
        vec3 exit = raySphereIntersection(eye, dir, CLOUD_START_ALT);
        dist = distance(entry, exit);
    } else {
        // We are inside the clouds
        entry = eye;
        vec3 exit = raySphereIntersection(eye, dir, CLOUD_END_ALT);
        if (exit == eye)
            exit = raySphereIntersection(eye, dir, CLOUD_START_ALT);
        dist = max(distance(entry, exit), 2000.0);
    }

    float t = 0.0;
    float stepDelta = dist / float(MAX_MARCHING_STEPS);

    // TODO: Pass the Sun direction to the shader
    //vec3 lightDir = normalize((fg_ViewMatrixInverse *
    //                           gl_LightSource[0].position).xyz);
    vec3 lightDir = normalize(vec3(-0.3, -0.1, -0.1));
    float costheta = dot(dir, lightDir);

    float phase = mix(HenyeyGreenstein(costheta, 0.8),
                      HenyeyGreenstein(costheta, -0.2),
                      0.5);

    // The RGB part contains the scattered light color and the alpha value contains
    // the transmittance, both along the ray
    vec4 result = vec4(0.0, 0.0, 0.0, 1.0);

    for (int i = 0; i < MAX_MARCHING_STEPS; ++i) {
        vec3 samplePoint = entry + dir * t;

        float earthRadius = length(fg_CameraPositionCart) - fg_CameraPositionGeod.z;
        float altitude = length(samplePoint) - earthRadius;
        float normalizedAlt = clamp((altitude - CLOUD_START_ALT) /
                                    (CLOUD_END_ALT - CLOUD_START_ALT), 0.0, 1.0);

        float density = getDensity(samplePoint, normalizedAlt);

        // Only evaluate lighting for samples inside clouds
        if (density > 0.0) {
            vec3 ambient = getAmbientLight(normalizedAlt);

            // Analytical integration of Beer–Lambert’s law to calculate
            // transmittance
            vec3 S = (ambient + (phase * shadowing(samplePoint, normalizedAlt, lightDir))) * density;
            //float transmittance = exp(-density * stepDelta);
            float transmittance = max(exp(-density * stepDelta), (exp(-density * stepDelta * 0.25) * 0.7));
            vec3 Sint = (S - S * transmittance) / density;

            result.rgb += result.a * Sint;
            result.a *= transmittance;
        }

        if (result.a <= 0.1)
            break;

        t += stepDelta;
    }

    return result;
}

void main()
{
    vec2 uv = gl_FragCoord.xy / fg_BufferSize;
    vec2 rayNDS = uv * 2.0 - 1.0;
    vec4 rayCS = vec4(rayNDS, -1.0, 1.0);
    vec4 rayVS = fg_ProjectionMatrixInverse * rayCS;
    rayVS = vec4(rayVS.xy, -1.0, 0.0);

    vec3 rayWS = normalize(fg_ViewMatrixInverse * rayVS).xyz;

    vec4 cloudColor = rayMarch(fg_CameraPositionCart, rayWS);
    vec3 backgroundColor = texture2D(color_tex, uv).xyz;

    gl_FragColor = vec4(cloudColor.rgb + backgroundColor.rgb * cloudColor.a, 1.0);
}
Icecode GL
 
Posts: 506
Joined: Thu Aug 12, 2010 12:17 pm
Location: Spain
Callsign: icecode
Version: GIT
OS: Arch Linux

Re: Revisiting clouds

Postby Thorsten » Fri Sep 21, 2018 8:39 am

At this point, I wasn't after the looks, I was after squeezing performance out of the code so that we have room to go for different looks :) It seemed an idea very much worth trying (I'm always a fan of using stuff we know about a problem).

So, what does it act on? A large box?
Thorsten
 
Posts: 10635
Joined: Mon Nov 02, 2009 8:33 am

Re: Revisiting clouds

Postby Icecode GL » Fri Sep 21, 2018 8:43 am

Yeah, that's the problem. As I said earlier it acts on a fullscreen quad and requires uniforms passed by the Compositor, so you can't try it out as it is unfortunately. You can see how it works though and see if it matches with what you have in mind.

You can try using a big billboarded plane in the sky and replacing the fg_View/ProjectionMatrixInverse by the default OpenGL ones and it should work theoretically.

These are the noise textures: https://imgur.com/a/xRYvM4w
The big one is the base cloud shape and the smaller one is the erosion.
Icecode GL
 
Posts: 506
Joined: Thu Aug 12, 2010 12:17 pm
Location: Spain
Callsign: icecode
Version: GIT
OS: Arch Linux

Re: Revisiting clouds

Postby Thorsten » Fri Sep 21, 2018 11:53 am

Thanks (might be a while till I find the time to hack this into a shape I can play with...)
Thorsten
 
Posts: 10635
Joined: Mon Nov 02, 2009 8:33 am

Re: Revisiting clouds

Postby sidi762 » Sat Sep 22, 2018 1:20 pm

Sorry but another bug report(and I'm pretty confident that this is not a feature this time lol)

Image
Image

The cloud border is way too significant after I pulled the slider to full position(please ignore the not aligned slider, I didn't update to the latest nighty). It looks terrible when flying around mountains(as the screenshot shown).

I hope I've described it clear enough. I apologize if I didn't.

Best regards,
Sidi
sidi762
 
Posts: 114
Joined: Sat Jun 18, 2016 8:15 am
Callsign: DAG0762
Version: 2017.1.3
OS: MacOS Sierra

Re: Revisiting clouds

Postby Thorsten » Sat Sep 22, 2018 3:12 pm

It's not a bug, it's a feature - that's inverse Mie scattering. If the cloud appears bright on the fringes from the one side because all light is scattered that way, it necessarily has to appear dark seen from the opposite direction.

It's quite easy to see in the sky in the right light.
Thorsten
 
Posts: 10635
Joined: Mon Nov 02, 2009 8:33 am

Re: Revisiting clouds

Postby sidi762 » Sat Sep 22, 2018 3:23 pm

Yeah I know but I don't think it should apply to the fog ---- and clouds in mountain that mixes with each other is basically fog.

Other then that, I can't find a reason for why the second screenshot looks that horrible.
Last edited by sidi762 on Sat Sep 22, 2018 3:25 pm, edited 1 time in total.
sidi762
 
Posts: 114
Joined: Sat Jun 18, 2016 8:15 am
Callsign: DAG0762
Version: 2017.1.3
OS: MacOS Sierra

Re: Revisiting clouds

Postby Thorsten » Sat Sep 22, 2018 3:25 pm

If you don't like it, don't use it.
Thorsten
 
Posts: 10635
Joined: Mon Nov 02, 2009 8:33 am

Re: Revisiting clouds

Postby sidi762 » Sat Sep 22, 2018 3:33 pm

What I'm doing is trying to make those amazing clouds better. I don't think I deserve this altitude.

And I believe I'm not the only one who doesn't like it. It's basically unrealistic and ugly.

Regards,
Sidi
sidi762
 
Posts: 114
Joined: Sat Jun 18, 2016 8:15 am
Callsign: DAG0762
Version: 2017.1.3
OS: MacOS Sierra

Re: Revisiting clouds

Postby Thorsten » Sat Sep 22, 2018 3:36 pm

Here's the release note:

Please drop me a note when this doesn't work (I've tested NVIDIA and nouveau drivers but can't test Intel or AMD) but at this point please *don't* notify me if the lighting looks odd under some circumstances - this is experimental, and I am still testing and trying to catch all the cases where this doesn't work.
Thorsten
 
Posts: 10635
Joined: Mon Nov 02, 2009 8:33 am

Re: Revisiting clouds

Postby sidi762 » Sat Sep 22, 2018 3:42 pm

I apologize as I didn't know it before.
sidi762
 
Posts: 114
Joined: Sat Jun 18, 2016 8:15 am
Callsign: DAG0762
Version: 2017.1.3
OS: MacOS Sierra

Re: Revisiting clouds

Postby Thorsten » Sat Sep 22, 2018 5:10 pm

I just now realize you probably couldn't have known, I think I only announced this to the mailing list, so my fault.
Thorsten
 
Posts: 10635
Joined: Mon Nov 02, 2009 8:33 am

PreviousNext

Return to Effects and shaders

Who is online

Users browsing this forum: No registered users and 1 guest