Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,28 @@
# SPDX-FileCopyrightText: 2002-2022 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
set(INC
include
)
set(INC_SYS
)
set(SRC
source/sky_single_scattering.cpp
source/sky_multiple_scattering.cpp
source/sky_hosek.cpp
source/sky_hosek_data.h
include/sky_hosek.h
include/sky_nishita.h
source/sky_math.h
)
set(LIB
PRIVATE bf::dependencies::optional::tbb
)
blender_add_lib(bf_intern_sky "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")

View File

@@ -0,0 +1,400 @@
/* SPDX-FileCopyrightText: 2012-2013 Lukas Hosek and Alexander Wilkie. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause */
/* ============================================================================
This file is part of a sample implementation of the analytical skylight and
solar radiance models presented in the SIGGRAPH 2012 paper
"An Analytic Model for Full Spectral Sky-Dome Radiance"
and the 2013 IEEE CG&A paper
"Adding a Solar Radiance Function to the Hosek Skylight Model"
both by
Lukas Hosek and Alexander Wilkie
Charles University in Prague, Czech Republic
Version: 1.4a, February 22nd, 2013
Version history:
1.4a February 22nd, 2013
Removed unnecessary and counter-intuitive solar radius parameters
from the interface of the colourspace sky dome initialization functions.
1.4 February 11th, 2013
Fixed a bug which caused the relative brightness of the solar disc
and the sky dome to be off by a factor of about 6. The sun was too
bright: this affected both normal and alien sun scenarios. The
coefficients of the solar radiance function were changed to fix this.
1.3 January 21st, 2013 (not released to the public)
Added support for solar discs that are not exactly the same size as
the terrestrial sun. Also added support for suns with a different
emission spectrum ("Alien World" functionality).
1.2a December 18th, 2012
Fixed a mistake and some inaccuracies in the solar radiance function
explanations found in ArHosekSkyModel.h. The actual source code is
unchanged compared to version 1.2.
1.2 December 17th, 2012
Native RGB data and a solar radiance function that matches the turbidity
conditions were added.
1.1 September 2012
The coefficients of the spectral model are now scaled so that the output
is given in physical units: W / (m^-2 * sr * nm). Also, the output of the
XYZ model is now no longer scaled to the range [0...1]. Instead, it is
the result of a simple conversion from spectral data via the CIE 2 degree
standard observer matching functions. Therefore, after multiplication
with 683 lm / W, the Y channel now corresponds to luminance in lm.
1.0 May 11th, 2012
Initial release.
Please visit http://cgg.mff.cuni.cz/projects/SkylightModelling/ to check if
an updated version of this code has been published!
============================================================================ */
/*
This code is taken from ART, a rendering research system written in a
mix of C99 / Objective C. Since ART is not a small system and is intended to
be inter-operable with other libraries, and since C does not have namespaces,
the structures and functions in ART all have to have somewhat wordy
canonical names that begin with Ar.../ar..., like those seen in this example.
Usage information:
==================
Model initialization
--------------------
A separate ArHosekSkyModelState has to be maintained for each spectral
band you want to use the model for. So in a renderer with 'num_channels'
bands, you would need something like
ArHosekSkyModelState * skymodel_state[num_channels];
You then have to allocate and initialize these states. In the following code
snippet, we assume that 'albedo' is defined as
double albedo[num_channels];
with a ground albedo value between [0,1] for each channel. The solar elevation
is given in radians.
for ( unsigned int i = 0; i < num_channels; i++ )
skymodel_state[i] =
arhosekskymodelstate_alloc_init(
turbidity,
albedo[i],
solarElevation
);
Note that starting with version 1.3, there is also a second initialization
function which generates skydome states for different solar emission spectra
and solar radii: 'arhosekskymodelstate_alienworld_alloc_init()'.
See the notes about the "Alien World" functionality provided further down for a
discussion of the usefulness and limits of that second initialization function.
Sky model states that have been initialized with either function behave in a
completely identical fashion during use and cleanup.
Using the model to generate skydome samples
-------------------------------------------
Generating a skydome radiance spectrum "skydome_result" for a given location
on the skydome determined via the angles theta and gamma works as follows:
double skydome_result[num_channels];
for ( unsigned int i = 0; i < num_channels; i++ )
skydome_result[i] =
arhosekskymodel_radiance(
skymodel_state[i],
theta,
gamma,
channel_center[i]
);
The variable "channel_center" is assumed to hold the channel center wavelengths
for each of the num_channels samples of the spectrum we are building.
Cleanup after use
-----------------
After rendering is complete, the content of the sky model states should be
disposed of via
for ( unsigned int i = 0; i < num_channels; i++ )
arhosekskymodelstate_free( skymodel_state[i] );
CIE XYZ Version of the Model
----------------------------
Usage of the CIE XYZ version of the model is exactly the same, except that
num_channels is of course always 3, and that ArHosekTristimSkyModelState and
arhosek_tristim_skymodel_radiance() have to be used instead of their spectral
counterparts.
RGB Version of the Model
------------------------
The RGB version uses sRGB primaries with a linear gamma ramp. The same set of
functions as with the XYZ data is used, except the model is initialized
by calling arhosek_rgb_skymodelstate_alloc_init.
Solar Radiance Function
-----------------------
For each position on the solar disc, this function returns the entire radiance
one sees - direct emission, as well as in-scattered light in the area of the
solar disc. The latter is important for low solar elevations - nice images of
the setting sun would not be possible without this. This is also the reason why
this function, just like the regular sky dome model evaluation function, needs
access to the sky dome data structures, as these provide information on
in-scattered radiance.
CAVEAT #1: in this release, this function is only provided in spectral form!
RGB/XYZ versions to follow at a later date.
CAVEAT #2: (fixed from release 1.3 onwards)
CAVEAT #3: limb darkening renders the brightness of the solar disc
inhomogeneous even for high solar elevations - only taking a single
sample at the centre of the sun will yield an incorrect power
estimate for the solar disc! Always take multiple random samples
across the entire solar disc to estimate its power!
CAVEAT #4: in this version, the limb darkening calculations still use a fairly
computationally expensive 5th order polynomial that was directly
taken from astronomical literature. For the purposes of Computer
Graphics, this is needlessly accurate, though, and will be replaced
by a cheaper approximation in a future release.
"Alien World" functionality
---------------------------
The Hosek sky model can be used to roughly (!) predict the appearance of
outdoor scenes on earth-like planets, i.e. planets of a similar size and
atmospheric make-up. Since the spectral version of our model predicts sky dome
luminance patterns and solar radiance independently for each waveband, and
since the intensity of each waveband is solely dependent on the input radiance
from the star that the world in question is orbiting, it is trivial to re-scale
the wavebands to match a different star radiance.
At least in theory, the spectral version of the model has always been capable
of this sort of thing, and the actual sky dome and solar radiance models were
actually not altered at all in this release. All we did was to add some support
functionality for doing this more easily with the existing data and functions,
and to add some explanations.
Just use 'arhosekskymodelstate_alienworld_alloc_init()' to initialize the sky
model states (you will have to provide values for star temperature and solar
intensity compared to the terrestrial sun), and do everything else as you
did before.
CAVEAT #1: we assume the emission of the star that illuminates the alien world
to be a perfect blackbody emission spectrum. This is never entirely
realistic - real star emission spectra are considerably more complex
than this, mainly due to absorption effects in the outer layers of
stars. However, blackbody spectra are a reasonable first assumption
in a usage scenario like this, where 100% accuracy is simply not
necessary: for rendering purposes, there are likely no visible
differences between a highly accurate solution based on a more
involved simulation, and this approximation.
CAVEAT #2: we always use limb darkening data from our own sun to provide this
"appearance feature", even for suns of strongly different
temperature. Which is presumably not very realistic, but (as with
the unaltered blackbody spectrum from caveat #1) probably not a bad
first guess, either. If you need more accuracy than we provide here,
please make inquiries with a friendly astro-physicst of your choice.
CAVEAT #3: you have to provide a value for the solar intensity of the star
which illuminates the alien world. For this, please bear in mind
that there is very likely a comparatively tight range of absolute
solar irradiance values for which an earth-like planet with an
atmosphere like the one we assume in our model can exist in the
first place!
Too much irradiance, and the atmosphere probably boils off into
space, too little, it freezes. Which means that stars of
considerably different emission colour than our sun will have to be
fairly different in size from it, to still provide a reasonable and
inhabitable amount of irradiance. Red stars will need to be much
larger than our sun, while white or blue stars will have to be
comparatively tiny. The initialization function handles this and
computes a plausible solar radius for a given emission spectrum. In
terms of absolute radiometric values, you should probably not stray
all too far from a solar intensity value of 1.0.
CAVEAT #4: although we now support different solar radii for the actual solar
disc, the sky dome luminance patterns are *not* parameterised by
this value - i.e. the patterns stay exactly the same for different
solar radii! Which is of course not correct. But in our experience,
solar discs up to several degrees in diameter (! - our own sun is
half a degree across) do not cause the luminance patterns on the sky
to change perceptibly. The reason we know this is that we initially
used unrealistically large suns in our brute force path tracer, in
order to improve convergence speeds (which in the beginning were
abysmal). Later, we managed to do the reference renderings much
faster even with realistically small suns, and found that there was
no real difference in skydome appearance anyway.
Conclusion: changing the solar radius should not be over-done, so
close orbits around red supergiants are a no-no. But for the
purposes of getting a fairly credible first impression of what an
alien world with a reasonably sized sun would look like, what we are
doing here is probably still o.k.
HINT #1: if you want to model the sky of an earth-like planet that orbits
a binary star, just super-impose two of these models with solar
intensity of ~0.5 each, and closely spaced solar positions. Light is
additive, after all. Tattooine, here we come... :-)
P.S. according to Star Wars canon, Tattooine orbits a binary
that is made up of a G and K class star, respectively.
So ~5500K and ~4200K should be good first guesses for their
temperature. Just in case you were wondering, after reading the
previous paragraph.
*/
/** \file
* \ingroup intern_sky_modal
*/
#pragma once
using SKY_ArHosekSkyModelConfiguration = double[9];
// Spectral version of the model
/* ----------------------------------------------------------------------------
ArHosekSkyModelState struct
---------------------------
This struct holds the pre-computation data for one particular albedo value.
Most fields are self-explanatory, but users should never directly
manipulate any of them anyway. The only consistent way to manipulate such
structs is via the functions 'arhosekskymodelstate_alloc_init' and
'arhosekskymodelstate_free'.
'emission_correction_factor_sky'
'emission_correction_factor_sun'
The original model coefficients were fitted against the emission of
our local sun. If a different solar emission is desired (i.e. if the
model is being used to predict skydome appearance for an earth-like
planet that orbits a different star), these correction factors, which
are determined during the alloc_init step, are applied to each waveband
separately (they default to 1.0 in normal usage). This is the simplest
way to retrofit this sort of capability to the existing model. The
different factors for sky and sun are needed since the solar disc may
be of a different size compared to the terrestrial sun.
---------------------------------------------------------------------------- */
struct SKY_ArHosekSkyModelState {
SKY_ArHosekSkyModelConfiguration configs[11];
double radiances[11];
double turbidity;
double solar_radius;
double emission_correction_factor_sky[11];
double emission_correction_factor_sun[11];
double albedo;
double elevation;
};
/* ----------------------------------------------------------------------------
arhosekskymodelstate_alloc_init() function
------------------------------------------
Initializes an #ArHosekSkyModelState struct for a terrestrial setting.
---------------------------------------------------------------------------- */
SKY_ArHosekSkyModelState *SKY_arhosekskymodelstate_alloc_init(const double solar_elevation,
const double atmospheric_turbidity,
const double ground_albedo);
/* ----------------------------------------------------------------------------
arhosekskymodelstate_alienworld_alloc_init() function
-----------------------------------------------------
Initializes an ArHosekSkyModelState struct for an "alien world" setting
with a sun of a surface temperature given in 'kelvin'. The parameter
'solar_intensity' controls the overall brightness of the sky, relative
to the solar irradiance on Earth. A value of 1.0 yields a sky dome that
is, on average over the wavelengths covered in the model (!), as bright
as the terrestrial sky in radiometric terms.
Which means that the solar radius has to be adjusted, since the
emissivity of a solar surface with a given temperature is more or less
fixed. So hotter suns have to be smaller to be equally bright as the
terrestrial sun, while cooler suns have to be larger. Note that there are
limits to the validity of the luminance patterns of the underlying model:
see the discussion above for more on this. In particular, an alien sun with
a surface temperature of only 2000 Kelvin has to be very large if it is
to be as bright as the terrestrial sun - so large that the luminance
patterns are no longer a really good fit in that case.
If you need information about the solar radius that the model computes
for a given temperature (say, for light source sampling purposes), you
have to query the 'solar_radius' variable of the sky model state returned
*after* running this function.
---------------------------------------------------------------------------- */
SKY_ArHosekSkyModelState *SKY_arhosekskymodelstate_alienworld_alloc_init(
const double solar_elevation,
const double solar_intensity,
const double solar_surface_temperature_kelvin,
const double atmospheric_turbidity,
const double ground_albedo);
void SKY_arhosekskymodelstate_free(SKY_ArHosekSkyModelState *state);
double SKY_arhosekskymodel_radiance(SKY_ArHosekSkyModelState *state,
double theta,
double gamma,
double wavelength);
// CIE XYZ and RGB versions
SKY_ArHosekSkyModelState *SKY_arhosek_xyz_skymodelstate_alloc_init(const double turbidity,
const double albedo,
const double elevation);
SKY_ArHosekSkyModelState *SKY_arhosek_rgb_skymodelstate_alloc_init(const double turbidity,
const double albedo,
const double elevation);
double SKY_arhosek_tristim_skymodel_radiance(SKY_ArHosekSkyModelState *state,
double theta,
double gamma,
int channel);
// Delivers the complete function: sky + sun, including limb darkening.
// Please read the above description before using this - there are several
// caveats!
double SKY_arhosekskymodel_solar_radiance(SKY_ArHosekSkyModelState *state,
double theta,
double gamma,
double wavelength);

