Blender V5.0
sky_single_scattering.cpp
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2011-2020 Blender Authors
2 *
3 * SPDX-License-Identifier: Apache-2.0 */
4
8
9#include <algorithm>
10
11#include "sky_math.h"
12#include "sky_nishita.h"
13
14/* Constants. */
15static const float RAYLEIGH_SCALE = 8e3f; /* Rayleigh scale height (m). */
16static const float MIE_SCALE = 1.2e3f; /* Mie scale height (m). */
17static const float MIE_COEFF = 2e-5f; /* Mie scattering coefficient (m^-1). */
18static const float MIE_G = 0.76f; /* Aerosols anisotropy. */
19static const float SQR_G = MIE_G * MIE_G; /* Squared aerosols anisotropy. */
20static const float EARTH_RADIUS = 6360e3f; /* Radius of Earth (m). */
21static const float ATMOSPHERE_RADIUS = 6420e3f; /* Radius of atmosphere (m). */
22static const int STEPS = 32; /* Segments of primary ray. */
23static const int NUM_WAVELENGTHS = 21; /* Number of wavelengths. */
24static const int MIN_WAVELENGTH = 380; /* Lowest sampled wavelength (nm). */
25static const int MAX_WAVELENGTH = 780; /* Highest sampled wavelength (nm). */
26/* Step between each sampled wavelength (nm). */
28/* Sun irradiance on top of the atmosphere (W*m^-2*nm^-1). */
29static const float IRRADIANCE[] = {
30 1.45756829855592995315f, 1.56596305559738380175f, 1.65148449067670455293f,
31 1.71496242737209314555f, 1.75797983805020541226f, 1.78256407885924539336f,
32 1.79095108475838560302f, 1.78541550133410664714f, 1.76815554864306845317f,
33 1.74122069647250410362f, 1.70647127164943679389f, 1.66556087452739887134f,
34 1.61993437242451854274f, 1.57083597368892080581f, 1.51932335059305478886f,
35 1.46628494965214395407f, 1.41245852740172450623f, 1.35844961970384092709f,
36 1.30474913844739281998f, 1.25174963272610817455f, 1.19975998755420620867f};
37/* Rayleigh scattering coefficient (m^-1). */
38static const float RAYLEIGH_COEFF[] = {
39 0.00005424820087636473f, 0.00004418549866505454f, 0.00003635151910165377f,
40 0.00003017929012024763f, 0.00002526320226989157f, 0.00002130859310621843f,
41 0.00001809838025320633f, 0.00001547057129129042f, 0.00001330284977336850f,
42 0.00001150184784075764f, 0.00000999557429990163f, 0.00000872799973630707f,
43 0.00000765513700977967f, 0.00000674217203751443f, 0.00000596134125832052f,
44 0.00000529034598065810f, 0.00000471115687557433f, 0.00000420910481110487f,
45 0.00000377218381260133f, 0.00000339051255477280f, 0.00000305591531679811f};
46/* Ozone absorption coefficient (m^-1). */
47static const float OZONE_COEFF[] = {
48 0.00000000325126849861f, 0.00000000585395365047f, 0.00000001977191155085f,
49 0.00000007309568762914f, 0.00000020084561514287f, 0.00000040383958096161f,
50 0.00000063551335912363f, 0.00000096707041180970f, 0.00000154797400424410f,
51 0.00000209038647223331f, 0.00000246128056164565f, 0.00000273551299461512f,
52 0.00000215125863128643f, 0.00000159051840791988f, 0.00000112356197979857f,
53 0.00000073527551487574f, 0.00000046450130357806f, 0.00000033096079921048f,
54 0.00000022512612292678f, 0.00000014879129266490f, 0.00000016828623364192f};
55/* CIE XYZ color matching functions. */
56static const float CMF_XYZ[][3] = {{0.00136800000f, 0.00003900000f, 0.00645000100f},
57 {0.01431000000f, 0.00039600000f, 0.06785001000f},
58 {0.13438000000f, 0.00400000000f, 0.64560000000f},
59 {0.34828000000f, 0.02300000000f, 1.74706000000f},
60 {0.29080000000f, 0.06000000000f, 1.66920000000f},
61 {0.09564000000f, 0.13902000000f, 0.81295010000f},
62 {0.00490000000f, 0.32300000000f, 0.27200000000f},
63 {0.06327000000f, 0.71000000000f, 0.07824999000f},
64 {0.29040000000f, 0.95400000000f, 0.02030000000f},
65 {0.59450000000f, 0.99500000000f, 0.00390000000f},
66 {0.91630000000f, 0.87000000000f, 0.00165000100f},
67 {1.06220000000f, 0.63100000000f, 0.00080000000f},
68 {0.85444990000f, 0.38100000000f, 0.00019000000f},
69 {0.44790000000f, 0.17500000000f, 0.00002000000f},
70 {0.16490000000f, 0.06100000000f, 0.00000000000f},
71 {0.04677000000f, 0.01700000000f, 0.00000000000f},
72 {0.01135916000f, 0.00410200000f, 0.00000000000f},
73 {0.00289932700f, 0.00104700000f, 0.00000000000f},
74 {0.00069007860f, 0.00024920000f, 0.00000000000f},
75 {0.00016615050f, 0.00006000000f, 0.00000000000f},
76 {0.00004150994f, 0.00001499000f, 0.00000000000f}};
77
78/* Parameters for optical depth quadrature.
79 * See the comment in ray_optical_depth for more detail.
80 * Computed using sympy and following Python code:
81 * # from sympy.integrals.quadrature import gauss_laguerre
82 * # from sympy import exp
83 * # x, w = gauss_laguerre(8, 50)
84 * # xend = 25
85 * # print([(xi / xend).evalf(10) for xi in x])
86 * # print([(wi * exp(xi) / xend).evalf(10) for xi, wi in zip(x, w)])
87 */
88static const int QUADRATURE_STEPS = 8;
89static const float QUADRATURE_NODES[] = {0.006811185292f,
90 0.03614807107f,
91 0.09004346519f,
92 0.1706680068f,
93 0.2818362161f,
94 0.4303406404f,
95 0.6296271457f,
96 0.9145252695f};
97static const float QUADRATURE_WEIGHTS[] = {0.01750893642f,
98 0.04135477391f,
99 0.06678839063f,
100 0.09507698807f,
101 0.1283416365f,
102 0.1707430204f,
103 0.2327233347f,
104 0.3562490486f};
105
106static float3 geographical_to_direction(float lat, float lon)
107{
108 return make_float3(cosf(lat) * cosf(lon), cosf(lat) * sinf(lon), sinf(lat));
109}
110
111static float3 spec_to_xyz(const float *spectrum)
112{
113 float3 xyz = make_float3(0.0f, 0.0f, 0.0f);
114 for (int i = 0; i < NUM_WAVELENGTHS; i++) {
115 xyz.x += CMF_XYZ[i][0] * spectrum[i];
116 xyz.y += CMF_XYZ[i][1] * spectrum[i];
117 xyz.z += CMF_XYZ[i][2] * spectrum[i];
118 }
119 return xyz * STEP_LAMBDA;
120}
121
122/* Atmosphere volume models */
123static float density_rayleigh(float height)
124{
125 return expf(-height / RAYLEIGH_SCALE);
126}
127
128static float density_mie(float height)
129{
130 return expf(-height / MIE_SCALE);
131}
132
133static float density_ozone(float height)
134{
135 return fmax(0.0, 1.0 - (fabs(height - 25000.0) / 15000.0));
136}
137
138static float phase_rayleigh(float mu)
139{
140 return (0.1875f * M_1_PI_F) * (1.0f + sqr(mu));
141}
142
143static float phase_mie(float mu)
144{
145 return (3.0f * (1.0f - SQR_G) * (1.0f + sqr(mu))) /
146 (8.0f * M_PI_F * (2.0f + SQR_G) * powf((1.0f + SQR_G - 2.0f * MIE_G * mu), 1.5));
147}
148
149/* Intersection helpers. */
151{
152 if (dir.z >= 0) {
153 return false;
154 }
155 float b = -2.0f * dot(dir, -pos);
156 float c = len_squared(pos) - sqr(EARTH_RADIUS);
157 float t = b * b - 4.0f * c;
158 if (t >= 0.0f) {
159 return true;
160 }
161 return false;
162}
163
165{
166 float b = -2.0f * dot(dir, -pos);
167 float c = len_squared(pos) - sqr(ATMOSPHERE_RADIUS);
168 float t = (-b + sqrtf(b * b - 4.0f * c)) / 2.0f;
169 return make_float3(pos.x + dir.x * t, pos.y + dir.y * t, pos.z + dir.z * t);
170}
171
172static float3 ray_optical_depth(float3 ray_origin, float3 ray_dir)
173{
174 /* This function computes the optical depth along a ray.
175 * Instead of using classic ray marching, the code is based on Gauss-Laguerre quadrature,
176 * which is designed to compute the integral of f(x)*exp(-x) from 0 to infinity.
177 * This works well here, since the optical depth along the ray tends to decrease exponentially.
178 * By setting f(x) = g(x) exp(x), the exponentials cancel out and we get the integral of g(x).
179 * The nodes and weights used here are the standard n=6 Gauss-Laguerre values, except that
180 * the exp(x) scaling factor is already included in the weights.
181 * The parametrization along the ray is scaled so that the last quadrature node is still within
182 * the atmosphere. */
183 float3 ray_end = atmosphere_intersection(ray_origin, ray_dir);
184 float ray_length = distance(ray_origin, ray_end);
185
186 float3 segment = ray_length * ray_dir;
187
188 /* Instead of tracking the transmission spectrum across all wavelengths directly,
189 * we use the fact that the density always has the same spectrum for each type of
190 * scattering, so we split the density into a constant spectrum and a factor and
191 * only track the factors. */
192 float3 optical_depth = make_float3(0.0f, 0.0f, 0.0f);
193
194 for (int i = 0; i < QUADRATURE_STEPS; i++) {
195 float3 P = ray_origin + QUADRATURE_NODES[i] * segment;
196
197 /* Height above sea level. */
198 float height = len(P) - EARTH_RADIUS;
199
200 float3 density = make_float3(
201 density_rayleigh(height), density_mie(height), density_ozone(height));
202 optical_depth += density * QUADRATURE_WEIGHTS[i];
203 }
204
205 return optical_depth * ray_length;
206}
207
208static void single_scattering(float3 ray_dir,
209 float3 sun_dir,
210 float3 ray_origin,
211 float air_density,
212 float aerosol_density,
213 float ozone_density,
214 float *r_spectrum)
215{
216 /* This code computes single-inscattering along a ray through the atmosphere. */
217 float3 ray_end = atmosphere_intersection(ray_origin, ray_dir);
218 float ray_length = distance(ray_origin, ray_end);
219
220 /* To compute the inscattering, we step along the ray in segments and accumulate
221 * the inscattering as well as the optical depth along each segment. */
222 float segment_length = ray_length / STEPS;
223 float3 segment = segment_length * ray_dir;
224
225 /* Instead of tracking the transmission spectrum across all wavelengths directly,
226 * we use the fact that the density always has the same spectrum for each type of
227 * scattering, so we split the density into a constant spectrum and a factor and
228 * only track the factors. */
229 float3 optical_depth = make_float3(0.0f, 0.0f, 0.0f);
230
231 /* Zero out light accumulation. */
232 for (int wl = 0; wl < NUM_WAVELENGTHS; wl++) {
233 r_spectrum[wl] = 0.0f;
234 }
235
236 /* Phase function for scattering and the density scale factor. */
237 float mu = dot(ray_dir, sun_dir);
238 float3 phase_function = make_float3(phase_rayleigh(mu), phase_mie(mu), 0.0f);
239 float3 density_scale = make_float3(air_density, aerosol_density, ozone_density);
240
241 /* The density and in-scattering of each segment is evaluated at its middle. */
242 float3 P = ray_origin + 0.5f * segment;
243
244 for (int i = 0; i < STEPS; i++) {
245 /* Height above sea level. */
246 float height = len(P) - EARTH_RADIUS;
247
248 /* Evaluate and accumulate optical depth along the ray. */
249 float3 density = density_scale * make_float3(density_rayleigh(height),
250 density_mie(height),
251 density_ozone(height));
252 optical_depth += segment_length * density;
253
254 /* If the Earth isn't in the way, evaluate inscattering from the Sun. */
255 if (!surface_intersection(P, sun_dir)) {
256 float3 light_optical_depth = density_scale * ray_optical_depth(P, sun_dir);
257 float3 total_optical_depth = optical_depth + light_optical_depth;
258
259 /* Attenuation of light. */
260 for (int wl = 0; wl < NUM_WAVELENGTHS; wl++) {
261 float3 extinction_density = total_optical_depth * make_float3(RAYLEIGH_COEFF[wl],
262 1.11f * MIE_COEFF,
263 OZONE_COEFF[wl]);
264 float attenuation = expf(-reduce_add(extinction_density));
265
266 float3 scattering_density = density * make_float3(RAYLEIGH_COEFF[wl], MIE_COEFF, 0.0f);
267
268 /* The total inscattered radiance from one segment is:
269 * Tr(A<->B) * Tr(B<->C) * sigma_s * phase * L * segment_length
270 *
271 * These terms are:
272 * Tr(A<->B): Transmission from start to scattering position (tracked in optical_depth)
273 * Tr(B<->C): Transmission from scattering position to light (computed in
274 * ray_optical_depth) sigma_s: Scattering density phase: Phase function of the scattering
275 * type (Rayleigh or Mie) L: Radiance coming from the light source segment_length: The
276 * length of the segment
277 *
278 * The code here is just that, with a bit of additional optimization to not store full
279 * spectra for the optical depth
280 */
281 r_spectrum[wl] += attenuation * reduce_add(phase_function * scattering_density) *
282 IRRADIANCE[wl] * segment_length;
283 }
284 }
285
286 /* Advance along ray. */
287 P += segment;
288 }
289}
290
292 int stride,
293 int width,
294 int height,
295 float sun_elevation,
296 float altitude,
297 float air_density,
298 float aerosol_density,
299 float ozone_density)
300{
301 /* Clamp altitude to avoid numerical issues. */
302 altitude = clamp(altitude, 1.0f, 59999.0f);
303 /* Calculate texture pixels. */
304 const int half_width = width / 2;
305 const int half_height = height / 2;
306 const float3 cam_pos = make_float3(0, 0, EARTH_RADIUS + altitude);
307 const float3 sun_dir = geographical_to_direction(sun_elevation, 0.0f);
308 const float longitude_step = M_2PI_F / width;
309 const int rows_per_task = std::max(1024 / width, 1);
310
311 /* Compute Sky in the upper hemisphere. */
312 SKY_parallel_for(half_height, height, rows_per_task, [=](const size_t begin, const size_t end) {
313 for (int y = begin; y < end; y++) {
314 /* Sample more pixels toward the horizon. */
315 float latitude = M_PI_2_F * sqr(float(y) / half_height - 1.0f);
316 float *pixel_row = pixels + (y * width * stride);
317
318 for (int x = 0; x < half_width; x++) {
319 float longitude = longitude_step * x - M_PI_F;
320 float3 dir = geographical_to_direction(latitude, longitude);
321 float spectrum[NUM_WAVELENGTHS];
323 dir, sun_dir, cam_pos, air_density, aerosol_density, ozone_density, spectrum);
324 const float3 xyz = spec_to_xyz(spectrum);
325
326 /* Store pixels. */
327 int pos_x = x * stride;
328 pixel_row[pos_x] = xyz.x;
329 pixel_row[pos_x + 1] = xyz.y;
330 pixel_row[pos_x + 2] = xyz.z;
331 /* Mirror sky. */
332 int mirror_x = (width - x - 1) * stride;
333 pixel_row[mirror_x] = xyz.x;
334 pixel_row[mirror_x + 1] = xyz.y;
335 pixel_row[mirror_x + 2] = xyz.z;
336 }
337 }
338 });
339
340 /* Fill in the lower hemisphere by fading out the horizon. */
341 for (int y = 0; y < half_height; y++) {
342 /* Sample more pixels toward the horizon. */
343 float latitude = M_PI_2_F * sqr(float(y) / half_height - 1.0f);
344 float3 dir = geographical_to_direction(latitude, 0.0f);
345 float fade = 0.0f;
346 if (dir.z < 0.4f) {
347 fade = 1.0f - dir.z * 2.5f;
348 fade = sqr(fade) * fade;
349 }
350 float *pixel_row = pixels + (y * width * stride);
351 float *horizon_row = pixels + (half_height * width * stride);
352
353 for (int x = 0, offset = 0; x < width; x++, offset += stride) {
354 pixel_row[offset + 0] = horizon_row[offset + 0] * fade;
355 pixel_row[offset + 1] = horizon_row[offset + 1] * fade;
356 pixel_row[offset + 2] = horizon_row[offset + 2] * fade;
357 }
358 }
359}
360
361/*********** Sun ***********/
362static void sun_radiation(float3 cam_dir,
363 float altitude,
364 float air_density,
365 float aerosol_density,
366 float solid_angle,
367 float *r_spectrum)
368{
369 float3 cam_pos = make_float3(0, 0, EARTH_RADIUS + altitude);
370 float3 optical_depth = ray_optical_depth(cam_pos, cam_dir);
371
372 /* Compute final spectrum. */
373 for (int i = 0; i < NUM_WAVELENGTHS; i++) {
374 /* Combine spectra and the optical depth into transmittance. */
375 float transmittance = RAYLEIGH_COEFF[i] * optical_depth.x * air_density +
376 1.11f * MIE_COEFF * optical_depth.y * aerosol_density;
377 r_spectrum[i] = IRRADIANCE[i] * expf(-transmittance) / solid_angle;
378 }
379}
380
382 float angular_diameter,
383 float altitude,
384 float air_density,
385 float aerosol_density,
386 float r_pixel_bottom[3],
387 float r_pixel_top[3])
388{
389 /* Clamp altitude to avoid numerical issues. */
390 altitude = clamp(altitude, 1.0f, 59999.0f);
391 float half_angular = angular_diameter / 2.0f;
392 float solid_angle = M_2PI_F * (1.0f - cosf(half_angular));
393 float spectrum[NUM_WAVELENGTHS];
394 float bottom = sun_elevation - half_angular;
395 float top = sun_elevation + half_angular;
396 float elevation_bottom, elevation_top;
397 float3 pix_bottom, pix_top, sun_dir;
398
399 /* Compute 2 pixels for Sun disc: one is the lowest point of the disc, one is the highest.
400 * Return black pixels if Sun is below horizon. */
401 elevation_bottom = (bottom > 0.0f) ? bottom : 0.0f;
402 elevation_top = (top > 0.0f) ? top : 0.0f;
403 if (elevation_top > 0.0f) {
404 sun_dir = geographical_to_direction(elevation_bottom, 0.0f);
405 sun_radiation(sun_dir, altitude, air_density, aerosol_density, solid_angle, spectrum);
406 pix_bottom = spec_to_xyz(spectrum);
407 sun_dir = geographical_to_direction(elevation_top, 0.0f);
408 sun_radiation(sun_dir, altitude, air_density, aerosol_density, solid_angle, spectrum);
409 pix_top = spec_to_xyz(spectrum);
410 }
411 else {
412 pix_bottom = make_float3(0.0f, 0.0f, 0.0f);
413 pix_top = make_float3(0.0f, 0.0f, 0.0f);
414 }
415
416 /* Store pixels. */
417 r_pixel_bottom[0] = pix_bottom.x;
418 r_pixel_bottom[1] = pix_bottom.y;
419 r_pixel_bottom[2] = pix_bottom.z;
420 r_pixel_top[0] = pix_top.x;
421 r_pixel_top[1] = pix_top.y;
422 r_pixel_top[2] = pix_top.z;
423}
iter begin(iter)
dot(value.rgb, luminance_coefficients)") DEFINE_VALUE("REDUCE(lhs
#define M_PI_2_F
#define M_1_PI_F
#define expf(x)
#define powf(x, y)
ccl_device_forceinline float3 make_float3(const float x, const float y, const float z)
uint pos
uint top
constexpr T clamp(T, U, U) RET
float distance(VecOp< float, D >, VecOp< float, D >) RET
ccl_device_inline dual1 reduce_add(const dual< T > a)
Definition math_dual.h:49
ccl_device_inline float len_squared(const float2 a)
ccl_device_inline float2 fabs(const float2 a)
CCL_NAMESPACE_BEGIN ccl_device float fade(const float t)
Definition noise.h:18
#define sqr
#define sqrtf
#define M_2PI_F
#define sinf
#define M_PI_F
#define cosf
void SKY_parallel_for(const size_t begin, const size_t end, const size_t grainsize, const Function &function)
Definition sky_math.h:482
static const float SQR_G
static const float EARTH_RADIUS
static const float ATMOSPHERE_RADIUS
static float3 ray_optical_depth(float3 ray_origin, float3 ray_dir)
static float3 spec_to_xyz(const float *spectrum)
static float phase_rayleigh(float mu)
static const int MIN_WAVELENGTH
static void sun_radiation(float3 cam_dir, float altitude, float air_density, float aerosol_density, float solid_angle, float *r_spectrum)
static const float MIE_SCALE
static const float OZONE_COEFF[]
void SKY_single_scattering_precompute_sun(float sun_elevation, float angular_diameter, float altitude, float air_density, float aerosol_density, float r_pixel_bottom[3], float r_pixel_top[3])
static void single_scattering(float3 ray_dir, float3 sun_dir, float3 ray_origin, float air_density, float aerosol_density, float ozone_density, float *r_spectrum)
static float3 geographical_to_direction(float lat, float lon)
static float phase_mie(float mu)
void SKY_single_scattering_precompute_texture(float *pixels, int stride, int width, int height, float sun_elevation, float altitude, float air_density, float aerosol_density, float ozone_density)
static const float CMF_XYZ[][3]
static const float IRRADIANCE[]
static float density_mie(float height)
static const float QUADRATURE_NODES[]
static bool surface_intersection(float3 pos, float3 dir)
static const int NUM_WAVELENGTHS
static const float RAYLEIGH_COEFF[]
static const int STEPS
static float density_rayleigh(float height)
static const int QUADRATURE_STEPS
static const float MIE_G
static const float MIE_COEFF
static float3 atmosphere_intersection(float3 pos, float3 dir)
static const float RAYLEIGH_SCALE
static float density_ozone(float height)
static const float QUADRATURE_WEIGHTS[]
static const int MAX_WAVELENGTH
static const float STEP_LAMBDA
float z
Definition sky_math.h:136
float y
Definition sky_math.h:136
float x
Definition sky_math.h:136
i
Definition text_draw.cc:230
uint len