/// <summary>
        /// Finally, we provide the actual implementation of the precomputation algorithm
        /// described in Algorithm 4.1 of
        /// <a href="https://hal.inria.fr/inria-00288758/en">our paper</a>. Each step is
        /// explained by the inline comments below.
        /// </summary>
        void Precompute(
            ComputeShader compute,
            TextureBuffer buffer,
            double[] lambdas,
            double[] luminance_from_radiance,
            bool blend,
            int num_scattering_orders)
        {
            int BLEND       = blend ? 1 : 0;
            int NUM_THREADS = CONSTANTS.NUM_THREADS;

            BindToCompute(compute, lambdas, luminance_from_radiance);

            int compute_transmittance       = compute.FindKernel("ComputeTransmittance");
            int compute_direct_irradiance   = compute.FindKernel("ComputeDirectIrradiance");
            int compute_single_scattering   = compute.FindKernel("ComputeSingleScattering");
            int compute_scattering_density  = compute.FindKernel("ComputeScatteringDensity");
            int compute_indirect_irradiance = compute.FindKernel("ComputeIndirectIrradiance");
            int compute_multiple_scattering = compute.FindKernel("ComputeMultipleScattering");

            // Compute the transmittance, and store it in transmittance_texture
            compute.SetTexture(compute_transmittance, "transmittanceWrite", buffer.TransmittanceArray[WRITE]);
            compute.SetVector("blend", new Vector4(0, 0, 0, 0));
            compute.Dispatch(compute_transmittance, CONSTANTS.TRANSMITTANCE_WIDTH / NUM_THREADS, CONSTANTS.TRANSMITTANCE_HEIGHT / NUM_THREADS, 1);
            Swap(buffer.TransmittanceArray);

            // Compute the direct irradiance, store it in delta_irradiance_texture and,
            // depending on 'blend', either initialize irradiance_texture_ with zeros or
            // leave it unchanged (we don't want the direct irradiance in
            // irradiance_texture_, but only the irradiance from the sky).
            compute.SetTexture(compute_direct_irradiance, "deltaIrradianceWrite", buffer.DeltaIrradianceTexture); //0
            compute.SetTexture(compute_direct_irradiance, "irradianceWrite", buffer.IrradianceArray[WRITE]);      //1
            compute.SetTexture(compute_direct_irradiance, "irradianceRead", buffer.IrradianceArray[READ]);
            compute.SetTexture(compute_direct_irradiance, "transmittanceRead", buffer.TransmittanceArray[READ]);
            compute.SetVector("blend", new Vector4(0, BLEND, 0, 0));
            compute.Dispatch(compute_direct_irradiance, CONSTANTS.IRRADIANCE_WIDTH / NUM_THREADS, CONSTANTS.IRRADIANCE_HEIGHT / NUM_THREADS, 1);
            Swap(buffer.IrradianceArray);

            // Compute the rayleigh and mie single scattering, store them in
            // delta_rayleigh_scattering_texture and delta_mie_scattering_texture, and
            // either store them or accumulate them in scattering_texture_ and
            // optional_single_mie_scattering_texture_.
            compute.SetTexture(compute_single_scattering, "deltaRayleighScatteringWrite", buffer.DeltaRayleighScatteringTexture);      //0
            compute.SetTexture(compute_single_scattering, "deltaMieScatteringWrite", buffer.DeltaMieScatteringTexture);                //1
            compute.SetTexture(compute_single_scattering, "scatteringWrite", buffer.ScatteringArray[WRITE]);                           //2
            compute.SetTexture(compute_single_scattering, "scatteringRead", buffer.ScatteringArray[READ]);
            compute.SetTexture(compute_single_scattering, "singleMieScatteringWrite", buffer.OptionalSingleMieScatteringArray[WRITE]); //3
            compute.SetTexture(compute_single_scattering, "singleMieScatteringRead", buffer.OptionalSingleMieScatteringArray[READ]);
            compute.SetTexture(compute_single_scattering, "transmittanceRead", buffer.TransmittanceArray[READ]);
            compute.SetVector("blend", new Vector4(0, 0, BLEND, BLEND));

            for (int layer = 0; layer < CONSTANTS.SCATTERING_DEPTH; ++layer)
            {
                compute.SetInt("layer", layer);
                compute.Dispatch(compute_single_scattering, CONSTANTS.SCATTERING_WIDTH / NUM_THREADS, CONSTANTS.SCATTERING_HEIGHT / NUM_THREADS, 1);
            }
            Swap(buffer.ScatteringArray);
            Swap(buffer.OptionalSingleMieScatteringArray);

            // Compute the 2nd, 3rd and 4th order of scattering, in sequence.
            for (int scattering_order = 2; scattering_order <= num_scattering_orders; ++scattering_order)
            {
                // Compute the scattering density, and store it in
                // delta_scattering_density_texture.
                compute.SetTexture(compute_scattering_density, "deltaScatteringDensityWrite", buffer.DeltaScatteringDensityTexture); //0
                compute.SetTexture(compute_scattering_density, "transmittanceRead", buffer.TransmittanceArray[READ]);
                compute.SetTexture(compute_scattering_density, "singleRayleighScatteringRead", buffer.DeltaRayleighScatteringTexture);
                compute.SetTexture(compute_scattering_density, "singleMieScatteringRead", buffer.DeltaMieScatteringTexture);
                compute.SetTexture(compute_scattering_density, "multipleScatteringRead", buffer.DeltaMultipleScatteringTexture);
                compute.SetTexture(compute_scattering_density, "irradianceRead", buffer.DeltaIrradianceTexture);
                compute.SetInt("scatteringOrder", scattering_order);
                compute.SetVector("blend", new Vector4(0, 0, 0, 0));

                for (int layer = 0; layer < CONSTANTS.SCATTERING_DEPTH; ++layer)
                {
                    compute.SetInt("layer", layer);
                    compute.Dispatch(compute_scattering_density, CONSTANTS.SCATTERING_WIDTH / NUM_THREADS, CONSTANTS.SCATTERING_HEIGHT / NUM_THREADS, 1);
                }

                // Compute the indirect irradiance, store it in delta_irradiance_texture and
                // accumulate it in irradiance_texture_.
                compute.SetTexture(compute_indirect_irradiance, "deltaIrradianceWrite", buffer.DeltaIrradianceTexture); //0
                compute.SetTexture(compute_indirect_irradiance, "irradianceWrite", buffer.IrradianceArray[WRITE]);      //1
                compute.SetTexture(compute_indirect_irradiance, "irradianceRead", buffer.IrradianceArray[READ]);
                compute.SetTexture(compute_indirect_irradiance, "singleRayleighScatteringRead", buffer.DeltaRayleighScatteringTexture);
                compute.SetTexture(compute_indirect_irradiance, "singleMieScatteringRead", buffer.DeltaMieScatteringTexture);
                compute.SetTexture(compute_indirect_irradiance, "multipleScatteringRead", buffer.DeltaMultipleScatteringTexture);
                compute.SetInt("scatteringOrder", scattering_order - 1);
                compute.SetVector("blend", new Vector4(0, 1, 0, 0));

                compute.Dispatch(compute_indirect_irradiance, CONSTANTS.IRRADIANCE_WIDTH / NUM_THREADS, CONSTANTS.IRRADIANCE_HEIGHT / NUM_THREADS, 1);
                Swap(buffer.IrradianceArray);

                // Compute the multiple scattering, store it in
                // delta_multiple_scattering_texture, and accumulate it in
                // scattering_texture_.
                compute.SetTexture(compute_multiple_scattering, "deltaMultipleScatteringWrite", buffer.DeltaMultipleScatteringTexture); //0
                compute.SetTexture(compute_multiple_scattering, "scatteringWrite", buffer.ScatteringArray[WRITE]);                      //1
                compute.SetTexture(compute_multiple_scattering, "scatteringRead", buffer.ScatteringArray[READ]);
                compute.SetTexture(compute_multiple_scattering, "transmittanceRead", buffer.TransmittanceArray[READ]);
                compute.SetTexture(compute_multiple_scattering, "deltaScatteringDensityRead", buffer.DeltaScatteringDensityTexture);
                compute.SetVector("blend", new Vector4(0, 1, 0, 0));

                for (int layer = 0; layer < CONSTANTS.SCATTERING_DEPTH; ++layer)
                {
                    compute.SetInt("layer", layer);
                    compute.Dispatch(compute_multiple_scattering, CONSTANTS.SCATTERING_WIDTH / NUM_THREADS, CONSTANTS.SCATTERING_HEIGHT / NUM_THREADS, 1);
                }
                Swap(buffer.ScatteringArray);
            }

            return;
        }
        /// <summary>
        /// The Init method precomputes the atmosphere textures. It first allocates the
        /// temporary resources it needs, then calls Precompute to do the
        /// actual precomputations, and finally destroys the temporary resources.
        ///
        /// Note that there are two precomputation modes here, depending on whether we
        /// want to store precomputed irradiance or illuminance values:
        ///
        /// In precomputed irradiance mode, we simply need to call
        /// Precompute with the 3 wavelengths for which we want to precompute
        /// irradiance, namely kLambdaR, kLambdaG, kLambdaB(with the identity matrix for
        /// luminance_from_radiance, since we don't want any conversion from radiance to luminance).
        ///
        /// In precomputed illuminance mode, we need to precompute irradiance for
        /// num_precomputed_wavelengths, and then integrate the results,
        /// multiplied with the 3 CIE xyz color matching functions and the XYZ to sRGB
        /// matrix to get sRGB illuminance values.
        /// A naive solution would be to allocate temporary textures for the
        /// intermediate irradiance results, then perform the integration from irradiance
        /// to illuminance and store the result in the final precomputed texture. In
        /// pseudo-code (and assuming one wavelength per texture instead of 3):
        ///
        ///  create n temporary irradiance textures
        ///  for each wavelength lambda in the n wavelengths:
        ///     precompute irradiance at lambda into one of the temporary textures
        ///  initializes the final illuminance texture with zeros
        ///  for each wavelength lambda in the n wavelengths:
        ///     accumulate in the final illuminance texture the product of the
        ///     precomputed irradiance at lambda (read from the temporary textures)
        ///     with the value of the 3 sRGB color matching functions at lambda
        ///     (i.e. the product of the XYZ to sRGB matrix with the CIE xyz color matching functions).
        ///
        /// However, this be would waste GPU memory. Instead, we can avoid allocating
        /// temporary irradiance textures, by merging the two above loops:
        ///
        ///   for each wavelength lambda in the n wavelengths:
        ///     accumulate in the final illuminance texture (or, for the first
        ///     iteration, set this texture to) the product of the precomputed
        ///     irradiance at lambda (computed on the fly) with the value of the 3
        ///     sRGB color matching functions at lambda.
        ///
        /// This is the method we use below, with 3 wavelengths per iteration instead
        /// of 1, using Precompute to compute 3 irradiances values per
        /// iteration, and luminance_from_radiance to multiply 3 irradiances
        /// with the values of the 3 sRGB color matching functions at 3 different
        /// wavelengths (yielding a 3x3 matrix).
        ///
        /// This yields the following implementation:
        /// </summary>
        public void Init(ComputeShader compute, int num_scattering_orders)
        {
            // The precomputations require temporary textures, in particular to store the
            // contribution of one scattering order, which is needed to compute the next
            // order of scattering (the final precomputed textures store the sum of all
            // the scattering orders). We allocate them here, and destroy them at the end
            // of this method.
            TextureBuffer buffer = new TextureBuffer(HalfPrecision);

            buffer.Clear(compute);

            // The actual precomputations depend on whether we want to store precomputed
            // irradiance or illuminance values.
            if (NumPrecomputedWavelengths <= 3)
            {
                Precompute(compute, buffer, null, null, false, num_scattering_orders);
            }
            else
            {
                int    num_iterations = (NumPrecomputedWavelengths + 2) / 3;
                double dlambda        = (kLambdaMax - kLambdaMin) / (3.0 * num_iterations);

                for (int i = 0; i < num_iterations; ++i)
                {
                    double[] lambdas = new double[]
                    {
                        kLambdaMin + (3 * i + 0.5) * dlambda,
                        kLambdaMin + (3 * i + 1.5) * dlambda,
                        kLambdaMin + (3 * i + 2.5) * dlambda
                    };

                    double[] luminance_from_radiance = new double[]
                    {
                        Coeff(lambdas[0], 0) * dlambda, Coeff(lambdas[1], 0) * dlambda, Coeff(lambdas[2], 0) * dlambda,
                        Coeff(lambdas[0], 1) * dlambda, Coeff(lambdas[1], 1) * dlambda, Coeff(lambdas[2], 1) * dlambda,
                        Coeff(lambdas[0], 2) * dlambda, Coeff(lambdas[1], 2) * dlambda, Coeff(lambdas[2], 2) * dlambda
                    };

                    bool blend = i > 0;
                    Precompute(compute, buffer, lambdas, luminance_from_radiance, blend, num_scattering_orders);
                }

                // 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:
                int compute_transmittance = compute.FindKernel("ComputeTransmittance");
                BindToCompute(compute, null, null);
                compute.SetTexture(compute_transmittance, "transmittanceWrite", buffer.TransmittanceArray[WRITE]);
                compute.SetVector("blend", new Vector4(0, 0, 0, 0));

                int NUM = CONSTANTS.NUM_THREADS;
                compute.Dispatch(compute_transmittance, CONSTANTS.TRANSMITTANCE_WIDTH / NUM, CONSTANTS.TRANSMITTANCE_HEIGHT / NUM, 1);
                Swap(buffer.TransmittanceArray);
            }

            //Grab ref to textures and mark as null in buffer so they are not released.
            TransmittanceTexture            = buffer.TransmittanceArray[READ];
            buffer.TransmittanceArray[READ] = null;

            ScatteringTexture            = buffer.ScatteringArray[READ];
            buffer.ScatteringArray[READ] = null;

            IrradianceTexture            = buffer.IrradianceArray[READ];
            buffer.IrradianceArray[READ] = null;

            if (CombineScatteringTextures)
            {
                OptionalSingleMieScatteringTexture = null;
            }
            else
            {
                OptionalSingleMieScatteringTexture            = buffer.OptionalSingleMieScatteringArray[READ];
                buffer.OptionalSingleMieScatteringArray[READ] = null;
            }

            // Delete the temporary resources allocated at the begining of this method.
            buffer.Release();
        }