View File

@@ -0,0 +1,48 @@
/* SPDX-FileCopyrightText: 2020-2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup intern_sky_modal
*/
#pragma once
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);
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]);
void SKY_multiple_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);
void SKY_multiple_scattering_precompute_sun(float sun_elevation,
float angular_diameter,
float altitude,
float air_density,
float aerosol_density,
float ozone_density,
float r_pixel_bottom[3],
float r_pixel_top[3]);
float SKY_earth_intersection_angle(float altitude);

View File

@@ -0,0 +1,328 @@
/* SPDX-FileCopyrightText: 2012-2013 Lukas Hosek and Alexander Wilkie. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause */
/* ============================================================================
This file is part of a sample implementation of the analytical skylight and
solar radiance models presented in the SIGGRAPH 2012 paper
"An Analytic Model for Full Spectral Sky-Dome Radiance"
and the 2013 IEEE CG&A paper
"Adding a Solar Radiance Function to the Hosek Skylight Model"
both by
Lukas Hosek and Alexander Wilkie
Charles University in Prague, Czech Republic
Version: 1.4a, February 22nd, 2013
Version history:
1.4a February 22nd, 2013
Removed unnecessary and counter-intuitive solar radius parameters
from the interface of the color-space sky dome initialization functions.
1.4 February 11th, 2013
Fixed a bug which caused the relative brightness of the solar disc
and the sky dome to be off by a factor of about 6. The sun was too
bright: this affected both normal and alien sun scenarios. The
coefficients of the solar radiance function were changed to fix this.
1.3 January 21st, 2013 (not released to the public)
Added support for solar discs that are not exactly the same size as
the terrestrial sun. Also added support for suns with a different
emission spectrum ("Alien World" functionality).
1.2a December 18th, 2012
Fixed a mistake and some inaccuracies in the solar radiance function
explanations found in ArHosekSkyModel.h. The actual source code is
unchanged compared to version 1.2.
1.2 December 17th, 2012
Native RGB data and a solar radiance function that matches the turbidity
conditions were added.
1.1 September 2012
The coefficients of the spectral model are now scaled so that the output
is given in physical units: W / (m^-2 * sr * nm). Also, the output of the
XYZ model is now no longer scaled to the range [0...1]. Instead, it is
the result of a simple conversion from spectral data via the CIE 2 degree
standard observer matching functions. Therefore, after multiplication
with 683 lm / W, the Y channel now corresponds to luminance in lm.
1.0 May 11th, 2012
Initial release.
Please visit http://cgg.mff.cuni.cz/projects/SkylightModelling/ to check if
an updated version of this code has been published!
============================================================================ */
/*
All instructions on how to use this code are in the accompanying header file.
*/
/** \file
* \ingroup intern_sky_modal
*/
#include "sky_hosek.h"
#include "sky_hosek_data.h"
#include <cassert>
#include <cmath>
#include <cstdlib>
// Some macro definitions that occur elsewhere in ART, and that have to be
// replicated to make this a stand-alone module.
#ifndef MATH_PI
# define MATH_PI 3.141592653589793
#endif
#ifndef MATH_DEG_TO_RAD
# define MATH_DEG_TO_RAD (MATH_PI / 180.0)
#endif
#ifndef DEGREES
# define DEGREES *MATH_DEG_TO_RAD
#endif
#ifndef TERRESTRIAL_SOLAR_RADIUS
# define TERRESTRIAL_SOLAR_RADIUS ((0.51 DEGREES) / 2.0)
#endif
#ifndef ALLOC
# define ALLOC(_struct) ((_struct *)malloc(sizeof(_struct)))
#endif
/* Not defined on all platforms (macOS & WIN32). */
using uint = unsigned int;
// internal definitions
using ArHosekSkyModel_Dataset = const double *;
using ArHosekSkyModel_Radiance_Dataset = const double *;
// internal functions
static void ArHosekSkyModel_CookConfiguration(ArHosekSkyModel_Dataset dataset,
SKY_ArHosekSkyModelConfiguration config,
double turbidity,
double albedo,
double solar_elevation)
{
const double *elev_matrix;
int int_turbidity = int(turbidity);
double turbidity_rem = turbidity - double(int_turbidity);
solar_elevation = pow(solar_elevation / (MATH_PI / 2.0), (1.0 / 3.0));
// alb 0 low turb
elev_matrix = dataset + (9 * 6 * (int_turbidity - 1));
for (uint i = 0; i < 9; ++i) {
//(1-t).^3* A1 + 3*(1-t).^2.*t * A2 + 3*(1-t) .* t .^ 2 * A3 + t.^3 * A4;
config[i] =
(1.0 - albedo) * (1.0 - turbidity_rem) *
(pow(1.0 - solar_elevation, 5.0) * elev_matrix[i] +
5.0 * pow(1.0 - solar_elevation, 4.0) * solar_elevation * elev_matrix[i + 9] +
10.0 * pow(1.0 - solar_elevation, 3.0) * pow(solar_elevation, 2.0) * elev_matrix[i + 18] +
10.0 * pow(1.0 - solar_elevation, 2.0) * pow(solar_elevation, 3.0) * elev_matrix[i + 27] +
5.0 * (1.0 - solar_elevation) * pow(solar_elevation, 4.0) * elev_matrix[i + 36] +
pow(solar_elevation, 5.0) * elev_matrix[i + 45]);
}
// alb 1 low turb
elev_matrix = dataset + (9 * 6 * 10 + 9 * 6 * (int_turbidity - 1));
for (uint i = 0; i < 9; ++i) {
//(1-t).^3* A1 + 3*(1-t).^2.*t * A2 + 3*(1-t) .* t .^ 2 * A3 + t.^3 * A4;
config[i] +=
(albedo) * (1.0 - turbidity_rem) *
(pow(1.0 - solar_elevation, 5.0) * elev_matrix[i] +
5.0 * pow(1.0 - solar_elevation, 4.0) * solar_elevation * elev_matrix[i + 9] +
10.0 * pow(1.0 - solar_elevation, 3.0) * pow(solar_elevation, 2.0) * elev_matrix[i + 18] +
10.0 * pow(1.0 - solar_elevation, 2.0) * pow(solar_elevation, 3.0) * elev_matrix[i + 27] +
5.0 * (1.0 - solar_elevation) * pow(solar_elevation, 4.0) * elev_matrix[i + 36] +
pow(solar_elevation, 5.0) * elev_matrix[i + 45]);
}
if (int_turbidity == 10) {
return;
}
// alb 0 high turb
elev_matrix = dataset + (9 * 6 * (int_turbidity));
for (uint i = 0; i < 9; ++i) {
//(1-t).^3* A1 + 3*(1-t).^2.*t * A2 + 3*(1-t) .* t .^ 2 * A3 + t.^3 * A4;
config[i] +=
(1.0 - albedo) * (turbidity_rem) *
(pow(1.0 - solar_elevation, 5.0) * elev_matrix[i] +
5.0 * pow(1.0 - solar_elevation, 4.0) * solar_elevation * elev_matrix[i + 9] +
10.0 * pow(1.0 - solar_elevation, 3.0) * pow(solar_elevation, 2.0) * elev_matrix[i + 18] +
10.0 * pow(1.0 - solar_elevation, 2.0) * pow(solar_elevation, 3.0) * elev_matrix[i + 27] +
5.0 * (1.0 - solar_elevation) * pow(solar_elevation, 4.0) * elev_matrix[i + 36] +
pow(solar_elevation, 5.0) * elev_matrix[i + 45]);
}
// alb 1 high turb
elev_matrix = dataset + (9 * 6 * 10 + 9 * 6 * (int_turbidity));
for (uint i = 0; i < 9; ++i) {
//(1-t).^3* A1 + 3*(1-t).^2.*t * A2 + 3*(1-t) .* t .^ 2 * A3 + t.^3 * A4;
config[i] +=
(albedo) * (turbidity_rem) *
(pow(1.0 - solar_elevation, 5.0) * elev_matrix[i] +
5.0 * pow(1.0 - solar_elevation, 4.0) * solar_elevation * elev_matrix[i + 9] +
10.0 * pow(1.0 - solar_elevation, 3.0) * pow(solar_elevation, 2.0) * elev_matrix[i + 18] +
10.0 * pow(1.0 - solar_elevation, 2.0) * pow(solar_elevation, 3.0) * elev_matrix[i + 27] +
5.0 * (1.0 - solar_elevation) * pow(solar_elevation, 4.0) * elev_matrix[i + 36] +
pow(solar_elevation, 5.0) * elev_matrix[i + 45]);
}
}
static double ArHosekSkyModel_CookRadianceConfiguration(ArHosekSkyModel_Radiance_Dataset dataset,
double turbidity,
double albedo,
double solar_elevation)
{
const double *elev_matrix;
int int_turbidity = int(turbidity);
double turbidity_rem = turbidity - double(int_turbidity);
double res;
solar_elevation = pow(solar_elevation / (MATH_PI / 2.0), (1.0 / 3.0));
// alb 0 low turb
elev_matrix = dataset + (6 * (int_turbidity - 1));
//(1-t).^3* A1 + 3*(1-t).^2.*t * A2 + 3*(1-t) .* t .^ 2 * A3 + t.^3 * A4;
res = (1.0 - albedo) * (1.0 - turbidity_rem) *
(pow(1.0 - solar_elevation, 5.0) * elev_matrix[0] +
5.0 * pow(1.0 - solar_elevation, 4.0) * solar_elevation * elev_matrix[1] +
10.0 * pow(1.0 - solar_elevation, 3.0) * pow(solar_elevation, 2.0) * elev_matrix[2] +
10.0 * pow(1.0 - solar_elevation, 2.0) * pow(solar_elevation, 3.0) * elev_matrix[3] +
5.0 * (1.0 - solar_elevation) * pow(solar_elevation, 4.0) * elev_matrix[4] +
pow(solar_elevation, 5.0) * elev_matrix[5]);
// alb 1 low turb
elev_matrix = dataset + (6 * 10 + 6 * (int_turbidity - 1));
//(1-t).^3* A1 + 3*(1-t).^2.*t * A2 + 3*(1-t) .* t .^ 2 * A3 + t.^3 * A4;
res += (albedo) * (1.0 - turbidity_rem) *
(pow(1.0 - solar_elevation, 5.0) * elev_matrix[0] +
5.0 * pow(1.0 - solar_elevation, 4.0) * solar_elevation * elev_matrix[1] +
10.0 * pow(1.0 - solar_elevation, 3.0) * pow(solar_elevation, 2.0) * elev_matrix[2] +
10.0 * pow(1.0 - solar_elevation, 2.0) * pow(solar_elevation, 3.0) * elev_matrix[3] +
5.0 * (1.0 - solar_elevation) * pow(solar_elevation, 4.0) * elev_matrix[4] +
pow(solar_elevation, 5.0) * elev_matrix[5]);
if (int_turbidity == 10) {
return res;
}
// alb 0 high turb
elev_matrix = dataset + (6 * (int_turbidity));
//(1-t).^3* A1 + 3*(1-t).^2.*t * A2 + 3*(1-t) .* t .^ 2 * A3 + t.^3 * A4;
res += (1.0 - albedo) * (turbidity_rem) *
(pow(1.0 - solar_elevation, 5.0) * elev_matrix[0] +
5.0 * pow(1.0 - solar_elevation, 4.0) * solar_elevation * elev_matrix[1] +
10.0 * pow(1.0 - solar_elevation, 3.0) * pow(solar_elevation, 2.0) * elev_matrix[2] +
10.0 * pow(1.0 - solar_elevation, 2.0) * pow(solar_elevation, 3.0) * elev_matrix[3] +
5.0 * (1.0 - solar_elevation) * pow(solar_elevation, 4.0) * elev_matrix[4] +
pow(solar_elevation, 5.0) * elev_matrix[5]);
// alb 1 high turb
elev_matrix = dataset + (6 * 10 + 6 * (int_turbidity));
//(1-t).^3* A1 + 3*(1-t).^2.*t * A2 + 3*(1-t) .* t .^ 2 * A3 + t.^3 * A4;
res += (albedo) * (turbidity_rem) *
(pow(1.0 - solar_elevation, 5.0) * elev_matrix[0] +
5.0 * pow(1.0 - solar_elevation, 4.0) * solar_elevation * elev_matrix[1] +
10.0 * pow(1.0 - solar_elevation, 3.0) * pow(solar_elevation, 2.0) * elev_matrix[2] +
10.0 * pow(1.0 - solar_elevation, 2.0) * pow(solar_elevation, 3.0) * elev_matrix[3] +
5.0 * (1.0 - solar_elevation) * pow(solar_elevation, 4.0) * elev_matrix[4] +
pow(solar_elevation, 5.0) * elev_matrix[5]);
return res;
}
static double ArHosekSkyModel_GetRadianceInternal(
const SKY_ArHosekSkyModelConfiguration configuration, const double theta, const double gamma)
{
const double expM = exp(configuration[4] * gamma);
const double rayM = cos(gamma) * cos(gamma);
const double mieM =
(1.0 + cos(gamma) * cos(gamma)) /
pow((1.0 + configuration[8] * configuration[8] - 2.0 * configuration[8] * cos(gamma)), 1.5);
const double zenith = sqrt(cos(theta));
return (1.0 + configuration[0] * exp(configuration[1] / (cos(theta) + 0.01))) *
(configuration[2] + configuration[3] * expM + configuration[5] * rayM +
configuration[6] * mieM + configuration[7] * zenith);
}
void SKY_arhosekskymodelstate_free(SKY_ArHosekSkyModelState *state)
{
free(state);
}
double SKY_arhosekskymodel_radiance(SKY_ArHosekSkyModelState *state,
double theta,
double gamma,
double wavelength)
{
int low_wl = int((wavelength - 320.0) / 40.0);
if (low_wl < 0 || low_wl >= 11) {
return 0.0;
}
double interp = fmod((wavelength - 320.0) / 40.0, 1.0);
double val_low = ArHosekSkyModel_GetRadianceInternal(state->configs[low_wl], theta, gamma) *
state->radiances[low_wl] * state->emission_correction_factor_sky[low_wl];
if (interp < 1e-6) {
return val_low;
}
double result = (1.0 - interp) * val_low;
if (low_wl + 1 < 11) {
result += interp *
ArHosekSkyModel_GetRadianceInternal(state->configs[low_wl + 1], theta, gamma) *
state->radiances[low_wl + 1] * state->emission_correction_factor_sky[low_wl + 1];
}
return result;
}
// xyz and rgb versions
SKY_ArHosekSkyModelState *SKY_arhosek_xyz_skymodelstate_alloc_init(const double turbidity,
const double albedo,
const double elevation)
{
SKY_ArHosekSkyModelState *state = ALLOC(SKY_ArHosekSkyModelState);
state->solar_radius = TERRESTRIAL_SOLAR_RADIUS;
state->turbidity = turbidity;
state->albedo = albedo;
state->elevation = elevation;
for (uint channel = 0; channel < 3; ++channel) {
ArHosekSkyModel_CookConfiguration(
datasetsXYZ[channel], state->configs[channel], turbidity, albedo, elevation);
state->radiances[channel] = ArHosekSkyModel_CookRadianceConfiguration(
datasetsXYZRad[channel], turbidity, albedo, elevation);
}
return state;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,499 @@
/* SPDX-FileCopyrightText: 2020-2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup intern_sky_modal
*/
#ifndef __SKY_MATH_H__
#define __SKY_MATH_H__
#ifdef WITH_TBB
# include <tbb/parallel_for.h>
#else
# include <algorithm>
#endif
/* Minimal math implementation for sky model. */
#include <cmath>
#ifndef M_PI_F
# define M_PI_F (3.1415926535897932f) /* pi */
#endif
#ifndef M_PI_2_F
# define M_PI_2_F (1.5707963267948966f) /* pi/2 */
#endif
#ifndef M_2PI_F
# define M_2PI_F (6.2831853071795864f) /* 2*pi */
#endif
#ifndef M_1_PI_F
# define M_1_PI_F (0.3183098861837067f) /* 1/pi */
#endif
#ifndef M_4PI_F
# define M_4PI_F (12.566370614359172f) /* 4*pi */
#endif
#ifndef M_1_4PI_F
# define M_1_4PI_F (0.0795774715459476f) /* 1/(4*pi) */
#endif
/* float2 */
struct float2 {
float x, y;
float2() = default;
float2(const float *ptr) : x{ptr[0]}, y{ptr[1]} {}
float2(const float (*ptr)[2]) : float2((const float *)ptr) {}
explicit float2(float value) : x(value), y(value) {}
explicit float2(int value) : x(value), y(value) {}
float2(float x, float y) : x{x}, y{y} {}
operator const float *() const
{
return &x;
}
operator float *()
{
return &x;
}
friend float2 operator*(const float2 &a, float b)
{
return {a.x * b, a.y * b};
}
friend float2 operator*(float b, const float2 &a)
{
return {a.x * b, a.y * b};
}
friend float2 operator/(const float2 &a, float b)
{
return {a.x / b, a.y / b};
}
friend float2 operator/(float b, const float2 &a)
{
return {a.x / b, a.y / b};
}
friend float2 operator/(const float2 &a, const float2 &b)
{
return {a.x / b.x, a.y / b.y};
}
friend float2 operator-(const float2 &a, const float2 &b)
{
return {a.x - b.x, a.y - b.y};
}
friend float2 operator-(const float2 &a)
{
return {-a.x, -a.y};
}
float length_squared() const
{
return x * x + y * y;
}
float length() const
{
return sqrt(length_squared());
}
static float distance(const float2 &a, const float2 &b)
{
return (a - b).length();
}
friend float2 operator+(const float2 &a, const float2 &b)
{
return {a.x + b.x, a.y + b.y};
}
void operator+=(const float2 &b)
{
this->x += b.x;
this->y += b.y;
}
friend float2 operator*(const float2 &a, const float2 &b)
{
return {a.x * b.x, a.y * b.y};
}
};
/* float3 */
struct float3 {
float x, y, z;
float3() = default;
float3(const float *ptr) : x{ptr[0]}, y{ptr[1]}, z{ptr[2]} {}
float3(const float (*ptr)[3]) : float3((const float *)ptr) {}
explicit float3(float value) : x(value), y(value), z(value) {}
explicit float3(int value) : x(value), y(value), z(value) {}
float3(float x, float y, float z) : x{x}, y{y}, z{z} {}
operator const float *() const
{
return &x;
}
operator float *()
{
return &x;
}
friend float3 operator*(const float3 &a, float b)
{
return {a.x * b, a.y * b, a.z * b};
}
friend float3 operator*(float b, const float3 &a)
{
return {a.x * b, a.y * b, a.z * b};
}
friend float3 operator/(const float3 &a, float b)
{
return {a.x / b, a.y / b, a.z / b};
}
friend float3 operator/(float b, const float3 &a)
{
return {a.x / b, a.y / b, a.z / b};
}
friend float3 operator-(const float3 &a, const float3 &b)
{
return {a.x - b.x, a.y - b.y, a.z - b.z};
}
friend float3 operator-(const float3 &a)
{
return {-a.x, -a.y, -a.z};
}
float length_squared() const
{
return x * x + y * y + z * z;
}
float length() const
{
return sqrt(length_squared());
}
static float distance(const float3 &a, const float3 &b)
{
return (a - b).length();
}
friend float3 operator+(const float3 &a, const float3 &b)
{
return {a.x + b.x, a.y + b.y, a.z + b.z};
}
void operator+=(const float3 &b)
{
this->x += b.x;
this->y += b.y;
this->z += b.z;
}
friend float3 operator*(const float3 &a, const float3 &b)
{
return {a.x * b.x, a.y * b.y, a.z * b.z};
}
};
/* float4 */
struct float4 {
float x, y, z, w;
float4() = default;
float4(const float *ptr) : x{ptr[0]}, y{ptr[1]}, z{ptr[2]}, w{ptr[3]} {}
float4(const float (*ptr)[4]) : float4((const float *)ptr) {}
explicit float4(float value) : x(value), y(value), z(value), w(value) {}
explicit float4(int value) : x(value), y(value), z(value), w(value) {}
float4(float x, float y, float z, float w) : x{x}, y{y}, z{z}, w{w} {}
operator const float *() const
{
return &x;
}
operator float *()
{
return &x;
}
friend float4 operator*(const float4 &a, float b)
{
return {a.x * b, a.y * b, a.z * b, a.w * b};
}
friend float4 operator*(float b, const float4 &a)
{
return {a.x * b, a.y * b, a.z * b, a.w * b};
}
friend float4 operator/(const float4 &a, float b)
{
return {a.x / b, a.y / b, a.z / b, a.w / b};
}
friend float4 operator/(float b, const float4 &a)
{
return {a.x / b, a.y / b, a.z / b, a.w / b};
}
friend float4 operator*(const float4 &a, const float4 &b)
{
return {a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w};
}
friend float4 operator/(const float4 &a, const float4 &b)
{
return {a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w};
}
friend float4 operator-(const float4 &a, const float4 &b)
{
return {a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w};
}
friend float4 operator-(const float4 &a)
{
return {-a.x, -a.y, -a.z, -a.w};
}
float length_squared() const
{
return x * x + y * y + z * z + w * w;
}
float length() const
{
return sqrt(length_squared());
}
static float distance(const float4 &a, const float4 &b)
{
return (a - b).length();
}
friend float4 operator+(const float4 &a, const float4 &b)
{
return {a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w};
}
void operator+=(const float4 &b)
{
this->x += b.x;
this->y += b.y;
this->z += b.z;
this->w += b.w;
}
void operator*=(const float4 &b)
{
this->x *= b.x;
this->y *= b.y;
this->z *= b.z;
this->w *= b.w;
}
};
inline float sqr(float a)
{
return a * a;
}
inline float safe_sqrtf(const float f)
{
return sqrt(fmax(f, 0.0f));
}
inline float2 make_float2(float x, float y)
{
return float2(x, y);
}
inline float dot(const float2 &a, const float2 &b)
{
return a.x * b.x + a.y * b.y;
}
inline float distance(const float2 &a, const float2 &b)
{
return float2::distance(a, b);
}
inline float len_squared(float2 f)
{
return f.length_squared();
}
inline float len(float2 f)
{
return f.length();
}
inline float reduce_add(float2 f)
{
return f.x + f.y;
}
inline float3 make_float3(float x, float y, float z)
{
return float3(x, y, z);
}
inline float dot(const float3 &a, const float3 &b)
{
return a.x * b.x + a.y * b.y + a.z * b.z;
}
inline float distance(const float3 &a, const float3 &b)
{
return float3::distance(a, b);
}
inline float len_squared(float3 f)
{
return f.length_squared();
}
inline float len(float3 f)
{
return f.length();
}
inline float reduce_add(float3 f)
{
return f.x + f.y + f.z;
}
inline float4 make_float4(float x, float y, float z, float w)
{
return float4(x, y, z, w);
}
inline float dot(const float4 a, const float4 b)
{
return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
}
inline float distance(const float4 &a, const float4 &b)
{
return float4::distance(a, b);
}
inline float len_squared(float4 f)
{
return f.length_squared();
}
inline float len(float4 f)
{
return f.length();
}
inline float reduce_add(float4 f)
{
return f.x + f.y + f.z + f.w;
}
inline float4 exp(float4 a)
{
return make_float4(expf(a.x), expf(a.y), expf(a.z), expf(a.w));
}
inline float4 max(float4 a, float b)
{
return make_float4(fmax(a.x, b), fmax(a.y, b), fmax(a.z, b), fmax(a.w, b));
}
inline float clamp(float x, float min, float max)
{
if (x < min) {
return min;
}
if (x > max) {
return max;
}
return x;
}
inline float saturate(const float a)
{
return clamp(a, 0.0f, 1.0f);
}
template<typename T> inline T mix(T x, T y, float a)
{
return x + a * (y - x);
}
inline float3 sun_direction(float sun_cos_theta)
{
return make_float3(-sqrtf(1.0f - sun_cos_theta * sun_cos_theta), 0.0f, sun_cos_theta);
}
inline float ray_sphere_intersection(float3 pos, float3 dir, float radius)
{
float b = dot(pos, dir);
float c = dot(pos, pos) - radius * radius;
if (c > 0.0f && b > 0.0f) {
return -1.0f;
}
float d = b * b - c;
if (d < 0) {
return -1.0f;
}
if (d >= b * b) {
return -b + sqrtf(d);
}
return -b - sqrtf(d);
}
/* Minimal parallel for implementation. */
template<typename Function>
inline void SKY_parallel_for(const size_t begin,
const size_t end,
const size_t grainsize,
const Function &function)
{
#ifdef WITH_TBB
tbb::parallel_for(
tbb::blocked_range<size_t>(begin, end, grainsize),
[function](const tbb::blocked_range<size_t> &r) { function(r.begin(), r.end()); });
#else
for (size_t i = begin; i < end; i += grainsize) {
function(i, std::min(i + grainsize, end));
}
(void)grainsize;
#endif
}
#endif /* __SKY_MATH_H__ */

View File

@@ -0,0 +1,401 @@
/* SPDX-FileCopyrightText: 2022 Fernando García Liñán
* SPDX-FileCopyrightText: 2011-2025 Blender Authors
*
* SPDX-License-Identifier: MIT */
/** \file
* \ingroup intern_sky_modal
*/
/*
* This code is a converted version of the ShaderToy written by Fernando García Liñán.
*
* This shader is the final result of my Master's Thesis.
* The main contributions are:
*
* 1. A spectral rendering technique that only requires 4 wavelength samples to
* get accurate results.
* 2. A multiple scattering approximation.
*
* Both of these approximations rely on an analytical fit, so they only work for
* Earth's atmosphere. We make up for it by using a very flexible atmosphere
* model that is able to represent a wide variety of atmospheric conditions.
*
* A brief description of this spectral rendering technique can be found in the
* following article:
* https://fgarlin.com/posts/2024-12-06-spectral_sky/
*
* The path tracer that has been used as a ground truth can be found at:
* https://github.com/fgarlin/skytracer
*/
#include <algorithm>
#include "sky_math.h"
#include "sky_nishita.h"
using std::min;
/* Earth's atmosphere parameters. */
/* Ground reflectance. */
static const float4 GROUND_ALBEDO = make_float4(0.3f, 0.3f, 0.3f, 0.3f);
static const float PHASE_ISOTROPIC = M_1_4PI_F;
static const float RAYLEIGH_PHASE_SCALE = (3.0f / 16.0f) * M_1_PI_F;
/* Aerosols anisotropy. */
static const float G = 0.8f;
static const float SQR_G = G * G;
/* Earth radius (km). */
static const float EARTH_RADIUS = 6371.0f;
/* Atmosphere thickness (km). */
static const float ATMOSPHERE_THICKNESS = 100.0f;
static const float ATMOSPHERE_RADIUS = EARTH_RADIUS + ATMOSPHERE_THICKNESS;
/* Ray marching steps. Higher steps means increased accuracy but worse performance. */
static const int TRANSMITTANCE_STEPS = 64;
static const int IN_SCATTERING_STEPS = 64;
/* LUTs. */
static const int TRANSMITTANCE_RES_X = 256;
static const int TRANSMITTANCE_RES_Y = 64;
/* Spectral data sampled at 630, 560, 490, 430 nm for urban area. */
static const float4 SUN_SPECTRAL_IRRADIANCE = make_float4(1.679f, 1.828f, 1.986f, 1.307f);
static const float4 MOLECULAR_SCATTERING_COEFFICIENT_BASE = make_float4(
6.605e-3f, 1.067e-2f, 1.842e-2f, 3.156e-2f);
static const float4 OZONE_ABSORPTION_CROSS_SECTION = make_float4(
3.472e-25f, 3.914e-25f, 1.349e-25f, 11.03e-27f);
/* Average ozone dobson of monthly mean values. */
static const float OZONE_MEAN_DOBSON = 334.5f;
static const float4 AEROSOL_ABSORPTION_CROSS_SECTION = make_float4(
2.8722e-24f, 4.6168e-24f, 7.9706e-24f, 1.3578e-23f);
static const float4 AEROSOL_SCATTERING_CROSS_SECTION = make_float4(
1.5908e-22f, 1.7711e-22f, 2.0942e-22f, 2.4033e-22f);
static const float AEROSOL_BASE_DENSITY = 1.3681e20f;
static const float AEROSOL_BACKGROUND_DENSITY = 2e6f;
static const float AEROSOL_HEIGHT_SCALE = 0.73f;
/* Spectral to XYZ space conversion matrix. */
static const float3 SPECTRAL_XYZ[4] = {
make_float3(53.386917738564668023f, 22.981337506691024754f, 0.0f),
make_float3(43.904844466369358263f, 71.347795700053393866f, 0.102506867965741307f),
make_float3(1.6137278251608962005f, 18.422960591455485011f, 31.742921188390805758f),
make_float3(20.762668673810577145f, 2.3614213523314368527f, 110.48009643252140334f),
};
inline float molecular_phase_function(const float cos_theta)
{
return RAYLEIGH_PHASE_SCALE * (1.0f + sqr(cos_theta));
}
inline float aerosol_phase_function(const float cos_theta)
{
float den = 1.0f + SQR_G + 2.0f * G * cos_theta;
return M_1_4PI_F * (1.0f - SQR_G) / (den * sqrtf(den));
}
inline float4 get_molecular_scattering_coefficient(const float h)
{
return MOLECULAR_SCATTERING_COEFFICIENT_BASE * expf(-0.07771971f * powf(h, 1.16364243f));
}
inline float4 get_molecular_absorption_coefficient(const float h)
{
const float log_h = logf(fmaxf(h, 1e-4f));
float density = 3.78547397e20f * expf(-sqr(log_h - 3.22261f) * 5.55555555f - log_h);
return OZONE_ABSORPTION_CROSS_SECTION * OZONE_MEAN_DOBSON * density;
}
inline float get_aerosol_density(const float h)
{
float division = AEROSOL_BACKGROUND_DENSITY / AEROSOL_BASE_DENSITY;
return AEROSOL_BASE_DENSITY * (expf(-h / AEROSOL_HEIGHT_SCALE) + division);
}
inline float3 spectral_to_xyz(const float4 L)
{
float3 xyz = make_float3(0.0f, 0.0f, 0.0f);
for (int i = 0; i < 4; i++) {
xyz += SPECTRAL_XYZ[i] * L[i];
}
return xyz;
}
/* Precomputed data. */
class SkyMultipleScattering {
public:
SkyMultipleScattering(const float air_density,
const float aerosol_density,
const float ozone_density)
: air_density(air_density), aerosol_density(aerosol_density), ozone_density(ozone_density)
{
}
/* Compute atmosphere's transmittance from the given altitude to the sun. */
float4 get_transmittance(const float cos_theta, const float normalized_altitude) const
{
const float3 sun_dir = sun_direction(cos_theta);
const float distance_to_earth_center = mix(
EARTH_RADIUS, ATMOSPHERE_RADIUS, normalized_altitude);
const float3 ray_origin = make_float3(0.0f, 0.0f, distance_to_earth_center);
const float t_d = ray_sphere_intersection(ray_origin, sun_dir, ATMOSPHERE_RADIUS);
const float t_step = t_d / TRANSMITTANCE_STEPS;
float4 result = make_float4(0.0f, 0.0f, 0.0f, 0.0f);
for (int step = 0; step < TRANSMITTANCE_STEPS; step++) {
const float t = (step + 0.5f) * t_step;
const float3 x_t = ray_origin + sun_dir * t;
const float altitude = fmaxf(x_t.length() - EARTH_RADIUS, 0.0f);
float4 aerosol_absorption, aerosol_scattering, molecular_absorption, molecular_scattering;
get_atmosphere_collision_coefficients(altitude,
aerosol_absorption,
aerosol_scattering,
molecular_absorption,
molecular_scattering);
const float4 extinction = aerosol_absorption + aerosol_scattering + molecular_absorption +
molecular_scattering;
result += extinction * t_step;
}
return exp(-result);
}
/* Compute in-scattered radiance for the given ray. */
float4 get_inscattering(const float3 sun_dir,
const float3 ray_origin,
const float3 ray_dir,
const float t_d) const
{
const float cos_theta = dot(-ray_dir, sun_dir);
const float molecular_phase = molecular_phase_function(cos_theta);
const float aerosol_phase = aerosol_phase_function(cos_theta);
const float dt = t_d / IN_SCATTERING_STEPS;
float4 L_inscattering = make_float4(0.0f, 0.0f, 0.0f, 0.0f);
float4 transmittance = make_float4(1.0f, 1.0f, 1.0f, 1.0f);
for (int i = 0; i < IN_SCATTERING_STEPS; i++) {
const float t = (i + 0.5f) * dt;
const float3 x_t = ray_origin + ray_dir * t;
const float distance_to_earth_center = x_t.length();
const float3 zenith_dir = x_t / distance_to_earth_center;
const float altitude = fmaxf(distance_to_earth_center - EARTH_RADIUS, 0.0f);
const float normalized_altitude = altitude / ATMOSPHERE_THICKNESS;
const float sample_cos_theta = dot(zenith_dir, sun_dir);
float4 aerosol_absorption, aerosol_scattering, molecular_absorption, molecular_scattering;
get_atmosphere_collision_coefficients(altitude,
aerosol_absorption,
aerosol_scattering,
molecular_absorption,
molecular_scattering);
const float4 extinction = aerosol_absorption + aerosol_scattering + molecular_absorption +
molecular_scattering;
const float4 transmittance_to_sun = lookup_transmittance(sample_cos_theta,
normalized_altitude);
const float4 ms = lookup_multiscattering(
sample_cos_theta, normalized_altitude, distance_to_earth_center);
const float4 S = SUN_SPECTRAL_IRRADIANCE *
(molecular_scattering * (molecular_phase * transmittance_to_sun + ms) +
aerosol_scattering * (aerosol_phase * transmittance_to_sun + ms));
const float4 step_transmittance = exp(-dt * extinction);
/* Energy-conserving analytical integration "Physically Based Sky, Atmosphere and Cloud
* Rendering in Frostbite" by Sébastien Hillaire. */
const float4 cut_ext = max(extinction, 1e-7f);
const float4 S_int = (S - S * step_transmittance) / cut_ext;
L_inscattering = L_inscattering + transmittance * S_int;
transmittance *= step_transmittance;
}
return L_inscattering;
}
/* Precompute the transmittance LUT. Must be called before get_inscattering(). */
void precompute_lut()
{
SKY_parallel_for(0, TRANSMITTANCE_RES_Y, 4, [&](const size_t begin, const size_t end) {
for (int y = begin; y < end; y++) {
for (int x = 0; x < TRANSMITTANCE_RES_X; x++) {
const float2 uv = make_float2(x / float(TRANSMITTANCE_RES_X - 1),
y / float(TRANSMITTANCE_RES_Y - 1));
transmittance_lut[y][x] = get_transmittance(uv.x * 2.0f - 1.0f, uv.y);
}
}
});
}
protected:
float4 transmittance_lut[TRANSMITTANCE_RES_Y][TRANSMITTANCE_RES_X];
float air_density;
float aerosol_density;
float ozone_density;
/* Compute absorption/scattering coeffients at the given altitude. */
void get_atmosphere_collision_coefficients(const float altitude,
float4 &aerosol_absorption,
float4 &aerosol_scattering,
float4 &molecular_absorption,
float4 &molecular_scattering) const
{
const float local_aerosol_density = get_aerosol_density(altitude) * aerosol_density;
aerosol_absorption = AEROSOL_ABSORPTION_CROSS_SECTION * local_aerosol_density;
aerosol_scattering = AEROSOL_SCATTERING_CROSS_SECTION * local_aerosol_density;
molecular_absorption = get_molecular_absorption_coefficient(altitude) * ozone_density;
molecular_scattering = get_molecular_scattering_coefficient(altitude) * air_density;
}
float4 lookup_multiscattering(float cos_theta, float normalized_height, float d) const
{
/* Solid angle subtended by the planet from a point at d distance from the planet center. */
const float omega = M_2PI_F * (1.0f - safe_sqrtf(1.0f - sqr(EARTH_RADIUS / d)));
const float4 T_to_ground = lookup_transmittance_at_ground(cos_theta);
/* We can split the path into Ground <-> Sample <-> Sun.
* The LUT gives us both T(Sample,Sun) and T(Ground,Sun) = T(Ground,Sample)*T(Sample,Sun),
* so we can easily compute T(Ground,Sample) from those two. */
const float4 T_ground_to_sample = lookup_transmittance_to_sun(0.0f) /
lookup_transmittance_to_sun(normalized_height);
/* 2nd order scattering from the ground. */
const float4 L_ground = PHASE_ISOTROPIC * omega * (GROUND_ALBEDO * M_1_PI_F) * T_to_ground *
T_ground_to_sample * cos_theta;
/* Fit of Earth's multiple scattering coming from other points in the atmosphere. */
const float4 L_ms = 0.02f * make_float4(0.217f, 0.347f, 0.594f, 1.0f) *
(1.0f / (1.0f + 5.0f * expf(-17.92f * cos_theta)));
return L_ms + L_ground;
}
/* Look up a transmittance from the precomputed LUT. */
float4 lookup_transmittance(const float cos_theta, const float normalized_altitude) const
{
const float u = saturate(cos_theta * 0.5f + 0.5f);
const float v = saturate(normalized_altitude);
const float x = float(TRANSMITTANCE_RES_X - 1) * u;
const float y = float(TRANSMITTANCE_RES_Y - 1) * v;
const int x1 = int(x);
const int y1 = int(y);
const int x2 = min(x1 + 1, TRANSMITTANCE_RES_X - 1);
const int y2 = min(y1 + 1, TRANSMITTANCE_RES_Y - 1);
const float fx = x - x1;
const float fy = y - y1;
const float4 bottom = mix(transmittance_lut[y1][x1], transmittance_lut[y1][x2], fx);
const float4 top = mix(transmittance_lut[y2][x1], transmittance_lut[y2][x2], fx);
return mix(bottom, top, fy);
}
/* Specialized versions of lookup_transmittance that skip one interpolation. */
float4 lookup_transmittance_at_ground(const float cos_theta) const
{
const float u = saturate(cos_theta * 0.5f + 0.5f);
const float x = float(TRANSMITTANCE_RES_X - 1) * u;
const int x1 = int(x);
const int x2 = min(x1 + 1, TRANSMITTANCE_RES_X - 1);
const int y = 0;
const float fx = x - x1;
return mix(transmittance_lut[y][x1], transmittance_lut[y][x2], fx);
}
float4 lookup_transmittance_to_sun(const float normalized_altitude) const
{
const float v = saturate(normalized_altitude);
const float y = float(TRANSMITTANCE_RES_Y - 1) * v;
const int x = TRANSMITTANCE_RES_X - 1;
const int y1 = int(y);
const int y2 = min(y1 + 1, TRANSMITTANCE_RES_Y - 1);
const float fy = y - y1;
return mix(transmittance_lut[y1][x], transmittance_lut[y2][x], fy);
}
};
void SKY_multiple_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)
{
SkyMultipleScattering sms(air_density, aerosol_density, ozone_density);
sms.precompute_lut();
/* Clamp altitude to avoid numerical issues. */
altitude = clamp(altitude, 1.0f, 99999.0f) / 1000.0f;
const int half_width = width / 2;
const float sun_zenith_cos_angle = cosf(M_PI_2_F - sun_elevation);
const float3 sun_dir = sun_direction(sun_zenith_cos_angle);
const int rows_per_task = std::max(1024 / width, 1);
SKY_parallel_for(0, height, rows_per_task, [&](const size_t begin, const size_t end) {
for (int y = begin; y < end; y++) {
float *pixel_row = pixels + (y * width * stride);
for (int x = 0; x < half_width; x++) {
float2 uv = make_float2((x + 0.5f) / width, (y + 0.5f) / height);
const float azimuth = M_2PI_F * uv.x;
/* Apply a non-linear transformation to the elevation to dedicate more texels to the
* horizon, where having more detail matters. */
const float l = uv.y * 2.0f - 1.0f;
/* [-pi/2, pi/2]. */
const float elev = copysignf(sqr(l), l) * M_PI_2_F;
const float3 ray_dir = make_float3(
cosf(elev) * cosf(azimuth), cosf(elev) * sinf(azimuth), sinf(elev));
const float3 ray_origin = make_float3(0.0f, 0.0f, EARTH_RADIUS + altitude);
const float atmos_dist = ray_sphere_intersection(ray_origin, ray_dir, ATMOSPHERE_RADIUS);
const float ground_dist = ray_sphere_intersection(ray_origin, ray_dir, EARTH_RADIUS);
/* If no ground collision then use the distance to the outer atmosphere, else we have a
* collision with the ground so we use the distance to it. */
const float t_d = (ground_dist < 0.0f) ? atmos_dist : ground_dist;
const float4 L = sms.get_inscattering(sun_dir, ray_origin, ray_dir, t_d);
const float3 sky = spectral_to_xyz(L);
/* Store pixels. */
const int pos_x = x * stride;
pixel_row[pos_x] = sky.x;
pixel_row[pos_x + 1] = sky.y;
pixel_row[pos_x + 2] = sky.z;
/* Mirror pixels. */
const int mirror_x = (width - x - 1) * stride;
pixel_row[mirror_x] = sky.x;
pixel_row[mirror_x + 1] = sky.y;
pixel_row[mirror_x + 2] = sky.z;
}
}
});
}
void SKY_multiple_scattering_precompute_sun(float sun_elevation,
float angular_diameter,
float altitude,
float air_density,
float aerosol_density,
float ozone_density,
float r_pixel_bottom[3],
float r_pixel_top[3])
{
const SkyMultipleScattering sms(air_density, aerosol_density, ozone_density);
/* Clamp altitude to avoid numerical issues. */
altitude = clamp(altitude, 1.0f, 99999.0f) / 1000.0f;
const float half_angular = angular_diameter / 2.0f;
const float solid_angle = M_2PI_F * (1.0f - cosf(half_angular));
const float normalized_altitude = altitude / ATMOSPHERE_THICKNESS;
/* Compute 2 pixels for Sun disc: one is the lowest point of the disc, one is the highest. */
auto get_sun_xyz = [&](const float elevation) {
const float sun_zenith_cos_angle = cosf(M_PI_2_F - elevation);
const float4 transmittance_to_sun = sms.get_transmittance(sun_zenith_cos_angle,
normalized_altitude);
const float4 spectrum = SUN_SPECTRAL_IRRADIANCE * transmittance_to_sun / solid_angle;
return spectral_to_xyz(spectrum);
};
const float3 bottom = get_sun_xyz(sun_elevation - half_angular);
const float3 top = get_sun_xyz(sun_elevation + half_angular);
/* Store pixels */
r_pixel_bottom[0] = bottom.x;
r_pixel_bottom[1] = bottom.y;
r_pixel_bottom[2] = bottom.z;
r_pixel_top[0] = top.x;
r_pixel_top[1] = top.y;
r_pixel_top[2] = top.z;
}
float SKY_earth_intersection_angle(float altitude)
{
/* Calculate intersection angle between line passing through viewpoint and Earth surface. */
return M_PI_2_F - asinf(EARTH_RADIUS / (EARTH_RADIUS + altitude / 1000.0f));
}

