physics/cloud.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-License-Identifier: BSD-3-Clause | ||
| 2 | /// @file cloud.h | ||
| 3 | /// @brief Clouds: droplet phase functions, multiple-scattering transport in slabs, and the noise | ||
| 4 | /// textures cloud layers are carved from. | ||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include "ernie/physics/spectrum.h" | ||
| 8 | #include "ernie/util/math.h" | ||
| 9 | |||
| 10 | #include <cstddef> | ||
| 11 | #include <cstdint> | ||
| 12 | #include <numbers> | ||
| 13 | #include <vector> | ||
| 14 | |||
| 15 | namespace ernie { | ||
| 16 | |||
| 17 | // ---------------------------------------------------------------------------------------------- | ||
| 18 | // Droplet phase functions | ||
| 19 | |||
| 20 | /// @brief Cloud droplets' phase function in each band. | ||
| 21 | /// Mie scattering by gamma-distributed radii, read by volume.slang's `dropletAt`. Its lobes give | ||
| 22 | /// the silver lining, fogbow and glory. The narrow forward peak is folded into the extinction for | ||
| 23 | /// multiple scattering and kept for single scattering. | ||
| 24 | /// | ||
| 25 | /// References: | ||
| 26 | /// - A. Bouthors, F. Neyret and S. Lefebvre, "Real-time realistic illumination and shading of | ||
| 27 | /// stratiform clouds", Eurographics Workshop on Natural Phenomena 2006 (the droplets' gamma | ||
| 28 | /// distribution, and the forward peak folded into the extinction, their "modified Mie"). | ||
| 29 | /// - A. Bouthors, F. Neyret, N. Max, E. Bruneton and C. Crassin, "Interactive multiple | ||
| 30 | /// anisotropic scattering in clouds", ACM SIGGRAPH Symposium on Interactive 3D Graphics and | ||
| 31 | /// Games 2008. | ||
| 32 | /// - J. E. Hansen and L. D. Travis, "Light scattering in planetary atmospheres", Space Science | ||
| 33 | /// Reviews 16(4):527-610, 1974, section 2.4 (distributions alike in effective radius and | ||
| 34 | /// variance scatter alike: the gamma's as a log-normal's). https://doi.org/10.1007/BF00168069 | ||
| 35 | 3 | struct DropletPhaseTable { | |
| 36 | /// Samples per row, at theta(u) = pi/2 (1 - cos pi u) for u = i / (angles - 1): dense near the | ||
| 37 | /// forward and back peaks. | ||
| 38 | std::size_t angles = 0; | ||
| 39 | /// Rows of `angles` values (sr^-1). One per band with its peak kept, over 1 - forwardFraction; | ||
| 40 | /// then the bands' mean with the peak folded away, for multiple scattering. | ||
| 41 | std::vector<float> values; | ||
| 42 | /// Share of light in the forward peak, averaged over bands; the extinction is scaled by 1 | ||
| 43 | /// minus it. | ||
| 44 | double forwardFraction = 0.0; | ||
| 45 | |||
| 46 | /// @brief The scattering angle of a sample. | ||
| 47 | /// @param i The sample's index along a row. | ||
| 48 | /// @return theta(i / (angles - 1)), in radians. | ||
| 49 | 51195 | double Angle(std::size_t i) const { | |
| 50 | 51195 | return std::numbers::pi * CosineSpacing(i, angles); | |
| 51 | } | ||
| 52 | }; | ||
| 53 | |||
| 54 | /// Half angle of the forward peak folded into the extinction, in degrees. | ||
| 55 | inline constexpr double kDropletForwardDegrees = 5.0; | ||
| 56 | |||
| 57 | /// @brief Computes droplets' phase function at each band's centre. | ||
| 58 | /// @param effectiveRadiusUm Effective droplet radius, in micrometres. | ||
| 59 | /// @param spread The gamma distribution's width parameter; effective variance is | ||
| 60 | /// 1 / (spread + 2). | ||
| 61 | /// @param bands The frame's bands. | ||
| 62 | /// @param angles Samples per row. | ||
| 63 | /// @return The table. | ||
| 64 | DropletPhaseTable DropletPhase(double effectiveRadiusUm, double spread, const SpectralBands& bands, | ||
| 65 | std::size_t angles = 1024); | ||
| 66 | |||
| 67 | // ---------------------------------------------------------------------------------------------- | ||
| 68 | // Transport in slabs | ||
| 69 | |||
| 70 | /// Scattering order sets kept apart, each with its own collector: 2, 3-4, 5-6, 7-8, 9-12, 13-18, | ||
| 71 | /// 19-30, 31+. Low orders stay near the view ray; high ones diffuse. | ||
| 72 | inline constexpr std::size_t kCloudOrderSets = 8; | ||
| 73 | |||
| 74 | /// @brief Finds the order set an order falls in. | ||
| 75 | /// @param order Times scattered, 2 or more. | ||
| 76 | /// @return The order set. | ||
| 77 | std::size_t CloudOrderSet(std::size_t order); | ||
| 78 | |||
| 79 | /// @brief The canonical transport table's lattice. | ||
| 80 | /// Lengths are optical depths of extinction less the forward peak (`DropletPhase`). | ||
| 81 | struct CloudTransportShape { | ||
| 82 | /// Light cosines to the slab normal: (i + 1) / count. | ||
| 83 | std::size_t lightCosines = 8; | ||
| 84 | /// Slab optical thicknesses: 0.25 * 2^i. | ||
| 85 | std::size_t thicknesses = 10; | ||
| 86 | /// View depths as thickness fractions, (1 - cos pi u) / 2 with u = i / (count - 1): dense near | ||
| 87 | /// both faces. | ||
| 88 | std::size_t depths = 9; | ||
| 89 | /// Bins of the travel direction's cosine to the inward normal, over [-1, 1]; negative goes | ||
| 90 | /// back up. | ||
| 91 | std::size_t viewCosines = 16; | ||
| 92 | /// Bins of azimuth from the light's, over [0, pi]; symmetric about the light's plane. | ||
| 93 | std::size_t azimuths = 8; | ||
| 94 | |||
| 95 | /// @return Light cosine `i`. | ||
| 96 | double LightCosine(std::size_t i) const; | ||
| 97 | /// @return Optical thickness `i`. | ||
| 98 | double Thickness(std::size_t i) const; | ||
| 99 | /// @return Depth `i` as a fraction of the thickness. | ||
| 100 | double DepthFraction(std::size_t i) const; | ||
| 101 | /// @return The cosine at bin `i`'s centre. | ||
| 102 | double ViewCosine(std::size_t i) const; | ||
| 103 | /// @return The azimuth at bin `i`'s centre. | ||
| 104 | double Azimuth(std::size_t i) const; | ||
| 105 | /// @return Entries per order set. | ||
| 106 | std::size_t Cells() const; | ||
| 107 | /// @brief Indexes an entry within an order set. | ||
| 108 | /// @param light The light cosine index. | ||
| 109 | /// @param thickness The thickness index. | ||
| 110 | /// @param depth The depth index. | ||
| 111 | /// @param viewCosine The view cosine bin. | ||
| 112 | /// @param azimuth The azimuth bin. | ||
| 113 | /// @return The cell index. | ||
| 114 | std::size_t Cell(std::size_t light, std::size_t thickness, std::size_t depth, | ||
| 115 | std::size_t viewCosine, std::size_t azimuth) const; | ||
| 116 | }; | ||
| 117 | |||
| 118 | /// @brief Light transport in a plane-parallel cloud slab lit from above, per scattering order set. | ||
| 119 | /// Gives radiance at a point inside in each direction, and where on the lit face it entered (its | ||
| 120 | /// collector). Lit faces of any cloud, treated locally as slabs, read multiple scattering from it. | ||
| 121 | /// | ||
| 122 | /// References: | ||
| 123 | /// - A. Bouthors, F. Neyret, N. Max, E. Bruneton and C. Crassin, "Interactive multiple | ||
| 124 | /// anisotropic scattering in clouds", ACM SIGGRAPH Symposium on Interactive 3D Graphics and | ||
| 125 | /// Games 2008 (the canonical transport function and its collectors, by Monte Carlo in slabs; | ||
| 126 | /// stored here as a lattice rather than fitted). | ||
| 127 | /// - S. Chandrasekhar, "Radiative Transfer", Dover 1960 (plane-parallel slabs). | ||
| 128 | 2 | struct CloudTransportTable { | |
| 129 | /// The lattice. | ||
| 130 | CloudTransportShape shape; | ||
| 131 | /// Four values per order set and cell: radiance per unit irradiance normal to the light | ||
| 132 | /// (sr^-1); the collector's centre on the lit face along and across the light's azimuth; and | ||
| 133 | /// its spread. Lengths in optical depths. | ||
| 134 | std::vector<float> values; | ||
| 135 | /// Single-scattering radiance per cell, to check against its analytic form. | ||
| 136 | std::vector<float> single; | ||
| 137 | |||
| 138 | /// @brief Reads one value. | ||
| 139 | /// @param set The order set. | ||
| 140 | /// @param cell The cell (`CloudTransportShape::Cell`). | ||
| 141 | /// @param component 0 radiance, 1-2 collector centre, 3 spread. | ||
| 142 | /// @return The value. | ||
| 143 | 49177 | float At(std::size_t set, std::size_t cell, std::size_t component) const { | |
| 144 | 49177 | return values[(set * shape.Cells() + cell) * 4 + component]; | |
| 145 | } | ||
| 146 | }; | ||
| 147 | |||
| 148 | /// @brief How `SimulateCloudTransport` runs. | ||
| 149 | struct CloudTransportOptions { | ||
| 150 | /// The lattice. | ||
| 151 | CloudTransportShape shape; | ||
| 152 | /// Photons per slab (light cosine and thickness). | ||
| 153 | std::size_t photons = 1 << 17; | ||
| 154 | /// The random seed. | ||
| 155 | std::uint64_t seed = 1; | ||
| 156 | }; | ||
| 157 | |||
| 158 | /// @brief Simulates the canonical transport by Monte Carlo. | ||
| 159 | /// Photons enter each slab's lit face and scatter without absorption; crossings of the depth | ||
| 160 | /// planes are tallied by order set and direction with their entry points. | ||
| 161 | /// @param phase Droplets; the mean row (peak folded away) is used. | ||
| 162 | /// @param options The lattice and sampling. | ||
| 163 | /// @return The table. | ||
| 164 | CloudTransportTable SimulateCloudTransport(const DropletPhaseTable& phase, | ||
| 165 | const CloudTransportOptions& options = {}); | ||
| 166 | |||
| 167 | /// @brief The transport for Bouthors et al.'s droplets (7 um, gamma 2), cached between runs. | ||
| 168 | /// Read from `ERNIE_CLOUD_CACHE` or "clouds" in the user's cache folder; else simulated (some | ||
| 169 | /// seconds) and stored. | ||
| 170 | /// @param options The lattice and sampling. | ||
| 171 | /// @return The table, without `single`. | ||
| 172 | CloudTransportTable CachedCloudTransport(const CloudTransportOptions& options = {}); | ||
| 173 | |||
| 174 | // ---------------------------------------------------------------------------------------------- | ||
| 175 | // Noise textures | ||
| 176 | |||
| 177 | /// @brief The noise textures cloud layers are carved from. | ||
| 178 | /// Read by volume.slang's `cloudLayersDensity`. Values in [0, 1], x fastest; every texture tiles. | ||
| 179 | /// | ||
| 180 | /// References: | ||
| 181 | /// - A. Schneider and N. Vos, "The Real-Time Volumetric Cloudscapes of Horizon: Zero Dawn", | ||
| 182 | /// SIGGRAPH 2015 Advances in Real-Time Rendering (Perlin-Worley shapes eroded by Worley | ||
| 183 | /// details). | ||
| 184 | /// - S. Hillaire, TileableVolumeNoise, 2017, MIT (https://github.com/sebh/TileableVolumeNoise): | ||
| 185 | /// the textures' recipes. | ||
| 186 | /// - takram, three-geospatial's clouds (packages/clouds/src/shaders: cloudShape, | ||
| 187 | /// cloudShapeDetail, localWeather and turbulence), 2024, MIT, Copyright (c) 2024 Shota | ||
| 188 | /// Matsuda: the frequencies and weights here. | ||
| 189 | struct CloudNoise { | ||
| 190 | /// Side of the shape volume, in texels. | ||
| 191 | std::size_t shapeSize = 128; | ||
| 192 | /// Perlin-Worley shape: shapeSize^3 values. | ||
| 193 | std::vector<float> shape; | ||
| 194 | /// Side of the detail volume, in texels. | ||
| 195 | std::size_t detailSize = 32; | ||
| 196 | /// Worley detail: detailSize^3 values. | ||
| 197 | std::vector<float> detail; | ||
| 198 | /// Side of the weather map, in texels. | ||
| 199 | std::size_t weatherSize = 512; | ||
| 200 | /// weatherSize^2 texels of four channels: low, mid and high layers' coverage, then 1. | ||
| 201 | std::vector<float> weather; | ||
| 202 | /// Side of the turbulence map, in texels. | ||
| 203 | std::size_t turbulenceSize = 128; | ||
| 204 | /// turbulenceSize^2 texels of three channels: a unit curl direction mapped to [0, 1]. | ||
| 205 | std::vector<float> turbulence; | ||
| 206 | }; | ||
| 207 | |||
| 208 | /// @brief Generates the cloud noise across CPU threads. | ||
| 209 | /// @param shapeSize Side of the shape volume, in texels. | ||
| 210 | /// @param detailSize Side of the detail volume, in texels. | ||
| 211 | /// @param weatherSize Side of the weather map, in texels. | ||
| 212 | /// @param turbulenceSize Side of the turbulence map, in texels. | ||
| 213 | /// @return The textures. | ||
| 214 | CloudNoise MakeCloudNoise(std::size_t shapeSize = 128, std::size_t detailSize = 32, | ||
| 215 | std::size_t weatherSize = 512, std::size_t turbulenceSize = 128); | ||
| 216 | |||
| 217 | /// @brief `MakeCloudNoise`, cached between runs. | ||
| 218 | /// Read from `ERNIE_CLOUD_CACHE` or "clouds" in the user's cache folder; else generated (some | ||
| 219 | /// seconds) and stored there. | ||
| 220 | /// @return The textures. | ||
| 221 | CloudNoise CachedCloudNoise(); | ||
| 222 | |||
| 223 | } // namespace ernie | ||
| 224 |