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,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;
}