+
+# Advanced Cloud model
+
+The advanced cloud model is a new addition to the atmospheres plug-in drawing heavy inspiration from Andrew Schneider's talks about the Nubis cloud model in Decima Engine. There is one new type of texture that has to be provided in the config file, `cloudTypeTexture`.
+
+
+
+
+
+
+
+
+The texture is sampled depending on cloud type (U) and height in the cloud layer (V). The cloud type by default is calculated from the conventional cloud texture (+ some 2D noise and scaling), but you could also easily modify the shader code to read the cloud type directly from some texture.
+
+Use .png for cloud type textures for proper alpha channel support! The alpha channel can be used as a multiplier for the density of the clouds, for example to model fog or cirrus clouds.
+
+The channels of the texture represent
+1. The base density of the cloud. Unlike in Andrew Schneider's work, a single texture has to suffice for this because the cloud bottoms aren't as relevant for this system's main use case, generating views from space. In the shader code, the cloud type is remapped so that all values greater than `CLOUD_COVER_MAX` get assigned the maximum cloud type. This helps produce nice clean cloud tops for very large cloud formations.
+2. Influence strength of low-frequency noises.
+3. High-frequency noise influence. Keep this low for cloud bottoms.
+
+All channels can be fine-tuned for specific scenarios. Currently the system is configured for large scale cloud scapes seen from space by default.
+
+All noises are subject to a LOD system so that high-frequency noises are only sampled when they need to be. If you are changing the noise blending, you might also consider changing the LOD interval constants.
+
+The sample jitter and quality multiplier can be set through the gui and web api.
+
+## References
+### talks by Andrew Schneider at the SIGGRAPH advances in real-time rendering workshop
+[2015](https://advances.realtimerendering.com/s2015/The%20Real-time%20Volumetric%20Cloudscapes%20of%20Horizon%20-%20Zero%20Dawn%20-%20ARTR.pdf)
+
+[2017](https://advances.realtimerendering.com/s2017/Nubis%20-%20Authoring%20Realtime%20Volumetric%20Cloudscapes%20with%20the%20Decima%20Engine%20-%20Final%20.pdf)
+
+[2022](https://advances.realtimerendering.com/s2022/SIGGRAPH2022-Advances-NubisEvolved-NoVideos.pdf)
+
+[2023](https://advances.realtimerendering.com/s2023/Nubis%20Cubed%20(Advances%202023).pdf)
+
+### Phase function used
+
+Jendersie, Johannes, and Eugene d'Eon. "An Approximate Mie Scattering Function for Fog and Cloud Rendering." ACM SIGGRAPH 2023 Talks. 2023. 1-2.
\ No newline at end of file
diff --git a/docs/img/banner-advanced-clouds.jpg b/docs/img/banner-advanced-clouds.jpg
new file mode 100644
index 000000000..c890258de
Binary files /dev/null and b/docs/img/banner-advanced-clouds.jpg differ
diff --git a/docs/img/banner_advanced_clouds.png b/docs/img/banner_advanced_clouds.png
new file mode 100644
index 000000000..698030354
Binary files /dev/null and b/docs/img/banner_advanced_clouds.png differ
diff --git a/docs/img/cloudType.jpg b/docs/img/cloudType.jpg
new file mode 100644
index 000000000..073311b0d
Binary files /dev/null and b/docs/img/cloudType.jpg differ
diff --git a/docs/img/cloudType_density_prior.jpg b/docs/img/cloudType_density_prior.jpg
new file mode 100644
index 000000000..32313fc28
Binary files /dev/null and b/docs/img/cloudType_density_prior.jpg differ
diff --git a/docs/img/cloudType_high_frequency_noise_strength.jpg b/docs/img/cloudType_high_frequency_noise_strength.jpg
new file mode 100644
index 000000000..42f94ae0f
Binary files /dev/null and b/docs/img/cloudType_high_frequency_noise_strength.jpg differ
diff --git a/docs/img/cloudType_low_frequency_noise_strength.jpg b/docs/img/cloudType_low_frequency_noise_strength.jpg
new file mode 100644
index 000000000..98bb8e78f
Binary files /dev/null and b/docs/img/cloudType_low_frequency_noise_strength.jpg differ
diff --git a/plugins/csp-atmospheres/README.md b/plugins/csp-atmospheres/README.md
index 051b8c08c..c80be0ec8 100644
--- a/plugins/csp-atmospheres/README.md
+++ b/plugins/csp-atmospheres/README.md
@@ -78,6 +78,7 @@ They are not physically based but provide some plausible results.
"topAltitude": 80000,
"bottomAltitude": 0,
"cloudTexture": "../share/resources/textures/earth-clouds.jpg",
+ "cloudTypeTexture": "../share/resources/textures/cloudTop.jpg",
"model": "CosmoScoutVR",
"modelSettings": {
"mieAnisotropy": 0.76,
diff --git a/plugins/csp-atmospheres/gui/atmospheres_settings.html b/plugins/csp-atmospheres/gui/atmospheres_settings.html
index 8a3700b8e..af6633294 100644
--- a/plugins/csp-atmospheres/gui/atmospheres_settings.html
+++ b/plugins/csp-atmospheres/gui/atmospheres_settings.html
@@ -40,6 +40,16 @@
Clouds
+
+
+
+
+
Cloud Quality
+
+
+
+
+
+
+
Cloud Max Samples
+
+
+
+
+
+
+
Cloud Sample Jitter
+
+
+
+
+
Cloud Altitude
+
+
+
Cloud Type exp
+
+
+
+
+
+
+
Cloud Type floor
+
+
+
+
+
+
+
Cloud Type ceil
+
+
+
+
+
+
+
Cloud Type min
+
+
+
+
+
+
+
Cloud Type max
+
+
+
+
+
+
+
Cloud Density
+
+
+
+
+
+
+
Cloud Absorption
+
+
+
+
+
+
+
Cloud Cutoff
+
+
+
+
+
+
+
Cloud LF wave length
+
+
+
+
+
+
+
Cloud HF wave length
+
+
+
+
diff --git a/plugins/csp-atmospheres/gui/js/csp-atmospheres.js b/plugins/csp-atmospheres/gui/js/csp-atmospheres.js
index 11c7e4da1..dfce2ae67 100644
--- a/plugins/csp-atmospheres/gui/js/csp-atmospheres.js
+++ b/plugins/csp-atmospheres/gui/js/csp-atmospheres.js
@@ -21,6 +21,20 @@
init() {
CosmoScout.gui.initSlider("atmosphere.setCloudAltitude", 0, 10000, 100, [2000]);
CosmoScout.gui.initSlider("atmosphere.setWaterLevel", -5000, 5000, 20, [0]);
+ CosmoScout.gui.initSlider("atmosphere.setCloudQuality", 0.1, 3, .1, [1]);
+ CosmoScout.gui.initSlider("atmosphere.setCloudMaxSamples", 10, 20000, 10, [400]);
+ CosmoScout.gui.initSlider("atmosphere.setCloudJitter", 0, 1, .01, [.5]);
+ CosmoScout.gui.initSlider("atmosphere.setCloudTypeExponent", 0.01, 5, .1, [1]);
+ CosmoScout.gui.initSlider("atmosphere.setCloudRangeMin", 0, 1, 0.01, [0]);
+ CosmoScout.gui.initSlider("atmosphere.setCloudRangeMax", 0, 1, 0.01, [1]);
+ CosmoScout.gui.initSlider("atmosphere.setCloudTypeMin", 0, 1, .01, [0]);
+ CosmoScout.gui.initSlider("atmosphere.setCloudTypeMax", 0, 1, .01, [1]);
+ CosmoScout.gui.initSlider("atmosphere.setCloudDensityMultiplier", .1, 10, .1, [1]);
+ CosmoScout.gui.initSlider("atmosphere.setCloudAbsorption", 0, 1, .01, [0]);
+ CosmoScout.gui.initSlider("atmosphere.setCloudCoverageExponent", .1, 5, .1, [1]);
+ CosmoScout.gui.initSlider("atmosphere.setCloudCutoff", 0, 1, .01, [.1]);
+ CosmoScout.gui.initSlider("atmosphere.setCloudLFRepetitionScale", 100, 50000, 10, [5000]);
+ CosmoScout.gui.initSlider("atmosphere.setCloudHFRepetitionScale", 100, 20000, 10, [768]);
}
}
diff --git a/plugins/csp-atmospheres/shaders/csp-atmosphere.frag b/plugins/csp-atmospheres/shaders/csp-atmosphere.frag
index 2e38323de..36f800d3d 100644
--- a/plugins/csp-atmospheres/shaders/csp-atmosphere.frag
+++ b/plugins/csp-atmospheres/shaders/csp-atmosphere.frag
@@ -41,6 +41,10 @@ uniform sampler2D uCloudTexture;
uniform float uCloudAltitude;
uniform sampler3D uLimbLuminanceTexture;
uniform vec3 uShadowCoordinates;
+uniform sampler3D uNoiseTexture;
+uniform sampler2D uNoiseTexture2D;
+uniform sampler2D uCloudTypeTexture;
+uniform float uTestUniform;
// outputs
layout(location = 0) out vec3 oColor;
@@ -372,16 +376,654 @@ vec4 getOceanShade(float d) {
// -------------------------------------------------------------------------------------------------
-// Returns the value of the cloud texture at the position described by the three parameters.
-float getCloudDensity(vec3 rayOrigin, vec3 rayDir, float tIntersection) {
+float remap(float v, float min_old, float max_old, float min_new, float max_new){
+ float v_in_0_1 = (v - min_old) / (max_old - min_old);
+ return clamp(v_in_0_1 * (max_new - min_new) + min_new, min(min_new, max_new), max(max_new, min_new));
+}
+
+float INFINITY = 1 / 0.;
+
+// heights between which clouds appear
+float CUMULONIMBUS_START_HEIGHT = 1500;
+float CUMULONIMBUS_END_HEIGHT = 5000;
+uniform float uCoverageExponent = .5;
+float CLOUD_BASE_FRACTION = 0.;
+
+float CLOUD_TYPE_NOISE_WORLEY_SCALE=5.3;
+float CLOUD_TYPE_NOISE_PERLIN_SCALE=30;
+
+uniform float CLOUD_TYPE_EXPONENT = 1.;
+uniform float CLOUD_TYPE_RANGE_START = 0.;
+uniform float CLOUD_TYPE_RANGE_END = 1.;
+uniform float CLOUD_TYPE_MIN = 0.;
+uniform float CLOUD_TYPE_MAX = 1.;
+
+// cloud types are remapped from [0,1] so that all values above this become 1
+float CLOUD_COVER_MAX = .8;
+
+// fraction of the cloud layer thickness by which the thickness is locally varying at high frequency
+float CLOUD_HEIGHT_VARIATION = .1;
+
+// high frequency noises begin to fade at this distance
+float HF_FADE_DISTANCE = 10000;
+// high frequency noises have faded to .5 at this distance
+float HF_END_DISTANCE = 100000;
+
+// low frequency noises begin to fade at this distance
+float LF_FADE_DISTANCE = 500000;
+// low frequency noises have faded to .5 at this distance
+float LF_END_DISTANCE = 2000000;
+
+// parameter for converting cloud density in [0, 1] to density along path in 1/meter
+// Source: I made it up
+float BASE_DENSITY = 2e-3;
+uniform float uCloudDensityMultiplier = 1;
+// how much light gets absorbed relative to being scattered
+uniform float uCloudAbsorption = 0.;
+
+// parameter for tuning the intensity of the (not physically based) multiscattering approximation
+float MS_INTENSITY = .2;
+
+// parameter for setting the clouds to a fancy color
+vec3 CLOUD_COLOR = vec3(1.);
+
+
+// longer segments are cut to this length in scattering calculations to not get erroneously high
+// amounts of inscattering over very long segments
+float MAXIMUM_DIST_BETWEEN_SAMPLES = 250;
+
+// adaptive step size parameters
+
+uniform float CLOUD_QUALITY = 1;
+float HD_CLOSE_DISTANCE = 10000;
+float CLOSE_STEP = 100;
+float MID_STEP = 100;
+float FAR_STEP = 500;
+float MID_DISTANCE = 50000;
+float FAR_DISTANCE = 200000;
+float MAXIMUM_SAMPLES = 400;
+uniform float uCloudCutoff = .2;
+bool SAMPLE_JITTER = true;
+uniform float JITTER_INTENSITY = 0.5;
+bool SECONDARY_RAYS = true;
+
+uniform float uCloudLFRepetitionScale = 5000;
+uniform float uCloudHFRepetitionScale = 1190;
+
+// get the cloud type at these texture coordinates
+// adds high frequency noises to the values from the cloud texture to replace coarse
+// bilinear interpolation artifacts with smaller artifacts that are harder to notice
+// returns the noises alongside the result
+vec4 GetLocalCloudType(vec2 texCoords){
+ vec4 worleySample = textureLod(uNoiseTexture2D, texCoords * CLOUD_TYPE_NOISE_WORLEY_SCALE, 0);
+ vec4 perlinSample = textureLod(uNoiseTexture2D, texCoords * CLOUD_TYPE_NOISE_PERLIN_SCALE, 0);
+ float worleyNoise = worleySample.b;
+ float perlinNoise = perlinSample.g;
+ float cloudType = worleyNoise * 0.5 + perlinNoise * .5;
+ return vec4(remap(pow(cloudType, CLOUD_TYPE_EXPONENT), CLOUD_TYPE_RANGE_START, CLOUD_TYPE_RANGE_END, CLOUD_TYPE_MIN, CLOUD_TYPE_MAX), perlinSample);
+}
+
+// get the low-fidelity cloud prior like in the Nubis cloud system
+// see the 2022 SIGGRAPH talk by Andrew Schneider for what the vertical profile is for
+// this implementation differs from Nubis by taking only one texture for cloud type instead of combining a top and bottom type
+// returns cloud prior, low and high frequency modes
+vec4 GetVerticalProfile(vec3 position){
+ vec2 lngLat = getLngLat(position);
+ vec2 texCoords = vec2(lngLat.x / (2 * PI) + 0.5, 1.0 - lngLat.y / PI + 0.5);
+ float density = remap(textureLod(uCloudTexture, texCoords, 2).r, 0, CLOUD_COVER_MAX, 0, 1);
+ vec4 hcomp_with_noise = GetLocalCloudType(texCoords);
+ float cloudType = hcomp_with_noise.r;
+ vec3 noiseSample = hcomp_with_noise.gba;
+ float endHeight = CUMULONIMBUS_END_HEIGHT * (1 - CLOUD_HEIGHT_VARIATION * noiseSample.g);
+ float topAltitude = PLANET_RADIUS + endHeight;
+ float thickness = endHeight - CUMULONIMBUS_START_HEIGHT;
+ // "progress" in cloud from bottom to top in range 0 to 1
+ float height_in_cloud = remap(length(position), PLANET_RADIUS + CUMULONIMBUS_START_HEIGHT, topAltitude, 0, 1);
+ vec4 cloudConfig = textureLod(uCloudTypeTexture, vec2(cloudType, 1-height_in_cloud), 0);
+ return vec4(cloudConfig.r * density * .95, cloudConfig.g, cloudConfig.b, cloudConfig.a);
+}
+
+// get the density of clouds at a position in 3d space
+vec2 getCumuloNimbusDensity(vec3 position, vec3 cam_pos, bool high_res = true){
+ vec4 cloudConfig = GetVerticalProfile(position);
+ float cloudBase = cloudConfig.r;
+ float erosionStrength = cloudConfig.g;
+ float hfStrength = cloudConfig.b;
+ float cameraDist = length(cam_pos - position);
+ vec4 noise2Dl = textureLod(uNoiseTexture2D, getLngLat(position) * 1, 0);
+ vec4 noise2D = textureLod(uNoiseTexture2D, getLngLat(position) * 5, 0);
+
+ float cloudDensity = pow(cloudBase, uCoverageExponent);
+
+ float lfInfluence = cloudConfig.g;
+ float hfInfluence = hfStrength;
+ if(cameraDist < LF_END_DISTANCE){
+ vec4 lfNoises = textureLod(uNoiseTexture, position / uCloudLFRepetitionScale, 0);
+ // blend between worley and perlin noises using a noise at a different frequency to reduce repetition
+ float lr_worley_noise = (1 - lfNoises.b) * .8 + lfNoises.r * .2;
+ float lr_whispy_noise = lfNoises.r * .2 + lfNoises.g * .8;
+ float blended_lf_noise = mix(lr_worley_noise, lr_whispy_noise, noise2Dl.r);
+ // when camDist is in the fade out range, the noise is mixed with 0.5
+ blended_lf_noise = mix(blended_lf_noise, .5, remap(cameraDist, LF_FADE_DISTANCE, LF_END_DISTANCE, 0, 1)) * .5 + .5 * noise2D.r;
+ // using the formula from Andrew Schneider's SIGGRAPH presentations on Nubis
+ cloudDensity = clamp(lfInfluence * blended_lf_noise - (lfInfluence - cloudDensity), 0, 1);
+
+ if(high_res && cameraDist < HF_END_DISTANCE){
+ vec4 hf_noises = textureLod(uNoiseTexture, position / uCloudHFRepetitionScale, 0);
+ float hr_worley_noise = (1 - hf_noises.b) * .5 + lfNoises.r * .5;
+ float hr_whispy_noise = hf_noises.b * .3 + lfNoises.g * .7;
+ float blended_hf_noise = mix(hr_worley_noise, hr_whispy_noise, lfNoises.r);
+ blended_hf_noise = mix(blended_hf_noise, .5, remap(cameraDist, HF_FADE_DISTANCE, HF_END_DISTANCE, 0, 1));
+ cloudDensity = clamp(hfInfluence * blended_hf_noise - (hfInfluence - cloudDensity), 0, 1);
+ }else{
+ cloudDensity = clamp(hfInfluence * .5 - (hfInfluence - cloudDensity), 0, 1);
+ }
+ }else{
+ // reduce density by assuming noise=0.5
+ // without this operation, the cloud would become more dense at the LOD region border
+ // MUST BE PERFORMED FOR ALL FUTURE NOISE SCALES TO AVOID DISCONTINUITIES
+ cloudDensity = clamp(lfInfluence * .5 - (lfInfluence - cloudDensity), 0, 1);
+ cloudDensity = clamp(hfInfluence * .5 - (hfInfluence - cloudDensity), 0, 1);
+ }
+ if(isnan(cloudDensity)){
+ cloudDensity = 0;
+ }
+
+ float h = length(position) - PLANET_RADIUS;
+ float height_factor = exp(-h / 8000);
+ return vec2(cloudDensity > uCloudCutoff ? height_factor * cloudConfig.a : 0, cloudDensity);
+}
+
+// check if vertical profile is too small too allow for a cloud here
+bool CumuloNimbusGuaranteedFree(vec3 position){
+ vec4 vertical_profile = GetVerticalProfile(position);
+ return vertical_profile.r < .01;//pow(cloudType, .2) * 1.2;
+}
+
+// calculate a cheap approximation of the maximum distance that can be safely skipped
+float CumuloNimbusFreeDistance(vec3 position, vec3 dir, float dist_from_camera, float interval_end){
+ if(!CumuloNimbusGuaranteedFree(position)){
+ return 0;
+ }else{
+ int num_samples = 20;
+ float dist_to_cover = interval_end - dist_from_camera;
+ // further away from the camera, slight flickering of small clouds matters less
+ float base_step = remap(dist_from_camera, 0, FAR_DISTANCE, 100, 1000);
+ if(num_samples * base_step > dist_to_cover){
+ base_step = dist_to_cover / num_samples;
+ }
+ float distance = base_step;
+ float last_distance = 0;
+ bool free = true;
+ int samples_taken = 0;
+ while(samples_taken < num_samples && free){
+ distance += base_step * remap(float(samples_taken) / 10, 0, 1, 1, 3);
+ free = CumuloNimbusGuaranteedFree(position + dir * distance);
+ samples_taken = samples_taken + 1;
+ if(!free){
+ return last_distance;
+ }
+ last_distance = distance;
+ }
+ return distance;
+ }
+}
+
+// mixture of HG, CS and Draine's phase proposed in "An Approximate Mie Scattering Function for Fog and Cloud Rendering"
+float phaseComponent(float alpha, float g, float c){
+ return 1. / 4. / PI * (1 - pow(g, 2)) / pow(1 + pow(g, 2) - 2 * g * c, 3/2.) * (1 + alpha * pow(c, 2)) / (1 + alpha * (1 + 2 * pow(g, 2)) / 3.);
+}
+
+// phase function from "An Approximate Mie Scattering Function for Fog and Cloud Rendering"
+float cloudPhase(vec3 r1, vec3 r2){
+ float c = dot(normalize(r1), normalize(-r2));
+ // particle size parameter
+ float d = 10e-6;
+ float g_HG = exp(-0.0990567 / (d - 1.67154));
+ float g_D = exp(-2.20679 / (d + 3.91029) - 0.428934);
+ float alpha = exp(3.62489) - 8.29288 / (d + 5.52825);
+ float omega_D = exp(-.599085 / (d - .641583)) - .665888;
+ return (1 - omega_D) * phaseComponent(0, g_HG, c) + omega_D * phaseComponent(alpha, g_D, c);
+}
+
+
+// currently just a wrapper for one cloud density function
+// this is the place for adding additional cloud bands
+vec2 getCloudDensity(vec3 position, vec3 cam_pos, bool hf = true){
+ vec2 acc = vec2(0);
+ float height = length(position) - PLANET_RADIUS;
+ if(height > CUMULONIMBUS_START_HEIGHT && height < CUMULONIMBUS_END_HEIGHT){
+ acc += getCumuloNimbusDensity(position, cam_pos, hf);
+ }
+ return acc;
+}
+
+float henyeyGreenstein(vec3 r1, vec3 r2, float g){
+ float cosTheta = dot(normalize(r1), normalize(r2));
+ float temp = 1 + pow(g, .5) + 2 * g * cosTheta;
+ return (1 - g * g) / (temp * pow(temp, .5)) / 4 / PI;
+ return 1 / 4 / PI * (1 - g * g) / pow(1 + g * g + 2 * g * cosTheta, 1.5);
+}
+
+// ray marching the transmittance through the cloud field. No adaptive step size used here
+float raymarchTransmittance(vec3 rayOrigin, vec3 rayDir, vec2 interval, vec3 cam_pos, int samples=10, bool jitter=true){
+ if(interval.y < 0){
+ return 1.;
+ }
+ float t_last = interval.x;
+ float path_transmittance = 1;
+ float MAXIMUM_DIST_BETWEEN_SAMPLES = 1000;
+ float interval_length = interval.y - interval.x;
+
+ float last_extinction = getCloudDensity(rayOrigin, cam_pos).x * BASE_DENSITY * uCloudDensityMultiplier * (1+uCloudAbsorption);
+ vec3 position = rayOrigin;
+ for(int i = 1; i <= samples * CLOUD_QUALITY; ++i){
+ float dist_to_cam = length(rayOrigin - cam_pos);
+ float jitter_value = SAMPLE_JITTER && jitter ? (hash33(position).x - .5) * JITTER_INTENSITY : 0;
+ // reduce jitter on extremely long paths to avoid transmittance turning into pure noise. Introduces some banding
+ jitter_value = remap(interval_length, 0, 500000, jitter_value, 0);
+ // stop clouds seen from below from becoming extremely dark
+ jitter_value = remap(i, 0, samples - 1, 0, jitter_value);
+ // more samples in the close vicinity of the point to capture self-shadowing of smaller clouds with fewer samples
+ float progress = pow((float(i) + jitter_value) / float(samples), 2);
+ float t_now = remap(progress, 0, 1, interval.x, interval.y);
+
+ float dist = t_now - t_last;
+ position = rayOrigin + rayDir * t_now;
+
+ if(!CumuloNimbusGuaranteedFree(position)){
+ float local_density = getCloudDensity(position, cam_pos, false).x;
+
+ float scatter_coefficient = local_density * BASE_DENSITY * uCloudDensityMultiplier;
+ float extinction = scatter_coefficient * (1+uCloudAbsorption);
+ float clamped_dist = clamp(dist, 0, MAXIMUM_DIST_BETWEEN_SAMPLES);
+ float extinction_along_segment = exp(-(extinction + last_extinction) * .5 * clamped_dist);
+ path_transmittance *= extinction_along_segment;
+ last_extinction = extinction;
+ }
+ }
+ return path_transmittance;
+}
+
+// The function where all the integration happens
+// uses adaptive step sizes to bring performance to an acceptable level
+vec4 raymarchInterval(vec3 rayOrigin, vec3 rayDir, vec3 sunDir, vec2 interval, out vec3 path_transmittance){
+ if(interval.y < 0){
+ path_transmittance = vec3(1);
+ return vec4(0, 0, 0, 1);
+ }
+
+ // t values are parameters for rayOrigin + t * rayDir
+ float t_last = interval.x;
+ // progress is in [0, 1]
+ float progress = 0;
+ vec3 inscattering_acc = vec3(0.);
+ path_transmittance = vec3(1);
+
+ float interval_length = interval.y - interval.x;
+ // tracked for debugging
+ float skipped_distance = 0;
+
+ // assuming the sun is infinitely far away, one phase calculation is enough because only single scattering is used
+ float phase = henyeyGreenstein(sunDir, -rayDir, .99);
+
+ vec3 atmo_transmittance;
+ vec3 atmo_inscattering;
+
+ // minimum normed progress that is made iteration to avoid crashes when changing step size logic
+ float minimum_progress = 10. / interval_length;
+
+ // track the start of the current cloud-free part of the interval.
+ // Inscattering from the atmosphere is added only when a cloud is encountered to reduce atmo model evaluations
+ // DO NOT evaluate the atmosphere model for very short intervals
+ float t_cloudfree_start = interval.x;
+ vec2 density_struct = getCloudDensity(rayOrigin + rayDir * interval.x, rayOrigin);
+ float start_density = density_struct.x;
+ bool in_cloud = start_density > 0;
+ bool encounter_cloud = false;
+ float in_cloud_counter = 0;
+ float last_scatter_coefficient = start_density * BASE_DENSITY * uCloudDensityMultiplier;
+ int samples_taken = 0;
+ float maximum_density = 0;
+ float model_density = density_struct.y;
+
+ float quality_multiplier = 1 / CLOUD_QUALITY;
+ float small_step = 200 / interval_length;
+ float big_step = 1000 / interval_length;
+
+ //===== BEGIN OF RAY MARCHING LOOP ======
+
+ while(progress < 1 && samples_taken < MAXIMUM_SAMPLES && path_transmittance.r > .001){
+ samples_taken += 1;
+ float t_now = remap(progress, 0, 1, interval.x, interval.y);
+ vec3 position = rayOrigin + rayDir * t_now;
+
+ //===== BEGIN OF STEP SIZE CONTROL =====
+
+
+ // Skipping regions that are guaranteed to be free of clouds
+ //bool skipped = false;
+ //if(!in_cloud && false){
+ // float freeDistance = CumuloNimbusFreeDistance(position, rayDir, t_now, interval.y);
+ // if(freeDistance > 0){
+ // skipped_distance += freeDistance;
+ // progress += freeDistance / interval_length;
+ // skipped = true;
+ // }
+ //}
+ //float distance_in_interval = t_now - interval.x;
+ //
+ //// step size is increased when transmittance is low
+ //float low_transmittance_multiplier = remap(path_transmittance.r, .5, 0, 1, 3);
+ //low_transmittance_multiplier = remap(t_now, 0, HD_CLOSE_DISTANCE, 1, low_transmittance_multiplier);
+//
+ //// step size is increased when many samples have been taken already
+ //float samples_taken_multiplier = remap(float(samples_taken) / MAXIMUM_SAMPLES, 0, 1, 1, 10);
+ //// step size is decreased when the interval is short
+ //float short_domain_multiplier = remap(interval_length, 0, HD_CLOSE_DISTANCE, .1, 1);
+ //short_domain_multiplier = remap(t_now, 0, MID_DISTANCE, short_domain_multiplier, 1);
+//
+ //float close_to_camera_multiplier = remap(t_now, 0, HD_CLOSE_DISTANCE, .02, 1);
+ ////close_to_camera_multiplier = 1;
+ //
+ //float near_cloud_multiplier = remap(model_density, .5 * uCloudCutoff, uCloudCutoff, 1, .2);
+ //near_cloud_multiplier = remap(t_now, 0, HD_CLOSE_DISTANCE, near_cloud_multiplier, 1);
+ //near_cloud_multiplier = remap(path_transmittance.r, 1, 0.2, near_cloud_multiplier, 1);
+//
+ //float jitter_multiplier = remap(hash33(position).x, 0, 1, 1 - JITTER_INTENSITY * .5, 1 + JITTER_INTENSITY * .5);
+ //jitter_multiplier = SAMPLE_JITTER ? remap(t_now, -100000, FAR_DISTANCE, 1, jitter_multiplier) : 1;
+ //
+//
+ //// there are two parameterized intervals with different functions for the step size as a function of distance from camera
+ //// Note that discontinuities in this function are a VERY BAD idea and give weird artifacts
+ //float step = CLOSE_STEP;
+ //float relevant_distance = remap(t_now, 0, 200000, distance_in_interval, distance_in_interval * .5 + t_now * .5);
+ //relevant_distance = distance_in_interval;
+ //if(relevant_distance < MID_DISTANCE){
+ // step = remap(relevant_distance, 0, MID_DISTANCE, CLOSE_STEP, MID_STEP);
+ //}else{
+ // step = remap(relevant_distance, MID_DISTANCE, FAR_DISTANCE, MID_STEP, FAR_STEP);
+ //}
+ //step = min(step, interval_length / 20);
+ //step /= interval_length;
+ //progress += step * low_transmittance_multiplier * samples_taken_multiplier * short_domain_multiplier * near_cloud_multiplier * jitter_multiplier * close_to_camera_multiplier * quality_multiplier;
+
+ float step_size = 0;
+ if(in_cloud || encounter_cloud){
+ float jitter_multiplier = remap(hash33(position).x, 0, 1, 1 - JITTER_INTENSITY * .5, 1 + JITTER_INTENSITY * .5);
+ float close_up_multiplier = remap(t_now, 0, MID_DISTANCE, .1, 1);
+ step_size = small_step* jitter_multiplier * close_up_multiplier;
+ }else{
+ step_size = min(big_step, .02 * quality_multiplier);
+ }
+ step_size = step_size * quality_multiplier;
+ progress += step_size;
+
+ progress = clamp(progress, 0, 1);
+ t_now = remap(progress, 0, 1, interval.x, interval.y);
+ float dist = t_now - t_last;
+ position = rayOrigin + rayDir * t_now;
+
+ //===== END OF STEP SIZE CONTROL =====
+ //===== BEGIN OF SCATTERING INTEGRATION =====
+ float scatter_coefficient = 0;
+ if(!CumuloNimbusGuaranteedFree(position)){
+ density_struct = getCloudDensity(position, rayOrigin);
+ float local_density = density_struct.x;
+ model_density = density_struct.y;
+ maximum_density = clamp(max(maximum_density, local_density), 0, 1);
+ scatter_coefficient = local_density * BASE_DENSITY * uCloudDensityMultiplier;
+ // the light available at that point
+ vec3 incoming_transmittance = vec3(1);
+ vec3 local_incoming = GetSkyLuminance(position, sunDir, sunDir, incoming_transmittance);
+ // using the luminance provided by the atmosphere model gives unstable results and artifacts
+ // the transmittance from the atmosphere model seems fine though
+ local_incoming = vec3(144809.5,129443.421875,127098.6484375) * incoming_transmittance;
+
+ if(local_density > 0){
+ if(!encounter_cloud){
+ encounter_cloud = true;
+ progress -= step_size;
+ continue;
+ }
+
+ //===== INSIDE CLOUD =====
+ //return vec4(10000, 0, 0, 1);
+ if(!in_cloud){
+ // if entering cloud, integrate the inscattering from the regular atmosphere for the cloud-free interval
+ atmo_inscattering = GetSkyLuminanceToPoint(rayOrigin + rayDir * t_cloudfree_start, position, sunDir, atmo_transmittance);
+ inscattering_acc += path_transmittance * atmo_inscattering;
+ path_transmittance *= atmo_transmittance;
+ }
+ in_cloud = true;
+ in_cloud_counter +=1;
+ t_cloudfree_start = t_now;
+
+ // clamp to keep segment lengths reasonable to not break the lighting model
+ float sdist = clamp(dist, 0, MAXIMUM_DIST_BETWEEN_SAMPLES);
+ // extinction coefficient.
+ // Important to use midpoint for accurate Hillaire integration trick to work
+ float sigma_e = (scatter_coefficient + last_scatter_coefficient) / 2 * (1 + uCloudAbsorption);
+ float transmittance_along_segment = exp(-sigma_e * sdist);
+ vec3 direct_incoming = local_incoming;
+
+ // get transmittance through clouds
+ vec2 top_intersection = intersectSphere(position, sunDir, PLANET_RADIUS + CUMULONIMBUS_END_HEIGHT);
+ int transmittance_samples = int(remap(path_transmittance.r, 0, 1, 10, 2));
+ float in_transmittance = SECONDARY_RAYS ? raymarchTransmittance(position, sunDir, vec2(0, top_intersection.y), rayOrigin, transmittance_samples) : 1;
+ direct_incoming *= in_transmittance;
+
+ // multiscattering approximation like in Nubis
+ float ms_volume = remap(scatter_coefficient * dist, .1, 1.0, 0.0, 1.0);
+ ms_volume *= pow(incoming_transmittance.r, 5);
+ ms_volume *= MS_INTENSITY;
+ vec3 msContrib = local_incoming * ms_volume;
+
+ // Hillaire integration trick for more consistent appearance for different step sizes
+ // considers the transmittance along the segment while integrating the scattering inside that segment
+ // breaks when not using midpoint extinction coefficient
+ vec3 S = scatter_coefficient * direct_incoming * phase * 4 * PI * CLOUD_COLOR + msContrib / sdist;
+ inscattering_acc += path_transmittance * (S - S * transmittance_along_segment) / sigma_e;
+
+ // reduce transmittance along the path
+ path_transmittance *= vec3(transmittance_along_segment);
+ }else{
+ //===== NOT INSIDE CLOUD =====
+ in_cloud = false;
+ encounter_cloud = false;
+ in_cloud_counter = 0;
+ }
+ }
+
+ //===== END OF SCATTERING INTEGRATION =====
+
+ t_last = t_now;
+ last_scatter_coefficient = scatter_coefficient;
+ // terminate ray early when there is almost no transmittance left
+ if (path_transmittance.r < .001){
+ return vec4(inscattering_acc, path_transmittance.r);
+ }
+ // useful when working on adaptive step sizes to ensure that a step is always taken => fewer crashes during development
+ progress += minimum_progress;
+ }
+
+ //===== END OF RAY MARCHING LOOP =====
+
+ float skipped_fraction = skipped_distance / interval_length;
+ //return vec4(skipped_fraction, 1-skipped_fraction, 0, 1)*10000;
+ float sample_ratio = float(samples_taken) / float(MAXIMUM_SAMPLES);
+ //return vec4(sample_ratio, 1-sample_ratio, 0, 1)*10000;
+ //return vec4(maximum_density, 1-maximum_density, 0, 1)*10000;
+ //if(samples_taken == MAXIMUM_SAMPLES){
+ // return vec4(100000, 0, 100000, 1);
+ //}
+
+ // have to add atmo inscattering when exiting the interval
+ if(!in_cloud){
+ atmo_inscattering = GetSkyLuminanceToPoint(rayOrigin + rayDir * t_cloudfree_start, rayOrigin + rayDir * interval.y, sunDir, atmo_transmittance);
+ inscattering_acc += path_transmittance * atmo_inscattering;
+ path_transmittance *= atmo_transmittance;
+ }
+
+ // cheat a little near horizon: approximate scattering from surrounding atmosphere
+ if(length(inscattering_acc) < 100 && path_transmittance.r < 1 - 1e-5){
+ //surroundingColor = vec3(0, 10000, 0);
+ path_transmittance = mix(vec3(1), path_transmittance, length(inscattering_acc) / 100);
+ }
+ return vec4(inscattering_acc, path_transmittance.r);// + mix(vec4(1, 0, 0, 0), vec4(0, 1, 0, 0), float(samples) / 100) * 100000;
+}
+
+// computes the cloud inscattered luminance in xyz and transmittance in alpha
+vec4 getCloudColor(vec3 rayOrigin, vec3 rayDir, vec3 sunDir, float surfaceDistance, out vec3 transmittance) {
+ float thickness = CUMULONIMBUS_END_HEIGHT - CUMULONIMBUS_START_HEIGHT;
+
+ // The sphere radius of the upper-most and lowest cloud layers
+ float topAltitude = PLANET_RADIUS + CUMULONIMBUS_END_HEIGHT;
+ float lowAltitude = PLANET_RADIUS + CUMULONIMBUS_START_HEIGHT;
+
+ vec2 topIntersections = intersectSphere(rayOrigin, rayDir, topAltitude);
+ vec2 lowIntersections = intersectSphere(rayOrigin, rayDir, lowAltitude);
+ vec2 atmo_intersections = intersectAtmosphere(rayOrigin, rayDir);
+
+ float originHeight = length(rayOrigin);
+ bool hitsSurface = surfaceDistance < atmo_intersections.y || intersectSphere(rayOrigin, rayDir, PLANET_RADIUS).y > 0;
+ bool originInClouds = originHeight > lowAltitude && originHeight < topAltitude;
+
+ bool hitTop = topIntersections.y > 0;
+ bool hitBottom = lowIntersections.y > 0;
+
+ bool above = originHeight > topAltitude;
+ bool below = originHeight < lowAltitude;
+
+ vec2 interval1 = vec2(0, -1);
+ vec2 interval2 = vec2(0, -1);
+
+ // use infintiy for no intersection to allow selecting other variables through min operation
+ float lowXcorrected = lowIntersections.x < lowIntersections.y ? lowIntersections.x : INFINITY;
+
+ if(above){
+ interval1.x = topIntersections.x;
+ interval1.y = lowIntersections.x;
+ if(!hitsSurface){
+ if(hitBottom){
+ // ray exits the cloud layer at the bottom and reintersects it, creating a second interval
+ interval2.x = lowIntersections.y;
+ interval2.y = topIntersections.y;
+ }else{
+ // ray leaves the cloud layer on the upper side
+ interval1.y = topIntersections.y;
+ }
+ }else{
+ if(hitBottom){
+ interval1.y = min(surfaceDistance, lowXcorrected);
+ }else{
+ interval1.y = surfaceDistance;
+ }
+ if(!hitTop || surfaceDistance < topIntersections.x){
+ interval1.y = -1;
+ }
+ }
+ }else{
+ if(below){
+ if(lowIntersections.y > 0){
+ interval1.x = lowIntersections.y;
+ interval1.y = topIntersections.y;
+ }else{
+
+ }
+ }else{
+ interval1.x = 0;
+ if(lowIntersections.y > 0){
+ interval1.y = lowIntersections.x;
+ // check for second interval
+ interval2.x = lowIntersections.y;
+ interval2.y = topIntersections.y;
+ }else{
+ interval1.y = topIntersections.y;
+ }
+ }
+ }
+
+ if(interval1.y - interval1.x < 1){
+ interval1.y = -1;
+ interval1.x = 0;
+ }
+
+ interval1.y = min(interval1.y, surfaceDistance);
+ interval2.y = min(interval2.y, surfaceDistance);
+
+ if(interval1.y <= interval1.x){
+ interval1.y = -1;
+ interval1.x = 0;
+ }
+
+ //return vec4(interval1.y - interval1.x / 10000, 10000, 0, 1);
+
+ if(interval2.y < interval2.x){
+ interval2.y = -1;
+ interval2.x = 0;
+ }
+
+ vec3 transmittance_int1 = vec3(1);
+ vec3 transmittance_int2 = vec3(1);
+ vec4 scatter_data1 = raymarchInterval(rayOrigin, rayDir, sunDir, interval1, transmittance_int1);
+ vec4 scatter_data2 = vec4(0,0,0,1);
+ if(scatter_data1.a > .0001){
+ scatter_data2 = raymarchInterval(rayOrigin, rayDir, sunDir, interval2, transmittance_int2);
+ }
+ if(scatter_data1.x < 1e-6 && scatter_data2.x < 1e-6){
+ // no significant inscattering from clouds. just return standard inscattering
+ if(hitsSurface){
+ return vec4(GetSkyLuminanceToPoint(rayOrigin, rayOrigin + surfaceDistance * rayDir, sunDir, transmittance), transmittance);
+ }else{
+ return vec4(GetSkyLuminance(rayOrigin, rayDir, sunDir, transmittance), transmittance.r);
+ }
+ }
+
+ //return vec4(interval1.x, 1, 0, 1);
+ vec3 transmittance_before_int1;
+ vec3 inscattering_before_int1 = GetSkyLuminanceToPoint(rayOrigin, rayOrigin + rayDir * interval1.x, sunDir, transmittance_before_int1);
+
+ if(scatter_data2.x < 1e-6){
+ // no significant inscattering from second interval. return first interval inscattering with transmittance behind
+ vec3 transmittance_behind_int1 = vec3(1);
+ vec3 inscattering_behind_int1 = vec3(0);
+ //return vec4(0, 1000, 0, 1);
+ if(hitsSurface){
+ if(surfaceDistance > interval1.y){
+ inscattering_behind_int1 = GetSkyLuminanceToPoint(rayOrigin + rayDir * interval1.y, rayOrigin + rayDir * surfaceDistance, sunDir, transmittance_behind_int1);
+ }
+ }else{
+ inscattering_behind_int1 = GetSkyLuminance(rayOrigin + rayDir * interval1.y, rayDir, sunDir, transmittance_behind_int1);
+ }
+ vec3 inScatter = inscattering_before_int1 + transmittance_before_int1 * (scatter_data1.xyz + transmittance_int1 * inscattering_behind_int1);
+ transmittance = transmittance_before_int1 * transmittance_int1 * transmittance_behind_int1;
+ return vec4(inScatter, transmittance.x);
+ }else{
+ vec3 transmittance_between_intervals = vec3(1);
+ vec3 inscattering_between_intervals = GetSkyLuminanceToPoint(rayOrigin + rayDir * interval1.y, rayOrigin + rayDir * interval2.x, sunDir, transmittance_between_intervals);
+ vec3 transmittance_behind = vec3(1);
+ vec3 inscattering_behind = GetSkyLuminance(rayOrigin + rayDir * interval2.y, rayDir, sunDir, transmittance_behind);
+
+ transmittance = transmittance_int1 * transmittance_between_intervals * transmittance_int2 * transmittance_behind;
+ vec3 inScatter = inscattering_before_int1 + transmittance_before_int1 * (scatter_data1.xyz + transmittance_int1 * (inscattering_between_intervals + transmittance_between_intervals * (scatter_data2.xyz + transmittance_int2 * inscattering_behind)));
+ return vec4(inScatter, transmittance.x);
+ }
+
+ return vec4(0, 100000, 0, 1);
+}
+
+
+// cloud density function for the standard cosmoscout cloud system
+float getCloudDensityDefault(vec3 rayOrigin, vec3 rayDir, float tIntersection){
vec3 position = rayOrigin + rayDir * tIntersection;
vec2 lngLat = getLngLat(position);
vec2 texCoords = vec2(lngLat.x / (2 * PI) + 0.5, 1.0 - lngLat.y / PI + 0.5);
-#if ENABLE_HDR
- return sRGBtoLinear(texture(uCloudTexture, texCoords).r);
-#else
- return texture(uCloudTexture, texCoords).r;
-#endif
+ #if ENABLE_HDR
+ return sRGBtoLinear(textureLod(uCloudTexture, texCoords, 0).r);
+ #else
+ return textureLod(uCloudTexture, texCoords, 0).r;
+ #endif
}
// Computes the color of the clouds along the ray described by the input parameters. The cloud color
@@ -393,9 +1035,8 @@ float getCloudDensity(vec3 rayOrigin, vec3 rayDir, float tIntersection) {
// observer and the cloud.
// This method contains a couple of hard-coded values which could be made configurable in the
// future.
-vec4 getCloudColor(vec3 rayOrigin, vec3 rayDir, vec3 sunDir, float surfaceDistance) {
-
- // The distance between the top and bottom cloud layers.
+vec4 getCloudColorDefault(vec3 rayOrigin, vec3 rayDir, vec3 sunDir, float surfaceDistance){
+// The distance between the top and bottom cloud layers.
float thickness = uCloudAltitude * 0.5;
// The distance to the planet surface where the fade-out starts.
@@ -448,10 +1089,10 @@ vec4 getCloudColor(vec3 rayOrigin, vec3 rayDir, vec3 sunDir, float surfaceDistan
// Check whether the cloud sphere is intersected from above...
if (intersections.x > 0 && intersections.x < surfaceDistance) {
// hits from above,
- density += getCloudDensity(rayOrigin, rayDir, intersections.x) * fac;
+ density += getCloudDensityDefault(rayOrigin, rayDir, intersections.x) * fac;
} else if (intersections.y < surfaceDistance) {
// ... or from from below.
- density += getCloudDensity(rayOrigin, rayDir, intersections.y) * fac;
+ density += getCloudDensityDefault(rayOrigin, rayDir, intersections.y) * fac;
}
}
}
@@ -481,8 +1122,21 @@ float getCloudShadow(vec3 rayOrigin, vec3 rayDir) {
// Reduce cloud opacity when end point is very close to planet surface.
float fac = clamp(abs(intersections.y) / fadeWidth, 0, 1);
-
- return 1.0 - getCloudDensity(rayOrigin, rayDir, intersections.y) * fac;
+ vec3 position = rayOrigin + rayDir * intersections.y;
+ #if OLD_CLOUDS
+ return 1.0 - getCloudDensityDefault(rayOrigin, rayDir, intersections.y) * fac;
+ #else
+ topAltitude = PLANET_RADIUS + CUMULONIMBUS_END_HEIGHT;
+ thickness = CUMULONIMBUS_END_HEIGHT - CUMULONIMBUS_START_HEIGHT;
+
+ vec2 topIntersections = intersectSphere(rayOrigin, rayDir, topAltitude);
+ vec2 lowIntersections = intersectSphere(rayOrigin, rayDir, topAltitude - thickness);
+ vec2 interval = vec2(lowIntersections.y, topIntersections.y);
+ int transmittance_samples = int(remap(interval.y - interval.x, 10000, 100000, 50, 100));
+ float transmittance = raymarchTransmittance(rayOrigin, rayDir, interval, rayOrigin, transmittance_samples, false);
+ //float transmittance = clamp(raymarchingResult.a, .01, 1.);
+ return transmittance;
+ #endif
}
// Returns a precomputed luminance of the atmosphere ring around the occluder for the
@@ -910,7 +1564,9 @@ void main() {
}
#endif
+ vec3 oColorOld = oColor;
oColor = transmittance * oColor + inScatter;
+ // save for compositing in new cloud model
#if ENABLE_WATER
if (underWater) {
@@ -918,10 +1574,11 @@ void main() {
}
#endif
-// Last, but not least, add the clouds.
#if ENABLE_CLOUDS
+#if OLD_CLOUDS
+// Default cloud model, not truly physically based
if (!underWater) {
- vec4 cloudColor = getCloudColor(vsIn.rayOrigin, rayDir, uSunDir, surfaceDistance);
+ vec4 cloudColor = getCloudColorDefault(vsIn.rayOrigin, rayDir, uSunDir, surfaceDistance);
cloudColor.rgb *= eclipseShadow;
#if !ENABLE_HDR
@@ -930,6 +1587,21 @@ void main() {
oColor = mix(oColor, cloudColor.rgb, cloudColor.a);
}
+#else
+ // new cloud model. Utilizes proper physically based rendering
+ if (!underWater) {
+ vec3 transmittance;
+ vec4 cloudColor = getCloudColor(vsIn.rayOrigin, rayDir, uSunDir, surfaceDistance, transmittance);
+ cloudColor.rgb *= eclipseShadow;
+
+
+#if !ENABLE_HDR
+ cloudColor.rgb = tonemap(cloudColor.rgb / uSunInfo.y);
+#endif
+
+ oColor = oColorOld * transmittance + cloudColor.rgb;
+ }
+#endif
#endif
// If HDR-mode is disabled, we have to convert to sRGB color space.
@@ -944,4 +1616,6 @@ void main() {
}
}
+
+
#endif
\ No newline at end of file
diff --git a/plugins/csp-atmospheres/src/Atmosphere.cpp b/plugins/csp-atmospheres/src/Atmosphere.cpp
index 841790aaa..5da151d02 100644
--- a/plugins/csp-atmospheres/src/Atmosphere.cpp
+++ b/plugins/csp-atmospheres/src/Atmosphere.cpp
@@ -35,6 +35,10 @@
#include
#include
+#include "TileableVolumeNoise.h"
+#include
+#include
+
namespace csp::atmospheres {
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -81,6 +85,14 @@ Atmosphere::~Atmosphere() {
if (mLimbLuminanceTexture != 0) {
glDeleteTextures(1, &mLimbLuminanceTexture);
}
+
+ if (mNoiseTexture != 0) {
+ glDeleteTextures(1, &mNoiseTexture);
+ }
+
+ if(mNoiseTexture2D != 0) {
+ glDeleteTextures(1, &mNoiseTexture2D);
+ }
}
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -117,15 +129,84 @@ void Atmosphere::configure(Plugin::Settings::Atmosphere const& settings) {
if (mSettings.mCloudTexture != settings.mCloudTexture) {
if (settings.mCloudTexture.has_value() && !settings.mCloudTexture.value().empty()) {
mCloudTexture = cs::graphics::TextureLoader::loadFromFile(settings.mCloudTexture.value());
- mCloudTexture->Bind();
- glTexParameteri(mCloudTexture->GetTarget(), GL_TEXTURE_MAX_LOD, 5);
- mCloudTexture->Unbind();
+
+ auto start_time = std::chrono::high_resolution_clock::now();
+ int resx, resy, resz, channels;
+ // higher 3d texture resolutions are more expensive and are not guaranteed to be supported on all systems
+ resx = 32;
+ resy = 32;
+ resz = 32;
+ int resz2, resy2;
+ resz2 = 256;
+ resy2 = 256;
+ channels = 3;
+ std::vector cpu_noise3D(resx * resy * resz * channels, 0);
+ std::vector cpu_noise2D(resz2 * resy2 * channels, 0);
+ for(int i = 0; i < resz; i++){
+ float u = (float)i / (resz - 1);
+ for(int j = 0; j < resy; j++){
+ float v = (float)j / (resy- 1);
+ for(int k = 0; k < resx; k++){
+ float w = (float)k / (resx - 1);
+ cpu_noise3D[(i * resy * resx + j * resx + k) * channels] = Tileable3dNoise::PerlinNoise(glm::vec3(u, v, w), 5, 5);
+ cpu_noise3D[(i * resy * resx + j * resx + k) * channels + 1] = Tileable3dNoise::PerlinNoise(glm::vec3(u, v, w), 3, 10);
+ cpu_noise3D[(i * resy * resx + j * resx + k) * channels + 2] = Tileable3dNoise::WorleyNoise(glm::vec3(u, v, w), 7);
+ }
+ }
+ }
+ for(int i = 0; i < resz2; i++){
+ float u = (float)i / (resz2 - 1);
+ for(int j = 0; j < resy2; j++){
+ float v = (float)j / (resy2- 1);
+ cpu_noise2D[(i * resz2 + j) * channels] = Tileable3dNoise::PerlinNoise(glm::vec3(u, v, 2), 100, 10);
+ cpu_noise2D[(i * resz2 + j) * channels + 1] = Tileable3dNoise::PerlinNoise(glm::vec3(u, v, 2), 500, 10);
+ cpu_noise2D[(i * resz2 + j) * channels + 2] = Tileable3dNoise::WorleyNoise(glm::vec3(u, v, 2), 60);
+ }
+ }
+
+
+ auto end_time = std::chrono::high_resolution_clock::now();
+ auto interval = std::chrono::duration_cast(end_time - start_time);
+ logger().info("generating noise texture on CPU took " +std::to_string(interval.count()) + "ms");
+
+
+ glGenTextures(1, &mNoiseTexture);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_3D, mNoiseTexture);
+ glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+ glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB, resx, resy, resz, 0, GL_RGB, GL_FLOAT, cpu_noise3D.data());
+
+ glGenTextures(1, &mNoiseTexture2D);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, mNoiseTexture2D);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, resz2, resy2, 0, GL_RGB, GL_FLOAT, cpu_noise2D.data());
} else {
mCloudTexture.reset();
}
mShaderDirty = true;
}
+ if(mSettings.mCloudTypeTexture != settings.mCloudTypeTexture){
+ if (settings.mCloudTypeTexture.has_value() && !settings.mCloudTypeTexture.value().empty()) {
+ mCloudTypeTexture = cs::graphics::TextureLoader::loadFromFile(settings.mCloudTypeTexture.value());
+ mCloudTypeTexture->SetWrapS(GL_CLAMP);
+ mCloudTypeTexture->SetWrapT(GL_CLAMP);
+ }else{
+ mCloudTypeTexture.reset();
+ }
+ mShaderDirty = true;
+ }
+
// Reload the limb luminance texture if required.
if (mSettings.mLimbLuminanceTexture != settings.mLimbLuminanceTexture) {
if (settings.mLimbLuminanceTexture.has_value() &&
@@ -149,7 +230,8 @@ void Atmosphere::configure(Plugin::Settings::Atmosphere const& settings) {
mSettings.mEnableWater != settings.mEnableWater ||
mSettings.mEnableWaves != settings.mEnableWaves ||
mSettings.mEnableClouds != settings.mEnableClouds ||
- mSettings.mEnableLimbLuminance != settings.mEnableLimbLuminance) {
+ mSettings.mEnableLimbLuminance != settings.mEnableLimbLuminance ||
+ mSettings.mAdvancedClouds != settings.mAdvancedClouds) {
mShaderDirty = true;
}
@@ -178,6 +260,17 @@ void Atmosphere::createShader(ShaderType type, VistaGLSLShader& shader, Uniforms
sFrag, "ATMOSPHERE_RADIUS", std::to_string(mRadii[0] + mSettings.mTopAltitude));
cs::utils::replaceString(
sFrag, "ENABLE_CLOUDS", std::to_string(mSettings.mEnableClouds.get() && mCloudTexture));
+ if(mSettings.mEnableClouds.get() && !mCloudTexture){
+ logger().warn("No cloud texture in config but clouds are enabled");
+ }
+ cs::utils::replaceString(
+ sFrag, "OLD_CLOUDS", std::to_string(!mSettings.mAdvancedClouds.get() || !(mCloudTypeTexture) || !(mCloudTexture)));
+ if(mSettings.mAdvancedClouds.get() && !mCloudTypeTexture){
+ logger().warn("No cloud type texture in config but advanced clouds activated");
+ }
+ if(mSettings.mAdvancedClouds.get() && !mCloudTexture){
+ logger().warn("No cloud texture in config file but advanced clouds activated");
+ }
cs::utils::replaceString(sFrag, "ENABLE_LIMB_LUMINANCE",
std::to_string(mSettings.mEnableLimbLuminance.get() && mLimbLuminanceTexture));
cs::utils::replaceString(sFrag, "ENABLE_WATER", std::to_string(mSettings.mEnableWater.get()));
@@ -213,6 +306,25 @@ void Atmosphere::createShader(ShaderType type, VistaGLSLShader& shader, Uniforms
uniforms.atmoPanoUniforms = shader.GetUniformLocation("uAtmoPanoUniforms");
uniforms.sunElevation = shader.GetUniformLocation("sunElevation");
uniforms.shadowCoordinates = shader.GetUniformLocation("uShadowCoordinates");
+ uniforms.noiseTexture = shader.GetUniformLocation("uNoiseTexture");
+ uniforms.noiseTexture2D = shader.GetUniformLocation("uNoiseTexture2D");
+ uniforms.cloudTypeTexture = shader.GetUniformLocation("uCloudTypeTexture");
+ uniforms.cloudDensityMultiplier = shader.GetUniformLocation("uCloudDensityMultiplier");
+ uniforms.cloudAbsorption = shader.GetUniformLocation("uCloudAbsorption");
+ uniforms.coverageExponent = shader.GetUniformLocation("uCoverageExponent");
+ uniforms.cloudCutoff = shader.GetUniformLocation("uCloudCutoff");
+ uniforms.cloudLFRepetitionScale = shader.GetUniformLocation("uCloudLFRepetitionScale");
+ uniforms.cloudHFRepetitionScale = shader.GetUniformLocation("uCloudHFRepetitionScale");
+
+ uniforms.cloudQuality = shader.GetUniformLocation("CLOUD_QUALITY");
+ uniforms.cloudMaxSamples = shader.GetUniformLocation("MAXIMUM_SAMPLES");
+ uniforms.cloudJitter = shader.GetUniformLocation("JITTER_INTENSITY");
+ uniforms.cloudTypeExponent = shader.GetUniformLocation("CLOUD_TYPE_EXPONENT");
+ uniforms.cloudRangeMin = shader.GetUniformLocation("CLOUD_TYPE_RANGE_START");
+ uniforms.cloudRangeMax = shader.GetUniformLocation("CLOUD_TYPE_RANGE_END");
+ uniforms.cloudTypeMin = shader.GetUniformLocation("CLOUD_TYPE_MIN");
+ uniforms.cloudTypeMax = shader.GetUniformLocation("CLOUD_TYPE_MAX");
+
// We bind the eclipse shadow map to texture unit 3. The color and depth buffer are bound to 0 and
// 1, 2 is used for the cloud map, 3 is used for the limb luminance texture.
@@ -278,6 +390,13 @@ bool Atmosphere::Do() {
cs::utils::FrameStats::ScopedSamplesCounter samplesCounter("Atmosphere of " + mObjectName);
if (mShaderDirty || mEclipseShadowReceiver->needsRecompilation()) {
+ if (mSettings.mCloudTypeTexture.has_value()) {
+ mCloudTypeTexture = cs::graphics::TextureLoader::loadFromFile(mSettings.mCloudTypeTexture.value());
+ mCloudTypeTexture->SetWrapS(GL_CLAMP);
+ mCloudTypeTexture->SetWrapT(GL_CLAMP);
+ }else{
+ mCloudTypeTexture.reset();
+ }
updateShaders();
mShaderDirty = false;
}
@@ -393,6 +512,20 @@ bool Atmosphere::Do() {
mCloudTexture->Bind(GL_TEXTURE2);
mAtmoShader.SetUniform(mAtmoUniforms.cloudTexture, 2);
mAtmoShader.SetUniform(mAtmoUniforms.cloudAltitude, mSettings.mCloudAltitude.get());
+ mAtmoShader.SetUniform(mAtmoUniforms.cloudDensityMultiplier, mSettings.mCloudDensityMultiplier.get());
+ mAtmoShader.SetUniform(mAtmoUniforms.cloudAbsorption, mSettings.mCloudAbsorption.get());
+ mAtmoShader.SetUniform(mAtmoUniforms.coverageExponent, mSettings.mCloudCoverageExponent.get());
+ mAtmoShader.SetUniform(mAtmoUniforms.cloudCutoff, mSettings.mCloudCutoff.get());
+ mAtmoShader.SetUniform(mAtmoUniforms.cloudLFRepetitionScale, mSettings.mCloudLFRepetitionScale.get());
+ mAtmoShader.SetUniform(mAtmoUniforms.cloudHFRepetitionScale, mSettings.mCloudHFRepetitionScale.get());
+ mAtmoShader.SetUniform(mAtmoUniforms.cloudQuality, mSettings.mCloudQuality.get());
+ mAtmoShader.SetUniform(mAtmoUniforms.cloudMaxSamples, mSettings.mCloudMaxSamples.get());
+ mAtmoShader.SetUniform(mAtmoUniforms.cloudJitter, mSettings.mCloudJitter.get());
+ mAtmoShader.SetUniform(mAtmoUniforms.cloudTypeExponent, mSettings.mCloudTypeExponent.get());
+ mAtmoShader.SetUniform(mAtmoUniforms.cloudRangeMin, mSettings.mCloudRangeMin.get());
+ mAtmoShader.SetUniform(mAtmoUniforms.cloudRangeMax, mSettings.mCloudRangeMax.get());
+ mAtmoShader.SetUniform(mAtmoUniforms.cloudTypeMin, mSettings.mCloudTypeMin.get());
+ mAtmoShader.SetUniform(mAtmoUniforms.cloudTypeMax, mSettings.mCloudTypeMax.get());
}
if (mSettings.mEnableLimbLuminance.get() && mLimbLuminanceTexture) {
@@ -401,6 +534,24 @@ bool Atmosphere::Do() {
mAtmoShader.SetUniform(mAtmoUniforms.limbLuminanceTexture, 3);
}
+ if (mSettings.mEnableClouds.get() && mNoiseTexture) {
+ glActiveTexture(GL_TEXTURE4);
+ glBindTexture(GL_TEXTURE_3D, mNoiseTexture);
+ mAtmoShader.SetUniform(mAtmoUniforms.noiseTexture, 4);
+ }
+
+ if (mSettings.mEnableClouds.get() && mCloudTypeTexture) {
+ mCloudTypeTexture->Bind(GL_TEXTURE5);
+ mAtmoShader.SetUniform(mAtmoUniforms.cloudTypeTexture, 5);
+ }
+
+ if (mSettings.mEnableClouds.get() && mNoiseTexture2D) {
+ glActiveTexture(GL_TEXTURE7);
+ glBindTexture(GL_TEXTURE_2D, mNoiseTexture2D);
+ mAtmoShader.SetUniform(mAtmoUniforms.noiseTexture2D, 7);
+ }
+
+
glUniformMatrix4fv(
mAtmoUniforms.inverseModelViewMatrix, 1, GL_FALSE, glm::value_ptr(glm::mat4(matInvMV)));
glUniformMatrix4fv(mAtmoUniforms.scaleMatrix, 1, GL_FALSE, glm::value_ptr(glm::mat4(matScale)));
diff --git a/plugins/csp-atmospheres/src/Atmosphere.hpp b/plugins/csp-atmospheres/src/Atmosphere.hpp
index f0d62183d..e3ded4767 100644
--- a/plugins/csp-atmospheres/src/Atmosphere.hpp
+++ b/plugins/csp-atmospheres/src/Atmosphere.hpp
@@ -58,6 +58,8 @@ class Atmosphere : public IVistaOpenGLDraw {
uint32_t colorBuffer = 0;
uint32_t waterLevel = 0;
uint32_t cloudTexture = 0;
+ uint32_t cloudTypeTexture = 0;
+ uint32_t noiseTexture2D = 0;
uint32_t cloudAltitude = 0;
uint32_t limbLuminanceTexture = 0;
uint32_t inverseModelViewMatrix = 0;
@@ -66,6 +68,22 @@ class Atmosphere : public IVistaOpenGLDraw {
uint32_t modelMatrix = 0;
uint32_t modelViewProjectionMatrix = 0;
uint32_t shadowCoordinates = 0;
+ uint32_t noiseTexture = 0;
+ uint32_t cloudDensityMultiplier = 0;
+ uint32_t cloudAbsorption = 0;
+ uint32_t coverageExponent = 0;
+ uint32_t cloudCutoff = 0;
+ uint32_t cloudLFRepetitionScale = 0;
+ uint32_t cloudHFRepetitionScale = 0;
+
+ uint32_t cloudQuality = 0;
+ uint32_t cloudMaxSamples = 0;
+ uint32_t cloudJitter = 0;
+ uint32_t cloudTypeExponent = 0;
+ uint32_t cloudRangeMin = 0;
+ uint32_t cloudRangeMax = 0;
+ uint32_t cloudTypeMin = 0;
+ uint32_t cloudTypeMax = 0;
// Only used by the panorama shader.
uint32_t atmoPanoUniforms = 0;
@@ -90,6 +108,9 @@ class Atmosphere : public IVistaOpenGLDraw {
std::shared_ptr mHDRBuffer;
std::shared_ptr mEclipseShadowReceiver;
std::unique_ptr mCloudTexture;
+ std::unique_ptr mCloudTypeTexture;
+ GLuint mNoiseTexture = 0;
+ GLuint mNoiseTexture2D = 0;
GLuint mLimbLuminanceTexture = 0;
glm::dvec3 mRadii = glm::dvec3(1.0, 1.0, 1.0);
diff --git a/plugins/csp-atmospheres/src/Plugin.cpp b/plugins/csp-atmospheres/src/Plugin.cpp
index 374306066..e6e4086c2 100644
--- a/plugins/csp-atmospheres/src/Plugin.cpp
+++ b/plugins/csp-atmospheres/src/Plugin.cpp
@@ -67,11 +67,27 @@ void from_json(nlohmann::json const& j, Plugin::Settings::Atmosphere& o) {
cs::core::Settings::deserialize(j, "enableWaves", o.mEnableWaves);
cs::core::Settings::deserialize(j, "waterLevel", o.mWaterLevel);
cs::core::Settings::deserialize(j, "enableClouds", o.mEnableClouds);
+ cs::core::Settings::deserialize(j, "advancedClouds", o.mAdvancedClouds);
cs::core::Settings::deserialize(j, "cloudTexture", o.mCloudTexture);
+ cs::core::Settings::deserialize(j, "cloudTypeTexture", o.mCloudTypeTexture);
cs::core::Settings::deserialize(j, "cloudAltitude", o.mCloudAltitude);
cs::core::Settings::deserialize(j, "enableLimbLuminance", o.mEnableLimbLuminance);
cs::core::Settings::deserialize(j, "limbLuminanceTexture", o.mLimbLuminanceTexture);
cs::core::Settings::deserialize(j, "renderSkydome", o.mRenderSkydome);
+ cs::core::Settings::deserialize(j, "cloudDensityMultiplier", o.mCloudDensityMultiplier);
+ cs::core::Settings::deserialize(j, "cloudAbsorption", o.mCloudAbsorption);
+ cs::core::Settings::deserialize(j, "cloudCoverageExponent", o.mCloudCoverageExponent);
+ cs::core::Settings::deserialize(j, "cloudCutoff", o.mCloudCutoff);
+ cs::core::Settings::deserialize(j, "cloudLFRepetitionScale", o.mCloudLFRepetitionScale);
+ cs::core::Settings::deserialize(j, "cloudHFRepetitionScale", o.mCloudHFRepetitionScale);
+
+ cs::core::Settings::deserialize(j, "cloudQuality", o.mCloudQuality);
+ cs::core::Settings::deserialize(j, "cloudMaxSamples", o.mCloudMaxSamples);
+ cs::core::Settings::deserialize(j, "cloudTypeExponent", o.mCloudTypeExponent);
+ cs::core::Settings::deserialize(j, "cloudRangeMin", o.mCloudRangeMin);
+ cs::core::Settings::deserialize(j, "cloudRangeMax", o.mCloudRangeMax);
+ cs::core::Settings::deserialize(j, "cloudTypeMin", o.mCloudTypeMin);
+ cs::core::Settings::deserialize(j, "cloudTypeMax", o.mCloudTypeMax);
}
void to_json(nlohmann::json& j, Plugin::Settings::Atmosphere const& o) {
@@ -83,11 +99,27 @@ void to_json(nlohmann::json& j, Plugin::Settings::Atmosphere const& o) {
cs::core::Settings::serialize(j, "enableWaves", o.mEnableWaves);
cs::core::Settings::serialize(j, "waterLevel", o.mWaterLevel);
cs::core::Settings::serialize(j, "enableClouds", o.mEnableClouds);
+ cs::core::Settings::serialize(j, "advancedClouds", o.mAdvancedClouds);
cs::core::Settings::serialize(j, "cloudTexture", o.mCloudTexture);
+ cs::core::Settings::serialize(j, "cloudTypeTexture", o.mCloudTypeTexture);
cs::core::Settings::serialize(j, "cloudAltitude", o.mCloudAltitude);
cs::core::Settings::serialize(j, "enableLimbLuminance", o.mEnableLimbLuminance);
cs::core::Settings::serialize(j, "limbLuminanceTexture", o.mLimbLuminanceTexture);
cs::core::Settings::serialize(j, "renderSkydome", o.mRenderSkydome);
+ cs::core::Settings::serialize(j, "cloudDensityMultiplier", o.mCloudDensityMultiplier);
+ cs::core::Settings::serialize(j, "cloudAbsorption", o.mCloudAbsorption);
+ cs::core::Settings::serialize(j, "cloudCoverageExponent", o.mCloudCoverageExponent);
+ cs::core::Settings::serialize(j, "cloudCutoff", o.mCloudCutoff);
+ cs::core::Settings::serialize(j, "cloudLFRepetitionScale", o.mCloudLFRepetitionScale);
+ cs::core::Settings::serialize(j, "cloudHFRepetitionScale", o.mCloudHFRepetitionScale);
+
+ cs::core::Settings::serialize(j, "cloudQuality", o.mCloudQuality);
+ cs::core::Settings::serialize(j, "cloudMaxSamples", o.mCloudMaxSamples);
+ cs::core::Settings::serialize(j, "cloudTypeExponent", o.mCloudTypeExponent);
+ cs::core::Settings::serialize(j, "cloudRangeMin", o.mCloudRangeMin);
+ cs::core::Settings::serialize(j, "cloudRangeMax", o.mCloudRangeMax);
+ cs::core::Settings::serialize(j, "cloudTypeMin", o.mCloudTypeMin);
+ cs::core::Settings::serialize(j, "cloudTypeMax", o.mCloudTypeMax);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -131,6 +163,8 @@ void Plugin::init() {
mGuiManager->setSliderValue("atmosphere.setWaterLevel", settings.mWaterLevel.get());
mGuiManager->setCheckboxValue(
"atmosphere.setEnableClouds", settings.mEnableClouds.get());
+ mGuiManager->setCheckboxValue(
+ "atmosphere.setAdvancedClouds", settings.mAdvancedClouds.get());
mGuiManager->setSliderValue(
"atmosphere.setCloudAltitude", settings.mCloudAltitude.get());
}
@@ -174,6 +208,16 @@ void Plugin::init() {
}
}));
+ mGuiManager->getGui()->registerCallback("atmosphere.setAdvancedClouds",
+ "Enables or disables old cloud system. New cloud system is used if set to false",
+ std::function([this](bool enable) {
+ if (!mActiveAtmosphere.empty()) {
+ auto& settings = mPluginSettings->mAtmospheres.at(mActiveAtmosphere);
+ settings.mAdvancedClouds = enable;
+ mAtmospheres.at(mActiveAtmosphere)->configure(settings);
+ }
+ }));
+
mGuiManager->getGui()->registerCallback("atmosphere.setCloudAltitude",
"Higher values create a more realistic atmosphere.", std::function([this](double value) {
if (!mActiveAtmosphere.empty()) {
@@ -196,6 +240,142 @@ void Plugin::init() {
"Enables or disables rendering of atmospheres.",
std::function([this](bool enable) { mPluginSettings->mEnable = enable; }));
+ mGuiManager->getGui()->registerCallback("atmosphere.setCloudQuality",
+ "Divide all step sizes by this value", std::function([this](double value) {
+ if (!mActiveAtmosphere.empty()) {
+ auto& settings = mPluginSettings->mAtmospheres.at(mActiveAtmosphere);
+ settings.mCloudQuality = static_cast(value);
+ mAtmospheres.at(mActiveAtmosphere)->configure(settings);
+ }
+ }));
+
+ mGuiManager->getGui()->registerCallback("atmosphere.setCloudMaxSamples",
+ "Maximum number of samples taken on a single ray by the cloud system. Increase this value if you see pink artifacts in the sky.", std::function([this](double value) {
+ if (!mActiveAtmosphere.empty()) {
+ auto& settings = mPluginSettings->mAtmospheres.at(mActiveAtmosphere);
+ settings.mCloudMaxSamples = static_cast(value);
+ mAtmospheres.at(mActiveAtmosphere)->configure(settings);
+ }
+ }));
+
+ mGuiManager->getGui()->registerCallback("atmosphere.setCloudJitter",
+ "Set the intensity of random jittering of samples. Increase to break up banding artifacts. Decrease if images are too noisy.", std::function([this](double value) {
+ if (!mActiveAtmosphere.empty()) {
+ auto& settings = mPluginSettings->mAtmospheres.at(mActiveAtmosphere);
+ settings.mCloudJitter = static_cast(value);
+ mAtmospheres.at(mActiveAtmosphere)->configure(settings);
+ }
+ }));
+
+ mGuiManager->getGui()->registerCallback("atmosphere.setCloudTypeExponent",
+ "Exponent on the cloud type noise before remapping. values < 1 bias towards more dense cloud types",
+ std::function([this](double value) {
+ if (!mActiveAtmosphere.empty()) {
+ auto& settings = mPluginSettings->mAtmospheres.at(mActiveAtmosphere);
+ settings.mCloudTypeExponent = static_cast(value);
+ mAtmospheres.at(mActiveAtmosphere)->configure(settings);
+ }
+ }));
+
+ mGuiManager->getGui()->registerCallback("atmosphere.setCloudRangeMin",
+ "All cloud type noise values below this value are mapped to the minimum cloud type",
+ std::function([this](double value) {
+ if (!mActiveAtmosphere.empty()) {
+ auto& settings = mPluginSettings->mAtmospheres.at(mActiveAtmosphere);
+ settings.mCloudRangeMin = static_cast(value);
+ mAtmospheres.at(mActiveAtmosphere)->configure(settings);
+ }
+ }));
+
+ mGuiManager->getGui()->registerCallback("atmosphere.setCloudRangeMax",
+ "All cloud type noise values above this value are mapped to the maximum cloud type",
+ std::function([this](double value) {
+ if (!mActiveAtmosphere.empty()) {
+ auto& settings = mPluginSettings->mAtmospheres.at(mActiveAtmosphere);
+ settings.mCloudRangeMax = static_cast(value);
+ mAtmospheres.at(mActiveAtmosphere)->configure(settings);
+ }
+ }));
+
+ mGuiManager->getGui()->registerCallback("atmosphere.setCloudTypeMin",
+ "Minimum cloud type to be used", std::function([this](double value) {
+ if (!mActiveAtmosphere.empty()) {
+ auto& settings = mPluginSettings->mAtmospheres.at(mActiveAtmosphere);
+ settings.mCloudTypeMin = static_cast(value);
+ mAtmospheres.at(mActiveAtmosphere)->configure(settings);
+ }
+ }));
+
+ mGuiManager->getGui()->registerCallback("atmosphere.setCloudTypeMax",
+ "Maximum cloud type to be used", std::function([this](double value) {
+ if (!mActiveAtmosphere.empty()) {
+ auto& settings = mPluginSettings->mAtmospheres.at(mActiveAtmosphere);
+ settings.mCloudTypeMax = static_cast(value);
+ mAtmospheres.at(mActiveAtmosphere)->configure(settings);
+ }
+ }));
+
+ mGuiManager->getGui()->registerCallback("atmosphere.setCloudDensityMultiplier",
+ "Set a multiplier for the cloud density", std::function([this](double value) {
+ if (!mActiveAtmosphere.empty()) {
+ auto& settings = mPluginSettings->mAtmospheres.at(mActiveAtmosphere);
+ settings.mCloudDensityMultiplier = static_cast(value);
+ mAtmospheres.at(mActiveAtmosphere)->configure(settings);
+ }
+ }));
+
+ mGuiManager->getGui()->registerCallback("atmosphere.setCloudAbsorption",
+ "Set the fraction of light absorbed in interactions with the cloud medium. Real clouds "
+ "scatter almost all light with little absorption.",
+ std::function([this](double value) {
+ if (!mActiveAtmosphere.empty()) {
+ auto& settings = mPluginSettings->mAtmospheres.at(mActiveAtmosphere);
+ settings.mCloudAbsorption = static_cast(value);
+ mAtmospheres.at(mActiveAtmosphere)->configure(settings);
+ }
+ }));
+
+ mGuiManager->getGui()->registerCallback("atmosphere.setCloudCoverageExponent",
+ "Exponent applied to the vertical profile before adding noises.",
+ std::function([this](double value) {
+ if (!mActiveAtmosphere.empty()) {
+ auto& settings = mPluginSettings->mAtmospheres.at(mActiveAtmosphere);
+ settings.mCloudCoverageExponent = static_cast(value);
+ mAtmospheres.at(mActiveAtmosphere)->configure(settings);
+ }
+ }));
+
+ mGuiManager->getGui()->registerCallback("atmosphere.setCloudCutoff",
+ "Clouds will be rendered for all locations where the cloud model produces a value larger "
+ "than the cutoff",
+ std::function([this](double value) {
+ if (!mActiveAtmosphere.empty()) {
+ auto& settings = mPluginSettings->mAtmospheres.at(mActiveAtmosphere);
+ settings.mCloudCutoff = static_cast(value);
+ mAtmospheres.at(mActiveAtmosphere)->configure(settings);
+ }
+ }));
+
+ mGuiManager->getGui()->registerCallback("atmosphere.setCloudLFRepetitionScale",
+ "Set the distance (in meters) that one repetition of the low frequency noise texture covers",
+ std::function([this](double value) {
+ if (!mActiveAtmosphere.empty()) {
+ auto& settings = mPluginSettings->mAtmospheres.at(mActiveAtmosphere);
+ settings.mCloudLFRepetitionScale = static_cast(value);
+ mAtmospheres.at(mActiveAtmosphere)->configure(settings);
+ }
+ }));
+
+ mGuiManager->getGui()->registerCallback("atmosphere.setCloudHFRepetitionScale",
+ "Set the distance (in meters) that one repetition of the high frequency noise texture covers",
+ std::function([this](double value) {
+ if (!mActiveAtmosphere.empty()) {
+ auto& settings = mPluginSettings->mAtmospheres.at(mActiveAtmosphere);
+ settings.mCloudHFRepetitionScale = static_cast(value);
+ mAtmospheres.at(mActiveAtmosphere)->configure(settings);
+ }
+ }));
+
// Load settings.
onLoad();
@@ -219,8 +399,23 @@ void Plugin::deInit() {
mGuiManager->getGui()->unregisterCallback("atmosphere.setEnableWaves");
mGuiManager->getGui()->unregisterCallback("atmosphere.setWaterLevel");
mGuiManager->getGui()->unregisterCallback("atmosphere.setEnableClouds");
+ mGuiManager->getGui()->unregisterCallback("atmosphere.setAdvancedClouds");
mGuiManager->getGui()->unregisterCallback("atmosphere.setCloudAltitude");
mGuiManager->getGui()->unregisterCallback("atmosphere.setEnableLimbLuminance");
+ mGuiManager->getGui()->unregisterCallback("atmosphere.setCloudDensityMultiplier");
+ mGuiManager->getGui()->unregisterCallback("atmosphere.setCloudAbsorption");
+ mGuiManager->getGui()->unregisterCallback("atmosphere.setCloudCoverageExponent");
+ mGuiManager->getGui()->unregisterCallback("atmosphere.setCloudCutoff");
+ mGuiManager->getGui()->unregisterCallback("atmosphere.setCloudLFRepetitionScale");
+ mGuiManager->getGui()->unregisterCallback("atmosphere.setCloudHFRepetitionScale");
+
+ mGuiManager->getGui()->unregisterCallback("atmosphere.setCloudQuality");
+ mGuiManager->getGui()->unregisterCallback("atmpsphere.setCloudMaxSamples");
+ mGuiManager->getGui()->unregisterCallback("atmosphere.setCloudTypeExponent");
+ mGuiManager->getGui()->unregisterCallback("atmosphere.setCloudRangeMin");
+ mGuiManager->getGui()->unregisterCallback("atmosphere.setCloudRangeMax");
+ mGuiManager->getGui()->unregisterCallback("atmosphere.setCloudTypeMin");
+ mGuiManager->getGui()->unregisterCallback("atmosphere.setCloudTypeMax");
mSolarSystem->pActiveObject.disconnect(mActiveObjectConnection);
mAllSettings->onLoad().disconnect(mOnLoadConnection);
diff --git a/plugins/csp-atmospheres/src/Plugin.hpp b/plugins/csp-atmospheres/src/Plugin.hpp
index 100cfd9f1..3fe39d6c7 100644
--- a/plugins/csp-atmospheres/src/Plugin.hpp
+++ b/plugins/csp-atmospheres/src/Plugin.hpp
@@ -50,10 +50,28 @@ class Plugin : public cs::core::PluginBase {
cs::utils::DefaultProperty mWaterLevel{0.F}; ///< In meters.
cs::utils::DefaultProperty mEnableClouds{true};
std::optional mCloudTexture; ///< Path to the cloud texture.
+ std::optional mCloudTypeTexture;
cs::utils::DefaultProperty mCloudAltitude{3000.F}; ///< In meters.
cs::utils::DefaultProperty mEnableLimbLuminance{true};
+ cs::utils::DefaultProperty mAdvancedClouds{false};
std::optional mLimbLuminanceTexture; ///< Path to the limb luminance texture.
+ /// advanced cloud model additional parameters
+ cs::utils::DefaultProperty mCloudQuality{1.f};
+ cs::utils::DefaultProperty mCloudMaxSamples{400.f};
+ cs::utils::DefaultProperty mCloudJitter{.5f};
+ cs::utils::DefaultProperty mCloudTypeExponent{1.f};
+ cs::utils::DefaultProperty mCloudRangeMin{0.f};
+ cs::utils::DefaultProperty mCloudRangeMax{1.f};
+ cs::utils::DefaultProperty mCloudTypeMin{0.f};
+ cs::utils::DefaultProperty mCloudTypeMax{1.f};
+ cs::utils::DefaultProperty mCloudDensityMultiplier{1.f};
+ cs::utils::DefaultProperty mCloudAbsorption{0.f};
+ cs::utils::DefaultProperty mCloudCoverageExponent{1.f};
+ cs::utils::DefaultProperty mCloudCutoff{.1f};
+ cs::utils::DefaultProperty mCloudLFRepetitionScale{5000.f};
+ cs::utils::DefaultProperty mCloudHFRepetitionScale{1231.f};
+
/// If this is set to true, the plugin will save a fish-eye view of the sky to a file one
/// the preprocessing is done.
cs::utils::DefaultProperty mRenderSkydome{false};
diff --git a/plugins/csp-atmospheres/src/TileableVolumeNoise.cpp b/plugins/csp-atmospheres/src/TileableVolumeNoise.cpp
new file mode 100644
index 000000000..81a605f25
--- /dev/null
+++ b/plugins/csp-atmospheres/src/TileableVolumeNoise.cpp
@@ -0,0 +1,106 @@
+// The MIT License (MIT)
+//
+// Copyright(c) 2017 Sébastien Hillaire
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#include "TileableVolumeNoise.h"
+
+#include
+#include
+// Perlin noise based on GLM http://glm.g-truc.net
+// Worley noise based on https://www.shadertoy.com/view/Xl2XRR by Marc-Andre Loyer
+
+float Tileable3dNoise::hash(float n)
+{
+ return glm::fract(sin(n+1.951f) * 43758.5453f);
+}
+
+// hash based 3d value noise
+float Tileable3dNoise::noise(const glm::vec3& x)
+{
+ glm::vec3 p = glm::floor(x);
+ glm::vec3 f = glm::fract(x);
+
+ f = f*f*(glm::vec3(3.0f) - glm::vec3(2.0f) * f);
+ float n = p.x + p.y*57.0f + 113.0f*p.z;
+ return glm::mix(
+ glm::mix(
+ glm::mix(hash(n + 0.0f), hash(n + 1.0f), f.x),
+ glm::mix(hash(n + 57.0f), hash(n + 58.0f), f.x),
+ f.y),
+ glm::mix(
+ glm::mix(hash(n + 113.0f), hash(n + 114.0f), f.x),
+ glm::mix(hash(n + 170.0f), hash(n + 171.0f), f.x),
+ f.y),
+ f.z);
+}
+
+float Tileable3dNoise::Cells(const glm::vec3& p, float cellCount)
+{
+ const glm::vec3 pCell = p * cellCount;
+ float d = 1.0e10;
+ for (int xo = -1; xo <= 1; xo++)
+ {
+ for (int yo = -1; yo <= 1; yo++)
+ {
+ for (int zo = -1; zo <= 1; zo++)
+ {
+ glm::vec3 tp = glm::floor(pCell) + glm::vec3(xo, yo, zo);
+
+ tp = pCell - tp - noise(glm::mod(tp, cellCount / 1));
+
+ d = glm::min(d, dot(tp, tp));
+ }
+ }
+ }
+ d = std::fminf(d, 1.0f);
+ d = std::fmaxf(d, 0.0f);
+ return d;
+}
+
+
+float Tileable3dNoise::WorleyNoise(const glm::vec3& p, float cellCount)
+{
+ return Cells(p, cellCount);
+}
+
+
+
+float Tileable3dNoise::PerlinNoise(const glm::vec3& pIn, float frequency, int octaveCount)
+{
+ const float octaveFrenquencyFactor = 2; // noise frequency factor between octave, forced to 2
+
+ // Compute the sum for each octave
+ float sum = 0.0f;
+ float weightSum = 0.0f;
+ float weight = 0.5f;
+ for (int oct = 0; oct < octaveCount; oct++)
+ {
+ // Perlin vec3 is bugged in GLM on the Z axis :(, black stripes are visible
+ // So instead we use 4d Perlin and only use xyz...
+ //glm::vec3 p(x * freq, y * freq, z * freq);
+ //float val = glm::perlin(p, glm::vec3(freq)) *0.5 + 0.5;
+
+ glm::vec4 p = glm::vec4(pIn.x, pIn.y, pIn.z, 0.0f) * glm::vec4(frequency);
+ float val = glm::perlin(p, glm::vec4(frequency));
+
+ sum += val * weight;
+ weightSum += weight;
+
+ weight *= weight;
+ frequency *= octaveFrenquencyFactor;
+ }
+
+ float noise = (sum / weightSum) *0.5f + 0.5f;
+ noise = std::fminf(noise, 1.0f);
+ noise = std::fmaxf(noise, 0.0f);
+ return noise;
+ }
+
+
+
+
diff --git a/plugins/csp-atmospheres/src/TileableVolumeNoise.h b/plugins/csp-atmospheres/src/TileableVolumeNoise.h
new file mode 100644
index 000000000..1517601bc
--- /dev/null
+++ b/plugins/csp-atmospheres/src/TileableVolumeNoise.h
@@ -0,0 +1,42 @@
+// The MIT License (MIT)
+//
+// Copyright(c) 2017 Sébastien Hillaire
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#ifndef D_TILEABLE3DNOISE
+#define D_TILEABLE3DNOISE
+
+#include
+
+class Tileable3dNoise
+{
+public:
+
+ /// @return Tileable Worley noise value in [0, 1].
+ /// @param p 3d coordinate in [0, 1], being the range of the repeatable pattern.
+ /// @param cellCount the number of cell for the repetitive pattern.
+ static float WorleyNoise(const glm::vec3& p, float cellCount);
+
+ /// @return Tileable Perlin noise value in [0, 1].
+ /// @param p 3d coordinate in [0, 1], being the range of the repeatable pattern.
+ static float PerlinNoise(const glm::vec3& p, float frequency, int octaveCount);
+
+private:
+
+ ///
+ /// Worley noise function based on https://www.shadertoy.com/view/Xl2XRR by Marc-Andre Loyer
+ ///
+
+ static float hash(float n);
+ static float noise(const glm::vec3& x);
+ static float Cells(const glm::vec3& p, float numCells);
+
+};
+
+#endif // D_TILEABLE3DNOISE
+
diff --git a/plugins/csp-atmospheres/textures/cloudTop.png b/plugins/csp-atmospheres/textures/cloudTop.png
new file mode 100644
index 000000000..385957b90
Binary files /dev/null and b/plugins/csp-atmospheres/textures/cloudTop.png differ
diff --git a/plugins/csp-atmospheres/textures/cloudTop.xcf b/plugins/csp-atmospheres/textures/cloudTop.xcf
new file mode 100644
index 000000000..cce0e947d
Binary files /dev/null and b/plugins/csp-atmospheres/textures/cloudTop.xcf differ
diff --git a/plugins/csp-atmospheres/textures/cloudTop2.xcf b/plugins/csp-atmospheres/textures/cloudTop2.xcf
new file mode 100644
index 000000000..117850673
Binary files /dev/null and b/plugins/csp-atmospheres/textures/cloudTop2.xcf differ
diff --git a/plugins/csp-web-api/src/Plugin.cpp b/plugins/csp-web-api/src/Plugin.cpp
index 150082e2d..cf70c2d52 100644
--- a/plugins/csp-web-api/src/Plugin.cpp
+++ b/plugins/csp-web-api/src/Plugin.cpp
@@ -269,9 +269,9 @@ void Plugin::init() {
std::unique_lock lock(mCaptureMutex);
// Read all paramters.
- mCaptureDelay = std::clamp(getParam(conn, "delay", 50), 1, 200);
- mCaptureWidth = std::clamp(getParam(conn, "width", 0), 0, 4096);
- mCaptureHeight = std::clamp(getParam(conn, "height", 0), 0, 4096);
+ mCaptureDelay = std::clamp(getParam(conn, "delay", 50), 1, 10000);
+ mCaptureWidth = std::clamp(getParam(conn, "width", 0), 0, 8192);
+ mCaptureHeight = std::clamp(getParam(conn, "height", 0), 0, 8192);
mRestoreState = getParam(conn, "restoreState", "false") == "true";
mCaptureGui = getParam(conn, "gui", "auto");
mCaptureDepth = getParam(conn, "depth", "false") == "true";