diff --git a/config/base/scene/simple_desktop.json b/config/base/scene/simple_desktop.json index e7f5cab65..19cd3e2e8 100644 --- a/config/base/scene/simple_desktop.json +++ b/config/base/scene/simple_desktop.json @@ -282,9 +282,13 @@ "graphics": { "enableHDR": true, "eclipseShadowMaps": { - "Earth": {}, + "Earth": { + "texture":"../share/resources/textures/earthShadow.tif" + }, "Moon": {}, - "Mars": {}, + "Mars": { + "texture":"../share/resources/textures/marsShadow.tif" + }, "Phobos": {}, "Deimos": {}, "Jupiter": {}, @@ -739,6 +743,7 @@ "atmospheres": { "Earth": { "cloudTexture": "../share/resources/textures/earth-clouds.jpg", + "limbLuminanceTexture": "../share/resources/textures/earthLimbLuminance.tif", "topAltitude": 80000, "bottomAltitude": -100, "model": "Bruneton", diff --git a/docs/changelog.md b/docs/changelog.md index 06f50e10f..90b02ef62 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -15,8 +15,10 @@ SPDX-License-Identifier: CC-BY-4.0 #### New Features +- `csp-atmospheres` now supports refraction of light through the atmosphere. This deforms the Sun in Earth's atmosphere when it is close to the horizon. When observed from space, the sunset can turn into a glowing red ring around Earth. +- Eclipse shadows now consider the atmosphere of the occluder. This is used for Earth and Mars. As light is refracted through the atmosphere, the shadow is not completely dark anymore. Instead, some copper-red light is now shed on the Moon during a lunar eclipse. +- `csp-trajectories` now draws proxies for celestial objects when they are smaller than a few pixels in HDR mode. This makes them visible even if they are very small, such as when looking in the sky at night. This also drastically reduces flickering in HDR mode when the bodies are very small on the screen. - The `csp-stars` plugin now comes with two new rendering modes: Glare Discs and Software-Rasterized Points. The latter is the new default mode and is much faster than the old ones. -- The `csp-trajectories` now draws proxies for celestial objects when they are smaller than a few pixels in HDR mode. This makes them visible even if they are very small, such as when looking in the sky at night. This also drastically reduces flickering in HDR mode when the bodies are very small on the screen. - The `/capture` endpoint of the `csp-web-api` now supports an optional `restoreState` parameter. If set to `true`, the size of the window and the visibility of the user interface will be restored after capturing the image. Thanks to [@DanielPSchenk](https://github.com/DanielPSchenk) for this contribution! #### Other Changes diff --git a/externals/vista b/externals/vista index dc6baf659..df385c8b7 160000 --- a/externals/vista +++ b/externals/vista @@ -1 +1 @@ -Subproject commit dc6baf6594d123a78272a82d1a327dab76522d3f +Subproject commit df385c8b7d891135820bb8ace9f0de0acbc37b3c diff --git a/plugins/csp-atmospheres/README.md b/plugins/csp-atmospheres/README.md index a8e4d4c40..051b8c08c 100644 --- a/plugins/csp-atmospheres/README.md +++ b/plugins/csp-atmospheres/README.md @@ -145,15 +145,21 @@ They are not physically based but provide some plausible results. The Bruneton model is significantly more advanced. It precomputes multiple scattering and is based on [this open-source implementation](https://github.com/ebruneton/precomputed_atmospheric_scattering) (see also the corresponding [Paper](https://inria.hal.science/inria-00288758/en)). -Similar to the `CosmoScoutVR` model, the original implementation by Eric Bruneton uses Rayleigh scattering for molecules and the Cornette-Shanks phase function for aerosols. -We generalized this implementation by loading phase functions, extinction coefficients, and particle density distributions from CSV files. +We have significantly extended the original implementation to allow for more flexibility. +See our paper [Physically Based Real-Time Rendering of Atmospheres using Mie Theory](https://onlinelibrary.wiley.com/doi/full/10.1111/cgf.15010) for more details. + +As a first change, we now load phase functions, extinction coefficients, and particle density distributions from CSV files. This allows us to simulate arbitrary particle types. In particular, we can now use Mie Theory to precompute the scattering behaviour of a wide variety of particle types, including for instance Martian dust. +Next, our implementation can compute refraction. +This allows for displaced horizons and the simulation of astronomical refraction. +This is also used for computing light entering the eclipse shadows of celestial bodies. + Another change to the original implementation is that we put the precomputation of the atmospheric scattering into a separate executable. This allows us to perform the preprocessing offline with a much higher fidelity than what would be possible during application startup. -As a consequence to the changes mentioned above, **two preprocessing steps are required to use this model**. +There are **two preprocessing steps are required to use this model**. #### Preprocessing Step 1: Precompute the Particle-Scattering CSV Tables @@ -197,7 +203,7 @@ Once the multiple scattering textures are precomputed, the configuration for the
-Example Configuration for Mars (Realistic) +Example Configuration for Mars ```javascript "Mars": { diff --git a/plugins/csp-atmospheres/REUSE.toml b/plugins/csp-atmospheres/REUSE.toml index 1d2108ef1..6e8498863 100644 --- a/plugins/csp-atmospheres/REUSE.toml +++ b/plugins/csp-atmospheres/REUSE.toml @@ -21,6 +21,12 @@ precedence = "aggregate" SPDX-FileCopyrightText = "German Aerospace Center (DLR) " SPDX-License-Identifier = "CC0-1.0" +[[annotations]] +path = "scattering-table-generator/ior-settings/**" +precedence = "aggregate" +SPDX-FileCopyrightText = "German Aerospace Center (DLR) " +SPDX-License-Identifier = "CC0-1.0" + [[annotations]] path = "scattering-table-generator/output/**" precedence = "aggregate" diff --git a/plugins/csp-atmospheres/bruneton-preprocessor/Metadata.cpp b/plugins/csp-atmospheres/bruneton-preprocessor/Metadata.cpp index 4cdf4faa3..1b1b59ab1 100644 --- a/plugins/csp-atmospheres/bruneton-preprocessor/Metadata.cpp +++ b/plugins/csp-atmospheres/bruneton-preprocessor/Metadata.cpp @@ -14,6 +14,7 @@ void to_json(nlohmann::json& j, Metadata const& o) { cs::core::Settings::serialize(j, "sunIlluminance", o.mSunIlluminance); cs::core::Settings::serialize(j, "scatteringTextureNuSize", o.mScatteringTextureNuSize); cs::core::Settings::serialize(j, "maxSunZenithAngle", o.mMaxSunZenithAngle); + cs::core::Settings::serialize(j, "refraction", o.mRefraction); } //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/plugins/csp-atmospheres/bruneton-preprocessor/Metadata.hpp b/plugins/csp-atmospheres/bruneton-preprocessor/Metadata.hpp index cff5eee86..8cb372b0e 100644 --- a/plugins/csp-atmospheres/bruneton-preprocessor/Metadata.hpp +++ b/plugins/csp-atmospheres/bruneton-preprocessor/Metadata.hpp @@ -26,6 +26,9 @@ struct Metadata { /// The maximum Sun zenith angle for which atmospheric scattering was be precomputed. float mMaxSunZenithAngle{}; + + /// Whether refraction was used during preprocessing. + bool mRefraction{}; }; void to_json(nlohmann::json& j, Metadata const& o); diff --git a/plugins/csp-atmospheres/bruneton-preprocessor/Params.cpp b/plugins/csp-atmospheres/bruneton-preprocessor/Params.cpp index 39105848e..9af7ed667 100644 --- a/plugins/csp-atmospheres/bruneton-preprocessor/Params.cpp +++ b/plugins/csp-atmospheres/bruneton-preprocessor/Params.cpp @@ -31,13 +31,18 @@ void from_json(nlohmann::json const& j, Params& o) { cs::core::Settings::deserialize(j, "molecules", o.mMolecules); cs::core::Settings::deserialize(j, "aerosols", o.mAerosols); cs::core::Settings::deserialize(j, "ozone", o.mOzone); + cs::core::Settings::deserialize(j, "ior", o.mRefractiveIndex); cs::core::Settings::deserialize(j, "minAltitude", o.mMinAltitude); cs::core::Settings::deserialize(j, "maxAltitude", o.mMaxAltitude); + cs::core::Settings::deserialize(j, "refraction", o.mRefraction); cs::core::Settings::deserialize(j, "groundAlbedo", o.mGroundAlbedo); cs::core::Settings::deserialize(j, "multiScatteringOrder", o.mMultiScatteringOrder); cs::core::Settings::deserialize(j, "sampleCountOpticalDepth", o.mSampleCountOpticalDepth); + cs::core::Settings::deserialize(j, "stepSizeOpticalDepth", o.mStepSizeOpticalDepth); cs::core::Settings::deserialize(j, "sampleCountSingleScattering", o.mSampleCountSingleScattering); + cs::core::Settings::deserialize(j, "stepSizeSingleScattering", o.mStepSizeSingleScattering); cs::core::Settings::deserialize(j, "sampleCountMultiScattering", o.mSampleCountMultiScattering); + cs::core::Settings::deserialize(j, "stepSizeMultiScattering", o.mStepSizeMultiScattering); cs::core::Settings::deserialize( j, "sampleCountScatteringDensity", o.mSampleCountScatteringDensity); cs::core::Settings::deserialize( diff --git a/plugins/csp-atmospheres/bruneton-preprocessor/Params.hpp b/plugins/csp-atmospheres/bruneton-preprocessor/Params.hpp index 0bc514a63..0f1651fca 100644 --- a/plugins/csp-atmospheres/bruneton-preprocessor/Params.hpp +++ b/plugins/csp-atmospheres/bruneton-preprocessor/Params.hpp @@ -61,6 +61,10 @@ struct Params { ScatteringComponent mAerosols; std::optional mOzone; + /// To compute the refraction of light in the atmosphere, the refractive index of the atmosphere + /// is needed. For increased precision, the refractive index is stored as n-1. + float mRefractiveIndex = 0.0002777F; + /// The wavelength values, in nanometers, and sorted in increasing order, for which the /// phase functions and extinction coefficients in the atmosphere components are given. std::vector mWavelengths; @@ -69,22 +73,36 @@ struct Params { float mMinAltitude = 6371000.F; float mMaxAltitude = 6471000.F; + /// Refract the light when it travels through the atmosphere. This will produce an additional + /// look-up texture in the same parameter space as the transmittance texture. For each sample, + /// it contains the wavelength-dependent angular deviation of the light ray due to refraction. + cs::utils::DefaultProperty mRefraction{true}; + /// The average reflectance of the ground used during multiple scattering. cs::utils::DefaultProperty mGroundAlbedo{0.1F}; /// The number of multiple scattering events to precompute. Use zero for single-scattering only. cs::utils::DefaultProperty mMultiScatteringOrder{4}; - /// The number of samples to evaluate when precomputing the optical depth. + /// The number of samples to evaluate when precomputing the optical depth. If refraction is used, + /// the algorithm uses a fixed step size instead of a fixed number of samples. So if mRefraction + /// is true, mStepSizeOpticalDepth (in meters) will be used instead of mSampleCountOpticalDepth. cs::utils::DefaultProperty mSampleCountOpticalDepth{500}; + cs::utils::DefaultProperty mStepSizeOpticalDepth{10000}; /// The number of samples to evaluate when precomputing the single scattering. Larger values - /// improve the sampling of thin atmospheric layers. + /// improve the sampling of thin atmospheric layers. If refraction is used, the algorithm uses a + /// fixed step size instead of a fixed number of samples. So if mRefraction is true, + /// mStepSizeSingleScattering (in meters) will be used instead of mSampleCountSingleScattering. cs::utils::DefaultProperty mSampleCountSingleScattering{50}; + cs::utils::DefaultProperty mStepSizeSingleScattering{10000}; /// The number of samples to evaluate when precomputing the multiple scattering. Larger values - /// tend to darken the horizon for thick atmospheres. + /// tend to darken the horizon for thick atmospheres. If refraction is used, the algorithm uses a + /// fixed step size instead of a fixed number of samples. So if mRefraction is true, + /// mStepSizeMultiScattering (in meters) will be used instead of mSampleCountMultiScattering. cs::utils::DefaultProperty mSampleCountMultiScattering{50}; + cs::utils::DefaultProperty mStepSizeMultiScattering{10000}; /// The number of samples to evaluate when precomputing the scattering density. Larger values /// spread out colors in the sky. diff --git a/plugins/csp-atmospheres/bruneton-preprocessor/Preprocessor.cpp b/plugins/csp-atmospheres/bruneton-preprocessor/Preprocessor.cpp index 421f216b5..fb0982b11 100644 --- a/plugins/csp-atmospheres/bruneton-preprocessor/Preprocessor.cpp +++ b/plugins/csp-atmospheres/bruneton-preprocessor/Preprocessor.cpp @@ -31,6 +31,13 @@ // density distributions are now loaded from CSV files and then later sampled from textures. We also // store photometric values instead of radiometric values in the final textures. +// Also, we added the possibility to compute the refraction of light rays through the atmosphere. +// If enabled, an additional texture is generated which contains the angular deviation of the light +// rays as well as their closest approach to the planet's surface (this can be negative if the light +// ray intersects the planet's surface). +// The texture uses the same parametrization as the transmittance texture. The values are computed +// by the kComputeTransmittanceShader. + // Below, we will indicate for each group of function whether something has been changed and a link // to the original explanations of the methods by Eric Bruneton. @@ -185,9 +192,11 @@ constexpr float XYZ_TO_SRGB[9] = { // An explanation of the following shaders is available online: // https://ebruneton.github.io/precomputed_atmospheric_scattering/atmosphere/model.cc.html#shaders -// The only functional difference is that the kAtmosphereShader does not provide the radiance API +// The only functional differences are that the kAtmosphereShader does not provide the radiance API // anymore as it is not required by CosmoScout VR. Also, the shadow_length parameters have been // removed and the GetSunAndSkyIlluminance() does not require the surface normal anymore. +// Lastly, the kComputeTransmittanceShader has been extended to compute the refraction of light rays +// through the atmosphere. const char kVertexShader[] = R"( #version 330 @@ -227,8 +236,20 @@ const char kGeometryShader[] = R"( const char kComputeTransmittanceShader[] = R"( layout(location = 0) out vec3 oTransmittance; +#if COMPUTE_REFRACTION + layout(location = 1) out vec3 oThetaDeviationContactRadius; +#endif + void main() { - oTransmittance = computeTransmittanceToTopAtmosphereBoundaryTexture(ATMOSPHERE, gl_FragCoord.xy); + #if COMPUTE_REFRACTION + float contactRadius; + float thetaDeviation; + oTransmittance = computeTransmittanceToTopAtmosphereBoundaryTexture(ATMOSPHERE, gl_FragCoord.xy, thetaDeviation, contactRadius); + oThetaDeviationContactRadius = vec3(thetaDeviation, contactRadius, 0.0); + + #else + oTransmittance = computeTransmittanceToTopAtmosphereBoundaryTexture(ATMOSPHERE, gl_FragCoord.xy); + #endif } )"; @@ -313,7 +334,7 @@ const char kComputeMultipleScatteringShader[] = R"( void main() { float nu; - oDeltaMultipleScattering = computeMultipleScatteringTexture(uTransmittanceTexture, + oDeltaMultipleScattering = computeMultipleScatteringTexture(ATMOSPHERE, uTransmittanceTexture, uScatteringDensityTexture, vec3(gl_FragCoord.xy, uLayer + 0.5), nu); @@ -655,8 +676,6 @@ Preprocessor::Preprocessor(Params params) , mScatteringTextureHeight(mParams.mScatteringTextureMuSize.get()) , mScatteringTextureDepth(mParams.mScatteringTextureRSize.get()) { - std::cout << "Preprocessing atmosphere..." << std::endl; - // Compute angular radius of the sun. float sunRadius = 696340000.F; // meters mMetadata.mSunAngularRadius = std::asin(sunRadius / mParams.mSunDistance); @@ -674,6 +693,7 @@ Preprocessor::Preprocessor(Params params) mMetadata.mSunIlluminance = glm::vec3(sunKR, sunKG, sunKB); mMetadata.mScatteringTextureNuSize = mParams.mScatteringTextureNuSize.get(); mMetadata.mMaxSunZenithAngle = mParams.mMaxSunZenithAngle.get(); + mMetadata.mRefraction = mParams.mRefraction.get(); // A lambda that creates a GLSL header containing our atmosphere computation functions, // specialized for the given atmosphere parameters and for the 3 wavelengths in 'lambdas'. @@ -689,8 +709,9 @@ Preprocessor::Preprocessor(Params params) // clang-format off mGlslHeaderFactory = [=](glm::vec3 const& lambdas) { return - "#version 330\n" + + "#version 400\n" + definitions + + "#define COMPUTE_REFRACTION " + cs::utils::toString(mParams.mRefraction) + "\n" + "const int TRANSMITTANCE_TEXTURE_WIDTH = " + cs::utils::toString(mParams.mTransmittanceTextureWidth) + ";\n" + "const int TRANSMITTANCE_TEXTURE_HEIGHT = " + cs::utils::toString(mParams.mTransmittanceTextureHeight) + ";\n" + "const int SCATTERING_TEXTURE_R_SIZE = " + cs::utils::toString(mParams.mScatteringTextureRSize) + ";\n" + @@ -700,12 +721,16 @@ Preprocessor::Preprocessor(Params params) "const int IRRADIANCE_TEXTURE_WIDTH = " + cs::utils::toString(mParams.mIrradianceTextureWidth) + ";\n" + "const int IRRADIANCE_TEXTURE_HEIGHT = " + cs::utils::toString(mParams.mIrradianceTextureHeight) + ";\n" + "const int SAMPLE_COUNT_OPTICAL_DEPTH = " + cs::utils::toString(mParams.mSampleCountOpticalDepth) + ";\n" + + "const int STEP_SIZE_OPTICAL_DEPTH = " + cs::utils::toString(mParams.mStepSizeOpticalDepth) + ";\n" + "const int SAMPLE_COUNT_SINGLE_SCATTERING = " + cs::utils::toString(mParams.mSampleCountSingleScattering) + ";\n" + + "const int STEP_SIZE_SINGLE_SCATTERING = " + cs::utils::toString(mParams.mStepSizeSingleScattering) + ";\n" + "const int SAMPLE_COUNT_SCATTERING_DENSITY = " + cs::utils::toString(mParams.mSampleCountScatteringDensity) + ";\n" + "const int SAMPLE_COUNT_MULTI_SCATTERING = " + cs::utils::toString(mParams.mSampleCountMultiScattering) + ";\n" + + "const int STEP_SIZE_MULTI_SCATTERING = " + cs::utils::toString(mParams.mStepSizeMultiScattering) + ";\n" + "const int SAMPLE_COUNT_INDIRECT_IRRADIANCE = " + cs::utils::toString(mParams.mSampleCountIndirectIrradiance) + ";\n" + "const vec3 SOLAR_IRRADIANCE = " + extractVec3(WAVELENGTHS, SOLAR_IRRADIANCE, lambdas) + ";\n" + "const vec3 GROUND_ALBEDO = vec3(" + cs::utils::toString(mParams.mGroundAlbedo) + ");\n" + + "const float INDEX_OF_REFRACTION = " + cs::utils::toString(mParams.mRefractiveIndex) + ";\n" + "const float SUN_ANGULAR_RADIUS = " + cs::utils::toString(mMetadata.mSunAngularRadius) + ";\n" + "const float BOTTOM_RADIUS = " + cs::utils::toString(mParams.mMinAltitude) + ";\n" + "const float TOP_RADIUS = " + cs::utils::toString(mParams.mMaxAltitude) + ";\n" + @@ -723,6 +748,9 @@ Preprocessor::Preprocessor(Params params) mTransmittanceTexture = NewTexture2d(mParams.mTransmittanceTextureWidth.get(), mParams.mTransmittanceTextureHeight.get(), GL_RGB32F, GL_RGB, GL_FLOAT); + mThetaDeviationTexture = NewTexture2d(mParams.mTransmittanceTextureWidth.get(), + mParams.mTransmittanceTextureHeight.get(), GL_RGB32F, GL_RGB, GL_FLOAT); + mMultipleScatteringTexture = NewTexture3d(mScatteringTextureWidth, mScatteringTextureHeight, mScatteringTextureDepth, GL_RGB32F, GL_RGB, GL_FLOAT); @@ -782,6 +810,7 @@ Preprocessor::~Preprocessor() { glDeleteVertexArrays(1, &mFullScreenQuadVAO); glDeleteTextures(1, &mPhaseTexture); glDeleteTextures(1, &mTransmittanceTexture); + glDeleteTextures(1, &mThetaDeviationTexture); glDeleteTextures(1, &mMultipleScatteringTexture); glDeleteTextures(1, &mSingleAerosolsScatteringTexture); glDeleteTextures(1, &mIrradianceTexture); @@ -870,24 +899,38 @@ void Preprocessor::run(unsigned int numScatteringOrders) { deltaAerosolsScatteringTexture, deltaScatteringDensityTexture, deltaMultipleScatteringTexture, lambdas, luminanceFromRadiance, i > 0 /* blend */, numScatteringOrders); + glFinish(); } - // After the above iterations, the transmittance texture contains the transmittance for the 3 - // wavelengths used at the last iteration. But we want the transmittance at kLambdaR, kLambdaG, - // kLambdaB instead, so we must recompute it here for these 3 wavelengths: + std::cout << "Finishing precomputation..." << std::endl; + + // After the above iterations, the transmittance texture and the theta-deviation texture contain + // data for the 3 wavelengths used at the last iteration. But we want data at kLambdaR, + // kLambdaG, kLambdaB instead, so we must recompute it here for these 3 wavelengths: std::string header = mGlslHeaderFactory({kLambdaR, kLambdaG, kLambdaB}); Program computeTransmittance(kVertexShader, header + kComputeTransmittanceShader); glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, mTransmittanceTexture, 0); - glDrawBuffer(GL_COLOR_ATTACHMENT0); + + if (mParams.mRefraction.get()) { + glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, mThetaDeviationTexture, 0); + + const GLuint kDrawBuffers[4] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1}; + glDrawBuffers(2, kDrawBuffers); + } else { + glDrawBuffer(GL_COLOR_ATTACHMENT0); + } + glViewport( 0, 0, mParams.mTransmittanceTextureWidth.get(), mParams.mTransmittanceTextureHeight.get()); glScissor( 0, 0, mParams.mTransmittanceTextureWidth.get(), mParams.mTransmittanceTextureHeight.get()); computeTransmittance.Use(); computeTransmittance.BindTexture2d("uDensityTexture", mDensityTexture, 0); - DrawQuad({false}, mFullScreenQuadVAO); - - glFlush(); + if (mParams.mRefraction.get()) { + DrawQuad({false, false}, mFullScreenQuadVAO); + } else { + DrawQuad({false}, mFullScreenQuadVAO); + } // Also, the mPhaseTexture contains the phase functions for the last used wavelengths. We need // to update it with kLambdaR, kLambdaG, kLambdaB as well. @@ -895,6 +938,10 @@ void Preprocessor::run(unsigned int numScatteringOrders) { {mParams.mMolecules, mParams.mAerosols}, {kLambdaR, kLambdaG, kLambdaB}); } + glFinish(); + + std::cout << "Precomputation Done." << std::endl; + // Delete the temporary resources allocated at the beginning of this method. glUseProgram(0); glBindFramebuffer(GL_FRAMEBUFFER, 0); @@ -915,8 +962,29 @@ void Preprocessor::run(unsigned int numScatteringOrders) { void Preprocessor::save(std::string const& directory) { std::cout << "Saving precomputed atmosphere to disk..." << std::endl; - // Save the precomputed textures to disk. We need to store mMultipleScatteringTexture, - // mSingleAerosolsScatteringTexture, mPhaseTexture, mTransmittanceTexture, and mIrradianceTexture. + // For debugging purposes, we print the maximum ray deviation in degrees. + std::vector pixels( + mParams.mTransmittanceTextureWidth.get() * mParams.mTransmittanceTextureHeight.get() * 3); + glBindTexture(GL_TEXTURE_2D, mThetaDeviationTexture); + glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_FLOAT, pixels.data()); + glBindTexture(GL_TEXTURE_2D, 0); + + float maxThetaDeviation = 0.F; + for (int x = 0; x < mParams.mTransmittanceTextureWidth.get(); ++x) { + for (int y = 0; y < mParams.mTransmittanceTextureHeight.get(); ++y) { + int i = 3 * (y * mParams.mTransmittanceTextureWidth.get() + x); + + float thetaDeviation = pixels[i]; + float contactRadius = pixels[i + 1]; + + if (contactRadius > 0.F) { + maxThetaDeviation = std::max(maxThetaDeviation, thetaDeviation); + } + } + } + + std::cout << "Maximum ray deviation: " << maxThetaDeviation * 180.F / glm::pi() + << " degrees." << std::endl; auto write2D = [](std::string const& path, GLuint texture, int width, int height) { std::vector data(width * height * 3); @@ -977,6 +1045,11 @@ void Preprocessor::save(std::string const& directory) { write3D(directory + "/single_aerosols_scattering.tif", mSingleAerosolsScatteringTexture, mScatteringTextureWidth, mScatteringTextureHeight, mScatteringTextureDepth); + if (mParams.mRefraction.get()) { + write2D(directory + "/theta_deviation.tif", mThetaDeviationTexture, + mParams.mTransmittanceTextureWidth.get(), mParams.mTransmittanceTextureHeight.get()); + } + std::ofstream out(directory + "/metadata.json"); nlohmann::json data = mMetadata; out << std::setw(2) << data; @@ -1069,7 +1142,16 @@ void Preprocessor::precompute(GLuint fbo, GLuint deltaIrradianceTexture, // 1. Compute the transmittance, and store it in mTransmittanceTexture. glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, mTransmittanceTexture, 0); - glDrawBuffer(GL_COLOR_ATTACHMENT0); + + if (mParams.mRefraction.get()) { + glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, mThetaDeviationTexture, 0); + + const GLuint kDrawBuffers[4] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1}; + glDrawBuffers(2, kDrawBuffers); + } else { + glDrawBuffer(GL_COLOR_ATTACHMENT0); + } + glViewport( 0, 0, mParams.mTransmittanceTextureWidth.get(), mParams.mTransmittanceTextureHeight.get()); glScissor( diff --git a/plugins/csp-atmospheres/bruneton-preprocessor/Preprocessor.hpp b/plugins/csp-atmospheres/bruneton-preprocessor/Preprocessor.hpp index 3c87c9275..b90428371 100644 --- a/plugins/csp-atmospheres/bruneton-preprocessor/Preprocessor.hpp +++ b/plugins/csp-atmospheres/bruneton-preprocessor/Preprocessor.hpp @@ -68,12 +68,13 @@ class Preprocessor { GLuint mMultipleScatteringTexture = 0; GLuint mSingleAerosolsScatteringTexture = 0; - GLuint mPhaseTexture = 0; - GLuint mDensityTexture = 0; - GLuint mTransmittanceTexture = 0; - GLuint mIrradianceTexture = 0; - GLuint mFullScreenQuadVAO = 0; - GLuint mFullScreenQuadVBO = 0; + GLuint mPhaseTexture = 0; + GLuint mDensityTexture = 0; + GLuint mTransmittanceTexture = 0; + GLuint mThetaDeviationTexture = 0; + GLuint mIrradianceTexture = 0; + GLuint mFullScreenQuadVAO = 0; + GLuint mFullScreenQuadVBO = 0; }; #endif // PREPROCESSOR_HPP diff --git a/plugins/csp-atmospheres/bruneton-preprocessor/output b/plugins/csp-atmospheres/bruneton-preprocessor/output index d8bbefb0c..f40bac3a2 160000 --- a/plugins/csp-atmospheres/bruneton-preprocessor/output +++ b/plugins/csp-atmospheres/bruneton-preprocessor/output @@ -1 +1 @@ -Subproject commit d8bbefb0ca1bb098fccb135f126490320dd5c6dc +Subproject commit f40bac3a2b35249b9b5e313c537e782fb4f61e90 diff --git a/plugins/csp-atmospheres/bruneton-preprocessor/settings/earth.json b/plugins/csp-atmospheres/bruneton-preprocessor/settings/earth.json index 477a24d58..04615cd62 100644 --- a/plugins/csp-atmospheres/bruneton-preprocessor/settings/earth.json +++ b/plugins/csp-atmospheres/bruneton-preprocessor/settings/earth.json @@ -1,22 +1,33 @@ { - "multiScatteringOrder": 10, - "sunDistance": 149600000000, - "minAltitude": 6370900, - "maxAltitude": 6451000, - "molecules": { - "phase": "plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_molecules_phase.csv", - "betaSca": "plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_molecules_scattering.csv", - "betaAbs": "plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_molecules_absorption.csv", - "density": "plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_molecules_density.csv" - }, - "aerosols": { - "phase": "plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_aerosols_phase.csv", - "betaSca": "plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_aerosols_scattering.csv", - "betaAbs": "plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_aerosols_absorption.csv", - "density": "plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_aerosols_density.csv" - }, - "ozone": { - "betaAbs": "plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_ozone_absorption.csv", - "density": "plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_ozone_density.csv" - } -} \ No newline at end of file + "multiScatteringOrder": 5, + "sunDistance": 149600000000, + "minAltitude": 6371000, + "maxAltitude": 6451000, + "stepSizeOpticalDepth": 2000, + "stepSizeSingleScattering": 2000, + "stepSizeMultiScattering": 4000, + "transmittanceTextureWidth": 512, + "transmittanceTextureHeight": 256, + "scatteringTextureRSize": 32, + "scatteringTextureMuSize": 64, + "scatteringTextureMuSSize": 32, + "scatteringTextureNuSize": 8, + "refraction": true, + "molecules": { + "phase": "plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_molecules_phase.csv", + "betaSca": "plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_molecules_scattering.csv", + "betaAbs": "plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_molecules_absorption.csv", + "density": "plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_molecules_density.csv" + }, + "aerosols": { + "phase": "plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_aerosols_phase.csv", + "betaSca": "plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_aerosols_scattering.csv", + "betaAbs": "plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_aerosols_absorption.csv", + "density": "plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_aerosols_density.csv" + }, + "ozone": { + "betaAbs": "plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_ozone_absorption.csv", + "density": "plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_ozone_density.csv" + }, + "ior": 0.000277 +} diff --git a/plugins/csp-atmospheres/bruneton-preprocessor/settings/mars.json b/plugins/csp-atmospheres/bruneton-preprocessor/settings/mars.json index 4541564a5..e2b348924 100644 --- a/plugins/csp-atmospheres/bruneton-preprocessor/settings/mars.json +++ b/plugins/csp-atmospheres/bruneton-preprocessor/settings/mars.json @@ -1,18 +1,29 @@ { - "multiScatteringOrder": 10, - "sunDistance": 227900000000, - "minAltitude": 3385000, - "maxAltitude": 3469500, - "molecules": { - "phase": "plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_molecules_cinematic_phase.csv", - "betaSca": "plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_molecules_cinematic_scattering.csv", - "betaAbs": "plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_molecules_cinematic_absorption.csv", - "density": "plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_molecules_cinematic_density.csv" - }, - "aerosols": { - "phase": "plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_aerosols_cinematic_phase.csv", - "betaSca": "plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_aerosols_cinematic_scattering.csv", - "betaAbs": "plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_aerosols_cinematic_absorption.csv", - "density": "plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_aerosols_cinematic_density.csv" - } -} \ No newline at end of file + "multiScatteringOrder": 10, + "sunDistance": 227900000000, + "minAltitude": 3385000, + "maxAltitude": 3469500, + "stepSizeOpticalDepth": 2000, + "stepSizeSingleScattering": 2000, + "stepSizeMultiScattering": 2000, + "transmittanceTextureWidth": 512, + "transmittanceTextureHeight": 256, + "scatteringTextureRSize": 32, + "scatteringTextureMuSize": 128, + "scatteringTextureMuSSize": 32, + "scatteringTextureNuSize": 8, + "refraction": true, + "molecules": { + "phase": "plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_molecules_cinematic_phase.csv", + "betaSca": "plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_molecules_cinematic_scattering.csv", + "betaAbs": "plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_molecules_cinematic_absorption.csv", + "density": "plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_molecules_cinematic_density.csv" + }, + "aerosols": { + "phase": "plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_aerosols_cinematic_phase.csv", + "betaSca": "plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_aerosols_cinematic_scattering.csv", + "betaAbs": "plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_aerosols_cinematic_absorption.csv", + "density": "plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_aerosols_cinematic_density.csv" + }, + "ior": 3.383e-6 +} diff --git a/plugins/csp-atmospheres/bruneton-preprocessor/shaders/csp-atmosphere-preprocessing-functions.glsl b/plugins/csp-atmospheres/bruneton-preprocessor/shaders/csp-atmosphere-preprocessing-functions.glsl index 043488b84..288a2464d 100644 --- a/plugins/csp-atmospheres/bruneton-preprocessor/shaders/csp-atmosphere-preprocessing-functions.glsl +++ b/plugins/csp-atmospheres/bruneton-preprocessor/shaders/csp-atmosphere-preprocessing-functions.glsl @@ -7,87 +7,380 @@ // SPDX-FileCopyrightText: 2008 INRIA // SPDX-License-Identifier: BSD-3-Clause -// This file is based on the original implementation by Eric Bruneton: +#line 11 + +// This file is roughly based on the original implementation by Eric Bruneton: // https://github.com/ebruneton/precomputed_atmospheric_scattering/blob/master/atmosphere/functions.glsl -// While implementing the atmospheric model into CosmoScout VR, we have refactored some parts of the -// code, however this is mostly related to how variables are named and how input parameters are -// passed to the shader. The only fundamental change is that the phase functions for aerosols and -// molecules as well as their density distributions are now sampled from textures. +// While implementing the atmospheric model into CosmoScout VR, we have extended this model in +// several ways. There are two fundamental changes: +// 1. The phase functions for aerosols and molecules as well as their density distributions are +// now sampled from textures. +// 2. The index of refraction of the atmosphere is now passed to the shader and if +// COMPUTE_REFRACTION is set, rays are refracted accordingly. + +// Below, we will indicate for each group of function whether something important has been changed +// with respect to the original implementation and add a link to the original explanation. + +// Refraction Computation -------------------------------------------------------------------------- + +// This part is new. The wavelength-dependent INDEX_OF_REFRACTION is passed to the shader. According +// to the Gladsstone-Dale relation, the refractive index is proportional to the density of the air +// molecules. The density of the air molecules and aerosols is sampled from a texture. + +// This returns the relative density in [0...1] of a component at a given altitude (in meters). The +// density texture contains the density of the air molecules, aerosols, or ozone molecules at +// different v coordinates, so this needs to be passed as an argument. +float getDensity(float densityTextureV, float altitude) { + float u = clamp(altitude / (TOP_RADIUS - BOTTOM_RADIUS), 0.0, 1.0); + return texture(uDensityTexture, vec2(u, densityTextureV)).r; +} + +// Using acos is not very stable for small angles. This function is used to compute small angles +// between two normalized vectors in a more stable way. +float angleBetweenVectors(vec2 u, vec2 v) { + return 2.0 * asin(0.5 * length(u - v)); +} + +#if COMPUTE_REFRACTION -// Below, we will indicate for each group of function whether something has been changed and a link -// to the original explanations of the methods by Eric Bruneton. +// Returns IoR - 1.0 at the given altitude. Here, single floating point precision is sufficient. +float getRefractiveIndexMinusOne(float altitude) { + return INDEX_OF_REFRACTION * getDensity(ATMOSPHERE.molecules.densityTextureV, altitude); +} + +// Returns the IoR at the given altitude. To properly represent very small changes in the refractive +// index, we use double precision here. +double getRefractiveIndex(float altitude) { + return 1.0lf + double(getRefractiveIndexMinusOne(altitude)); +} + +// Returns the gradient length of the refractive index at the given altitude. +float getIoRGradientLength(float altitude, float dh) { + return (getRefractiveIndexMinusOne(altitude + dh) - getRefractiveIndexMinusOne(altitude)) / dh; +} + +// Refracts the ray according to "Visualizing sunsets through inhomogeneous atmospheres" by +// Seron et al. (2004) using a fixed step size. +dvec2 refractRaySeron(dvec2 origin, dvec2 dir, double dx) { + float altitude = max(0, float(length(origin) - BOTTOM_RADIUS)); + double refractiveIndex = getRefractiveIndex(altitude); + double gradientLength = getIoRGradientLength(altitude, 10); + dvec2 dn = normalize(origin) * gradientLength; + return normalize(refractiveIndex * dir + dn * dx); +} + +// Refracts the ray according to "Comment on 'Improved ray tracing air mass numbers model' by van +// der Werf (2008). Due to the involved trigonometry, have to use single precision in some places. +dvec2 refractRayWerf(dvec2 origin, dvec2 dir, double dx) { + + double RplusH = length(origin); + float h = float(max(0, RplusH - BOTTOM_RADIUS)); + + // This could be computed using cos(PI / 2.0 - acos(dot(origin / RplusH, dir)), however it can + // be simplified to the following using trigonometric identities. + double cosBeta = sqrt(1.0 - pow(float(dot(origin / RplusH, dir)), 2.0)); + + double curvature = cosBeta / getRefractiveIndex(h) * getIoRGradientLength(h, 10); + double deltaBeta = dx * (cosBeta / RplusH + curvature); + double deltaPhi = cosBeta * dx / RplusH; + float diff = float(deltaBeta - deltaPhi); + + // Rotate dir by diff. + return dvec2(dir.x * cos(diff) - dir.y * sin(diff), dir.x * sin(diff) + dir.y * cos(diff)); +} + +// Use this to switch between the two refraction methods. +#define refractRay refractRaySeron + +// The method just advances the ray in the direction of the ray. No refraction is computed. +void rayStepSimple(inout dvec2 origin, inout dvec2 dir, double dx) { + origin += dir * dx; +} + +// The Euler method just advances the ray in the direction of the refracted ray. +void rayStepEuler(inout dvec2 origin, inout dvec2 dir, double dx) { + dir = refractRay(origin, dir, dx); + origin += dir * dx; +} + +// The Runge-Kutta 4 method is more accurate but also more expensive. +void rayStepRK4(inout dvec2 origin, inout dvec2 dir, double dx) { + dvec2 k1 = refractRay(origin, dir, dx); + dvec2 k2 = refractRay(origin + k1 * dx / 2.0, dir, dx); + dvec2 k3 = refractRay(origin + k2 * dx / 2.0, dir, dx); + dvec2 k4 = refractRay(origin + k3 * dx, dir, dx); + dir = (k1 + 2.0 * k2 + 2.0 * k3 + k4) / 6.0; + origin += dir * dx; +} + +// Use this to switch between the two ray stepping methods. +#define rayStep rayStepRK4 + +#endif // COMPUTE_REFRACTION // Transmittance Computation ----------------------------------------------------------------------- // The code below is used to comute the optical depth (or transmittance) from any point in the // atmosphere towards the top atmosphere boundary. -// An explanation of the following methods is available online: -// https://ebruneton.github.io/precomputed_atmospheric_scattering/atmosphere/functions.glsl.html#transmittance - -// The only functional difference is that the density of the air molecules, aerosols, and ozone +// The first functional difference is that the density of the air molecules, aerosols, and ozone // molecules is now sampled from a texture (in getDensity()) instead of analytically computed. +// In addition, we have two variants of the computeOpticalLengthToTopAtmosphereBoundary method. +// If COMPUTE_REFRACTION is set, the ray is bent according to the refractive index of the air +// molecules. If not, we use the original implementation by Eric Bruneton with evenly spaced samples +// along the ray. + +#if COMPUTE_REFRACTION + +// If our ray hit the surface of the planet due to refraction, the optical depth should be infinite. +// However, returning a very high optical depth is not a good solution, because the transmittance +// computation in getTransmittance() in common.glsl relies on the fact that the ray does not +// intersect the planet in either the forward or backward direction. +// As a solution, we store the contact radius (which is the altitude of the closest approach of the +// ray to the planet and which is negative if the ray intersects the planet) in the RayInfo struct. +// This value is then used to determine if the ray actually hit the planet. +// The thetaDeviation is the angle between the original ray direction and the refracted ray +// direction. This is used to displace astronomical bodies in the sky. +struct RayInfo { + float opticalDepth; + float thetaDeviation; + float contactRadius; +}; + +RayInfo computeOpticalLengthToTopAtmosphereBoundary(float densityTextureV, float r, float mu) { + + dvec2 startRayDir = vec2(sqrt(1 - mu * mu), mu); + + RayInfo result; + result.opticalDepth = 0.0; + result.thetaDeviation = 0.0; + result.contactRadius = r - BOTTOM_RADIUS; + + dvec2 currentDir = dvec2(sqrt(1 - mu * mu), mu); + dvec2 samplePos = vec2(0.0, r); + bool leftAtmosphere = false; + double weight = 0.5; + float dx = STEP_SIZE_OPTICAL_DEPTH; + + while (!leftAtmosphere) { + double sampleRadius = length(samplePos); + + dvec2 segmentStart = samplePos - currentDir * dx * 0.5; + double segmentStartR = length(segmentStart); + dvec2 segmentEnd = samplePos + currentDir * dx * 0.5; + double segmentEndR = length(segmentEnd); + + // If the segment leaves the atmosphere, we compute how much of the segment is inside the + // atmosphere. + if (segmentEndR > TOP_RADIUS) { + weight = 1.0 - (segmentEndR - TOP_RADIUS) / (segmentEndR - segmentStartR); + leftAtmosphere = true; + } -float getDensity(float densityTextureV, float altitude) { - float u = clamp(altitude / (TOP_RADIUS - BOTTOM_RADIUS), 0.0, 1.0); - return texture(uDensityTexture, vec2(u, densityTextureV)).r; + float altitude = float(sampleRadius) - BOTTOM_RADIUS; + result.opticalDepth += getDensity(densityTextureV, altitude) * float(weight); + result.contactRadius = min(result.contactRadius, altitude); + + rayStep(samplePos, currentDir, dx); + weight = 1.0; + } + + result.thetaDeviation = angleBetweenVectors(vec2(startRayDir), vec2(currentDir)); + result.opticalDepth *= dx; + + return result; } -float computeOpticalLengthToTopAtmosphereBoundary(float densityTextureV, float r, float mu) { - float dx = distanceToTopAtmosphereBoundary(r, mu) / float(SAMPLE_COUNT_OPTICAL_DEPTH); - float result = 0.0; +#else + +// If no refraction is computed, the optical depth is simply the integral of the density along the +// ray. We use the original implementation by Eric Bruneton with evenly spaced samples along the +// ray. An explanation of this method is available online: +// https://ebruneton.github.io/precomputed_atmospheric_scattering/atmosphere/functions.glsl.html#transmittance +struct RayInfo { + float opticalDepth; +}; + +RayInfo computeOpticalLengthToTopAtmosphereBoundary(float densityTextureV, float r, float mu) { + float dx = distanceToTopAtmosphereBoundary(r, mu) / float(SAMPLE_COUNT_OPTICAL_DEPTH); + RayInfo result; + result.opticalDepth = 0.0; + for (int i = 0; i <= SAMPLE_COUNT_OPTICAL_DEPTH; ++i) { float d_i = float(i) * dx; float r_i = sqrt(d_i * d_i + 2.0 * r * mu * d_i + r * r); float y_i = getDensity(densityTextureV, r_i - BOTTOM_RADIUS); float weight_i = i == 0 || i == SAMPLE_COUNT_OPTICAL_DEPTH ? 0.5 : 1.0; - result += y_i * weight_i * dx; + result.opticalDepth += y_i * weight_i * dx; } return result; } -vec3 computeTransmittanceToTopAtmosphereBoundary( - AtmosphereComponents atmosphere, float r, float mu) { - return exp(-(atmosphere.molecules.extinction * computeOpticalLengthToTopAtmosphereBoundary( - atmosphere.molecules.densityTextureV, r, mu) + - atmosphere.aerosols.extinction * computeOpticalLengthToTopAtmosphereBoundary( - atmosphere.aerosols.densityTextureV, r, mu) + - atmosphere.ozone.extinction * computeOpticalLengthToTopAtmosphereBoundary( - atmosphere.ozone.densityTextureV, r, mu))); -} +#endif // Transmittance Texture Precomputation ------------------------------------------------------------ // The code below is used to store the precomputed transmittance values in a 2D lookup table. -// An explanation of the following methods is available online: +// An explanation of the following method is available online: // https://ebruneton.github.io/precomputed_atmospheric_scattering/atmosphere/functions.glsl.html#transmittance_precomputation -// There is no functional difference to the original code. +// The only difference to the original implementation is that the method also returns the theta +// deviation and the contact radius if COMPUTE_REFRACTION is set. + +#if COMPUTE_REFRACTION + +vec3 computeTransmittanceToTopAtmosphereBoundaryTexture(AtmosphereComponents atmosphere, + vec2 fragCoord, out float thetaDeviation, out float contactRadius) { + +#else vec3 computeTransmittanceToTopAtmosphereBoundaryTexture( AtmosphereComponents atmosphere, vec2 fragCoord) { - const vec2 TRANSMITTANCE_TEXTURE_SIZE = - vec2(TRANSMITTANCE_TEXTURE_WIDTH, TRANSMITTANCE_TEXTURE_HEIGHT); + +#endif + float r; float mu; - getRMuFromTransmittanceTextureUv(fragCoord / TRANSMITTANCE_TEXTURE_SIZE, r, mu); - return computeTransmittanceToTopAtmosphereBoundary(atmosphere, r, mu); + getRMuFromTransmittanceTextureUv( + fragCoord / vec2(TRANSMITTANCE_TEXTURE_WIDTH, TRANSMITTANCE_TEXTURE_HEIGHT), r, mu); + + RayInfo molecules = + computeOpticalLengthToTopAtmosphereBoundary(atmosphere.molecules.densityTextureV, r, mu); + RayInfo aerosols = + computeOpticalLengthToTopAtmosphereBoundary(atmosphere.aerosols.densityTextureV, r, mu); + RayInfo ozone = + computeOpticalLengthToTopAtmosphereBoundary(atmosphere.ozone.densityTextureV, r, mu); + + vec3 transmittance = exp(-(atmosphere.molecules.extinction * molecules.opticalDepth + + atmosphere.aerosols.extinction * aerosols.opticalDepth + + atmosphere.ozone.extinction * ozone.opticalDepth)); + +#if COMPUTE_REFRACTION + thetaDeviation = molecules.thetaDeviation; + contactRadius = molecules.contactRadius; +#endif + + return transmittance; } // Single-Scattering Computation ------------------------------------------------------------------- // The code below is used to compute the amount of light scattered into a specific direction during // a single scattering event for air molecules and aerosols. +// If refraction is enabled, we cannot use the original implementation by Eric Bruneton, because the +// ray is bent. + +#if COMPUTE_REFRACTION + +// Double precision variant of the same method defined in common.glsl. +double clampCosine(double mu) { + return clamp(mu, -1.0, 1.0); +} + +// Returns the optical depth of a ray segment of unit length. It is based on the atmospheric density +// at the given planet-center distance and the extinction coefficients of the air molecules, +// aerosols, and ozone. +vec3 getOpticalDepth(AtmosphereComponents atmosphere, float r) { + float altitude = r - BOTTOM_RADIUS; + float moleculesDensity = getDensity(atmosphere.molecules.densityTextureV, altitude); + float aerosolsDensity = getDensity(atmosphere.aerosols.densityTextureV, altitude); + float ozoneDensity = getDensity(atmosphere.ozone.densityTextureV, altitude); + + return atmosphere.molecules.extinction * moleculesDensity + + atmosphere.aerosols.extinction * aerosolsDensity + + atmosphere.ozone.extinction * ozoneDensity; +} + +// The direction to the sun is encoded using the cosines of the zenith angle muS and the cosine to +// the ray direction nu. We reconstruct the 3D direction vector from these two values. +vec3 getSunDirection(float mu, float muS, float nu) { + float rayDirX = safeSqrt(1 - mu * mu); + float rayDirY = mu; + + float sunDirX = (nu - rayDirY * muS) / (rayDirX + 1e-20); + float sunDirY = muS; + float sunDirZ = safeSqrt(1 - sunDirX * sunDirX - sunDirY * sunDirY); + + return vec3(sunDirX, sunDirY, sunDirZ); +} + +// As for the transmittance, the single-scattering computation is different if refraction is +// incorporated. The ray is bent, so we cannot simply accumulate the scattering along a straight +// line. +// We want to keep the code as close to the original implementation as possible. Therefore, we +// did not change the signature of the method. However, we now trace a 2D ray in the atmosphere and +// therefore need the 3D direction to the sun. The method above is used to reconstruct this. +// If we were to remove the code without refraction, we would trace the rays in 2D and could pass +// the sun direction directly to the method. +void computeSingleScattering(AtmosphereComponents atmosphere, sampler2D transmittanceTexture, + float r, float mu, float muS, float nu, bool rayRMuIntersectsGround, out vec3 molecules, + out vec3 aerosols) { + + dvec2 currentDir = vec2(sqrt(1 - mu * mu), mu); + vec3 sunDir = getSunDirection(mu, muS, nu); + + vec3 moleculesSum = vec3(0.0); + vec3 aerosolsSum = vec3(0.0); + dvec3 opticalDepthRay = dvec3(0.0); + + dvec2 samplePos = vec2(0.0, r); + bool hitGroundOrLeftAtmosphere = false; + double weight = 0.5; + float dx = STEP_SIZE_SINGLE_SCATTERING; + + while (!hitGroundOrLeftAtmosphere) { + double sampleRadius = length(samplePos); + + dvec2 segmentStart = samplePos - currentDir * dx * 0.5; + double segmentStartR = length(segmentStart); + dvec2 segmentEnd = samplePos + currentDir * dx * 0.5; + double segmentEndR = length(segmentEnd); + + // If the segment intersects the ground, we compute how much of the segment is inside the + // atmosphere. + if (segmentEndR < BOTTOM_RADIUS) { + weight = 1.0 - (BOTTOM_RADIUS - segmentEndR) / (segmentStartR - segmentEndR); + hitGroundOrLeftAtmosphere = true; + } + + // If the segment leaves the atmosphere, we compute how much of the segment is inside the + // atmosphere. + if (segmentEndR > TOP_RADIUS) { + weight = 1.0 - (segmentEndR - TOP_RADIUS) / (segmentEndR - segmentStartR); + hitGroundOrLeftAtmosphere = true; + } + + float muSD = clampCosine(dot(sunDir, vec3(samplePos, 0.0)) / float(sampleRadius)); + + vec3 transmittanceSun = getTransmittanceToSun(transmittanceTexture, float(sampleRadius), muSD); + opticalDepthRay += getOpticalDepth(atmosphere, float(sampleRadius)) * dx * weight; + vec3 transmittanceRay = exp(-vec3(opticalDepthRay)); + + float altitude = float(sampleRadius) - BOTTOM_RADIUS; + float moleculesDensity = getDensity(atmosphere.molecules.densityTextureV, altitude); + float aerosolsDensity = getDensity(atmosphere.aerosols.densityTextureV, altitude); + moleculesSum += transmittanceSun * transmittanceRay * moleculesDensity * float(weight); + aerosolsSum += transmittanceSun * transmittanceRay * aerosolsDensity * float(weight); + + rayStep(samplePos, currentDir, dx); + weight = 1.0; + } + + molecules = moleculesSum * SOLAR_IRRADIANCE * atmosphere.molecules.scattering * dx; + aerosols = aerosolsSum * SOLAR_IRRADIANCE * atmosphere.aerosols.scattering * dx; +} + +#else + +// If no refraction is computed, the single-scattering computation is the same as in the original +// implementation by Eric Bruneton. The ray is not bent, so we can accumulate the scattering along a +// straight line. // An explanation of the following methods is available online: // https://ebruneton.github.io/precomputed_atmospheric_scattering/atmosphere/functions.glsl.html#single_scattering -// Most of the methods below are functionality-wise identical to the original implementation. The -// only difference is that the RayleighPhaseFunction() and MiePhaseFunction() have been removed and -// replaced by a generic phaseFunction() which samples the phase function from a texture. - void computeSingleScatteringIntegrand(AtmosphereComponents atmosphere, sampler2D transmittanceTexture, float r, float mu, float muS, float nu, float d, bool rayRMuIntersectsGround, out vec3 molecules, out vec3 aerosols) { @@ -99,14 +392,6 @@ void computeSingleScatteringIntegrand(AtmosphereComponents atmosphere, aerosols = transmittance * getDensity(atmosphere.aerosols.densityTextureV, rD - BOTTOM_RADIUS); } -float distanceToNearestAtmosphereBoundary(float r, float mu, bool rayRMuIntersectsGround) { - if (rayRMuIntersectsGround) { - return distanceToBottomAtmosphereBoundary(r, mu); - } else { - return distanceToTopAtmosphereBoundary(r, mu); - } -} - void computeSingleScattering(AtmosphereComponents atmosphere, sampler2D transmittanceTexture, float r, float mu, float muS, float nu, bool rayRMuIntersectsGround, out vec3 molecules, out vec3 aerosols) { @@ -133,6 +418,10 @@ void computeSingleScattering(AtmosphereComponents atmosphere, sampler2D transmit aerosols = aerosolsSum * dx * SOLAR_IRRADIANCE * atmosphere.aerosols.scattering; } +#endif + +// The RayleighPhaseFunction() and MiePhaseFunction() have been removed and replaced by a generic +// phaseFunction() which samples the phase function from a texture. vec3 phaseFunction(ScatteringComponent component, float nu) { float theta = acos(nu) / PI; // 0<->1 return texture2D(uPhaseTexture, vec2(theta, component.phaseTextureV)).rgb; @@ -215,9 +504,13 @@ vec3 getScattering(AtmosphereComponents atmosphere, sampler3D singleMoleculesSca // the atmosphere. // An explanation of the following methods is available online: -// https://ebruneton.github.io/precomputed_atmospheric_scattering/atmosphere/functions.glsl.html#multipleScattering +// https://ebruneton.github.io/precomputed_atmospheric_scattering/atmosphere/functions.glsl.html#multiple_scattering -// There is no functional difference to the original code. +// Similar to the single-scattering computation, the multiple-scattering computation is different if +// refraction is incorporated. The ray is bent, so we cannot simply accumulate the scattering along +// a straight line. + +// All other methods are basically the same as in the original implementation by Eric Bruneton. vec3 getIrradiance(sampler2D irradianceTexture, float r, float muS); @@ -297,8 +590,74 @@ vec3 computeScatteringDensity(AtmosphereComponents atmosphere, sampler2D transmi return moleculesAerosols; } -vec3 computeMultipleScattering(sampler2D transmittanceTexture, sampler3D scatteringDensityTexture, - float r, float mu, float muS, float nu, bool rayRMuIntersectsGround) { +#if COMPUTE_REFRACTION + +// As for the single-scattering computation, the multiple-scattering computation is different if +// refraction is incorporated. We trace the rays in 2D and use a reconstructed 3D sun light +// direction. +vec3 computeMultipleScattering(AtmosphereComponents atmosphere, sampler2D transmittanceTexture, + sampler3D scatteringDensityTexture, float r, float mu, float muS, float nu, + bool rayRMuIntersectsGround) { + + dvec2 currentDir = vec2(sqrt(1 - mu * mu), mu); + vec3 sunDir = getSunDirection(mu, muS, nu); + + vec3 moleculesAerosolsSum = vec3(0.0); + dvec3 opticalDepthRay = dvec3(0.0); + + dvec2 samplePos = vec2(0.0, r); + bool hitGroundOrLeftAtmosphere = false; + double weight = 0.5; + float dx = STEP_SIZE_MULTI_SCATTERING; + + while (!hitGroundOrLeftAtmosphere) { + double sampleRadius = length(samplePos); + + dvec2 segmentStart = samplePos - currentDir * dx * 0.5; + double segmentStartR = length(segmentStart); + dvec2 segmentEnd = samplePos + currentDir * dx * 0.5; + double segmentEndR = length(segmentEnd); + + // If the segment intersects the ground, we compute how much of the segment is inside the + // atmosphere. + if (segmentEndR < BOTTOM_RADIUS) { + weight = 1.0 - (BOTTOM_RADIUS - segmentEndR) / (segmentStartR - segmentEndR); + hitGroundOrLeftAtmosphere = true; + } + + // If the segment leaves the atmosphere, we compute how much of the segment is inside the + // atmosphere. + if (segmentEndR > TOP_RADIUS) { + weight = 1.0 - (segmentEndR - TOP_RADIUS) / (segmentEndR - segmentStartR); + hitGroundOrLeftAtmosphere = true; + } + + float currentMu = float(clampCosine(dot(samplePos / sampleRadius, currentDir))); + float currentMuS = float(clampCosine(dot(dvec3(samplePos, 0.0) / sampleRadius, sunDir))); + float currentNu = float(clampCosine(dot(dvec3(currentDir, 0.0), sunDir))); + + opticalDepthRay += getOpticalDepth(atmosphere, float(sampleRadius)) * dx * weight; + vec3 transmittanceRay = exp(-vec3(opticalDepthRay)) * dx; + + moleculesAerosolsSum += getScattering(scatteringDensityTexture, float(sampleRadius), currentMu, + currentMuS, currentNu, rayRMuIntersectsGround) * + transmittanceRay * float(weight); + + rayStep(samplePos, currentDir, dx); + weight = 1.0; + } + + return moleculesAerosolsSum; +} + +#else + +// See the second step of +// https://ebruneton.github.io/precomputed_atmospheric_scattering/atmosphere/functions.glsl.html#multiple_scattering + +vec3 computeMultipleScattering(AtmosphereComponents atmosphere, sampler2D transmittanceTexture, + sampler3D scatteringDensityTexture, float r, float mu, float muS, float nu, + bool rayRMuIntersectsGround) { // The integration step, i.e. the length of each integration interval. float dx = distanceToNearestAtmosphereBoundary(r, mu, rayRMuIntersectsGround) / @@ -325,6 +684,8 @@ vec3 computeMultipleScattering(sampler2D transmittanceTexture, sampler3D scatter return moleculesAerosolsSum; } +#endif + // Multiple-Scattering Texture Precomputation ------------------------------------------------------ // The code below is used to store the multiple scattering (with the phase function applied) in a @@ -350,15 +711,16 @@ vec3 computeScatteringDensityTexture(AtmosphereComponents atmosphere, irradianceTexture, r, mu, muS, nu, scatteringOrder); } -vec3 computeMultipleScatteringTexture(sampler2D transmittanceTexture, - sampler3D scatteringDensityTexture, vec3 fragCoord, out float nu) { +vec3 computeMultipleScatteringTexture(AtmosphereComponents atmosphere, + sampler2D transmittanceTexture, sampler3D scatteringDensityTexture, vec3 fragCoord, + out float nu) { float r; float mu; float muS; bool rayRMuIntersectsGround; getRMuMuSNuFromScatteringTextureFragCoord(fragCoord, r, mu, muS, nu, rayRMuIntersectsGround); - return computeMultipleScattering( - transmittanceTexture, scatteringDensityTexture, r, mu, muS, nu, rayRMuIntersectsGround); + return computeMultipleScattering(atmosphere, transmittanceTexture, scatteringDensityTexture, r, + mu, muS, nu, rayRMuIntersectsGround); } // Compute Irradiance ------------------------------------------------------------------------------ diff --git a/plugins/csp-atmospheres/gui/atmospheres_settings.html b/plugins/csp-atmospheres/gui/atmospheres_settings.html index 56301b747..8a3700b8e 100644 --- a/plugins/csp-atmospheres/gui/atmospheres_settings.html +++ b/plugins/csp-atmospheres/gui/atmospheres_settings.html @@ -4,52 +4,65 @@ -->
-
+
-
+
-
+
-
+
+
+ +
-
- Water Level -
-
-
-
+
Water Level
+
+
-
- Cloud Altitude -
-
-
-
+
Cloud Altitude
+
+
-
\ No newline at end of file +
diff --git a/plugins/csp-atmospheres/scattering-table-generator/README.md b/plugins/csp-atmospheres/scattering-table-generator/README.md index 975cd1129..b4916189d 100644 --- a/plugins/csp-atmospheres/scattering-table-generator/README.md +++ b/plugins/csp-atmospheres/scattering-table-generator/README.md @@ -26,12 +26,12 @@ This depends on where the `scattering-table-generator` is installed to, but this ```bash # For Windows (powershell) -cd install\windows-Release\bin -$env:Path += ";..\lib" +cd cosmoscout-vr +$env:Path += ";install\windows-Release\lib" # For Linux (bash) -cd install/linux-Release/bin -export LD_LIBRARY_PATH=../lib:$LD_LIBRARY_PATH +cd cosmoscout-vr +export LD_LIBRARY_PATH=install/linux-Release/lib:$LD_LIBRARY_PATH ``` ### The Preprocessing Modes @@ -41,22 +41,23 @@ This tool provides means to compute them physically-based using Mie Theory as we To learn about the different operation modes, you can issue this command: ```bash -./scattering-table-generator --help +install/linux-Release/bin/scattering-table-generator --help ``` > [!TIP] > Unless stated otherwise, length units must always be given in [m]. For instance, this is true for altitudes, wavelengths, and for particle radii. -| Mode | Description | -| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `mie` | This mode computes phase functions as well as scattering- and absorption coefficients for a given particle mixture using Mie Theory. The particle mixture follows a specified multi-modal size distribution and can have a complex, wavelength-dependent refractive index. Use `./scattering-table-generator mie --help` to learn about all the options. Also, below a multiple examples to get you started. | -| `rayleigh` | This mode writes the phase function and scattering coefficients of Rayleigh molecules for the specified wavelengths. Use `./scattering-table-generator rayleigh --help` to learn about all the options. | -| `angstrom` | This mode writes scattering, and absorption coefficients based on Ångström's turbidity formula and a single-scattering albedo value. Use `./scattering-table-generator angstrom --help` to learn about all the options. | -| `hulst` | This mode writes scattering, and absorption coefficients based on van de Hulst's Anomalous Diffraction Approximation and the turbidity approximation used in the [Costa Paper](https://arxiv.org/abs/2010.03534). Use `./scattering-table-generator hulst --help` to learn about all the options. | -| `manual` | This mode writes some user-specified scattering coefficients or absorption coefficients for the specified wavelengths. Use `./scattering-table-generator manual --help` to learn about all the options. | -| `cornette` `henyey` `dhenyey` | These modes write either the Cornette-Shanks, the Henyey-Greenstein, or the Double-Henyey-Greenstein parametric phase function for the specified wavelengths. Use `./scattering-table-generator --help` to learn about all the options. | -| `ozone` | This mode writes the absorption coefficients of ozone molecules for the specified wavelengths. Use `./scattering-table-generator ozone --help` to learn about all the options. | -| `density` | This mode samples a given multi-modal density function at evenly spaced altitudes and writes the resulting data. Use `./scattering-table-generator density --help` to learn about all the options. | +| Mode | Description | +| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `mie` | This mode computes phase functions as well as scattering- and absorption coefficients for a given particle mixture using Mie Theory. The particle mixture follows a specified multi-modal size distribution and can have a complex, wavelength-dependent refractive index. Use `scattering-table-generator mie --help` to learn about all the options. Also, below a multiple examples to get you started. | +| `rayleigh` | This mode writes the phase function and scattering coefficients of Rayleigh molecules for the specified wavelengths. Use `scattering-table-generator rayleigh --help` to learn about all the options. | +| `angstrom` | This mode writes scattering, and absorption coefficients based on Ångström's turbidity formula and a single-scattering albedo value. Use `scattering-table-generator angstrom --help` to learn about all the options. | +| `hulst` | This mode writes scattering, and absorption coefficients based on van de Hulst's Anomalous Diffraction Approximation and the turbidity approximation used in the [Costa Paper](https://arxiv.org/abs/2010.03534). Use `scattering-table-generator hulst --help` to learn about all the options. | +| `manual` | This mode writes some user-specified scattering coefficients or absorption coefficients for the specified wavelengths. Use `scattering-table-generator manual --help` to learn about all the options. | +| `cornette` `henyey` `dhenyey` | These modes write either the Cornette-Shanks, the Henyey-Greenstein, or the Double-Henyey-Greenstein parametric phase function for the specified wavelengths. Use `scattering-table-generator --help` to learn about all the options. | +| `ozone` | This mode writes the absorption coefficients of ozone molecules for the specified wavelengths. Use `scattering-table-generator ozone --help` to learn about all the options. | +| `density` | This mode samples a given multi-modal density function at evenly spaced altitudes and writes the resulting data. Use `scattering-table-generator density --help` to learn about all the options. | +| `ior` | This mode approximates the refractive index of a mixture of gases. It is not really used during the preprocessing as only one, wavelength-independent value is used by the atmospheric scattering, but it can be used to get this one value nonetheless. For increased precision, `n-1` is written to the output. Use `scattering-table-generator ior --help` to learn about all the options. | ## The CSV Files @@ -126,16 +127,20 @@ Below are the input values which we currently use for Earth's atmosphere in Cosm ```bash # Molecules -./scattering-table-generator density -i ../../../plugins/csp-atmospheres/scattering-table-generator/density-settings/earth_bruneton_molecules.json -o earth_cosmoscout_molecules -./scattering-table-generator rayleigh --scattering-depolarization 0.0279 --phase-depolarization 0.0279 --penndorf-ior --theta-samples 91 -o earth_cosmoscout_molecules +install/linux-Release/bin/scattering-table-generator density -i plugins/csp-atmospheres/scattering-table-generator/density-settings/earth_bruneton_molecules.json -o plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_molecules +install/linux-Release/bin/scattering-table-generator rayleigh --scattering-depolarization 0.0279 --phase-depolarization 0.0279 --penndorf-ior --theta-samples 91 -o plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_molecules # Aerosols -./scattering-table-generator density -i ../../../plugins/csp-atmospheres/scattering-table-generator/density-settings/earth_bruneton_aerosols.json -o earth_cosmoscout_aerosols -./scattering-table-generator mie -i ../../../plugins/csp-atmospheres/scattering-table-generator/mie-settings/earth_haze.json --theta-samples 91 --number-density 5e8 --radius-samples 10000 -o earth_cosmoscout_aerosols +install/linux-Release/bin/scattering-table-generator density -i plugins/csp-atmospheres/scattering-table-generator/density-settings/earth_bruneton_aerosols.json -o plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_aerosols +install/linux-Release/bin/scattering-table-generator mie -i plugins/csp-atmospheres/scattering-table-generator/mie-settings/earth_haze.json --theta-samples 91 --number-density 5e8 --radius-samples 10000 -o plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_aerosols # Ozone -./scattering-table-generator density -i ../../../plugins/csp-atmospheres/scattering-table-generator/density-settings/earth_bruneton_ozone.json -o earth_cosmoscout_ozone -./scattering-table-generator ozone -o earth_cosmoscout_ozone +install/linux-Release/bin/scattering-table-generator density -i plugins/csp-atmospheres/scattering-table-generator/density-settings/earth_bruneton_ozone.json -o plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_ozone +install/linux-Release/bin/scattering-table-generator ozone -o plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_ozone + +# IoR Information for Light Refraction +install/linux-Release/bin/scattering-table-generator ior -i plugins/csp-atmospheres/scattering-table-generator/ior-settings/earth.json -o plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_ior --temperature 288 --pressure 101325 + ```
@@ -157,12 +162,12 @@ The molecules are identical in both versions; they only differ in the number of ```bash # Molecules -./scattering-table-generator density -i ../../../plugins/csp-atmospheres/scattering-table-generator/density-settings/mars_cosmoscout_molecules.json -o mars_cosmoscout_molecules_realistic -./scattering-table-generator rayleigh --ior 1.00000337 --scattering-depolarization 0.09 --phase-depolarization 0.09 --number-density 2.05e23 --theta-samples 91 -o mars_cosmoscout_molecules_realistic +install/linux-Release/bin/scattering-table-generator density -i plugins/csp-atmospheres/scattering-table-generator/density-settings/mars_cosmoscout_molecules.json -o plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_molecules_realistic +install/linux-Release/bin/scattering-table-generator rayleigh --ior 1.00000337 --scattering-depolarization 0.09 --phase-depolarization 0.09 --number-density 2.05e23 --theta-samples 91 -o plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_molecules_realistic # Aerosols -./scattering-table-generator density -i ../../../plugins/csp-atmospheres/scattering-table-generator/density-settings/mars_cosmoscout_aerosols_realistic.json -o mars_cosmoscout_aerosols_realistic -./scattering-table-generator mie -i ../../../plugins/csp-atmospheres/scattering-table-generator/mie-settings/mars_realistic.json --theta-samples 91 --number-density 5e9 --radius-samples 10000 -o mars_cosmoscout_aerosols_realistic +install/linux-Release/bin/scattering-table-generator density -i plugins/csp-atmospheres/scattering-table-generator/density-settings/mars_cosmoscout_aerosols_realistic.json -o plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_aerosols_realistic +install/linux-Release/bin/scattering-table-generator mie -i plugins/csp-atmospheres/scattering-table-generator/mie-settings/mars_realistic.json --theta-samples 91 --number-density 5e9 --radius-samples 10000 -o plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_aerosols_realistic ``` @@ -172,12 +177,12 @@ The molecules are identical in both versions; they only differ in the number of ```bash # Molecules -./scattering-table-generator density -i ../../../plugins/csp-atmospheres/scattering-table-generator/density-settings/mars_cosmoscout_molecules.json -o mars_cosmoscout_molecules_cinematic -./scattering-table-generator rayleigh --lambdas 440e-9,550e-9,680e-9 --ior 1.00000337 --scattering-depolarization 0.09 --phase-depolarization 0.09 --number-density 2.05e23 --theta-samples 91 -o mars_cosmoscout_molecules_cinematic +install/linux-Release/bin/scattering-table-generator density -i plugins/csp-atmospheres/scattering-table-generator/density-settings/mars_cosmoscout_molecules.json -o plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_molecules_cinematic +install/linux-Release/bin/scattering-table-generator rayleigh --lambdas 440e-9,550e-9,680e-9 --ior 1.00000337 --scattering-depolarization 0.09 --phase-depolarization 0.09 --number-density 2.05e23 --theta-samples 91 -o plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_molecules_cinematic # Aerosols -./scattering-table-generator density -i ../../../plugins/csp-atmospheres/scattering-table-generator/density-settings/mars_cosmoscout_aerosols_cinematic.json -o mars_cosmoscout_aerosols_cinematic -./scattering-table-generator mie --lambdas 440e-9,550e-9,680e-9 -i ../../../plugins/csp-atmospheres/scattering-table-generator/mie-settings/mars_cinematic.json --phase-flattening 0.8 --theta-samples 91 --number-density 5e9 --radius-samples 10000 -o mars_cosmoscout_aerosols_cinematic +install/linux-Release/bin/scattering-table-generator density -i plugins/csp-atmospheres/scattering-table-generator/density-settings/mars_cosmoscout_aerosols_cinematic.json -o plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_aerosols_cinematic +install/linux-Release/bin/scattering-table-generator mie --lambdas 440e-9,550e-9,680e-9 -i plugins/csp-atmospheres/scattering-table-generator/mie-settings/mars_cinematic.json --phase-flattening 0.8 --theta-samples 91 --number-density 5e9 --radius-samples 10000 -o plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_aerosols_cinematic ``` @@ -200,15 +205,15 @@ If we divide it by 100, we get plausible results. ```bash # Molecules -./scattering-table-generator density -i ../../../plugins/csp-atmospheres/scattering-table-generator/density-settings/earth_bruneton_molecules.json -o earth_bruneton2008_molecules -./scattering-table-generator rayleigh --lambdas 440e-9,550e-9,680e-9 -o earth_bruneton2008_molecules -./scattering-table-generator manual --lambdas 440e-9,550e-9,680e-9 --quantity beta_sca --values 33.1e-6,15.5e-6,5.8e-6 -o earth_bruneton2008_molecules_scattering +install/linux-Release/bin/scattering-table-generator density -i plugins/csp-atmospheres/scattering-table-generator/density-settings/earth_bruneton_molecules.json -o plugins/csp-atmospheres/scattering-table-generator/output/earth_bruneton2008_molecules +install/linux-Release/bin/scattering-table-generator rayleigh --lambdas 440e-9,550e-9,680e-9 -o plugins/csp-atmospheres/scattering-table-generator/output/earth_bruneton2008_molecules +install/linux-Release/bin/scattering-table-generator manual --lambdas 440e-9,550e-9,680e-9 --quantity beta_sca --values 33.1e-6,15.5e-6,5.8e-6 -o plugins/csp-atmospheres/scattering-table-generator/output/earth_bruneton2008_molecules_scattering # Aerosols -./scattering-table-generator density -i ../../../plugins/csp-atmospheres/scattering-table-generator/density-settings/earth_bruneton_aerosols.json -o earth_bruneton2008_aerosols -./scattering-table-generator cornette --lambdas 440e-9,550e-9,680e-9 --g 0.76 -o earth_bruneton2008_aerosols -./scattering-table-generator manual --lambdas 440e-9,550e-9,680e-9 --quantity beta_sca --values 2.1e-5 -o earth_bruneton2008_aerosols_scattering -./scattering-table-generator manual --lambdas 440e-9,550e-9,680e-9 --quantity beta_abs --values 2.1e-6 -o earth_bruneton2008_aerosols_absorption +install/linux-Release/bin/scattering-table-generator density -i plugins/csp-atmospheres/scattering-table-generator/density-settings/earth_bruneton_aerosols.json -o plugins/csp-atmospheres/scattering-table-generator/output/earth_bruneton2008_aerosols +install/linux-Release/bin/scattering-table-generator cornette --lambdas 440e-9,550e-9,680e-9 --g 0.76 -o plugins/csp-atmospheres/scattering-table-generator/output/earth_bruneton2008_aerosols +install/linux-Release/bin/scattering-table-generator manual --lambdas 440e-9,550e-9,680e-9 --quantity beta_sca --values 2.1e-5 -o plugins/csp-atmospheres/scattering-table-generator/output/earth_bruneton2008_aerosols_scattering +install/linux-Release/bin/scattering-table-generator manual --lambdas 440e-9,550e-9,680e-9 --quantity beta_abs --values 2.1e-6 -o plugins/csp-atmospheres/scattering-table-generator/output/earth_bruneton2008_aerosols_absorption ``` @@ -225,17 +230,17 @@ In this paper, Eric Bruneton also included **Ozone**. ```bash # Molecules -./scattering-table-generator density -i ../../../plugins/csp-atmospheres/scattering-table-generator/density-settings/earth_bruneton_molecules.json -o earth_bruneton2016_molecules -./scattering-table-generator rayleigh --lambda-samples 40 --penndorf-extinction -o earth_bruneton2016_molecules +install/linux-Release/bin/scattering-table-generator density -i plugins/csp-atmospheres/scattering-table-generator/density-settings/earth_bruneton_molecules.json -o plugins/csp-atmospheres/scattering-table-generator/output/earth_bruneton2016_molecules +install/linux-Release/bin/scattering-table-generator rayleigh --lambda-samples 40 --penndorf-extinction -o plugins/csp-atmospheres/scattering-table-generator/output/earth_bruneton2016_molecules # Aerosols -./scattering-table-generator density -i ../../../plugins/csp-atmospheres/scattering-table-generator/density-settings/earth_bruneton_aerosols.json -o earth_bruneton2016_aerosols -./scattering-table-generator cornette --lambda-samples 40 --g 0.7 -o earth_bruneton2016_aerosols -./scattering-table-generator angstrom --lambda-samples 40 --alpha 0.8 --beta 0.04 --single-scattering-albedo 0.8 --scale-height 1200 -o earth_bruneton2016_aerosols +install/linux-Release/bin/scattering-table-generator density -i plugins/csp-atmospheres/scattering-table-generator/density-settings/earth_bruneton_aerosols.json -o plugins/csp-atmospheres/scattering-table-generator/output/earth_bruneton2016_aerosols +install/linux-Release/bin/scattering-table-generator cornette --lambda-samples 40 --g 0.7 -o plugins/csp-atmospheres/scattering-table-generator/output/earth_bruneton2016_aerosols +install/linux-Release/bin/scattering-table-generator angstrom --lambda-samples 40 --alpha 0.8 --beta 0.04 --single-scattering-albedo 0.8 --scale-height 1200 -o plugins/csp-atmospheres/scattering-table-generator/output/earth_bruneton2016_aerosols # Ozone -./scattering-table-generator density -i ../../../plugins/csp-atmospheres/scattering-table-generator/density-settings/earth_bruneton_ozone.json -o earth_bruneton2016_ozone -./scattering-table-generator ozone --lambda-samples 40 -o earth_bruneton2016_ozone +install/linux-Release/bin/scattering-table-generator density -i plugins/csp-atmospheres/scattering-table-generator/density-settings/earth_bruneton_ozone.json -o plugins/csp-atmospheres/scattering-table-generator/output/earth_bruneton2016_ozone +install/linux-Release/bin/scattering-table-generator ozone --lambda-samples 40 -o plugins/csp-atmospheres/scattering-table-generator/output/earth_bruneton2016_ozone ``` @@ -256,18 +261,18 @@ They actually use a different **ozone** density profile than Bruneton, but the r ```bash # Molecules -./scattering-table-generator density -i ../../../plugins/csp-atmospheres/scattering-table-generator/density-settings/earth_bruneton_molecules.json -o earth_costa_molecules -./scattering-table-generator rayleigh --lambdas 440e-9,550e-9,680e-9 --penndorf-ior --penndorf-phase --scattering-depolarization 0.0279 --number-density 2.68731e25 -o earth_costa_molecules +install/linux-Release/bin/scattering-table-generator density -i plugins/csp-atmospheres/scattering-table-generator/density-settings/earth_bruneton_molecules.json -o plugins/csp-atmospheres/scattering-table-generator/output/earth_costa_molecules +install/linux-Release/bin/scattering-table-generator rayleigh --lambdas 440e-9,550e-9,680e-9 --penndorf-ior --penndorf-phase --scattering-depolarization 0.0279 --number-density 2.68731e25 -o plugins/csp-atmospheres/scattering-table-generator/output/earth_costa_molecules # Aerosols -./scattering-table-generator density -i ../../../plugins/csp-atmospheres/scattering-table-generator/density-settings/earth_bruneton_aerosols.json -o earth_costa_aerosols -./scattering-table-generator henyey --lambdas 440e-9,550e-9,680e-9 --g 0.85 -o earth_costa_aerosols -./scattering-table-generator manual --lambdas 440e-9,550e-9,680e-9 --quantity beta_sca --values 4e-5 -o earth_costa_aerosols_scattering -./scattering-table-generator manual --lambdas 440e-9,550e-9,680e-9 --quantity beta_abs --values 4e-6 -o earth_costa_aerosols_absorption +install/linux-Release/bin/scattering-table-generator density -i plugins/csp-atmospheres/scattering-table-generator/density-settings/earth_bruneton_aerosols.json -o plugins/csp-atmospheres/scattering-table-generator/output/earth_costa_aerosols +install/linux-Release/bin/scattering-table-generator henyey --lambdas 440e-9,550e-9,680e-9 --g 0.85 -o plugins/csp-atmospheres/scattering-table-generator/output/earth_costa_aerosols +install/linux-Release/bin/scattering-table-generator manual --lambdas 440e-9,550e-9,680e-9 --quantity beta_sca --values 4e-5 -o plugins/csp-atmospheres/scattering-table-generator/output/earth_costa_aerosols_scattering +install/linux-Release/bin/scattering-table-generator manual --lambdas 440e-9,550e-9,680e-9 --quantity beta_abs --values 4e-6 -o plugins/csp-atmospheres/scattering-table-generator/output/earth_costa_aerosols_absorption # Ozone -./scattering-table-generator density -i ../../../plugins/csp-atmospheres/scattering-table-generator/density-settings/earth_bruneton_ozone.json -o earth_costa_ozone -./scattering-table-generator ozone --lambdas 440e-9,550e-9,680e-9 -o earth_costa_ozone +install/linux-Release/bin/scattering-table-generator density -i plugins/csp-atmospheres/scattering-table-generator/density-settings/earth_bruneton_ozone.json -o plugins/csp-atmospheres/scattering-table-generator/output/earth_costa_ozone +install/linux-Release/bin/scattering-table-generator ozone --lambdas 440e-9,550e-9,680e-9 -o plugins/csp-atmospheres/scattering-table-generator/output/earth_costa_ozone ``` @@ -282,15 +287,15 @@ In this paper, **molecules** are modelled using a manual parametrization of Rayl ```bash # Molecules -./scattering-table-generator density -i ../../../plugins/csp-atmospheres/scattering-table-generator/density-settings/mars_collienne_molecules.json -o mars_collienne_molecules -./scattering-table-generator rayleigh --lambdas 440e-9,550e-9,680e-9 -o mars_collienne_molecules -./scattering-table-generator manual --lambdas 440e-9,550e-9,680e-9 --quantity beta_sca --values 5.75e-6,13.57e-6,19.918e-6 -o mars_collienne_molecules_scattering +install/linux-Release/bin/scattering-table-generator density -i plugins/csp-atmospheres/scattering-table-generator/density-settings/mars_collienne_molecules.json -o plugins/csp-atmospheres/scattering-table-generator/output/mars_collienne_molecules +install/linux-Release/bin/scattering-table-generator rayleigh --lambdas 440e-9,550e-9,680e-9 -o plugins/csp-atmospheres/scattering-table-generator/output/mars_collienne_molecules +install/linux-Release/bin/scattering-table-generator manual --lambdas 440e-9,550e-9,680e-9 --quantity beta_sca --values 5.75e-6,13.57e-6,19.918e-6 -o plugins/csp-atmospheres/scattering-table-generator/output/mars_collienne_molecules_scattering # Aerosols -./scattering-table-generator density -i ../../../plugins/csp-atmospheres/scattering-table-generator/density-settings/mars_collienne_aerosols.json -o mars_collienne_aerosols -./scattering-table-generator cornette --lambdas 440e-9,550e-9,680e-9 --g 0.76 -o mars_collienne_aerosols -./scattering-table-generator manual --lambdas 440e-9,550e-9,680e-9 --quantity beta_sca --values 3e-6 -o mars_collienne_aerosols_scattering -./scattering-table-generator manual --lambdas 440e-9,550e-9,680e-9 --quantity beta_abs --values 0 -o mars_collienne_aerosols_absorption +install/linux-Release/bin/scattering-table-generator density -i plugins/csp-atmospheres/scattering-table-generator/density-settings/mars_collienne_aerosols.json -o plugins/csp-atmospheres/scattering-table-generator/output/mars_collienne_aerosols +install/linux-Release/bin/scattering-table-generator cornette --lambdas 440e-9,550e-9,680e-9 --g 0.76 -o plugins/csp-atmospheres/scattering-table-generator/output/mars_collienne_aerosols +install/linux-Release/bin/scattering-table-generator manual --lambdas 440e-9,550e-9,680e-9 --quantity beta_sca --values 3e-6 -o plugins/csp-atmospheres/scattering-table-generator/output/mars_collienne_aerosols_scattering +install/linux-Release/bin/scattering-table-generator manual --lambdas 440e-9,550e-9,680e-9 --quantity beta_abs --values 0 -o plugins/csp-atmospheres/scattering-table-generator/output/mars_collienne_aerosols_absorption ``` @@ -325,16 +330,16 @@ The values below generate a plausible atmosphere, however most of the values are ```bash # Molecules -./scattering-table-generator density -i ../../../plugins/csp-atmospheres/scattering-table-generator/density-settings/mars_costa_molecules.json -o mars_costa_molecules -./scattering-table-generator rayleigh --lambdas 440e-9,550e-9,680e-9 --ior 1.00000337 --penndorf-phase --scattering-depolarization 0.09 --number-density 2.05e23 -o mars_costa_molecules +install/linux-Release/bin/scattering-table-generator density -i plugins/csp-atmospheres/scattering-table-generator/density-settings/mars_costa_molecules.json -o plugins/csp-atmospheres/scattering-table-generator/output/mars_costa_molecules +install/linux-Release/bin/scattering-table-generator rayleigh --lambdas 440e-9,550e-9,680e-9 --ior 1.00000337 --penndorf-phase --scattering-depolarization 0.09 --number-density 2.05e23 -o plugins/csp-atmospheres/scattering-table-generator/output/mars_costa_molecules # Aerosols -./scattering-table-generator density -i ../../../plugins/csp-atmospheres/scattering-table-generator/density-settings/mars_costa_aerosols.json -o mars_costa_aerosols -./scattering-table-generator hulst --lambdas 440e-9,550e-9,680e-9 --junge 4 --number-density 0.02e8 --kappa 0.07,0.16,0.31 --turbidity 1.01 --radius 1.6e-6 -n 1.52 -k 0.013,0.006,0.001 -o mars_costa_aerosols -./scattering-table-generator dhenyey --lambdas 440e-9,550e-9,680e-9 --g1 0.67,0.4,0.03 --g2 0.094,0.094,0.094 --alpha 0.743,0.743,0.743 -o mars_costa_aerosols +install/linux-Release/bin/scattering-table-generator density -i plugins/csp-atmospheres/scattering-table-generator/density-settings/mars_costa_aerosols.json -o plugins/csp-atmospheres/scattering-table-generator/output/mars_costa_aerosols +install/linux-Release/bin/scattering-table-generator hulst --lambdas 440e-9,550e-9,680e-9 --junge 4 --number-density 0.02e8 --kappa 0.07,0.16,0.31 --turbidity 1.01 --radius 1.6e-6 -n 1.52 -k 0.013,0.006,0.001 -o plugins/csp-atmospheres/scattering-table-generator/output/mars_costa_aerosols +install/linux-Release/bin/scattering-table-generator dhenyey --lambdas 440e-9,550e-9,680e-9 --g1 0.67,0.4,0.03 --g2 0.094,0.094,0.094 --alpha 0.743,0.743,0.743 -o plugins/csp-atmospheres/scattering-table-generator/output/mars_costa_aerosols # Paper values: -#./scattering-table-generator dhenyey --lambdas 440e-9,550e-9,680e-9 --g1 0.67,0.4,0.03 --g2 0.099,0.89,0.094 --alpha 0.01,0.04,0.743 -o mars_costa_aerosols +#./scattering-table-generator dhenyey --lambdas 440e-9,550e-9,680e-9 --g1 0.67,0.4,0.03 --g2 0.099,0.89,0.094 --alpha 0.01,0.04,0.743 -o plugins/csp-atmospheres/scattering-table-generator/output/mars_costa_aerosols ``` diff --git a/plugins/csp-atmospheres/scattering-table-generator/density-settings/earth_bruneton_aerosols.json b/plugins/csp-atmospheres/scattering-table-generator/density-settings/earth_bruneton_aerosols.json index bd9168a97..8d1b6bca0 100644 --- a/plugins/csp-atmospheres/scattering-table-generator/density-settings/earth_bruneton_aerosols.json +++ b/plugins/csp-atmospheres/scattering-table-generator/density-settings/earth_bruneton_aerosols.json @@ -1,9 +1,9 @@ { - "densityModes": [ - { - "type": "exponential", - "scaleHeight": 1200, - "relativeNumberDensity": 1 - } - ] -} \ No newline at end of file + "densityModes": [ + { + "type": "exponential", + "scaleHeight": 1200, + "relativeNumberDensity": 1 + } + ] +} diff --git a/plugins/csp-atmospheres/scattering-table-generator/density-settings/earth_bruneton_molecules.json b/plugins/csp-atmospheres/scattering-table-generator/density-settings/earth_bruneton_molecules.json index a8f35d56a..b7eacfcc3 100644 --- a/plugins/csp-atmospheres/scattering-table-generator/density-settings/earth_bruneton_molecules.json +++ b/plugins/csp-atmospheres/scattering-table-generator/density-settings/earth_bruneton_molecules.json @@ -1,9 +1,9 @@ { - "densityModes": [ - { - "type": "exponential", - "scaleHeight": 8000, - "relativeNumberDensity": 1 - } - ] -} \ No newline at end of file + "densityModes": [ + { + "type": "exponential", + "scaleHeight": 8000, + "relativeNumberDensity": 1 + } + ] +} diff --git a/plugins/csp-atmospheres/scattering-table-generator/ior-settings/earth.json b/plugins/csp-atmospheres/scattering-table-generator/ior-settings/earth.json new file mode 100644 index 000000000..75cfaa076 --- /dev/null +++ b/plugins/csp-atmospheres/scattering-table-generator/ior-settings/earth.json @@ -0,0 +1,20 @@ +{ + "components": [ + { + "type": "nitrogen", + "volumeMixingRatio": 0.78084 + }, + { + "type": "oxygen", + "volumeMixingRatio": 0.20946 + }, + { + "type": "argon", + "volumeMixingRatio": 0.00934 + }, + { + "type": "carbonDioxide", + "volumeMixingRatio": 0.000412 + } + ] +} diff --git a/plugins/csp-atmospheres/scattering-table-generator/ior-settings/mars.json b/plugins/csp-atmospheres/scattering-table-generator/ior-settings/mars.json new file mode 100644 index 000000000..3b00bd060 --- /dev/null +++ b/plugins/csp-atmospheres/scattering-table-generator/ior-settings/mars.json @@ -0,0 +1,16 @@ +{ + "components": [ + { + "type": "carbonDioxide", + "volumeMixingRatio": 0.951 + }, + { + "type": "nitrogen", + "volumeMixingRatio": 0.028 + }, + { + "type": "argon", + "volumeMixingRatio": 0.021 + } + ] +} diff --git a/plugins/csp-atmospheres/scattering-table-generator/iorMode.cpp b/plugins/csp-atmospheres/scattering-table-generator/iorMode.cpp new file mode 100644 index 000000000..b656b2cf7 --- /dev/null +++ b/plugins/csp-atmospheres/scattering-table-generator/iorMode.cpp @@ -0,0 +1,195 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////// +// This file is part of CosmoScout VR // +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// SPDX-FileCopyrightText: German Aerospace Center (DLR) +// SPDX-License-Identifier: MIT + +#include "iorMode.hpp" + +#include "common.hpp" +#include "densityMode.hpp" + +#include "../../../src/cs-utils/CommandLine.hpp" +#include "../../../src/cs-utils/utils.hpp" + +#include +#include +#include + +#include + +namespace { + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// These gases are currently supported. // +//////////////////////////////////////////////////////////////////////////////////////////////////// + +enum class GasType { eNitrogen, eOxygen, eArgon, eCarbonDioxide }; + +// Make the DensityDistributionTypes available for JSON deserialization. +NLOHMANN_JSON_SERIALIZE_ENUM(GasType, { + {GasType::eNitrogen, "nitrogen"}, + {GasType::eOxygen, "oxygen"}, + {GasType::eArgon, "argon"}, + {GasType::eCarbonDioxide, "carbonDioxide"}, + }) + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Each atmosphere is composed of a number of gases. Each gas is described by its type and the // +// volume mixing ratio. // +//////////////////////////////////////////////////////////////////////////////////////////////////// + +struct IorComponent { + GasType type; + double volumeMixingRatio; +}; + +struct IorSettings { + std::vector components; +}; + +void from_json(nlohmann::json const& j, IorComponent& s) { + j.at("type").get_to(s.type); + j.at("volumeMixingRatio").get_to(s.volumeMixingRatio); +} + +// Make this type available for JSON deserialization. +void from_json(nlohmann::json const& j, IorSettings& s) { + j.at("components").get_to(s.components); +} + +// Computes the refractive index of a gas at a given wavelength (in m), pressure (in Pa), and +// temperature (in K). +double getIoR(GasType type, double lambda, double pressure, double temperature) { + double mu2 = std::pow(lambda * 1e6, -2.0); + double n0 = 1.0; + + switch (type) { + // E. R. Peck and B. N. Khanna. Dispersion of nitrogen + case GasType::eNitrogen: + n0 = 1.0 + 6.8552e-5 + 3.243157e-2 / (144 - mu2); + break; + + // J. Zhang, Z. H. Lu, and L. J. Wang. Precision refractive index measurements of air, N2, O2, + // Ar, and CO2 with a frequency comb. This is given at 20°C. + case GasType::eOxygen: + n0 = 1.0 + 1.181494e-4 + 9.708931e-3 / (75.4 - mu2); + n0 = 1.0 + (n0 - 1) * 293.15 / 273; // Correct for the actual temperature. + break; + + // E. R. Peck and D. J. Fisher. Dispersion of argon + case GasType::eArgon: + n0 = 1.0 + 6.7867e-5 + 3.0182943e-2 / (144 - mu2); + break; + + // J. G. Old, K. L. Gentili, and E. R. Peck. Dispersion of Carbon Dioxide + case GasType::eCarbonDioxide: + n0 = 1.0 + 0.00000154489 / (0.0584738 - mu2) + 0.083091927 / (210.9241 - mu2) + + 0.0028764190 / (60.122959 - mu2); + break; + } + + // The pressure is given in Pa, the temperature in K. The IoR above is measured at STP (273 K, + // 101325 Pa). We need to correct for the actual pressure and temperature. + return 1.0 + (n0 - 1.0) * pressure / temperature * 273 / 101325.0; +} + +} // namespace + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +int iorMode(std::vector const& arguments) { + + bool cPrintHelp = false; + std::string cInput = ""; + std::string cOutput = "ior"; + double cPressure = 101325; + double cTemperature = 273; + std::string cLambdas = ""; + double cMinLambda = 0.36e-6; + double cMaxLambda = 0.83e-6; + int32_t cLambdaSamples = 15; + double cMinAltitude = 0.0; + double cMaxAltitude = 80000; + int32_t cAltitudeSamples = 1024; + + // First configure all possible command line options. + cs::utils::CommandLine args("Here are the available options:"); + args.addArgument( + {"-i", "--input"}, &cInput, "The JSON file with the IoR information (required)."); + args.addArgument({"-o", "--output"}, &cOutput, + "The IoR data will be written to .csv (default: \"" + cOutput + "\")."); + args.addArgument({"--pressure"}, &cPressure, + "The pressure in Pa (default: " + std::to_string(cPressure) + ")."); + args.addArgument({"--temperature"}, &cTemperature, + "The temperature in K (default: " + std::to_string(cTemperature) + ")."); + args.addArgument({"--min-altitude"}, &cMinAltitude, + "The minimum altitude in m (default: " + std::to_string(cMinAltitude) + ")."); + args.addArgument({"--max-altitude"}, &cMaxAltitude, + "The maximum altitude in m (default: " + std::to_string(cMaxAltitude) + ")."); + args.addArgument({"--altitude-samples"}, &cAltitudeSamples, + "The number of altitudes to compute (default: " + std::to_string(cAltitudeSamples) + ")."); + common::addLambdaFlags(args, &cLambdas, &cMinLambda, &cMaxLambda, &cLambdaSamples); + args.addArgument({"-h", "--help"}, &cPrintHelp, "Show this help message."); + + // Then do the actual parsing. + try { + args.parse(arguments); + } catch (std::runtime_error const& e) { + std::cerr << "Failed to parse command line arguments: " << e.what() << std::endl; + return 1; + } + + // When cPrintHelp was set to true, we print a help message and exit. + if (cPrintHelp) { + args.printHelp(); + return 0; + } + + // The input information is mandatory. + if (cInput.empty()) { + std::cerr << "Please specify an IoR information file with the --ior-input option!" << std::endl; + return 1; + } + + // Try parsing the composition settings. + std::ifstream stream(cInput); + nlohmann::json json; + stream >> json; + IorSettings iorSettings; + from_json(json, iorSettings); + + // Compute the total volume mixing ratio for normalization. + double totalVolume = 0.0; + for (auto const& component : iorSettings.components) { + totalVolume += component.volumeMixingRatio; + } + + // Open the output file for writing and write the CSV header. + std::ofstream output(cOutput + ".csv"); + output << "lambda,n-1" << std::endl; + + // Now assemble a list of wavelengths in m. This is either provided with the --lambda-samples + // command-line parameter or via the combination of --min-lambda, --max-lambda, and + // --lambda-samples. + std::vector lambdas = + common::computeLambdas(cLambdas, cMinLambda, cMaxLambda, cLambdaSamples); + + // For each wavelength, compute the IoR and write it to the output file. + for (double lambda : lambdas) { + + double ior = 0.0; + + for (auto const& component : iorSettings.components) { + ior += component.volumeMixingRatio * getIoR(component.type, lambda, cPressure, cTemperature) / + totalVolume; + } + + output << fmt::format("{},{}", lambda, ior - 1.0) << std::endl; + } + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/plugins/csp-atmospheres/scattering-table-generator/iorMode.hpp b/plugins/csp-atmospheres/scattering-table-generator/iorMode.hpp new file mode 100644 index 000000000..af5b5101c --- /dev/null +++ b/plugins/csp-atmospheres/scattering-table-generator/iorMode.hpp @@ -0,0 +1,21 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////// +// This file is part of CosmoScout VR // +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// SPDX-FileCopyrightText: German Aerospace Center (DLR) +// SPDX-License-Identifier: MIT + +#ifndef IOR_MODE_HPP +#define IOR_MODE_HPP + +#include +#include + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// This method is used to precompute the wavelength-dependent index of refraction of an // +// atmosphere as a function of altitude. See README.md for usage details. // +//////////////////////////////////////////////////////////////////////////////////////////////////// + +int iorMode(std::vector const& arguments); + +#endif // IOR_MODE_HPP \ No newline at end of file diff --git a/plugins/csp-atmospheres/scattering-table-generator/main.cpp b/plugins/csp-atmospheres/scattering-table-generator/main.cpp index 1c23201f0..e65cc5eb8 100644 --- a/plugins/csp-atmospheres/scattering-table-generator/main.cpp +++ b/plugins/csp-atmospheres/scattering-table-generator/main.cpp @@ -10,6 +10,7 @@ #include "angstromMode.hpp" #include "densityMode.hpp" #include "hulstMode.hpp" +#include "iorMode.hpp" #include "manualMode.hpp" #include "mieMode.hpp" #include "ozoneMode.hpp" @@ -38,6 +39,7 @@ void printHelp() { std::cout << "dhenyey Write the Double-Henyey-Greenstein phase function for the given wavelengths." << std::endl; std::cout << "ozone Write ozone absorption coefficients for the given wavelengths." << std::endl; std::cout << "density Precompute particle density distributions as a function of altitude." << std::endl; + std::cout << "ior Precompute atmospheric index of refraction as a function of altitude." << std::endl; } // clang-format on @@ -97,6 +99,10 @@ int main(int argc, char** argv) { return densityMode(arguments); } + if (cMode == "ior") { + return iorMode(arguments); + } + printHelp(); return 0; diff --git a/plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_aerosols_absorption.csv b/plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_aerosols_absorption.csv index 32ef22eba..115a01e94 100644 --- a/plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_aerosols_absorption.csv +++ b/plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_aerosols_absorption.csv @@ -1,16 +1,16 @@ lambda,beta_abs -3.6e-07,4.7087710456567195e-06 -3.935714285714286e-07,4.669362745728e-06 -4.271428571428571e-07,4.4864950875022234e-06 -4.607142857142857e-07,4.172385949072078e-06 -4.942857142857143e-07,3.550140373212952e-06 -5.278571428571428e-07,3.4133540597479645e-06 -5.614285714285714e-07,3.1134041634011083e-06 -5.95e-07,3.019824480038516e-06 -6.285714285714286e-07,2.817254682706799e-06 -6.621428571428571e-07,2.722217862933896e-06 -6.957142857142857e-07,2.7103588705260765e-06 -7.292857142857143e-07,2.452618648974496e-06 -7.628571428571428e-07,2.2901205208664713e-06 -7.964285714285715e-07,2.2037035403202004e-06 -8.3e-07,2.0346508337442743e-06 +3.6e-07,4.892415693834374e-06 +3.935714285714286e-07,4.544553591285171e-06 +4.271428571428571e-07,4.409014228615248e-06 +4.607142857142857e-07,3.954229665914797e-06 +4.942857142857143e-07,3.5584787914631302e-06 +5.278571428571428e-07,3.4722503913369437e-06 +5.614285714285714e-07,3.063957689222532e-06 +5.95e-07,3.0060721562744676e-06 +6.285714285714286e-07,2.9933102869973157e-06 +6.621428571428571e-07,2.5333017961428594e-06 +6.957142857142857e-07,2.6198992726824502e-06 +7.292857142857143e-07,2.392062104283752e-06 +7.628571428571428e-07,2.202373772614936e-06 +7.964285714285715e-07,2.207564740880914e-06 +8.3e-07,2.046348637370256e-06 diff --git a/plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_aerosols_phase.csv b/plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_aerosols_phase.csv index 5de633832..c1b06c1e6 100644 --- a/plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_aerosols_phase.csv +++ b/plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_aerosols_phase.csv @@ -1,16 +1,16 @@ lambda,0.0,1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0,13.0,14.0,15.0,16.0,17.0,18.0,19.0,20.0,21.0,22.0,23.0,24.0,25.0,26.0,27.0,28.0,29.0,30.0,31.0,32.0,33.0,34.0,35.0,36.0,37.0,38.0,39.0,40.0,41.0,42.0,43.0,44.0,45.0,46.0,47.0,48.0,49.0,50.0,51.0,52.0,53.0,54.0,55.0,56.0,57.0,58.0,59.0,60.0,61.0,62.0,63.0,64.0,65.0,66.0,67.0,68.0,69.0,70.0,71.0,72.0,73.0,74.0,75.0,76.0,77.0,78.0,79.0,80.0,81.0,82.0,83.0,84.0,85.0,86.0,87.0,88.0,89.0,90.0,91.0,92.0,93.0,94.0,95.0,96.0,97.0,98.0,99.0,100.0,101.0,102.0,103.0,104.0,105.0,106.0,107.0,108.0,109.0,110.0,111.0,112.0,113.0,114.0,115.0,116.0,117.0,118.0,119.0,120.0,121.0,122.0,123.0,124.0,125.0,126.0,127.0,128.0,129.0,130.0,131.0,132.0,133.0,134.0,135.0,136.0,137.0,138.0,139.0,140.0,141.0,142.0,143.0,144.0,145.0,146.0,147.0,148.0,149.0,150.0,151.0,152.0,153.0,154.0,155.0,156.0,157.0,158.0,159.0,160.0,161.0,162.0,163.0,164.0,165.0,166.0,167.0,168.0,169.0,170.0,171.0,172.0,173.0,174.0,175.0,176.0,177.0,178.0,179.0,180.0 -3.6e-07,5.914744381090301,3.5267013080115395,2.2631022411116817,1.81725187026707,1.6288360896219167,1.5282278019841642,1.4527552958967132,1.3867314993751616,1.322299562857801,1.2575472235882807,1.1927579404075024,1.1279844697734913,1.06374127328513,1.000903872611182,0.9399320948502375,0.8812041866653575,0.8251059010019333,0.771813956630904,0.7214631486969206,0.6739943916069541,0.6293459893610968,0.5874263034322414,0.5480719074309565,0.5111627104922557,0.47656071838080794,0.4441343207184985,0.41376410069334696,0.38532192894670547,0.35876356649343377,0.3340177742578158,0.3109838842815517,0.2895710415424317,0.2697336516342007,0.2513720670152156,0.23439935807413664,0.2187242010099586,0.2042464725767659,0.19085666499759574,0.1784594294484372,0.1669613582073217,0.15628555423034146,0.1463466140359885,0.13709293136062653,0.128463073179997,0.12039729629424877,0.1128741419741254,0.10585867076165477,0.099303431514557,0.09319470579654368,0.08751235816789324,0.08222413230415274,0.07730318312934628,0.07272013024842817,0.06845537449553095,0.06448634301457368,0.060785824909385965,0.057331352065992366,0.054106321975299534,0.05109280458354126,0.048274982844611916,0.0456416671792147,0.04317909914272448,0.04087897763207132,0.03873176000844603,0.036729187580867864,0.03486208912707532,0.03312108932831893,0.031497697507878536,0.02998256588691157,0.02856440791572262,0.027237367343146145,0.025992884966518047,0.02482274626063608,0.023720845372129123,0.022680425649917332,0.02169816557846009,0.020770509702669783,0.0198949436050801,0.019066505602176393,0.01828403264096875,0.01754549641603022,0.016849271175930474,0.016192556464878452,0.015573730732802134,0.01499164939282401,0.01444484153446657,0.01393206538940635,0.013451863448430912,0.01300324595791968,0.012584566375714179,0.012194526785575277,0.011831305833632188,0.011493252650352013,0.011177806521915187,0.01088232402190099,0.010604051564797658,0.010340158502124257,0.01008805672947715,0.009845599613675489,0.00961103101923758,0.009383362430327143,0.009162519777236785,0.008949003222832872,0.008743816714738615,0.008548480914579557,0.00836462559062764,0.00819376989215437,0.008037057277175153,0.007895120284443901,0.0077679974589752905,0.007655029121165315,0.007554964477202632,0.007466276674086611,0.007387174091287915,0.007315912293466078,0.007250964365721008,0.007191219872361146,0.007136018793832523,0.007085197858052249,0.007039041107907516,0.006998066064899826,0.006962870310013163,0.006934024395377608,0.006911941359992254,0.0068967160110451325,0.006888219502546889,0.006886173493123928,0.0068902647072102705,0.006900354779288521,0.006916612618022541,0.006939616210873564,0.0069703962235236755,0.007010301148922647,0.007060898091961857,0.007123704549631578,0.007199868127289454,0.0072898547397715775,0.007393219753503805,0.007508550332916575,0.007633416749537824,0.007764491106539076,0.007897995585524646,0.008030232143763544,0.008157947149831897,0.008278748357540009,0.008391622075424295,0.008497262614797984,0.008597884331547387,0.008697041560917829,0.00879920243835126,0.008908737718838912,0.009029323007950008,0.009162967513950614,0.009309181300138641,0.00946497381145408,0.009624841069780437,0.009780851274856516,0.00992307171261352,0.01004076687457327,0.01012384443873139,0.01016406793227024,0.010156136907161856,0.010098839979205793,0.009995559698374524,0.009854325107678544,0.00968757081451726,0.009510801563530583,0.009340891206527953,0.009194373469541007,0.009086348716255207,0.009029377509051958,0.009030769268185709,0.009090623448749072,0.009202146267235194,0.009351929082030223,0.009521091814620148,0.009689337794969654,0.009839789442675008,0.009963674835569756,0.010053865010580493,0.01008946412615836 -3.935714285714286e-07,4.811459471277012,3.4082739774253,2.3092370524736796,1.876954457623404,1.6760885903670666,1.561600303418997,1.4744862564808259,1.3976619555935932,1.32441326510225,1.2525039753758715,1.181543573868372,1.1124076486587071,1.0453926762589507,0.9809514459128813,0.919429298848146,0.8609602840181392,0.8056338258400915,0.7535449878330724,0.7045902434981902,0.6586351343225278,0.61551849370374,0.5752140902213885,0.5375312538526729,0.502369250918869,0.46957251556761087,0.4389999270565486,0.4104740454958656,0.38385104273358006,0.358981619569329,0.33573686289878624,0.3139737126307512,0.29355076907820077,0.2743697942285186,0.2563507403223169,0.23941246930637303,0.22347452449340133,0.20849013580190243,0.1944498368728872,0.18128166451619013,0.16895775096063673,0.1574578412621742,0.1467761320798586,0.13687140784864005,0.12772490726499533,0.11931097955929013,0.11158536045091723,0.10450608695020215,0.09802847276711743,0.09210199128270302,0.08666147826075986,0.08166640773722408,0.07706420136591823,0.07279974055251068,0.06883440828601257,0.06513162180103353,0.06165920140702565,0.058398618032835935,0.05532895982067596,0.05243687216243131,0.04970614159320334,0.04712504917074644,0.04468726521712052,0.04238423652796559,0.040214058741248,0.03816825958623185,0.036243424388324674,0.03443388196640877,0.03274101606398259,0.031158606529792993,0.029675809889645202,0.028287828624739966,0.026987738329868748,0.025766417987508252,0.024616846299113107,0.023530884222103636,0.02250483343357085,0.021534991567227463,0.020620342264112496,0.019758197280907355,0.018948363402981835,0.018189527185847744,0.017479842752844553,0.016815029728327435,0.01619061777808587,0.015601482635364627,0.015043250361810876,0.014511158180518222,0.014002338884259247,0.013515353205961051,0.013050735218982047,0.012609332314654581,0.012191743550996548,0.011798787615073664,0.011430608112859605,0.011086723246210295,0.010765956435062207,0.010466343763116275,0.010185813526250186,0.009922924724106937,0.009676557735742941,0.009445971996631369,0.009230749792823447,0.009030430888081325,0.008844545057425057,0.00867208548514008,0.008511556721594734,0.008361174305009838,0.008219067796741874,0.008083707864412626,0.007954257316342982,0.00783072348926419,0.007713896224904805,0.007605100825197193,0.007505848425509134,0.007417336554357396,0.007340176199472279,0.007274129496899303,0.007218246914258279,0.007171200587928593,0.007131648870945373,0.007098676207611881,0.007072214425868245,0.00705310894933118,0.007042929431564484,0.007043641384767343,0.007056858733423098,0.007083378352476901,0.007122939599843228,0.007173982790685241,0.007233649892318115,0.007298328223245587,0.0073642643721527085,0.007428183846325174,0.007487686099284449,0.007541455966568555,0.007589394406735274,0.007632425661290679,0.00767218311579526,0.0077107512981656485,0.007750511407987615,0.007794008570279393,0.007843992900425187,0.007903501441754692,0.00797592848968285,0.008064906930024375,0.008173820171899564,0.008305134299517162,0.008459556877938505,0.008635465746024005,0.008828576652580281,0.009031891828735962,0.009236199830084611,0.009431136319064051,0.009606800492814383,0.009755251001102902,0.009871284208884282,0.009953035938933307,0.010002126998962031,0.010022788486657077,0.010020499374876828,0.010000802815413238,0.009968065291624986,0.009924702788563517,0.009870884089456813,0.009805442735734074,0.00972766034919342,0.009638935701628838,0.009543714154192353,0.00944978018472024,0.009368486986998381,0.009313954845615873,0.0093001590594375,0.00933732046099483,0.009428145801456678,0.009565772888028653,0.009735137733475051,0.009916183268435928,0.010088549649890146,0.010237238581020058,0.010346940121946582,0.010389799798956396 -4.271428571428571e-07,5.348732969354341,3.721510192154086,2.588661521755491,2.070962655139778,1.7983398644304032,1.6152266496542094,1.4729529967914265,1.355423166515425,1.25641629418541,1.172134672331176,1.0993724238143643,1.03446865947592,0.9744350541075333,0.9177431380735126,0.863569030041638,0.8121701123925452,0.7636639734044366,0.71834014888754,0.6761000804687746,0.6364438836112251,0.5987133022146675,0.5624689464557754,0.5274750897681748,0.4938259596708495,0.4618667909759109,0.4319522911958862,0.40433426334032346,0.37897741247100997,0.3556328485634891,0.3339001874910542,0.3133748050748783,0.29379612866208366,0.27506161806285284,0.2572729857333732,0.24056649164216645,0.2250993414617275,0.2109283512357895,0.1979483667023409,0.18598133882995033,0.17481431449345744,0.1642331243420582,0.1541204092215479,0.14446192225200416,0.13531740942772325,0.12677103657378086,0.11891632171318475,0.11177206049393848,0.10527166619071743,0.09929150018368874,0.093699778064478,0.08838445416171255,0.08327756227830044,0.07836267513099363,0.0736641359188148,0.06923357637825675,0.06512181251751119,0.06134822214712077,0.057900018386583964,0.05473921738021313,0.05181629604822642,0.04909952392527176,0.04656560841856498,0.0442048093877985,0.04201407678736816,0.03998872037773817,0.03812013529689385,0.03638067876071573,0.03473708871813843,0.03315815603825506,0.03162093534197989,0.030115688106289635,0.028650137344888454,0.027240754806811356,0.025910402487165626,0.024675330845201054,0.02354279677872984,0.022509589331068666,0.02155704145927727,0.020663435966443672,0.01980834467634878,0.01897817307065536,0.01817124578423116,0.01739677475837202,0.016668308413323382,0.015999914715550873,0.015399338544759446,0.014863313906082695,0.014381120984430213,0.013937251614331207,0.013516199703269968,0.013108969021075266,0.01271476413260801,0.01233877136785418,0.01198784060151617,0.01166584418493701,0.011371288460729231,0.011097281267122129,0.010833901780484574,0.010572326800978738,0.010308055569474695,0.010042170715092716,0.009779990194651362,0.009528025500930525,0.009291182430816522,0.009071278881547082,0.008867299356967197,0.008676570752144185,0.008496527206736614,0.008325976089927961,0.008165283184032876,0.008015807314597187,0.007879078816295248,0.007756124216546033,0.007647383497080595,0.007553003873983586,0.0074732582084822705,0.007408486729357858,0.007358778700304939,0.007323192456352002,0.0072994318884788524,0.007283958011265553,0.00727266521513121,0.007261780910351716,0.007248711757316406,0.007232694936500695,0.007215022663170493,0.007198432112431735,0.007186267564589525,0.007181573797022766,0.00718621022093287,0.007200441081318297,0.007222980557904743,0.0072515118291255,0.007283599414530044,0.007317642955869393,0.007353563221042811,0.0073930836868840305,0.007439535835134489,0.007496917570182209,0.007568686729085078,0.007656668678006072,0.007760226091434967,0.007876108331286352,0.007999289122595959,0.008124449088593211,0.008247795331322784,0.008368686011656084,0.008490292803031336,0.008618395077733033,0.008758885776308217,0.008915077800424318,0.009085538094952687,0.00926346308484362,0.009437931853265911,0.009597240298539795,0.009733079200664243,0.00984330818938099,0.009931477509806795,0.010003661796529872,0.010064389526365349,0.010113065415793375,0.010143067256405212,0.010145107163947537,0.010112619658110434,0.010046531902228685,0.00995723074048596,0.009861464295744882,0.009776030291461152,0.009712616869858727,0.009675230361371401,0.00966148292103665,0.009666553752806934,0.009688237570458629,0.00973021504169831,0.009800095598115796,0.009903486693801776,0.010038222339953145,0.010192534300181208,0.010346707028512663,0.010470604426838915,0.010520522495768691 -4.607142857142857e-07,4.52446568766762,3.3553905569194837,2.2890625338015553,1.8253882434221127,1.597256653543558,1.4621084445252626,1.3658800670443296,1.2848275744598672,1.212022495423335,1.1446631139492582,1.0808418642578017,1.0207356829116405,0.96405830998592,0.9105935283672419,0.8599748952711297,0.8118121249650921,0.7657968371626982,0.7216439027735128,0.6791880818269492,0.6383823578171791,0.5992426852067475,0.5618170673345705,0.5262226852389146,0.49255500004815056,0.4608475565286771,0.43115534505785197,0.4034773837653568,0.37776728487393657,0.35390797982113914,0.33177514431938887,0.3112860090789177,0.2922897149104325,0.2746401970518127,0.25817190594362593,0.24278402199383226,0.22835184889668592,0.2147593736654916,0.2019669024122517,0.18993844643878685,0.17860134184820395,0.1679272766458316,0.15791705436027126,0.14854255687457882,0.13979546734951137,0.13161775532035186,0.12397442352384358,0.1168247306491181,0.11010514082503949,0.10377169400989124,0.09779349227957189,0.09214124485415047,0.08679846686607963,0.08175083655669889,0.07700363578800512,0.07255307081029722,0.06839849690486806,0.06453020942180869,0.06092451370225937,0.057554949931904624,0.054389501493819686,0.05140633213115809,0.04857964806017708,0.045897539617959074,0.043352620629075224,0.04095356977293636,0.03871055876569714,0.03663325916028803,0.03473161917942506,0.03300398580541363,0.03143910387709742,0.03001958860436266,0.02872071564730991,0.027516364481096168,0.02638422811836498,0.025301466259938947,0.024254217097970213,0.023239254569526586,0.022261256408910773,0.02132554894564634,0.020436427485953763,0.019599777886631094,0.01881655263328367,0.01808470498995787,0.01739977232753242,0.01675535371503244,0.01614618856104347,0.015569065913193559,0.015021364586399043,0.014503436179203674,0.014016124645377446,0.013560413996652981,0.013135559700310941,0.012738719518062965,0.012365789623371561,0.012012200787045688,0.011674386536163787,0.011349667729385636,0.011037137781915417,0.01073768932572112,0.010453290852926869,0.010186157777168312,0.009937763097847933,0.009708234242979504,0.009496676271712873,0.00930121595114636,0.009119829681729802,0.008951191228182463,0.008794841933004624,0.00865104941451208,0.00852049345586051,0.008403689099047965,0.008300149468225682,0.00820801197475426,0.008124283668118288,0.008045369517134192,0.00796773999304782,0.007888939689162076,0.00780800628812001,0.007725618660786811,0.007643738617133774,0.0075649485509678305,0.00749168238241785,0.007425767476911051,0.007368082317873242,0.007318638189481579,0.007277276840482478,0.007244096124908949,0.007219778438739613,0.007205780573018871,0.007203869435874544,0.007215540848075612,0.0072414077076187865,0.0072808399938994765,0.007332182782449151,0.00739325074590891,0.007462180127432028,0.007538139514011128,0.007621635035750045,0.007714442210704702,0.00781895117467939,0.007937023566479077,0.008068728583350844,0.008211465333793123,0.008359945674816138,0.008507256462555561,0.008646431087751514,0.008772334739401356,0.008883373511165272,0.00898199866388436,0.009074117047136065,0.009167387413398651,0.009268997203770056,0.009383306759465306,0.009510146964907722,0.00964417165738478,0.009775602922754222,0.009892595120283431,0.009984463030089662,0.010044354774566156,0.010070815377437271,0.010068123428751572,0.010044972616275226,0.010011879230169633,0.009978099521496118,0.00994945573294508,0.00992784810360299,0.009911561863450094,0.009896988055327212,0.0098815381867139,0.009866544281282306,0.009859222567774868,0.009871521007499674,0.009915389904742769,0.009997770350013563,0.010118808453781644,0.010270728413129067,0.010438650121041124,0.010606303825734053,0.010759155573268194,0.010876527505026563,0.010923097272505728 -4.942857142857143e-07,4.577123470665704,3.28472495981411,2.2457128597557325,1.7406511113375376,1.4928099755329178,1.35225710034934,1.2579158101137002,1.1842210610100725,1.120118752949587,1.0606962260589257,1.004333788594359,0.9504564382566916,0.8990672644573121,0.8501982637248189,0.803831721379995,0.7599842790226107,0.7185928409591462,0.6795536906973323,0.6426846883836393,0.6079039488745861,0.5750615180228078,0.5439825396569375,0.514466506630743,0.48639311682978,0.45967334703022145,0.43418907978349297,0.4098918913072288,0.3867053499877535,0.3645305828101234,0.34333623342195696,0.32309853540379546,0.30378237424048904,0.28540513184845046,0.26800506448058103,0.2515981493652034,0.2361710141239858,0.22170041428695333,0.20818199881061186,0.19559972093692463,0.18388510456175325,0.1729857785916976,0.1628224912731689,0.1533155842850254,0.14441134854675503,0.13606164537239812,0.1281950633846687,0.12076359186938346,0.11375447623000967,0.10716700497232659,0.10096821653817384,0.09516441039945008,0.08974514588788775,0.08469731394651624,0.08000323545154799,0.07563600653016009,0.07158492621659371,0.06782215898855094,0.06431610398078115,0.06103453284913078,0.05795157070900068,0.05503977241434739,0.05228773596469162,0.04967211878704555,0.04718822722195869,0.0448302610583118,0.04259351451637728,0.04047072368968062,0.03846685188337686,0.03658295747758566,0.034817511320962675,0.03316742358868921,0.031627231567689124,0.030188130950836167,0.028841264734997405,0.02757707913303173,0.026383940806718173,0.02525441853854311,0.024183842090721074,0.02316840323042347,0.022205137745270796,0.021293958841783863,0.020435438087747018,0.019630444452477632,0.018881339262614875,0.018185082431427222,0.017537482757737425,0.016932605475750467,0.016364110781406597,0.015824823302557047,0.015309676072048292,0.014813554718310105,0.014333086094674383,0.01386867399844119,0.01342232235430426,0.012997031677632916,0.012596634138264161,0.012223841790530966,0.01187943334647923,0.011562635271084828,0.011271102449693584,0.011001160935293682,0.010748500002230113,0.010509227042153567,0.010280435619896956,0.010060577005110025,0.009849875434349837,0.009649654184098107,0.009461934568052728,0.009288545079123791,0.009130290005650488,0.008986564062350745,0.008855199148174816,0.008733049156139969,0.008616569551399635,0.008502552968021375,0.008389022645740147,0.008275898023487738,0.008165141831482706,0.008060300656918797,0.007965752693929398,0.007885849406947323,0.007824065279986775,0.007782201609380792,0.007759887299685413,0.007754709059503361,0.00776270734874098,0.007779275032713979,0.007800264638645331,0.00782292351262407,0.007846243184117785,0.007870949206015434,0.007899080350545661,0.007933125573712606,0.007975073448170751,0.008025473903666504,0.008082920912372057,0.008144014538980462,0.008203827028178783,0.008256988697726895,0.0082990122479467,0.008327690577293734,0.008344104426245091,0.008352792293794358,0.008361151405395771,0.008378323915014576,0.008413870113348588,0.008475954793889039,0.008569483599729387,0.00869509497188164,0.00884912222418686,0.009024390748722572,0.009211465760661446,0.009400219045109563,0.009581480629680835,0.009748397005252745,0.009897388298288718,0.010028366996308033,0.010143795646434159,0.010247214543614512,0.010341878835555382,0.01042919990316281,0.01050792178476902,0.01057424384668674,0.010622615254265756,0.010647477376171282,0.01064524252306902,0.010616102963920886,0.010564797960021265,0.010500648190877301,0.010437776716629717,0.010394133300151053,0.010387441850923726,0.010429555933326529,0.010522907783377694,0.010659989791454223,0.010825435191066305,0.01100136232550085,0.011172881937857976,0.011328401939063088,0.011448818874733108,0.01149694005689613 -5.278571428571428e-07,3.6894818016267537,3.013748303868259,2.1404857024251855,1.655208793348747,1.4147957087866814,1.2826585382908935,1.1969268896336396,1.1333427376467284,1.079519947404281,1.029745974822731,0.9820698632920305,0.936080389102378,0.8908383119684078,0.8468622484790775,0.8040253084282611,0.7625466415144931,0.7225572713884977,0.684106921158951,0.64727022306457,0.6120530684722308,0.5784679081793144,0.5464474757160506,0.5160011472720442,0.4870871124408477,0.45958155943213963,0.4334869202758127,0.4087834246718466,0.385364176700384,0.3631943963605904,0.34225707666477256,0.32246247864870786,0.30373187443478855,0.2860494008971586,0.26936703429527487,0.25361618102335337,0.23871935977341,0.224660211591329,0.21139095075190925,0.198823042199627,0.1869391269302332,0.17572618247048236,0.1651559998830616,0.1551982428140923,0.14585838335631393,0.13709846191939537,0.12890102631119846,0.12124946197518821,0.11412709859396573,0.10749727882794585,0.10132380753140999,0.09559127370947004,0.09025493678583814,0.08527732406071048,0.08063279382370567,0.07629029512301785,0.07222661097372127,0.06841425094562889,0.06482606830582487,0.06145109498450509,0.058281623541820725,0.05531356188053951,0.052529602610670356,0.04992224322233561,0.04748312624324495,0.04519985422868221,0.04306299435573019,0.04106578704750751,0.03919066531601593,0.03742037907764366,0.0357468767362149,0.034155235118134286,0.03263694995956388,0.03118721775255088,0.02979973583644223,0.02847263932699943,0.02720673036090804,0.02600369785193286,0.024866531994490404,0.023796436635664314,0.02279482608539107,0.021862239949741534,0.02099712202779455,0.02019387264610115,0.019446628963034222,0.018747891883681807,0.018089652238254893,0.017463544367227475,0.016864722117636448,0.016287769940213276,0.01572951702554934,0.015189910405462302,0.014669873525563435,0.014171783066588473,0.013698378824487654,0.013252886247056323,0.012837944975034224,0.012454985996845842,0.012104146467236966,0.011783667445336562,0.011490015851503383,0.011218984097340704,0.010966105380205033,0.010726732859959004,0.01049683734964472,0.010273496443266847,0.01005523359984479,0.009842170814943854,0.009635768374479218,0.009438330250027388,0.009252479241373932,0.009080839563545195,0.008925649601180126,0.008788301215907212,0.008669091447184385,0.008567368865741472,0.008481591491957883,0.008409520309653484,0.008348647166382202,0.008296630873662928,0.008251550493471153,0.008212234503622712,0.008178095688303567,0.00814913043854016,0.008125776801383593,0.008108536095042468,0.008097739468102848,0.008093417762998175,0.008095136087772412,0.008101821927576582,0.00811201124461462,0.008124164831186147,0.00813690565525046,0.008149217016428494,0.008160706898888408,0.008171848314357308,0.008184037797628501,0.008199497129410663,0.008220919332965348,0.008251075986021116,0.00829245918916493,0.008346893874240775,0.008415071729114012,0.008496416579221365,0.008589314172000092,0.008691339582546595,0.008799341377698878,0.008909976464971773,0.009020554598833056,0.009129508354976026,0.009236328970557255,0.009341539789755477,0.009446424322633049,0.009552250502997293,0.009660134216115897,0.009771082929422433,0.009885143572672105,0.010000839595397779,0.01011561365583051,0.010225847363536367,0.010326866565043736,0.010413878309917546,0.010482652832305324,0.010530290129729876,0.01055624364691885,0.010562440373028658,0.010552895007603164,0.010533082614438855,0.010509797294968811,0.010490900500361416,0.010484923563744612,0.010500265805982388,0.010542492381495669,0.01061326146031986,0.010709103570557358,0.01082056633436397,0.010935625249243441,0.011045211403586025,0.011146426017498889,0.011240588823332313,0.011319507549842827,0.011352918558417932 -5.614285714285714e-07,4.210719961989957,3.131084231415544,2.1434991389284015,1.6314148981533914,1.3818115222418221,1.241597538695698,1.15331806664629,1.0897602241677615,1.037602969835703,0.9913500904836475,0.947679637508309,0.9051176965134801,0.8636233911550902,0.8229596951046582,0.7830347841050604,0.7438218581484778,0.7056115628302877,0.66878932624661,0.6334049164331715,0.599443388488212,0.5669364056169056,0.535963453355018,0.5065352346686123,0.47857166699095594,0.45206061724213253,0.4268987632219705,0.4030433236008127,0.3804225239007934,0.3589699619611222,0.3386492290265813,0.319416734238536,0.301178193756278,0.2838856022868508,0.2675316173853833,0.2520915231373435,0.23754627674985226,0.22383032687924262,0.21094020376832526,0.19885059270040084,0.1874942328179101,0.17684295651054227,0.16687574110859876,0.1575193714623196,0.14872719930306977,0.14048802890265874,0.13276903299069778,0.12549053942226274,0.11862772040474892,0.11215980006837249,0.10605738917216574,0.10029785224958179,0.09484513888270807,0.08969259535912931,0.08483372944050474,0.08025051628360616,0.07593258451270041,0.07187475395299571,0.06807301112704094,0.06450788463810755,0.06116127428825743,0.05803126219117217,0.055106471563760986,0.05237283945404173,0.04981436533415381,0.047409939983881315,0.04515010515975749,0.043025173122560836,0.04101918962645035,0.03911957821005655,0.03732011504614527,0.035609576847767895,0.033984665955176754,0.03244206193666647,0.030976231196906713,0.02958575881278068,0.028266198620667846,0.027016189005592738,0.025839196097406973,0.024732271602250545,0.023691363684564353,0.022715651052736204,0.021801162241605263,0.020944463553141595,0.020143651331812944,0.019393742704431158,0.01868992760109237,0.018028063046818265,0.017405101726033406,0.016817062567132664,0.016261184725248307,0.015734782487295026,0.015235413406084851,0.014762069785088663,0.014312946339528357,0.013886635030971425,0.01348239990090437,0.013099634642910007,0.012737231577160304,0.01239440178629809,0.012070059099411548,0.011763558244010035,0.011474154257318297,0.011200792440298297,0.010942587360271277,0.01069880137213163,0.010468896017982484,0.010252559914337725,0.01004937996684235,0.009859116350549467,0.00968163591660307,0.009516594810614816,0.009363820362684905,0.009223262164814745,0.009094601522677725,0.00897725346477129,0.008870452661643454,0.008773533153428392,0.00868588582558081,0.008606756914341599,0.008535557604852785,0.008471726121564743,0.008414863173291114,0.008364852821660426,0.008321735205452555,0.008285654829913508,0.008256994250944156,0.008236059236891923,0.008223074409574089,0.008218269289213508,0.008221666445752661,0.008233001682108663,0.008251867100687782,0.00827764528705825,0.008309445826127486,0.00834625562930374,0.008387146957085282,0.008431243462250756,0.008477933655148586,0.008527087542160725,0.008578946126246554,0.008634084271921803,0.008693470208239887,0.008758389497276151,0.008830394220985227,0.008911032195461844,0.009001676317165438,0.009103451400877613,0.009217093270845345,0.009342488051692894,0.009478316478229108,0.009622292012791427,0.009771406001125188,0.009921843286938828,0.010069419526727321,0.010209931521175889,0.010338994036768015,0.010452411875112479,0.010547166949746707,0.010621736912810226,0.010676068334996569,0.010711282009851641,0.010729862654233749,0.01073588945867275,0.010734450500202376,0.010730806400898827,0.010730081707761034,0.010737107657507154,0.01075551245434576,0.010787245684445718,0.010833309901382282,0.010895315142392416,0.010974511025345681,0.011068559092066681,0.011171048125564527,0.011273613723239507,0.011368326694716617,0.011451477011340352,0.011526591404108727,0.011600126928726816,0.011666436281934877,0.011695753387074268 -5.95e-07,3.6769221962914727,2.971280237010411,2.0578034259286917,1.5691142379425742,1.306497820396156,1.1665249069453805,1.079854914919026,1.0184372654918192,0.9700571574718503,0.9281740962000923,0.8898651707483463,0.853772791739811,0.8186012859360329,0.7842908622681165,0.7507035767923852,0.7177178588790516,0.6855708320064197,0.6542281788468488,0.6236951533772724,0.5939021942284595,0.5649042176850158,0.5367633777093561,0.5095026646079669,0.48310248096531444,0.4575465138272071,0.4329050818770025,0.40923310429508136,0.3865296882820243,0.3648185599526287,0.3441121386823697,0.3244467305713006,0.3058190633112382,0.28826186137749027,0.2717268238135893,0.2561842922012653,0.24163180872046583,0.22800225538788396,0.2152140433147272,0.20319880619508884,0.19191476732598667,0.18130201610569274,0.17130374048708782,0.16184470812895935,0.1529149402184658,0.14445854375145023,0.13643959608296372,0.12885108308530646,0.1216877045455264,0.11492870482035826,0.10853907189202533,0.10251729608132115,0.09687134163482453,0.09157928366456936,0.0866199569490403,0.081974648673559,0.07762296904662094,0.07354120712629969,0.06971695532039317,0.06611477769847926,0.06272886474981648,0.05954583106585257,0.05654220152493181,0.05370265595710414,0.051023990301537835,0.04850885152655845,0.04614610796532236,0.04392136132576727,0.04183600595428047,0.0398873130453363,0.03806769238054601,0.036366038602367,0.034773952102353047,0.03328218437602578,0.03188012820497439,0.030557149107583205,0.0293014348117153,0.028104972795280694,0.02696149785894759,0.02586507365551754,0.02481209286052521,0.023802444555941417,0.022835425360104314,0.021911730211054095,0.021033497946327914,0.020203147560053623,0.019422996310400596,0.018692808725112014,0.018012725433755945,0.01738008807277577,0.01679168541201725,0.016243271611585723,0.01573041099006484,0.015247462620744686,0.014789347703456772,0.014352589147550415,0.013933433841893862,0.013529429994893469,0.013140121593900099,0.01276566075107268,0.012406913792469769,0.012065518490161383,0.011743798496890126,0.01144352301716983,0.011165797731509474,0.010911153436865344,0.01067955383171737,0.010470079827529817,0.01028103410552371,0.010110200639615681,0.009955030449098743,0.009812866094308986,0.00968130139331277,0.00955832137164503,0.009442451392399298,0.009332911961524443,0.009229426644134847,0.009131990242149295,0.009040823376115151,0.008956289840623216,0.008878591556714477,0.008807738317542384,0.00874357380089913,0.00868567880906609,0.008633625421825976,0.008586788617834462,0.008544671331978162,0.008507149048980301,0.008474434927925819,0.008447358502548137,0.008427271270501977,0.008415888824030103,0.008415180558737849,0.008427127337548962,0.008453357699251908,0.008494902195013263,0.00855190296998655,0.008623525043196096,0.008707801912484344,0.008801626006418184,0.008901137546982262,0.009002209510706434,0.009100806590469156,0.009193272346906702,0.009276809926034068,0.009349826881824527,0.00941183428510021,0.009463670228330999,0.009507617841591013,0.009547238374437368,0.00958691122932192,0.009630825816967946,0.009682433398336781,0.009744322237146644,0.009818003990533106,0.00990363880268247,0.010000179596957984,0.010105387981934107,0.010215845083674601,0.01032733002133694,0.010435257143227171,0.010535523157371813,0.010625352896760037,0.010703397475385033,0.010769277404900003,0.0108236921900566,0.010868882205774368,0.01090809127599001,0.010944765935792355,0.010981905881470156,0.011022914134132424,0.011072124201730885,0.011132889628931138,0.01120559334141339,0.011286738847638679,0.01137024258590708,0.011450401761728714,0.011524193552563676,0.011594189775404449,0.011666901578767054,0.011734759751620158,0.011764936281212664 -6.285714285714286e-07,3.6326472905907377,2.9870438203054257,2.1517771578854314,1.6668304029899765,1.3976893507371444,1.2342899738989537,1.124663923161691,1.0458075395828967,0.982303774242126,0.9285546348580109,0.8807698223083114,0.83711984553005,0.7966578164625414,0.7586526207286833,0.7229167952310801,0.6891489292511959,0.6571253206127761,0.6266115619460565,0.5972898993933725,0.5690059558302641,0.5417375967676363,0.515458357790903,0.490032478979337,0.46557826663766055,0.44211264199873884,0.4196323518765715,0.3981322782883338,0.3776113963340414,0.3579835541072144,0.3391852085815002,0.32114617869464823,0.30380644610443985,0.28716071945176314,0.2712075956131228,0.2559357081136434,0.24136080873515564,0.2275289255015492,0.21449882273022078,0.2022806053991553,0.1908733094896061,0.18027260752818575,0.1704309827531324,0.16126770127057527,0.1527409087155691,0.14477214971044677,0.1372467021798198,0.1301282442919529,0.12335714560413727,0.11686992384093398,0.11068151101907661,0.10480617496180732,0.09923105987048535,0.09397756898208305,0.0890544667317984,0.08445868013241246,0.0801844609707419,0.07620269349562586,0.07249271338240419,0.06901719529436989,0.06573393746439735,0.06260448306994762,0.059614089589758106,0.05674167651505176,0.05398093808079652,0.05133405955428241,0.04880528918785384,0.046394280860594335,0.044108773746537426,0.04195972592062532,0.03994597092281639,0.03806050423010609,0.03629600141865272,0.03464140180834834,0.033083546415186994,0.031616967440063794,0.030234805944633,0.028928873110101273,0.02769463941897402,0.026533059041733188,0.025442763010739222,0.0244203199264771,0.023462696543559196,0.02256535484214002,0.021723190197912846,0.02092955940796257,0.020176421198176368,0.01945916907277347,0.018773813499675614,0.018118201371526393,0.01749282727793678,0.016899977977667333,0.016341233239978385,0.01581876381507881,0.015333065531841222,0.014882881229957198,0.014464745962930457,0.01407385812144736,0.013704780813725557,0.013352930839735775,0.013014305262993252,0.012686715784866773,0.01237040350537333,0.012067427847797685,0.011780888760350751,0.011513895557719462,0.011268581408861243,0.011045477828751094,0.010843066316706246,0.010657831250486569,0.01048521532476134,0.010320380665031761,0.010159047356088802,0.009998624937387606,0.009838880844171715,0.009682056343549511,0.00953240012002225,0.009395160552219183,0.00927548090507047,0.009177164761727894,0.009101727433004072,0.009048012179179025,0.009012214978003415,0.008988605588709836,0.008970971040286853,0.008953934569444296,0.00893404021749317,0.00891051929839323,0.008885697281370364,0.00886459605940945,0.008853442624883101,0.008858307162020884,0.008883591683822606,0.008930749026160995,0.008997696490783717,0.00907886679826914,0.009166060354852863,0.009249955033711057,0.009322056269925185,0.009376625813197722,0.009412014216095417,0.009431185893441043,0.009441179476791746,0.009451604019728777,0.009472689662123732,0.009513255183558866,0.00957886724256029,0.009670237392415878,0.009782386731789543,0.009905569776541149,0.01002703203639972,0.010133424931932157,0.010214015597916641,0.010262981718820112,0.01028090955544751,0.0102753600415978,0.010259684735684556,0.010250884194553386,0.01026671669355837,0.010322042752312055,0.01042589498047012,0.010579812974159305,0.010777315245793827,0.011005160944630431,0.011245030792376395,0.011475490044305469,0.011675730022203368,0.011829701725434716,0.01192895851011293,0.011974338210324013,0.011975883286553067,0.011950326476088078,0.011916860673902034,0.011892081822560232,0.011886801906040127,0.011905338056524571,0.011946537035188736,0.012008522835852645,0.012090229965983657,0.012186226969379578,0.01227473416374034,0.01231288656274872 -6.621428571428571e-07,5.015308336418541,2.6463554517189998,2.0064350257420847,1.5761736184902124,1.3175832448780191,1.1676012284830348,1.0739381534799401,1.0094891409356932,0.9606548173860663,0.9192927490451906,0.8820253887194225,0.8470190854471318,0.8132604051820356,0.7801594628367845,0.7472058741199463,0.7143788561885378,0.6818744673271109,0.6498471242940075,0.618312015288281,0.5874325187163406,0.5573113337507462,0.5280848960502962,0.49984405513949687,0.47266474835223254,0.4466214051279021,0.42176045051336264,0.398108414191041,0.37566581801971916,0.35441048236516126,0.3342944609046717,0.3153105116714725,0.29741945374307094,0.28059261235020205,0.264748360524334,0.2498518804330642,0.2358516254473414,0.2227033792620325,0.2103551976000407,0.19877615778321864,0.18791671065513138,0.17769845692863065,0.16807747266792455,0.1590634647345449,0.15061112402793744,0.14266894360799154,0.13519523335184974,0.12817167925680117,0.12157820698640465,0.11537832155799546,0.10953924865032041,0.10404794962743782,0.09887756551450008,0.0939999787128151,0.08938955681886483,0.08503499942704201,0.08091409029262146,0.0770102233914293,0.07330472171977735,0.06978274908495515,0.06643512005534744,0.06323953488621255,0.06019536682063427,0.05728952257507208,0.05451997685632082,0.051877039991338005,0.04935494844759485,0.046954523046163106,0.04467139530211939,0.042503224300539565,0.040445378630323096,0.03849000730359584,0.03663827273262355,0.03489122318706424,0.03324560525894772,0.03169829018511958,0.030242328435855916,0.028875714740372184,0.027597345908495014,0.026401051862378826,0.02528292318812209,0.024239027903160976,0.023264725216812245,0.02235491200788497,0.02150713645675489,0.0207156322534228,0.01997569299303094,0.01928436415893126,0.01863742123725849,0.01803173559158135,0.017464270823175213,0.01693188227412647,0.016431643240556407,0.01596136878996372,0.015518126837071052,0.015099989325657145,0.01470482138012189,0.014330251118126885,0.013974418824043955,0.013635504031111119,0.01331190991463428,0.013002412667532763,0.01270594794576288,0.012421566871419586,0.012148294591120245,0.011885393701399138,0.011632462338233984,0.011389207532474105,0.01115559589205783,0.010931848135995343,0.010718128658137403,0.01051470996419232,0.010322153798000912,0.010141179480268088,0.009972497327900112,0.009816562127670674,0.00967375229445458,0.009544296733921192,0.009428461656438873,0.009326414126829424,0.009238044449822472,0.009163052234934481,0.009101160554036614,0.009051886584889209,0.009014632153524006,0.008988664034939005,0.008973164311434918,0.00896731128361748,0.008970291185512574,0.008981399811754219,0.009000049592387652,0.00902574761395237,0.009058060484617981,0.009096667658926636,0.009141368143896073,0.009191954316385071,0.009248159087066317,0.009309641275564884,0.009375897990078283,0.00944638501110166,0.009520647345323355,0.00959827555068276,0.009678700654317722,0.009761105450520694,0.009844494734654076,0.009927727734833098,0.010009648663069075,0.010089039853404766,0.010164638876638474,0.010235383974610313,0.010300450747030619,0.010359342408534337,0.01041195453562428,0.01045828566370079,0.010498244913682321,0.010532186384529593,0.010561424299332217,0.01058779188276615,0.010613101674973974,0.010638997331035058,0.010667160197217161,0.010699888815159212,0.010739985627635618,0.01078986340401535,0.010850943247960744,0.010923451922821117,0.011006328930567435,0.011097988392807586,0.011196516140693646,0.01129887609747948,0.011401669833666868,0.011503204781525268,0.011603370901539388,0.01170140592405904,0.011794341443090156,0.01187755795777907,0.011948306486810967,0.012009603831664176,0.012070993141480855,0.012141145803853108,0.012209214990554865,0.012239452184171138 -6.957142857142857e-07,3.12390523893367,2.6982556561289286,2.035856808210359,1.6200354047843977,1.3619298215762394,1.2040269763369806,1.100146089964946,1.0274638306583768,0.9695297957542202,0.9197766240699369,0.8751781120502208,0.8334548732097583,0.7932336066531279,0.7541552961710717,0.7163558674844808,0.6797692054627448,0.6443727767756313,0.6102892784741226,0.5777336014804147,0.54681718089723,0.5175848697293934,0.49011837257020974,0.4643850577625476,0.44031178706388496,0.41782753218447327,0.3968410287643434,0.37721431456686927,0.35883586240756443,0.34159707170109743,0.3253547929315749,0.3100190431102638,0.2954502818195195,0.2815386945787936,0.2682103694457569,0.2554144964804949,0.24307248524689534,0.23113082188833725,0.2195869949850371,0.20842695155745258,0.19764148436429493,0.18727256822029173,0.17731764756073684,0.16778821241089042,0.15866952101211443,0.14996424828076757,0.1416902175997944,0.13387702029304,0.12655118252185454,0.11968219149467728,0.11323571252056548,0.10720337735784277,0.10156621853583485,0.09632009075795589,0.09143667618102262,0.08686025080387468,0.08257018340256686,0.07856686293668201,0.07481991543518592,0.0712878595378974,0.06795266823043909,0.06478105123908762,0.06177096014254198,0.05891166051549708,0.056180053571062545,0.053573519400506225,0.05109002081481816,0.048721152517493364,0.04646113961436294,0.0443031565678553,0.04224803095115115,0.04029412553619188,0.03844553839861758,0.036696821611753846,0.03504244226385785,0.03347963939437521,0.03200627244101564,0.0306220801468322,0.029322260565727568,0.028098727028151877,0.026947164222178625,0.025864063912646815,0.024847296517171633,0.023890310565039985,0.022987776251247663,0.022135866792806363,0.02133185986685679,0.020573411653657638,0.019858147599144515,0.01918406588061325,0.018548672721726014,0.01794874827838235,0.017383337416145037,0.016852100563234405,0.016353199807458037,0.015884716755512916,0.015444678252138414,0.015030915489244302,0.014641298222556358,0.014274085935255174,0.01392769650809311,0.013599591691650236,0.013286799524056614,0.012987180616338512,0.012699320533696802,0.012421863020378954,0.012154122181479115,0.011895808106747287,0.01164687619423535,0.01140783567670054,0.011179773263981138,0.010964040041555973,0.01076208414322241,0.01057533726830045,0.010405141331092747,0.010252917221005936,0.010119716110073833,0.010006041381482,0.009911723609030703,0.009836183933122572,0.009778173094466934,0.009735537012564714,0.00970587979933258,0.009686473122286479,0.009674354754525567,0.009666306817353006,0.00965885871511907,0.00964890638425444,0.00963401507981089,0.009612579031711642,0.009583752432547502,0.009547568762754822,0.009505075314329911,0.009458090374702697,0.009409240138486,0.009361933483186771,0.009320256891359001,0.009288789079941207,0.009271953855016774,0.009273625370066452,0.00929710679921603,0.009345071670816902,0.00941951248807036,0.0095214693034393,0.009650668883837851,0.009805475344004987,0.009982839201971343,0.010178282453214118,0.010386295307017259,0.010600727124143871,0.010815218496035282,0.011023809970962951,0.011221020704654798,0.011401602486433583,0.011560780661435632,0.011694855864316475,0.011801599336649683,0.011880441313856516,0.011932616422295664,0.011960772379594815,0.011968499376077229,0.011960581608042548,0.011943144057921377,0.011922935596510854,0.011906171289120908,0.011897745551066754,0.011901068893916827,0.011917864845697148,0.011947686922853239,0.011989247187281744,0.012042607503022735,0.012108506888627824,0.012186164205004927,0.0122728270371995,0.01236459285243262,0.012455997586204982,0.012542152287388633,0.012623967949130956,0.012708699724586428,0.012800377024496212,0.012882749917358702,0.012917836542133042 -7.292857142857143e-07,3.0115341459364826,2.668780997748441,2.0383503535980476,1.5809590433696288,1.3074947027143087,1.139672509493565,1.0348507669437956,0.9645081732908323,0.9111433854514596,0.8674931910344036,0.8300223664123071,0.7957634941315034,0.7635717313297312,0.7324017645200938,0.7018844404559421,0.67199130914955,0.6427835063012813,0.6142027391906034,0.5861138306132344,0.5586879437245101,0.5320829214929371,0.5063548591456796,0.48145335365966985,0.4575004842225946,0.4345161040351098,0.41249530808584645,0.39151195006299755,0.3714350561063837,0.3521956591214648,0.3338361254049257,0.316354298019396,0.2997387762032578,0.2839084799960777,0.2688457765802235,0.2545393205955383,0.2409422250431656,0.2280448539550195,0.2158322074723118,0.20427986304659695,0.1933369788641438,0.18296806582649164,0.17317540492155464,0.16393445867667866,0.15522541133933573,0.14703799525844852,0.13932791230468708,0.1320604056420708,0.1252219788661702,0.1188003955370486,0.1127819247444444,0.1071271251806029,0.10180390975515737,0.0967869168801206,0.09205948467875251,0.08760008985464926,0.08338327866749491,0.07939844848721457,0.07563028741220754,0.07205407774067911,0.06865129337731886,0.06542298015917598,0.06235145054178262,0.0594241653245699,0.05663688633479169,0.05398353546999349,0.05145754789107572,0.04905425982585641,0.046772637698015436,0.04460354276757933,0.04254378410659632,0.04059116575411576,0.03874875168301118,0.03700968834045669,0.035366353020235435,0.033820194373585244,0.03236518382765032,0.030995793488566722,0.02970875679603632,0.02849968259133291,0.02736057458336316,0.026288278086221674,0.025280293440340298,0.02432947406303534,0.023431744557898454,0.02258446218045099,0.021782989897246502,0.021021807316596586,0.020298183944205843,0.01961117998988132,0.018959505197982514,0.018341164242361804,0.017753821443348035,0.01719661176346444,0.01666960389413274,0.01617199179360222,0.01570283870532689,0.01526194520071855,0.014848362852627972,0.014460801135216701,0.014098459115189683,0.013760207125665494,0.013444263057348069,0.013148792938885963,0.012872113923348522,0.012612186560093998,0.012367045009791293,0.01213509056382123,0.011914966874561683,0.011705579819049954,0.01150598174881152,0.011315542158975934,0.011133973833538409,0.01096139099524258,0.010798177839881075,0.01064477536545901,0.010502002973285317,0.01037096285521164,0.010252782170838689,0.010148427254308555,0.010058341609415853,0.009982708839349612,0.009921681249934132,0.009874998885317536,0.009841823105726209,0.00982093281999331,0.009810905511513375,0.009810142808080336,0.009816945975334748,0.009829631345230011,0.009846445283319473,0.009865644472490985,0.009885830387449841,0.009906070965727065,0.009925805093047434,0.009944800116609021,0.009963223004957384,0.009981648093001083,0.010000890301598267,0.010021958582529134,0.010046105588277255,0.010074630304554962,0.010108685718301579,0.010149302875625484,0.010197331572778974,0.010253306212583706,0.010317481704438703,0.01038968628881633,0.010468977719513817,0.01055408998659756,0.010644004736676023,0.010737303538726314,0.010831773404538331,0.010925358480837968,0.011016707635007386,0.011104734122856567,0.011188594948116219,0.011268277387363655,0.011344427481021628,0.01141782947196458,0.01148974758843291,0.01156187937171918,0.01163611369660482,0.011714781514420524,0.011799869010044175,0.011891795667260572,0.011989171478607997,0.012090000063173954,0.012192303996420363,0.01229383675985579,0.012392604816727583,0.012488001466062102,0.01258067797993231,0.012670764223435186,0.01275752484995711,0.012838818721509458,0.012912098259970084,0.012980257419035857,0.01305327282099903,0.013137398574962905,0.013216822572440915,0.013251459368688625 -7.628571428571428e-07,3.0754404839556697,2.693954472559552,2.033371685416504,1.592646078008722,1.3221447791991374,1.1431445188555944,1.026758529536944,0.9478633011385174,0.8914400261401825,0.8459435644483669,0.8069812061692084,0.7719377417795013,0.7393385752251697,0.7083091587333953,0.6783631139288019,0.6493476343021668,0.6209346952208156,0.5933587411549407,0.5665189005307145,0.5404239593890374,0.5150592100513538,0.4905801194628049,0.46697288730297365,0.4442618102298279,0.42241481644782886,0.40139879422030544,0.3812593054471953,0.36200603958193717,0.34358318662905896,0.32605215531832554,0.30935399213042347,0.2933303754808651,0.27804319614146084,0.26351910110757937,0.24972364908788905,0.2366190865615084,0.2241770072239893,0.21246028129382175,0.20138966117525106,0.190894346295904,0.18099890894045512,0.17169540179212142,0.16297381731276306,0.1547802035895818,0.14706239177599056,0.13982745029721433,0.13302029476239904,0.12661043662965413,0.12059962739284048,0.11494851998087313,0.10961986570781833,0.10455722997767881,0.09976111300664424,0.09520107291716853,0.09085297682060943,0.08672289169275799,0.08278980574569411,0.07903794700588225,0.07543570224982282,0.07198502393311315,0.06868355140738315,0.06551966480144021,0.062499961218663705,0.05961548925029965,0.05686046777246711,0.054237630019349774,0.051737338560368946,0.0493657792373508,0.0471178125623752,0.04498824661157973,0.04297627260036267,0.04107411860759478,0.03928394374770138,0.03759835609549886,0.036010634873617296,0.03451297093399227,0.03310068952532615,0.031772871495423835,0.030524609052181677,0.029349157539937557,0.028237108076154815,0.02717929550424311,0.026172605644268605,0.0252152696049925,0.024301792429667252,0.023428938436617693,0.022593008346184627,0.021791789608597442,0.021024142144295703,0.02028881866184258,0.01958617828923609,0.018915371204174074,0.018275521165796872,0.017668037544157886,0.017095307711298042,0.016557950219498015,0.01605590732573441,0.015589285827600944,0.01515772673089831,0.01476025522078331,0.014395556105053083,0.014062220033881863,0.013758142721983316,0.0134806688904708,0.01322664684360371,0.012992539090291789,0.012774886919091559,0.012570652840909783,0.012377149527833418,0.012191811369103151,0.012012786765839025,0.011839102996398032,0.011670294218358024,0.011506409035274456,0.011348061088134445,0.01119640306014003,0.011053112968717142,0.010920083660294825,0.010799060783342149,0.010691736402197668,0.010599744659680757,0.010524357957612196,0.010466335109061383,0.010425712944216558,0.010401917798366366,0.01039393960336777,0.010400014482274657,0.010417853985697976,0.010445197455691507,0.010479821016833257,0.010519432239408695,0.010561702673556555,0.010604425942817545,0.01064585607202294,0.010684914167661805,0.010721142238648864,0.010754547408901197,0.01078559010275342,0.010815366040011062,0.010845617065724594,0.010878490766897193,0.010916155294751276,0.010960363572597422,0.011012442706214084,0.011073569969357197,0.011144598761395511,0.011225817798915632,0.011317070866469088,0.011417636729442592,0.01152587813896051,0.011639598371404379,0.011756495672210586,0.011873927667320433,0.01198912843369455,0.012100101427668337,0.012205891181589053,0.012306207850099013,0.012401187727638386,0.012491357850001933,0.012577730817492144,0.012661819535625816,0.012745640815566171,0.012831145698845664,0.012919032777718546,0.01300944962397957,0.01310282685117098,0.013198682242418746,0.013294155795380121,0.013385893507323226,0.013472601264687236,0.013555702102407616,0.013638013320563129,0.013720851847413026,0.013801745686753119,0.013875467829769443,0.013941050099434183,0.01400661535450674,0.014084940111059352,0.014178935627274342,0.014266513148291034,0.014304125099667876 -7.964285714285715e-07,2.706611341247518,2.495346985163388,2.0334872732012426,1.608278770252207,1.3273206582642032,1.152055919130306,1.0370716727651716,0.9577863656518513,0.8990064563878326,0.8530027651136225,0.8150322722966709,0.7817003425537634,0.7508533347642614,0.7212069338969914,0.6923370548977019,0.6639197686488058,0.6358199076148247,0.6081505590654637,0.5808850664236537,0.5540725760829959,0.5278342525345301,0.5021998785320592,0.47719937058466194,0.45291963779296407,0.42948676962403154,0.4069747988980999,0.38540607126129345,0.3647993658535451,0.3451475004803607,0.3264561389707601,0.30874334269678805,0.29198822208993663,0.27615685367623954,0.2612300493913658,0.24718813458303357,0.23398586322854067,0.22156383881835107,0.20988450872000033,0.1989131468169526,0.18859590995452202,0.17889238581293188,0.16977940761106547,0.1612102432543914,0.15312460946468306,0.14549410734609422,0.13829812739366798,0.1314921699638597,0.12504801446199143,0.11895576308198037,0.1131862812806857,0.10771471240675061,0.1025332927608253,0.09762385072948083,0.09296335589970149,0.08853468163170133,0.08432788077225023,0.08034324705682379,0.07656757555146786,0.07297420027658327,0.0695574468171711,0.06632069315453894,0.06325426081111234,0.06034947035716302,0.05759651654363325,0.05497974672798948,0.05249363394253814,0.05013701739387979,0.04790135265518524,0.045780646945974064,0.043772772349128804,0.04187200969928292,0.040072225216789416,0.0383684337246024,0.036753717725760054,0.035222293871617204,0.03377321571203262,0.032405589221958,0.031113464713006904,0.02989046670200862,0.028733956339093838,0.02764121384550264,0.026608604989365115,0.02563405961736653,0.024714781083748773,0.02384601765514358,0.023024034583611835,0.02224660601103928,0.021511607232249964,0.02081690867624304,0.02015994248595485,0.019537916992361477,0.01894897192722327,0.018391421897384182,0.01786304949670256,0.01736225733161793,0.016887985431011797,0.016438742744662612,0.01601314589555484,0.015610425831365056,0.015229813209145253,0.014870243755401868,0.014530765345617968,0.01421065258223496,0.013909107910577664,0.013625310959726205,0.013358606813266227,0.0131083414087459,0.01287380531087966,0.012654218473657493,0.012448508512867789,0.012255531142869477,0.01207453780462736,0.011905073692780646,0.011746620798796013,0.011598555752893334,0.011460328908006103,0.011331581229910605,0.011212096360982172,0.011101721449119164,0.011000351276898973,0.010907913321060982,0.010824385150376451,0.010749910805594414,0.010684883572455013,0.01062979049759874,0.010584935152075554,0.010550450476732448,0.010526541417410553,0.010513430550413955,0.010511126030015397,0.01051952499868019,0.010538616500132552,0.010568290367101522,0.010607983110386625,0.010656688858054758,0.010713285062154618,0.010776745326800776,0.010846094900543411,0.010920261142937015,0.010998054170378224,0.0110783347189661,0.011160081603129023,0.011242178231674537,0.011323197265821547,0.011401537238552825,0.011475828450504818,0.011545310443421202,0.011610067880776126,0.011670823033219853,0.01172814426995341,0.011781869497441474,0.011831539932045524,0.011877516259493365,0.011921331083224658,0.011964619052703988,0.012008430583774035,0.012053821395579546,0.012102251980746711,0.012155630080935072,0.012216320153980878,0.01228623745053875,0.012365592844172532,0.012453341560419396,0.012548745482116711,0.012651275059511441,0.01275946340206087,0.012870425825050472,0.012979745113265736,0.013082685390422304,0.013176124312459117,0.013259169190416144,0.013332653513962445,0.01339798861626667,0.013456585993173993,0.013510175113497377,0.013562091747549316,0.01361944055267361,0.013691829805036186,0.013781902099601145,0.013868073687742245,0.013905341361908548 -8.3e-07,3.2550934644355864,2.8611082133916983,2.1380794790917665,1.6166140107793645,1.3074601783128743,1.1118985066271008,0.9841002107065782,0.8992644493634306,0.8371724214234366,0.7896879120374193,0.7515693391030066,0.7192562166946382,0.690782444047094,0.6640929550250498,0.6384492201867418,0.6135700049785574,0.5891501530025406,0.5652101124950495,0.5415534432568755,0.5183077046015385,0.49555183310295203,0.47334797929602923,0.45171151068516574,0.4306704130268032,0.4102526264796917,0.3906181336466296,0.3717041576383266,0.35346831165830567,0.33600573102810266,0.3193243843565414,0.3034662455673725,0.2884318128791875,0.2741708208728194,0.2606973818164586,0.24793516618407974,0.2358348257825436,0.2243843831470203,0.21360334014528654,0.20347038315618748,0.19387411952429148,0.18475443333697625,0.17609319599008003,0.1679046273021562,0.16015311907851676,0.1527611135659615,0.1456971700098477,0.13896885236366327,0.13255697508064343,0.12641895791232655,0.12054381065126826,0.1149443502328607,0.10960249763764279,0.10447505118156189,0.09957003490030876,0.09486511519289764,0.09036501710184938,0.08607731684835748,0.08198524252726704,0.07809485783283558,0.07440160024094636,0.07089499112891103,0.0675577732414446,0.06438813904868816,0.06138068132903534,0.05854586320485708,0.055876169087879686,0.0533533636783168,0.050982005765135494,0.048753204620335416,0.04664975081978776,0.04466655477514012,0.04279792004718839,0.041032128315415195,0.039365540699646095,0.03779375520051514,0.0363108140925523,0.034906677786284514,0.033575940916047875,0.03231007274381839,0.03110185916823865,0.029952516010202438,0.02885696545196786,0.027810185140481225,0.026808345519474638,0.025850034357507694,0.024933713425495625,0.024058083513450006,0.02322185111764516,0.02242283458107166,0.021662117478581825,0.020939021742935115,0.020252036957463946,0.019600715140629117,0.018984034982967052,0.018401731961168854,0.017854468266367052,0.017341646814630504,0.016861867170124674,0.016414103069365228,0.015996804484697164,0.01560858433240549,0.015248313248436104,0.014914511908979694,0.014605252799889498,0.014318341241790081,0.014051905255090709,0.013804350290738544,0.013574231414325408,0.0133597728892757,0.013159100570590847,0.012970732790376714,0.012793214982324006,0.012625226795767936,0.012465748815913485,0.012314140012844787,0.012170041206482685,0.01203314354959611,0.011903284688484676,0.011780481500872172,0.011665085839837617,0.011557770451044232,0.011459117064819346,0.01136956556385209,0.011289563822688134,0.01121949243088934,0.01115968828299641,0.011110702349010236,0.011073107117785572,0.011046936942362895,0.011031892202357625,0.01102780892417497,0.011034540707183089,0.011051904908092878,0.0110797079945048,0.011117514261010862,0.011164584100581037,0.011219957337853298,0.011282524021362319,0.01135111006801008,0.01142446087255621,0.011501333349274868,0.011580669809350498,0.011661468146131471,0.011742750127319306,0.011823960397462474,0.011904980605742382,0.01198571209189872,0.012065984094581497,0.012145483718649422,0.012223762055418929,0.012300985189632303,0.012378318793656373,0.012456899422322407,0.012536925504952043,0.012618329180297818,0.012701744588349701,0.012788273875597401,0.012879011342498763,0.01297496266227404,0.01307677548948077,0.013184968894971607,0.01329994351922844,0.013421813539797408,0.013550485150025526,0.01368459031280537,0.013820144339953176,0.013951821462368466,0.014074623518579226,0.014184925100193384,0.014280478720527689,0.014360504349225292,0.014427051831529153,0.014483840626269031,0.014534676261549367,0.014582912859626682,0.01463239186349379,0.01469175056286546,0.014771614872769158,0.014872322380966717,0.01496870457923729,0.015010632526913169 +3.6e-07,4.996072079501112,3.5186432574260493,2.3168045216276236,1.8926688422625129,1.7084189099331732,1.599729698640666,1.515022573968547,1.4382313538589748,1.3635203937828002,1.2899770832626447,1.2169183484604265,1.1450946447878114,1.075289552769291,1.007975602353452,0.9435847897570298,0.8823348230534077,0.8242737935265403,0.7694893935423961,0.717891054761244,0.6693837945951502,0.6238619437653019,0.5812095309598482,0.5412866536381653,0.5039708179831895,0.46916066054118405,0.4367147567942085,0.40656797610529577,0.378604937156107,0.35267529707799916,0.3286574893939754,0.3064420515387931,0.2858946593616266,0.2668718075588923,0.24925661120219597,0.23292441480857443,0.21774974935178673,0.20364375876365753,0.19049349921432135,0.17822395418741938,0.16676557570882897,0.15605353563843485,0.14604333393732377,0.13669377756243675,0.1279731131630992,0.11985593938234242,0.11231868835917824,0.10532457852845285,0.09884154159318752,0.09282957918888536,0.08724931039594641,0.08206647185969536,0.07723466612097285,0.07271613862843619,0.0684784556959825,0.0645030289146041,0.06075915331960658,0.05723338807605082,0.05391259010840765,0.05079789295940519,0.047876612245714524,0.045146331358853514,0.04260219537248719,0.04024094976379814,0.038056078797474455,0.036038718100527084,0.03417870769955003,0.03246637655949009,0.0308857030073056,0.029423859658899633,0.02807014436069782,0.02680943932615198,0.02562950096947614,0.02451815279078401,0.023468486514093913,0.022475082575346358,0.02153295691204971,0.02063823755147488,0.0197902051513225,0.018987536509697728,0.01822860631342372,0.017510867959419568,0.016831841400571295,0.016188151012374807,0.015577082625808387,0.014995598829583666,0.014442579837484364,0.013917052790636694,0.013419197471406932,0.012951242790059134,0.01251430759823717,0.012109787728114295,0.011737536013146753,0.011396256821657612,0.011082990682118921,0.010793513958310522,0.010522929639757702,0.010266178849437852,0.010018909510261898,0.009778207227060527,0.009542630911431833,0.009312274183083703,0.009088601987833036,0.008873895238603558,0.008670603010823605,0.008480958295312964,0.008306512216147621,0.008147990404989787,0.008005430027160738,0.007878266500515232,0.007765611751681655,0.007666408254136507,0.007579588982077762,0.007504052054727617,0.007438396411155037,0.007380949049948404,0.007329618599045494,0.0072819690135733075,0.00723550399092864,0.007188065535317976,0.007138249105518685,0.007085732356617722,0.007031532512553092,0.006977842132111988,0.006927736951252592,0.0068847318029887075,0.006851956738880442,0.0068316879592881345,0.006824931474494387,0.0068313979570882805,0.00684963441240234,0.006877507172368454,0.006912913634879591,0.00695435205054066,0.007001312426154927,0.007054525289602836,0.007115762267105627,0.00718738886732667,0.00727179326508617,0.007370823693370733,0.00748521223266791,0.007614309215182673,0.007756327348078807,0.007908651428947071,0.008068186296980105,0.008231789643320893,0.008396692133000205,0.008560706771175281,0.008722249016459396,0.008880253648294499,0.009033859769023798,0.009182224566194882,0.009324434466471597,0.00945932898766583,0.009585681241019842,0.009702432388838987,0.009808635651530187,0.009903403482521151,0.009985875421141977,0.010054675988859814,0.010107504215076881,0.01014102076030227,0.01015085552419073,0.01013218873985262,0.01008092244614556,0.009995164857841417,0.009876499611491753,0.009730928913106852,0.009569260003028162,0.009406900422733436,0.009262688656558531,0.009156673737376452,0.009106907615581,0.009124819203752892,0.009211799215524197,0.009358950739648136,0.009547884051110308,0.009753653875673647,0.009950798292190598,0.01011914151704521,0.010240231115211213,0.010286670308946207 +3.935714285714286e-07,5.050476299799945,3.393257726695833,2.272814951672725,1.8247530980473894,1.627940911932403,1.5161597286335273,1.432011119227676,1.358798872024814,1.289670292251682,1.2223081240761364,1.1565118209181995,1.0921851296751999,1.029744826433882,0.9695159266213051,0.9117381269486153,0.856592037437614,0.8040924379642218,0.7542238087185018,0.7069011995664592,0.662077747581137,0.6196521061023165,0.5796132908549986,0.541886645912776,0.5063677690747203,0.4729697258503271,0.4416104363990458,0.41225451353195136,0.3848088389146935,0.35917750125596165,0.33526261814421765,0.31300129039487556,0.2923056019167267,0.27306154743162275,0.25516118639368757,0.23852231751272748,0.2230414243319654,0.20861979220750068,0.19517258056897857,0.1826217070464462,0.1708935667290379,0.1599338015788411,0.14969443494229676,0.14014517336065654,0.13124690054265092,0.12297668992898203,0.11529747266164266,0.10817295281194991,0.10156046285724533,0.09542808265671786,0.08973132759815172,0.08442333716020406,0.07947243799912274,0.07484108849587633,0.07049961044194336,0.06643801745380548,0.06263338124792167,0.059072820755814166,0.05575387702891895,0.05266890319814396,0.04980073011519857,0.04714200886306425,0.04467347208517387,0.04238037439478797,0.04024547800873125,0.03824859672968741,0.03637467243151027,0.0346112269497519,0.0329478841802162,0.031380318393977306,0.02990112747548691,0.028511143280667513,0.027207019931663433,0.025985093050422053,0.024840322454985023,0.02376273623121207,0.02274486492911793,0.02177943836138383,0.020858450155246334,0.019976837549266917,0.019130344332785845,0.018321213044213733,0.01755284216733481,0.016829260834747117,0.01615429759894751,0.015531855308488123,0.014960706652361508,0.014438373733101886,0.013959140476480746,0.013515505426681023,0.013099736748803997,0.012705358132470606,0.012327297994304695,0.011962256104925297,0.01160974223039909,0.011270708678833797,0.010946687285036488,0.01063920426336328,0.010349152240001795,0.010076188700985691,0.009818665082728254,0.00957454567263257,0.009341862228978126,0.009119125542679234,0.008905889923399402,0.008702918850718454,0.008511661321436392,0.00833391947302373,0.008171078870343617,0.008023674824954622,0.007891382440079128,0.0077731198825542495,0.00766731927667895,0.007572176299335656,0.0074861836654281045,0.007408346236424357,0.007338114603329225,0.007275370820915004,0.007220107948834012,0.007172161573993553,0.007131057793726819,0.007096107138449195,0.0070666118764771335,0.007042245209309414,0.00702324004742575,0.0070104542169547425,0.007005264427181506,0.0070092247431615415,0.007023519525878079,0.007048454454146913,0.007083266418562348,0.00712610016115459,0.007174392752857174,0.007225642804833307,0.0072781519858592155,0.007331522170656472,0.0073868168373790975,0.007446476012780914,0.007513722408511888,0.007591558662993837,0.007681747318010333,0.007784229287188594,0.007896957226504476,0.008016233850491885,0.008137629338314582,0.00825717997720017,0.008372525490417705,0.008483495309200041,0.008592142867341694,0.008702386006815879,0.008818797922345394,0.008944952048583874,0.009082096501317014,0.009228584880387409,0.009379577743606487,0.009527420142346655,0.0096631516065227,0.009778374901690017,0.00986691545677851,0.009925412322396123,0.009953383522456274,0.009953144049514672,0.009928974002249409,0.00988578538474489,0.00982796704549897,0.009759096330134057,0.009682055179572178,0.009599640566247255,0.009515813625884254,0.009436786065668574,0.009371751392430655,0.009332729033129651,0.009331785472118029,0.009376772069037616,0.009469064218939003,0.009602855318098501,0.009765242663706233,0.00993819643692185,0.010103007736634178,0.010245439070759952,0.010351010929621767,0.010392523920817458 +4.271428571428571e-07,4.236489439695137,3.3226931517058182,2.341074883039565,1.8837784270654923,1.653055532076324,1.511486270482712,1.4046294275886577,1.3131577328475261,1.231469557907506,1.1571096586078176,1.089425045914367,1.0272856566640352,0.9698791251327046,0.9163955775722128,0.8659308220700771,0.8176698696883772,0.7712157321577993,0.7265002730809446,0.6834882674038222,0.6423744718952588,0.603330631201587,0.5665284212036977,0.5320631796101458,0.4998398121762991,0.4696539884859131,0.4412797527694759,0.41451552564890237,0.3891466435914563,0.36499187941266514,0.341985466972481,0.32008482831760504,0.2993138586669736,0.2797152681576831,0.2613173855880156,0.24415271340346834,0.22821812205490388,0.21346710932790566,0.19982366739981802,0.1871864078313259,0.175426146981557,0.1644584157978103,0.1542000165780931,0.1445940520804832,0.13559786500669999,0.12719315615839816,0.11936695023893594,0.11211255643757098,0.1054235559678484,0.09925981823678377,0.09356530430966813,0.08828609020496306,0.0833561725902302,0.07871293834618019,0.07431168572159058,0.07011408020843952,0.06610717902089905,0.062302284008739876,0.05871381476719472,0.05535720771096787,0.05224604221437743,0.049385672272892436,0.04676267111500207,0.04435811615481547,0.042135000211464387,0.040062001575493056,0.03811160997515327,0.036258815542026666,0.03449319174760327,0.03281464088339417,0.031223466901461102,0.029725491326620532,0.0283243384182797,0.027018390635728775,0.025804072139037758,0.024671881930988664,0.02361392425532722,0.0226224813215183,0.021692438836104028,0.02081860798303589,0.01999955175281946,0.019233292047284592,0.01851481046400496,0.017837001590884673,0.017191011878714333,0.016567514723804258,0.015959552708775665,0.015365418122656087,0.01478842941007683,0.014235048090909192,0.013713362692516232,0.013230627629799496,0.012790588232365434,0.012390240285751218,0.012023148396048348,0.011679491621379606,0.011349791575201693,0.011027328095512582,0.010709291378116444,0.01039756548481808,0.010097084560681941,0.009814518276565352,0.009556131583391305,0.009325160615667858,0.00912131978326832,0.008941391646678629,0.008780417350836879,0.00863296278415036,0.008494239464895663,0.008360751321125303,0.008230632013772587,0.008103451990671237,0.007979943709185854,0.007861414288848387,0.0077494905655935194,0.007645826917204117,0.007551952990917537,0.007468920205682321,0.007397044242997791,0.007335641146871928,0.007283087052844921,0.007237304475132498,0.00719643621690835,0.007159596507175866,0.007127529403043529,0.007102526190351883,0.007087707233522491,0.007085997171433119,0.0070990111606617865,0.007126276682135941,0.0071651783507419575,0.00721176098213311,0.007261881210420605,0.0073123796357205855,0.007362022640106836,0.007411582718664124,0.007463128983972156,0.0075188798549081365,0.007580049380800823,0.00764630833151972,0.007716101801310966,0.007787612317207458,0.007859785132149966,0.007933299374304892,0.008010859004831792,0.008096345400078525,0.008193114372315733,0.008302552133843144,0.00842334522948847,0.008551459290897099,0.008681449938220048,0.00880850467734539,0.008930250803269568,0.009047467554272283,0.009163576090961922,0.00928340760205483,0.009410707670483805,0.009546028971224111,0.009686122963152669,0.009824413320907999,0.009952890777701513,0.010064533933845139,0.010154903003539184,0.010222462280669014,0.010267976045898835,0.010293057216165346,0.010298354802054373,0.010282871293622707,0.010244391792525974,0.010181891200708365,0.01010004207519371,0.010012090319663472,0.009938244086836603,0.009901021690975316,0.00991996939941245,0.01000565065408067,0.010154652221390364,0.01034865121334788,0.010559862117152078,0.010758479728560201,0.010909787504798547,0.010968755565522908 +4.607142857142857e-07,4.764848965310126,3.2102156327395095,2.109022861266686,1.658043175344903,1.4529311068008697,1.3429533432628618,1.2695544569175878,1.2120116533412075,1.160288179476124,1.1107916211128783,1.0617495957896674,1.0124185839726199,0.9630280933451197,0.9140338564118393,0.8656348130032893,0.8180751500454958,0.7718172754836599,0.7272085813425154,0.6844518503445095,0.6436510625810619,0.6049398918262391,0.5684196810865584,0.5340482766806526,0.5017600121673611,0.4714355329566959,0.44296941853475297,0.4162844769705379,0.39126191777494324,0.3677370298673857,0.3455846536119997,0.3246899346357056,0.3049344977951822,0.28626595457016596,0.268628517354678,0.2519108771274003,0.23607808546245476,0.22111903513802153,0.20701095737668176,0.19371693055170763,0.18121131373822083,0.16947081457690535,0.15848417186343444,0.1482531234956916,0.13872859016833333,0.1298791192329803,0.12166396446635688,0.1140670565161751,0.10702985384322736,0.10052611274216053,0.09452341499953021,0.08897394056044484,0.08383027513620232,0.07905749784974266,0.0746258311785113,0.07050303223598049,0.06665548982565021,0.06306002935210149,0.05969761926205407,0.05654308188940862,0.05357717054313462,0.05078883594426536,0.04816770289823331,0.04570260498845475,0.043379164468670295,0.04119164916956551,0.03913150396766542,0.03719308173589041,0.035367780106373925,0.0336541210258441,0.03204290371581162,0.03052742397195578,0.029103100929137808,0.027768437126460696,0.026513616406315964,0.02533693094966879,0.024232732126293014,0.023195593481483343,0.022221090686021534,0.021305930790897265,0.02044534087930214,0.01963512959763236,0.018873180348917287,0.01815492715034612,0.017476250956027985,0.016835642720730806,0.01622916921668859,0.01565526315828381,0.015111772520733358,0.014596179325741714,0.014106746815004623,0.013642622941528109,0.013202785769811615,0.01278578815254159,0.012390506057229054,0.01201669917521635,0.011664096956639001,0.011331803855729418,0.01101913653624861,0.01072573758564774,0.010451094694300753,0.010194413868470486,0.009954905790916659,0.009731562744587209,0.009523411182275671,0.009329305479173546,0.009147914646391241,0.008977946685811101,0.008818244157779563,0.008667580672859186,0.008524967362264877,0.008389562210737452,0.008260761345905917,0.008138394526175985,0.00802260217172545,0.007913700694556018,0.007812408195106597,0.007719682357199276,0.007636641347667676,0.00756447267477245,0.00750427695902397,0.007457046956071239,0.007423384685886436,0.007403439189398632,0.007397003222352393,0.007403256789524847,0.007420812675932207,0.0074478538582555,0.007482083865905611,0.007520881826418512,0.0075614725951171705,0.00760113571180749,0.0076374755810279865,0.00766851331216686,0.0076929020107180494,0.007710164586121407,0.007720747550489623,0.007726015707132702,0.007728132307588434,0.007730031741497988,0.007735362401648057,0.007748179341335231,0.007772477374548282,0.007811819310746579,0.007869147525578485,0.007946606665371404,0.008045274621509701,0.008164841495994933,0.00830355190258348,0.008458203474070341,0.008624221032820728,0.00879602803032105,0.008967461957772946,0.009131982554204592,0.009282880666465553,0.009414000525435463,0.009520388049906132,0.009598660836795236,0.009647357355693744,0.009666950508831583,0.009659789098203402,0.00962986073315213,0.009582462252609102,0.009524416277907857,0.009463674411017125,0.009408249086854895,0.009365530918760603,0.009341948078710239,0.009342134380695609,0.009368406816298854,0.00942191178186392,0.009503585550485515,0.009612309239818812,0.009742511913857941,0.009885108878056477,0.010029446713623255,0.010164618019747329,0.01028267968433309,0.010381670220358873,0.010465302306747786,0.010531795243547954,0.010559738727769082 +4.942857142857143e-07,3.889252931177458,2.9075402728985886,2.089270060829647,1.6597127062129517,1.4453991670906148,1.3283492861760595,1.2520906899796782,1.1945458279806678,1.1442901065266022,1.0971892676686534,1.0506383257993066,1.0041230467157456,0.957336246680743,0.9106167592847495,0.8641058871194386,0.8181747029816894,0.7730593861458719,0.7290759618740138,0.6864444665058786,0.6453808958650286,0.6060627202218186,0.5686390311953277,0.5331540748821298,0.49962375453753,0.46808233103462915,0.43848556845836795,0.41075267350683387,0.3848405983831548,0.36064308933368966,0.3380552485702385,0.3169902536807694,0.2973341891857922,0.2789775157535366,0.26183499767497226,0.2458495431257555,0.2308979382735062,0.21689790441237228,0.2037956240512594,0.1915064833818062,0.17996843883291705,0.16914014923255988,0.15898278739736346,0.14944145699880704,0.14046807515426968,0.13202923760595534,0.12409900374223118,0.11665077265652551,0.10966482244865618,0.10311822362624014,0.09698014720312499,0.09123837589457764,0.08587840423966289,0.08087960087898245,0.07621262477510166,0.07186842465545486,0.06783546533969755,0.06408023391770941,0.060590839695863534,0.05735234424049929,0.05434148398486522,0.05154143921597987,0.0489314594567531,0.046494583887366416,0.04421941548566847,0.04208434332709352,0.04007674678107178,0.038192051091398706,0.0364190662560384,0.0347430321132609,0.03315708895358744,0.03165678078951626,0.030240234284669473,0.02890183850857528,0.027633692017448135,0.026435659520196803,0.025303499484458584,0.024233274792269055,0.02322296847696498,0.022269219386582037,0.02136679408621616,0.020513307334616558,0.01970555881695869,0.018940468397028003,0.018216660098461485,0.017531515185934914,0.016881749674484196,0.016265398532758078,0.015681172274287423,0.015127952305254206,0.014605130213328155,0.014111950079632509,0.013647196068166221,0.013209799560977892,0.012798820148418925,0.012413066850775152,0.012051205965378195,0.011711333595031884,0.011392040408796947,0.01109183031427233,0.010808913652723268,0.010541896327451157,0.01028947473642299,0.010050373782630326,0.009823800192132316,0.009609432623859373,0.009407073116489318,0.009216936307169611,0.009039386482833242,0.008874742781568641,0.00872335476704676,0.008585644290108194,0.008461910677447178,0.008352130084006603,0.008255996646919974,0.008172884278279979,0.008101848104207133,0.008041768946880705,0.007991296054165471,0.007948972194097483,0.007913280632082367,0.007882812404258008,0.007856258176789747,0.007832553971850315,0.007810982264816488,0.007791046688514331,0.007772550691606338,0.007755687578785844,0.007740907720565108,0.007728897446350418,0.007720479380957098,0.007716536441940939,0.0077180355236508265,0.007725956768967512,0.007741234431536258,0.007764669526355595,0.007796906669315125,0.00783844281659395,0.007889668765055922,0.0079508372351125,0.008022031553584672,0.008103185789522103,0.008194044586863085,0.008294029778125878,0.008402148127180686,0.008516997678028283,0.00863681188357585,0.008759534900757676,0.008882971624785387,0.009004790453213054,0.009122405463485972,0.00923279312442429,0.009332789382411616,0.009419714697143648,0.009491474725140298,0.009546648631710812,0.00958445259069566,0.009604661689707947,0.00960790083688324,0.009596094639537903,0.009572479839507118,0.009541220850389496,0.009507044496348238,0.009474980005851575,0.0094499042402854,0.009436766468551416,0.009440513894764403,0.009464527904466778,0.009509605359183493,0.009574922374201284,0.009659480132115875,0.009762740986246032,0.009883333463974667,0.010017099739421916,0.010156467471152893,0.010291645706296638,0.010413233846702053,0.010516277182969293,0.010602957352637369,0.01067993592919327,0.010744198033110601,0.01077159523767911 +5.278571428571428e-07,3.8202030532457947,3.025791237254338,2.0981726418703235,1.6389581982665835,1.4147985087925015,1.2893183778590156,1.2073482304539835,1.1447386724095878,1.0915474645350376,1.0437501830199742,0.9976823917290593,0.9528392289278339,0.909073009408323,0.8660587966192269,0.8240025272679938,0.7830890451813468,0.7433207859449521,0.7047166317169555,0.6673404773711106,0.631150387073128,0.5962292469155165,0.5626154383058116,0.5303123269571961,0.4994027560391145,0.46989854497746564,0.44175729230350963,0.4150111044625525,0.38966716342300617,0.36568503868482716,0.34309115100846327,0.3218262916890118,0.30184732052021096,0.28307990250945003,0.265482669465791,0.24899133290080808,0.23355361342901979,0.2191232996511209,0.20567428441222058,0.19315256392273786,0.1815029836204081,0.17066877492520932,0.16061228464498062,0.15127194382939152,0.14260214488652886,0.1345434681268595,0.12700964139973464,0.11995980812457283,0.11332586334552304,0.10706835346967733,0.10112779432936947,0.09549140497062926,0.0901411718892343,0.08505064378151848,0.08021697104509254,0.07564558683185656,0.07134061261218788,0.06730731987108002,0.06354476747132645,0.060060588810173454,0.05684691689936176,0.05388207099534554,0.05114605362258454,0.04861417631266916,0.046264095085102035,0.04406965757018243,0.04200037281716841,0.040036115027688016,0.03816352048731903,0.036367338023784584,0.03464257853095966,0.03298999017913086,0.03141526784017758,0.02992204802542001,0.02851595125208801,0.02720262965889531,0.0259809957710218,0.024847856589308718,0.023798541951710914,0.02282498287014746,0.02191596558753421,0.021062640682345757,0.020254456146526725,0.01948336367762202,0.01874459022882352,0.018036014488493454,0.01735769899196725,0.0167116349164624,0.016101679587311877,0.015530279300112162,0.014999812312436493,0.014510479801366764,0.014060531792331249,0.01364640464351109,0.013263205347827026,0.01290495367583135,0.012565684469512181,0.012240971688585092,0.011927636822804505,0.011623297543024045,0.011327402353441561,0.011040985440417407,0.010766011872522322,0.010504556957012566,0.010258057743321454,0.010027304422356808,0.009812481788612583,0.009612970129785146,0.00942749195617192,0.009254676653378167,0.009093310453112723,0.008942726709746973,0.00880284343567779,0.008674164792815981,0.008557619188548823,0.008453963729033792,0.008363559878093143,0.008285885200652387,0.00821939849466526,0.008161796032203291,0.008110014410376749,0.008060876363575336,0.00801186359572691,0.007961368206222764,0.00790914189489399,0.007856633617914045,0.007807016946234553,0.007764641331387719,0.00773424833944634,0.007720210206747889,0.007725672212018108,0.007751980785613026,0.0077984166643380644,0.007862185700358595,0.007938676817796772,0.008022183648553132,0.00810688921087496,0.00818785749215886,0.008261683257368153,0.008326936674636458,0.00838429724756328,0.00843624417544724,0.008486498547206914,0.008539288158684342,0.00859845501121576,0.008666371409092327,0.008743223448329661,0.00882683290831564,0.008913115763305134,0.008997145537273266,0.009074053498115442,0.009140123945677682,0.009193521178589981,0.009234461298022724,0.009265508261452995,0.009291371691106423,0.009317905955531502,0.00935117301152025,0.00939645929210077,0.009457281228956798,0.00953437597736274,0.009625374611246925,0.009725549540485351,0.009829046280259914,0.009929633491065657,0.01002140943469892,0.010100283811590855,0.010164384178797998,0.010214252823443149,0.010254229033729665,0.010292154888513953,0.010337466789851016,0.010399076727956562,0.01048141789757111,0.010582359493123705,0.010695808652854688,0.010813879150539829,0.010929056006907256,0.011037332352100998,0.011137340615203984,0.011219028631736959,0.011253053679271543 +5.614285714285714e-07,3.658696324939927,2.969924144225029,2.0425918248947026,1.5610341286694085,1.324812916803765,1.1916728815375308,1.1088317454020906,1.0513421197262505,1.0064571952152848,0.9665914869719441,0.9293740623293902,0.8933213961397118,0.8579758671805608,0.8228430114991431,0.7879499604255459,0.7532225986495171,0.7188219429424585,0.6849205683463391,0.6514587882370295,0.6185481164399762,0.5863431279639564,0.5549041217999046,0.5243725828828595,0.4947943444282432,0.466222948139687,0.43879341960725865,0.41255571677600117,0.38752728487316074,0.3637856623510353,0.3413552673959774,0.32025876931656155,0.30048447275295637,0.28200969425138867,0.2648367478628083,0.2489118393195897,0.2341634250512598,0.2204796548425792,0.20780951707141865,0.19604642520068866,0.1850963417911391,0.17488528369695586,0.16534283571909333,0.1564007170438778,0.14797761371225482,0.1400336671881587,0.13252272337572202,0.12541049771017182,0.11867399408651703,0.11230624000617788,0.1062651894061001,0.10053526078603613,0.0951294006176918,0.09002802358563364,0.08521265599599241,0.08066471183339174,0.07638355277316564,0.07235151893869156,0.06854846269357161,0.0649586953910102,0.06157673522700936,0.058393370998317394,0.05538620769420325,0.052548778516620605,0.04987287783528765,0.047353189793169305,0.044986313059620836,0.042760205203323784,0.040669400443063254,0.038707706044371605,0.036869534518212066,0.03515025534220856,0.03353992134837083,0.03203034108026237,0.03061442335981297,0.029282312175430796,0.028030931085637627,0.026851807037274775,0.025737814792259266,0.02468335252021129,0.023685220419385696,0.02273985625290435,0.02184334206974461,0.020992883110798118,0.020187071423831148,0.019423837663230543,0.0186999438329462,0.018013748714374424,0.01736373114544361,0.01674626682131663,0.01615865352447097,0.01559914091047921,0.015067223079500454,0.01456214361306972,0.014082487779532374,0.013627757336329347,0.013198174783382326,0.012793895306993363,0.012415833408205323,0.012064279170207871,0.011738945443795022,0.011439726228618807,0.011165701535679485,0.01091516869182057,0.01068594373027677,0.010475791806565786,0.010282182327926236,0.010102360501848288,0.009933720630721055,0.009774016172948567,0.009621689775021494,0.009475873888823982,0.009336172175276764,0.009202716400707715,0.009076045469238778,0.008956915316580804,0.00884618156245098,0.00874451623651164,0.008652177613236,0.008569088353053879,0.008494828495161671,0.008428347130968359,0.008368336678789638,0.008313514451371557,0.008262609464573576,0.008214725315233786,0.008169650572451096,0.008127909163743723,0.008090820291629587,0.00806036724780098,0.008038877923912448,0.00802898933901357,0.008033344456238194,0.008054155338890144,0.008092936456189241,0.008150218225693066,0.008225309013373663,0.008316217237607903,0.00841976197988188,0.008531860713308687,0.008647796754845635,0.008762486563770731,0.00887098045054934,0.008968963910910302,0.00905310185408785,0.009121263025118941,0.009172783323868316,0.009208522067744166,0.009230628885917268,0.009242379566192473,0.009248042109914008,0.009252538408788502,0.00926043030228903,0.009274940087719653,0.009298236206666291,0.009331977550598027,0.009376950540802977,0.009432775463899477,0.009498134880290728,0.009570857955295232,0.009648045838914394,0.00972699023589042,0.009806176348639476,0.009885182430028732,0.009964233792623097,0.010043901581890027,0.01012499698205443,0.010208671360384796,0.01029595538639093,0.010387506621909619,0.010484280073844526,0.010587897684731009,0.010699894037504818,0.010819770202970303,0.010943151191069856,0.011061794522274515,0.011168093533825703,0.011260378389125964,0.011343355320292459,0.011422869629182406,0.011492329923126963,0.011522484712669673 +5.95e-07,4.244199508813605,3.052430243910399,2.0793082946124253,1.5953272869391888,1.3477371687853217,1.2081839288594893,1.1201114888778338,1.0594558772453233,1.0111659574172636,0.9690059521328572,0.9298679227637581,0.892248610310602,0.8551525916518428,0.8183228401458904,0.7816489685396494,0.7453455599599222,0.709546041336997,0.6744607571863384,0.6402702389236198,0.6069431237525584,0.5747357908706452,0.5437685412072953,0.5140196393681181,0.48550598384839605,0.45836602090657974,0.43252712853109454,0.40796943619511505,0.38466901414320814,0.36260176375951714,0.34173677730771757,0.32201183254610477,0.3033773898343434,0.2858192325507632,0.2692317358822722,0.25358598800371585,0.23885679919460828,0.22497819515497913,0.21192398540244545,0.19963979809986843,0.18806764769098605,0.17719178333706628,0.1669774497718569,0.1573972731744223,0.1484170900975225,0.13998310382856335,0.13205679924951735,0.12460763645151116,0.11762647398692606,0.11108081178935897,0.10492325030471557,0.09913573351350921,0.0937055933629023,0.08859594844471609,0.08379763238708098,0.07929587086515826,0.07505762804284759,0.0710698998828188,0.06733337779870292,0.06381943929889036,0.06051623693281147,0.05742643712439308,0.054533012138871316,0.051816490395185794,0.04927193328303629,0.046891855089073266,0.04466538733107846,0.04258531779684184,0.040630732873426306,0.03879812003689284,0.03707686549234056,0.03545697426954306,0.03392863851335008,0.03248632730204343,0.03112030809032893,0.029822542501096762,0.028587633590069016,0.02741248634933333,0.026293775215401785,0.02522677215737265,0.0242106700333031,0.023242183567453185,0.022319108735577953,0.02144231271702822,0.020609407544705857,0.019820403384805046,0.019075337520609116,0.018372981506784156,0.01771036702829147,0.017086337513761343,0.016498425557073156,0.01594441352819479,0.015422690904854122,0.014930665326725401,0.014465961463077262,0.014025977361912701,0.013608953569293645,0.01321301017480469,0.012836824403074347,0.012479592397552423,0.012140669308374121,0.011819889568584367,0.011517302920655278,0.011233115639222272,0.010967540982726572,0.010720767381451399,0.010493104397545073,0.010284406740930075,0.010094023422908861,0.00992115858508881,0.009764795670261383,0.009623710573779101,0.009496267602476313,0.009380622698057827,0.009274804114732447,0.00917710667816283,0.009086014737300242,0.009000105264305449,0.008918492494850876,0.008840450034804456,0.008765543059360451,0.008693894653717652,0.008625975346548714,0.008562473066576719,0.008504289755937037,0.00845238690898727,0.00840776880306023,0.00837129846032833,0.008343583200739125,0.008324982899681279,0.008315609499142073,0.008315462045208727,0.008324254091569346,0.008341361902870839,0.008366172093240259,0.008397983306832184,0.008436061453126342,0.008479742474139462,0.008528420876737074,0.00858161713499933,0.008639115632913027,0.00870095034028699,0.008767177560196997,0.008837871520572183,0.008913252261732165,0.00899338982804178,0.009078229574963776,0.00916762464439966,0.009260839770151482,0.009356461014150665,0.009453033558943938,0.009549292773125563,0.00964375702571943,0.009734983808394007,0.009821817268266817,0.009902883697717756,0.009976602871459378,0.01004201170040154,0.010099275981962632,0.01014947021893278,0.010194498314590872,0.010236602300382525,0.010278082200899803,0.010322015400721224,0.010371530391729654,0.010428450566187492,0.010493914810488808,0.010568508286179945,0.010651940195966698,0.010743451544761735,0.010842285528050817,0.010947861305089956,0.011059412457425402,0.011174681298380916,0.011288529926704502,0.011394068424134112,0.011485585605568279,0.011563150784559545,0.01163373186594863,0.011704832305359514,0.011769824053678143,0.011798601387164528 +6.285714285714286e-07,3.5859649449452062,2.940113069083032,2.13414477325331,1.6588531168863079,1.3970646177497044,1.2433629246688118,1.1432357808600913,1.0687890223129495,1.0081982795644424,0.9557515658183962,0.9080733331623938,0.8636367870250775,0.8218435779746309,0.7819248220349188,0.7438373336840111,0.7074006930880334,0.672527152967651,0.6391147219365342,0.6069560868898154,0.5760182647800892,0.5461818262529184,0.5173117931258039,0.48933552451857876,0.4624164964590848,0.43660423792282427,0.4119627663538539,0.38853342450007083,0.3663886835079229,0.3455421359168605,0.32601074931448387,0.30783662876561874,0.29094840767438324,0.27524090206375357,0.26066592241537667,0.24710760605238857,0.2344383623963261,0.2225534482678564,0.21135124772206734,0.20074181069598795,0.19063360915533123,0.18097061480889204,0.17170922853727727,0.16283923791472904,0.15437519720780726,0.1462900485627965,0.1385584870380204,0.13121736858923758,0.12426042609243663,0.11769649557405612,0.11150521357665359,0.10567117392754836,0.10018106428544764,0.09502550093359771,0.09017361659988785,0.08560274179673932,0.08127228319261565,0.0771554095340244,0.07324319067328867,0.06952567197046239,0.06597991949222182,0.06260847247639142,0.059402295510637866,0.056359222781494044,0.05347634853972358,0.05075685873257114,0.048210776191758196,0.045834065338710865,0.0436141425765359,0.041544598440900346,0.03961806399276639,0.03782422556950591,0.03614252149680388,0.034567337125351115,0.03308139982314848,0.03166952608410003,0.030326167935669848,0.029044898367951814,0.027822191654550094,0.026657371354651512,0.025551171037393183,0.024507358529878513,0.02352606756360941,0.0226090332327299,0.021753759450086063,0.020957024809599604,0.020213893557194055,0.019516663066421427,0.018858737925762006,0.018233102594792123,0.017632349080733183,0.01705217034538124,0.016490790805608755,0.015948260513305328,0.015425945642234958,0.014926657027127942,0.0144540563449805,0.014011509602203416,0.013601424002506048,0.013224575491984114,0.012880115197721717,0.012565491261115506,0.012276537188603717,0.012008262789738574,0.011755790360126256,0.01151481875126816,0.011282460452967512,0.01105720825641965,0.010838928967718918,0.010628762272022791,0.010428680843255015,0.010241061284979494,0.010067883988250894,0.009910264477258646,0.009768130205499699,0.00964009650342107,0.00952384104499456,0.00941639389497518,0.009314690689188537,0.009216100359677638,0.00911908306174589,0.009023501535212166,0.008930481647029037,0.008842248280909654,0.00876176941306558,0.008692108446831305,0.008635768968719372,0.008594387768435748,0.008568468908163337,0.008557058728405587,0.00855821040230956,0.00856961294366569,0.008589320904670747,0.008616399964287303,0.00865111700103912,0.008695027952992597,0.00875101324095412,0.008823024254541222,0.008915315300388428,0.00903130764261579,0.009172495308624363,0.00933769425758821,0.009522638848426477,0.00971974877011873,0.00991843544375396,0.010106257237481042,0.010270994358196813,0.010402690456589573,0.010494770842572151,0.010544626063743425,0.01055449137904461,0.01053192736842839,0.01048919874505285,0.010441565826617816,0.010405170047813847,0.010395059250966563,0.010423240197695454,0.010497016695665232,0.010617627772962338,0.010779744035458177,0.01097219805907244,0.011179500508961505,0.011383797956740186,0.01156767961320628,0.011716597083056697,0.011820442209504204,0.011874594195429503,0.011880348882160156,0.011844908814463399,0.011781214440991075,0.011706387778946378,0.011638902443051906,0.01159567478994958,0.011587993117138878,0.011619173137966676,0.011685507466920035,0.011778086259355182,0.011886358771623542,0.012003149444709588,0.01212212763960054,0.012223017669904067,0.012264982576471331 +6.621428571428571e-07,3.1188542665291883,2.6797180981977182,1.9462604297090076,1.47988192965098,1.2080941173148723,1.0512900265785883,0.9577587780372514,0.8960444787430355,0.852007547189396,0.8159140295734967,0.7853655782766878,0.7579039633367195,0.7315856755697118,0.7059436190026697,0.6808110363246591,0.6558105929444304,0.6308953310617464,0.6060910477404029,0.5814536729200805,0.5570554990105433,0.5328880654364986,0.5091051265732883,0.48583927116192205,0.4631608127558374,0.44107592039119076,0.4196706359659284,0.39898040462957196,0.3790208638992423,0.35985769690535047,0.34151583655483414,0.3239417422537683,0.30713918871985085,0.29111878575917993,0.27589168713796397,0.2614322733718979,0.24770850413128379,0.23469417746470142,0.22235786270100918,0.2106672492357217,0.1996057888804834,0.189136465465639,0.17923766346786046,0.16988408897010124,0.16103978495347096,0.15266755824177475,0.1447357049539605,0.1372424041212386,0.13016513033548338,0.12347286000380818,0.11714504513940449,0.11116266379119479,0.10550231526045317,0.10014013404474142,0.09506581164983184,0.09027126238799928,0.08574150595394091,0.08145930344107626,0.07740547745276494,0.07356928498833601,0.06993526330917944,0.06649687265026166,0.06324951045404753,0.06017848977801415,0.057275646712593116,0.054523402190625334,0.051919034798245486,0.04945632606275971,0.04713034666198451,0.04493143323897021,0.042846554604206784,0.04087724097420149,0.03901564580467887,0.03725467120495302,0.0355860092165636,0.034007621261068766,0.03251857647946232,0.031112377919830958,0.02978383657593407,0.02852784189297068,0.027340997959461585,0.02622045008721772,0.025164466518639118,0.024169840951458087,0.023232288664765947,0.0223481934272249,0.021515648903965345,0.0207325957056229,0.01999552425110927,0.01930155540783035,0.018647978069144974,0.01803234495491017,0.017453034007335288,0.01690793961407741,0.016394113800154667,0.015908949726120388,0.015450463221698312,0.015016808473663225,0.014605964776224261,0.014216301202927575,0.013846666102994508,0.013495551654256803,0.013161637316827355,0.01284418864794764,0.01254243409796627,0.01225564943660902,0.011983414307158937,0.011725495406860123,0.01148163256326589,0.011251680591405003,0.011035692783201613,0.010833621303167765,0.010645373911321334,0.010470856553668063,0.010309959303462797,0.010162620426000094,0.010028534201352404,0.009907249133512973,0.009798414761046087,0.009701577865606183,0.009616265923169767,0.009541978659590436,0.009477979126967535,0.009423557689973844,0.009378104801022129,0.00934096061157088,0.00931160138410722,0.009289592461616541,0.009274364863557179,0.009265428626330542,0.009262507469251797,0.009265336231008057,0.009273665864475514,0.009287312905134617,0.009306073402806002,0.009329778272202738,0.009358282560989035,0.009391372676581794,0.009428872738895894,0.009470647000008525,0.009516476152602675,0.00956614126930082,0.009619353449051677,0.009675458242058996,0.009733665096569701,0.009793558391828067,0.00985489417065208,0.009917266524990448,0.009980500838951538,0.01004473881940127,0.01010976989055174,0.010175010381296132,0.010240285746991628,0.010306030246928301,0.01037282828367122,0.010441139325035667,0.010511029504136526,0.01058250109622099,0.010656090464950313,0.010732769929018992,0.010813653303534359,0.010900091361035579,0.010993580850148883,0.011095073335945662,0.011204614013610456,0.01132170933356724,0.01144482318123227,0.011570710194077627,0.011694753667961624,0.011812654864268735,0.011923041042166096,0.012026632830372627,0.012123274971177374,0.01221255182739785,0.012294072912451054,0.012365126370405715,0.012423502547671819,0.012472744220581972,0.012524034884135258,0.012587889552989985,0.012653292960952514,0.012683052379020442 +6.957142857142857e-07,3.6627414400175455,2.8581253876755133,1.9904751406918055,1.548492947392213,1.2709111379829365,1.1148282738503748,1.0160478149186314,0.948342949035841,0.897876041290045,0.8579802249537092,0.8227574271360852,0.7899329336360712,0.7588921706292392,0.7289919210360646,0.6998924553375453,0.6714086525148999,0.6434955964680192,0.6160500366653702,0.5891888148198171,0.562970340724929,0.5374764053208319,0.512732593958928,0.4887710088382556,0.46557994319285767,0.4432052915270098,0.4216013732133153,0.40073951876483155,0.3805943889776015,0.36122746078243184,0.3425932446319171,0.32469463605991145,0.30754540148572634,0.2911105202309321,0.27542323666833235,0.2604389483660071,0.24616817061201188,0.23261703854812096,0.21974928096091875,0.20756607463204155,0.19607812504356212,0.1852805390035138,0.17510931529318322,0.1655414090411874,0.15656633292474104,0.14813385258254352,0.1402357201172139,0.132823801652217,0.1258457688033963,0.11928811863679524,0.1131175962274454,0.10728854149499643,0.10176997554264418,0.09655307729342719,0.09161288001836758,0.08692992901392392,0.08248927280706476,0.07827830326130376,0.07428524910934126,0.07050621625312731,0.06692644344248018,0.06355362352981077,0.06037746411278381,0.057388510656054596,0.05457748613679506,0.051937391727100726,0.0494566233977023,0.047131923222319905,0.04495237189714136,0.04290626095953857,0.04098247664770514,0.03917098968933842,0.037461743941542905,0.03584742509833499,0.03431784028698697,0.03286724162136502,0.03149077681436855,0.030179872313713604,0.028932743279789314,0.027746426098037062,0.026619848725181954,0.025550921696694755,0.024538666933997456,0.02358235316332345,0.022681773133661924,0.021834697465083038,0.02103867392777928,0.02029144314729608,0.01959079669569153,0.018934687581292017,0.018318504408167992,0.017738563144100158,0.01719145730732725,0.01667425565639555,0.01618419247927537,0.015718975362288456,0.015277299992455312,0.014856650108441248,0.014455682996798487,0.014073381732314814,0.013709539369449134,0.013364415460371157,0.013038357649033291,0.012731943536483445,0.012445410649802936,0.012178658826858927,0.011931167802134207,0.01170222994902034,0.011491003979274746,0.011296307609783476,0.01111688155258881,0.010951205777356048,0.010797608509610795,0.010654530614774758,0.010520618892031614,0.010394712812575414,0.010275932712412556,0.010163937061685942,0.010058745108879967,0.009960461501838061,0.009869609389929443,0.009787091115013673,0.009713678294851725,0.009649974554833936,0.009596456674281353,0.009553533114574556,0.009521359112021881,0.009499737362107834,0.009488050525116871,0.00948526923103167,0.009490039345695022,0.009500955992243718,0.009516449016673532,0.009534973510599107,0.009554996972835454,0.00957529391346648,0.009595120598116586,0.009614346814629953,0.009633441654511217,0.009653329294970396,0.009675183699768632,0.009700270864959943,0.009729914019110032,0.009765670160559249,0.009809304218445253,0.00986250046938343,0.00992622209584894,0.010000498026214047,0.010085358871501316,0.010180434456628714,0.010284521475905676,0.010396278913398513,0.010513778929344042,0.010634222107541254,0.010754772750648522,0.010873243883914153,0.01098782681042245,0.011096706105620803,0.011198673007143079,0.011293467034361869,0.011381680321809645,0.011464592283177852,0.011543576793243315,0.011620043918040616,0.011695896260059725,0.011773396164274117,0.011853923124285824,0.011936892586165093,0.01202022529297081,0.012101926466539206,0.012181618466890122,0.012261156292234506,0.012343241873401333,0.012427152241012396,0.01250745766588087,0.012579462869525366,0.012643035983997818,0.012702093233577088,0.012764730584207813,0.012838732210714762,0.012911006209465774,0.01294307538563725 +7.292857142857143e-07,2.944687706280265,2.6355421706815427,2.0306840821545937,1.5673950683396263,1.2982532079273885,1.1322358356211746,1.025637566169235,0.9541076089050861,0.9022332741968084,0.8603480299325905,0.8240408009591705,0.7915064146794749,0.760956085269048,0.7312379709183595,0.7018937030552121,0.6730158647336428,0.6446288714149244,0.6166587179313052,0.5889338022489207,0.5616377495353598,0.5350515747176581,0.5092145367587649,0.48417077454930463,0.4599422663265728,0.43656713831943583,0.4141681805046105,0.3928071336697145,0.37244654358611323,0.35308352723961595,0.3347177636876861,0.31729959383897144,0.30079696696303737,0.2851748374357463,0.2703976310944669,0.2564347355996686,0.24323866150403575,0.23077259438780828,0.21895371588629087,0.20771851578234496,0.19707385753033543,0.18697243429366137,0.1773484130923765,0.16817366424154187,0.1594390454927829,0.15111750870544916,0.14315758552928542,0.13557017020582446,0.12835982210534888,0.12148032672191901,0.11492028867461529,0.10867355789593214,0.10274563220395064,0.09714004477106854,0.09183222003674928,0.086819878565358,0.08208638744381438,0.07762196762673142,0.07343676829669944,0.0695081754496448,0.06583017188039933,0.06240244903089021,0.05920202898440559,0.056216608790418336,0.053433039467096946,0.05083969083098709,0.048429276224113145,0.04618692834103113,0.04409854457277961,0.042150374993344036,0.04033014635886638,0.03862982794296307,0.0370404490501229,0.03554989891149489,0.034146210911430185,0.03282295747451327,0.031574790921904995,0.03039319835565591,0.0292715246560536,0.028203837715978605,0.027184864241414437,0.026212668144321615,0.0252826548597765,0.0243903132351451,0.02353557064924226,0.022715354111253278,0.0219272432694241,0.021172646457795076,0.020449245467163302,0.01975453731744042,0.019089469962208363,0.018454755303401578,0.017850470592879586,0.017275946509774303,0.016730604801083244,0.016214713548536486,0.015728105707048114,0.015270775975944187,0.01484295035374391,0.014444228747015281,0.014074173131272991,0.01373225844059512,0.013417713802278878,0.013129559813697612,0.0128664769525425,0.012627128859386142,0.012409914111829868,0.01221290760351141,0.012034127123909581,0.01187135087297891,0.011722316639511404,0.01158494748491037,0.011457084739782639,0.011336669955865124,0.011221977921048414,0.011111372774796464,0.01100336675300872,0.010896743350042279,0.010790589382602654,0.010684403431191897,0.010578029451063276,0.010471647040248958,0.010365802626573257,0.010261297652323646,0.010159118100948257,0.010060439015375584,0.00996667081781362,0.009879458790798481,0.009800425945537353,0.00973093076505085,0.009672241106301565,0.009625677065676977,0.00959239149856525,0.009573168938929727,0.00956839735227649,0.009578076347446755,0.009601879856941576,0.009639233081335937,0.009689283674333169,0.009750924945059263,0.00982288102089749,0.009903667540741672,0.009991508143200921,0.01008438386279133,0.010180263625348228,0.010277382264310405,0.010374232759700779,0.010469313399045208,0.010561077923878772,0.010648172257761553,0.010729597708297137,0.010804789740406894,0.010873857482671015,0.010937514362499658,0.010996625882518776,0.011052019703014336,0.011104647686752185,0.011155807091577057,0.011207257231110762,0.011261107896544827,0.011319387850388378,0.011383708741972084,0.011455376269178029,0.011535919414291473,0.011626974916306482,0.011728117238280523,0.011835701508464157,0.01194505239940024,0.012052738585053216,0.01215680829989342,0.012255780752463879,0.012348649748483485,0.012435813234340364,0.012518236998695502,0.012594475769201812,0.0126610718676877,0.01271806622105847,0.012772773308320452,0.012836528827536938,0.012914279876808033,0.0129897538138515,0.01302304937890252 +7.628571428571428e-07,3.481900136254616,2.8326716975755803,2.108574924275164,1.6299129822047733,1.3054068086249206,1.1050693730005035,0.9820182894354521,0.9007363961690249,0.8418703104836598,0.7960773955118398,0.756922880956178,0.7224526961046752,0.6910181186667711,0.6616700381198234,0.6334735763740479,0.606186042559924,0.5797102840407126,0.5539590481828667,0.5289816490409611,0.5048313806933238,0.4816715773800402,0.45959895842745285,0.43853802173740203,0.41845808513773175,0.39936471913698224,0.3812794878918106,0.3642215576404415,0.34810617332535265,0.3327867086090317,0.31827493658154565,0.30452437287932066,0.2914223492383599,0.2789249059465113,0.2670124921389611,0.2556028143474124,0.24466296973043009,0.2340900951711637,0.22379675790189,0.21382862636366803,0.20418175882010897,0.19483934640806244,0.18573682722652365,0.1768513033915411,0.1682467674367039,0.15995709832582303,0.15193556240231543,0.14421345448263692,0.13679025571731954,0.1296928980955441,0.12293403224785485,0.11650502302654325,0.11041217586822541,0.10466025217699057,0.09923764368631038,0.09414921986349652,0.08940327900920768,0.08496271959717976,0.08080976245218756,0.07693324410544992,0.07332248175348613,0.0699522966108087,0.06679813596695855,0.06383879068817298,0.06106165262023324,0.058452918490906905,0.05598501821415176,0.05363992559719933,0.05140475874011805,0.04927066065884108,0.04722489281652936,0.04525866718326764,0.04337006397547238,0.04155283270686829,0.03980602459299729,0.0381194622981567,0.0364977423355992,0.03494179295386815,0.03345564101718827,0.03203918734984529,0.030691138816347687,0.029410596642951817,0.028199755132875738,0.027059971141828714,0.025991544436366247,0.02499289781322016,0.024058296329052972,0.0231856657219078,0.022370534866152588,0.021609254303916976,0.020899135409640023,0.020234247520667385,0.019610373992342146,0.019023389310150192,0.018467847736168393,0.0179398220764076,0.01743621752285242,0.016954540955248964,0.01649226053872331,0.016047455133155624,0.015618982040809613,0.015207067604767838,0.014812302498433374,0.014435272494995904,0.014076855627525715,0.013738048255271303,0.0134202656024844,0.013124932350000089,0.012853369804007702,0.012606413106375669,0.012384310425600863,0.012186842134289748,0.012013623055745062,0.011863933273641205,0.01173636238312691,0.01162892436011224,0.011539100939580191,0.011464168540772928,0.01140147385145948,0.011348340778907168,0.011301967543115422,0.011259623990765813,0.011219050968110716,0.011178500485581888,0.011136688105913025,0.011092605113349526,0.01104571672979567,0.010996122856494659,0.010944534493922579,0.01089213454403822,0.010840447700371927,0.010791463545782488,0.010747392458771942,0.010710583476648687,0.010683419982239732,0.010668090947809135,0.010666460851788505,0.010680086519111525,0.010710117094439251,0.010757329771460182,0.010821920563596496,0.010903279592788837,0.01100004206260266,0.01111050936442387,0.011232725208386125,0.01136405951138428,0.01150140338185745,0.011641644827827409,0.011781751697967089,0.011918752267089742,0.012050075040851831,0.012174078461637528,0.012289730129091602,0.012396137707617397,0.012492106277139382,0.012576901069528235,0.012651249951681202,0.01271703825334564,0.012776358130399516,0.012831036611641382,0.012883245195889402,0.012935547815515567,0.012990286984932337,0.013049670172562618,0.013116119778081194,0.013191362030960316,0.013274618932650917,0.013362286973939297,0.013449442792889024,0.01353124527134131,0.013604776048610705,0.013671755549486703,0.013737209535485627,0.013804142826827837,0.013870365461649534,0.013931554160916198,0.013985931833090156,0.014038969133986659,0.014105204056299081,0.0141940046269448,0.01428350237331562,0.01432305876442485 +7.964285714285715e-07,2.720138453851113,2.436457491172508,1.9246537089704614,1.521075329133387,1.2426499278132643,1.0672267853552677,0.9540300865309096,0.8788909368202876,0.8251572057752566,0.7852504012107723,0.7531977300951256,0.7258444425210102,0.7007375474430633,0.6767168264471188,0.6534530690207303,0.6307836963223357,0.6085035784544532,0.5864534928094182,0.564369721092804,0.5422674507801752,0.520299459409246,0.49864948630815475,0.47737030502688155,0.45644926761405513,0.43592010217281474,0.415827832639366,0.39627741850120424,0.3772598678546893,0.358846159115947,0.341101010151794,0.3239980576414024,0.3075351035134872,0.29170637562924306,0.2765729189518146,0.26215002723799585,0.24839623320090182,0.2352858968599651,0.22282674050426646,0.21103797452511544,0.19986924984337054,0.18929267682209827,0.17925585782386577,0.16973980464614707,0.1607597388683903,0.15229904730877433,0.14431000139733705,0.13677934104934836,0.1296919510547933,0.12300551122691947,0.11667420081025102,0.11067956483960664,0.10502446146205453,0.09970122716598917,0.09466794447035662,0.08991556753711484,0.08543782191301755,0.08119846337106132,0.07718502048391236,0.073395102708859,0.06981547502517783,0.06642243499897806,0.06321533724550978,0.0601834914329127,0.0573245212274219,0.054618117426367,0.0520563185820108,0.049636241974357245,0.04735149925727264,0.045189792398411956,0.0431448022977016,0.04121888480936516,0.03940489287233063,0.03769394087941795,0.036074737516073034,0.03454862717690979,0.033111802021954226,0.03176092609872816,0.030488872399388748,0.02928894735070341,0.02815781905950348,0.02709387889237847,0.02609362565205584,0.02515153189194522,0.02426330502117107,0.02342430930914573,0.022633143097379328,0.021888156174410717,0.021186458359605188,0.020525224839537136,0.01990101406305788,0.019310221316506376,0.018751378510478863,0.018223322940436205,0.017723840209862837,0.01725066429965882,0.016802069278021168,0.016376273185743594,0.015971979049864603,0.0155882240830922,0.01522369077455261,0.014877426817359556,0.014548452215519805,0.014235915520790286,0.013939417818954207,0.013658741177910898,0.013393138649464167,0.01314159002227923,0.012903418430218902,0.012678221099665321,0.012465675539355354,0.012265247070698024,0.01207644135013946,0.011898700825858647,0.01173157188599392,0.011574895428176378,0.011428549890688262,0.011292449328084335,0.011166417235748492,0.011050450379832337,0.010944578751456924,0.010848477978069709,0.010761797748882265,0.010684407563116673,0.010616418537479326,0.01055783230536655,0.010508364682498095,0.010467849938192155,0.01043618232653952,0.01041307760936699,0.010398184051315387,0.01039119476163837,0.010391851172352742,0.010399799368505514,0.010414559754036809,0.010435698359503438,0.010462866146254657,0.01049571224820182,0.010533903674187689,0.010577221581731747,0.010625399666580342,0.010677867564492224,0.010733902234305566,0.010792903469179563,0.010854584558889286,0.01091910451966783,0.010986703185113219,0.011057267713105943,0.011130551666370777,0.011206296566388982,0.011284093430503047,0.011363789225549618,0.011445822237930302,0.0115307883483931,0.011619360605612321,0.01171256826639787,0.011811261918233682,0.011915465154940923,0.01202463232756938,0.012137997083625892,0.012254900216519366,0.012375498190032811,0.012500868169382007,0.012632168301977054,0.012769628042924343,0.012911279005955774,0.013052804460205455,0.013189313388927449,0.013316709252577993,0.013432662821831701,0.013536722212708694,0.013628762070515471,0.013708377675943961,0.013775422016728581,0.013830486646648035,0.013876064357127106,0.013917395616893518,0.013964131025828075,0.014028581835281696,0.014114363316151084,0.01419838697629366,0.014234905085567733 +8.3e-07,3.2489778504233655,2.838121212076369,2.1388630178828167,1.6450928079263174,1.3231548349401159,1.121388677772054,0.9911938151545512,0.9015221662390239,0.8369069729760074,0.7884259868605547,0.7483305719003365,0.7135681354322405,0.6823144567927392,0.6536131021975087,0.6263046820290251,0.5998475394887395,0.5740621629534003,0.5490484762732719,0.5250053907318638,0.5017666810243221,0.47926392377671784,0.45745282541497834,0.4364242449719369,0.41623882245861715,0.39695463522274693,0.37854977210608637,0.3610057671194931,0.3442636992995824,0.32835857501469046,0.3133561489253229,0.29914195156830387,0.2855997492657337,0.27272616658427645,0.26052506014684274,0.24896975420678352,0.23788182911844535,0.22724340099573884,0.21710207464691966,0.20742534377512492,0.19813733313847307,0.1891878296147117,0.18056620944102547,0.17225321755612014,0.1642824164352264,0.15659512092741795,0.14918670198333173,0.14205686518270472,0.1352225568261698,0.12869091605623528,0.12242380631151441,0.1164125334395035,0.11070129353520736,0.10529366555895596,0.10015538227157122,0.09527545649978769,0.09065465634376911,0.08627473547698371,0.08214820927976274,0.07827578904559347,0.07463950512652265,0.07122152268544432,0.06800808030008446,0.0649814155139776,0.06213254381322234,0.0594527596974252,0.056926222339822426,0.0545393487398112,0.05227409709257207,0.050133063452024305,0.04809974176501646,0.046172313452313866,0.04433593328773111,0.042576549959673596,0.040891468127252806,0.03927666629037125,0.03773016556245675,0.03625428519195466,0.03484097896658406,0.033486309050963114,0.03218962982364456,0.03094600413624511,0.029757473387634736,0.028625511436658774,0.027545026082327253,0.026512181512243337,0.025526937944090864,0.02459068510562062,0.023701733817432652,0.022857145489457314,0.0220557288158719,0.02129408822349689,0.020571787269085377,0.01988781002864357,0.019240182249661977,0.0186271536639259,0.01804753798057397,0.01750035188769668,0.016984430790851244,0.01649907304446831,0.016043517914029335,0.015617202658874103,0.015219310384341751,0.014849262371686149,0.014506392200561229,0.0141901847610016,0.013900125013601342,0.013635234832876611,0.013394445286859683,0.013176555873918538,0.012980303271504822,0.012804561178055123,0.012647930145765382,0.012508667972872993,0.01238512090493504,0.012275453030255236,0.012177624730507573,0.012089626198939898,0.012009824277844448,0.011936911322072424,0.011869668875723714,0.011807030418293892,0.011748037214436137,0.011692117699707079,0.01163911263899821,0.011589276801513204,0.011543274010363655,0.011501745204911545,0.011465439323572453,0.011435218284788645,0.011411854020154627,0.011396328014304869,0.01138981740830181,0.011393477961116573,0.011408269961602187,0.011434769119671118,0.01147330578050554,0.01152402088044757,0.011586763208533038,0.011661166415336974,0.011746663330754486,0.011842254939622678,0.011946471352775613,0.0120577445318568,0.012174431378554068,0.012294434160901774,0.012415508785636832,0.012535886702410253,0.012654193482869436,0.012769291715279902,0.012880345407402538,0.012986395181484495,0.013086273168425169,0.013179203219377733,0.013265325787075158,0.013345845696756406,0.013422168171660436,0.013495277888598502,0.013566269669478602,0.013636829562434533,0.013709465531715839,0.013786799867707153,0.01387055067517296,0.013961215692872534,0.014057921325972767,0.014158779647954988,0.014262307032931198,0.014366809674560293,0.014467918902125787,0.01455909522358813,0.014635877172340802,0.014699440905216877,0.014754745088568739,0.014805642384175238,0.014853045943131095,0.014898642035111103,0.01494722776691763,0.015006396539526973,0.015086173871194375,0.015188691247565726,0.015287680888507226,0.015330671838138903 diff --git a/plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_aerosols_scattering.csv b/plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_aerosols_scattering.csv index deacf7529..8a4125cb9 100644 --- a/plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_aerosols_scattering.csv +++ b/plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_aerosols_scattering.csv @@ -1,16 +1,16 @@ lambda,beta_sca -3.6e-07,4.1872074946473325e-05 -3.935714285714286e-07,4.112551650266747e-05 -4.271428571428571e-07,3.742227361824723e-05 -4.607142857142857e-07,3.59563358716743e-05 -4.942857142857143e-07,2.990776494308206e-05 -5.278571428571428e-07,2.9162298294843853e-05 -5.614285714285714e-07,2.6295963428340563e-05 -5.95e-07,2.5420160029298024e-05 -6.285714285714286e-07,2.2722266850076822e-05 -6.621428571428571e-07,2.2538382488586153e-05 -6.957142857142857e-07,2.1330475224475745e-05 -7.292857142857143e-07,1.9269461697534365e-05 -7.628571428571428e-07,1.7259825928323254e-05 -7.964285714285715e-07,1.665520427147717e-05 -8.3e-07,1.4468751449133464e-05 +3.6e-07,4.31500039893444e-05 +3.935714285714286e-07,4.036097612995268e-05 +4.271428571428571e-07,3.761550030756117e-05 +4.607142857142857e-07,3.580372510564653e-05 +4.942857142857143e-07,3.223980372726434e-05 +5.278571428571428e-07,3.055280202266099e-05 +5.614285714285714e-07,2.6675197035399513e-05 +5.95e-07,2.5705758175485124e-05 +6.285714285714286e-07,2.3875540277441757e-05 +6.621428571428571e-07,2.0625556273746728e-05 +6.957142857142857e-07,2.1049417242077675e-05 +7.292857142857143e-07,1.8926177892778238e-05 +7.628571428571428e-07,1.6057358499711568e-05 +7.964285714285715e-07,1.6893662855551236e-05 +8.3e-07,1.4361412539379753e-05 diff --git a/plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_ozone_absorption.csv b/plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_ozone_absorption.csv index 1db98026b..43ec77042 100644 --- a/plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_ozone_absorption.csv +++ b/plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_ozone_absorption.csv @@ -1,16 +1,16 @@ lambda,beta_abs -3.6e-07,6.34132e-09 -3.935714285714286e-07,5.223297685714303e-09 -4.271428571428571e-07,4.0915332857142814e-08 -4.607142857142857e-07,2.019894671428572e-07 -4.942857142857143e-07,6.177336228571443e-07 -5.278571428571428e-07,1.4096001999999993e-06 -5.614285714285714e-07,2.3237175999999998e-06 -5.95e-07,2.6117640000000004e-06 -6.285714285714286e-07,1.7680460000000005e-06 -6.621428571428571e-07,9.516202428571441e-07 -6.957142857142857e-07,4.459575514285708e-07 -7.292857142857143e-07,2.2668683571428574e-07 -7.628571428571428e-07,1.3709074e-07 -7.964285714285715e-07,8.178076428571426e-08 -8.3e-07,3.818227e-08 +3.6e-07,4.72024e-09 +3.935714285714286e-07,3.88768885714287e-09 +4.271428571428571e-07,3.045405714285711e-08 +4.607142857142857e-07,1.502439428571429e-07 +4.942857142857143e-07,4.6066228571428677e-07 +5.278571428571428e-07,1.0516622857142852e-06 +5.614285714285714e-07,1.734111428571429e-06 +5.95e-07,1.9493200000000007e-06 +6.285714285714286e-07,1.3193960000000003e-06 +6.621428571428571e-07,7.099640000000009e-07 +6.957142857142857e-07,3.323633142857138e-07 +7.292857142857143e-07,1.686662285714286e-07 +7.628571428571428e-07,1.0178417142857143e-07 +7.964285714285715e-07,6.04796857142857e-08 +8.3e-07,2.7946880000000003e-08 diff --git a/plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_aerosols_cinematic_absorption.csv b/plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_aerosols_cinematic_absorption.csv index 18cb2eddc..3027a9772 100644 --- a/plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_aerosols_cinematic_absorption.csv +++ b/plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_aerosols_cinematic_absorption.csv @@ -1,4 +1,4 @@ lambda,beta_abs -4.4e-07,1.0698977873005721e-05 -5.5e-07,6.060108140257827e-06 -6.8e-07,1.3553515911733974e-06 +4.4e-07,1.0575338611934134e-05 +5.5e-07,6.194913573437476e-06 +6.8e-07,1.434500173855293e-06 diff --git a/plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_aerosols_cinematic_phase.csv b/plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_aerosols_cinematic_phase.csv index 8e2b1660f..f6d063452 100644 --- a/plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_aerosols_cinematic_phase.csv +++ b/plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_aerosols_cinematic_phase.csv @@ -1,4 +1,4 @@ lambda,0.0,1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0,13.0,14.0,15.0,16.0,17.0,18.0,19.0,20.0,21.0,22.0,23.0,24.0,25.0,26.0,27.0,28.0,29.0,30.0,31.0,32.0,33.0,34.0,35.0,36.0,37.0,38.0,39.0,40.0,41.0,42.0,43.0,44.0,45.0,46.0,47.0,48.0,49.0,50.0,51.0,52.0,53.0,54.0,55.0,56.0,57.0,58.0,59.0,60.0,61.0,62.0,63.0,64.0,65.0,66.0,67.0,68.0,69.0,70.0,71.0,72.0,73.0,74.0,75.0,76.0,77.0,78.0,79.0,80.0,81.0,82.0,83.0,84.0,85.0,86.0,87.0,88.0,89.0,90.0,91.0,92.0,93.0,94.0,95.0,96.0,97.0,98.0,99.0,100.0,101.0,102.0,103.0,104.0,105.0,106.0,107.0,108.0,109.0,110.0,111.0,112.0,113.0,114.0,115.0,116.0,117.0,118.0,119.0,120.0,121.0,122.0,123.0,124.0,125.0,126.0,127.0,128.0,129.0,130.0,131.0,132.0,133.0,134.0,135.0,136.0,137.0,138.0,139.0,140.0,141.0,142.0,143.0,144.0,145.0,146.0,147.0,148.0,149.0,150.0,151.0,152.0,153.0,154.0,155.0,156.0,157.0,158.0,159.0,160.0,161.0,162.0,163.0,164.0,165.0,166.0,167.0,168.0,169.0,170.0,171.0,172.0,173.0,174.0,175.0,176.0,177.0,178.0,179.0,180.0 -4.4e-07,6.447668411685092,5.829428952163698,4.521513476227873,3.2338301031959586,2.202758937386915,1.493478945588993,1.0345653609976029,0.7374755317066886,0.5448330960014026,0.41453060709843237,0.32543379372932724,0.26409307020517997,0.2221269585849513,0.19233678810054233,0.17068591239788905,0.15369602935750049,0.1404315344856329,0.13057624083941638,0.12343909241940107,0.11773858277671133,0.11300309313367166,0.10889461861359481,0.1051646072150923,0.10179547283936224,0.09882163890382557,0.09636529536815078,0.09445209112124948,0.09274651194841971,0.09100186009406944,0.08934537431268759,0.0878785646335671,0.08656642461969258,0.08526922974720602,0.08407753581298275,0.08302902728749498,0.08206874719525774,0.08113104144643535,0.08014837809182011,0.07920772111192366,0.07840899397091028,0.0777494364128141,0.07712391124431865,0.07643247169011193,0.07572545172678796,0.07503231773513273,0.07438233348545313,0.0738028559454048,0.07332489963179654,0.07294847071491947,0.07258150006519429,0.07218056584012662,0.0717340537401667,0.07129348696666332,0.07088850109009973,0.07052103510758277,0.07019588688139013,0.0698961161854105,0.06962029415074765,0.06934311761785035,0.06907116871195683,0.06881275877948997,0.06856956510965394,0.06834203138413608,0.06812461707911428,0.0679289537478804,0.06774342206092679,0.06756212281936395,0.06738351369218042,0.06721852406360218,0.06707491182263053,0.06693741816956834,0.0667997344081147,0.0666633290640333,0.06653904138240217,0.06642146034127006,0.06630300692364821,0.06619206280717246,0.06609415907094887,0.06600625309324361,0.06591742220228834,0.06582957599111867,0.06574853203439832,0.06567361828057583,0.06560410659010595,0.0655387298928536,0.06547810745572563,0.06541933684616173,0.06536091681981025,0.06530458908336281,0.06525137092399103,0.06520305857036271,0.06515915314739229,0.06511795857198734,0.06507767742789188,0.06503815527826724,0.06500144646258815,0.064967774452362,0.06493626536826884,0.06490635998634549,0.06487822598783925,0.06485248751053263,0.06482896167141027,0.06480714504905341,0.06478647580130498,0.06476684535756581,0.06474868785942517,0.06473209541508904,0.06471672573573391,0.0647021322253658,0.0646883804220208,0.064675985750869,0.06466539484008446,0.06465696176212833,0.06465054900650927,0.06464543592836086,0.0646409137353962,0.06463691539443389,0.06463402139911455,0.06463255704011724,0.06463238466663158,0.06463347513510559,0.06463591243899801,0.06463979270773301,0.064645099172227,0.06465152446047343,0.06465877637255468,0.06466683709088551,0.06467609591947007,0.0646874712669819,0.06470192943547043,0.0647198925353971,0.0647411411893059,0.0647648398523638,0.06478981140974176,0.06481510304772647,0.06484035579339971,0.06486606143099449,0.06489358648416581,0.06492429953794017,0.06495837256485353,0.06499468505973033,0.06503191095920978,0.06506967002365298,0.0651088562000894,0.06515067863626944,0.06519496710102923,0.06523969632755784,0.06528247514069194,0.06532213058425657,0.06535879491207415,0.06539297657823735,0.06542470542889901,0.06545377257384363,0.06548095299121275,0.0655083143691026,0.06553790626192903,0.06557072015400833,0.0656069018017578,0.06564651273159504,0.0656906267555566,0.06574219820846931,0.06580549579570315,0.06588402471095355,0.06597810016287055,0.06608364831893707,0.0661930747147751,0.06629589800691005,0.06637735179490103,0.06641818963345669,0.06639969559700937,0.06631252875023995,0.06616447460240182,0.06598133384738862,0.06579971973092302,0.06565830609244609,0.06559338676779954,0.06563403444268902,0.06578765795856635,0.06601647815764729,0.06622838603919466,0.06631545485797237 -5.5e-07,3.658970296859405,3.4194319801090494,2.828252971090292,2.1541194473327163,1.5967827619755932,1.1980012687450516,0.911674153834906,0.6977098000406632,0.5420099337365049,0.4332763453376916,0.35546737514832827,0.2972081018551824,0.2540277395620881,0.22269377846705746,0.19930676131208858,0.18113950979729226,0.1667394883450672,0.15496085400202825,0.14516270674380605,0.1373131247392903,0.1311679534110556,0.12601752980580855,0.12138199297401825,0.1172857801659237,0.11376868346903432,0.11060440361325571,0.1075492375112749,0.10456862942213199,0.10177617479099964,0.0992879295722836,0.09713875061015226,0.09526996598735038,0.09358349765193603,0.09200538109634537,0.09049671355938324,0.0890422931735382,0.0876530007548242,0.08634753695105067,0.08511382333495532,0.08391693402449726,0.0827509120294678,0.08164942994074285,0.08064265819347391,0.07973409882904939,0.07891191298729679,0.07814617438108871,0.07738631972038248,0.07660384892777296,0.07582967238037988,0.07511402186663342,0.07446814174882335,0.07388118844396566,0.07335991460728443,0.07289976427060216,0.07245183261022164,0.0719811409532741,0.07152825991893765,0.07114897794432677,0.07082211791445475,0.07048549345086963,0.07013563046811377,0.06981689103476955,0.06953087928738061,0.06923451963888419,0.06892293731000429,0.06864280552727801,0.06841689962390644,0.06821404374220574,0.06800433794849461,0.06779555637472585,0.06760191992478247,0.06741714540507418,0.06723460097314415,0.06706442249518117,0.06691542497950745,0.06678105897778981,0.06665395600874839,0.06653637656615843,0.06642788158887283,0.06631939198660175,0.06620790927082816,0.06610312995163249,0.0660120200993168,0.06592870436320677,0.06584583482455594,0.06576527937743669,0.0656914548094213,0.065622769419162,0.06555558630187013,0.06549050722869056,0.06542972520121984,0.06537243921066951,0.06531715705658997,0.06526566333784653,0.06522089717529317,0.06518248731896925,0.06514721830530114,0.06511247459359683,0.06507713387005118,0.06504121440884697,0.06500674540579172,0.0649765922997021,0.06495087190297529,0.06492723753996764,0.0649051257606556,0.06488616501857956,0.06487058403442889,0.06485693232511253,0.06484447147638885,0.06483263180657078,0.06482000429817752,0.06480658670126453,0.064794579437921,0.06478518034263635,0.06477740122995755,0.06477080167911903,0.06476640355757596,0.06476478956851768,0.0647657629613991,0.06476935521278933,0.06477556163806113,0.06478422030904399,0.06479591370714904,0.06481152109615651,0.06483091865709247,0.06485360297224808,0.06487969314885376,0.06490869101257457,0.06493851455007775,0.06496737664277409,0.06499573559326885,0.06502585905281882,0.06506085615616533,0.06510414060763903,0.06515771590581496,0.06522014280925825,0.06528715177512871,0.06535455334395962,0.06542048017737895,0.06548596226045106,0.0655543228613134,0.0656291546144069,0.06571173589596563,0.0658004052788675,0.06589205477381992,0.06598302975579858,0.06606918020516547,0.06614759834716115,0.06621881452029224,0.0662851778330405,0.066347125367076,0.06640347515257988,0.06645453641569037,0.06650177690309796,0.06654589357254181,0.0665885237341486,0.06663340754911012,0.06668343804466638,0.06674139739531404,0.06681501427652987,0.06691313745359433,0.0670356410200659,0.0671763045836515,0.06733344579617151,0.06750573067538071,0.06768169674868442,0.06784348143275203,0.06796989834353231,0.0680280256890355,0.0679833852358038,0.06782995238092196,0.06759138694316284,0.06729831841953278,0.06699808000346683,0.06677312115087765,0.0667027556550754,0.06681536129512446,0.06710128811996538,0.06751489407064305,0.06791465910796202,0.06808497050884636 -6.8e-07,2.0564109121223515,1.9686438429374733,1.740887656965721,1.4541124430719778,1.1822966274211333,0.9592698972864094,0.7838955755136711,0.6440037395409299,0.5316199239736545,0.44299639161833426,0.37426811724492837,0.32094325984647337,0.27956911420417785,0.24786052652192936,0.2236676147101022,0.20475107131090892,0.18934507324802008,0.17638961701247788,0.1652895971447315,0.15572502668422844,0.1475657727697946,0.14067258059923132,0.13473598687073407,0.12940206699154144,0.12451086890747359,0.12009804594016972,0.11617997186448259,0.11264827167658178,0.10939973101648226,0.10646864539388336,0.10393712404216032,0.10173991340646832,0.09965547034902536,0.0975219209182322,0.09538808757856043,0.09339409985656894,0.0915651270181231,0.08981388916314904,0.08811488995116673,0.08655936874469111,0.08521158521503595,0.08400362723566017,0.08282708680967552,0.08166723622345251,0.08058640694790167,0.07961589747554548,0.0787280983695854,0.0778955719423686,0.07711744790814958,0.07638786357166208,0.07568590253593095,0.0750060015879652,0.07436473470779761,0.07376914860361974,0.07320667964313599,0.07267437555655488,0.0721897882040873,0.07175939429945563,0.07136054975989317,0.07097202162569331,0.07060154986039888,0.07026200549703539,0.06993864204985976,0.06960890191298173,0.06928466536051324,0.06900133813674464,0.06876358281041173,0.06853385400033757,0.0682850973872851,0.06803669680185533,0.06782121074743852,0.06763873558800547,0.06746656538320386,0.06729734787133411,0.06714179843843074,0.06700153016388281,0.06686412707565535,0.06672526169577064,0.06659588926074729,0.06648339565111602,0.06638009142880498,0.06627583503100151,0.06617164516835698,0.06607488478793429,0.06598808805562896,0.0659085666222193,0.06583484256999365,0.06576777448247521,0.06570664936268723,0.0656480458463529,0.06558856971617456,0.06552722452597586,0.06546615399494468,0.06540996131030526,0.06536220863303975,0.06532154638963064,0.06528375439244344,0.0652478111136747,0.06521583678932068,0.06518705784038716,0.06515823426347997,0.0651309829739567,0.06511054454165092,0.06509509021585842,0.06507558532338109,0.06504990865085898,0.06502736501250195,0.06501459160132028,0.0650069733888097,0.06499930398469626,0.06499446098755322,0.06499463411521164,0.06499350604717455,0.06498519007319002,0.06497376177166264,0.06496677424334396,0.06496516829510655,0.06496582402854642,0.06496924600781882,0.06497906017962314,0.06499693587245928,0.06502169519582365,0.06505094567008445,0.06508162447473301,0.06511172405374431,0.06514256363394981,0.06517632796392615,0.06521117040897327,0.0652431384793786,0.06527426454687822,0.0653143983932972,0.06537224399787736,0.06544699414316897,0.0655305204614951,0.06561515382752163,0.06569765851476181,0.06577882697936221,0.06586217048463665,0.06595101695792797,0.06604438773807214,0.0661376797720346,0.06622971420388882,0.0663266361201428,0.06643569544366414,0.06655698876912672,0.06668427308701902,0.06681172409919536,0.06693579615603837,0.06705126698131464,0.06714971981154418,0.06722412008382926,0.06727545405366808,0.06731559693769051,0.06736137207582613,0.06742100847322048,0.06748728618576579,0.06755003953410961,0.06761332797443709,0.06768981746961275,0.06777968866956517,0.06787189629883478,0.06797025757345289,0.06809904225740548,0.06827286031450897,0.06847732599541245,0.06868701859282995,0.06888108549789107,0.06902992906832799,0.06908954046856634,0.06902479165432164,0.06882495418716496,0.06850081884232258,0.06810264695573497,0.06774225910565757,0.06754873036454997,0.06758775998918702,0.06786018588858907,0.06835784872463462,0.06902463111670641,0.0696466292440029,0.06990686302831227 +4.4e-07,6.682828096379368,5.952434300275174,4.466813314018511,3.134412485694429,2.157257096811556,1.4918634279307457,1.0461913866628894,0.7494258264381904,0.5510265501931964,0.4170900941654832,0.3289435311926457,0.268006310869151,0.22415389652844864,0.19194032775802522,0.16898561057482592,0.15236174173377282,0.1401605400565934,0.13084877067894998,0.12354251925720955,0.11739081566335843,0.11236233776539267,0.10834541298135231,0.10512878816515756,0.10232098656972849,0.099571175034197,0.09693376032404727,0.09460318602130532,0.09259533169858068,0.0908774500585596,0.08937404419830955,0.08803652287523002,0.08682039441936291,0.0855311220370454,0.0842713829388653,0.08316495116922912,0.0821717493235066,0.08120580113650085,0.08027318836069271,0.07941975524026001,0.07860355403556131,0.0778631438632756,0.07718173485303569,0.07645717934667975,0.07574751532777146,0.07511099045483742,0.07454212462960637,0.07399569294860305,0.07346466734039453,0.07295545394485416,0.07249209282747725,0.07209731842744377,0.07170487623949472,0.07131645340961147,0.07093478964732595,0.07057399487259276,0.07026195111262734,0.0699684311826872,0.06965790431912397,0.06935017499241701,0.06909656478533416,0.06886181261326037,0.06862625884956297,0.06840520608645441,0.06819625146966957,0.06799232305363305,0.06778425773562435,0.06758671880328178,0.06740532291937212,0.06724312718084868,0.06708999806912679,0.0669380882672427,0.06679605304186823,0.0666666132558013,0.0665509875135973,0.06643833466151257,0.06632706863399923,0.06621967220768604,0.06611677484969565,0.06602253729248124,0.06593361387199688,0.06584876630172203,0.06576843431781038,0.06569417424882326,0.065623244609301,0.0655534505368016,0.06548857342025284,0.0654279266850124,0.0653715836047069,0.0653194572813631,0.0652690844936388,0.06521993908181024,0.06517313866938561,0.06512958037632827,0.06508749994316441,0.06504650796402933,0.06500818162960738,0.06497356107585639,0.06494306048908406,0.06491490310081792,0.06488742129830391,0.06486096489242855,0.06483637162640822,0.06481390787822353,0.06479314365531347,0.0647737672397142,0.06475570033625296,0.06473922064935776,0.06472464209734365,0.06471151611364503,0.06469932205641632,0.06468796792137366,0.06467752002504258,0.06466798845266575,0.06465929952550072,0.06465162875689337,0.06464525819236415,0.06464027218486966,0.06463653839193034,0.06463393353494373,0.0646328709797109,0.06463393438646022,0.06463710775402069,0.06464196578043212,0.06464807192024087,0.06465527139940999,0.06466381371311203,0.06467388031933922,0.06468539465630445,0.06469839289221306,0.06471324350685717,0.06473051049339876,0.0647504241189022,0.06477261347745618,0.0647966682497972,0.06482242712295307,0.06484954139871202,0.06487750402432631,0.06490623796992463,0.06493624598103588,0.06496832565857648,0.06500329671609971,0.06504158292861703,0.06508271863954292,0.06512530329650638,0.06516750677170352,0.06520793563481166,0.06524642838531776,0.06528365315032163,0.06531972236080072,0.06535405647834673,0.06538684449177952,0.06541959812487548,0.06545379947778482,0.06548930881326119,0.06552431776106815,0.06555711327528989,0.06558817739982803,0.06562052178463874,0.06565785683308023,0.06570284937883865,0.06575745938733375,0.0658242844025508,0.06590650425907708,0.0660057318360394,0.06611905914076792,0.06623755336975036,0.06634689412808845,0.06642942945056803,0.06646720821304108,0.06644579415396812,0.06635816308958775,0.0662090486128264,0.06601927676939662,0.06582582458906476,0.06567425652481451,0.06560786569443997,0.06565645449514339,0.0658213441582815,0.0660581386307996,0.0662734069685659,0.06636123936752915 +5.5e-07,3.6835314576884226,3.450862369484291,2.875115263111729,2.207856822970148,1.6332327439334244,1.2033730854835212,0.8987476134501732,0.6868873294887817,0.5389120409599826,0.4326248501060515,0.3540334808147646,0.2954304474904566,0.25247609723773545,0.22147298124557974,0.1983394932692354,0.18015952138711433,0.16577135949320415,0.15437942385208708,0.14514594372366646,0.13764483847211173,0.13153586441264936,0.12631487340944964,0.1216551189320752,0.11747177414627734,0.11370917113838475,0.11029906783674438,0.10719648580194041,0.10436737495228876,0.10175974795942394,0.0993422540783549,0.09717150077887005,0.09526177185416201,0.09344718491758039,0.09162314162774352,0.08995495449426634,0.0885842136836309,0.08738518863165017,0.08619558707146487,0.08499339017257805,0.0837806201225541,0.08257032584203315,0.08145708192795445,0.08050481066286332,0.07966268456022234,0.07886778007268347,0.07808870509850122,0.07729128086758898,0.07649936885509621,0.07579866658989048,0.07519438833245397,0.07459912888256162,0.0739840250364596,0.07340110802689812,0.07287958766139009,0.07240437250212974,0.07195369515810746,0.07151671485190922,0.0711090274259215,0.07074782654004132,0.07040695753896721,0.07006175992324472,0.06974355664972909,0.06947405917336491,0.06921342951029325,0.06893223092430208,0.06865407994496839,0.06840035520767274,0.06816754912729425,0.06795658187676637,0.06776896584598122,0.06759208342284387,0.06741744474380078,0.06725073103080018,0.0670960937415765,0.06695054048149005,0.06681038224228941,0.06667233212033728,0.0665368509257641,0.06641073041467449,0.06629649611223205,0.06618798039081138,0.06608372194703396,0.06598996133033129,0.06590800136966989,0.06583341466852993,0.0657631066636877,0.06569315864611913,0.06561979651149721,0.06554614724540564,0.0654782982651111,0.06541759161734509,0.06536292646417909,0.0653141098116935,0.06526965365752022,0.06522658536422875,0.06518378438527338,0.06514252901923685,0.06510348097469937,0.06506603848349019,0.06503081119028596,0.06499919219139029,0.06497114747409097,0.06494610373334161,0.06492343296874965,0.06490159029662503,0.06488008528298873,0.06486016370986669,0.06484179903620081,0.06482389793042319,0.06480717835338667,0.0647934937768787,0.0647835526701913,0.06477658884656788,0.06477133738293045,0.0647672554755082,0.06476449061809318,0.06476287730563593,0.06476229512162886,0.06476344645149441,0.0647671933285932,0.06477373108997256,0.06478277226036887,0.06479423552555996,0.06480843209664625,0.06482532634809994,0.06484420254473532,0.0648643625399679,0.06488558912864714,0.06490847549968336,0.06493506037352303,0.06496810428080584,0.06500875214580351,0.06505544649304544,0.06510556537301643,0.06515764594632535,0.06521207445824982,0.06527029714874119,0.06533317433605287,0.06540022751901867,0.06547145401482121,0.06554868603162493,0.06563335654022928,0.06572388862692144,0.06581594735232771,0.06590474796822333,0.06598797206042255,0.06606639317944392,0.06614101386613154,0.06621098616984264,0.06627464947886576,0.06633145131650825,0.06638277076449388,0.06643063834139801,0.06647617436518177,0.06651982434058436,0.0665617188562484,0.06660321564857141,0.06664948482710294,0.0667070814385841,0.06678052973816406,0.06687468180539664,0.06699412200230156,0.06713804339388982,0.0673003648670952,0.06747168326580102,0.06764108670194953,0.06779882934212335,0.06792656285172603,0.0679860827363181,0.06793875978539882,0.06777838590144497,0.06753629359283445,0.06726229305568907,0.06700597446477793,0.06681142652419586,0.06672432748705252,0.0667931773678671,0.06704940529710453,0.06745788368664189,0.06786265631306397,0.06803578098627026 +6.8e-07,2.4930675519555145,2.283069772289389,1.8422127132965211,1.453798164261963,1.1756200486170467,0.9524253228772931,0.7715000105722207,0.631851597797634,0.5214236789597099,0.4349489662660422,0.3693430040516988,0.31915581177522445,0.2794560285037337,0.24777631052651522,0.22380198417108604,0.20564150423112515,0.1903121858472439,0.17678349326492127,0.16532346412507995,0.15592131179954416,0.14798260829376875,0.14112601981332523,0.13529137468461744,0.12998404315683337,0.12503621490201133,0.120785560892697,0.11698623385355804,0.11332754890860945,0.10991470470267882,0.10672751759723817,0.10389236522217696,0.1015194315334026,0.0991781312139537,0.09679895162894173,0.0947989560576864,0.09296412233196188,0.09108511643621145,0.08958897029553188,0.08830210498043359,0.08673028299228079,0.08522230003803401,0.08397136544066733,0.08264271822252799,0.08142206205174167,0.08052673440412589,0.07968503396509481,0.07880801555074772,0.07793310157750474,0.07703250163255483,0.07623332202163045,0.07556903215623416,0.074959102879019,0.07437203012075677,0.07373775150443779,0.07313461943697028,0.0726515786142256,0.07214685252789496,0.07165605076776976,0.07129511002903653,0.07092049379252886,0.07052034184579425,0.07020154035160997,0.06987428495578932,0.0695137072116264,0.06920406087108197,0.06893604702807737,0.06869671465309123,0.068478878219644,0.06824917697847543,0.06802484514475546,0.0678215988308288,0.06762013734608949,0.06742669801309964,0.06724849104008995,0.06707784945361865,0.06693126500990138,0.06681212652908239,0.06669372351178426,0.06657568002987782,0.06645853788062094,0.06633636120159425,0.06623759797257651,0.06615717659311648,0.06606455175712153,0.06597455025538156,0.06588994371747499,0.06580553417743587,0.0657364073874425,0.0656718776953795,0.06560769253087743,0.06555568199816661,0.06550589867446316,0.06545155413577448,0.06539948270454381,0.06535460135385886,0.0653134336212499,0.06527297485701797,0.06523783411878913,0.065205316529677,0.06517433099570466,0.06514706157029501,0.06512056738504164,0.06509646706611918,0.06507056357017843,0.06504430260584641,0.06502900565483173,0.06501814404679972,0.06500654724116357,0.0649985946941371,0.06499213856424871,0.06498588939602283,0.06497581739629542,0.06496263714412713,0.06495464818281739,0.0649504966626784,0.06494900975407278,0.06495245273596044,0.06495923740631313,0.06496953568963179,0.06498192040413875,0.06499579803664195,0.06501419058518566,0.06503670673138467,0.06506146107272669,0.06508664538422368,0.06511300809786275,0.06514215800955481,0.06517121486772585,0.06520490838256024,0.06525203395259764,0.06530893218038523,0.06536767518553573,0.06542573851375524,0.06548914969492904,0.06556635576860502,0.06565597153034342,0.0657470383724454,0.0658283046344314,0.06591095042784516,0.06600887085047048,0.06611175840979536,0.06623272644074152,0.0663810361465479,0.06651573061760005,0.06662513382063275,0.06672655647810444,0.0668193482377922,0.06691421487656032,0.0670092079425199,0.06708989708597583,0.06715860680934065,0.06720966366203444,0.06725311698881084,0.06730660660480448,0.06735819918740273,0.06740623312896792,0.0674542925458631,0.06750487316235002,0.06757334909907219,0.0676542142672494,0.06775524243448794,0.06789034535346433,0.06803931663116736,0.06821323339449104,0.06841691585728445,0.06861757200212645,0.06880480125172272,0.06894096999696071,0.06898362933303162,0.06892958949423453,0.06876722056759796,0.068488470958415,0.06810981667338517,0.0677450756570547,0.06753950555308456,0.06753881221441747,0.06781807396415462,0.0683689724624699,0.06901747950430825,0.06962638807278487,0.06990950223602271 diff --git a/plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_aerosols_cinematic_scattering.csv b/plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_aerosols_cinematic_scattering.csv index 4450f34b9..e0e240bd5 100644 --- a/plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_aerosols_cinematic_scattering.csv +++ b/plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_aerosols_cinematic_scattering.csv @@ -1,4 +1,4 @@ lambda,beta_sca -4.4e-07,2.0141362014554096e-05 -5.5e-07,2.5227733049205797e-05 -6.8e-07,3.117554267848886e-05 +4.4e-07,1.9932975413913606e-05 +5.5e-07,2.5609507087596744e-05 +6.8e-07,3.1364071859739784e-05 diff --git a/plugins/csp-atmospheres/scattering-table-generator/ozoneMode.cpp b/plugins/csp-atmospheres/scattering-table-generator/ozoneMode.cpp index fae4b2c0d..6c9236b13 100644 --- a/plugins/csp-atmospheres/scattering-table-generator/ozoneMode.cpp +++ b/plugins/csp-atmospheres/scattering-table-generator/ozoneMode.cpp @@ -22,7 +22,7 @@ int ozoneMode(std::vector const& arguments) { bool cPrintHelp = false; std::string cOutput = "ozone"; - double cNumberDensity = 5.374e18; + double cNumberDensity = 4e18; // See https://amt.copernicus.org/articles/14/6057/2021/ std::string cLambdas = ""; double cMinLambda = 0.36e-6; double cMaxLambda = 0.83e-6; @@ -66,16 +66,18 @@ int ozoneMode(std::vector const& arguments) { // Values from // http://www.iup.uni-bremen.de/gruppen/molspec/databases/referencespectra/o3spectra2011/index.html // for 233K, summed and averaged in each bin (e.g. the value for 360nm is the average of the - // original values for all wavelengths between 360 and 370nm). Values in m². + // original values for all wavelengths between 360 and 370nm). Values in m². The data has been + // updated as newer data became available on the website. const double minLambda = 0.36e-6; const double maxLambda = 0.83e-6; - const std::vector absorptions = {1.18e-27, 2.182e-28, 2.818e-28, 6.636e-28, 1.527e-27, - 2.763e-27, 5.52e-27, 8.451e-27, 1.582e-26, 2.316e-26, 3.669e-26, 4.924e-26, 7.752e-26, - 9.016e-26, 1.48e-25, 1.602e-25, 2.139e-25, 2.755e-25, 3.091e-25, 3.5e-25, 4.266e-25, - 4.672e-25, 4.398e-25, 4.701e-25, 5.019e-25, 4.305e-25, 3.74e-25, 3.215e-25, 2.662e-25, - 2.238e-25, 1.852e-25, 1.473e-25, 1.209e-25, 9.423e-26, 7.455e-26, 6.566e-26, 5.105e-26, - 4.15e-26, 4.228e-26, 3.237e-26, 2.451e-26, 2.801e-26, 2.534e-26, 1.624e-26, 1.465e-26, - 2.078e-26, 1.383e-26, 7.105e-27}; + const std::vector absorptions = {1.18006e-27, 2.18205e-28, 2.81764e-28, 6.63629e-28, + 1.52685e-27, 2.76259e-27, 5.51975e-27, 8.45102e-27, 1.58232e-26, 2.31555e-26, 3.66625e-26, + 4.92413e-26, 7.76088e-26, 9.02900e-26, 1.48333e-25, 1.60547e-25, 2.14349e-25, 2.76161e-25, + 3.09823e-25, 3.50934e-25, 4.27703e-25, 4.68477e-25, 4.40965e-25, 4.71385e-25, 5.03275e-25, + 4.31623e-25, 3.74999e-25, 3.22324e-25, 2.66819e-25, 2.24367e-25, 1.85651e-25, 1.47571e-25, + 1.21105e-25, 9.43730e-26, 7.46292e-26, 6.57117e-26, 5.10619e-26, 4.14823e-26, 4.22622e-26, + 3.23257e-26, 2.44425e-26, 2.79549e-26, 2.52744e-26, 1.61447e-26, 1.45506e-26, 2.07028e-26, + 1.37295e-26, 6.98672e-27}; // Open the output file and write the CSV header. std::ofstream output(cOutput + "_absorption.csv"); diff --git a/plugins/csp-atmospheres/shaders/atmosphere-models/bruneton/common.glsl b/plugins/csp-atmospheres/shaders/atmosphere-models/bruneton/common.glsl index 81ef78f34..6283eae7a 100644 --- a/plugins/csp-atmospheres/shaders/atmosphere-models/bruneton/common.glsl +++ b/plugins/csp-atmospheres/shaders/atmosphere-models/bruneton/common.glsl @@ -53,6 +53,14 @@ bool rayIntersectsGround(float r, float mu) { return mu < 0.0 && r * r * (mu * mu - 1.0) + BOTTOM_RADIUS * BOTTOM_RADIUS >= 0.0; } +float distanceToNearestAtmosphereBoundary(float r, float mu, bool rayRMuIntersectsGround) { + if (rayRMuIntersectsGround) { + return distanceToBottomAtmosphereBoundary(r, mu); + } else { + return distanceToTopAtmosphereBoundary(r, mu); + } +} + // Transmittance Texture Precomputation ------------------------------------------------------------ // The code below is used to store the precomputed transmittance values in a 2D lookup table. diff --git a/plugins/csp-atmospheres/shaders/atmosphere-models/bruneton/model.glsl b/plugins/csp-atmospheres/shaders/atmosphere-models/bruneton/model.glsl index b5d372410..c22783cd6 100644 --- a/plugins/csp-atmospheres/shaders/atmosphere-models/bruneton/model.glsl +++ b/plugins/csp-atmospheres/shaders/atmosphere-models/bruneton/model.glsl @@ -26,6 +26,10 @@ uniform sampler3D uMultipleScatteringTexture; uniform sampler3D uSingleAerosolsScatteringTexture; uniform sampler2D uIrradianceTexture; +#if USE_REFRACTION +uniform sampler2D uThetaDeviationTexture; +#endif + vec3 moleculePhaseFunction(float nu) { float theta = acos(nu) / PI; // 0<->1 return texture2D(uPhaseTexture, vec2(theta, 0.0)).rgb; @@ -147,7 +151,12 @@ vec3 getSkyRadianceToPoint(sampler2D transmittanceTexture, sampler3D multipleSca float d = length(point - camera); bool rayRMuIntersectsGround = rayIntersectsGround(r, mu); - transmittance = getTransmittance(transmittanceTexture, r, mu, d, rayRMuIntersectsGround); + // We deliberately use mu < 0 for the last parameter here. This way, we compute the transmittance + // to the point using the ray segment which passes farthest away from the ground. If we used + // rayRMuIntersectsGround instead, the transmittance for rays passing very close above the horizon + // would compute the transmittance based on two values with a very small transmittance, which + // would result in precision errors, especially if refraction is enabled. + transmittance = getTransmittance(transmittanceTexture, r, mu, d, mu < 0); vec3 multipleScattering; vec3 singleAerosolsScattering; @@ -209,6 +218,44 @@ vec3 getSunAndSkyIrradiance(sampler2D transmittanceTexture, sampler2D irradiance return SOLAR_ILLUMINANCE * getTransmittanceToSun(transmittanceTexture, r, muS); } +// Rodrigues' rotation formula +vec3 rotateVector2(vec3 v, vec3 a, float sinMu) { + float cosMu = sqrt(1.0 - sinMu * sinMu); + return v * cosMu + cross(a, v) * sinMu + a * dot(a, v) * (1.0 - cosMu); +} + +// Public API -------------------------------------------------------------------------------------- + +bool RefractionSupported() { +#if USE_REFRACTION + return true; +#else + return false; +#endif +} + +// If refraction is supported, the ray is rotated by the angle of deviation stored in the deviation +// texture towards the ground. +vec3 GetRefractedRay(vec3 camera, vec3 ray, out bool hitsGround) { +#if USE_REFRACTION + float r = length(camera); + float mu = dot(camera / r, ray); + vec2 uv = getTransmittanceTextureUvFromRMu(r, mu); + + vec2 deviationContactRadius = texture(uThetaDeviationTexture, uv).rg; + float sinMu = sin(deviationContactRadius.r); + vec3 axis = normalize(cross(camera, ray)); + + hitsGround = deviationContactRadius.g < 0.0; + + return rotateVector2(ray, axis, sinMu); + +#else + hitsGround = false; + return ray; +#endif +} + vec3 GetSkyLuminance(vec3 camera, vec3 viewRay, vec3 sunDirection, out vec3 transmittance) { return getSkyRadiance(uTransmittanceTexture, uMultipleScatteringTexture, uSingleAerosolsScatteringTexture, camera, viewRay, sunDirection, transmittance); diff --git a/plugins/csp-atmospheres/shaders/atmosphere-models/cosmoscout/model.glsl b/plugins/csp-atmospheres/shaders/atmosphere-models/cosmoscout/model.glsl index 8c5bd079c..019264099 100644 --- a/plugins/csp-atmospheres/shaders/atmosphere-models/cosmoscout/model.glsl +++ b/plugins/csp-atmospheres/shaders/atmosphere-models/cosmoscout/model.glsl @@ -155,6 +155,17 @@ vec3 _getInscatter( // -------------------------------------------------------------------------------------- public API +// This model does not support refraction. +bool RefractionSupported() { + return false; +} + +// This model does not support refraction, so the input ray is returned unchanged. +vec3 GetRefractedRay(vec3 camera, vec3 ray, out bool hitsGround) { + hitsGround = false; + return ray; +} + // Returns the sky luminance (in cd/m^2) along the segment from 'camera' to the nearest // atmosphere boundary in direction 'viewRay', as well as the transmittance along this segment. vec3 GetSkyLuminance(vec3 camera, vec3 viewRay, vec3 sunDirection, out vec3 transmittance) { diff --git a/plugins/csp-atmospheres/shaders/csp-atmosphere.frag b/plugins/csp-atmospheres/shaders/csp-atmosphere.frag index b376f33a6..4539411db 100644 --- a/plugins/csp-atmospheres/shaders/csp-atmosphere.frag +++ b/plugins/csp-atmospheres/shaders/csp-atmosphere.frag @@ -30,16 +30,17 @@ uniform sampler2D uDepthBuffer; #endif uniform vec3 uSunDir; -uniform float uSunIlluminance; -uniform float uSunLuminance; +uniform vec3 uSunInfo; // x: sun luminance, y: sun illuminance, z: sun angular radius uniform float uTime; uniform mat4 uMatM; +uniform mat4 uMatMVP; uniform mat4 uMatScale; uniform mat4 uMatInvP; uniform float uWaterLevel; uniform sampler2D uCloudTexture; uniform float uCloudAltitude; -uniform float uSunElevation; +uniform sampler3D uLimbLuminanceTexture; +uniform vec3 uShadowCoordinates; // outputs layout(location = 0) out vec3 oColor; @@ -49,6 +50,13 @@ layout(location = 0) out vec3 oColor; // Each atmospheric model will implement these three methods. We forward-declare them here. The // actual implementation comes from the model's shader which is linked to this shader. +// This will return true or false depending on whether the atmosphere model supports refraction. +bool RefractionSupported(); + +// This will return the view ray after refraction by the atmosphere after it travelled all the way +// to the end of the atmosphere. +vec3 GetRefractedRay(vec3 camera, vec3 ray, out bool hitsGround); + // Returns the sky luminance (in cd/m^2) along the segment from 'camera' to the nearest // atmosphere boundary in direction 'viewRay', as well as the transmittance along this segment. vec3 GetSkyLuminance(vec3 camera, vec3 viewRay, vec3 sunDirection, out vec3 transmittance); @@ -239,32 +247,76 @@ vec2 getLngLat(vec3 position) { // Returns the background color at the current pixel. If multisampling is used, we take the average // color. -vec3 getFramebufferColor() { +vec3 getFramebufferColor(vec2 texcoords) { #if HDR_SAMPLES > 0 vec3 color = vec3(0.0); for (int i = 0; i < HDR_SAMPLES; ++i) { - color += texelFetch(uColorBuffer, ivec2(vsIn.texcoords * textureSize(uColorBuffer)), i).rgb; + color += texelFetch(uColorBuffer, ivec2(texcoords * textureSize(uColorBuffer)), i).rgb; } return color / HDR_SAMPLES; #else - return texture(uColorBuffer, vsIn.texcoords).rgb; + return texture(uColorBuffer, texcoords).rgb; #endif } // Returns the depth at the current pixel. If multisampling is used, we take the minimum depth. -float getFramebufferDepth() { +float getFramebufferDepth(vec2 texcoords) { #if HDR_SAMPLES > 0 float depth = 1.0; for (int i = 0; i < HDR_SAMPLES; ++i) { - depth = min( - depth, texelFetch(uDepthBuffer, ivec2(vsIn.texcoords * textureSize(uDepthBuffer)), i).r); + depth = min(depth, texelFetch(uDepthBuffer, ivec2(texcoords * textureSize(uDepthBuffer)), i).r); } return depth; #else - return texture(uDepthBuffer, vsIn.texcoords).r; + return texture(uDepthBuffer, texcoords).r; #endif } +// Using acos is not very stable for small angles. This function is used to compute the angle +// between two vectors in a more stable way. +float angleBetweenVectors(vec3 u, vec3 v) { + return 2.0 * asin(0.5 * length(u - v)); +} + +// This methods returns a color from the framebuffer which most likely represents what an observer +// would see if looking in the direction of the given ray. If the ray hits the ground, black is +// returned. If the ray is refracted around the planet, we cannot sample the framebuffer but return +// black as well. However, if the ray would hit the Sun, the color of the Sun is returned. +vec3 getRefractedFramebufferColor(vec3 rayOrigin, vec3 rayDir, out vec3 refractedRay) { + + // First, we assume that the refracted ray will leave the atmosphere unblocked. We compute the + // texture coordinates where the ray would hit the framebuffer. + bool hitsGround; + refractedRay = GetRefractedRay(rayOrigin, rayDir, hitsGround); + + if (hitsGround) { + return vec3(0, 0, 0); + } + + vec4 texcoords = uMatMVP * vec4(refractedRay, 0.0); + texcoords.xy = texcoords.xy / texcoords.w * 0.5 + 0.5; + + // We can only sample the color buffer if the point is inside the screen. + bool inside = all(lessThan(texcoords.xy, vec2(1.0))) && all(greaterThan(texcoords.xy, vec2(0.0))); + + // Also, we check the depth buffer to see if the point is occluded. If it is, we do not sample + // the color buffer. + bool occluded = getFramebufferDepth(texcoords.xy) > 0.0001; + + if (inside && !occluded) { + return getFramebufferColor(texcoords.xy); + } + + float sunAngularRadius = uSunInfo.z; + float sunColor = 0.0; + + if (angleBetweenVectors(refractedRay, uSunDir) < sunAngularRadius) { + sunColor = uSunInfo.x; + } + + return vec3(sunColor); +} + // Returns the distance to the surface of the depth buffer at the current pixel. If the depth of the // next opaque object is very close to the far end of our depth buffer, we will get jittering // artifacts. That's the case if we are next to a satellite or on a moon and look towards a planet @@ -273,7 +325,7 @@ float getFramebufferDepth() { // intersection with the planet analytically and blend to this value instead. This means, if you are // close to a satellite, mountains of the planet below cannot poke through the atmosphere anymore. float getSurfaceDistance(vec3 rayOrigin, vec3 rayDir) { - float depth = getFramebufferDepth(); + float depth = getFramebufferDepth(vsIn.texcoords); // If the fragment is really far away, the inverse reverse infinite projection divides by zero. // So we add a minimum threshold here. @@ -433,10 +485,126 @@ float getCloudShadow(vec3 rayOrigin, vec3 rayDir) { return 1.0 - getCloudDensity(rayOrigin, rayDir, intersections.y) * fac; } +// Returns a precomputed luminance of the atmosphere ring around the occluder for the +// given observer position and viewing direction. This is used if the observer is inside the +// shadow of the occluder. The normal atmosphere code would result in severe artifacts as only +// a few refracted sunrays would actually hit the observer. The precomputed texture has been +// generated by rendering the atmosphere from the occluder's point of view with a high resolution. +// The precomputed texture is stored in a small four-dimensional texture. +vec3 getApproximateLimbLuminance(vec3 rayOrigin, vec3 rayDir) { + float dist = length(rayOrigin); + vec3 toCenter = rayOrigin / dist; + vec3 projSun = dot(uSunDir, toCenter) * toCenter - uSunDir; + vec3 projAtmo = dot(rayDir, toCenter) * toCenter - rayDir; + + // The x and y coordinates of the texture are computed on the CPU and passed as a uniform. They + // are based on the observer's position in the shadow volume. + float x = uShadowCoordinates.x; + float y = uShadowCoordinates.y; + + // For each [x, y] coordinate in the texture, the texture contains a two-dimensional image of the + // atmosphere ring around the occluder as shown below. The image is stored layer-wise in the third + // dimension of the texture. The first strip of pixels is the bottom layer, the second strip is + // the second layer, and so on. The layers are stored consecutively in the texture. Usually, only + // very few layers are needed. + // + // projSun + // │ projAtmo + // This part is ┌┬--.. / + // drawn below in ---> ├┤-. /' + // more detail. └┴-./ . \ + // │β⁠/ \ . │ + // o │ . │ + // / . │ + // ┌--' . / + // ├ - ' . + // └---'' + // + float beta = acos(clamp(dot(normalize(projSun), normalize(projAtmo)), -1.0, 1.0)) / PI; + + // The texture is stored as a 3D texture with the size [res, res, layers * res]. + ivec3 texSize = textureSize(uLimbLuminanceTexture, 0); + float res = float(texSize.x); + float layers = float(texSize.z) / res; + + // If there is only one layer, we can use a fast path. + if (layers == 1) { + vec3 luminance = texture(uLimbLuminanceTexture, vec3(x, y, beta)).rgb; + +#if !ENABLE_HDR + luminance = tonemap(luminance / uSunInfo.y); + luminance = linearToSRGB(luminance); +#endif + + return luminance; + } + + // As shown above, the limb is vertically subdivided in a set of layers which are stored + // consecutively in the texture. If there are two layers, the pixel strip from [x, y, 0] to + // [x, y, 0.5] contains the luminance of the bottom layer, and the pixel strip from [x, y, 0.5] to + // [x, y, 1.0] contains the luminance of the upper layer. The same applies for three layers, four + // layers, and so on. + + // Add the layer start and end point we have to sample at pixel centers to avoid linear + // interpolation with the start or end of the next or previous layer. + float layerWidth = 1.0 / layers - 1.0 / res; + + float phiOcc = asin(PLANET_RADIUS / dist); + float phiAtmo = asin(ATMOSPHERE_RADIUS / dist); + float phi = acos(clamp(dot(rayDir, -toCenter), -1.0, 1.0)) - phiOcc; + float relativePhi = clamp(phi / (phiAtmo - phiOcc), 0.0, 1.0); + + // This is a visualization of a vertical cross-section of the atmosphere as shown above. The + // planet is at the bottom, the upper atmosphere boundary is at the top. In this example, three + // layers are used: + // + // phiAtmo ┌─────────┐ 1.0 + // │ │ + // │ │ + // │ │ + // ├─────────┤ + // │ │ + // │ │ relativePhi + // │ │ + // ├─────────┤ + // │ │ + // │ │ + // │ │ + // phiOcc └─────────┘ 0.0 + + vec3 luminance; + + // In the upper half of the top layer and in the lower half of the bottom layer, we do not need to + // blend between two layers. For all other positions, we blend between the two closest layers. + if (relativePhi < 0.5 / layers || relativePhi > 1.0 - 0.5 / layers) { + float layerStart = floor(relativePhi * layers) / layers + 0.5 / res; + float z = layerStart + beta * layerWidth; + luminance = texture(uLimbLuminanceTexture, vec3(x, y, z)).rgb; + } else { + float upperLayerStart = floor(relativePhi * layers + 0.5) / layers + 0.5 / res; + float lowerLayerStart = floor(relativePhi * layers - 0.5) / layers + 0.5 / res; + float upperZ = upperLayerStart + beta * layerWidth; + float lowerZ = lowerLayerStart + beta * layerWidth; + vec3 upperLuminance = texture(uLimbLuminanceTexture, vec3(x, y, upperZ)).rgb; + vec3 lowerLuminance = texture(uLimbLuminanceTexture, vec3(x, y, lowerZ)).rgb; + float blend = relativePhi * layers - 0.5 - floor(relativePhi * layers - 0.5); + luminance = mix(lowerLuminance, upperLuminance, blend); + } + +#if !ENABLE_HDR + luminance = tonemap(luminance / uSunInfo.y); + luminance = linearToSRGB(luminance); +#endif + + return luminance; +} + // ------------------------------------------------------------------------------------------------- #if SKYDOME_MODE +uniform float uSunElevation; + // In this special mode, the atmosphere shader will draw a fish-eye view of the entire sky. This is // meant for testing and debugging purposes. void main() { @@ -514,32 +682,66 @@ void main() { // resulting atmosphere color. Finally, we will compute the color of the clouds and overlay them as // well. void main() { - vec3 rayDir = normalize(vsIn.rayDir); - - // Get the planet / background color without any atmosphere. - oColor = getFramebufferColor(); + vec3 rayDir = normalize(vsIn.rayDir); + vec3 refractedRay = rayDir; // If the ray does not actually hit the atmosphere or the exit is already behind camera, we do not // have to modify the color any further. vec2 atmosphereIntersections = intersectAtmosphere(vsIn.rayOrigin, rayDir); if (atmosphereIntersections.x > atmosphereIntersections.y || atmosphereIntersections.y < 0) { + oColor = getFramebufferColor(vsIn.texcoords); return; } // If something is in front of the atmosphere, we do not have to do anything either. float surfaceDistance = getSurfaceDistance(vsIn.rayOrigin, rayDir); if (surfaceDistance < atmosphereIntersections.x) { + oColor = getFramebufferColor(vsIn.texcoords); return; } + // If possible, use the precomputed limb luminance to get the color of the atmosphere ring around + // the occluder. +#if ENABLE_LIMB_LUMINANCE + if (RefractionSupported()) { + // The third coordinate of the shadow coordinates is the approximate width of the atmosphere + // ring around the occluder in pixels. + float pixelWidth = uShadowCoordinates.z; + + // Use the limb luminance only if the observer is not too close to the occluder and if the + // ring is thinner than 50 pixels. + if (uShadowCoordinates.x > 0.05 && uShadowCoordinates.y > 0.0 && pixelWidth < 50.0) { + + vec2 planetIntersections = intersectPlanetsphere(vsIn.rayOrigin, rayDir); + if (planetIntersections.x > planetIntersections.y) { + oColor = getApproximateLimbLuminance(vsIn.rayOrigin, rayDir); + return; + } + } + } +#endif + + vec3 entryPoint = + vsIn.rayOrigin + rayDir * (atmosphereIntersections.x > 0.0 ? atmosphereIntersections.x : 0.0); + vec3 exitPoint = + vsIn.rayOrigin + rayDir * (atmosphereIntersections.x > 0.0 ? atmosphereIntersections.x + : atmosphereIntersections.y); + + // The ray hits an object if the distance to the depth buffer is smaller than the ray exit. + bool hitsSurface = surfaceDistance < atmosphereIntersections.y; + + if (RefractionSupported() && !hitsSurface) { + oColor = getRefractedFramebufferColor(entryPoint, rayDir, refractedRay); + } else { + oColor = getFramebufferColor(vsIn.texcoords); + } + // Always operate in linear color space. #if !ENABLE_HDR oColor = sRGBtoLinear(oColor); #endif - // The ray hits an object if the distance to the depth buffer is smaller than the ray exit. - bool hitsSurface = surfaceDistance < atmosphereIntersections.y; - bool underWater = false; + bool underWater = false; vec4 oceanWaterShade = vec4(0.0); vec4 oceanSurfaceColor = vec4(0.0); @@ -563,9 +765,8 @@ void main() { // Looking down onto the ocean. if (oceanIntersections.x > 0) { - - vec3 oceanSurface = vsIn.rayOrigin + rayDir * oceanIntersections.x; - vec3 idealNormal = normalize(oceanSurface); + vec3 oceanSurface = vsIn.rayOrigin + rayDir * oceanIntersections.x; + vec3 idealNormal = normalize(oceanSurface); #if ENABLE_WAVES const float WAVE_SPEED = 0.2; @@ -624,13 +825,13 @@ void main() { float softSpecular = pow(specularIntensity, 200) * 0.0001; float hardSpecular = pow(specularIntensity, 2000) * 0.002; vec3 eclipseShadow = getEclipseShadow((uMatM * vec4(oceanSurface, 1.0)).xyz); - oceanSurfaceColor.rgb += mix(softSpecular, hardSpecular, waveFade) * uSunLuminance * + oceanSurfaceColor.rgb += mix(softSpecular, hardSpecular, waveFade) * uSunInfo.x * transmittance * eclipseShadow * pow(smoothstep(0, 1, dot(uSunDir, idealNormal)), 0.2); #if !ENABLE_HDR // In non-HDR mode, we need to apply tone mapping to the ocean color. - oceanSurfaceColor.rgb = tonemap(oceanSurfaceColor.rgb / uSunIlluminance); + oceanSurfaceColor.rgb = tonemap(oceanSurfaceColor.rgb / uSunInfo.y); #endif // The atmosphere now actually ends at the ocean surface. @@ -669,12 +870,12 @@ void main() { // no actual shadow volume in the atmosphere. eclipseShadow = getEclipseShadow((uMatM * vec4(surfacePoint, 1.0)).xyz); - // We have to divide by uSunIlluminance because the planet shader already multiplied the result + // We have to divide by uSunInfo.y because the planet shader already multiplied the result // of the BRDF with the Sun's illuminance. Since the planet shader does not know whether a // atmosphere will be drawn later, it only can assume that it is in direct sun light. However, // if there is an atmosphere, actually less light reaches the surface. So we have to divide by // the direct sun illuminance and multiply by the attenuated illuminance. - oColor = cloudShadow * oColor * illuminance / uSunIlluminance; + oColor = cloudShadow * oColor * illuminance / uSunInfo.y; oceanSurfaceColor.rgb *= cloudShadow; @@ -683,18 +884,23 @@ void main() { // If the ray leaves the atmosphere unblocked, we only need to compute the luminance of the sky. inScatter = GetSkyLuminance(vsIn.rayOrigin, rayDir, uSunDir, transmittance); +#if !ENABLE_HDR + // If HDR Mode is disabled, we draw an artificial Sun. Else the Sun would look very dim close to + // the horizon. + if (angleBetweenVectors(refractedRay, uSunDir) < uSunInfo.z) { + inScatter += uSunInfo.x; + } +#endif + // We also incorporate eclipse shadows. However, we only evaluate at the ray exit point. There // is no actual shadow volume in the atmosphere. - vec3 exitPoint = - vsIn.rayOrigin + rayDir * (atmosphereIntersections.x > 0.0 ? atmosphereIntersections.x - : atmosphereIntersections.y); eclipseShadow = getEclipseShadow((uMatM * vec4(exitPoint, 1.0)).xyz); } inScatter *= eclipseShadow; #if !ENABLE_HDR - inScatter = tonemap(inScatter / uSunIlluminance); + inScatter = tonemap(inScatter / uSunInfo.y); #endif #if ENABLE_WATER @@ -719,7 +925,7 @@ void main() { cloudColor.rgb *= eclipseShadow; #if !ENABLE_HDR - cloudColor.rgb = tonemap(cloudColor.rgb / uSunIlluminance); + cloudColor.rgb = tonemap(cloudColor.rgb / uSunInfo.y); #endif oColor = mix(oColor, cloudColor.rgb, cloudColor.a); diff --git a/plugins/csp-atmospheres/src/Atmosphere.cpp b/plugins/csp-atmospheres/src/Atmosphere.cpp index c4d23468f..ab5791d49 100644 --- a/plugins/csp-atmospheres/src/Atmosphere.cpp +++ b/plugins/csp-atmospheres/src/Atmosphere.cpp @@ -11,11 +11,13 @@ #include "logger.hpp" #include "models/bruneton/Model.hpp" #include "models/cosmoscout/Model.hpp" +#include "utils.hpp" #include "../../../src/cs-core/GraphicsEngine.hpp" #include "../../../src/cs-core/SolarSystem.hpp" #include "../../../src/cs-graphics/TextureLoader.hpp" #include "../../../src/cs-utils/FrameStats.hpp" +#include "../../../src/cs-utils/Frustum.hpp" #include "../../../src/cs-utils/filesystem.hpp" #include @@ -99,6 +101,10 @@ Atmosphere::~Atmosphere() { pSG->GetRoot()->DisconnectChild(mAtmosphereNode.get()); mAllSettings->mGraphics.pEnableHDR.disconnect(mEnableHDRConnection); + + if (mLimbLuminanceTexture != 0) { + glDeleteTextures(1, &mLimbLuminanceTexture); + } } //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -144,6 +150,18 @@ void Atmosphere::configure(Plugin::Settings::Atmosphere const& settings) { mShaderDirty = true; } + // Reload the limb luminance texture if required. + if (mSettings.mLimbLuminanceTexture != settings.mLimbLuminanceTexture) { + if (settings.mLimbLuminanceTexture.has_value() && + !settings.mLimbLuminanceTexture.value().empty()) { + mLimbLuminanceTexture = + std::get<0>(utils::read3DTexture(settings.mLimbLuminanceTexture.value())); + } else { + mLimbLuminanceTexture = 0; + } + mShaderDirty = true; + } + // Recreate the shader if required. if (mRadii != radii) { mRadii = radii; @@ -154,7 +172,8 @@ void Atmosphere::configure(Plugin::Settings::Atmosphere const& settings) { mSettings.mBottomAltitude != settings.mBottomAltitude || mSettings.mEnableWater != settings.mEnableWater || mSettings.mEnableWaves != settings.mEnableWaves || - mSettings.mEnableClouds != settings.mEnableClouds) { + mSettings.mEnableClouds != settings.mEnableClouds || + mSettings.mEnableLimbLuminance != settings.mEnableLimbLuminance) { mShaderDirty = true; } @@ -168,21 +187,23 @@ void Atmosphere::configure(Plugin::Settings::Atmosphere const& settings) { //////////////////////////////////////////////////////////////////////////////////////////////////// -void Atmosphere::updateShader() { - mAtmoShader = VistaGLSLShader(); +void Atmosphere::createShader(ShaderType type, VistaGLSLShader& shader, Uniforms& uniforms) const { + shader = VistaGLSLShader(); auto sVert = cs::utils::filesystem::loadToString("../share/resources/shaders/csp-atmosphere.vert"); auto sFrag = cs::utils::filesystem::loadToString("../share/resources/shaders/csp-atmosphere.frag"); - cs::utils::replaceString(sFrag, "SKYDOME_MODE", "0"); + cs::utils::replaceString(sFrag, "SKYDOME_MODE", type == ShaderType::eSkyDome ? "1" : "0"); cs::utils::replaceString( sFrag, "PLANET_RADIUS", std::to_string(mRadii[0] + mSettings.mBottomAltitude.get())); cs::utils::replaceString( sFrag, "ATMOSPHERE_RADIUS", std::to_string(mRadii[0] + mSettings.mTopAltitude)); cs::utils::replaceString( sFrag, "ENABLE_CLOUDS", std::to_string(mSettings.mEnableClouds.get() && mCloudTexture)); + 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())); cs::utils::replaceString(sFrag, "ENABLE_WAVES", std::to_string(mSettings.mEnableWaves.get())); cs::utils::replaceString(sFrag, "ENABLE_HDR", std::to_string(mHDRBuffer != nullptr)); @@ -191,31 +212,43 @@ void Atmosphere::updateShader() { cs::utils::replaceString( sFrag, "ECLIPSE_SHADER_SNIPPET", mEclipseShadowReceiver->getShaderSnippet()); - mAtmoShader.InitVertexShaderFromString(sVert); - mAtmoShader.InitFragmentShaderFromString(sFrag); + shader.InitVertexShaderFromString(sVert); + shader.InitFragmentShaderFromString(sFrag); // Add the fragment shader from the atmospheric model. - glAttachShader(mAtmoShader.GetProgram(), mModel->getShader()); - - mAtmoShader.Link(); - - mUniforms.sunDir = mAtmoShader.GetUniformLocation("uSunDir"); - mUniforms.sunIlluminance = mAtmoShader.GetUniformLocation("uSunIlluminance"); - mUniforms.sunLuminance = mAtmoShader.GetUniformLocation("uSunLuminance"); - mUniforms.time = mAtmoShader.GetUniformLocation("uTime"); - mUniforms.depthBuffer = mAtmoShader.GetUniformLocation("uDepthBuffer"); - mUniforms.colorBuffer = mAtmoShader.GetUniformLocation("uColorBuffer"); - mUniforms.waterLevel = mAtmoShader.GetUniformLocation("uWaterLevel"); - mUniforms.cloudTexture = mAtmoShader.GetUniformLocation("uCloudTexture"); - mUniforms.cloudAltitude = mAtmoShader.GetUniformLocation("uCloudAltitude"); - mUniforms.inverseModelViewMatrix = mAtmoShader.GetUniformLocation("uMatInvMV"); - mUniforms.inverseProjectionMatrix = mAtmoShader.GetUniformLocation("uMatInvP"); - mUniforms.scaleMatrix = mAtmoShader.GetUniformLocation("uMatScale"); - mUniforms.modelMatrix = mAtmoShader.GetUniformLocation("uMatM"); + glAttachShader(shader.GetProgram(), mModel->getShader()); + + shader.Link(); + + uniforms.sunDir = shader.GetUniformLocation("uSunDir"); + uniforms.sunInfo = shader.GetUniformLocation("uSunInfo"); + uniforms.time = shader.GetUniformLocation("uTime"); + uniforms.depthBuffer = shader.GetUniformLocation("uDepthBuffer"); + uniforms.colorBuffer = shader.GetUniformLocation("uColorBuffer"); + uniforms.waterLevel = shader.GetUniformLocation("uWaterLevel"); + uniforms.cloudTexture = shader.GetUniformLocation("uCloudTexture"); + uniforms.cloudAltitude = shader.GetUniformLocation("uCloudAltitude"); + uniforms.limbLuminanceTexture = shader.GetUniformLocation("uLimbLuminanceTexture"); + uniforms.inverseModelViewMatrix = shader.GetUniformLocation("uMatInvMV"); + uniforms.inverseProjectionMatrix = shader.GetUniformLocation("uMatInvP"); + uniforms.scaleMatrix = shader.GetUniformLocation("uMatScale"); + uniforms.modelMatrix = shader.GetUniformLocation("uMatM"); + uniforms.modelViewProjectionMatrix = shader.GetUniformLocation("uMatMVP"); + uniforms.atmoPanoUniforms = shader.GetUniformLocation("uAtmoPanoUniforms"); + uniforms.sunElevation = shader.GetUniformLocation("sunElevation"); + uniforms.shadowCoordinates = shader.GetUniformLocation("uShadowCoordinates"); // 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. - mEclipseShadowReceiver->init(&mAtmoShader, 3); + // 1, 2 is used for the cloud map, 3 is used for the limb luminance texture. + if (type == ShaderType::eAtmosphere) { + mEclipseShadowReceiver->init(&shader, 4); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void Atmosphere::updateShaders() { + createShader(ShaderType::eAtmosphere, mAtmoShader, mAtmoUniforms); } //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -232,12 +265,10 @@ void Atmosphere::update(double time) { mSceneScale = mSolarSystem->getObserver().getScale(); mEclipseShadowReceiver->update(*object); - // update brightness value ------------------------------------------------- - // This is a crude approximation of the overall scene brightness due to - // atmospheric scattering, camera position and the Sun's position. - // It may be used for fake HDR effects such as dimming stars. + // This is a crude approximation of the overall scene brightness due to atmospheric scattering, + // camera position and the Sun's position. It may be used for fake HDR effects such as dimming + // stars. - // some required positions and directions glm::dvec3 planet = object->getObserverRelativePosition() * object->getRelativeScale(mSolarSystem->getObserver()); double dist = glm::length(planet); @@ -251,10 +282,10 @@ void Atmosphere::update(double time) { // [noon ... midnight] -> [1 ... -1] double daySide = glm::dot(-toPlanet, glm::dvec3(mSunDirection)); - // limit brightness when on night side (also in dusk and dawn time) + // Limit brightness when on night side (also in dusk and dawn time). daySide = std::pow(std::min(1.0, std::max(0.0, daySide + 1.0)), 50.0); - // reduce brightness in outer space + // Reduce brightness in outer space. mGraphicsEngine->pApproximateSceneBrightness = static_cast((1.0 - heightInAtmosphere) * daySide); @@ -271,11 +302,11 @@ bool Atmosphere::Do() { cs::utils::FrameStats::ScopedSamplesCounter samplesCounter("Atmosphere of " + mObjectName); if (mShaderDirty || mEclipseShadowReceiver->needsRecompilation()) { - updateShader(); + updateShaders(); mShaderDirty = false; } - // save current lighting and meterial state of the OpenGL state machine ---- + // save current lighting and meterial state of the OpenGL state machine -------------------------- glPushAttrib(GL_LIGHTING_BIT | GL_ENABLE_BIT); glDisable(GL_LIGHTING); glDisable(GL_DEPTH_TEST); @@ -283,11 +314,11 @@ bool Atmosphere::Do() { glEnable(GL_TEXTURE_2D); glDepthMask(GL_FALSE); - // copy depth buffer ------------------------------------------------------- - if (!mHDRBuffer) { - std::array iViewport{}; - glGetIntegerv(GL_VIEWPORT, iViewport.data()); + // copy depth buffer ----------------------------------------------------------------------------- + std::array iViewport{}; + glGetIntegerv(GL_VIEWPORT, iViewport.data()); + if (!mHDRBuffer) { auto* viewport = GetVistaSystem()->GetDisplayManager()->GetCurrentRenderInfo()->m_pViewport; auto const& data = mGBufferData[viewport]; @@ -299,7 +330,7 @@ bool Atmosphere::Do() { iViewport.at(3), 0); } - // get matrices and related values ----------------------------------------- + // get matrices and related values --------------------------------------------------------------- std::array glMatV{}; std::array glMatP{}; @@ -316,10 +347,12 @@ bool Atmosphere::Do() { // We apply this non-uniform scaling to the observer-relative transformation of the planet. glm::dmat4 matM = mObserverRelativeTransformation * matEllipsoid; glm::dmat4 matV = glm::make_mat4x4(glMatV.data()); + glm::dmat4 matP = glm::make_mat4x4(glMatP.data()); glm::dmat4 matInvV = glm::inverse(matV); glm::dmat4 matInvWorld = glm::inverse(mObserverRelativeTransformation); glm::dmat4 matInvMV = matInverseEllipsoid * matInvWorld * matInvV; - glm::mat4 matInvP = glm::inverse(glm::make_mat4x4(glMatP.data())); + glm::mat4 matInvP = glm::inverse(matP); + glm::mat4 matMVP = glm::mat4(matP * matV * matM); // Reconstructing the frame-buffer depth in the atmosphere shader is a bit involved as a simple // multiplication with matInvP would lead to coordinates in observer-relative coordinates which @@ -329,17 +362,49 @@ bool Atmosphere::Do() { matScale = glm::scale(matScale, glm::dvec3(mSceneScale)); matScale[3] = glm::vec4(0.0); - glm::vec3 sunDir = glm::normalize(glm::vec3(matInvWorld * glm::vec4(mSunDirection, 0))); + glm::vec3 sunDir = + glm::normalize(glm::vec3(matInverseEllipsoid * matInvWorld * glm::vec4(mSunDirection, 0))); - // set uniforms ------------------------------------------------------------ + // set uniforms ---------------------------------------------------------------------------------- mAtmoShader.Bind(); - mAtmoShader.SetUniform(mUniforms.sunIlluminance, static_cast(mSunIlluminance)); - mAtmoShader.SetUniform(mUniforms.sunLuminance, static_cast(mSunLuminance)); - mAtmoShader.SetUniform(mUniforms.sunDir, sunDir[0], sunDir[1], sunDir[2]); + float sunRadius = float(mSolarSystem->getSun()->getRadii()[0]); + float sunDist = float(glm::length(mSolarSystem->pSunPosition.get()) * mSceneScale); + float phiSun = std::asin(sunRadius / sunDist); + + // If precomputed limb luminance is about to be used, we need to pass the eclipse-shadow map + // coordinates to the shader as they are two of the three texture coordinates. We also need to + // compute the approximate pixel width of the atmosphere ring as the precomputed limb luminance is + // only used if the ring is pretty thin. + if (mSettings.mEnableLimbLuminance.get() && mLimbLuminanceTexture) { + glm::vec3 occDir = glm::vec3(matInvMV[3]); + float delta = + std::acos(std::min(1.F, glm::dot(glm::normalize(occDir), glm::normalize(-sunDir)))); + float occDist = glm::length(occDir); + float planetRadius = float(mRadii[0] + mSettings.mBottomAltitude.get()); + float atmoRadius = float(mRadii[0] + mSettings.mTopAltitude); + float phiOcc = std::asin(planetRadius / occDist); + float phiAtmo = std::asin(atmoRadius / occDist); + float x = 1.0F / (phiOcc / phiSun + 1.0F); + float y = 1.0F - delta / (phiOcc + phiSun); + + cs::utils::Frustum frustum; + frustum.setFromMatrix(matP); + float approxPixelSize = float(frustum.getHorizontalFOV() / iViewport[2]); + float pixelWidth = (phiAtmo - phiOcc) / approxPixelSize; + if (occDist < atmoRadius) { + x = -1.0; + } + + mAtmoShader.SetUniform(mAtmoUniforms.shadowCoordinates, x, y, pixelWidth); + } + + mAtmoShader.SetUniform(mAtmoUniforms.sunInfo, static_cast(mSunLuminance), + static_cast(mSunIlluminance), phiSun); + mAtmoShader.SetUniform(mAtmoUniforms.sunDir, sunDir[0], sunDir[1], sunDir[2]); // The noise shader does not like huge numbers. So we rather loop the time. - mAtmoShader.SetUniform(mUniforms.time, static_cast(std::fmod(mTime, 1.0e4))); + mAtmoShader.SetUniform(mAtmoUniforms.time, static_cast(std::fmod(mTime, 1.0e4))); if (mHDRBuffer) { mHDRBuffer->doPingPong(); @@ -353,36 +418,47 @@ bool Atmosphere::Do() { data.mColorBuffer->Bind(GL_TEXTURE1); } - mAtmoShader.SetUniform(mUniforms.depthBuffer, 0); - mAtmoShader.SetUniform(mUniforms.colorBuffer, 1); + mAtmoShader.SetUniform(mAtmoUniforms.depthBuffer, 0); + mAtmoShader.SetUniform(mAtmoUniforms.colorBuffer, 1); if (mSettings.mEnableWater.get()) { - mAtmoShader.SetUniform(mUniforms.waterLevel, + mAtmoShader.SetUniform(mAtmoUniforms.waterLevel, mSettings.mWaterLevel.get() * mAllSettings->mGraphics.pHeightScale.get() - static_cast(mSettings.mBottomAltitude.get())); } if (mSettings.mEnableClouds.get() && mCloudTexture) { mCloudTexture->Bind(GL_TEXTURE2); - mAtmoShader.SetUniform(mUniforms.cloudTexture, 2); - mAtmoShader.SetUniform(mUniforms.cloudAltitude, mSettings.mCloudAltitude.get()); + mAtmoShader.SetUniform(mAtmoUniforms.cloudTexture, 2); + mAtmoShader.SetUniform(mAtmoUniforms.cloudAltitude, mSettings.mCloudAltitude.get()); } + if (mSettings.mEnableLimbLuminance.get() && mLimbLuminanceTexture) { + glActiveTexture(GL_TEXTURE3); + glBindTexture(GL_TEXTURE_3D, mLimbLuminanceTexture); + mAtmoShader.SetUniform(mAtmoUniforms.limbLuminanceTexture, 3); + } + + glUniformMatrix4fv( + mAtmoUniforms.inverseModelViewMatrix, 1, GL_FALSE, glm::value_ptr(glm::mat4(matInvMV))); + glUniformMatrix4fv(mAtmoUniforms.scaleMatrix, 1, GL_FALSE, glm::value_ptr(glm::mat4(matScale))); + glUniformMatrix4fv(mAtmoUniforms.inverseProjectionMatrix, 1, GL_FALSE, glm::value_ptr(matInvP)); + glUniformMatrix4fv(mAtmoUniforms.modelMatrix, 1, GL_FALSE, glm::value_ptr(glm::mat4(matM))); glUniformMatrix4fv( - mUniforms.inverseModelViewMatrix, 1, GL_FALSE, glm::value_ptr(glm::mat4(matInvMV))); - glUniformMatrix4fv(mUniforms.scaleMatrix, 1, GL_FALSE, glm::value_ptr(glm::mat4(matScale))); - glUniformMatrix4fv(mUniforms.inverseProjectionMatrix, 1, GL_FALSE, glm::value_ptr(matInvP)); - glUniformMatrix4fv(mUniforms.modelMatrix, 1, GL_FALSE, glm::value_ptr(glm::mat4(matM))); + mAtmoUniforms.modelViewProjectionMatrix, 1, GL_FALSE, glm::value_ptr(glm::mat4(matMVP))); // Initialize eclipse shadow-related uniforms and textures. mEclipseShadowReceiver->preRender(); - mModel->setUniforms(mAtmoShader.GetProgram(), 4); + // 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, then there are four + // for the eclipse shadow receiver. + mModel->setUniforms(mAtmoShader.GetProgram(), 8); - // draw -------------------------------------------------------------------- + // draw ------------------------------------------------------------------------------------------ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - // clean up ---------------------------------------------------------------- + // clean up -------------------------------------------------------------------------------------- // Reset eclipse shadow-related texture units. mEclipseShadowReceiver->postRender(); @@ -398,7 +474,12 @@ bool Atmosphere::Do() { } if (mSettings.mEnableClouds.get() && mCloudTexture) { - mCloudTexture->Unbind(GL_TEXTURE3); + mCloudTexture->Unbind(GL_TEXTURE2); + } + + if (mSettings.mEnableClouds.get() && mLimbLuminanceTexture) { + glActiveTexture(GL_TEXTURE3); + glBindTexture(GL_TEXTURE_3D, 0); } mAtmoShader.Release(); @@ -430,32 +511,8 @@ void Atmosphere::renderSkyDome(std::string const& name) const { const int SIZE = 512; VistaGLSLShader shader; - - auto sVert = cs::utils::filesystem::loadToString( - "../share/resources/shaders/csp-atmospheres/atmosphere.vert"); - auto sFrag = cs::utils::filesystem::loadToString( - "../share/resources/shaders/csp-atmospheres/atmosphere.frag"); - - cs::utils::replaceString(sFrag, "SKYDOME_MODE", "1"); - cs::utils::replaceString( - sFrag, "PLANET_RADIUS", std::to_string(mRadii[0] + mSettings.mBottomAltitude.get())); - cs::utils::replaceString( - sFrag, "ATMOSPHERE_RADIUS", std::to_string(mRadii[0] + mSettings.mTopAltitude)); - cs::utils::replaceString(sFrag, "ENABLE_CLOUDS", "0"); - cs::utils::replaceString(sFrag, "ENABLE_WATER", "0"); - cs::utils::replaceString(sFrag, "ENABLE_WAVES", "0"); - cs::utils::replaceString(sFrag, "ENABLE_HDR", "0"); - cs::utils::replaceString(sFrag, "HDR_SAMPLES", "0"); - cs::utils::replaceString( - sFrag, "ECLIPSE_SHADER_SNIPPET", mEclipseShadowReceiver->getShaderSnippet()); - - shader.InitVertexShaderFromString(sVert); - shader.InitFragmentShaderFromString(sFrag); - - // Add the fragment shader from the atmospheric model. - glAttachShader(shader.GetProgram(), mModel->getShader()); - - shader.Link(); + Uniforms uniforms; + createShader(ShaderType::eSkyDome, shader, uniforms); GLuint texture; glGenTextures(1, &texture); @@ -473,7 +530,6 @@ void Atmosphere::renderSkyDome(std::string const& name) const { glViewport(0, 0, SIZE, SIZE); glScissor(0, 0, SIZE, SIZE); - // save current lighting and meterial state of the OpenGL state machine ---- glPushAttrib(GL_LIGHTING_BIT | GL_ENABLE_BIT); glDisable(GL_LIGHTING); glDisable(GL_DEPTH_TEST); @@ -481,26 +537,22 @@ void Atmosphere::renderSkyDome(std::string const& name) const { glEnable(GL_TEXTURE_2D); glDepthMask(GL_FALSE); - // set uniforms ------------------------------------------------------------ shader.Bind(); double const sunLuminousPower = 3.75e28; double sunDist = 149597870700; double sunIlluminance = sunLuminousPower / (sunDist * sunDist * 4.0 * glm::pi()); - shader.SetUniform( - shader.GetUniformLocation("uSunIlluminance"), static_cast(sunIlluminance)); + shader.SetUniform(uniforms.sunInfo, 0.f, static_cast(sunIlluminance), 0.f); std::vector pixels(SIZE * SIZE * 4); std::vector elevation = {0.0, 45.0, 75.0, 90.0}; for (float e : elevation) { - shader.SetUniform(shader.GetUniformLocation("uSunElevation"), e); - + shader.SetUniform(uniforms.sunElevation, e); mModel->setUniforms(shader.GetProgram(), 4); - // draw -------------------------------------------------------------------- glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glReadPixels(0, 0, SIZE, SIZE, GL_RGBA, GL_FLOAT, &pixels[0]); @@ -508,7 +560,6 @@ void Atmosphere::renderSkyDome(std::string const& name) const { stbi_write_hdr(fmt::format("{}_{}.hdr", name, e).c_str(), SIZE, SIZE, 4, pixels.data()); } - // clean up ---------------------------------------------------------------- glDepthMask(GL_TRUE); shader.Release(); diff --git a/plugins/csp-atmospheres/src/Atmosphere.hpp b/plugins/csp-atmospheres/src/Atmosphere.hpp index 0b78cbf62..02e52db49 100644 --- a/plugins/csp-atmospheres/src/Atmosphere.hpp +++ b/plugins/csp-atmospheres/src/Atmosphere.hpp @@ -50,7 +50,34 @@ class Atmosphere : public IVistaOpenGLDraw { bool GetBoundingBox(VistaBoundingBox& bb) override; private: - void updateShader(); + struct Uniforms { + uint32_t sunDir = 0; + uint32_t sunInfo = 0; + uint32_t time = 0; + uint32_t depthBuffer = 0; + uint32_t colorBuffer = 0; + uint32_t waterLevel = 0; + uint32_t cloudTexture = 0; + uint32_t cloudAltitude = 0; + uint32_t limbLuminanceTexture = 0; + uint32_t inverseModelViewMatrix = 0; + uint32_t inverseProjectionMatrix = 0; + uint32_t scaleMatrix = 0; + uint32_t modelMatrix = 0; + uint32_t modelViewProjectionMatrix = 0; + uint32_t shadowCoordinates = 0; + + // Only used by the panorama shader. + uint32_t atmoPanoUniforms = 0; + + // Only used by the skydome shader. + uint32_t sunElevation = 0; + }; + + enum class ShaderType { eAtmosphere, eSkyDome }; + + void createShader(ShaderType type, VistaGLSLShader& shader, Uniforms& uniforms) const; + void updateShaders(); void renderSkyDome(std::string const& name) const; @@ -63,6 +90,7 @@ class Atmosphere : public IVistaOpenGLDraw { std::shared_ptr mHDRBuffer; std::shared_ptr mEclipseShadowReceiver; std::unique_ptr mCloudTexture; + GLuint mLimbLuminanceTexture = 0; glm::dvec3 mRadii = glm::dvec3(1.0, 1.0, 1.0); glm::dmat4 mObserverRelativeTransformation = glm::dmat4(1.0); @@ -71,8 +99,6 @@ class Atmosphere : public IVistaOpenGLDraw { int mEnableHDRConnection = -1; - VistaGLSLShader mAtmoShader; - struct GBufferData { std::unique_ptr mDepthBuffer; std::unique_ptr mColorBuffer; @@ -86,21 +112,8 @@ class Atmosphere : public IVistaOpenGLDraw { glm::dvec3 mSunDirection = glm::dvec3(1.0, 0.0, 0.0); double mTime = 0.0; - struct Uniforms { - uint32_t sunDir = 0; - uint32_t sunIlluminance = 0; - uint32_t sunLuminance = 0; - uint32_t time = 0; - uint32_t depthBuffer = 0; - uint32_t colorBuffer = 0; - uint32_t waterLevel = 0; - uint32_t cloudTexture = 0; - uint32_t cloudAltitude = 0; - uint32_t inverseModelViewMatrix = 0; - uint32_t inverseProjectionMatrix = 0; - uint32_t scaleMatrix = 0; - uint32_t modelMatrix = 0; - } mUniforms; + VistaGLSLShader mAtmoShader; + Uniforms mAtmoUniforms; std::unique_ptr mModel; }; diff --git a/plugins/csp-atmospheres/src/Plugin.cpp b/plugins/csp-atmospheres/src/Plugin.cpp index 97f0d23f9..374306066 100644 --- a/plugins/csp-atmospheres/src/Plugin.cpp +++ b/plugins/csp-atmospheres/src/Plugin.cpp @@ -69,6 +69,8 @@ void from_json(nlohmann::json const& j, Plugin::Settings::Atmosphere& o) { cs::core::Settings::deserialize(j, "enableClouds", o.mEnableClouds); cs::core::Settings::deserialize(j, "cloudTexture", o.mCloudTexture); 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); } @@ -83,6 +85,8 @@ void to_json(nlohmann::json& j, Plugin::Settings::Atmosphere const& o) { cs::core::Settings::serialize(j, "enableClouds", o.mEnableClouds); cs::core::Settings::serialize(j, "cloudTexture", o.mCloudTexture); 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); } @@ -179,6 +183,15 @@ void Plugin::init() { } })); + mGuiManager->getGui()->registerCallback("atmosphere.setEnableLimbLuminance", + "Enables or disables rendering of a precomputed luminance when inside the shadow.", + std::function([this](bool enable) { + for (auto& settings : mPluginSettings->mAtmospheres) { + settings.second.mEnableLimbLuminance = enable; + mAtmospheres.at(settings.first)->configure(settings.second); + } + })); + mGuiManager->getGui()->registerCallback("atmosphere.setEnable", "Enables or disables rendering of atmospheres.", std::function([this](bool enable) { mPluginSettings->mEnable = enable; })); @@ -203,9 +216,11 @@ void Plugin::deInit() { mGuiManager->getGui()->unregisterCallback("atmosphere.setEnable"); mGuiManager->getGui()->unregisterCallback("atmosphere.setEnableWater"); + mGuiManager->getGui()->unregisterCallback("atmosphere.setEnableWaves"); mGuiManager->getGui()->unregisterCallback("atmosphere.setWaterLevel"); mGuiManager->getGui()->unregisterCallback("atmosphere.setEnableClouds"); mGuiManager->getGui()->unregisterCallback("atmosphere.setCloudAltitude"); + mGuiManager->getGui()->unregisterCallback("atmosphere.setEnableLimbLuminance"); 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 c510f7e2a..100cfd9f1 100644 --- a/plugins/csp-atmospheres/src/Plugin.hpp +++ b/plugins/csp-atmospheres/src/Plugin.hpp @@ -51,6 +51,8 @@ class Plugin : public cs::core::PluginBase { cs::utils::DefaultProperty mEnableClouds{true}; std::optional mCloudTexture; ///< Path to the cloud texture. cs::utils::DefaultProperty mCloudAltitude{3000.F}; ///< In meters. + cs::utils::DefaultProperty mEnableLimbLuminance{true}; + std::optional mLimbLuminanceTexture; ///< Path to the limb luminance texture. /// 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. diff --git a/plugins/csp-atmospheres/src/models/bruneton/Metadata.cpp b/plugins/csp-atmospheres/src/models/bruneton/Metadata.cpp index 65d730ee5..ba78e72aa 100644 --- a/plugins/csp-atmospheres/src/models/bruneton/Metadata.cpp +++ b/plugins/csp-atmospheres/src/models/bruneton/Metadata.cpp @@ -16,6 +16,7 @@ void from_json(nlohmann::json const& j, Metadata& o) { cs::core::Settings::deserialize(j, "sunIlluminance", o.mSunIlluminance); cs::core::Settings::deserialize(j, "scatteringTextureNuSize", o.mScatteringTextureNuSize); cs::core::Settings::deserialize(j, "maxSunZenithAngle", o.mMaxSunZenithAngle); + cs::core::Settings::deserialize(j, "refraction", o.mRefraction); } } // namespace csp::atmospheres::models::bruneton diff --git a/plugins/csp-atmospheres/src/models/bruneton/Metadata.hpp b/plugins/csp-atmospheres/src/models/bruneton/Metadata.hpp index 83cdd5a43..df01281e3 100644 --- a/plugins/csp-atmospheres/src/models/bruneton/Metadata.hpp +++ b/plugins/csp-atmospheres/src/models/bruneton/Metadata.hpp @@ -32,6 +32,9 @@ struct Metadata { /// The maximum Sun zenith angle for which atmospheric scattering is specified during the /// precomputation step and passed to the plugin here. float mMaxSunZenithAngle{}; + + /// Whether refraction was used during preprocessing. + bool mRefraction{}; }; void from_json(nlohmann::json const& j, Metadata& o); diff --git a/plugins/csp-atmospheres/src/models/bruneton/Model.cpp b/plugins/csp-atmospheres/src/models/bruneton/Model.cpp index ed169ad50..28b21101c 100644 --- a/plugins/csp-atmospheres/src/models/bruneton/Model.cpp +++ b/plugins/csp-atmospheres/src/models/bruneton/Model.cpp @@ -10,6 +10,7 @@ #include "../../../src/cs-utils/filesystem.hpp" #include "../../../src/cs-utils/utils.hpp" #include "../../logger.hpp" +#include "../../utils.hpp" #include "Metadata.hpp" #include @@ -18,7 +19,6 @@ #include #include #include -#include #include @@ -72,33 +72,38 @@ bool Model::init( } // Load the precomputed textures. - mPhaseTexture = std::get<0>(read2DTexture(settings.mDataDirectory + "/phase.tif")); + mPhaseTexture = std::get<0>(utils::read2DTexture(settings.mDataDirectory + "/phase.tif")); { - auto const [t, w, h] = read2DTexture(settings.mDataDirectory + "/transmittance.tif"); - mTransmittanceTexture = t; - mTransmittanceTextureWidth = w; - mTransmittanceTextureHeight = h; + auto const [t, s] = utils::read2DTexture(settings.mDataDirectory + "/transmittance.tif"); + mTransmittanceTexture = t; + mTransmittanceTextureWidth = s.x; + mTransmittanceTextureHeight = s.y; } { - auto const [t, w, h] = read2DTexture(settings.mDataDirectory + "/indirect_illuminance.tif"); + auto const [t, s] = utils::read2DTexture(settings.mDataDirectory + "/indirect_illuminance.tif"); mIrradianceTexture = t; - mIrradianceTextureWidth = w; - mIrradianceTextureHeight = h; + mIrradianceTextureWidth = s.x; + mIrradianceTextureHeight = s.y; } { - auto const [t, w, h, d] = read3DTexture(settings.mDataDirectory + "/multiple_scattering.tif"); + auto const [t, s] = utils::read3DTexture(settings.mDataDirectory + "/multiple_scattering.tif"); mMultipleScatteringTexture = t; mScatteringTextureNuSize = meta.mScatteringTextureNuSize; - mScatteringTextureMuSSize = w / mScatteringTextureNuSize; - mScatteringTextureMuSize = h; - mScatteringTextureRSize = d; + mScatteringTextureMuSSize = s.x / mScatteringTextureNuSize; + mScatteringTextureMuSize = s.y; + mScatteringTextureRSize = s.z; } - mSingleAerosolsScatteringTexture = - std::get<0>(read3DTexture(settings.mDataDirectory + "/single_aerosols_scattering.tif")); + mSingleAerosolsScatteringTexture = std::get<0>( + utils::read3DTexture(settings.mDataDirectory + "/single_aerosols_scattering.tif")); + + if (meta.mRefraction) { + mThetaDeviationTexture = + std::get<0>(utils::read2DTexture(settings.mDataDirectory + "/theta_deviation.tif")); + } // Now create the shader. We load the common and model glsl files and concatenate them with the // some constants and the metadata. @@ -111,6 +116,7 @@ bool Model::init( // clang-format off std::string shader = std::string("#version 330\n") + + "#define USE_REFRACTION " + cs::utils::toString(meta.mRefraction) + "\n" + "const int TRANSMITTANCE_TEXTURE_WIDTH = " + cs::utils::toString(mTransmittanceTextureWidth) + ";\n" + "const int TRANSMITTANCE_TEXTURE_HEIGHT = " + cs::utils::toString(mTransmittanceTextureHeight) + ";\n" + "const int SCATTERING_TEXTURE_R_SIZE = " + cs::utils::toString(mScatteringTextureRSize) + ";\n" + @@ -167,93 +173,13 @@ GLuint Model::setUniforms(GLuint program, GLuint startTextureUnit) const { glUniform1i( glGetUniformLocation(program, "uSingleAerosolsScatteringTexture"), startTextureUnit + 4); - return startTextureUnit + 5; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// - -std::tuple Model::read2DTexture(std::string const& path) const { - auto* data = TIFFOpen(path.c_str(), "r"); - - if (!data) { - logger().error("Failed to open TIFF file '{}'", path); - return {0u, 0, 0}; - } - - uint32_t width{}; - uint32_t height{}; - - TIFFGetField(data, TIFFTAG_IMAGELENGTH, &height); - TIFFGetField(data, TIFFTAG_IMAGEWIDTH, &width); - - std::vector pixels(width * height * 3); - - for (unsigned y = 0; y < height; y++) { - TIFFReadScanline(data, &pixels[width * 3 * y], y); - } - - TIFFClose(data); - - GLuint texture; - glGenTextures(1, &texture); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, texture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, width, height, 0, GL_RGB, GL_FLOAT, pixels.data()); - - return {texture, width, height}; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// - -std::tuple Model::read3DTexture(std::string const& path) const { - auto* data = TIFFOpen(path.c_str(), "r"); - - if (!data) { - logger().error("Failed to open TIFF file '{}'", path); - return {0u, 0, 0, 0}; - } - - uint32_t width{}; - uint32_t height{}; - uint32_t depth{}; - - TIFFGetField(data, TIFFTAG_IMAGELENGTH, &height); - TIFFGetField(data, TIFFTAG_IMAGEWIDTH, &width); - - do { - depth++; - } while (TIFFReadDirectory(data)); - - std::vector pixels(width * height * depth * 3); - - for (unsigned z = 0; z < depth; z++) { - TIFFSetDirectory(data, z); - for (unsigned y = 0; y < height; y++) { - TIFFReadScanline(data, &pixels[width * 3 * y + (3 * width * height * z)], y); - } + if (mThetaDeviationTexture) { + glActiveTexture(GL_TEXTURE0 + startTextureUnit + 5); + glBindTexture(GL_TEXTURE_2D, mThetaDeviationTexture); + glUniform1i(glGetUniformLocation(program, "uThetaDeviationTexture"), startTextureUnit + 5); } - TIFFClose(data); - - GLuint texture; - glGenTextures(1, &texture); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_3D, texture); - glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); - glTexImage3D( - GL_TEXTURE_3D, 0, GL_RGB32F, width, height, depth, 0, GL_RGB, GL_FLOAT, pixels.data()); - - return {texture, width, height, depth}; + return startTextureUnit + 6; } //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/plugins/csp-atmospheres/src/models/bruneton/Model.hpp b/plugins/csp-atmospheres/src/models/bruneton/Model.hpp index 5727cb03e..472ece6d2 100644 --- a/plugins/csp-atmospheres/src/models/bruneton/Model.hpp +++ b/plugins/csp-atmospheres/src/models/bruneton/Model.hpp @@ -47,9 +47,6 @@ class Model : public ModelBase { GLuint setUniforms(GLuint program, GLuint startTextureUnit) const override; private: - std::tuple read2DTexture(std::string const& path) const; - std::tuple read3DTexture(std::string const& path) const; - int32_t mTransmittanceTextureWidth{}; int32_t mTransmittanceTextureHeight{}; int32_t mIrradianceTextureWidth{}; @@ -65,9 +62,10 @@ class Model : public ModelBase { GLuint mMultipleScatteringTexture = 0; GLuint mSingleAerosolsScatteringTexture = 0; - GLuint mPhaseTexture = 0; - GLuint mTransmittanceTexture = 0; - GLuint mIrradianceTexture = 0; + GLuint mPhaseTexture = 0; + GLuint mTransmittanceTexture = 0; + GLuint mThetaDeviationTexture = 0; + GLuint mIrradianceTexture = 0; GLuint mAtmosphereShader = 0; }; diff --git a/plugins/csp-atmospheres/src/utils.cpp b/plugins/csp-atmospheres/src/utils.cpp new file mode 100644 index 000000000..e02a8182b --- /dev/null +++ b/plugins/csp-atmospheres/src/utils.cpp @@ -0,0 +1,104 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////// +// This file is part of CosmoScout VR // +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// SPDX-FileCopyrightText: German Aerospace Center (DLR) +// SPDX-License-Identifier: MIT + +#include "utils.hpp" + +#include "logger.hpp" + +#include + +namespace csp::atmospheres::utils { + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +std::tuple read2DTexture(std::string const& path) { + auto* data = TIFFOpen(path.c_str(), "r"); + + if (!data) { + logger().error("Failed to open TIFF file '{}'", path); + return {0u, {0, 0}}; + } + + uint32_t width{}; + uint32_t height{}; + + TIFFGetField(data, TIFFTAG_IMAGELENGTH, &height); + TIFFGetField(data, TIFFTAG_IMAGEWIDTH, &width); + + std::vector pixels(width * height * 3); + + for (unsigned y = 0; y < height; y++) { + TIFFReadScanline(data, &pixels[width * 3 * y], y); + } + + TIFFClose(data); + + GLuint texture; + glGenTextures(1, &texture); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, width, height, 0, GL_RGB, GL_FLOAT, pixels.data()); + + return {texture, {width, height}}; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +std::tuple read3DTexture(std::string const& path) { + auto* data = TIFFOpen(path.c_str(), "r"); + + if (!data) { + logger().error("Failed to open TIFF file '{}'", path); + return {0u, {0, 0, 0}}; + } + + uint32_t width{}; + uint32_t height{}; + uint32_t depth{}; + + TIFFGetField(data, TIFFTAG_IMAGELENGTH, &height); + TIFFGetField(data, TIFFTAG_IMAGEWIDTH, &width); + + do { + depth++; + } while (TIFFReadDirectory(data)); + + std::vector pixels(width * height * depth * 3); + + for (unsigned z = 0; z < depth; z++) { + TIFFSetDirectory(data, z); + for (unsigned y = 0; y < height; y++) { + TIFFReadScanline(data, &pixels[width * 3 * y + (3 * width * height * z)], y); + } + } + + TIFFClose(data); + + GLuint texture; + glGenTextures(1, &texture); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_3D, texture); + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + glTexImage3D( + GL_TEXTURE_3D, 0, GL_RGB32F, width, height, depth, 0, GL_RGB, GL_FLOAT, pixels.data()); + + return {texture, {width, height, depth}}; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +} // namespace csp::atmospheres::utils diff --git a/plugins/csp-atmospheres/src/utils.hpp b/plugins/csp-atmospheres/src/utils.hpp new file mode 100644 index 000000000..196d1b3de --- /dev/null +++ b/plugins/csp-atmospheres/src/utils.hpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////// +// This file is part of CosmoScout VR // +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// SPDX-FileCopyrightText: German Aerospace Center (DLR) +// SPDX-License-Identifier: MIT + +#ifndef CSP_ATMOSPHERES_UTILS_HPP +#define CSP_ATMOSPHERES_UTILS_HPP + +#include +#include +#include + +#include + +namespace csp::atmospheres::utils { + +// Loads a 2D tiff texture containing precomputed values for the atmosphere. This is for instance +// used for the transmittance texture. It returns a tuple containing the OpenGL texture handle and +// size of the texture. +std::tuple read2DTexture(std::string const& path); + +// Loads a 3D tiff texture containing precomputed values for the atmosphere. This is for instance +// used for the single scattering texture. It returns a tuple containing the OpenGL texture handle +// and size of the texture. +std::tuple read3DTexture(std::string const& path); +} // namespace csp::atmospheres::utils + +#endif // CSP_ATMOSPHERES_UTILS_HPP diff --git a/plugins/csp-atmospheres/textures/earthLimbLuminance.tif b/plugins/csp-atmospheres/textures/earthLimbLuminance.tif new file mode 100644 index 000000000..6f561f634 Binary files /dev/null and b/plugins/csp-atmospheres/textures/earthLimbLuminance.tif differ diff --git a/plugins/csp-atmospheres/textures/earthLimbLuminance.tif.license b/plugins/csp-atmospheres/textures/earthLimbLuminance.tif.license new file mode 100644 index 000000000..92b2cfd3d --- /dev/null +++ b/plugins/csp-atmospheres/textures/earthLimbLuminance.tif.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 German Aerospace Center (DLR) + +SPDX-License-Identifier: CC0-1.0 diff --git a/plugins/csp-lod-bodies/src/LODVisitor.hpp b/plugins/csp-lod-bodies/src/LODVisitor.hpp index b4de68192..889d80039 100644 --- a/plugins/csp-lod-bodies/src/LODVisitor.hpp +++ b/plugins/csp-lod-bodies/src/LODVisitor.hpp @@ -8,7 +8,7 @@ #ifndef CSP_LOD_BODIES_LODVISITOR_HPP #define CSP_LOD_BODIES_LODVISITOR_HPP -#include "Frustum.hpp" +#include "../../../../src/cs-utils/Frustum.hpp" #include "TileId.hpp" #include "TileVisitor.hpp" @@ -54,10 +54,10 @@ class LODVisitor : public TileVisitor { private: /// Struct storing camera information. struct CameraData { - Frustum mFrustumES; // frustum in eye space - Frustum mFrustumMS; // frustum in model space - glm::f64mat3x3 mMatN; - glm::dvec3 mCamPos; + cs::utils::Frustum mFrustumES; // frustum in eye space + cs::utils::Frustum mFrustumMS; // frustum in model space + glm::f64mat3x3 mMatN; + glm::dvec3 mCamPos; }; bool preTraverse() override; diff --git a/resources/textures/earthShadow.tif b/resources/textures/earthShadow.tif new file mode 100644 index 000000000..637193359 Binary files /dev/null and b/resources/textures/earthShadow.tif differ diff --git a/resources/textures/earthShadow.tif.license b/resources/textures/earthShadow.tif.license new file mode 100644 index 000000000..92b2cfd3d --- /dev/null +++ b/resources/textures/earthShadow.tif.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 German Aerospace Center (DLR) + +SPDX-License-Identifier: CC0-1.0 diff --git a/resources/textures/fallbackShadow.hdr b/resources/textures/fallbackShadow.hdr deleted file mode 100644 index b9f1341a5..000000000 Binary files a/resources/textures/fallbackShadow.hdr and /dev/null differ diff --git a/resources/textures/fallbackShadow.tif b/resources/textures/fallbackShadow.tif new file mode 100644 index 000000000..bffb1f0c6 Binary files /dev/null and b/resources/textures/fallbackShadow.tif differ diff --git a/resources/textures/fallbackShadow.hdr.license b/resources/textures/fallbackShadow.tif.license similarity index 100% rename from resources/textures/fallbackShadow.hdr.license rename to resources/textures/fallbackShadow.tif.license diff --git a/resources/textures/marsShadow.tif b/resources/textures/marsShadow.tif new file mode 100644 index 000000000..d8930cb4e Binary files /dev/null and b/resources/textures/marsShadow.tif differ diff --git a/resources/textures/marsShadow.tif.license b/resources/textures/marsShadow.tif.license new file mode 100644 index 000000000..92b2cfd3d --- /dev/null +++ b/resources/textures/marsShadow.tif.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 German Aerospace Center (DLR) + +SPDX-License-Identifier: CC0-1.0 diff --git a/src/cs-core/GraphicsEngine.cpp b/src/cs-core/GraphicsEngine.cpp index e5fc8313a..170289160 100644 --- a/src/cs-core/GraphicsEngine.cpp +++ b/src/cs-core/GraphicsEngine.cpp @@ -71,7 +71,7 @@ GraphicsEngine::GraphicsEngine(std::shared_ptr settings) : mSettings(std::move(settings)) , mShadowMap(std::make_shared()) , mFallbackEclipseShadowMap( - graphics::TextureLoader::loadFromFile("../share/resources/textures/fallbackShadow.hdr")) { + graphics::TextureLoader::loadFromFile("../share/resources/textures/fallbackShadow.tif")) { // Tell the user what's going on. logger().debug("Creating GraphicsEngine."); diff --git a/src/cs-graphics/TextureLoader.cpp b/src/cs-graphics/TextureLoader.cpp index 5d24ac234..a6f6f8ce9 100644 --- a/src/cs-graphics/TextureLoader.cpp +++ b/src/cs-graphics/TextureLoader.cpp @@ -61,19 +61,6 @@ std::unique_ptr TextureLoader::loadFromFile(std::string const& sFi int16 channels{}; TIFFGetField(data, TIFFTAG_SAMPLESPERPIXEL, &channels); - if (bpp != 8) { - logger().error( - "Failed to load '{}' with libtiff: Only 8 bit per sample are supported right now!", - sFileName); - return nullptr; - } - - std::vector pixels(width * height * channels); - - for (unsigned y = 0; y < height; y++) { - TIFFReadScanline(data, &pixels[width * channels * y], y); - } - GLenum ePixelFormat = GL_RGBA; if (channels == 1) { @@ -84,7 +71,36 @@ std::unique_ptr TextureLoader::loadFromFile(std::string const& sFi ePixelFormat = GL_RGB; } - result->UploadTexture(width, height, pixels.data(), true, ePixelFormat); + if (bpp != 8 && bpp != 32) { + logger().error( + "Failed to load '{}' with libtiff: Only 8 or 32 bit per sample are supported right now!", + sFileName); + return nullptr; + } + + if (bpp == 32) { + + std::vector pixels(width * height * channels); + + for (unsigned y = 0; y < height; y++) { + TIFFReadScanline(data, &pixels[width * channels * y], y); + } + + result->Bind(); + glTexImage2D( + GL_TEXTURE_2D, 0, GL_RGB32F, width, height, 0, ePixelFormat, GL_FLOAT, pixels.data()); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + } else { + + std::vector pixels(width * height * channels); + + for (unsigned y = 0; y < height; y++) { + TIFFReadScanline(data, &pixels[width * channels * y], y); + } + + result->UploadTexture(width, height, pixels.data(), true, ePixelFormat); + } TIFFClose(data); diff --git a/plugins/csp-lod-bodies/src/Frustum.cpp b/src/cs-utils/Frustum.cpp similarity index 98% rename from plugins/csp-lod-bodies/src/Frustum.cpp rename to src/cs-utils/Frustum.cpp index 806e2008e..d64b93a55 100644 --- a/plugins/csp-lod-bodies/src/Frustum.cpp +++ b/src/cs-utils/Frustum.cpp @@ -11,7 +11,7 @@ #include -namespace csp::lodbodies { +namespace cs::utils { //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -112,4 +112,4 @@ std::ostream& operator<<(std::ostream& os, Frustum const& frustum) { //////////////////////////////////////////////////////////////////////////////////////////////////// -} // namespace csp::lodbodies +} // namespace cs::utils diff --git a/plugins/csp-lod-bodies/src/Frustum.hpp b/src/cs-utils/Frustum.hpp similarity index 89% rename from plugins/csp-lod-bodies/src/Frustum.hpp rename to src/cs-utils/Frustum.hpp index 0b442d20a..4e320e33f 100644 --- a/plugins/csp-lod-bodies/src/Frustum.hpp +++ b/src/cs-utils/Frustum.hpp @@ -5,15 +5,17 @@ // SPDX-FileCopyrightText: German Aerospace Center (DLR) // SPDX-License-Identifier: MIT -#ifndef CSP_LOD_BODIES_FRUSTUM_HPP -#define CSP_LOD_BODIES_FRUSTUM_HPP +#ifndef CS_UTILS_FRUSTUM_HPP +#define CS_UTILS_FRUSTUM_HPP + +#include "cs_utils_export.hpp" #include #include #include -namespace csp::lodbodies { +namespace cs::utils { enum class FrustumPlaneIdx { eLeft = 0, eRight = 1, eBottom = 2, eTop = 3 }; @@ -22,7 +24,7 @@ std::ostream& operator<<(std::ostream& os, FrustumPlaneIdx fpi); /// Stores a (view) frustum as the intersection of six planes. The planes' normals point inside the /// frustum. A plane is represented as a `glm::fvec4` where the `xyz` components contain the (unit /// length) normal and the `w` component contains the distance from the origin. -class Frustum { +class CS_UTILS_EXPORT Frustum { public: static const size_t NUM_PLANES = 4; @@ -48,6 +50,6 @@ class Frustum { std::ostream& operator<<(std::ostream& os, Frustum const& frustum); -} // namespace csp::lodbodies +} // namespace cs::utils -#endif // CSP_LOD_BODIES_FRUSTUM_HPP +#endif // CS_UTILS_FRUSTUM_HPP diff --git a/tools/eclipse-shadow-generator/LimbDarkening.cu b/tools/eclipse-shadow-generator/LimbDarkening.cu index e3b80c50b..da0611c64 100644 --- a/tools/eclipse-shadow-generator/LimbDarkening.cu +++ b/tools/eclipse-shadow-generator/LimbDarkening.cu @@ -8,6 +8,8 @@ #include "LimbDarkening.cuh" #include "math.cuh" +namespace common { + //////////////////////////////////////////////////////////////////////////////////////////////////// void __host__ __device__ LimbDarkening::init() { @@ -37,3 +39,5 @@ double __host__ __device__ LimbDarkening::get(double r) const { } //////////////////////////////////////////////////////////////////////////////////////////////////// + +} // namespace common diff --git a/tools/eclipse-shadow-generator/LimbDarkening.cuh b/tools/eclipse-shadow-generator/LimbDarkening.cuh index 251d49f13..fba0d8213 100644 --- a/tools/eclipse-shadow-generator/LimbDarkening.cuh +++ b/tools/eclipse-shadow-generator/LimbDarkening.cuh @@ -10,21 +10,25 @@ #include -/// This struct implements a simple wavelength-independent limb darkening model. +namespace common { + +// This implements a simple wavelength-independent limb darkening model. class LimbDarkening { public: - /// This computes the average brightness over the entire solar disc by sampling so that the get() - /// method can return normalized values. + // This computes the average brightness over the entire solar disc by sampling so that the get() + // method can return normalized values. void __host__ __device__ init(); - /// Returns the Sun's brightness at the given radial distance to the center of the solar disc - /// between [0...1]. The returned values are normalized so that the average brightness over entire - /// disc is one. + // Returns the Sun's brightness at the given radial distance to the center of the solar disc + // between [0...1]. The returned values are normalized so that the average brightness over entire + // disc is one. double __host__ __device__ get(double r) const; private: double mAverage = 1.0; }; +} // namespace common + #endif // LIMB_DARKENING_HPP \ No newline at end of file diff --git a/tools/eclipse-shadow-generator/README.md b/tools/eclipse-shadow-generator/README.md index 6426c9502..9dfe1fcdc 100644 --- a/tools/eclipse-shadow-generator/README.md +++ b/tools/eclipse-shadow-generator/README.md @@ -1,34 +1,23 @@ - - - # Eclipse Shadow Map Generator -This tool can be used to generate the eclipse shadow maps used by CosmoScout VR. +# Eclipse Shadow Map Generator -## Building - -**Per default, the eclipse shadow map generator is not built. -To build it, you need to pass `-DCS_ECLIPSE_SHADOW_GENERATOR=On` in the make script.** +This tool can be used to generate the eclipse shadow maps used by CosmoScout VR as well as the precomputed limb luminance maps used for rendering the atmosphere around planets when the Sun is in opposition. -Cuda support in CMake is sometimes a bit wonky, so if you run into trouble, you can also try to build the eclipse shadow map generator manually. -This small script may serve as an example on how to do this: +There are two basic types of eclipse shadows: Those which do not consider an atmosphere around the occluder and those which do. +The former are generated according to the paper ["Real-Time Rendering of Eclipses without Incorporation of Atmospheric Effects"](https://onlinelibrary.wiley.com/doi/full/10.1111/cgf.14676). +The latter use an extended version of the Bruneton atmosphere model described in ["Physically Based Real-Time Rendering of Atmospheres using Mie Theory"](https://onlinelibrary.wiley.com/doi/full/10.1111/cgf.15010). -```bash -#!/bin/bash +The limb luminance maps are 3D textures which encode the luminance around the limb of a planet for every possible viewing position in the shadow of the planet. -SRC_DIR="$( cd "$( dirname "$0" )" && pwd )" +## Building -nvcc -ccbin g++-12 -allow-unsupported-compiler -arch=sm_75 -rdc=true \ - -Xcompiler --std=c++17 -Xcompiler \"-Wl,-rpath-link,"$SRC_DIR/../../install/linux-Release/lib"\" \ - -Xcompiler \"-Wl,--disable-new-dtags,-rpath,"$SRC_DIR/../../install/linux-Release/lib"\" "$SRC_DIR"/*.cu \ - -I"$SRC_DIR/../../build/linux-Release/src/cs-utils" \ - -I"$SRC_DIR/../../install/linux-externals-Release/include" \ - -L"$SRC_DIR/../../install/linux-Release/lib" \ - -lcs-utils \ - -o eclipse-shadow-generator -``` +Per default, the eclipse shadow map generator is not built. +To build it, you need to add `"CS_ECLIPSE_SHADOW_GENERATOR": "On",` to the `"cacheVariables"` in [CMakePresets.json](../../CMakePresets.json). +Then it will be built together with the rest of CosmoScout VR. ## Usage @@ -36,28 +25,114 @@ Once compiled, you'll need to set the library search path to contain the `instal This depends on where the `eclipse-shadow-generator` is installed to, but this may be something like this: ```powershell -# For powershell -$env:Path += ";..\lib" +# For Windows (powershell) +$env:Path += ";install\windows-Release\lib" -# For bash -export LD_LIBRARY_PATH=../lib:$LD_LIBRARY_PATH +# For Linux (bash) +export LD_LIBRARY_PATH=install/linux-Release/lib:$LD_LIBRARY_PATH ``` To learn about the usage of `eclipse-shadow-generator`, you can now issue this command: +```bash +install/linux-Release/bin/eclipse-shadow-generator --help +``` + +### Creating the Eclipse Shadow Maps used by CosmoScout VR + +The following commands were used to generate the eclipse shadow maps used by CosmoScout VR. +The `fallbackShadow.tif` is used for all celestial bodies which do not have a specific shadow map. +It includes the effect of limb darkening but no atmosphere. +There are two specific shadow maps for Earth and Mars, which include the effect of limb darkening and the atmosphere of the respective planet. + +The shadow maps for Earth and Mars require the output of the [Bruneton atmosphere model preprocessor](../../plugins/csp-atmospheres/bruneton-preprocessor/README.md) which in turn requires the output of the [scattering-table-generator](../../plugins/csp-atmospheres/scattering-table-generator/README.md). + +You find more information on how to use these tools in the respective READMEs. +For convenience, we provide the commands used to generate all necessary data below. + +
+Scattering-Table Computation + +```bash +# Earth Molecules +install/linux-Release/bin/scattering-table-generator density -i plugins/csp-atmospheres/scattering-table-generator/density-settings/earth_bruneton_molecules.json -o plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_molecules +install/linux-Release/bin/scattering-table-generator rayleigh --scattering-depolarization 0.0279 --phase-depolarization 0.0279 --penndorf-ior --theta-samples 91 -o plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_molecules + +# Earth Aerosols +install/linux-Release/bin/scattering-table-generator density -i plugins/csp-atmospheres/scattering-table-generator/density-settings/earth_bruneton_aerosols.json -o plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_aerosols +install/linux-Release/bin/scattering-table-generator mie -i plugins/csp-atmospheres/scattering-table-generator/mie-settings/earth_haze.json --theta-samples 91 --number-density 5e8 --radius-samples 10000 -o plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_aerosols + +# Earth Ozone +install/linux-Release/bin/scattering-table-generator density -i plugins/csp-atmospheres/scattering-table-generator/density-settings/earth_bruneton_ozone.json -o plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_ozone +install/linux-Release/bin/scattering-table-generator ozone -o plugins/csp-atmospheres/scattering-table-generator/output/earth_cosmoscout_ozone + +# Mars Molecules +install/linux-Release/bin/scattering-table-generator density -i plugins/csp-atmospheres/scattering-table-generator/density-settings/mars_cosmoscout_molecules.json -o plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_molecules_cinematic +install/linux-Release/bin/scattering-table-generator rayleigh --lambdas 440e-9,550e-9,680e-9 --ior 1.00000337 --scattering-depolarization 0.09 --phase-depolarization 0.09 --number-density 2.05e23 --theta-samples 91 -o plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_molecules_cinematic + +# Mars Aerosols +install/linux-Release/bin/scattering-table-generator density -i plugins/csp-atmospheres/scattering-table-generator/density-settings/mars_cosmoscout_aerosols_cinematic.json -o plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_aerosols_cinematic +install/linux-Release/bin/scattering-table-generator mie --lambdas 440e-9,550e-9,680e-9 -i plugins/csp-atmospheres/scattering-table-generator/mie-settings/mars_cinematic.json --phase-flattening 0.8 --theta-samples 91 --number-density 5e9 --radius-samples 10000 -o plugins/csp-atmospheres/scattering-table-generator/output/mars_cosmoscout_aerosols_cinematic +``` + +
+ +
+Atmospheric Scattering Computation ```bash -./eclipse-shadow-generator --help +# Earth +install/linux-Release/bin/bruneton-preprocessor plugins/csp-atmospheres/bruneton-preprocessor/settings/earth.json plugins/csp-atmospheres/bruneton-preprocessor/output/earth + +# Mars +install/linux-Release/bin/bruneton-preprocessor plugins/csp-atmospheres/bruneton-preprocessor/settings/mars.json plugins/csp-atmospheres/bruneton-preprocessor/output/mars ``` -Here are some other examples to get you started: +
+ +
+Shadow-Map Computation ```bash -# This simple command creates the default eclipse shadow map of CosmoScout VR -./eclipse-shadow-generator - -# Here are some other examples -./eclipse-shadow-generator --mode circles --output "circles.hdr" -./eclipse-shadow-generator --mode smoothstep --output "smoothstep.hdr" -./eclipse-shadow-generator --mode linear --with-umbra --mapping-exponent 5 --output "linear_with_umbra.hdr" -``` \ No newline at end of file +# Create the fallback shadow map. +install/linux-Release/bin/eclipse-shadow-generator simple-limb-darkening --with-umbra --output "resources/textures/fallbackShadow.tif" --size 256 + +# Create the shadow maps for Earth and Mars. +install/linux-Release/bin/eclipse-shadow-generator advanced-shadow --with-umbra --input plugins/csp-atmospheres/bruneton-preprocessor/output/earth/ --radius-occ 6371000 --radius-atmo 6451000 --sun-occ-dist 149600000000 --output "resources/textures/earthShadow.tif" --size 256 +install/linux-Release/bin/eclipse-shadow-generator advanced-shadow --with-umbra --input plugins/csp-atmospheres/bruneton-preprocessor/output/mars/ --radius-occ 3389500 --radius-atmo 3469500 --sun-occ-dist 227900000000 --output "resources/textures/marsShadow.tif" --size 256 + +# Create the limb luminance maps for Earth. For Mars it's not required as there is little to no refraction going on. +install/linux-Release/bin/eclipse-shadow-generator advanced-limb-luminance --with-umbra --input plugins/csp-atmospheres/bruneton-preprocessor/output/earth/ --radius-occ 6371000 --radius-atmo 6451000 --sun-occ-dist 149600000000 --output "plugins/csp-atmospheres/textures/earthLimbLuminance.tif" --size 64 --layers 2 +``` + +
+ +### Recreating Paper Figures and other Examples + +These are used for debugging purposes and can be used to visualize the results of the atmosphere rendering. + +```bash +install/linux-Release/bin/eclipse-shadow-generator advanced-planet-view --input plugins/csp-atmospheres/bruneton-preprocessor/output/earth --with-umbra --exposure 0.0001 --x 0.099 --y 0.9 --size 1024 --fov 6 --output "planet-view.tif" +install/linux-Release/bin/eclipse-shadow-generator advanced-atmo-view --input plugins/csp-atmospheres/bruneton-preprocessor/output/earth --with-umbra --exposure 0.0001 --x 0.099 --y 0.9 --size 1024 --output "atmo-view.tif" +``` + +Here are some examples related to the paper "Real-Time Rendering of Eclipses without Incorporation of Atmospheric Effects". + +```bash +install/linux-Release/bin/eclipse-shadow-generator simple-circles --output "circles.tif" +install/linux-Release/bin/eclipse-shadow-generator simple-smoothstep --output "smoothstep.tif" +install/linux-Release/bin/eclipse-shadow-generator simple-linear --with-umbra --mapping-exponent 5 --output "linear_with_umbra.tif" +``` + +For visualization purposes, you can use the following to create an animation of 250 frames where the Sun gradually sets behind the Earth: + +```bash +mkdir output + +for i in {0..150}; do + y=$(echo "scale=4; (150 - $i) / 150" | bc) + echo "Generating frame $i with delta $delta" + install/linux-Release/bin/eclipse-shadow-generator advanced-atmo-view --input ../share/resources/atmosphere-data/earth/ --output "output/shadow_$i.tif" --exposure 0.00005 --x 0.3 --y $y --with-umbra --size 1024 +done + +``` diff --git a/tools/eclipse-shadow-generator/advanced_modes.cu b/tools/eclipse-shadow-generator/advanced_modes.cu new file mode 100644 index 000000000..b0369523f --- /dev/null +++ b/tools/eclipse-shadow-generator/advanced_modes.cu @@ -0,0 +1,535 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////// +// This file is part of CosmoScout VR // +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// SPDX-FileCopyrightText: German Aerospace Center (DLR) +// SPDX-License-Identifier: MIT + +#include "advanced_modes.cuh" + +#include "atmosphere_rendering.cuh" +#include "common.hpp" +#include "gpuErrCheck.hpp" +#include "math.cuh" +#include "tiff_utils.hpp" + +#include +#include + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +namespace { + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +enum class Mode { eShadow, eLimbLuminance, ePlanetView, eAtmoView }; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// Tonemapping code and color space conversions. +// http://filmicworlds.com/blog/filmic-tonemapping-operators/ + +__device__ glm::vec3 uncharted2Tonemap(glm::vec3 color) { + const float A = 0.15; + const float B = 0.50; + const float C = 0.10; + const float D = 0.20; + const float E = 0.02; + const float F = 0.30; + return ((color * (A * color + C * B) + D * E) / (color * (A * color + B) + D * F)) - E / F; +} + +__device__ glm::vec3 tonemap(glm::vec3 color) { + const float W = 11.2; + color = uncharted2Tonemap(10.0f * color); + glm::vec3 whiteScale = glm::vec3(1.0) / uncharted2Tonemap(glm::vec3(W)); + return color * whiteScale; +} + +__device__ float linearToSRGB(float value) { + if (value <= 0.0031308f) + return 12.92f * value; + else + return 1.055f * pow(value, 1.0f / 2.4f) - 0.055f; +} + +__device__ glm::vec3 linearToSRGB(glm::vec3 color) { + return glm::vec3(linearToSRGB(color.r), linearToSRGB(color.g), linearToSRGB(color.b)); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +double __device__ getSunIlluminance(double sunDistance) { + const double sunLuminousPower = 3.75e28; + return sunLuminousPower / (4.0 * glm::pi() * sunDistance * sunDistance); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +__global__ void computeShadowMap(common::Output output, common::Mapping mapping, + common::Geometry geometry, common::LimbDarkening limbDarkening, advanced::Textures textures) { + + uint32_t uShadow = blockIdx.x * blockDim.x + threadIdx.x; + uint32_t vShadow = blockIdx.y * blockDim.y + threadIdx.y; + uint32_t i = vShadow * output.mSize + uShadow; + + if ((uShadow >= output.mSize) || (vShadow >= output.mSize)) { + return; + } + + // For integrating the luminance over all directions, we render an image of the atmosphere from + // the perspective of the point in space. We use a parametrization of the texture space which + // contains exactly on half of the atmosphere as seen from the point. The individual sample points + // are weighted by the solid angle they cover on the sphere around the point. + // + // ┌---.. + // vLimb │ ' + // └--. \ + // uLimb \ │ + // │ │ + // / │ + // ┌--' / + // │ . + // └---'' + // + // We use this resolution for the integration: + uint32_t samplesULimb = 256; + uint32_t samplesVLimb = 256; + + // First, compute the angular radii of Sun and occluder as well as the angle between the two. + double phiOcc, phiSun, delta; + math::mapPixelToAngles( + glm::ivec2(uShadow, vShadow), output.mSize, mapping, geometry, phiOcc, phiSun, delta); + + // Make sure to stick to positions outside the atmosphere. + double occDist = glm::max(geometry.mRadiusOcc / glm::sin(phiOcc), geometry.mRadiusAtmo); + double sunDist = geometry.mRadiusSun / glm::sin(phiSun); + double atmoRadius = geometry.mRadiusAtmo; + double phiAtmo = glm::asin(atmoRadius / occDist); + + glm::dvec3 camera = glm::dvec3(0.0, 0.0, occDist); + glm::dvec3 sunDirection = glm::dvec3(0.0, glm::sin(delta), -glm::cos(delta)); + + glm::vec3 indirectIlluminance(0.0); + + for (uint32_t sampleV = 0; sampleV < samplesVLimb; ++sampleV) { + double vLimb = (static_cast(sampleV) + 0.5) / samplesVLimb; + double upperBound = (static_cast(sampleV) + 1.0) / samplesVLimb; + double lowerBound = static_cast(sampleV) / samplesVLimb; + double upperPhiRay = phiOcc + upperBound * (phiAtmo - phiOcc); + double lowerPhiRay = phiOcc + lowerBound * (phiAtmo - phiOcc); + double rowSolidAngle = 0.5 * (math::getCapArea(upperPhiRay) - math::getCapArea(lowerPhiRay)); + double sampleSolidAngle = rowSolidAngle / samplesULimb; + + for (uint32_t sampleU = 0; sampleU < samplesULimb; ++sampleU) { + + double beta = ((static_cast(sampleU) + 0.5) / samplesULimb) * M_PI; + + // Compute the direction of the ray. + double phiRay = phiOcc + vLimb * (phiAtmo - phiOcc); + glm::dvec3 rayDir = glm::dvec3(0.0, glm::sin(phiRay), -glm::cos(phiRay)); + rayDir = + glm::normalize(math::rotateVector(rayDir, glm::dvec3(0.0, 0.0, -1.0), glm::cos(beta))); + + glm::vec3 luminance = advanced::getLuminance( + camera, rayDir, sunDirection, geometry, limbDarkening, textures, phiSun); + + indirectIlluminance += luminance * static_cast(sampleSolidAngle); + } + } + + // We only computed half of the atmosphere, so we multiply the result by two. + indirectIlluminance *= 2.0; + + // We now have the light which reaches our point through the atmosphere. However, there is also a + // certain amount of direct sunlight which reaches the point from paths which do not intersect the + // atmosphere. We use the formula from the simple mode to compute the visible fraction of the Sun + // above the upper atmosphere boundary. + + double sunArea = math::getCircleArea(1.0); + double radiusOcc, distance; + math::mapPixelToRadii(glm::ivec2(uShadow, vShadow), output.mSize, mapping, radiusOcc, distance); + double radiusAtmo = radiusOcc * geometry.mRadiusAtmo / geometry.mRadiusOcc; + double visibleFraction = + 1.0 - math::sampleCircleIntersection(1.0, radiusAtmo, distance, limbDarkening) / sunArea; + + // We multiply this fraction with the illuminance of the Sun to get the direct + // illuminance. + double fullIlluminance = getSunIlluminance(sunDist); + double directIlluminance = fullIlluminance * visibleFraction; + + // We add the direct and indirect illuminance to get the total illuminance. + glm::dvec3 totalIlluminance = indirectIlluminance + glm::vec3(directIlluminance); + + // And divide by the illuminance at the point if there were no atmosphere and no planet. + output.mBuffer[i * 3 + 0] = totalIlluminance.r / fullIlluminance; + output.mBuffer[i * 3 + 1] = totalIlluminance.g / fullIlluminance; + output.mBuffer[i * 3 + 2] = totalIlluminance.b / fullIlluminance; + + // Print a rough progress estimate. + if (i % 1000 == 0) { + printf("Progress: %f%%\n", (i / static_cast(output.mSize * output.mSize)) * 100.0); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +__global__ void computeLimbLuminance(common::Output output, common::Mapping mapping, + common::Geometry geometry, common::LimbDarkening limbDarkening, advanced::Textures textures, + int layers) { + + uint32_t x = blockIdx.x * blockDim.x + threadIdx.x; + uint32_t y = blockIdx.y * blockDim.y + threadIdx.y; + uint32_t z = blockIdx.z * blockDim.z + threadIdx.z; + uint32_t i = z * output.mSize * output.mSize + y * output.mSize + x; + + if ((x >= output.mSize) || (y >= output.mSize) || (z >= output.mSize * layers)) { + return; + } + + // For precomputing the atmosphere's luminance for every position in the shadow volume, we render + // an image of the atmosphere from the perspective of the point in space. We use a parametrization + // of the texture space which contains exactly on half of the atmosphere as seen from the point. + // We render with a relatively high resolution in the vertical direction to capture even a small + // refracted image of the Sun. The output texture however only contain a few layers in the vLimb + // direction to keep the memory requirements low. For this, we render each layer separately and + // integrate the luminance over the vLimb direction. + // + // uLimb - . + // ^ ┌---.. ' + // vLimb │ ├ -. /' \ + // │ └--./ . \ │ + // │β⁠/ \ . │ V + // o │ . │ + // / . │ + // ┌--' . / + // ├ - ' . + // └---'' + // + // The output texture is a 4D texture stored in a 3D texture: The x and y coordinates are the + // usual shadow map coordinates, and the z coordinate contains the layers of the atmosphere image + // around the planet. The resolution of the texture is [output.mSize, output.mSize, output.mSize * + // layers]. + uint32_t samplesULimb = output.mSize; + + // We use this many vertical samples for each layer. + uint32_t samplesVLimb = 256; + + // This thread computes the luminance for this layer and this position along the atmosphere ring. + uint32_t layer = z / output.mSize; + uint32_t sampleU = z % output.mSize; + + // First, compute the angular radii of Sun and occluder as well as the angle between the two. + double phiOcc, phiSun, delta; + math::mapPixelToAngles(glm::ivec2(x, y), output.mSize, mapping, geometry, phiOcc, phiSun, delta); + + // Make sure to stick to positions outside the atmosphere. + double occDist = glm::max(geometry.mRadiusOcc / glm::sin(phiOcc), geometry.mRadiusAtmo); + double sunDist = geometry.mRadiusSun / glm::sin(phiSun); + double atmoRadius = geometry.mRadiusAtmo; + double phiAtmo = glm::asin(atmoRadius / occDist); + + glm::dvec3 camera = glm::dvec3(0.0, 0.0, occDist); + glm::dvec3 sunDirection = glm::dvec3(0.0, glm::sin(delta), -glm::cos(delta)); + + double beta = ((static_cast(sampleU) + 0.5) / samplesULimb) * M_PI; + + glm::vec3 luminance(0.0); + + for (uint32_t sampleV = 0; sampleV < samplesVLimb; ++sampleV) { + double vLimb = (static_cast(sampleV) + 0.5) / samplesVLimb; + + double layerStart = static_cast(layer) / layers; + double layerEnd = static_cast(layer + 1) / layers; + vLimb = layerStart + vLimb * (layerEnd - layerStart); + + // Compute the direction of the ray. + double phiRay = phiOcc + vLimb * (phiAtmo - phiOcc); + glm::dvec3 rayDir = glm::dvec3(0.0, glm::sin(phiRay), -glm::cos(phiRay)); + rayDir = glm::normalize(math::rotateVector(rayDir, glm::dvec3(0.0, 0.0, -1.0), glm::cos(beta))); + + luminance += advanced::getLuminance( + camera, rayDir, sunDirection, geometry, limbDarkening, textures, phiSun) / + static_cast(samplesVLimb); + } + + // And divide by the illuminance at the point if there were no atmosphere and no planet. + output.mBuffer[i * 3 + 0] = luminance.r; + output.mBuffer[i * 3 + 1] = luminance.g; + output.mBuffer[i * 3 + 2] = luminance.b; + + // Print a rough progress estimate. + if (i % 1000 == 0) { + printf("Progress: %f%%\n", + (i / static_cast(output.mSize * output.mSize * output.mSize * layers)) * 100.0); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +__global__ void drawAtmoView(common::Mapping mapping, common::Geometry geometry, float exposure, + double phiOcc, double phiSun, double delta, common::Output output, + common::LimbDarkening limbDarkening, advanced::Textures textures) { + + uint32_t x = blockIdx.x * blockDim.x + threadIdx.x; + uint32_t y = blockIdx.y * blockDim.y + threadIdx.y; + uint32_t i = y * output.mSize + x; + + if ((x >= output.mSize) || (y >= output.mSize)) { + return; + } + + double occDist = geometry.mRadiusOcc / glm::sin(phiOcc); + double atmoRadius = geometry.mRadiusAtmo; + double phiAtmo = glm::asin(atmoRadius / occDist); + + glm::dvec3 camera = glm::dvec3(0.0, 0.0, occDist); + glm::dvec3 sunDirection = glm::dvec3(0.0, glm::sin(delta), -glm::cos(delta)); + + // Compute the direction of the ray. + double beta = (x / static_cast(output.mSize)) * M_PI; + double vLimb = (y / static_cast(output.mSize)); + + double phiRay = phiOcc + vLimb * (phiAtmo - phiOcc); + glm::dvec3 rayDir = glm::dvec3(0.0, glm::sin(phiRay), -glm::cos(phiRay)); + rayDir = glm::normalize(math::rotateVector(rayDir, glm::dvec3(0.0, 0.0, -1.0), glm::cos(beta))); + + glm::vec3 luminance = advanced::getLuminance( + camera, rayDir, sunDirection, geometry, limbDarkening, textures, phiSun); + + luminance = linearToSRGB(tonemap(luminance * exposure)); + + output.mBuffer[i * 3 + 0] = luminance.r; + output.mBuffer[i * 3 + 1] = luminance.g; + output.mBuffer[i * 3 + 2] = luminance.b; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +__global__ void drawPlanet(common::Mapping mapping, common::Geometry geometry, float exposure, + double phiOcc, double phiSun, double delta, float fov, common::Output output, + common::LimbDarkening limbDarkening, advanced::Textures textures) { + + uint32_t x = blockIdx.x * blockDim.x + threadIdx.x; + uint32_t y = blockIdx.y * blockDim.y + threadIdx.y; + uint32_t i = y * output.mSize + x; + + if ((x >= output.mSize) || (y >= output.mSize)) { + return; + } + + // Total eclipse from Moon, horizon close up. + double occDist = geometry.mRadiusOcc / glm::sin(phiOcc); + glm::dvec3 camera = glm::dvec3(0.0, 0.0, occDist); + double fieldOfView = fov * M_PI / 180.0; + glm::dvec3 sunDirection = glm::dvec3(0.0, glm::sin(delta), -glm::cos(delta)); + + // Compute the direction of the ray. + double theta = (x / static_cast(output.mSize) - 0.5) * fieldOfView; + double phi = (y / static_cast(output.mSize) - 0.5) * fieldOfView; + + glm::dvec3 rayDir = + glm::dvec3(glm::sin(theta) * glm::cos(phi), glm::sin(phi), -glm::cos(theta) * glm::cos(phi)); + + glm::vec3 luminance = advanced::getLuminance( + camera, rayDir, sunDirection, geometry, limbDarkening, textures, phiSun); + + luminance = linearToSRGB(tonemap(luminance * exposure)); + + output.mBuffer[i * 3 + 0] = luminance.r; + output.mBuffer[i * 3 + 1] = luminance.g; + output.mBuffer[i * 3 + 2] = luminance.b; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +int run(Mode mode, std::vector const& arguments) { + + std::string input; + common::Mapping mapping; + common::Output output; + common::Geometry geometry; + bool printHelp = false; + + // These are only required for the planet or atmosphere view modes. + float exposure = 0.0001; // The exposure of the image used during tonemapping. + float x = 0.5; // The shadow map x coordinate for which to render the view. + float y = 0.5; // The shadow map y coordinate for which to render the view. + + // This is only required for the planet view mode. + float fov = 45.0; // The field of view of the camera in degrees. + + // This is only required for the limb luminance mode. + int limbLuminanceLayers = 1; // The number of vertical layers in the limb luminance texture. + + // First configure all possible command line options. + cs::utils::CommandLine args("Here are the available options:"); + common::addMappingFlags(args, mapping); + common::addOutputFlags(args, output); + common::addGeometryFlags(args, geometry); + + args.addArgument({"--input"}, &input, "The path to the atmosphere settings directory."); + + if (mode == Mode::eAtmoView || mode == Mode::ePlanetView) { + args.addArgument({"--exposure"}, &exposure, + "The exposure of the image. Default is " + std::to_string(exposure)); + args.addArgument({"--x"}, &x, + "The shadow map x coordinate for which to render the view. " + "Default is " + + std::to_string(x)); + args.addArgument({"--y"}, &y, + "The shadow map y coordinate for which to render the view. " + "Default is " + + std::to_string(y)); + } + + if (mode == Mode::ePlanetView) { + args.addArgument({"--fov"}, &fov, + "The field of view of the camera in degrees. Default is " + std::to_string(fov)); + } + + if (mode == Mode::eLimbLuminance) { + args.addArgument({"--layers"}, &limbLuminanceLayers, + "The number of vertical layers in the limb luminance texture. Default is " + + std::to_string(limbLuminanceLayers)); + } + + args.addArgument({"-h", "--help"}, &printHelp, "Show this help message."); + + // Then do the actual parsing. + try { + args.parse(arguments); + } catch (std::runtime_error const& e) { + std::cerr << "Failed to parse command line arguments: " << e.what() << std::endl; + return 1; + } + + // When printHelp was set to true, we print a help message and exit. + if (printHelp) { + args.printHelp(); + return 0; + } + + // If we are in atmosphere mode, we need also the atmosphere settings. + if (input.empty()) { + std::cerr << "When using the 'bruneton', 'planet-view', or 'atmo-view' mode, you must provide " + "the path to the atmosphere settings directory using --input!" + << std::endl; + return 1; + } + + // Load the atmosphere settings. + auto textures = advanced::loadTextures(input); + + // Initialize the limb darkening model. + common::LimbDarkening limbDarkening; + limbDarkening.init(); + + // Compute the 2D kernel size. + dim3 blockSize(8, 8, mode == Mode::eLimbLuminance ? 8 : 1); + uint32_t numBlocksX = (output.mSize + blockSize.x - 1) / blockSize.x; + uint32_t numBlocksY = (output.mSize + blockSize.y - 1) / blockSize.y; + uint32_t numBlocksZ = mode == Mode::eLimbLuminance + ? (output.mSize * limbLuminanceLayers + blockSize.z - 1) / blockSize.z + : 1; + dim3 gridSize = dim3(numBlocksX, numBlocksY, numBlocksZ); + + // Allocate the shared memory for the shadow map. + if (mode == Mode::eLimbLuminance) { + gpuErrchk(cudaMallocManaged(&output.mBuffer, + static_cast(output.mSize * output.mSize * output.mSize * limbLuminanceLayers) * 3 * + sizeof(float))); + } else { + gpuErrchk(cudaMallocManaged( + &output.mBuffer, static_cast(output.mSize * output.mSize) * 3 * sizeof(float))); + } + + if (mode == Mode::eShadow) { + computeShadowMap<<>>(output, mapping, geometry, limbDarkening, textures); + } else if (mode == Mode::eLimbLuminance) { + computeLimbLuminance<<>>( + output, mapping, geometry, limbDarkening, textures, limbLuminanceLayers); + } else { + + double phiOcc, phiSun, delta; + glm::ivec2 pixel(x * output.mSize, y * output.mSize); + uint32_t iterations = + math::mapPixelToAngles(pixel, output.mSize, mapping, geometry, phiOcc, phiSun, delta); + + if (iterations == 0) { + std::cerr << "The given pixel is in an impossible configuration." << std::endl; + return 1; + } + + std::cout << "Required " << iterations << " iterations to find the correct angles for pixel (" + << pixel.x << ", " << pixel.y << ")." << std::endl; + std::cout << " - Observer Angular Radius: " << glm::degrees(phiOcc) << "°" << std::endl; + std::cout << " - Observer Distance: " << geometry.mRadiusOcc / glm::sin(phiOcc) * 0.001 << " km" + << std::endl; + std::cout << " - Sun Angular Radius: " << glm::degrees(phiSun) << "°" << std::endl; + std::cout << " - Sun Elevation: " << glm::degrees(delta) << "°" << std::endl; + + if (mode == Mode::ePlanetView) { + drawPlanet<<>>( + mapping, geometry, exposure, phiOcc, phiSun, delta, fov, output, limbDarkening, textures); + } else if (mode == Mode::eAtmoView) { + drawAtmoView<<>>( + mapping, geometry, exposure, phiOcc, phiSun, delta, output, limbDarkening, textures); + } + } + + gpuErrchk(cudaPeekAtLastError()); + gpuErrchk(cudaDeviceSynchronize()); + + // Finally write the output texture! + if (mode == Mode::eLimbLuminance) { + tiff_utils::write3D(output.mFile, output.mBuffer, static_cast(output.mSize), + static_cast(output.mSize), static_cast(output.mSize * limbLuminanceLayers), 3); + } else { + tiff_utils::write2D(output.mFile, output.mBuffer, static_cast(output.mSize), + static_cast(output.mSize), 3); + } + + // Free the shared memory. + gpuErrchk(cudaFree(output.mBuffer)); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +} // namespace + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +namespace advanced { + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +int shadowMode(std::vector const& arguments) { + return run(Mode::eShadow, arguments); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +int planetViewMode(std::vector const& arguments) { + return run(Mode::ePlanetView, arguments); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +int atmoViewMode(std::vector const& arguments) { + return run(Mode::eAtmoView, arguments); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +int limbLuminanceMode(std::vector const& arguments) { + return run(Mode::eLimbLuminance, arguments); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +} // namespace advanced + +//////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/tools/eclipse-shadow-generator/advanced_modes.cuh b/tools/eclipse-shadow-generator/advanced_modes.cuh new file mode 100644 index 000000000..8cb22bc88 --- /dev/null +++ b/tools/eclipse-shadow-generator/advanced_modes.cuh @@ -0,0 +1,62 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////// +// This file is part of CosmoScout VR // +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// SPDX-FileCopyrightText: German Aerospace Center (DLR) +// SPDX-License-Identifier: MIT + +#ifndef ADVANCED_MODES_HPP +#define ADVANCED_MODES_HPP + +#include +#include + +// The "advanced" modes compute eclipse shadows for spherical bodies which have an atmosphere. The +// "shadow" mode uses CosmoScout VR's extended Bruneton atmospheric scattering model to compute +// the shadow map. The required input data is precomputed using the "bruneton-preprocessor" tool of +// the csp-atmospheres plugin. The "limbLuminance" mode computes the vertically integrated luminance +// of the atmosphere ring around the planet for each position in the shadow map. This is used to +// render the atmosphere from within the planets shadow without artifacts. The "planetView" and +// "atmoView" modes render the atmosphere of a planet from the perspective of a given location in +// the shadow map for debugging and visualization purposes. +// +// Similar to the simple modes, the shadow map contains values between 0 and 1, where 0 means that +// that no light reaches that point in space and 1 means that no sunlight is blocked. To compute +// this, the illuminance at the point is computed by integrating the luminance of the Sun and the +// atmosphere over all directions around the point. This value is then divided by the illuminance at +// the point if there were no atmosphere and no planet. The result is the RGB color of the shadow +// map. + +namespace advanced { + +// Computes the shadow map evaluating our extended Bruneton precomputed atmospheric scattering model +// for each position in the shadow map. +int shadowMode(std::vector const& arguments); + +// Computes the luminance of the atmosphere as seen from every position in the shadow map. The +// result is a 4D texture: The x and y coordinates are the usual shadow map coordinates, and the z +// coordinate contains several pixel strips of the atmosphere image around the planet. Like this: +// strips - . +// ^ ┌---.. ' +// layers │ ├ - . ' \ +// │ └--. `. \ │ +// \ . │ V +// │ . │ +// / . │ +// ┌--' . / +// ├ - ' . +// └---'' +int limbLuminanceMode(std::vector const& arguments); + +// Draws the atmosphere of a planet into a texture as seen through a pinhole camera. The atmospheric +// scattering data and the position of the observer in shadow map coordinates are given via the +// command line arguments. +int planetViewMode(std::vector const& arguments); + +// Same as planetMode, but an angular parametrization is used so that the atmosphere fills the +// entire texture regardless of the observer's position. +int atmoViewMode(std::vector const& arguments); + +} // namespace advanced + +#endif // ADVANCED_MODES_HPP \ No newline at end of file diff --git a/tools/eclipse-shadow-generator/atmosphere_rendering.cu b/tools/eclipse-shadow-generator/atmosphere_rendering.cu new file mode 100644 index 000000000..08e7ece53 --- /dev/null +++ b/tools/eclipse-shadow-generator/atmosphere_rendering.cu @@ -0,0 +1,390 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////// +// This file is part of CosmoScout VR // +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// SPDX-FileCopyrightText: German Aerospace Center (DLR) +// SPDX-FileCopyrightText: 2017 Eric Bruneton +// SPDX-FileCopyrightText: 2008 INRIA +// SPDX-License-Identifier: BSD-3-Clause + +// Parts of this file are based on the original implementation by Eric Bruneton: +// https://github.com/ebruneton/precomputed_atmospheric_scattering/blob/master/atmosphere/functions.glsl + +// It has been ported to CUDA and in some cases it has been simplified as we are only interested in +// vantage points from outer space. + +// All methods which are based on the original implementation by Eric Bruneton are marked with a +// corresponding comment and a link to the original source code. + +#include "atmosphere_rendering.cuh" + +#include "tiff_utils.hpp" + +#include +#include + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +namespace { + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// Returns the luminance of the Sun in candela per square meter. +double __device__ getSunLuminance(double sunRadius) { + const double sunLuminousPower = 3.75e28; + const double sunLuminousExitance = + sunLuminousPower / (sunRadius * sunRadius * 4.0 * glm::pi()); + return sunLuminousExitance / glm::pi(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// Creates a CUDA texture object from the given RGBA texture. +cudaTextureObject_t createCudaTexture(tiff_utils::RGBATexture const& texture) { + cudaArray* cuArray; + auto channelDesc = cudaCreateChannelDesc(); + + cudaMallocArray(&cuArray, &channelDesc, texture.width, texture.height); + cudaMemcpy2DToArray(cuArray, 0, 0, texture.data.data(), texture.width * sizeof(float) * 4, + texture.width * sizeof(float) * 4, texture.height, cudaMemcpyHostToDevice); + + cudaResourceDesc resDesc = {}; + resDesc.resType = cudaResourceTypeArray; + resDesc.res.array.array = cuArray; + + cudaTextureDesc texDesc = {}; + texDesc.addressMode[0] = cudaAddressModeClamp; + texDesc.addressMode[1] = cudaAddressModeClamp; + texDesc.filterMode = cudaFilterModeLinear; + texDesc.readMode = cudaReadModeElementType; + texDesc.normalizedCoords = 1; + + cudaTextureObject_t textureObject = 0; + cudaCreateTextureObject(&textureObject, &resDesc, &texDesc, nullptr); + + gpuErrchk(cudaGetLastError()); + + return textureObject; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// Provides a similar API to the texture2D function in GLSL. Returns the RGBA value at the given +// texture coordinates as a glm::vec4. +__device__ glm::vec4 texture2D(cudaTextureObject_t tex, glm::vec2 uv) { + auto data = tex2D(tex, uv.x, uv.y); + return glm::vec4(data.x, data.y, data.z, data.w); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// In case the input value is negative, this function returns 0.0. Otherwise it returns the square +// root of the input value. +__device__ float safeSqrt(float a) { + return glm::sqrt(glm::max(a, 0.0f)); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// https://github.com/ebruneton/precomputed_atmospheric_scattering/blob/master/atmosphere/functions.glsl#L342 +__device__ float getTextureCoordFromUnitRange(float x, int textureSize) { + return 0.5 / float(textureSize) + x * (1.0 - 1.0 / float(textureSize)); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// https://github.com/ebruneton/precomputed_atmospheric_scattering/blob/master/atmosphere/functions.glsl#L207 +__device__ float distanceToTopAtmosphereBoundary( + common::Geometry const& geometry, float r, float mu) { + float discriminant = r * r * (mu * mu - 1.0) + geometry.mRadiusAtmo * geometry.mRadiusAtmo; + return glm::max(0.f, -r * mu + safeSqrt(discriminant)); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// As we are always in outer space, this function does not need the r parameter when compared to the +// original version: +// https://github.com/ebruneton/precomputed_atmospheric_scattering/blob/master/atmosphere/functions.glsl#L402 +__device__ glm::vec2 getTransmittanceTextureUvFromRMu( + advanced::Textures const& textures, common::Geometry const& geometry, double mu) { + + // Distance to top atmosphere boundary for a horizontal ray at ground level. + double H = + sqrt(geometry.mRadiusAtmo * geometry.mRadiusAtmo - geometry.mRadiusOcc * geometry.mRadiusOcc); + + // Distance to the top atmosphere boundary for the ray (r,mu), and its minimum + // and maximum values over all mu - obtained for (r,1) and (r,mu_horizon). + double d = distanceToTopAtmosphereBoundary(geometry, geometry.mRadiusAtmo, mu); + double dMax = 2.0 * H; + double xMu = d / dMax; + return glm::vec2(getTextureCoordFromUnitRange(xMu, textures.mTransmittanceTextureWidth), + getTextureCoordFromUnitRange(1.0, textures.mTransmittanceTextureHeight)); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// As we are always in outer space, this function does not need the r parameter when compared to the +// original version: +// https://github.com/ebruneton/precomputed_atmospheric_scattering/blob/master/atmosphere/functions.glsl#L773 +__device__ glm::vec3 getScatteringTextureUvwFromRMuMuSNu(advanced::Textures const& textures, + common::Geometry const& geometry, double mu, double muS, double nu, + bool rayRMuIntersectsGround) { + + // Distance to top atmosphere boundary for a horizontal ray at ground level. + double H = + sqrt(geometry.mRadiusAtmo * geometry.mRadiusAtmo - geometry.mRadiusOcc * geometry.mRadiusOcc); + + // Discriminant of the quadratic equation for the intersections of the ray (r,mu) with the ground + // (see rayIntersectsGround). + double rMu = geometry.mRadiusAtmo * mu; + double discriminant = rMu * rMu - geometry.mRadiusAtmo * geometry.mRadiusAtmo + + geometry.mRadiusOcc * geometry.mRadiusOcc; + double uMu; + if (rayRMuIntersectsGround) { + // Distance to the ground for the ray (r,mu), and its minimum and maximum values over all mu - + // obtained for (r,-1) and (r,mu_horizon). + double d = -rMu - safeSqrt(discriminant); + double dMin = geometry.mRadiusAtmo - geometry.mRadiusOcc; + double dMax = H; + uMu = 0.5 - 0.5 * getTextureCoordFromUnitRange(dMax == dMin ? 0.0 : (d - dMin) / (dMax - dMin), + textures.mScatteringTextureMuSize / 2); + } else { + // Distance to the top atmosphere boundary for the ray (r,mu), and its minimum and maximum + // values over all mu - obtained for (r,1) and (r,mu_horizon). + double d = -rMu + safeSqrt(discriminant + H * H); + double dMax = 2.0 * H; + uMu = 0.5 + 0.5 * getTextureCoordFromUnitRange(d / dMax, textures.mScatteringTextureMuSize / 2); + } + + double d = distanceToTopAtmosphereBoundary(geometry, geometry.mRadiusOcc, muS); + double dMin = geometry.mRadiusAtmo - geometry.mRadiusOcc; + double dMax = H; + double a = (d - dMin) / (dMax - dMin); + double D = distanceToTopAtmosphereBoundary(geometry, geometry.mRadiusOcc, textures.mMuSMin); + double A = (D - dMin) / (dMax - dMin); + // An ad-hoc function equal to 0 for muS = MU_S_MIN (because then d = D and thus a = A), equal to + // 1 for muS = 1 (because then d = dMin and thus a = 0), and with a large slope around muS = 0, + // to get more texture samples near the horizon. + float uMuS = getTextureCoordFromUnitRange( + glm::max(1.0 - a / A, 0.0) / (1.0 + a), textures.mScatteringTextureMuSSize); + + float uNu = (nu + 1.0) / 2.0; + return glm::vec3(uNu, uMuS, uMu); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// https://github.com/ebruneton/precomputed_atmospheric_scattering/blob/master/atmosphere/functions.glsl#L473 +__device__ glm::vec3 getTransmittanceToTopAtmosphereBoundary( + advanced::Textures const& textures, common::Geometry const& geometry, double mu) { + glm::vec2 uv = getTransmittanceTextureUvFromRMu(textures, geometry, mu); + return glm::vec3(texture2D(textures.mTransmittance, uv)); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// https://github.com/ebruneton/precomputed_atmospheric_scattering/blob/master/atmosphere/functions.glsl#L240 +__device__ bool rayIntersectsGround(common::Geometry const& geometry, double mu) { + return mu < 0.0 && geometry.mRadiusAtmo * geometry.mRadiusAtmo * (mu * mu - 1.0) + + geometry.mRadiusOcc * geometry.mRadiusOcc >= + 0.0; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// This is different. In the original implementation, the phase function is the Rayleigh phase +// function. We load the phase function from a texture. +__device__ glm::vec3 moleculePhaseFunction(cudaTextureObject_t phaseTexture, float nu) { + float theta = glm::acos(nu) / M_PI; // 0<->1 + return glm::vec3(texture2D(phaseTexture, glm::vec2(theta, 0.0))); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// This is different. In the original implementation, the phase function is the Cornette-Shanks +// phase function. We load the phase function from a texture. +__device__ glm::vec3 aerosolPhaseFunction(cudaTextureObject_t phaseTexture, float nu) { + float theta = glm::acos(nu) / M_PI; // 0<->1 + return glm::vec3(texture2D(phaseTexture, glm::vec2(theta, 1.0))); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// As we are always in outer space, this function does not need the r parameter when compared to the +// original version: +// https://github.com/ebruneton/precomputed_atmospheric_scattering/blob/master/atmosphere/functions.glsl#L1658 +__device__ void getCombinedScattering(advanced::Textures const& textures, + common::Geometry const& geometry, float mu, float muS, float nu, bool rayRMuIntersectsGround, + glm::vec3& multipleScattering, glm::vec3& singleAerosolsScattering) { + glm::vec3 uvw = + getScatteringTextureUvwFromRMuMuSNu(textures, geometry, mu, muS, nu, rayRMuIntersectsGround); + float texCoordX = uvw.x * float(textures.mScatteringTextureNuSize - 1); + float texX = floor(texCoordX); + float lerp = texCoordX - texX; + glm::vec2 uv0 = glm::vec2((texX + uvw.y) / float(textures.mScatteringTextureNuSize), uvw.z); + glm::vec2 uv1 = glm::vec2((texX + 1.0 + uvw.y) / float(textures.mScatteringTextureNuSize), uvw.z); + + multipleScattering = glm::vec3(texture2D(textures.mMultipleScattering, uv0) * (1.0f - lerp) + + texture2D(textures.mMultipleScattering, uv1) * lerp); + singleAerosolsScattering = + glm::vec3(texture2D(textures.mSingleAerosolsScattering, uv0) * (1.0f - lerp) + + texture2D(textures.mSingleAerosolsScattering, uv1) * lerp); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// Rotates the given ray towards the planet's surface by the angle given in the theta deviation +// texture. Also returns the contact radius which is the distance of closest approach to the +// planet's surface which the ray had when traveling through the atmosphere. +__device__ glm::dvec3 getRefractedRay(advanced::Textures const& textures, + common::Geometry const& geometry, glm::dvec3 camera, glm::dvec3 ray, double& contactRadius) { + + // If refraction is disabled, we can simply return the ray. However, we still need to compute the + // contact radius. + if (!textures.mThetaDeviation) { + double dist = glm::length(camera); + auto toOccluder = -camera / dist; + double angle = math::angleBetweenVectors(ray, toOccluder); + contactRadius = glm::acos(angle) * dist - geometry.mRadiusOcc; + + return ray; + } + + double mu = dot(camera, ray) / geometry.mRadiusAtmo; + glm::vec2 uv = getTransmittanceTextureUvFromRMu(textures, geometry, mu); + + // Cosine of the angular deviation of the ray due to refraction. + glm::vec2 thetaDeviationContactRadius = glm::vec2(texture2D(textures.mThetaDeviation, uv)); + double thetaDeviation = glm::cos(double(thetaDeviationContactRadius.x)); + glm::dvec3 axis = glm::normalize(glm::cross(camera, ray)); + + contactRadius = thetaDeviationContactRadius.y; + + return normalize(math::rotateVector(ray, axis, thetaDeviation)); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +} // namespace + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +namespace advanced { + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// Loads all required textures from the given output directory from the Bruneton preprocessor tool. +__host__ Textures loadTextures(std::string const& path) { + uint32_t scatteringTextureRSize = tiff_utils::getNumLayers(path + "/multiple_scattering.tif"); + + tiff_utils::RGBATexture multiscattering = + tiff_utils::read2DTexture(path + "/multiple_scattering.tif", scatteringTextureRSize - 1); + tiff_utils::RGBATexture singleScattering = tiff_utils::read2DTexture( + path + "/single_aerosols_scattering.tif", scatteringTextureRSize - 1); + + tiff_utils::RGBATexture phase = tiff_utils::read2DTexture(path + "/phase.tif"); + tiff_utils::RGBATexture transmittance = tiff_utils::read2DTexture(path + "/transmittance.tif"); + + Textures textures; + textures.mMultipleScattering = createCudaTexture(multiscattering); + textures.mSingleAerosolsScattering = createCudaTexture(singleScattering); + + textures.mPhase = createCudaTexture(phase); + textures.mTransmittance = createCudaTexture(transmittance); + + std::ifstream metaFile(path + "/metadata.json"); + nlohmann::json meta; + metaFile >> meta; + + uint32_t scatteringTextureNuSize = meta.at("scatteringTextureNuSize"); + double maxSunZenithAngle = meta.at("maxSunZenithAngle"); + + textures.mTransmittanceTextureWidth = transmittance.width; + textures.mTransmittanceTextureHeight = transmittance.height; + textures.mScatteringTextureMuSize = multiscattering.height; + textures.mScatteringTextureMuSSize = multiscattering.width / scatteringTextureNuSize; + textures.mScatteringTextureNuSize = scatteringTextureNuSize; + textures.mMuSMin = std::cos(maxSunZenithAngle); + + bool enableRefraction = meta.at("refraction"); + if (enableRefraction) { + tiff_utils::RGBATexture theta_deviation = + tiff_utils::read2DTexture(path + "/theta_deviation.tif"); + textures.mThetaDeviation = createCudaTexture(theta_deviation); + } + + return textures; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// Computes the luminance of the atmosphere for the given geometry. All distances are in meters. +// This is loosely based on the original implementation by Eric Bruneton: +// https://github.com/ebruneton/precomputed_atmospheric_scattering/blob/master/atmosphere/functions.glsl#L1705 +__device__ glm::vec3 getLuminance(glm::dvec3 camera, glm::dvec3 viewRay, glm::dvec3 sunDirection, + common::Geometry const& geometry, common::LimbDarkening const& limbDarkening, + Textures const& textures, double phiSun) { + + // Compute the distance to the top atmosphere boundary along the view ray, assuming the viewer is + // in space (or NaN if the view ray does not intersect the atmosphere). + double r = length(camera); + double rmu = dot(camera, viewRay); + double distanceToTopAtmosphereBoundary = + -rmu - sqrt(rmu * rmu - r * r + geometry.mRadiusAtmo * geometry.mRadiusAtmo); + + glm::vec3 skyLuminance = glm::vec3(0.0); + glm::vec3 transmittance = glm::vec3(1.0); + glm::dvec3 refractedRay = viewRay; + + // We only need to compute the luminance if the view ray intersects the atmosphere. + if (distanceToTopAtmosphereBoundary > 0.0) { + + camera += viewRay * distanceToTopAtmosphereBoundary; + + // Compute the mu, muS and nu parameters needed for the texture lookups. + double mu = (rmu + distanceToTopAtmosphereBoundary) / geometry.mRadiusAtmo; + double muS = dot(camera, sunDirection) / geometry.mRadiusAtmo; + double nu = dot(viewRay, sunDirection); + bool rayRMuIntersectsGround = rayIntersectsGround(geometry, mu); + + glm::vec3 multipleScattering; + glm::vec3 singleAerosolsScattering; + getCombinedScattering(textures, geometry, mu, muS, nu, rayRMuIntersectsGround, + multipleScattering, singleAerosolsScattering); + + skyLuminance = multipleScattering * moleculePhaseFunction(textures.mPhase, nu) + + singleAerosolsScattering * aerosolPhaseFunction(textures.mPhase, nu); + + double contactRadius; + refractedRay = getRefractedRay(textures, geometry, camera, viewRay, contactRadius); + + transmittance = rayRMuIntersectsGround + ? glm::vec3(0.0) + : getTransmittanceToTopAtmosphereBoundary(textures, geometry, mu); + + // To account for terrain height, we apply a very simple model. We assume that all rays passing + // above two times the mean elevation of the terrain are not affected by the terrain. All rays + // passing below zero elevation are completely blocked by the terrain. In between, the + // transmittance is linearly interpolated. If a cloud elevation is set, all rays passing below + // the cloud elevation are completely blocked by the cloud. + if (contactRadius < geometry.mCloudAltitude) { + transmittance = glm::vec3(0.0); + } else if (contactRadius < 2.F * geometry.mAverageTerrainHeight) { + float t = (2.F * geometry.mAverageTerrainHeight - contactRadius) / + (2.f * geometry.mAverageTerrainHeight); + transmittance = glm::mix(transmittance, glm::vec3(0.0), t); + } + } + + float sun = limbDarkening.get(math::angleBetweenVectors(refractedRay, sunDirection) / phiSun); + + glm::vec3 sunLuminance = transmittance * (float)getSunLuminance(geometry.mRadiusSun) * sun; + + return skyLuminance + sunLuminance; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +} // namespace advanced + +//////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/tools/eclipse-shadow-generator/atmosphere_rendering.cuh b/tools/eclipse-shadow-generator/atmosphere_rendering.cuh new file mode 100644 index 000000000..7d0ae003c --- /dev/null +++ b/tools/eclipse-shadow-generator/atmosphere_rendering.cuh @@ -0,0 +1,44 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////// +// This file is part of CosmoScout VR // +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// SPDX-FileCopyrightText: German Aerospace Center (DLR) +// SPDX-License-Identifier: MIT + +#ifndef ATMOSPHERE_RENDERING_HPP +#define ATMOSPHERE_RENDERING_HPP + +#include "common.hpp" +#include "gpuErrCheck.hpp" +#include "math.cuh" + +namespace advanced { + +// These input textures are required for the Bruneton precomputed atmospheric scattering model. +// They have to be precomputed using the "bruneton-preprocessor" tool of the csp-atmospheres plugin. +struct Textures { + cudaTextureObject_t mPhase = 0; + cudaTextureObject_t mThetaDeviation = 0; + cudaTextureObject_t mTransmittance = 0; + cudaTextureObject_t mMultipleScattering = 0; + cudaTextureObject_t mSingleAerosolsScattering = 0; + + int mTransmittanceTextureWidth; + int mTransmittanceTextureHeight; + int mScatteringTextureMuSize; + int mScatteringTextureMuSSize; + int mScatteringTextureNuSize; + double mMuSMin; +}; + +// Loads all required textures from the given output directory from the Bruneton preprocessor tool. +Textures loadTextures(std::string const& path); + +// Computes the luminance of the atmosphere for the given geometry. All distances are in meters. +__device__ glm::vec3 getLuminance(glm::dvec3 camera, glm::dvec3 viewRay, glm::dvec3 sunDirection, + common::Geometry const& geometry, common::LimbDarkening const& limbDarkening, + Textures const& textures, double phiSun); + +} // namespace advanced + +#endif // ATMOSPHERE_RENDERING_HPP \ No newline at end of file diff --git a/tools/eclipse-shadow-generator/common.cpp b/tools/eclipse-shadow-generator/common.cpp new file mode 100644 index 000000000..d836f44ee --- /dev/null +++ b/tools/eclipse-shadow-generator/common.cpp @@ -0,0 +1,58 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////// +// This file is part of CosmoScout VR // +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// SPDX-FileCopyrightText: German Aerospace Center (DLR) +// SPDX-License-Identifier: MIT + +#include "common.hpp" + +namespace common { + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void addMappingFlags(cs::utils::CommandLine& commandLine, Mapping& settings) { + commandLine.addArgument({"--with-umbra"}, &settings.mIncludeUmbra, + "Add the umbra region to the shadow map (default: " + std::to_string(settings.mIncludeUmbra) + + ")."); + commandLine.addArgument({"--mapping-exponent"}, &settings.mExponent, + "Adjusts the distribution of sampling positions. A value of 1.0 will position the " + "umbra's end in the middle of the texture, larger values will shift this to the " + "right. (default: " + + std::to_string(settings.mExponent) + ")."); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void addOutputFlags(cs::utils::CommandLine& commandLine, Output& settings) { + commandLine.addArgument({"-o", "--output"}, &settings.mFile, + "The image will be written to this file (default: \"" + settings.mFile + "\")."); + commandLine.addArgument({"-s", "--size"}, &settings.mSize, + "The output texture size (default: " + std::to_string(settings.mSize) + ")."); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void addGeometryFlags(cs::utils::CommandLine& commandLine, Geometry& settings) { + commandLine.addArgument({"--radius-occ"}, &settings.mRadiusOcc, + "The radius of the occulting body in meters (default: " + + std::to_string(settings.mRadiusOcc) + ")."); + commandLine.addArgument({"--radius-atmo"}, &settings.mRadiusAtmo, + "The radius of the atmosphere in meters (default: " + std::to_string(settings.mRadiusAtmo) + + ")."); + commandLine.addArgument({"--radius-sun"}, &settings.mRadiusSun, + "The radius of the Sun in meters (default: " + std::to_string(settings.mRadiusSun) + ")."); + commandLine.addArgument({"--sun-occ-dist"}, &settings.mSunOccDist, + "The distance between the sun and the occluding body in meters (default: " + + std::to_string(settings.mSunOccDist) + ")."); + commandLine.addArgument({"--average-terrain-height"}, &settings.mAverageTerrainHeight, + "The average height of opaque geometry blocking light, in meters (default: " + + std::to_string(settings.mAverageTerrainHeight) + ")."); + commandLine.addArgument({"--cloud-altitude"}, &settings.mCloudAltitude, + "No light will pass below this altitude, in meters (default: " + + std::to_string(settings.mCloudAltitude) + ")."); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +} // namespace common \ No newline at end of file diff --git a/tools/eclipse-shadow-generator/common.hpp b/tools/eclipse-shadow-generator/common.hpp new file mode 100644 index 000000000..95a9cb97e --- /dev/null +++ b/tools/eclipse-shadow-generator/common.hpp @@ -0,0 +1,58 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////// +// This file is part of CosmoScout VR // +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// SPDX-FileCopyrightText: German Aerospace Center (DLR) +// SPDX-License-Identifier: MIT + +#ifndef COMMON_HPP +#define COMMON_HPP + +#include "../../../src/cs-utils/CommandLine.hpp" + +#include + +namespace common { + +// This is used to describe the different shadow-map parameterization variants described in figure +// 10 of "Real-Time Rendering of Eclipses without Incorporation of Atmospheric Effects". The values +// can be set via command line arguments. +struct Mapping { + bool mIncludeUmbra = false; + double mExponent = 1.0; +}; + +// This is used to describe the output file and resolution. The values can be set via command line +// arguments. The buffer is allocated and filled by the shadow-map generator. +struct Output { + std::string mFile = "shadow.tif"; + uint32_t mSize = 512; + float* mBuffer; +}; + +// When computing an eclipse shadow involving atmospheric effects, the geometry of the Sun, +// occluding body, and its atmosphere are needed. The values can be set via command line arguments. +struct Geometry { + double mRadiusOcc = 6370900.0; + double mRadiusAtmo = 6451000.0; + double mRadiusSun = 696340000.0; + double mSunOccDist = 149597870700.0; + double mAverageTerrainHeight = 0.0; + double mCloudAltitude = 0.0; +}; + +// This adds the command line arguments for the shadow-map parameterization to the given +// CommandLine object. +void addMappingFlags(cs::utils::CommandLine& commandLine, Mapping& settings); + +// This adds the command line arguments for the output file and resolution to the given +// CommandLine object. +void addOutputFlags(cs::utils::CommandLine& commandLine, Output& settings); + +// This adds the command line arguments for the geometry of the Sun, occluding body, and its +// atmosphere to the given CommandLine object. +void addGeometryFlags(cs::utils::CommandLine& commandLine, Geometry& settings); + +} // namespace common + +#endif // COMMON_HPP \ No newline at end of file diff --git a/tools/eclipse-shadow-generator/gpuErrCheck.hpp b/tools/eclipse-shadow-generator/gpuErrCheck.hpp new file mode 100644 index 000000000..b5e475499 --- /dev/null +++ b/tools/eclipse-shadow-generator/gpuErrCheck.hpp @@ -0,0 +1,29 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////// +// This file is part of CosmoScout VR // +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// SPDX-FileCopyrightText: German Aerospace Center (DLR) +// SPDX-License-Identifier: MIT + +#ifndef GPU_ERR_CHECK_HPP +#define GPU_ERR_CHECK_HPP + +#include +#include +#include + +// This macro is used in multiple locations to check for Cuda errors. +// https://stackoverflow.com/questions/14038589/what-is-the-canonical-way-to-check-for-errors-using-the-cuda-runtime-api +#define gpuErrchk(ans) \ + { gpuAssert((ans), __FILE__, __LINE__); } +inline void gpuAssert(cudaError_t code, const char* file, int line, bool abort = true) { + if (code != cudaSuccess) { + std::cerr << "GPUassert: " << cudaGetErrorString(code) << " " << file << " (" << line << ")" + << std::endl; + if (abort) { + exit(code); + } + } +} + +#endif // GPU_ERR_CHECK_HPP \ No newline at end of file diff --git a/tools/eclipse-shadow-generator/main.cu b/tools/eclipse-shadow-generator/main.cu index a376e8d64..428378dd6 100644 --- a/tools/eclipse-shadow-generator/main.cu +++ b/tools/eclipse-shadow-generator/main.cu @@ -7,240 +7,82 @@ #include "../../src/cs-utils/CommandLine.hpp" -#include "LimbDarkening.cuh" -#include "math.cuh" - -#define STB_IMAGE_IMPLEMENTATION -#define STB_IMAGE_WRITE_IMPLEMENTATION -#include -#include +#include "advanced_modes.cuh" +#include "gpuErrCheck.hpp" +#include "simple_modes.cuh" + +// ------------------------------------------------------------------------------------------------- + +// clang-format off +void printHelp() { + std::cout << "Welcome to the eclipse shadow texture generator! Usage:" << std::endl; + std::cout << std::endl; + std::cout << " ./eclipse-shadow-generator " << std::endl; + std::cout << std::endl; + std::cout << "There are different operation modes available. " << std::endl; + std::cout << "Type './eclipse-shadow-generator --help' to learn more about a specific mode." << std::endl; + std::cout << std::endl; + std::cout << "These modes are available:" << std::endl; + std::cout << "bruneton Computes an eclipse shadow with atmospheric effects based on the Bruneton atmospheric model." << std::endl; + std::cout << "limp-darkening Computes an eclipse shadow without atmospheric effects using sampling of a limb-darkening function." << std::endl; + std::cout << "circles Computes an eclipse shadow without atmospheric effects based on circle intersections." << std::endl; + std::cout << "smoothstep Computes an eclipse shadow without atmospheric effects using a smoothstep function in the penumbra on circle intersections." << std::endl; + std::cout << "linear Computes an eclipse shadow without atmospheric effects with a linear brightness gradient." << std::endl; + std::cout << "planet-view Computes a view of a planet as seen from space." << std::endl; + std::cout << "atmo-view Computes a view of the entire atmosphere from a given position in space." << std::endl; + std::cout << "limb-luminance Computes the average luminance of atmosphere for each position in the shadow map in a direction-dependent manner." << std::endl; +} +// clang-format on //////////////////////////////////////////////////////////////////////////////////////////////////// // This tool can be used to create the eclipse shadow maps used by CosmoScout VR. See the // // README.md file in this directory for usage instructions! // //////////////////////////////////////////////////////////////////////////////////////////////////// -// This macro is used in multiple locations to check for Cuda errors. -// https://stackoverflow.com/questions/14038589/what-is-the-canonical-way-to-check-for-errors-using-the-cuda-runtime-api -#define gpuErrchk(ans) \ - { gpuAssert((ans), __FILE__, __LINE__); } -inline void gpuAssert(cudaError_t code, const char* file, int line, bool abort = true) { - if (code != cudaSuccess) { - fprintf(stderr, "GPUassert: %s %s %d\n", cudaGetErrorString(code), file, line); - if (abort) { - exit(code); - } - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// - -// This is used to pass the command line options to the Cuda kernel. -struct ShadowSettings { - uint32_t size = 512; - bool includeUmbra = false; - double mappingExponent = 1.0; -}; - -//////////////////////////////////////////////////////////////////////////////////////////////////// - -__constant__ LimbDarkening cLimbDarkening; -__constant__ ShadowSettings cShadowSettings; - -//////////////////////////////////////////////////////////////////////////////////////////////////// - -// Computes the shadow map by sampling the intersection area between circles representing the Sun -// and the occluder. This makes use of the global limb darkening function. -__global__ void computeLimbDarkeningShadow(float* shadowMap) { - uint32_t x = blockIdx.x * blockDim.x + threadIdx.x; - uint32_t y = blockIdx.y * blockDim.y + threadIdx.y; - uint32_t i = y * cShadowSettings.size + x; +int main(int argc, char** argv) { - if ((x >= cShadowSettings.size) || (y >= cShadowSettings.size)) { - return; + if (argc <= 1) { + printHelp(); + return 0; } - auto angles = math::mapPixelToAngles(glm::ivec2(x, y), cShadowSettings.size, - cShadowSettings.mappingExponent, cShadowSettings.includeUmbra); - - double sunArea = math::getCircleArea(1.0); - - shadowMap[i] = static_cast( - 1 - math::sampleCircleIntersection(1.0, angles.x, angles.y, cLimbDarkening) / sunArea); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// + std::string cMode(argv[1]); -// Computes the shadow map by analytically computing the intersection area between circles -// representing the Sun and the occluder. This does not use a limb darkening function. -__global__ void computeCircleIntersectionShadow(float* shadowMap) { - uint32_t x = blockIdx.x * blockDim.x + threadIdx.x; - uint32_t y = blockIdx.y * blockDim.y + threadIdx.y; - uint32_t i = y * cShadowSettings.size + x; + std::vector arguments(argv + 2, argv + argc); - if ((x >= cShadowSettings.size) || (y >= cShadowSettings.size)) { - return; + if (cMode == "simple-limb-darkening") { + return simple::limbDarkeningMode(arguments); } - auto angles = math::mapPixelToAngles(glm::ivec2(x, y), cShadowSettings.size, - cShadowSettings.mappingExponent, cShadowSettings.includeUmbra); - - double sunArea = math::getCircleArea(1.0); - - shadowMap[i] = - static_cast(1.0 - math::getCircleIntersection(1.0, angles.x, angles.y) / sunArea); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// - -// Computes the shadow map by assuming a linear brightness gradient from the outer edge of the -// penumbra to the start of the umbra / antumbra. In the antumbra, the shadow intensity decreases -// quadratically. This does not use a limb darkening function. -__global__ void computeLinearShadow(float* shadowMap) { - uint32_t x = blockIdx.x * blockDim.x + threadIdx.x; - uint32_t y = blockIdx.y * blockDim.y + threadIdx.y; - uint32_t i = y * cShadowSettings.size + x; - - if ((x >= cShadowSettings.size) || (y >= cShadowSettings.size)) { - return; + if (cMode == "simple-circles") { + return simple::circleIntersectionMode(arguments); } - auto angles = math::mapPixelToAngles(glm::ivec2(x, y), cShadowSettings.size, - cShadowSettings.mappingExponent, cShadowSettings.includeUmbra); - - double phiSun = 1.0; - double phiOcc = angles[0]; - double delta = angles[1]; - - double visiblePortion = - (delta - glm::abs(phiSun - phiOcc)) / (phiSun + phiOcc - glm::abs(phiSun - phiOcc)); - - double maxDepth = glm::min(1.0, glm::pow(phiOcc / phiSun, 2.0)); - - shadowMap[i] = static_cast(1.0 - maxDepth * glm::clamp(1.0 - visiblePortion, 0.0, 1.0)); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// - -// Computes the shadow map by assuming a smoothstep-based brightness gradient from the outer edge of -// the penumbra to the start of the umbra / antumbra. In the antumbra, the shadow intensity -// decreases quadratically. This does not use a limb darkening function. -__global__ void computeSmoothstepShadow(float* shadowMap) { - uint32_t x = blockIdx.x * blockDim.x + threadIdx.x; - uint32_t y = blockIdx.y * blockDim.y + threadIdx.y; - uint32_t i = y * cShadowSettings.size + x; - - if ((x >= cShadowSettings.size) || (y >= cShadowSettings.size)) { - return; + if (cMode == "simple-smoothstep") { + return simple::smoothstepMode(arguments); } - auto angles = math::mapPixelToAngles(glm::ivec2(x, y), cShadowSettings.size, - cShadowSettings.mappingExponent, cShadowSettings.includeUmbra); - - double phiSun = 1.0; - double phiOcc = angles[0]; - double delta = angles[1]; - - double visiblePortion = - (delta - glm::abs(phiSun - phiOcc)) / (phiSun + phiOcc - glm::abs(phiSun - phiOcc)); - - double maxDepth = glm::min(1.0, glm::pow(phiOcc / phiSun, 2.0)); - - shadowMap[i] = static_cast( - 1.0 - maxDepth * glm::clamp(1.0 - glm::smoothstep(0.0, 1.0, visiblePortion), 0.0, 1.0)); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// - -int main(int argc, char** argv) { - - stbi_flip_vertically_on_write(1); - - ShadowSettings settings; - - std::string cOutput = "shadow.hdr"; - std::string cMode = "limb-darkening"; - bool cPrintHelp = false; - - // First configure all possible command line options. - cs::utils::CommandLine args( - "Welcome to the shadow map generator! Here are the available options:"); - args.addArgument({"-o", "--output"}, &cOutput, - "The image will be written to this file (default: \"" + cOutput + "\")."); - args.addArgument({"--size"}, &settings.size, - "The output texture size (default: " + std::to_string(settings.size) + ")."); - args.addArgument({"--mode"}, &cMode, - "This should be either 'limb-darkening', 'circles', 'linear', or 'smoothstep' (default: " + - cMode + ")."); - args.addArgument({"--with-umbra"}, &settings.includeUmbra, - "Add the umbra region to the shadow map (default: " + std::to_string(settings.includeUmbra) + - ")."); - args.addArgument({"--mapping-exponent"}, &settings.mappingExponent, - "Adjusts the distribution of sampling positions. A value of 1.0 will position the " - "umbra's end in the middle of the texture, larger values will shift this to the " - "right. (default: " + - std::to_string(settings.mappingExponent) + ")."); - args.addArgument({"-h", "--help"}, &cPrintHelp, "Show this help message."); - - // Then do the actual parsing. - try { - std::vector arguments(argv + 1, argv + argc); - args.parse(arguments); - } catch (std::runtime_error const& e) { - std::cerr << "Failed to parse command line arguments: " << e.what() << std::endl; - return 1; + if (cMode == "simple-linear") { + return simple::linearMode(arguments); } - // When cPrintHelp was set to true, we print a help message and exit. - if (cPrintHelp) { - args.printHelp(); - return 0; + if (cMode == "advanced-shadow") { + return advanced::shadowMode(arguments); } - // Check whether a valid mode was given. - if (cMode != "limb-darkening" && cMode != "circles" && cMode != "linear" && - cMode != "smoothstep") { - std::cerr << "Invalid value given for --mode!" << std::endl; - - return 1; + if (cMode == "advanced-limb-luminance") { + return advanced::limbLuminanceMode(arguments); } - // Initialize the limb darkening model. - LimbDarkening limbDarkening; - limbDarkening.init(); - - // Initialize the global Cuda symbols. - cudaMemcpyToSymbol(cShadowSettings, &settings, sizeof(ShadowSettings)); - cudaMemcpyToSymbol(cLimbDarkening, &limbDarkening, sizeof(LimbDarkening)); - - // Compute the 2D kernel size. - dim3 blockSize(16, 16); - uint32_t numBlocksX = (settings.size + blockSize.x - 1) / blockSize.x; - uint32_t numBlocksY = (settings.size + blockSize.y - 1) / blockSize.y; - dim3 gridSize = dim3(numBlocksX, numBlocksY); - - // Allocate the shared memory for the shadow map. - float* shadow = nullptr; - gpuErrchk(cudaMallocManaged( - &shadow, static_cast(settings.size * settings.size) * sizeof(float))); - - // Compute the shadow map based on the given mode. - if (cMode == "limb-darkening") { - computeLimbDarkeningShadow<<>>(shadow); - } else if (cMode == "circles") { - computeCircleIntersectionShadow<<>>(shadow); - } else if (cMode == "linear") { - computeLinearShadow<<>>(shadow); - } else if (cMode == "smoothstep") { - computeSmoothstepShadow<<>>(shadow); + if (cMode == "advanced-planet-view") { + return advanced::planetViewMode(arguments); } - gpuErrchk(cudaPeekAtLastError()); - gpuErrchk(cudaDeviceSynchronize()); + if (cMode == "advanced-atmo-view") { + return advanced::atmoViewMode(arguments); + } - // Finally write the output texture! - stbi_write_hdr( - cOutput.c_str(), static_cast(settings.size), static_cast(settings.size), 1, shadow); + printHelp(); return 0; } diff --git a/tools/eclipse-shadow-generator/math.cu b/tools/eclipse-shadow-generator/math.cu index 5fbb0d783..45706c25f 100644 --- a/tools/eclipse-shadow-generator/math.cu +++ b/tools/eclipse-shadow-generator/math.cu @@ -11,6 +11,20 @@ namespace math { //////////////////////////////////////////////////////////////////////////////////////////////////// +double __host__ __device__ angleBetweenVectors(glm::dvec3 const& u, glm::dvec3 const& v) { + return 2.0 * glm::asin(0.5 * glm::length(u - v)); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +glm::dvec3 __host__ __device__ rotateVector( + glm::dvec3 const& v, glm::dvec3 const& a, double cosMu) { + double sinMu = glm::sqrt(1.0 - cosMu * cosMu); + return v * cosMu + glm::cross(a, v) * sinMu + a * glm::dot(a, v) * (1.0 - cosMu); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + double __host__ __device__ getCircleArea(double r) { return glm::pi() * r * r; } @@ -73,7 +87,7 @@ double __host__ __device__ getCircleIntersection(double rSun, double rOcc, doubl //////////////////////////////////////////////////////////////////////////////////////////////////// double __host__ __device__ sampleCircleIntersection( - double rSun, double rOcc, double d, LimbDarkening const& limbDarkening) { + double rSun, double rOcc, double d, common::LimbDarkening const& limbDarkening) { // Sanity checks. d = std::abs(d); @@ -136,42 +150,78 @@ double __host__ __device__ sampleCircleIntersection( //////////////////////////////////////////////////////////////////////////////////////////////////// -glm::dvec2 __host__ __device__ mapPixelToAngles( - glm::ivec2 const& pixel, uint32_t resolution, double exponent, bool includeUmbra) { - - double x = glm::pow((1.0 * pixel.x + 0.5) / resolution, exponent); - double y = 1.0 - glm::pow(1.0 - (1.0 * pixel.y + 0.5) / resolution, exponent); +void __host__ __device__ mapPixelToRadii(glm::ivec2 const& pixel, uint32_t resolution, + common::Mapping const& mapping, double& radiusOcc, double& distance) { - double phiSun = 1.0; - double phiOcc = phiSun / x - phiSun; + double x = glm::pow((1.0 * pixel.x + 0.5) / resolution, mapping.mExponent); + double y = 1.0 - glm::pow(1.0 - (1.0 * pixel.y + 0.5) / resolution, mapping.mExponent); - double minDelta = includeUmbra ? 0.0 : glm::max(phiOcc - phiSun, 0.0); - double maxDelta = phiOcc + phiSun; + double radiusSun = 1.0; + radiusOcc = radiusSun / x - radiusSun; - double delta = minDelta + y * (maxDelta - minDelta); + double minDistance = mapping.mIncludeUmbra ? 0.0 : glm::max(radiusOcc - radiusSun, 0.0); + double maxDistance = radiusOcc + radiusSun; - return {phiOcc, delta}; + distance = minDistance + y * (maxDistance - minDistance); } //////////////////////////////////////////////////////////////////////////////////////////////////// -glm::ivec2 __host__ __device__ mapAnglesToPixel( - glm::dvec2 const& angles, uint32_t resolution, double exponent, bool includeUmbra) { +uint32_t __host__ __device__ mapPixelToAngles(glm::ivec2 const& pixel, uint32_t resolution, + common::Mapping const& mapping, common::Geometry const& geometry, double& phiOcc, + double& phiSun, double& delta) { + + // This methods computes two circles, one representing the Sun and the other the occluder, as well + // as the distance between their centers. All values are scaled in such a way/ that the radius of + // the Solar disc is 1.0. + double radiusOcc, distance; + mapPixelToRadii(pixel, resolution, mapping, radiusOcc, distance); + + // To compute the actual geometry of the involved bodies, we need to find the position in space + // where the anguala radii and the anualar distance between the Sun and the occluder are as + // computed above, scaled by an unknown factor. + + // As an initial guess, we assume that the Sun appears as large as it does from the occluder's + // position. So we scale all values to this. + phiSun = glm::asin(geometry.mRadiusSun / geometry.mSunOccDist); + phiOcc = glm::min(glm::pi() / 2.0, radiusOcc * phiSun); + delta = glm::min(glm::pi() / 2.0, distance * phiSun); + + // If the occluder is larger than pi/2, an impossible situation is given. + if (phiOcc >= glm::pi() / 2.0) { + return 0; + } + + double error = 1.0; + uint32_t iterations = 0; - double phiSun = 1.0; - double phiOcc = angles.x; - double minDelta = includeUmbra ? 0.0 : glm::max(phiOcc - phiSun, 0.0); - double maxDelta = phiOcc + phiSun; + while (error > 0.0001 && ++iterations < 100) { - glm::ivec2 pixel; + // Compute how far we would be from the occluder if it appeared this large. + double occDist = geometry.mRadiusOcc / glm::sin(phiOcc); - double x = 1.0 / (phiOcc / phiSun + 1.0); - pixel.x = static_cast(resolution * glm::pow(x, 1.0 / exponent) - 0.5); + // Given the angular distance between the Sun and the occluder, we can compute the distance + // between the Sun and the searched point. This will be farther away than the initial guess. + double sunDist = occDist * glm::cos(delta) + + glm::sqrt(occDist * occDist * glm::cos(delta) * glm::cos(delta) - + occDist * occDist + geometry.mSunOccDist * geometry.mSunOccDist); - double y = (angles.y - minDelta) / (maxDelta - minDelta); - pixel.y = static_cast(resolution * (1.0 - glm::pow(1.0 - y, 1.0 / exponent)) - 0.5); + // Using the real radius of the Sun, we can now compute again how large the Sun will appear from + // the searched point. We use this as a new guess. + double newPhiSun = glm::asin(geometry.mRadiusSun / sunDist); + double newPhiOcc = radiusOcc * newPhiSun; + double newDelta = distance * newPhiSun; + + // Compute the maximum error in all three values. + error = glm::max(glm::abs(phiSun - newPhiSun) / phiSun, + glm::max(glm::abs(phiOcc - newPhiOcc) / phiOcc, glm::abs(delta - newDelta) / delta)); + + phiSun = newPhiSun; + phiOcc = newPhiOcc; + delta = newDelta; + } - return pixel; + return iterations; } //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/tools/eclipse-shadow-generator/math.cuh b/tools/eclipse-shadow-generator/math.cuh index b177b2f20..2a3388d1f 100644 --- a/tools/eclipse-shadow-generator/math.cuh +++ b/tools/eclipse-shadow-generator/math.cuh @@ -16,38 +16,76 @@ #include #include "LimbDarkening.cuh" +#include "common.hpp" namespace math { -/// Returns the surface area of a circle. +// Using acos is not very stable for small angles. This function uses asin to compute the angle +// between two vectors in a more stable way. +double __host__ __device__ angleBetweenVectors(glm::dvec3 const& u, glm::dvec3 const& v); + +// Rotates vector v around axis a using Rodrigues' rotation formula. +glm::dvec3 __host__ __device__ rotateVector(glm::dvec3 const& v, glm::dvec3 const& a, double cosMu); + +// Returns the surface area of a circle. double __host__ __device__ getCircleArea(double r); -/// Returns the surface area of a spherical cap on a unit sphere. +// Returns the surface area of a spherical cap on a unit sphere. double __host__ __device__ getCapArea(double r); -/// Returns the intersection area of two spherical caps with radii rSun and rOcc whose center points -/// are distance d away from each other. All values are given as angles on the unit sphere. +// Returns the intersection area of two spherical caps with radii rSun and rOcc whose center points +// are distance d away from each other. All values are given as angles on the unit sphere. double __host__ __device__ getCapIntersection(double rSun, double rOcc, double d); -/// Returns the intersection area of two circles with radii rSun and rOcc whose center points are -/// distance d away from each other. +// Returns the intersection area of two circles with radii rSun and rOcc whose center points are +// distance d away from each other. double __host__ __device__ getCircleIntersection(double rSun, double rOcc, double d); -/// Same as above, but the intersection area is computed by sampling. This is less precise but -/// allows for incorporating limb darkening. +// Same as above, but the intersection area is computed by sampling. This is less precise but +// allows for incorporating limb darkening. double __host__ __device__ sampleCircleIntersection( - double rSun, double rOcc, double d, LimbDarkening const& limbDarkening); - -/// Maps a pixel position in the shadow map to the three angles phiSun, phiOcc and delta. phiSun is -/// assumed to be 1.0, phiOcc is returned as first component, delta as second component. -glm::dvec2 __host__ __device__ mapPixelToAngles( - glm::ivec2 const& pixel, uint32_t resolution, double exponent, bool includeUmbra); - -/// Maps the three angles phiSun, phiOcc and delta to a pixel position in the shadow map to. phiSun -/// is assumed to be 1.0, phiOcc should be given as first component, delta as second component of -/// "angled". -glm::ivec2 __host__ __device__ mapAnglesToPixel( - glm::dvec2 const& angles, uint32_t resolution, double exponent, bool includeUmbra); + double rSun, double rOcc, double d, common::LimbDarkening const& limbDarkening); + +// Maps a pixel position in the shadow map to a corresponding radius of a 2D projection of the +// occluder, and a distance between the occluder and the Sun. Both values are scaled in such a way +// that the radius of the Solar disc is 1.0. This is usually sufficient for computing the visible +// fraction of the Sun. +// To get the actual angular radii and angular distances, more information on the involved geometry +// is needed. For this, use the function below. +void __host__ __device__ mapPixelToRadii(glm::ivec2 const& pixel, uint32_t resolution, + common::Mapping const& mapping, double& radiusOcc, double& distance); + +// To reconstruct the actual geometry of the involved bodies for a given location in the shadow map, +// we need additional information like the real-world radii of the Sun and the occluder, as well as +// their distance to each other. +// +// It happens that this reconstruction is a non-trivial problem which cannot be solved analytically. +// Even if there is only a single solution, it seems to be impossible to find it without numerical +// methods. +// +// For pixels with a x-coordinate close to zero, it may be impossible to find a solution. As the +// orbit radius is fixed, the Sun's maximum angular radius is fixed as well. Therefore, depending on +// on the occluder's radius, there may be no solution. In this case, the function returns 0. +// +// Our approach is as follow: +// 1. The largest possible angular radius of the Sun from any position in the occluder's shadow +// is its angular radius when observed from the occluder's position. Therefore, we scale all +// values returned by mapPixelToRadii this value. Most likely, the Sun will appear even smaller +// as our searched point is further away from the occluder, but we take this as an initial +// guess. +// 2. We now use the real radius of the occluder, the average distance between Sun and occluder, +// and the angular distance between the centers of Sun and occluder to compute the resulting +// distance to the Sun from the searched point. This will be farther away than the initial +// guess. +// 3. Using the real radius of the Sun, we can now compute again how large the Sun will appear +// from the searched point. This will be smaller than the initial guess. We again scale all +// angles to this value and repeat the process until we converge. +// +// The method returns the number of iterations needed to converge. +uint32_t __host__ __device__ mapPixelToAngles(glm::ivec2 const& pixel, uint32_t resolution, + common::Mapping const& mapping, common::Geometry const& geometry, double& phiOcc, + double& phiSun, double& delta); + } // namespace math #endif // MATH_HPP \ No newline at end of file diff --git a/tools/eclipse-shadow-generator/simple_modes.cu b/tools/eclipse-shadow-generator/simple_modes.cu new file mode 100644 index 000000000..79ece081e --- /dev/null +++ b/tools/eclipse-shadow-generator/simple_modes.cu @@ -0,0 +1,224 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////// +// This file is part of CosmoScout VR // +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// SPDX-FileCopyrightText: German Aerospace Center (DLR) +// SPDX-License-Identifier: MIT + +#include "simple_modes.cuh" + +#include "LimbDarkening.cuh" +#include "common.hpp" +#include "gpuErrCheck.hpp" +#include "math.cuh" +#include "tiff_utils.hpp" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +namespace { + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +enum class Mode { eLimbDarkening, eCircleIntersection, eLinear, eSmoothstep }; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +__device__ void writeAsRGBValue(float value, float* buffer, uint32_t index) { + buffer[index * 3 + 0] = value; + buffer[index * 3 + 1] = value; + buffer[index * 3 + 2] = value; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +__global__ void computeLimbDarkeningShadow( + common::Mapping mapping, common::Output output, common::LimbDarkening limbDarkening) { + uint32_t x = blockIdx.x * blockDim.x + threadIdx.x; + uint32_t y = blockIdx.y * blockDim.y + threadIdx.y; + uint32_t i = y * output.mSize + x; + + if ((x >= output.mSize) || (y >= output.mSize)) { + return; + } + + double radiusOcc, distance; + math::mapPixelToRadii(glm::ivec2(x, y), output.mSize, mapping, radiusOcc, distance); + + double sunArea = math::getCircleArea(1.0); + float intensity = static_cast( + 1 - math::sampleCircleIntersection(1.0, radiusOcc, distance, limbDarkening) / sunArea); + + writeAsRGBValue(intensity, output.mBuffer, i); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +__global__ void computeCircleIntersectionShadow(common::Mapping mapping, common::Output output) { + uint32_t x = blockIdx.x * blockDim.x + threadIdx.x; + uint32_t y = blockIdx.y * blockDim.y + threadIdx.y; + uint32_t i = y * output.mSize + x; + + if ((x >= output.mSize) || (y >= output.mSize)) { + return; + } + + double radiusOcc, distance; + math::mapPixelToRadii(glm::ivec2(x, y), output.mSize, mapping, radiusOcc, distance); + + double sunArea = math::getCircleArea(1.0); + float intensity = + static_cast(1.0 - math::getCircleIntersection(1.0, radiusOcc, distance) / sunArea); + + writeAsRGBValue(intensity, output.mBuffer, i); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +__global__ void computeLinearShadow(common::Mapping mapping, common::Output output) { + uint32_t x = blockIdx.x * blockDim.x + threadIdx.x; + uint32_t y = blockIdx.y * blockDim.y + threadIdx.y; + uint32_t i = y * output.mSize + x; + + if ((x >= output.mSize) || (y >= output.mSize)) { + return; + } + + double radiusSun = 1.0; + double radiusOcc, distance; + math::mapPixelToRadii(glm::ivec2(x, y), output.mSize, mapping, radiusOcc, distance); + + double visiblePortion = (distance - glm::abs(radiusSun - radiusOcc)) / + (radiusSun + radiusOcc - glm::abs(radiusSun - radiusOcc)); + + double maxDepth = glm::min(1.0, glm::pow(radiusOcc / radiusSun, 2.0)); + + float intensity = static_cast(1.0 - maxDepth * glm::clamp(1.0 - visiblePortion, 0.0, 1.0)); + + writeAsRGBValue(intensity, output.mBuffer, i); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +__global__ void computeSmoothstepShadow(common::Mapping mapping, common::Output output) { + uint32_t x = blockIdx.x * blockDim.x + threadIdx.x; + uint32_t y = blockIdx.y * blockDim.y + threadIdx.y; + uint32_t i = y * output.mSize + x; + + if ((x >= output.mSize) || (y >= output.mSize)) { + return; + } + + double radiusSun = 1.0; + double radiusOcc, distance; + math::mapPixelToRadii(glm::ivec2(x, y), output.mSize, mapping, radiusOcc, distance); + + double visiblePortion = (distance - glm::abs(radiusSun - radiusOcc)) / + (radiusSun + radiusOcc - glm::abs(radiusSun - radiusOcc)); + + double maxDepth = glm::min(1.0, glm::pow(radiusOcc / radiusSun, 2.0)); + + float intensity = static_cast( + 1.0 - maxDepth * glm::clamp(1.0 - glm::smoothstep(0.0, 1.0, visiblePortion), 0.0, 1.0)); + + writeAsRGBValue(intensity, output.mBuffer, i); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +int run(Mode mode, std::vector const& arguments) { + common::Mapping mapping; + common::Output output; + bool cPrintHelp = false; + + // First configure all possible command line options. + cs::utils::CommandLine args("Here are the available options:"); + common::addMappingFlags(args, mapping); + common::addOutputFlags(args, output); + args.addArgument({"-h", "--help"}, &cPrintHelp, "Show this help message."); + + // Then do the actual parsing. + try { + args.parse(arguments); + } catch (std::runtime_error const& e) { + std::cerr << "Failed to parse command line arguments: " << e.what() << std::endl; + return 1; + } + + // When cPrintHelp was set to true, we print a help message and exit. + if (cPrintHelp) { + args.printHelp(); + return 0; + } + + // Initialize the limb darkening model. + common::LimbDarkening limbDarkening; + limbDarkening.init(); + + // Compute the 2D kernel size. + dim3 blockSize(16, 16); + uint32_t numBlocksX = (output.mSize + blockSize.x - 1) / blockSize.x; + uint32_t numBlocksY = (output.mSize + blockSize.y - 1) / blockSize.y; + dim3 gridSize = dim3(numBlocksX, numBlocksY); + + // Allocate the shared memory for the shadow map. + gpuErrchk(cudaMallocManaged( + &output.mBuffer, static_cast(output.mSize * output.mSize) * 3 * sizeof(float))); + + if (mode == Mode::eLimbDarkening) { + computeLimbDarkeningShadow<<>>(mapping, output, limbDarkening); + } else if (mode == Mode::eCircleIntersection) { + computeCircleIntersectionShadow<<>>(mapping, output); + } else if (mode == Mode::eLinear) { + computeLinearShadow<<>>(mapping, output); + } else if (mode == Mode::eSmoothstep) { + computeSmoothstepShadow<<>>(mapping, output); + } + + gpuErrchk(cudaPeekAtLastError()); + gpuErrchk(cudaDeviceSynchronize()); + + // Finally write the output texture! + tiff_utils::write2D(output.mFile, output.mBuffer, static_cast(output.mSize), + static_cast(output.mSize), 3); + + // Free the shared memory. + gpuErrchk(cudaFree(output.mBuffer)); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +} // namespace + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +namespace simple { + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +int limbDarkeningMode(std::vector const& arguments) { + return run(Mode::eLimbDarkening, arguments); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +int circleIntersectionMode(std::vector const& arguments) { + return run(Mode::eCircleIntersection, arguments); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +int linearMode(std::vector const& arguments) { + return run(Mode::eLinear, arguments); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +int smoothstepMode(std::vector const& arguments) { + return run(Mode::eSmoothstep, arguments); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +} // namespace simple \ No newline at end of file diff --git a/tools/eclipse-shadow-generator/simple_modes.cuh b/tools/eclipse-shadow-generator/simple_modes.cuh new file mode 100644 index 000000000..e19bc5c30 --- /dev/null +++ b/tools/eclipse-shadow-generator/simple_modes.cuh @@ -0,0 +1,43 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////// +// This file is part of CosmoScout VR // +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// SPDX-FileCopyrightText: German Aerospace Center (DLR) +// SPDX-License-Identifier: MIT + +#ifndef SIMPLE_MODES_HPP +#define SIMPLE_MODES_HPP + +#include +#include + +// The "simple" modes compute eclipse shadows for spherical bodies which do not have an atmosphere. +// The most accurate mode is "limbDarkeningMode", which uses a limb darkening function to model the +// surface brightness of the Sun. The other modes are less accurate, and are mostly provided for +// comparison purposes. More details can be found in the paper "Real-Time Rendering of Eclipses +// without Incorporation of Atmospheric Effects" +// (https://onlinelibrary.wiley.com/doi/full/10.1111/cgf.14676). + +namespace simple { + +// Computes the shadow map by sampling a limb-darkening model in the intersection area between +// circles representing the Sun and the occluder. +int limbDarkeningMode(std::vector const& arguments); + +// Computes the shadow map by analytically computing the intersection area between circles +// representing the Sun and the occluder. This does not use a limb darkening function. +int circleIntersectionMode(std::vector const& arguments); + +// Computes the shadow map by assuming a linear brightness gradient from the outer edge of the +// penumbra to the start of the umbra / antumbra. In the antumbra, the shadow intensity decreases +// quadratically. This does not use a limb darkening function. +int linearMode(std::vector const& arguments); + +// Computes the shadow map by assuming a smoothstep-based brightness gradient from the outer edge of +// the penumbra to the start of the umbra / antumbra. In the antumbra, the shadow intensity +// decreases quadratically. This does not use a limb darkening function. +int smoothstepMode(std::vector const& arguments); + +} // namespace simple + +#endif // SIMPLE_MODES_HPP \ No newline at end of file diff --git a/tools/eclipse-shadow-generator/tiff_utils.cpp b/tools/eclipse-shadow-generator/tiff_utils.cpp new file mode 100644 index 000000000..d9603cbc1 --- /dev/null +++ b/tools/eclipse-shadow-generator/tiff_utils.cpp @@ -0,0 +1,134 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////// +// This file is part of CosmoScout VR // +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// SPDX-FileCopyrightText: German Aerospace Center (DLR) +// SPDX-License-Identifier: MIT + +#include "tiff_utils.hpp" + +#include +#include + +namespace { + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +std::vector addAlphaChannel(std::vector const& rgbData) { + std::vector rgbaData(rgbData.size() * 4 / 3); + + for (unsigned i = 0; i < rgbData.size() / 3; i++) { + rgbaData[i * 4 + 0] = rgbData[i * 3 + 0]; + rgbaData[i * 4 + 1] = rgbData[i * 3 + 1]; + rgbaData[i * 4 + 2] = rgbData[i * 3 + 2]; + rgbaData[i * 4 + 3] = 1.0; + } + + return rgbaData; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +} // namespace + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +namespace tiff_utils { + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +uint32_t getNumLayers(std::string const& path) { + auto* data = TIFFOpen(path.c_str(), "r"); + + if (!data) { + std::cerr << "Failed to open TIFF file'" << path << "' " << std::endl; + return 0; + } + + uint32_t numLayers = 0; + + do { + numLayers++; + } while (TIFFReadDirectory(data)); + + TIFFClose(data); + + return numLayers; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +RGBATexture read2DTexture(std::string const& path, uint32_t layer) { + RGBATexture texture; + + auto* data = TIFFOpen(path.c_str(), "r"); + + if (!data) { + std::cerr << "Failed to open TIFF file '" << path << "'" << std::endl; + return texture; + } + + TIFFGetField(data, TIFFTAG_IMAGELENGTH, &texture.height); + TIFFGetField(data, TIFFTAG_IMAGEWIDTH, &texture.width); + + std::vector rgbData(texture.width * texture.height * 3); + + TIFFSetDirectory(data, layer); + for (unsigned y = 0; y < texture.height; y++) { + TIFFReadScanline(data, &rgbData[texture.width * 3 * y], y); + } + + TIFFClose(data); + + texture.data = addAlphaChannel(rgbData); + + return texture; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void write2D(std::string const& path, float* texture, int width, int height, int components) { + auto* tiff = TIFFOpen(path.c_str(), "w"); + TIFFSetField(tiff, TIFFTAG_IMAGEWIDTH, width); + TIFFSetField(tiff, TIFFTAG_IMAGELENGTH, height); + TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, components); + TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 32); + TIFFSetField(tiff, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP); + TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); + TIFFSetField(tiff, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, 1); + for (int y = 0; y < height; ++y) { + TIFFWriteScanline(tiff, texture + y * width * components, height - y - 1); + } + TIFFClose(tiff); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void write3D( + std::string const& path, float* texture, int width, int height, int depth, int components) { + auto* tiff = TIFFOpen(path.c_str(), "w"); + + for (int z = 0; z < depth; ++z) { + TIFFSetField(tiff, TIFFTAG_PAGENUMBER, z, z); + TIFFSetField(tiff, TIFFTAG_SUBFILETYPE, FILETYPE_PAGE); + TIFFSetField(tiff, TIFFTAG_IMAGEWIDTH, width); + TIFFSetField(tiff, TIFFTAG_IMAGELENGTH, height); + TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, components); + TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 32); + TIFFSetField(tiff, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP); + TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); + TIFFSetField(tiff, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, 1); + for (int y = 0; y < height; ++y) { + TIFFWriteScanline( + tiff, texture + z * width * height * components + y * width * components, height - y - 1); + } + TIFFWriteDirectory(tiff); + } + TIFFClose(tiff); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +} // namespace tiff_utils diff --git a/tools/eclipse-shadow-generator/tiff_utils.hpp b/tools/eclipse-shadow-generator/tiff_utils.hpp new file mode 100644 index 000000000..e27738a20 --- /dev/null +++ b/tools/eclipse-shadow-generator/tiff_utils.hpp @@ -0,0 +1,37 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////// +// This file is part of CosmoScout VR // +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// SPDX-FileCopyrightText: German Aerospace Center (DLR) +// SPDX-License-Identifier: MIT + +#ifndef TIFF_HPP +#define TIFF_HPP + +#include +#include +#include + +namespace tiff_utils { +struct RGBATexture { + uint32_t width = 0; + uint32_t height = 0; + std::vector data; +}; + +// This helper function returns the number of layers in the given tiff file. +uint32_t getNumLayers(std::string const& path); + +// This helper function reads a 2D RGB tiff image from the given path and returns the data as a +// vector of floats. The data is stored in the order R, G, B, A, R, G, B, A, ... The alpha channel +// is always 1.0 but is still included in the returned vector for easier upload to the CUDA device. +// The optional layer parameter can be used to read a specific layer from a multi-layer tiff file. +RGBATexture read2DTexture(std::string const& path, uint32_t layer = 0); + +void write2D(std::string const& path, float* texture, int width, int height, int components); +void write3D( + std::string const& path, float* texture, int width, int height, int depth, int components); + +} // namespace tiff_utils + +#endif // TIFF_HPP \ No newline at end of file