GCC Code Coverage Report


ernie/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
6 / 6
100.0%
Branches:
4 / 4
100.0%
Functions:
1 / 1, 1 excl
100.0%

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
4/4
✓ Branch 0 taken 20477 times.
✓ Branch 0 taken 2046 times.
✓ Branch 1 taken 20483 times.
✓ Branch 1 taken 2 times.
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