/// <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(); }