View File

@@ -0,0 +1,423 @@
/* SPDX-FileCopyrightText: 2011-2020 Blender Authors
*
* SPDX-License-Identifier: Apache-2.0 */
/** \file
* \ingroup intern_sky_modal
*/
#include <algorithm>
#include "sky_math.h"
#include "sky_nishita.h"
/* Constants. */
static const float RAYLEIGH_SCALE = 8e3f; /* Rayleigh scale height (m). */
static const float MIE_SCALE = 1.2e3f; /* Mie scale height (m). */
static const float MIE_COEFF = 2e-5f; /* Mie scattering coefficient (m^-1). */
static const float MIE_G = 0.76f; /* Aerosols anisotropy. */
static const float SQR_G = MIE_G * MIE_G; /* Squared aerosols anisotropy. */
static const float EARTH_RADIUS = 6360e3f; /* Radius of Earth (m). */
static const float ATMOSPHERE_RADIUS = 6420e3f; /* Radius of atmosphere (m). */
static const int STEPS = 32; /* Segments of primary ray. */
static const int NUM_WAVELENGTHS = 21; /* Number of wavelengths. */
static const int MIN_WAVELENGTH = 380; /* Lowest sampled wavelength (nm). */
static const int MAX_WAVELENGTH = 780; /* Highest sampled wavelength (nm). */
/* Step between each sampled wavelength (nm). */
static const float STEP_LAMBDA = (MAX_WAVELENGTH - MIN_WAVELENGTH) / (NUM_WAVELENGTHS - 1);
/* Sun irradiance on top of the atmosphere (W*m^-2*nm^-1). */
static const float IRRADIANCE[] = {
1.45756829855592995315f, 1.56596305559738380175f, 1.65148449067670455293f,
1.71496242737209314555f, 1.75797983805020541226f, 1.78256407885924539336f,
1.79095108475838560302f, 1.78541550133410664714f, 1.76815554864306845317f,
1.74122069647250410362f, 1.70647127164943679389f, 1.66556087452739887134f,
1.61993437242451854274f, 1.57083597368892080581f, 1.51932335059305478886f,
1.46628494965214395407f, 1.41245852740172450623f, 1.35844961970384092709f,
1.30474913844739281998f, 1.25174963272610817455f, 1.19975998755420620867f};
/* Rayleigh scattering coefficient (m^-1). */
static const float RAYLEIGH_COEFF[] = {
0.00005424820087636473f, 0.00004418549866505454f, 0.00003635151910165377f,
0.00003017929012024763f, 0.00002526320226989157f, 0.00002130859310621843f,
0.00001809838025320633f, 0.00001547057129129042f, 0.00001330284977336850f,
0.00001150184784075764f, 0.00000999557429990163f, 0.00000872799973630707f,
0.00000765513700977967f, 0.00000674217203751443f, 0.00000596134125832052f,
0.00000529034598065810f, 0.00000471115687557433f, 0.00000420910481110487f,
0.00000377218381260133f, 0.00000339051255477280f, 0.00000305591531679811f};
/* Ozone absorption coefficient (m^-1). */
static const float OZONE_COEFF[] = {
0.00000000325126849861f, 0.00000000585395365047f, 0.00000001977191155085f,
0.00000007309568762914f, 0.00000020084561514287f, 0.00000040383958096161f,
0.00000063551335912363f, 0.00000096707041180970f, 0.00000154797400424410f,
0.00000209038647223331f, 0.00000246128056164565f, 0.00000273551299461512f,
0.00000215125863128643f, 0.00000159051840791988f, 0.00000112356197979857f,
0.00000073527551487574f, 0.00000046450130357806f, 0.00000033096079921048f,
0.00000022512612292678f, 0.00000014879129266490f, 0.00000016828623364192f};
/* CIE XYZ color matching functions. */
static const float CMF_XYZ[][3] = {{0.00136800000f, 0.00003900000f, 0.00645000100f},
{0.01431000000f, 0.00039600000f, 0.06785001000f},
{0.13438000000f, 0.00400000000f, 0.64560000000f},
{0.34828000000f, 0.02300000000f, 1.74706000000f},
{0.29080000000f, 0.06000000000f, 1.66920000000f},
{0.09564000000f, 0.13902000000f, 0.81295010000f},
{0.00490000000f, 0.32300000000f, 0.27200000000f},
{0.06327000000f, 0.71000000000f, 0.07824999000f},
{0.29040000000f, 0.95400000000f, 0.02030000000f},
{0.59450000000f, 0.99500000000f, 0.00390000000f},
{0.91630000000f, 0.87000000000f, 0.00165000100f},
{1.06220000000f, 0.63100000000f, 0.00080000000f},
{0.85444990000f, 0.38100000000f, 0.00019000000f},
{0.44790000000f, 0.17500000000f, 0.00002000000f},
{0.16490000000f, 0.06100000000f, 0.00000000000f},
{0.04677000000f, 0.01700000000f, 0.00000000000f},
{0.01135916000f, 0.00410200000f, 0.00000000000f},
{0.00289932700f, 0.00104700000f, 0.00000000000f},
{0.00069007860f, 0.00024920000f, 0.00000000000f},
{0.00016615050f, 0.00006000000f, 0.00000000000f},
{0.00004150994f, 0.00001499000f, 0.00000000000f}};
/* Parameters for optical depth quadrature.
* See the comment in ray_optical_depth for more detail.
* Computed using sympy and following Python code:
* # from sympy.integrals.quadrature import gauss_laguerre
* # from sympy import exp
* # x, w = gauss_laguerre(8, 50)
* # xend = 25
* # print([(xi / xend).evalf(10) for xi in x])
* # print([(wi * exp(xi) / xend).evalf(10) for xi, wi in zip(x, w)])
*/
static const int QUADRATURE_STEPS = 8;
static const float QUADRATURE_NODES[] = {0.006811185292f,
0.03614807107f,
0.09004346519f,
0.1706680068f,
0.2818362161f,
0.4303406404f,
0.6296271457f,
0.9145252695f};
static const float QUADRATURE_WEIGHTS[] = {0.01750893642f,
0.04135477391f,
0.06678839063f,
0.09507698807f,
0.1283416365f,
0.1707430204f,
0.2327233347f,
0.3562490486f};
static float3 geographical_to_direction(float lat, float lon)
{
return make_float3(cosf(lat) * cosf(lon), cosf(lat) * sinf(lon), sinf(lat));
}
static float3 spec_to_xyz(const float *spectrum)
{
float3 xyz = make_float3(0.0f, 0.0f, 0.0f);
for (int i = 0; i < NUM_WAVELENGTHS; i++) {
xyz.x += CMF_XYZ[i][0] * spectrum[i];
xyz.y += CMF_XYZ[i][1] * spectrum[i];
xyz.z += CMF_XYZ[i][2] * spectrum[i];
}
return xyz * STEP_LAMBDA;
}
/* Atmosphere volume models */
static float density_rayleigh(float height)
{
return expf(-height / RAYLEIGH_SCALE);
}
static float density_mie(float height)
{
return expf(-height / MIE_SCALE);
}
static float density_ozone(float height)
{
return fmax(0.0, 1.0 - (fabs(height - 25000.0) / 15000.0));
}
static float phase_rayleigh(float mu)
{
return (0.1875f * M_1_PI_F) * (1.0f + sqr(mu));
}
static float phase_mie(float mu)
{
return (3.0f * (1.0f - SQR_G) * (1.0f + sqr(mu))) /
(8.0f * M_PI_F * (2.0f + SQR_G) * powf((1.0f + SQR_G - 2.0f * MIE_G * mu), 1.5));
}
/* Intersection helpers. */
static bool surface_intersection(float3 pos, float3 dir)
{
if (dir.z >= 0) {
return false;
}
float b = -2.0f * dot(dir, -pos);
float c = len_squared(pos) - sqr(EARTH_RADIUS);
float t = b * b - 4.0f * c;
if (t >= 0.0f) {
return true;
}
return false;
}
static float3 atmosphere_intersection(float3 pos, float3 dir)
{
float b = -2.0f * dot(dir, -pos);
float c = len_squared(pos) - sqr(ATMOSPHERE_RADIUS);
float t = (-b + sqrtf(b * b - 4.0f * c)) / 2.0f;
return make_float3(pos.x + dir.x * t, pos.y + dir.y * t, pos.z + dir.z * t);
}
static float3 ray_optical_depth(float3 ray_origin, float3 ray_dir)
{
/* This function computes the optical depth along a ray.
* Instead of using classic ray marching, the code is based on Gauss-Laguerre quadrature,
* which is designed to compute the integral of f(x)*exp(-x) from 0 to infinity.
* This works well here, since the optical depth along the ray tends to decrease exponentially.
* By setting f(x) = g(x) exp(x), the exponentials cancel out and we get the integral of g(x).
* The nodes and weights used here are the standard n=6 Gauss-Laguerre values, except that
* the exp(x) scaling factor is already included in the weights.
* The parametrization along the ray is scaled so that the last quadrature node is still within
* the atmosphere. */
float3 ray_end = atmosphere_intersection(ray_origin, ray_dir);
float ray_length = distance(ray_origin, ray_end);
float3 segment = ray_length * ray_dir;
/* Instead of tracking the transmission spectrum across all wavelengths directly,
* we use the fact that the density always has the same spectrum for each type of
* scattering, so we split the density into a constant spectrum and a factor and
* only track the factors. */
float3 optical_depth = make_float3(0.0f, 0.0f, 0.0f);
for (int i = 0; i < QUADRATURE_STEPS; i++) {
float3 P = ray_origin + QUADRATURE_NODES[i] * segment;
/* Height above sea level. */
float height = len(P) - EARTH_RADIUS;
float3 density = make_float3(
density_rayleigh(height), density_mie(height), density_ozone(height));
optical_depth += density * QUADRATURE_WEIGHTS[i];
}
return optical_depth * ray_length;
}
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)
{
/* This code computes single-inscattering along a ray through the atmosphere. */
float3 ray_end = atmosphere_intersection(ray_origin, ray_dir);
float ray_length = distance(ray_origin, ray_end);
/* To compute the inscattering, we step along the ray in segments and accumulate
* the inscattering as well as the optical depth along each segment. */
float segment_length = ray_length / STEPS;
float3 segment = segment_length * ray_dir;
/* Instead of tracking the transmission spectrum across all wavelengths directly,
* we use the fact that the density always has the same spectrum for each type of
* scattering, so we split the density into a constant spectrum and a factor and
* only track the factors. */
float3 optical_depth = make_float3(0.0f, 0.0f, 0.0f);
/* Zero out light accumulation. */
for (int wl = 0; wl < NUM_WAVELENGTHS; wl++) {
r_spectrum[wl] = 0.0f;
}
/* Phase function for scattering and the density scale factor. */
float mu = dot(ray_dir, sun_dir);
float3 phase_function = make_float3(phase_rayleigh(mu), phase_mie(mu), 0.0f);
float3 density_scale = make_float3(air_density, aerosol_density, ozone_density);
/* The density and in-scattering of each segment is evaluated at its middle. */
float3 P = ray_origin + 0.5f * segment;
for (int i = 0; i < STEPS; i++) {
/* Height above sea level. */
float height = len(P) - EARTH_RADIUS;
/* Evaluate and accumulate optical depth along the ray. */
float3 density = density_scale * make_float3(density_rayleigh(height),
density_mie(height),
density_ozone(height));
optical_depth += segment_length * density;
/* If the Earth isn't in the way, evaluate inscattering from the Sun. */
if (!surface_intersection(P, sun_dir)) {
float3 light_optical_depth = density_scale * ray_optical_depth(P, sun_dir);
float3 total_optical_depth = optical_depth + light_optical_depth;
/* Attenuation of light. */
for (int wl = 0; wl < NUM_WAVELENGTHS; wl++) {
float3 extinction_density = total_optical_depth * make_float3(RAYLEIGH_COEFF[wl],
1.11f * MIE_COEFF,
OZONE_COEFF[wl]);
float attenuation = expf(-reduce_add(extinction_density));
float3 scattering_density = density * make_float3(RAYLEIGH_COEFF[wl], MIE_COEFF, 0.0f);
/* The total inscattered radiance from one segment is:
* Tr(A<->B) * Tr(B<->C) * sigma_s * phase * L * segment_length
*
* These terms are:
* Tr(A<->B): Transmission from start to scattering position (tracked in optical_depth)
* Tr(B<->C): Transmission from scattering position to light (computed in
* ray_optical_depth) sigma_s: Scattering density phase: Phase function of the scattering
* type (Rayleigh or Mie) L: Radiance coming from the light source segment_length: The
* length of the segment
*
* The code here is just that, with a bit of additional optimization to not store full
* spectra for the optical depth
*/
r_spectrum[wl] += attenuation * reduce_add(phase_function * scattering_density) *
IRRADIANCE[wl] * segment_length;
}
}
/* Advance along ray. */
P += segment;
}
}
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)
{
/* Clamp altitude to avoid numerical issues. */
altitude = clamp(altitude, 1.0f, 59999.0f);
/* Calculate texture pixels. */
const int half_width = width / 2;
const int half_height = height / 2;
const float3 cam_pos = make_float3(0, 0, EARTH_RADIUS + altitude);
const float3 sun_dir = geographical_to_direction(sun_elevation, 0.0f);
const float longitude_step = M_2PI_F / width;
const int rows_per_task = std::max(1024 / width, 1);
/* Compute Sky in the upper hemisphere. */
SKY_parallel_for(half_height, height, rows_per_task, [=](const size_t begin, const size_t end) {
for (int y = begin; y < end; y++) {
/* Sample more pixels toward the horizon. */
float latitude = M_PI_2_F * sqr(float(y) / half_height - 1.0f);
float *pixel_row = pixels + (y * width * stride);
for (int x = 0; x < half_width; x++) {
float longitude = longitude_step * x - M_PI_F;
float3 dir = geographical_to_direction(latitude, longitude);
float spectrum[NUM_WAVELENGTHS];
single_scattering(
dir, sun_dir, cam_pos, air_density, aerosol_density, ozone_density, spectrum);
const float3 xyz = spec_to_xyz(spectrum);
/* Store pixels. */
int pos_x = x * stride;
pixel_row[pos_x] = xyz.x;
pixel_row[pos_x + 1] = xyz.y;
pixel_row[pos_x + 2] = xyz.z;
/* Mirror sky. */
int mirror_x = (width - x - 1) * stride;
pixel_row[mirror_x] = xyz.x;
pixel_row[mirror_x + 1] = xyz.y;
pixel_row[mirror_x + 2] = xyz.z;
}
}
});
/* Fill in the lower hemisphere by fading out the horizon. */
for (int y = 0; y < half_height; y++) {
/* Sample more pixels toward the horizon. */
float latitude = M_PI_2_F * sqr(float(y) / half_height - 1.0f);
float3 dir = geographical_to_direction(latitude, 0.0f);
float fade = 0.0f;
if (dir.z < 0.4f) {
fade = 1.0f - dir.z * 2.5f;
fade = sqr(fade) * fade;
}
float *pixel_row = pixels + (y * width * stride);
float *horizon_row = pixels + (half_height * width * stride);
for (int x = 0, offset = 0; x < width; x++, offset += stride) {
pixel_row[offset + 0] = horizon_row[offset + 0] * fade;
pixel_row[offset + 1] = horizon_row[offset + 1] * fade;
pixel_row[offset + 2] = horizon_row[offset + 2] * fade;
}
}
}
/*********** Sun ***********/
static void sun_radiation(float3 cam_dir,
float altitude,
float air_density,
float aerosol_density,
float solid_angle,
float *r_spectrum)
{
float3 cam_pos = make_float3(0, 0, EARTH_RADIUS + altitude);
float3 optical_depth = ray_optical_depth(cam_pos, cam_dir);
/* Compute final spectrum. */
for (int i = 0; i < NUM_WAVELENGTHS; i++) {
/* Combine spectra and the optical depth into transmittance. */
float transmittance = RAYLEIGH_COEFF[i] * optical_depth.x * air_density +
1.11f * MIE_COEFF * optical_depth.y * aerosol_density;
r_spectrum[i] = IRRADIANCE[i] * expf(-transmittance) / solid_angle;
}
}
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])
{
/* Clamp altitude to avoid numerical issues. */
altitude = clamp(altitude, 1.0f, 59999.0f);
float half_angular = angular_diameter / 2.0f;
float solid_angle = M_2PI_F * (1.0f - cosf(half_angular));
float spectrum[NUM_WAVELENGTHS];
float bottom = sun_elevation - half_angular;
float top = sun_elevation + half_angular;
float elevation_bottom, elevation_top;
float3 pix_bottom, pix_top, sun_dir;
/* Compute 2 pixels for Sun disc: one is the lowest point of the disc, one is the highest.
* Return black pixels if Sun is below horizon. */
elevation_bottom = (bottom > 0.0f) ? bottom : 0.0f;
elevation_top = (top > 0.0f) ? top : 0.0f;
if (elevation_top > 0.0f) {
sun_dir = geographical_to_direction(elevation_bottom, 0.0f);
sun_radiation(sun_dir, altitude, air_density, aerosol_density, solid_angle, spectrum);
pix_bottom = spec_to_xyz(spectrum);
sun_dir = geographical_to_direction(elevation_top, 0.0f);
sun_radiation(sun_dir, altitude, air_density, aerosol_density, solid_angle, spectrum);
pix_top = spec_to_xyz(spectrum);
}
else {
pix_bottom = make_float3(0.0f, 0.0f, 0.0f);
pix_top = make_float3(0.0f, 0.0f, 0.0f);
}
/* Store pixels. */
r_pixel_bottom[0] = pix_bottom.x;
r_pixel_bottom[1] = pix_bottom.y;
r_pixel_bottom[2] = pix_bottom.z;
r_pixel_top[0] = pix_top.x;
r_pixel_top[1] = pix_top.y;
r_pixel_top[2] = pix_top.z;
}