diff --git a/sandbox/tests/test scenes/sun and sky/20 - nishita93 - theta 0 - ptne.appleseed b/sandbox/tests/test scenes/sun and sky/20 - nishita93 - theta 0 - ptne.appleseed
new file mode 100644
index 0000000000..66fc910b34
--- /dev/null
+++ b/sandbox/tests/test scenes/sun and sky/20 - nishita93 - theta 0 - ptne.appleseed
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0.95313944699513697 -0.00430888342411300 -0.30250062496702701 -1.21740753536959101
+ 0.00000000000000000 0.99989856662897403 -0.01424276845712700 0.35374149338383298
+ 0.30253131173781700 0.01357534445090600 0.95304276684796996 4.67618990067894735
+ 0.00000000000000000 0.00000000000000000 0.00000000000000000 1.00000000000000000
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0.729412019 0.729412019 0.729412019
+
+
+ 1.000000000
+
+
+
+
+
+
+
+ 1.000000000 1.000000000 1.000000000
+
+
+ 1.000000000
+
+
+
+
+
+
+
+ 1.000000000 1.000000000 1.000000000
+
+
+ 1.000000000
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sandbox/tests/test scenes/sun and sky/21 - nishita93 - theta 60 - ptne.appleseed b/sandbox/tests/test scenes/sun and sky/21 - nishita93 - theta 60 - ptne.appleseed
new file mode 100644
index 0000000000..4e6119ed31
--- /dev/null
+++ b/sandbox/tests/test scenes/sun and sky/21 - nishita93 - theta 60 - ptne.appleseed
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0.95313944699513697 -0.00430888342411300 -0.30250062496702701 -1.21740753536959101
+ 0.00000000000000000 0.99989856662897403 -0.01424276845712700 0.35374149338383298
+ 0.30253131173781700 0.01357534445090600 0.95304276684796996 4.67618990067894735
+ 0.00000000000000000 0.00000000000000000 0.00000000000000000 1.00000000000000000
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0.729412019 0.729412019 0.729412019
+
+
+ 1.000000000
+
+
+
+
+
+
+
+ 1.000000000 1.000000000 1.000000000
+
+
+ 1.000000000
+
+
+
+
+
+
+
+ 1.000000000 1.000000000 1.000000000
+
+
+ 1.000000000
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sandbox/tests/test scenes/sun and sky/22 - nishita93 - theta 86 - ptne.appleseed b/sandbox/tests/test scenes/sun and sky/22 - nishita93 - theta 86 - ptne.appleseed
new file mode 100644
index 0000000000..75b6672775
--- /dev/null
+++ b/sandbox/tests/test scenes/sun and sky/22 - nishita93 - theta 86 - ptne.appleseed
@@ -0,0 +1,214 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0.95313944699513697 -0.00430888342411300 -0.30250062496702701 -1.21740753536959101
+ 0.00000000000000000 0.99989856662897403 -0.01424276845712700 0.35374149338383298
+ 0.30253131173781700 0.01357534445090600 0.95304276684796996 4.67618990067894735
+ 0.00000000000000000 0.00000000000000000 0.00000000000000000 1.00000000000000000
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0.729412019 0.729412019 0.729412019
+
+
+ 1.000000000
+
+
+
+
+
+
+
+ 1.000000000 1.000000000 1.000000000
+
+
+ 1.000000000
+
+
+
+
+
+
+
+ 1.000000000 1.000000000 1.000000000
+
+
+ 1.000000000
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sandbox/tests/test scenes/sun and sky/23 - nishita93 - theta 86 - 10000m - ptne.appleseed b/sandbox/tests/test scenes/sun and sky/23 - nishita93 - theta 86 - 10000m - ptne.appleseed
new file mode 100644
index 0000000000..c73c74a93f
--- /dev/null
+++ b/sandbox/tests/test scenes/sun and sky/23 - nishita93 - theta 86 - 10000m - ptne.appleseed
@@ -0,0 +1,212 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0.95313944699513697 -0.00430888342411300 -0.30250062496702701 -1.21740753536959101
+ 0.00000000000000000 0.99989856662897403 -0.01424276845712700 0.35374149338383298
+ 0.30253131173781700 0.01357534445090600 0.95304276684796996 4.67618990067894735
+ 0.00000000000000000 0.00000000000000000 0.00000000000000000 1.00000000000000000
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0.729412019 0.729412019 0.729412019
+
+
+ 1.000000000
+
+
+
+
+
+
+
+ 1.000000000 1.000000000 1.000000000
+
+
+ 1.000000000
+
+
+
+
+
+
+
+ 1.000000000 1.000000000 1.000000000
+
+
+ 1.000000000
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sandbox/tests/test scenes/sun and sky/24 - nishita93 - theta 86 - 25000m - ptne.appleseed b/sandbox/tests/test scenes/sun and sky/24 - nishita93 - theta 86 - 25000m - ptne.appleseed
new file mode 100644
index 0000000000..6a63375b4e
--- /dev/null
+++ b/sandbox/tests/test scenes/sun and sky/24 - nishita93 - theta 86 - 25000m - ptne.appleseed
@@ -0,0 +1,212 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0.95313944699513697 -0.00430888342411300 -0.30250062496702701 -1.21740753536959101
+ 0.00000000000000000 0.99989856662897403 -0.01424276845712700 0.35374149338383298
+ 0.30253131173781700 0.01357534445090600 0.95304276684796996 4.67618990067894735
+ 0.00000000000000000 0.00000000000000000 0.00000000000000000 1.00000000000000000
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0.729412019 0.729412019 0.729412019
+
+
+ 1.000000000
+
+
+
+
+
+
+
+ 1.000000000 1.000000000 1.000000000
+
+
+ 1.000000000
+
+
+
+
+
+
+
+ 1.000000000 1.000000000 1.000000000
+
+
+ 1.000000000
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sandbox/tests/test scenes/sun and sky/25 - nishita93 - theta 86 - 50000m - ptne.appleseed b/sandbox/tests/test scenes/sun and sky/25 - nishita93 - theta 86 - 50000m - ptne.appleseed
new file mode 100644
index 0000000000..04b8f4e139
--- /dev/null
+++ b/sandbox/tests/test scenes/sun and sky/25 - nishita93 - theta 86 - 50000m - ptne.appleseed
@@ -0,0 +1,212 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0.95313944699513697 -0.00430888342411300 -0.30250062496702701 -1.21740753536959101
+ 0.00000000000000000 0.99989856662897403 -0.01424276845712700 0.35374149338383298
+ 0.30253131173781700 0.01357534445090600 0.95304276684796996 4.67618990067894735
+ 0.00000000000000000 0.00000000000000000 0.00000000000000000 1.00000000000000000
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0.729412019 0.729412019 0.729412019
+
+
+ 1.000000000
+
+
+
+
+
+
+
+ 1.000000000 1.000000000 1.000000000
+
+
+ 1.000000000
+
+
+
+
+
+
+
+ 1.000000000 1.000000000 1.000000000
+
+
+ 1.000000000
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sandbox/tests/test scenes/sun and sky/26 - nishita93 - theta 86 - 15000000m - ptne.appleseed b/sandbox/tests/test scenes/sun and sky/26 - nishita93 - theta 86 - 15000000m - ptne.appleseed
new file mode 100644
index 0000000000..7a53c62059
--- /dev/null
+++ b/sandbox/tests/test scenes/sun and sky/26 - nishita93 - theta 86 - 15000000m - ptne.appleseed
@@ -0,0 +1,203 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0.99280120839318042 -0.11926363706205065 0.01104289308886783 5536.50999955567021971
+ 0.00000000000000000 0.09219791032453716 0.99574070185555275 499224.97875194193329662
+ -0.11977378934074309 -0.98857257204846638 0.09153419678152656 45892.28435730801720638
+ 0.00000000000000000 0.00000000000000000 0.00000000000000000 1.00000000000000000
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0.729412019 0.729412019 0.729412019
+
+
+ 1.000000000
+
+
+
+
+
+
+
+ 1.000000000 1.000000000 1.000000000
+
+
+ 1.000000000
+
+
+
+
+
+
+
+ 1.000000000 1.000000000 1.000000000
+
+
+ 1.000000000
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sandbox/tests/test scenes/sun and sky/ref/20 - nishita93 - theta 0 - ptne.png b/sandbox/tests/test scenes/sun and sky/ref/20 - nishita93 - theta 0 - ptne.png
new file mode 100644
index 0000000000..50dd2e28f7
Binary files /dev/null and b/sandbox/tests/test scenes/sun and sky/ref/20 - nishita93 - theta 0 - ptne.png differ
diff --git a/sandbox/tests/test scenes/sun and sky/ref/21 - nishita93 - theta 60 - ptne.png b/sandbox/tests/test scenes/sun and sky/ref/21 - nishita93 - theta 60 - ptne.png
new file mode 100644
index 0000000000..34a484b24e
Binary files /dev/null and b/sandbox/tests/test scenes/sun and sky/ref/21 - nishita93 - theta 60 - ptne.png differ
diff --git a/sandbox/tests/test scenes/sun and sky/ref/22 - nishita93 - theta 86 - ptne.png b/sandbox/tests/test scenes/sun and sky/ref/22 - nishita93 - theta 86 - ptne.png
new file mode 100644
index 0000000000..cda7473c6c
Binary files /dev/null and b/sandbox/tests/test scenes/sun and sky/ref/22 - nishita93 - theta 86 - ptne.png differ
diff --git a/sandbox/tests/test scenes/sun and sky/ref/23 - nishita93 - theta 86 - 10000m - ptne.png b/sandbox/tests/test scenes/sun and sky/ref/23 - nishita93 - theta 86 - 10000m - ptne.png
new file mode 100644
index 0000000000..68ec908e3f
Binary files /dev/null and b/sandbox/tests/test scenes/sun and sky/ref/23 - nishita93 - theta 86 - 10000m - ptne.png differ
diff --git a/sandbox/tests/test scenes/sun and sky/ref/24 - nishita93 - theta 86 - 25000m - ptne.png b/sandbox/tests/test scenes/sun and sky/ref/24 - nishita93 - theta 86 - 25000m - ptne.png
new file mode 100644
index 0000000000..cc6e7d036e
Binary files /dev/null and b/sandbox/tests/test scenes/sun and sky/ref/24 - nishita93 - theta 86 - 25000m - ptne.png differ
diff --git a/sandbox/tests/test scenes/sun and sky/ref/25 - nishita93 - theta 86 - 25000m - ptne.png b/sandbox/tests/test scenes/sun and sky/ref/25 - nishita93 - theta 86 - 25000m - ptne.png
new file mode 100644
index 0000000000..e609ff7f37
Binary files /dev/null and b/sandbox/tests/test scenes/sun and sky/ref/25 - nishita93 - theta 86 - 25000m - ptne.png differ
diff --git a/sandbox/tests/test scenes/sun and sky/ref/26 - nishita93 - theta 86 - 15000000m - ptne.png b/sandbox/tests/test scenes/sun and sky/ref/26 - nishita93 - theta 86 - 15000000m - ptne.png
new file mode 100644
index 0000000000..0a9da67b49
Binary files /dev/null and b/sandbox/tests/test scenes/sun and sky/ref/26 - nishita93 - theta 86 - 15000000m - ptne.png differ
diff --git a/src/appleseed/CMakeLists.txt b/src/appleseed/CMakeLists.txt
index 68b428c1e4..cf49195acf 100644
--- a/src/appleseed/CMakeLists.txt
+++ b/src/appleseed/CMakeLists.txt
@@ -1806,6 +1806,8 @@ source_group ("renderer\\modeling\\environment" FILES
set (renderer_modeling_environmentedf_sources
renderer/modeling/environmentedf/ArHosekSkyModelData_CIEXYZ.h
+ renderer/modeling/environmentedf/atmosphereshell.cpp
+ renderer/modeling/environmentedf/atmosphereshell.h
renderer/modeling/environmentedf/constantenvironmentedf.cpp
renderer/modeling/environmentedf/constantenvironmentedf.h
renderer/modeling/environmentedf/constanthemisphereenvironmentedf.cpp
@@ -1825,8 +1827,14 @@ set (renderer_modeling_environmentedf_sources
renderer/modeling/environmentedf/latlongmapenvironmentedf.h
renderer/modeling/environmentedf/mirrorballmapenvironmentedf.cpp
renderer/modeling/environmentedf/mirrorballmapenvironmentedf.h
+ renderer/modeling/environmentedf/nishita93environmentedf.cpp
+ renderer/modeling/environmentedf/nishita93environmentedf.h
+ renderer/modeling/environmentedf/opticaldepth.cpp
+ renderer/modeling/environmentedf/opticaldepth.h
renderer/modeling/environmentedf/oslenvironmentedf.cpp
renderer/modeling/environmentedf/oslenvironmentedf.h
+ renderer/modeling/environmentedf/physicalsky.cpp
+ renderer/modeling/environmentedf/physicalsky.h
renderer/modeling/environmentedf/preethamenvironmentedf.cpp
renderer/modeling/environmentedf/preethamenvironmentedf.h
renderer/modeling/environmentedf/sphericalcoordinates.h
diff --git a/src/appleseed/renderer/modeling/environmentedf/atmosphereshell.cpp b/src/appleseed/renderer/modeling/environmentedf/atmosphereshell.cpp
new file mode 100644
index 0000000000..1ad31b7e85
--- /dev/null
+++ b/src/appleseed/renderer/modeling/environmentedf/atmosphereshell.cpp
@@ -0,0 +1,171 @@
+
+//
+// This source file is part of appleseed.
+// Visit https://appleseedhq.net/ for additional information and resources.
+//
+// This software is released under the MIT license.
+//
+// Copyright (c) 2020 Joel Barmettler, The appleseedhq Organization
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+// appleseed.foundation headers.
+#include "foundation/math/intersection/raysphere.h"
+#include "foundation/math/ray.h"
+
+// Header file.
+#include "atmosphereshell.h"
+
+#include
+
+using namespace foundation;
+
+float get_rayleigh_density(float height)
+{
+ return expf((-height + earth_radius) / rayleigh_scale);
+}
+
+float get_mie_density(float height)
+{
+ return expf((-height + earth_radius) / mie_scale);
+}
+
+float get_ozone_density(float height)
+{
+ const float total_height = height - earth_radius;
+ if (total_height < ozone_start)
+ return 0.0f;
+ if (total_height < ozone_peak)
+ return (total_height - ozone_start) * (1.0f / (ozone_peak - ozone_start));
+ return expf(-(total_height - ozone_peak) / ozone_start);
+}
+
+const int shell::n_atmosphere_shells;
+shell shell::atmosphere_shells[shell::n_atmosphere_shells + 1];
+
+shell::shell() {}
+
+shell::shell(int i) : index(i)
+{
+ radius = shell::find_radius(index);
+ densities();
+}
+
+shell::shell(int i, float r, float rd, float md, float od) :
+ index(i),
+ radius(r),
+ rayleigh_density(rd),
+ mie_density(md),
+ ozone_density(od) {}
+
+bool shell::ray_in_shell(const Ray3f& ray) const
+{
+ return (norm(ray.m_org) < radius);
+}
+
+shell::intersection shell::intersection_distance_inside(const Ray3f& ray) const
+{
+ const float radius_sqr = radius * radius;
+ float b = -2.0f * dot(ray.m_dir, -ray.m_org);
+ float c = square_norm(ray.m_org) - radius_sqr;
+ float distance = (-b + sqrtf(b * b - 4.0f * c)) / 2.0f;
+ return shell::intersection(distance, this);
+}
+
+size_t shell::intersection_distances_outside(const Ray3f& ray, shell::intersection intersections[2]) const
+{
+ float distances[2] = { 0.0f };
+ size_t n_distances = intersect_sphere_unit_direction(ray, earth_center, radius, distances);
+ intersections[0].distance = distances[0];
+ intersections[1].distance = distances[1];
+ intersections[1].involved_shell = this;
+ return n_distances;
+}
+
+void shell::densities()
+{
+ const float previous_radius = shell::find_radius(index-1);
+ const float shell_center_height = (radius + previous_radius) / 2.0f;
+ rayleigh_density = get_rayleigh_density(shell_center_height);
+ mie_density = get_mie_density(shell_center_height);
+ ozone_density = get_ozone_density(shell_center_height);
+}
+
+float shell::find_radius(int shell_index)
+{
+ if (shell_index < 0) {
+ return earth_radius;
+ }
+ const float scale = rayleigh_scale * 2;
+ float x = static_cast(shell_index) / static_cast(shell::n_atmosphere_shells - 1);
+ const float a = expf(-(atmosphere_radius - earth_radius) / scale) - 1.0f;
+ return earth_radius - scale * logf(a * x + 1.0f);
+}
+
+float shell::find_index(float shell_radius)
+{
+ for (int i = 0; i < shell::n_atmosphere_shells; i++) {
+ float radius_s1 = shell::atmosphere_shells[i].radius;
+ float radius_s2 = shell::atmosphere_shells[i + 1].radius;
+ float dist_s1 = radius_s1 - shell_radius;
+ float dist_s2 = radius_s2 - shell_radius;
+ if (dist_s1 <= 0 && dist_s2 > 0) {
+ return static_cast(i) + ((shell_radius - radius_s1) / (radius_s2 - radius_s1));
+ }
+ }
+ return shell::n_atmosphere_shells;
+}
+
+int shell::find_intersections(const Ray3f& ray, shell::intersection intersections[]) {
+ int intersct_i = 0;
+ for (int k = 0; k < shell::n_atmosphere_shells; k++) {
+ shell *kth_shell = &shell::atmosphere_shells[k];
+
+ if (kth_shell->ray_in_shell(ray)) {
+ intersections[intersct_i] = kth_shell->intersection_distance_inside(ray);
+ intersct_i++;
+ }
+ else {
+ shell::intersection ray_intersections[2];
+ size_t n_intersections = kth_shell->intersection_distances_outside(ray, ray_intersections);
+ if (n_intersections >= 1) {
+ ray_intersections[0].involved_shell = &shell::atmosphere_shells[k + 1];
+ intersections[intersct_i] = ray_intersections[0];
+ intersct_i++;
+ }
+ if (n_intersections == 2) {
+ intersections[intersct_i] = ray_intersections[1];
+ intersct_i++;
+ }
+ }
+ }
+
+ std::sort(intersections, intersections + intersct_i, shell::intersection::sort_by_distance);
+ return intersct_i;
+}
+
+shell::intersection::intersection() : distance(0) {}
+
+shell::intersection::intersection(float d, const shell* s) : distance(d), involved_shell(s) {}
+
+bool shell::intersection::sort_by_distance(const shell::intersection& i, const shell::intersection& j)
+{
+ return i.distance < j.distance;
+}
diff --git a/src/appleseed/renderer/modeling/environmentedf/atmosphereshell.h b/src/appleseed/renderer/modeling/environmentedf/atmosphereshell.h
new file mode 100644
index 0000000000..00cede0ced
--- /dev/null
+++ b/src/appleseed/renderer/modeling/environmentedf/atmosphereshell.h
@@ -0,0 +1,129 @@
+
+//
+// This source file is part of appleseed.
+// Visit https://appleseedhq.net/ for additional information and resources.
+//
+// This software is released under the MIT license.
+//
+// Copyright (c) 2020 Joel Barmettler, The appleseedhq Organization
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "foundation/math/vector.h"
+#include "foundation/math/ray.h"
+
+#include
+
+using namespace foundation;
+
+// Forward declarations.
+struct intersection;
+
+const float earth_radius = 6378137.0f; // radius of Earth (m) according to nssdc.gsfc.nasa.gov
+const float atmosphere_thickness = 60000.0f; // rough height of Mesosphere according to nasa.gov
+const float atmosphere_radius = earth_radius + atmosphere_thickness; // radius of atmosphere (m)
+const Vector3f earth_center(0.0f, 0.0f, 0.0f); // central point of the earth (m)
+
+const float rayleigh_scale = 7994.0f; // rayleigh scale height H0 (m)
+const float mie_scale = 1200.0f; // mie scale height H0 (m)
+
+const float ozone_ground = 0.4f; // total amount of ozone on the ground
+const float ozone_start = 10000.0f; // height at beginning of ozone layer in atmosphere (m)
+const float ozone_peak = 32000.0f; // height at peak density of ozone in atmosphere (m)
+
+// Density of rayleigh particles at height (m) above the earths surface.
+float get_rayleigh_density(float height);
+
+// Density of mie particles at height (m) above the earths surface.
+float get_mie_density(float height);
+
+// Density of ozone particles (m) above the earths surface.
+// Source: https://doi.org/10.1145/584458.584482
+float get_ozone_density(float height);
+
+//
+// Class representing a shell around the earth with constant rayleigh/mie particle density.
+//
+class shell {
+
+public:
+
+ static const int n_atmosphere_shells = 64; // number of atmospheric shells around the earth
+ static shell atmosphere_shells[n_atmosphere_shells + 1]; // precomputed atmospheric shells with constant particle density
+
+ //
+ // Intersection of ray with a shell after a distance (m).
+ //
+ struct intersection {
+
+ float distance; // distance before ray hits shell
+ const shell* involved_shell; // pointer to shell that was hit
+
+ intersection();
+
+ intersection(float d, const shell* s);
+
+ // Comparison function used to sort intersections with increasing distance.
+ static bool sort_by_distance(const intersection& i, const intersection& j);
+ };
+
+ int index; // shell index between [0, n_shells)
+ float radius; // radius (m) of shell around earth
+ float rayleigh_density; // constant density of aerosol particles withing shell
+ float mie_density; // constant density of dust particles within shell
+ float ozone_density; // constant density of ozone particles within shell
+
+ shell();
+
+ // Instanciate a new shell at index i of n_shells.
+ shell(int i);
+
+ // Predefine all shell values.
+ shell(int i, float r, float rd, float md, float od);
+
+ // Determines whether a given light ray origins inside of the shell.
+ bool ray_in_shell(const Ray3f& ray) const;
+
+ // Determines intersection from ray within a shell.
+ shell::intersection intersection_distance_inside(const Ray3f& ray) const;
+
+ // Determines 0, 1 or 2 intersections from ray outside of a shell.
+ size_t intersection_distances_outside(const Ray3f& ray, shell::intersection intersections[2]) const;
+
+ // Determine height (m) of shell.
+ static float find_radius(int shell_index);
+
+ // Returns index of shell that matches best. A return value of 4.2 suggest that the distance to shell 4 is 20% and shell 5 is 80%.
+ static float find_index(float shell_radius);
+
+ // Finds the intersection distances between the ray and each atmospheric shell.
+ // Returns the number N of intersections found, stores intersection objects in the intersections-array from position 0 to position N-1.
+ static int find_intersections(const Ray3f& ray, shell::intersection intersections[]);
+
+private:
+
+ // Finds rayleigh and mie density for this shell.
+ void densities();
+
+};
+
+
diff --git a/src/appleseed/renderer/modeling/environmentedf/environmentedffactoryregistrar.cpp b/src/appleseed/renderer/modeling/environmentedf/environmentedffactoryregistrar.cpp
index 1a99c9fb6d..1acb6c08be 100644
--- a/src/appleseed/renderer/modeling/environmentedf/environmentedffactoryregistrar.cpp
+++ b/src/appleseed/renderer/modeling/environmentedf/environmentedffactoryregistrar.cpp
@@ -41,6 +41,7 @@
#include "renderer/modeling/environmentedf/mirrorballmapenvironmentedf.h"
#include "renderer/modeling/environmentedf/oslenvironmentedf.h"
#include "renderer/modeling/environmentedf/preethamenvironmentedf.h"
+#include "renderer/modeling/environmentedf/nishita93environmentedf.h"
// appleseed.foundation headers.
#include "foundation/memory/autoreleaseptr.h"
@@ -73,6 +74,7 @@ EnvironmentEDFFactoryRegistrar::EnvironmentEDFFactoryRegistrar(const SearchPaths
impl->register_factory(auto_release_ptr(new MirrorBallMapEnvironmentEDFFactory()));
impl->register_factory(auto_release_ptr(new OSLEnvironmentEDFFactory()));
impl->register_factory(auto_release_ptr(new PreethamEnvironmentEDFFactory()));
+ impl->register_factory(auto_release_ptr(new Nishita93EnvironmentEDFFactory()));
}
EnvironmentEDFFactoryRegistrar::~EnvironmentEDFFactoryRegistrar()
diff --git a/src/appleseed/renderer/modeling/environmentedf/nishita93environmentedf.cpp b/src/appleseed/renderer/modeling/environmentedf/nishita93environmentedf.cpp
new file mode 100644
index 0000000000..6bf67e4496
--- /dev/null
+++ b/src/appleseed/renderer/modeling/environmentedf/nishita93environmentedf.cpp
@@ -0,0 +1,494 @@
+
+//
+// This source file is part of appleseed.
+// Visit https://appleseedhq.net/ for additional information and resources.
+//
+// This software is released under the MIT license.
+//
+// Copyright (c) 2020 Joel Barmettler, The appleseedhq Organization
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+// Interface header.
+#include "nishita93environmentedf.h"
+
+// appleseed.renderer headers.
+#include "renderer/modeling/color/colorspace.h"
+#include "renderer/modeling/environmentedf/environmentedf.h"
+
+// appleseed.foundation headers.
+#include "foundation/math/ray.h"
+#include "foundation/math/sampling/mappings.h"
+#include "foundation/utility/api/specializedapiarrays.h"
+
+// Standard headers.
+#include
+#include
+
+#include "physicalsky.h"
+
+// Forward declarations.
+namespace foundation { class IAbortSwitch; }
+namespace renderer { class Project; }
+
+using namespace foundation;
+
+namespace renderer
+{
+
+ namespace
+ {
+
+ //
+ // An environment EDF implementing the Nishita93 day sky model.
+ //
+ // Reference:
+ //
+ // http://nishitalab.org/user/nis/cdrom/sig93_nis.pdf
+ //
+
+ const char* Model = "nishita93_environment_edf";
+
+ class Nishita93EnvironmentEDF
+ : public EnvironmentEDF
+ {
+ public:
+ Nishita93EnvironmentEDF(
+ const char* name,
+ const ParamArray& params)
+ : EnvironmentEDF(name, params)
+ {
+ m_inputs.declare("sun_theta", InputFormat::Float, "45.0");
+ m_inputs.declare("sun_phi", InputFormat::Float, "0.0");
+ m_inputs.declare("sun_intensity_multiplier", InputFormat::Float, "1.0");
+ m_inputs.declare("elevation", InputFormat::Float, "0.0");
+ m_inputs.declare("air_molecule_density", InputFormat::Float, "1.0");
+ m_inputs.declare("dust_molecule_density", InputFormat::Float, "1.0");
+ m_inputs.declare("ozone_molecule_density", InputFormat::Float, "1.0");
+ m_inputs.declare("haze", InputFormat::Float, "0.8");
+ m_inputs.declare("horizon_shift", InputFormat::Float, "0.0");
+ m_inputs.declare("sun_angular_diameter", InputFormat::Float, "0.545");
+ }
+
+ void release() override
+ {
+ delete this;
+ }
+
+ const char* get_model() const override
+ {
+ return Model;
+ }
+
+ bool on_frame_begin(
+ const Project& project,
+ const BaseGroup* parent,
+ OnFrameBeginRecorder& recorder,
+ IAbortSwitch* abort_switch) override
+ {
+ if (!EnvironmentEDF::on_frame_begin(project, parent, recorder, abort_switch))
+ return false;
+
+ // Evaluate uniform values.
+ m_inputs.evaluate_uniforms(&m_uniform_values);
+
+ // Set user input values from user interface.
+ m_sun_theta = deg_to_rad(m_uniform_values.m_sun_theta);
+ m_sun_phi = deg_to_rad(m_uniform_values.m_sun_phi);
+ m_sun_intensity_multiplier = m_uniform_values.m_sun_intensity_multiplier;
+ m_elevation = m_uniform_values.m_elevation;
+ m_air_molecule_density = m_uniform_values.m_air_molecule_density;
+ m_dust_molecule_density = m_uniform_values.m_dust_molecule_density;
+ m_ozone_molecule_density = m_uniform_values.m_ozone_molecule_density;
+ m_haze = m_uniform_values.m_haze;
+ m_sun_angular_diameter = deg_to_rad(m_uniform_values.m_sun_angular_diameter);
+ m_precompute = m_params.get_optional("precompute", true);
+
+ // Compute the sun direction.
+ sun_dir = Vector3f::make_unit_vector(m_sun_theta, m_sun_phi);
+
+ // Precompute nishitas lookup table for optical depths.
+ sky_precomputations();
+
+ return true;
+ }
+
+ void sample(
+ const ShadingContext& shading_context,
+ const Vector2f& s,
+ Vector3f& outgoing,
+ Spectrum& value,
+ float& probability) const override
+ {
+ const Vector3f local_outgoing = sample_hemisphere_cosine(s);
+
+ Transformd scratch;
+ const Transformd& transform = m_transform_sequence.evaluate(0.0f, scratch);
+ outgoing = transform.vector_to_parent(local_outgoing);
+ const Vector3f shifted_outgoing = shift(local_outgoing);
+
+ RegularSpectrum31f radiance;
+ compute_sky_radiance(shading_context, shifted_outgoing, radiance);
+
+ value.set(radiance, g_std_lighting_conditions, Spectrum::Illuminance);
+ probability = shifted_outgoing.y > 0.0f ? shifted_outgoing.y * RcpPi() : 0.0f;
+ assert(probability >= 0.0f);
+ }
+
+ void evaluate(
+ const ShadingContext& shading_context,
+ const Vector3f& outgoing,
+ Spectrum& value) const override
+ {
+ assert(is_normalized(outgoing));
+
+ Transformd scratch;
+ const Transformd& transform = m_transform_sequence.evaluate(0.0f, scratch);
+ const Vector3f local_outgoing = transform.vector_to_local(outgoing);
+ const Vector3f shifted_outgoing = shift(local_outgoing);
+
+ RegularSpectrum31f radiance;
+ compute_sky_radiance(shading_context, shifted_outgoing, radiance);
+
+ value.set(radiance, g_std_lighting_conditions, Spectrum::Illuminance);
+ }
+
+ void evaluate(
+ const ShadingContext& shading_context,
+ const Vector3f& outgoing,
+ Spectrum& value,
+ float& probability) const override
+ {
+ assert(is_normalized(outgoing));
+
+ Transformd scratch;
+ const Transformd& transform = m_transform_sequence.evaluate(0.0f, scratch);
+ const Vector3f local_outgoing = transform.vector_to_local(outgoing);
+ const Vector3f shifted_outgoing = shift(local_outgoing);
+
+ RegularSpectrum31f radiance;
+ compute_sky_radiance(shading_context, shifted_outgoing, radiance);
+
+ value.set(radiance, g_std_lighting_conditions, Spectrum::Illuminance);
+ probability = shifted_outgoing.y > 0.0f ? shifted_outgoing.y * RcpPi() : 0.0f;
+ assert(probability >= 0.0f);
+ }
+
+ float evaluate_pdf(
+ const Vector3f& outgoing) const override
+ {
+ assert(is_normalized(outgoing));
+
+ Transformd scratch;
+ const Transformd& transform = m_transform_sequence.evaluate(0.0f, scratch);
+ const Vector3f local_outgoing = transform.vector_to_local(outgoing);
+ const Vector3f shifted_outgoing = shift(local_outgoing);
+
+ const float probability = shifted_outgoing.y > 0.0f ? shifted_outgoing.y * RcpPi() : 0.0f;
+ assert(probability >= 0.0f);
+
+ return probability;
+ }
+
+ private:
+ APPLESEED_DECLARE_INPUT_VALUES(InputValues)
+ {
+ float m_sun_theta; // angle (deg) between sun and zenith, 0=zenith
+ float m_sun_phi; // angle (deg) between sun and north, 0=north
+ float m_sun_intensity_multiplier; // increases or decreases the returned radiance values
+ float m_elevation; // elevation of camera above earth surface (m)
+ float m_air_molecule_density; // multiplier of air molecule density, affecting rayleigh scattering
+ float m_dust_molecule_density; // multiplier of dust molecule density, affecting mie scattering
+ float m_ozone_molecule_density; // multiplier of ozone molecule density, affecting ozone scattering
+ float m_haze; // u parameter for Cornette phase function used for Mie scattering
+ float m_horizon_shift;
+ float m_sun_angular_diameter; // rays with angle (rad) +- sun_angular_diameter will return sun radiance directly
+ };
+
+ InputValues m_uniform_values;
+
+ float m_sun_theta; // angle (rad) between sun and zenith, 0=zenith
+ float m_sun_phi; // angle (rad) between sun and north, 0=north
+ Vector3f sun_dir; // vector pointing into the direction of the sun
+
+ float m_sun_intensity_multiplier; // increases or decreases the returned radiance values
+ float m_elevation; // elevation of camera above earth surface (m)
+ float m_air_molecule_density; // multiplier of air molecule density, affecting rayleigh scattering
+ float m_dust_molecule_density; // multiplier of dust molecule density, affecting mie scattering
+ float m_ozone_molecule_density; // multiplier of ozone molecule density, affecting ozone scattering
+ float m_haze; // u parameter for Cornette phase function used for Mie scattering
+ float m_sun_angular_diameter; // rays with angle (rad) +- sun_angular_diameter will return sun radiance directly
+ bool m_precompute; // use 2D lookup table to precompute sky optical depths
+
+ // Fills a 3D precomputation table with optical depths value along the sun direction.
+ void sky_precomputations() {
+ nishita::precompute_mie_g(m_haze);
+ nishita::precompute_shells();
+ if (m_precompute)
+ nishita::precompute_optical_depths(sun_dir, m_air_molecule_density, m_dust_molecule_density, m_ozone_molecule_density);
+ }
+
+ // Compute the sky radiance along a given direction.
+ void compute_sky_radiance(
+ const ShadingContext& shading_context,
+ const Vector3f& outgoing,
+ RegularSpectrum31f& radiance) const
+ {
+
+ // Position of the camera at least 1.2 meters above earth radius to avoid numerical errors.
+ Vector3f camera_position = Vector3f(0.0f, earth_radius + 1.2f + m_elevation, 0.0f);
+ Ray3f ray = Ray3f(camera_position, outgoing);
+
+ // If the outoing vector points to the sun, return suns spectrum.
+ float sun_angular_radius = m_sun_angular_diameter / 2.0f;
+ float is_sun = norm(outgoing - sun_dir) < sun_angular_radius;
+ if (is_sun) {
+ const bool sun_hit = nishita::sun_disk(
+ ray,
+ m_air_molecule_density, // air molecule density (Rayleigh scattering)
+ m_dust_molecule_density, // dust molecule density (Mie scattering)
+ m_ozone_molecule_density, // ozone molecule density (Ozone scattering)
+ sun_angular_radius,
+ radiance
+ );
+ if (sun_hit)
+ return;
+ }
+
+ // Compute the final sky radiance.
+ nishita::single_scattering(
+ ray,
+ sun_dir, // sun direction
+ m_air_molecule_density, // air molecule density (Rayleigh scattering)
+ m_dust_molecule_density, // dust molecule density (Mie scattering)
+ m_ozone_molecule_density, // ozone molecule density (Ozone scattering)
+ m_precompute, // use precomputed lookup table
+ radiance
+ );
+ radiance *=
+ m_uniform_values.m_sun_intensity_multiplier // multiply sun intensity
+ * 1.5f; // since nishita93 underestimates radiance by 1/3 according to Bruneton
+ }
+
+ Vector3f shift(Vector3f v) const
+ {
+ v.y -= m_uniform_values.m_horizon_shift;
+ return normalize(v);
+ }
+ };
+ }
+
+
+ //
+ // Nishita93EnvironmentEDFFactory class implementation.
+ //
+ void Nishita93EnvironmentEDFFactory::release()
+ {
+ delete this;
+ }
+
+ const char* Nishita93EnvironmentEDFFactory::get_model() const
+ {
+ return Model;
+ }
+
+ Dictionary Nishita93EnvironmentEDFFactory::get_model_metadata() const
+ {
+ return
+ Dictionary()
+ .insert("name", Model)
+ .insert("label", "Nishita93 Environment EDF")
+ .insert("help", "Physical sky with single scattering environment");
+ }
+
+ DictionaryArray Nishita93EnvironmentEDFFactory::get_input_metadata() const
+ {
+ DictionaryArray metadata;
+
+ metadata.push_back(
+ Dictionary()
+ .insert("name", "sun_theta")
+ .insert("label", "Sun Theta Angle")
+ .insert("type", "numeric")
+ .insert("min",
+ Dictionary()
+ .insert("value", "0.0")
+ .insert("type", "hard"))
+ .insert("max",
+ Dictionary()
+ .insert("value", "100.0")
+ .insert("type", "hard"))
+ .insert("use", "required")
+ .insert("default", "45.0")
+ .insert("help", "Sun polar (vertical) angle in degrees"));
+
+ metadata.push_back(
+ Dictionary()
+ .insert("name", "sun_phi")
+ .insert("label", "Sun Phi Angle")
+ .insert("type", "numeric")
+ .insert("min",
+ Dictionary()
+ .insert("value", "-360.0")
+ .insert("type", "soft"))
+ .insert("max",
+ Dictionary()
+ .insert("value", "360.0")
+ .insert("type", "soft"))
+ .insert("use", "required")
+ .insert("default", "0.0")
+ .insert("help", "Sun azimuthal (horizontal) angle in degrees"));
+
+ metadata.push_back(
+ Dictionary()
+ .insert("name", "sun_intensity_multiplier")
+ .insert("label", "Sun intensity multiplier")
+ .insert("type", "numeric")
+ .insert("min",
+ Dictionary()
+ .insert("value", "0.0")
+ .insert("type", "hard"))
+ .insert("max",
+ Dictionary()
+ .insert("value", "10.0")
+ .insert("type", "soft"))
+ .insert("use", "optional")
+ .insert("default", "1.0")
+ .insert("help", "Multiplies sun intensity with constant factor"));
+
+ metadata.push_back(
+ Dictionary()
+ .insert("name", "air_molecule_density")
+ .insert("label", "Air Molecule Density")
+ .insert("type", "numeric")
+ .insert("min",
+ Dictionary()
+ .insert("value", "0.0")
+ .insert("type", "hard"))
+ .insert("max",
+ Dictionary()
+ .insert("value", "5.0")
+ .insert("type", "hard"))
+ .insert("use", "optional")
+ .insert("default", "1.0")
+ .insert("help", "Air molecule density affect Rayleigh scattering (blueness of sky)"));
+
+ metadata.push_back(
+ Dictionary()
+ .insert("name", "dust_molecule_density")
+ .insert("label", "Dust Molecule Density")
+ .insert("type", "numeric")
+ .insert("min",
+ Dictionary()
+ .insert("value", "0.0")
+ .insert("type", "hard"))
+ .insert("max",
+ Dictionary()
+ .insert("value", "5.0")
+ .insert("type", "hard"))
+ .insert("use", "optional")
+ .insert("default", "1.0")
+ .insert("help", "Dust molecule density affect Mie scattering (turbidity)"));
+
+ metadata.push_back(
+ Dictionary()
+ .insert("name", "ozone_molecule_density")
+ .insert("label", "Ozone Molecule Density")
+ .insert("type", "numeric")
+ .insert("min",
+ Dictionary()
+ .insert("value", "0.0")
+ .insert("type", "hard"))
+ .insert("max",
+ Dictionary()
+ .insert("value", "5.0")
+ .insert("type", "hard"))
+ .insert("use", "optional")
+ .insert("default", "1.0")
+ .insert("help", "Ozone molecules effect the sky color at twilight"));
+
+ metadata.push_back(
+ Dictionary()
+ .insert("name", "haze")
+ .insert("label", "Haze in the atmosphere")
+ .insert("type", "numeric")
+ .insert("min",
+ Dictionary()
+ .insert("value", "0.7")
+ .insert("type", "hard"))
+ .insert("max",
+ Dictionary()
+ .insert("value", "0.8")
+ .insert("type", "hard"))
+ .insert("use", "optional")
+ .insert("default", "0.8")
+ .insert("help", "Haze changes effect of dust particles on sunlight"));
+
+ metadata.push_back(
+ Dictionary()
+ .insert("name", "elevation")
+ .insert("label", "Elevation")
+ .insert("type", "text")
+ .insert("use", "optional")
+ .insert("default", "0")
+ .insert("help", "Elevates camera above earths surface"));
+
+ metadata.push_back(
+ Dictionary()
+ .insert("name", "sun_angular_diameter")
+ .insert("label", "Sun Size")
+ .insert("type", "text")
+ .insert("use", "optional")
+ .insert("default", "0.545")
+ .insert("help", "Angular diameter of sun disk, make 0 to hide sun"));
+
+ metadata.push_back(
+ Dictionary()
+ .insert("name", "horizon_shift")
+ .insert("label", "Horizon Shift")
+ .insert("type", "text")
+ .insert("use", "optional")
+ .insert("default", "0.0")
+ .insert("help", "Shift the horizon vertically"));
+
+ metadata.push_back(
+ Dictionary()
+ .insert("name", "precompute")
+ .insert("label", "Precalculate sky")
+ .insert("type", "boolean")
+ .insert("use", "optional")
+ .insert("default", "true")
+ .insert("help", "If enabled, precalculations make sky fast but slightly less accurate"));
+
+ add_common_input_metadata(metadata);
+
+ return metadata;
+ }
+
+ auto_release_ptr Nishita93EnvironmentEDFFactory::create(
+ const char* name,
+ const ParamArray& params) const
+ {
+ return
+ auto_release_ptr(
+ new Nishita93EnvironmentEDF(name, params));
+ }
+
+}
diff --git a/src/appleseed/renderer/modeling/environmentedf/nishita93environmentedf.h b/src/appleseed/renderer/modeling/environmentedf/nishita93environmentedf.h
new file mode 100644
index 0000000000..751b1e01a9
--- /dev/null
+++ b/src/appleseed/renderer/modeling/environmentedf/nishita93environmentedf.h
@@ -0,0 +1,76 @@
+
+//
+// This source file is part of appleseed.
+// Visit https://appleseedhq.net/ for additional information and resources.
+//
+// This software is released under the MIT license.
+//
+// Copyright (c) 2020 Joel Barmettler, The appleseedhq Organization
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+// appleseed.renderer headers.
+#include "renderer/modeling/environmentedf/ienvironmentedffactory.h"
+
+// appleseed.foundation headers.
+#include "foundation/memory/autoreleaseptr.h"
+#include "foundation/platform/compiler.h"
+
+// appleseed.main headers.
+#include "main/dllsymbol.h"
+
+// Forward declarations.
+namespace foundation { class Dictionary; }
+namespace foundation { class DictionaryArray; }
+namespace renderer { class EnvironmentEDF; }
+namespace renderer { class ParamArray; }
+
+namespace renderer
+{
+
+ //
+ // An environment EDF implementing the Nishita93 day sky model.
+ //
+
+ class APPLESEED_DLLSYMBOL Nishita93EnvironmentEDFFactory
+ : public IEnvironmentEDFFactory
+ {
+ public:
+ // Delete this instance.
+ void release() override;
+
+ // Return a string identifying this environment EDF model.
+ const char* get_model() const override;
+
+ // Return metadata for this environment EDF model.
+ foundation::Dictionary get_model_metadata() const override;
+
+ // Return metadata for the inputs of this environment EDF model.
+ foundation::DictionaryArray get_input_metadata() const override;
+
+ // Create a new environment EDF instance.
+ foundation::auto_release_ptr create(
+ const char* name,
+ const ParamArray& params) const override;
+ };
+
+}
diff --git a/src/appleseed/renderer/modeling/environmentedf/opticaldepth.cpp b/src/appleseed/renderer/modeling/environmentedf/opticaldepth.cpp
new file mode 100644
index 0000000000..149e2f88e4
--- /dev/null
+++ b/src/appleseed/renderer/modeling/environmentedf/opticaldepth.cpp
@@ -0,0 +1,52 @@
+
+//
+// This source file is part of appleseed.
+// Visit https://appleseedhq.net/ for additional information and resources.
+//
+// This software is released under the MIT license.
+//
+// Copyright (c) 2020 Joel Barmettler, The appleseedhq Organization
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "opticaldepth.h"
+
+sky::opticaldepth::opticaldepth() {
+ sky::opticaldepth(0.0f, 0.0f, 0.0f);
+}
+
+sky::opticaldepth::opticaldepth(float r, float m, float o) {
+ sky::opticaldepth(r, m, o, 1.0f, 1.0f, 1.0f);
+}
+
+sky::opticaldepth::opticaldepth(float r, float m, float o, float ad, float dd, float od) :
+ rayleigh(r),
+ mie(m),
+ ozone(o),
+ air_particle_density(ad),
+ dust_particle_density(dd),
+ ozone_particle_density(od) { }
+
+void sky::opticaldepth::increase(float segment_length, float rayleigh_density, float mie_density, float ozone_density) {
+ rayleigh += segment_length * rayleigh_density * air_particle_density;
+ mie += segment_length * mie_density * dust_particle_density;
+ ozone += segment_length * ozone_density * ozone_particle_density;
+}
+
diff --git a/src/appleseed/renderer/modeling/environmentedf/opticaldepth.h b/src/appleseed/renderer/modeling/environmentedf/opticaldepth.h
new file mode 100644
index 0000000000..c7fc77a50b
--- /dev/null
+++ b/src/appleseed/renderer/modeling/environmentedf/opticaldepth.h
@@ -0,0 +1,65 @@
+
+//
+// This source file is part of appleseed.
+// Visit https://appleseedhq.net/ for additional information and resources.
+//
+// This software is released under the MIT license.
+//
+// Copyright (c) 2020 Joel Barmettler, The appleseedhq Organization
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+namespace sky {
+ class opticaldepth {
+
+ public:
+ float rayleigh;
+ float mie;
+ float ozone;
+
+ opticaldepth();
+ opticaldepth(float r, float m, float o);
+ opticaldepth(float r, float m, float o, float ad, float dd, float od);
+
+ void increase(float segment_length, float rayleigh_density, float mie_density, float ozone_density);
+
+ inline sky::opticaldepth operator+(const sky::opticaldepth& rod) {
+ return sky::opticaldepth(rayleigh + rod.rayleigh, mie + rod.mie, ozone + rod.ozone, air_particle_density, dust_particle_density, ozone_particle_density);
+ }
+
+ inline sky::opticaldepth operator-(const sky::opticaldepth& rod) {
+ return sky::opticaldepth(rayleigh - rod.rayleigh, mie - rod.mie, ozone - rod.ozone, air_particle_density, dust_particle_density, ozone_particle_density);
+ }
+
+ inline sky::opticaldepth operator*(float i) {
+ return sky::opticaldepth(rayleigh * i, mie * i, ozone * i, air_particle_density, dust_particle_density, ozone_particle_density);
+ }
+
+ protected:
+ float air_particle_density;
+ float dust_particle_density;
+ float ozone_particle_density;
+ };
+
+};
+
+
diff --git a/src/appleseed/renderer/modeling/environmentedf/physicalsky.cpp b/src/appleseed/renderer/modeling/environmentedf/physicalsky.cpp
new file mode 100644
index 0000000000..4dff1ae269
--- /dev/null
+++ b/src/appleseed/renderer/modeling/environmentedf/physicalsky.cpp
@@ -0,0 +1,265 @@
+
+//
+// This source file is part of appleseed.
+// Visit https://appleseedhq.net/ for additional information and resources.
+//
+// This software is released under the MIT license.
+//
+// Copyright (c) 2020 Joel Barmettler, The appleseedhq Organization
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+// appleseed.foundation headers.
+#include "foundation/math/vector.h"
+#include "foundation/image/regularspectrum.h"
+#include "foundation/math/ray.h"
+#include "foundation/math/intersection/raysphere.h"
+#include "physicalsky.h"
+
+#include
+
+using namespace foundation;
+
+float mie_g; // mie assymetricity component g
+
+
+void nishita::precompute_mie_g(float haze) {
+ mie_g = nishita::mie_assymetricity(haze);
+}
+
+void nishita::precompute_shells() {
+ for (int i = 0; i < shell::n_atmosphere_shells; i++) {
+ shell::atmosphere_shells[i] = shell(i);
+ }
+ // Outermost "shell" is deep space with infinite radius.
+ shell::atmosphere_shells[shell::n_atmosphere_shells] = shell(shell::n_atmosphere_shells, INFINITY, 0.0f, 0.0f, 0.0f);
+}
+
+void nishita::precompute_optical_depths(const Vector3f& sun_dir, float air_particle_density, float dust_particle_density, float ozone_particle_density) {
+ float sqrt3 = sqrtf(3.0f);
+ Vector3f unit_vector = Vector3f(sqrt3);
+ Vector3f sun_dir_perpendicular = normalize(cross(sun_dir, unit_vector));
+
+ for (int n_cylinder = 0; n_cylinder < nishita::n_cylinders; n_cylinder++) {
+ float cylinder_radius = nishita::cylinder_delta * n_cylinder;
+ Ray3f cylinder_border = Ray3f(sun_dir_perpendicular * cylinder_radius, sun_dir);
+ for (int n_shell = 0; n_shell <= shell::n_atmosphere_shells; n_shell++) {
+ if (shell::atmosphere_shells[n_shell].ray_in_shell(cylinder_border)) {
+ shell::intersection shell_cylinder_intersection = shell::atmosphere_shells[n_shell].intersection_distance_inside(cylinder_border);
+ Vector3f intersection_point = cylinder_border.m_org + sun_dir * shell_cylinder_intersection.distance;
+ Ray3f intersection_ray = Ray3f(intersection_point, sun_dir);
+
+ const sky::opticaldepth optical_depth = nishita::ray_optical_depth(intersection_ray, air_particle_density, dust_particle_density, ozone_particle_density);
+ nishita::optical_depths_table[n_shell][n_cylinder] = optical_depth;
+ }
+ else {
+ nishita::optical_depths_table[n_shell][n_cylinder] = nishita::optical_depths_table[n_shell][n_cylinder-1];
+ }
+ }
+ }
+}
+
+float nishita::rayleigh_phase(float angle)
+{
+ const float angle_squared = angle * angle;
+ return 3.0f / (16.0f * Pi()) * (1.0f + angle_squared);
+}
+
+float nishita::mie_assymetricity(float u)
+{
+ const float x = 5.0f / 9.0f * u + 125.0f / 729.0f * powf(u, 3.0f) +
+ powf(64.0f / 27.0f - 325.0f / 243.0f * (u * u) + 1250.0f / 2187.0f * powf(u, 4), 1.0f / 2.0f);
+ return 5.0f / 9.0f * u - (4.0f / 3.0f - 25.0f / 81.0f * (u * u)) * powf(x, -1.0f / 3.0f) + powf(x, 1.0f / 3.0f);
+}
+
+float nishita::mie_phase(float angle)
+{
+ const static float mie_g_sqr = (mie_g * mie_g);
+ return 3.0f / (8.0f * Pi()) * (1.0f - mie_g_sqr) / (2.0f + mie_g_sqr) *
+ (1.0f + angle * angle) / powf(1.0f + mie_g_sqr - 2.0f * mie_g * angle, 3.0f / 2.0f);
+}
+
+bool nishita::intersects_earth(const Ray3f& ray)
+{
+ if (ray.m_dir.y >= 0)
+ return false;
+ return intersect_sphere_unit_direction(ray, earth_center, earth_radius);
+}
+
+bool nishita::intersects_earth(const Ray3f& ray, float& distance)
+{
+ if (ray.m_dir.y >= 0)
+ return false;
+ return intersect_sphere_unit_direction(ray, earth_center, earth_radius, distance);
+}
+
+bool nishita::ray_inside_earth(const Ray3f& ray)
+{
+ return (norm(ray.m_org) < earth_radius);
+}
+
+float nishita::distance_to_atmosphere(const Ray3f& ray) {
+ const float radius_sqr = earth_radius * earth_radius;
+ float b = -2.0f * dot(ray.m_dir, -ray.m_org);
+ float c = square_norm(ray.m_org) - radius_sqr;
+ return (-b + sqrtf(b * b - 4.0f * c)) / 2.0f;
+}
+
+sky::opticaldepth nishita::ray_optical_depth(const Ray3f& ray, float air_particle_density, float dust_particle_density, float ozone_particle_density)
+{
+ shell::intersection intersections[shell::n_atmosphere_shells * 2];
+ int n_intersections = shell::find_intersections(ray, intersections);
+ float passed_distance = 0.0f;
+
+ sky::opticaldepth optical_depth(0.0f, 0.0f, 0.0f, air_particle_density, dust_particle_density, ozone_particle_density);
+
+ for (int i = 0; i < n_intersections; i++) {
+
+ shell::intersection ith_intersection = intersections[i];
+ float segment_length = ith_intersection.distance - passed_distance;
+
+ optical_depth.increase(
+ segment_length,
+ ith_intersection.involved_shell->rayleigh_density,
+ ith_intersection.involved_shell->mie_density,
+ ith_intersection.involved_shell->ozone_density
+ );
+
+ passed_distance = ith_intersection.distance;
+ }
+ return optical_depth;
+}
+
+sky::opticaldepth nishita::lookup_optical_depth(const Ray3f& ray) {
+ Vector3f sun_dir = ray.m_dir;
+ float radius = sqrtf(square_distance_point_line(ray.m_org, earth_center, sun_dir));
+
+ float cylinder_index_raw = radius / nishita::cylinder_delta;
+ int cylinder_index = static_cast(cylinder_index_raw);
+ float second_cylinder_dominance = cylinder_index_raw - static_cast(cylinder_index);
+ float first_cylinder_dominance = 1.0f - second_cylinder_dominance;
+
+ float shell_index_raw = shell::find_index(norm(ray.m_org));
+ int shell_index = static_cast(shell_index_raw);
+ float second_shell_dominance = shell_index_raw - static_cast(shell_index);
+ float first_shell_dominance = 1.0f - second_shell_dominance;
+
+ sky::opticaldepth avg_cylinder_depths_1 = nishita::optical_depths_table[shell_index][cylinder_index] * first_cylinder_dominance
+ + nishita::optical_depths_table[shell_index][cylinder_index + 1] * second_cylinder_dominance;
+ sky::opticaldepth avg_cylinder_depths_2 = nishita::optical_depths_table[shell_index + 1][cylinder_index] * first_cylinder_dominance
+ + nishita::optical_depths_table[shell_index + 1][cylinder_index + 1] * second_cylinder_dominance;
+ sky::opticaldepth looked_up_depth = avg_cylinder_depths_1 * first_shell_dominance + avg_cylinder_depths_2 * second_shell_dominance;
+ return looked_up_depth;
+}
+
+void nishita::single_scattering(
+ const Ray3f& ray,
+ const Vector3f& sun_dir,
+ float air_particle_density,
+ float dust_particle_density,
+ float ozone_particle_density,
+ bool is_precomputed,
+ RegularSpectrum31f& spectrum)
+{
+ spectrum.set(0.0f);
+
+ float distance_to_earth_intersection = 0.0f;
+ bool earth_intersection = intersects_earth(ray, distance_to_earth_intersection);
+
+ shell::intersection intersections[shell::n_atmosphere_shells * 2];
+ int n_intersections = shell::find_intersections(ray, intersections);
+
+ sky::opticaldepth optical_depth(0.0f, 0.0f, 0.0f, air_particle_density, dust_particle_density, ozone_particle_density);
+
+ float angle = dot(ray.m_dir, sun_dir);
+ float rayleigh_phase_function = rayleigh_phase(angle);
+ float mie_phase_function = mie_phase(angle);
+
+ float passed_distance = 0;
+
+ for (int i = 0; i < n_intersections; i++) {
+
+ shell::intersection ith_intersection = intersections[i];
+ float segment_length = ith_intersection.distance - passed_distance;
+ passed_distance = ith_intersection.distance;
+ float half_segment_length = segment_length / 2.0f;
+
+ float distance_to_scatterpoint = (ith_intersection.distance - half_segment_length);
+
+ Vector3f segment_middle_point = ray.m_org + (ray.m_dir * distance_to_scatterpoint);
+ Ray3f scatter_ray = Ray3f(segment_middle_point, sun_dir);
+
+ if (earth_intersection && ((distance_to_scatterpoint > distance_to_earth_intersection) || intersects_earth(scatter_ray)))
+ break;
+
+ float rayleigh_density = ith_intersection.involved_shell->rayleigh_density;
+ float mie_density = ith_intersection.involved_shell->mie_density;
+ float ozone_density = ith_intersection.involved_shell->ozone_density;
+
+ optical_depth.increase(segment_length, rayleigh_density, mie_density, ozone_density);
+
+ sky::opticaldepth ligh_optical_depth;
+
+ if (is_precomputed)
+ ligh_optical_depth = lookup_optical_depth(scatter_ray);
+ else
+ ligh_optical_depth = ray_optical_depth(scatter_ray, air_particle_density, dust_particle_density, ozone_particle_density);
+
+ sky::opticaldepth total_optical_depth = optical_depth + ligh_optical_depth;
+ const RegularSpectrum31f total_extinction_density = rayleigh_coeff_spectrum * total_optical_depth.rayleigh +
+ mie_coeff_spectrum * total_optical_depth.mie +
+ ozone_coeff_spectrum * total_optical_depth.ozone;
+
+ float attenuations[num_wavelengths];
+ for (int wl = 0; wl < num_wavelengths; wl++) { attenuations[wl] = expf(-total_extinction_density[wl]); }
+ const RegularSpectrum31f attenuation = RegularSpectrum31f::from_array(attenuations);
+
+ const RegularSpectrum31f total_reduction = rayleigh_phase_function * rayleigh_density * rayleigh_coeff_spectrum + mie_phase_function * mie_density * mie_coeff_spectrum;
+ spectrum += attenuation * total_reduction * nishita::sun_radiance_spectrum * segment_length;
+ }
+}
+
+
+bool nishita::sun_disk(
+ const Ray3f& ray,
+ float air_particle_density,
+ float dust_particle_density,
+ float ozone_particle_density,
+ float sun_radius,
+ RegularSpectrum31f& spectrum)
+{
+ spectrum.set(0.0f);
+ if (intersects_earth(ray)) {
+ return false;
+ }
+ sky::opticaldepth optical_depth = ray_optical_depth(ray, air_particle_density, dust_particle_density, ozone_particle_density);
+ float solid_angle = Pi() * (1.0f - cosf(sun_radius));
+
+ const RegularSpectrum31f rayleigh_transmittance = rayleigh_coeff_spectrum * optical_depth.rayleigh * air_particle_density;
+ const RegularSpectrum31f mie_transmittance = mie_coeff_spectrum * optical_depth.mie * dust_particle_density;
+ const RegularSpectrum31f ozone_transmittance = ozone_coeff_spectrum * optical_depth.ozone * ozone_particle_density;
+ const RegularSpectrum31f total_transmittance = rayleigh_transmittance + mie_transmittance + ozone_transmittance;
+
+ float attenuations[num_wavelengths];
+ for (int wl = 0; wl < num_wavelengths; wl++) { attenuations[wl] = expf(-total_transmittance[wl]); }
+
+ spectrum = nishita::sun_radiance_spectrum / solid_angle * RegularSpectrum31f::from_array(attenuations);
+ return true;
+}
diff --git a/src/appleseed/renderer/modeling/environmentedf/physicalsky.h b/src/appleseed/renderer/modeling/environmentedf/physicalsky.h
new file mode 100644
index 0000000000..7feb3cb908
--- /dev/null
+++ b/src/appleseed/renderer/modeling/environmentedf/physicalsky.h
@@ -0,0 +1,222 @@
+
+//
+// This source file is part of appleseed.
+// Visit https://appleseedhq.net/ for additional information and resources.
+//
+// This software is released under the MIT license.
+//
+// Copyright (c) 2020 Joel Barmettler, The appleseedhq Organization
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "foundation/math/vector.h"
+#include "foundation/image/regularspectrum.h"
+#include "foundation/math/ray.h"
+#include "foundation/math/distance.h"
+
+#include "atmosphereshell.h"
+#include "opticaldepth.h"
+
+using namespace foundation;
+
+namespace nishita {
+
+ static const int num_wavelengths = 31; // number of wavelengths per spectrum (400nm to 700nm, delta=10nm)
+ static const int n_cylinders = 1024; // number of cylinders for optical depth precomputation
+
+ // Lookup table storing optical dephts into the direction of the sun.
+ static sky::opticaldepth optical_depths_table[shell::n_atmosphere_shells + 1][n_cylinders];
+
+ // Width of a single cylinder. Sum of all cylinder widths must be marginally smaller than the atmosphere radius.
+ static const float cylinder_delta = (atmosphere_radius - n_cylinders) / (n_cylinders - 1);
+
+ // Mie scattering coefficient and regular spectrum of mie coefficients.
+ const float mie_extinction_coeff = 2e-5f;
+ const RegularSpectrum31f mie_coeff_spectrum = RegularSpectrum31f(mie_extinction_coeff);
+
+ // Sun irradiance (W*m^-2*nm^-1) values at the top of the atmosphere.
+ // Source: https://www.nrel.gov/grid/solar-resource/spectra.html, Table SMART MODTRAN ETR Spectra.
+ const float sun_radiance[num_wavelengths] = {
+ 2.11275f, // 400nm
+ 2.58882f, // 410nm
+ 2.58291f, // 420nm
+ 2.42323f, // 430nm
+ 2.67605f, // 440nm
+ 2.96583f, // 450nm
+ 3.05454f, // 460nm
+ 3.00575f, // 470nm
+ 3.06637f, // 480nm
+ 2.88304f, // 490nm
+ 2.87121f, // 500nm
+ 2.78250f, // 510nm
+ 2.71006f, // 520nm
+ 2.72336f, // 530nm
+ 2.63613f, // 540nm
+ 2.55038f, // 550nm
+ 2.50602f, // 560nm
+ 2.53116f, // 570nm
+ 2.53559f, // 580nm
+ 2.51342f, // 590nm
+ 2.46315f, // 600nm
+ 2.41732f, // 610nm
+ 2.36853f, // 620nm
+ 2.32121f, // 630nm
+ 2.28277f, // 640nm
+ 2.23398f, // 650nm
+ 2.19702f, // 650nm
+ 2.15267f, // 670nm
+ 2.10979f, // 680nm
+ 2.07283f, // 690nm
+ 2.02404f // 700nm
+ };
+ const RegularSpectrum31f sun_radiance_spectrum = RegularSpectrum31f::from_array(sun_radiance);
+
+ // Rayleigh scattering coefficients (m^-1) from Rudolf Penndorf (1957) Table 3.
+ // Source: https://doi.org/10.1364/JOSA.47.000176
+ const float rayleigh_coeff[num_wavelengths] = {
+ 45.40e-6f, // 400nm
+ 40.98e-6f, // 410nm
+ 37.08e-6f, // 420nm
+ 33.65e-6f, // 430nm
+ 30.60e-6f, // 440nm
+ 27.89e-6f, // 450nm
+ 25.48e-6f, // 460nm
+ 23.33e-6f, // 470nm
+ 21.40e-6f, // 480nm
+ 19.66e-6f, // 490nm
+ 18.10e-6f, // 500nm
+ 16.69e-6f, // 510nm
+ 15.42e-6f, // 520nm
+ 14.26e-6f, // 530nm
+ 13.21e-6f, // 540nm
+ 12.26e-6f, // 550nm
+ 11.39e-6f, // 560nm
+ 10.60e-6f, // 570nm
+ 9.876e-6f, // 580nm
+ 9.212e-6f, // 590nm
+ 8.604e-6f, // 600nm
+ 8.045e-6f, // 610nm
+ 7.531e-6f, // 620nm
+ 7.057e-6f, // 630nm
+ 6.620e-6f, // 640nm
+ 6.217e-6f, // 650nm
+ 5.844e-6f, // 660nm
+ 5.498e-6f, // 670nm
+ 5.178e-6f, // 680nm
+ 4.881e-6f, // 690nm
+ 4.605e-6f, // 700nm
+ };
+ const RegularSpectrum31f rayleigh_coeff_spectrum = RegularSpectrum31f::from_array(rayleigh_coeff);
+
+ // Ozona absorption coefficient (m^-1).
+ // Source: https://www.iup.uni-bremen.de/gruppen/molspec/databases/referencespectra/o3spectra2011/index.html
+ const float ozone_coeff[num_wavelengths] = {
+ 3.804511196879277e-09f, // 400 nm
+ 6.913786897105462e-09f, // 410 nm
+ 1.3852765960014552e-08f, // 420 nm
+ 2.1308603627919998e-08f, // 430 nm
+ 3.974417614472733e-08f, // 440 nm
+ 5.779591314894535e-08f, // 450 nm
+ 9.191587335498181e-08f, // 460 nm
+ 1.2363721551643633e-07f, // 470 nm
+ 1.9505027060647285e-07f, // 480 nm
+ 2.2672051905767247e-07f, // 490 nm
+ 3.716605995280002e-07f, // 500 nm
+ 4.0267814468581854e-07f, // 510 nm
+ 5.364069922247275e-07f, // 520 nm
+ 6.912136535745463e-07f, // 530 nm
+ 7.745488102370914e-07f, // 540 nm
+ 8.772119777709093e-07f, // 550 nm
+ 1.0680234682312722e-06f, // 560 nm
+ 1.1695343279723626e-06f, // 570 nm
+ 1.1011384812494534e-06f, // 580 nm
+ 1.1759623019832746e-06f, // 590 nm
+ 1.2552240270210935e-06f, // 600 nm
+ 1.0772983295309093e-06f, // 610 nm
+ 9.361428617905462e-07f, // 620 nm
+ 8.052237676756349e-07f, // 630 nm
+ 6.675936847221821e-07f, // 640 nm
+ 5.619235334727269e-07f, // 650 nm
+ 4.6550674463418176e-07f, // 660 nm
+ 3.7068568738763686e-07f, // 670 nm
+ 3.0466838275272715e-07f, // 680 nm
+ 2.3788813137578206e-07f, // 690 nm
+ 1.8836707145585476e-07f, // 700 nm
+ };
+ const RegularSpectrum31f ozone_coeff_spectrum = RegularSpectrum31f::from_array(ozone_coeff);
+
+ // Precomputes g parameter determining Mie assymetricity depending on atmospheric haze condition.
+ void precompute_mie_g(float haze);
+
+ // Precomputes shell values with exponentially decreasing radius.
+ void precompute_shells();
+
+ // Precomputes optical dephts using n cylinders.
+ void precompute_optical_depths(const Vector3f& sun_dir, float air_particle_density, float dust_particle_density, float ozone_particle_density);
+
+ // Mie assymetricity value depending on atmospheric haze condition u, varies from 0.7 tp 0.85.
+ inline float mie_assymetricity(float u);
+
+ // Rayleigh phase function for a given angle (rad).
+ float rayleigh_phase(float angle);
+
+ // Mie phase function for a given angle (rad).
+ float mie_phase(float angle);
+
+ // Determines whether the light ray intersects with the earths surface.
+ bool intersects_earth(const Ray3f& ray);
+ bool intersects_earth(const Ray3f& ray, float& distance);
+
+ // Determines whether a ray is below the earths surface.
+ bool ray_inside_earth(const Ray3f& ray);
+
+ // Determines the distance a ray travels before hitting the outer point of the atmosphere.
+ float distance_to_atmosphere(const Ray3f& ray);
+
+ // Computes optical depth along a ray considering mie and rayleigh scattering.
+ sky::opticaldepth ray_optical_depth(const Ray3f& ray, float air_particle_density, float dust_particle_density, float ozone_particle_density);
+
+ // Finds best fitting optical depth from lookup table.
+ sky::opticaldepth lookup_optical_depth(const Ray3f& ray);
+
+ // Computes the irradiance spectrum of a single ray through the atmosphere, considering rayleigh and mie scattering.
+ void single_scattering(
+ const Ray3f& ray,
+ const Vector3f& sun_dir,
+ float air_particle_density,
+ float dust_particle_density,
+ float ozone_particle_density,
+ bool is_precomputed,
+ RegularSpectrum31f& spectrum);
+
+ // Returns the irradiance spectrum of the sun for rays pointing directgly at the sun.
+ bool sun_disk(
+ const Ray3f& ray,
+ float air_particle_density,
+ float dust_particle_density,
+ float ozone_particle_density,
+ float sun_radius,
+ RegularSpectrum31f& spectrum);
+
+
+}
+