diff --git a/CMakeLists.txt b/CMakeLists.txt index 73e1a79..21ca183 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,5 @@ cmake_minimum_required(VERSION 3.25) - option(APPLICATION_NAME "Application name" "Antkeeper") option(APPLICATION_VERSION "Application version string" "0.0.0") option(APPLICATION_AUTHOR "Application author" "C. J. Howard") diff --git a/src/engine/color/aces.hpp b/src/engine/color/aces.hpp index 56eed3c..f24f5b8 100644 --- a/src/engine/color/aces.hpp +++ b/src/engine/color/aces.hpp @@ -66,7 +66,7 @@ constexpr rgb::color_space ap1 * @return Saturation adjustment matrix. */ template -constexpr math::matrix adjust_saturation(T s, const math::vector3& to_y) +[[nodiscard]] constexpr math::matrix adjust_saturation(T s, const math::vector3& to_y) noexcept { const math::vector3 v = to_y * (T{1} - s); return math::matrix diff --git a/src/engine/color/cat.hpp b/src/engine/color/cat.hpp index 731f8e5..ab85005 100644 --- a/src/engine/color/cat.hpp +++ b/src/engine/color/cat.hpp @@ -81,7 +81,7 @@ constexpr math::matrix xyz_scaling = * @see http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html */ template -constexpr math::matrix matrix(const math::vector2& w0, const math::vector2& w1, const math::matrix& cone_response = bradford) +[[nodiscard]] constexpr math::matrix matrix(const math::vector2& w0, const math::vector2& w1, const math::matrix& cone_response = bradford) noexcept { // Convert CIE xy chromaticity coordinates to CIE XYZ colors const math::vector3 w0_xyz = {w0[0] / w0[1], T{1}, (T{1} - w0[0] - w0[1]) / w0[1]}; diff --git a/src/engine/color/cct.hpp b/src/engine/color/cct.hpp index ea734e7..0e85c55 100644 --- a/src/engine/color/cct.hpp +++ b/src/engine/color/cct.hpp @@ -38,7 +38,7 @@ namespace cct { * @see Krystek, M. (1985), An algorithm to calculate correlated colour temperature. Color Res. Appl., 10: 38-40. */ template -math::vector2 to_ucs(T t) +[[nodiscard]] math::vector2 to_ucs(T t) noexcept { const T tt = t * t; return math::vector2 diff --git a/src/engine/color/index.hpp b/src/engine/color/index.hpp index 0c31df9..cc7002d 100644 --- a/src/engine/color/index.hpp +++ b/src/engine/color/index.hpp @@ -34,7 +34,7 @@ namespace index { * @see Ballesteros, F. J. (2012). "New insights into black bodies". EPL 97 (2012) 34008. */ template -T bv_to_cct(T bv) +[[nodiscard]] T bv_to_cct(T bv) noexcept { return T{4600} * (T{1} / (T{0.92} * bv + T{1.7}) + T{1} / (T{0.92} * bv + T{0.62})); } diff --git a/src/engine/color/rgb.hpp b/src/engine/color/rgb.hpp index db601da..1752286 100644 --- a/src/engine/color/rgb.hpp +++ b/src/engine/color/rgb.hpp @@ -43,7 +43,7 @@ namespace rgb { * @see https://mina86.com/2019/srgb-xyz-matrix/ */ template -constexpr math::matrix to_xyz(const math::vector2& r, const math::vector2& g, const math::vector2& b, const math::vector2& w) +[[nodiscard]] constexpr math::matrix to_xyz(const math::vector2& r, const math::vector2& g, const math::vector2& b, const math::vector2& w) { const math::matrix m = { @@ -106,7 +106,17 @@ struct color_space * @param b CIE xy chromaticity coordinates of the blue primary. * @param w CIE xy chromaticity coordinates of the white point. */ - constexpr color_space(const math::vector2& r, const math::vector2& g, const math::vector2& b, const math::vector2& w, transfer_function_type eotf, transfer_function_type oetf); + constexpr color_space(const math::vector2& r, const math::vector2& g, const math::vector2& b, const math::vector2& w, transfer_function_type eotf, transfer_function_type oetf): + r(r), + g(g), + b(b), + w(w), + eotf(eotf), + oetf(oetf), + to_xyz(color::rgb::to_xyz(r, g, b, w)), + from_xyz(math::inverse(to_xyz)), + to_y{to_xyz[0][1], to_xyz[1][1], to_xyz[2][1]} + {} /** * Measures the luminance of a linear RGB color. @@ -114,28 +124,12 @@ struct color_space * @param x Linear RGB color. * @return return Luminance of @p x. */ - constexpr T luminance(const math::vector3& x) const; + [[nodiscard]] inline constexpr T luminance(const math::vector3& x) const noexcept + { + return math::dot(x, to_y); + } }; -template -constexpr color_space::color_space(const math::vector2& r, const math::vector2& g, const math::vector2& b, const math::vector2& w, transfer_function_type eotf, transfer_function_type oetf): - r(r), - g(g), - b(b), - w(w), - eotf(eotf), - oetf(oetf), - to_xyz(color::rgb::to_xyz(r, g, b, w)), - from_xyz(math::inverse(to_xyz)), - to_y{to_xyz[0][1], to_xyz[1][1], to_xyz[2][1]} -{} - -template -constexpr T color_space::luminance(const math::vector3& x) const -{ - return math::dot(x, to_y); -} - /** * Constructs a matrix which transforms a color from one RGB color space to another RGB color space. * @@ -146,7 +140,7 @@ constexpr T color_space::luminance(const math::vector3& x) const * @return Color space transformation matrix. */ template -constexpr math::matrix3x3 to_rgb(const color_space& s0, const color_space& s1, const math::matrix3x3& cone_response = color::cat::bradford) +[[nodiscard]] constexpr math::matrix3x3 to_rgb(const color_space& s0, const color_space& s1, const math::matrix3x3& cone_response = color::cat::bradford) { return s1.from_xyz * color::cat::matrix(s0.w, s1.w, cone_response) * s0.to_xyz; } diff --git a/src/engine/color/srgb.hpp b/src/engine/color/srgb.hpp index d10b8ff..2d34a5d 100644 --- a/src/engine/color/srgb.hpp +++ b/src/engine/color/srgb.hpp @@ -28,49 +28,43 @@ namespace color { /** - * Maps a non-linear sRGB signal to a linear sRGB color. + * sRGB opto-electronic transfer function (OETF). Maps a linear sRGB color to a non-linear sRGB signal. * - * @param x Non-linear sRGB signal. + * @param x Linear sRGB color. * - * @return Linear sRGB color. + * @return Non-linear sRGB signal. + * + * @see IEC 61966-2-1:1999 */ template -math::vector3 srgb_eotf(const math::vector3& x) +[[nodiscard]] math::vector3 srgb_oetf(const math::vector3& x) { auto f = [](T x) -> T { - return x < T{0.04045} ? x / T{12.92} : std::pow((x + T{0.055}) / T{1.055}, T{2.4}); + return x > T{0.0031308} ? std::pow(x, T{1.0 / 2.4}) * T{1.055} - T{0.055} : x * T{12.92}; }; - return math::vector3 - { - f(x[0]), - f(x[1]), - f(x[2]) - }; + return {f(x[0]), f(x[1]), f(x[2])}; } /** - * Maps a linear sRGB color to a non-linear sRGB signal. + * sRGB electro-optical transfer function (EOTF). Maps a non-linear sRGB signal to a linear sRGB color. * - * @param x Linear sRGB color. + * @param x Non-linear sRGB signal. * - * @return Non-linear sRGB signal. + * @return Linear sRGB color. + * + * @see IEC 61966-2-1:1999 */ template -math::vector3 srgb_oetf(const math::vector3& x) +[[nodiscard]] math::vector3 srgb_eotf(const math::vector3& x) { auto f = [](T x) -> T { - return x <= T{0.0031308} ? x * T{12.92} : std::pow(x, T{1} / T{2.4}) * T{1.055} - T{0.055}; + return x > T{0.0031308 * 12.92} ? std::pow((x + T{0.055}) / T{1.055}, T{2.4}) : x / T{12.92}; }; - return math::vector3 - { - f(x[0]), - f(x[1]), - f(x[2]) - }; + return {f(x[0]), f(x[1]), f(x[2])}; } /// sRGB color space. diff --git a/src/engine/color/ucs.hpp b/src/engine/color/ucs.hpp index d6570ee..44c0d03 100644 --- a/src/engine/color/ucs.hpp +++ b/src/engine/color/ucs.hpp @@ -35,7 +35,7 @@ namespace ucs { * @return CIE xyY color. */ template -constexpr math::vector3 to_xyy(const math::vector2& uv, T y = T{1}) +[[nodiscard]] constexpr math::vector3 to_xyy(const math::vector2& uv, T y = T{1}) noexcept { const T d = T{1} / (T{2} * uv[0] - T{8} * uv[1] + T{4}); return math::vector3{(T{3} * uv[0]) * d, (T{2} * uv[1]) * d, y}; diff --git a/src/engine/color/xyy.hpp b/src/engine/color/xyy.hpp index 48ec0fe..990d27a 100644 --- a/src/engine/color/xyy.hpp +++ b/src/engine/color/xyy.hpp @@ -34,7 +34,7 @@ namespace xyy { * @return return Luminance of @p x. */ template -inline constexpr T luminance(const math::vector3& x) +[[nodiscard]] inline constexpr T luminance(const math::vector3& x) noexcept { return x[2]; } @@ -46,7 +46,7 @@ inline constexpr T luminance(const math::vector3& x) * @return CIE 1960 UCS color. */ template -constexpr math::vector2 to_ucs(const math::vector3& x) +[[nodiscard]] constexpr math::vector2 to_ucs(const math::vector3& x) noexcept { const T d = (T{1} / (T{-2} * x[0] + T{12} * x[1] + T{3})); return math::vector2{(T{4} * x[0]) * d, (T{6} * x[1]) * d}; @@ -59,7 +59,7 @@ constexpr math::vector2 to_ucs(const math::vector3& x) * @return CIE XYZ color. */ template -constexpr math::vector3 to_xyz(const math::vector3& x) +[[nodiscard]] constexpr math::vector3 to_xyz(const math::vector3& x) noexcept { return math::vector3{(x[0] * x[2]) / x[1], x[2], ((T{1} - x[0] - x[1]) * x[2]) / x[1]}; } diff --git a/src/engine/color/xyz.hpp b/src/engine/color/xyz.hpp index c3a8f23..c6a56cd 100644 --- a/src/engine/color/xyz.hpp +++ b/src/engine/color/xyz.hpp @@ -38,7 +38,7 @@ namespace xyz { * @return return Luminance of @p x. */ template -inline constexpr T luminance(const math::vector3& x) +[[nodiscard]] inline constexpr T luminance(const math::vector3& x) noexcept { return x[1]; } @@ -50,7 +50,7 @@ inline constexpr T luminance(const math::vector3& x) * @return CIE xyY color. */ template -constexpr math::vector3 to_xyy(const math::vector3& x) +[[nodiscard]] constexpr math::vector3 to_xyy(const math::vector3& x) noexcept { const T sum = x[0] + x[1] + x[2]; return math::vector3{x[0] / sum, x[1] / sum, x[1]}; @@ -65,15 +65,15 @@ constexpr math::vector3 to_xyy(const math::vector3& x) * @see match(T) */ template -T match_x(T lambda) +[[nodiscard]] T match_x(T lambda) { - const T t0 = (lambda - T(442.0)) * ((lambda < T(442.0)) ? T(0.0624) : T(0.0374)); - const T t1 = (lambda - T(599.8)) * ((lambda < T(599.8)) ? T(0.0264) : T(0.0323)); - const T t2 = (lambda - T(501.1)) * ((lambda < T(501.1)) ? T(0.0490) : T(0.0382)); + const T t0 = (lambda - T{442.0}) * ((lambda < T{442.0}) ? T{0.0624} : T{0.0374}); + const T t1 = (lambda - T{599.8}) * ((lambda < T{599.8}) ? T{0.0264} : T{0.0323}); + const T t2 = (lambda - T{501.1}) * ((lambda < T{501.1}) ? T{0.0490} : T{0.0382}); - const T x0 = T( 0.362) * std::exp(T(-0.5) * t0 * t0); - const T x1 = T( 1.056) * std::exp(T(-0.5) * t1 * t1); - const T x2 = T(-0.065) * std::exp(T(-0.5) * t2 * t2); + const T x0 = T{ 0.362} * std::exp(T{-0.5} * t0 * t0); + const T x1 = T{ 1.056} * std::exp(T{-0.5} * t1 * t1); + const T x2 = T{-0.065} * std::exp(T{-0.5} * t2 * t2); return x0 + x1 + x2; } @@ -87,13 +87,13 @@ T match_x(T lambda) * @see match(T) */ template -T match_y(T lambda) +[[nodiscard]] T match_y(T lambda) { - const T t0 = (lambda - T(568.8)) * ((lambda < T(568.8)) ? T(0.0213) : T(0.0247)); - const T t1 = (lambda - T(530.9)) * ((lambda < T(530.9)) ? T(0.0613) : T(0.0322)); + const T t0 = (lambda - T{568.8}) * ((lambda < T{568.8}) ? T{0.0213} : T{0.0247}); + const T t1 = (lambda - T{530.9}) * ((lambda < T{530.9}) ? T{0.0613} : T{0.0322}); - const T y0 = T(0.821) * std::exp(T(-0.5) * t0 * t0); - const T y1 = T(0.286) * std::exp(T(-0.5) * t1 * t1); + const T y0 = T{0.821} * std::exp(T{-0.5} * t0 * t0); + const T y1 = T{0.286} * std::exp(T{-0.5} * t1 * t1); return y0 + y1; } @@ -107,13 +107,13 @@ T match_y(T lambda) * @see match(T) */ template -T match_z(T lambda) +[[nodiscard]] T match_z(T lambda) { - const T t0 = (lambda - T(437.0)) * ((lambda < T(437.0)) ? T(0.0845) : T(0.0278)); - const T t1 = (lambda - T(459.0)) * ((lambda < T(459.0)) ? T(0.0385) : T(0.0725)); + const T t0 = (lambda - T{437.0}) * ((lambda < T{437.0}) ? T{0.0845} : T{0.0278}); + const T t1 = (lambda - T{459.0}) * ((lambda < T{459.0}) ? T{0.0385} : T{0.0725}); - const T z0 = T(1.217) * std::exp(T(-0.5) * t0 * t0); - const T z1 = T(0.681) * std::exp(T(-0.5) * t1 * t1); + const T z0 = T{1.217} * std::exp(T{-0.5} * t0 * t0); + const T z1 = T{0.681} * std::exp(T{-0.5} * t1 * t1); return z0 + z1; } @@ -131,7 +131,7 @@ T match_z(T lambda) * @see Wyman, C., Sloan, P.J., & Shirley, P. (2013). Simple Analytic Approximations to the CIE XYZ Color Matching Functions. */ template -math::vector3 match(T lambda) +[[nodiscard]] math::vector3 match(T lambda) { return math::vector3 { diff --git a/src/engine/geom/intersection.hpp b/src/engine/geom/intersection.hpp index 7a3c21a..ff5dc2c 100644 --- a/src/engine/geom/intersection.hpp +++ b/src/engine/geom/intersection.hpp @@ -116,35 +116,24 @@ template * @param hypersphere Hypersphere. * * @return Tuple containing the distances along the ray to the first and second points of intersection, or `std::nullopt` if no intersection occurred. + * + * @see Haines, E., Günther, J., & Akenine-Möller, T. (2019). Precision improvements for ray/sphere intersection. Ray Tracing Gems: High-Quality and Real-Time Rendering with DXR and Other APIs, 87-94. */ template [[nodiscard]] std::optional> intersection(const ray& ray, const hypersphere& hypersphere) noexcept { - const math::vector displacement = ray.origin - hypersphere.center; - const T b = math::dot(displacement, ray.direction); - const T c = math::sqr_length(displacement) - hypersphere.radius * hypersphere.radius; - T h = b * b - c; + const math::vector d = ray.origin - hypersphere.center; + const T b = math::dot(d, ray.direction); + const math::vector qc = d - ray.direction * b; + const T h = hypersphere.radius * hypersphere.radius - math::dot(qc, qc); if (h < T{0}) { return std::nullopt; } - h = std::sqrt(h); - - T t0 = -b - h; - T t1 = -b + h; - if (t0 > t1) - { - std::swap(t0, t1); - } - - if (t0 < T{0}) - { - return std::nullopt; - } - - return std::tuple{t0, t1}; + const T sqrt_h = std::sqrt(h); + return std::tuple{-b - sqrt_h, -b + sqrt_h}; } /** diff --git a/src/engine/gl/framebuffer.cpp b/src/engine/gl/framebuffer.cpp index 7d19770..1640d96 100644 --- a/src/engine/gl/framebuffer.cpp +++ b/src/engine/gl/framebuffer.cpp @@ -18,7 +18,7 @@ */ #include -#include +#include #include namespace gl { @@ -31,9 +31,9 @@ static constexpr GLenum attachment_lut[] = }; framebuffer::framebuffer(int width, int height): - dimensions{width, height} + m_dimensions{width, height} { - glGenFramebuffers(1, &gl_framebuffer_id); + glGenFramebuffers(1, &m_gl_framebuffer_id); } framebuffer::framebuffer(): @@ -42,38 +42,38 @@ framebuffer::framebuffer(): framebuffer::~framebuffer() { - if (gl_framebuffer_id) + if (m_gl_framebuffer_id) { - glDeleteFramebuffers(1, &gl_framebuffer_id); + glDeleteFramebuffers(1, &m_gl_framebuffer_id); } } void framebuffer::resize(const std::array& dimensions) { - this->dimensions = dimensions; + m_dimensions = dimensions; } -void framebuffer::attach(framebuffer_attachment_type attachment_type, texture_2d* texture) +void framebuffer::attach(framebuffer_attachment_type attachment_type, texture* texture, std::uint8_t level) { - glBindFramebuffer(GL_FRAMEBUFFER, gl_framebuffer_id); + glBindFramebuffer(GL_FRAMEBUFFER, m_gl_framebuffer_id); GLenum gl_attachment = attachment_lut[static_cast(attachment_type)]; - glFramebufferTexture2D(GL_FRAMEBUFFER, gl_attachment, GL_TEXTURE_2D, texture->m_gl_texture_id, 0); + glFramebufferTexture(GL_FRAMEBUFFER, gl_attachment, texture->m_gl_texture_id, level); if (attachment_type == framebuffer_attachment_type::color) { - color_attachment = texture; + m_color_attachment = texture; } else if (attachment_type == framebuffer_attachment_type::depth) { - depth_attachment = texture; + m_depth_attachment = texture; } else if (attachment_type == framebuffer_attachment_type::stencil) { - stencil_attachment = texture; + m_stencil_attachment = texture; } - if (!color_attachment) + if (!m_color_attachment) { glDrawBuffer(GL_NONE); glReadBuffer(GL_NONE); diff --git a/src/engine/gl/framebuffer.hpp b/src/engine/gl/framebuffer.hpp index 691ce23..9814c4f 100644 --- a/src/engine/gl/framebuffer.hpp +++ b/src/engine/gl/framebuffer.hpp @@ -26,7 +26,7 @@ namespace gl { class rasterizer; -class texture_2d; +class texture; enum class framebuffer_attachment_type: std::uint8_t { @@ -59,64 +59,63 @@ public: * Attaches a color, depth, or stencil texture to the framebuffer. * * @param attachment_type Type of attachment. + * @param texture Texture to attach. + * @param level Mip level of the texture to attach. */ - void attach(framebuffer_attachment_type attachment_type, texture_2d* texture); + void attach(framebuffer_attachment_type attachment_type, texture* texture, std::uint8_t level = 0); /// Returns the dimensions of the framebuffer, in pixels. - const std::array& get_dimensions() const; + [[nodiscard]] inline const std::array& get_dimensions() const noexcept + { + return m_dimensions; + } - const texture_2d* get_color_attachment() const; - texture_2d* get_color_attachment(); - const texture_2d* get_depth_attachment() const; - texture_2d* get_depth_attachment(); - const texture_2d* get_stencil_attachment() const; - texture_2d* get_stencil_attachment(); + /// Returns the attached color texture, if any. + /// @{ + [[nodiscard]] inline const texture* get_color_attachment() const noexcept + { + return m_color_attachment; + } + [[nodiscard]] inline texture* get_color_attachment() noexcept + { + return m_color_attachment; + } + /// @} + + /// Returns the attached depth texture, if any. + /// @{ + [[nodiscard]] inline const texture* get_depth_attachment() const noexcept + { + return m_depth_attachment; + } + [[nodiscard]] inline texture* get_depth_attachment() noexcept + { + return m_depth_attachment; + } + /// @} + + /// Returns the attached stencil texture, if any. + /// @{ + [[nodiscard]] inline const texture* get_stencil_attachment() const noexcept + { + return m_stencil_attachment; + } + [[nodiscard]] inline texture* get_stencil_attachment() noexcept + { + return m_stencil_attachment; + } + /// @} private: friend class rasterizer; - unsigned int gl_framebuffer_id{0}; - std::array dimensions{0, 0}; - texture_2d* color_attachment{nullptr}; - texture_2d* depth_attachment{nullptr}; - texture_2d* stencil_attachment{nullptr}; + unsigned int m_gl_framebuffer_id{0}; + std::array m_dimensions{0, 0}; + texture* m_color_attachment{nullptr}; + texture* m_depth_attachment{nullptr}; + texture* m_stencil_attachment{nullptr}; }; -inline const std::array& framebuffer::get_dimensions() const -{ - return dimensions; -} - -inline const texture_2d* framebuffer::get_color_attachment() const -{ - return color_attachment; -} - -inline texture_2d* framebuffer::get_color_attachment() -{ - return color_attachment; -} - -inline const texture_2d* framebuffer::get_depth_attachment() const -{ - return depth_attachment; -} - -inline texture_2d* framebuffer::get_depth_attachment() -{ - return depth_attachment; -} - -inline const texture_2d* framebuffer::get_stencil_attachment() const -{ - return stencil_attachment; -} - -inline texture_2d* framebuffer::get_stencil_attachment() -{ - return stencil_attachment; -} - } // namespace gl #endif // ANTKEEPER_GL_FRAMEBUFFER_HPP diff --git a/src/engine/gl/rasterizer.cpp b/src/engine/gl/rasterizer.cpp index 7055b66..2ad63cd 100644 --- a/src/engine/gl/rasterizer.cpp +++ b/src/engine/gl/rasterizer.cpp @@ -57,11 +57,13 @@ rasterizer::rasterizer(): // Setup default framebuffer default_framebuffer = std::make_unique(); - default_framebuffer->gl_framebuffer_id = 0; - default_framebuffer->dimensions = {scissor_box[2], scissor_box[3]}; + default_framebuffer->m_gl_framebuffer_id = 0; + default_framebuffer->m_dimensions = {scissor_box[2], scissor_box[3]}; // Bind default framebuffer bound_framebuffer = default_framebuffer.get(); + + glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); } rasterizer::~rasterizer() @@ -69,14 +71,14 @@ rasterizer::~rasterizer() void rasterizer::context_resized(int width, int height) { - default_framebuffer->dimensions = {width, height}; + default_framebuffer->m_dimensions = {width, height}; } void rasterizer::use_framebuffer(const gl::framebuffer& framebuffer) { if (bound_framebuffer != &framebuffer) { - glBindFramebuffer(GL_FRAMEBUFFER, framebuffer.gl_framebuffer_id); + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer.m_gl_framebuffer_id); bound_framebuffer = &framebuffer; } } diff --git a/src/engine/gl/texture.cpp b/src/engine/gl/texture.cpp index 676d683..5fb620a 100644 --- a/src/engine/gl/texture.cpp +++ b/src/engine/gl/texture.cpp @@ -149,18 +149,69 @@ texture::~texture() glDeleteTextures(1, &m_gl_texture_id); } +void texture::read(std::span data, gl::pixel_type type, gl::pixel_format format, std::uint8_t level) const +{ + const GLenum gl_format = pixel_format_lut[std::to_underlying(format)]; + const GLenum gl_type = pixel_type_lut[std::to_underlying(type)]; + + glBindTexture(m_gl_texture_target, m_gl_texture_id); + glGetTexImage(m_gl_texture_target, static_cast(level), gl_format, gl_type, data.data()); +} + void texture::set_filters(texture_min_filter min_filter, texture_mag_filter mag_filter) { m_filters = {min_filter, mag_filter}; - GLenum gl_min_filter = min_filter_lut[std::to_underlying(min_filter)]; - GLenum gl_mag_filter = mag_filter_lut[std::to_underlying(mag_filter)]; + const GLenum gl_min_filter = min_filter_lut[std::to_underlying(min_filter)]; + const GLenum gl_mag_filter = mag_filter_lut[std::to_underlying(mag_filter)]; + + glBindTexture(m_gl_texture_target, m_gl_texture_id); + glTexParameteri(m_gl_texture_target, GL_TEXTURE_MIN_FILTER, gl_min_filter); + glTexParameteri(m_gl_texture_target, GL_TEXTURE_MAG_FILTER, gl_mag_filter); +} + +void texture::set_min_filter(texture_min_filter filter) +{ + const GLenum gl_min_filter = min_filter_lut[std::to_underlying(filter)]; glBindTexture(m_gl_texture_target, m_gl_texture_id); glTexParameteri(m_gl_texture_target, GL_TEXTURE_MIN_FILTER, gl_min_filter); +} + +void texture::set_mag_filter(texture_mag_filter filter) +{ + const GLenum gl_mag_filter = mag_filter_lut[std::to_underlying(filter)]; + + glBindTexture(m_gl_texture_target, m_gl_texture_id); glTexParameteri(m_gl_texture_target, GL_TEXTURE_MAG_FILTER, gl_mag_filter); } +void texture::set_base_level(std::uint8_t level) +{ + m_base_level = level; + + glBindTexture(m_gl_texture_target, m_gl_texture_id); + glTexParameteri(m_gl_texture_target, GL_TEXTURE_BASE_LEVEL, static_cast(m_base_level)); +} + +void texture::set_max_level(std::uint8_t level) +{ + m_max_level = level; + + glBindTexture(m_gl_texture_target, m_gl_texture_id); + glTexParameteri(m_gl_texture_target, GL_TEXTURE_MAX_LEVEL, static_cast(m_max_level)); +} + +void texture::set_mipmap_range(std::uint8_t base_level, std::uint8_t max_level) +{ + m_base_level = base_level; + m_max_level = max_level; + + glBindTexture(m_gl_texture_target, m_gl_texture_id); + glTexParameteri(m_gl_texture_target, GL_TEXTURE_BASE_LEVEL, static_cast(m_base_level)); + glTexParameteri(m_gl_texture_target, GL_TEXTURE_MAX_LEVEL, static_cast(m_max_level)); +} + void texture::set_max_anisotropy(float anisotropy) { m_max_anisotropy = std::max(0.0f, std::min(1.0f, anisotropy)); @@ -273,6 +324,7 @@ void texture::resize(std::uint16_t width, std::uint16_t height, std::uint16_t de } glGenerateMipmap(m_gl_texture_target); + glTexParameteriv(m_gl_texture_target, GL_TEXTURE_SWIZZLE_RGBA, gl_swizzle_mask); /// @TODO: remove this @@ -300,6 +352,16 @@ void texture::update_cube_faces(unsigned int gl_internal_format, unsigned int gl const auto layout = texture_cube::infer_cube_map_layout(width, height); const auto face_size = texture_cube::infer_cube_map_face_size(layout, width, height); + if (!data) + { + for (int i = 0; i < 6; ++i) + { + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl_internal_format, face_size, face_size, 0, gl_format, gl_type, nullptr); + } + + return; + } + std::size_t channel_count = 0; switch (m_pixel_format) { diff --git a/src/engine/gl/texture.hpp b/src/engine/gl/texture.hpp index 4a1c581..039661b 100644 --- a/src/engine/gl/texture.hpp +++ b/src/engine/gl/texture.hpp @@ -29,6 +29,7 @@ #include #include #include +#include #include namespace gl { @@ -51,13 +52,59 @@ public: virtual ~texture(); /** - * Sets the texture filtering modes. + * Reads texture pixel data from the GPU. + * + * @param[out] data Pixel data buffer. + * @param[in] type Returned pixel component data type. + * @param[in] format Returned pixel format. + * @param[in] level Mip level to read. + */ + void read(std::span data, gl::pixel_type type, gl::pixel_format format, std::uint8_t level = 0) const; + + /** + * Sets the texture filter modes. * * @param min_filter Texture minification filter mode. * @param mag_filter Texture magnification filter mode. */ void set_filters(texture_min_filter min_filter, texture_mag_filter mag_filter); + /** + * Sets the texture minification filter mode. + * + * @param filter Texture minification filter mode. + */ + void set_min_filter(texture_min_filter filter); + + /** + * Sets the texture magnification filter mode. + * + * @param filter Texture magnification filter mode. + */ + void set_mag_filter(texture_mag_filter filter); + + /** + * Sets the index of lowest mipmap level. + * + * @param level Index of the lowest mipmap level. + */ + void set_base_level(std::uint8_t level); + + /** + * Sets the index of highest mipmap level. + * + * @param level Index of the highest mipmap level. + */ + void set_max_level(std::uint8_t level); + + /** + * Sets the range of mipmap levels. + * + * @param base Index of the lowest mipmap level + * @param max Index of the highest mipmap level. + */ + void set_mipmap_range(std::uint8_t base_level, std::uint8_t max_level); + /** * Sets the maximum anisotropy. * @@ -122,6 +169,18 @@ public: return m_filters; } + /// Returns the index of the lowest mipmap level. + [[nodiscard]] inline std::uint8_t get_base_level() const noexcept + { + return m_base_level; + } + + /// Returns the index of the highest mipmap level. + [[nodiscard]] inline std::uint8_t get_max_level() const noexcept + { + return m_max_level; + } + /// Returns the maximum anisotropy. [[nodiscard]] inline float get_max_anisotropy() const noexcept { @@ -197,6 +256,8 @@ private: gl::color_space m_color_space{}; std::array m_wrapping{texture_wrapping::repeat, texture_wrapping::repeat, texture_wrapping::repeat}; std::tuple m_filters{texture_min_filter::linear_mipmap_linear, texture_mag_filter::linear}; + std::uint8_t m_base_level{}; + std::uint8_t m_max_level{255}; float m_max_anisotropy{}; }; diff --git a/src/engine/physics/gas/atmosphere.hpp b/src/engine/physics/gas/atmosphere.hpp index ed518f0..2d50b1d 100644 --- a/src/engine/physics/gas/atmosphere.hpp +++ b/src/engine/physics/gas/atmosphere.hpp @@ -239,7 +239,7 @@ namespace density { template T triangular(T d0, T z, T a, T b, T c) { - return d0 * max(T(0), max(T(0), c - z) / (a - c) - max(T(0), z - c) / (b - c) + T(1)); + return d0 * std::max(T(0), std::max(T(0), c - z) / (a - c) - std::max(T(0), z - c) / (b - c) + T(1)); } } // namespace density diff --git a/src/engine/render/passes/final-pass.cpp b/src/engine/render/passes/final-pass.cpp index 7ea52ca..c763dcc 100644 --- a/src/engine/render/passes/final-pass.cpp +++ b/src/engine/render/passes/final-pass.cpp @@ -99,6 +99,9 @@ void final_pass::render(render::context& ctx) { command(); } + + // Increment current frame + ++frame; } void final_pass::set_color_texture(const gl::texture_2d* texture) @@ -187,6 +190,10 @@ void final_pass::rebuild_command_buffer() { command_buffer.emplace_back([&, var](){var->update(time);}); } + if (const auto frame_var = shader_program->variable("frame")) + { + command_buffer.emplace_back([&, frame_var](){frame_var->update(frame);}); + } command_buffer.emplace_back ( diff --git a/src/engine/render/passes/final-pass.hpp b/src/engine/render/passes/final-pass.hpp index 340601c..b3a6da1 100644 --- a/src/engine/render/passes/final-pass.hpp +++ b/src/engine/render/passes/final-pass.hpp @@ -62,6 +62,7 @@ private: float blue_noise_scale; float2 resolution; float time; + int frame{}; std::vector> command_buffer; }; diff --git a/src/engine/render/passes/ground-pass.cpp b/src/engine/render/passes/ground-pass.cpp index 4c46e1f..56fc81e 100644 --- a/src/engine/render/passes/ground-pass.cpp +++ b/src/engine/render/passes/ground-pass.cpp @@ -37,7 +37,6 @@ #include #include #include -#include #include #include #include @@ -92,7 +91,6 @@ void ground_pass::render(render::context& ctx) const float4x4& view_projection = ctx.view_projection; float4x4 model_view_projection = projection * model_view; - float3 ambient_light_color = {0.0f, 0.0f, 0.0f}; float3 directional_light_color = {0.0f, 0.0f, 0.0f}; float3 directional_light_direction = {0.0f, 0.0f, 0.0f}; @@ -107,14 +105,6 @@ void ground_pass::render(render::context& ctx) const scene::light* light = static_cast(object); switch (light->get_light_type()) { - // Add ambient light - case scene::light_type::ambient: - { - // Pre-expose light - ambient_light_color = light->get_scaled_color_tween().interpolate(ctx.alpha) * ctx.exposure; - break; - } - // Add directional light case scene::light_type::directional: { @@ -149,8 +139,6 @@ void ground_pass::render(render::context& ctx) directional_light_colors_var->update(0, &directional_light_color, 1); if (directional_light_directions_var) directional_light_directions_var->update(0, &directional_light_direction, 1); - if (ambient_light_colors_var) - ambient_light_colors_var->update(0, &ambient_light_color, 1); ground_material->update(ctx.alpha); @@ -186,7 +174,6 @@ void ground_pass::set_ground_model(std::shared_ptr model) camera_position_var = shader_program->get_var("camera.position"); directional_light_colors_var = shader_program->get_var("directional_light_colors"); directional_light_directions_var = shader_program->get_var("directional_light_directions"); - ambient_light_colors_var = shader_program->get_var("ambient_light_colors"); } } } diff --git a/src/engine/render/passes/ground-pass.hpp b/src/engine/render/passes/ground-pass.hpp index a183fed..0318382 100644 --- a/src/engine/render/passes/ground-pass.hpp +++ b/src/engine/render/passes/ground-pass.hpp @@ -55,7 +55,6 @@ private: const gl::shader_variable* camera_position_var; const gl::shader_variable* directional_light_colors_var; const gl::shader_variable* directional_light_directions_var; - const gl::shader_variable* ambient_light_colors_var; std::shared_ptr ground_model; const material* ground_material; diff --git a/src/engine/render/passes/material-pass.cpp b/src/engine/render/passes/material-pass.cpp index 29dd418..c558caf 100644 --- a/src/engine/render/passes/material-pass.cpp +++ b/src/engine/render/passes/material-pass.cpp @@ -38,11 +38,11 @@ #include #include #include -#include #include #include #include #include +#include #include #include #include @@ -122,6 +122,9 @@ material_pass::material_pass(gl::rasterizer* rasterizer, const gl::framebuffer* // Load LTC LUT textures ltc_lut_1 = resource_manager->load("ltc-lut-1.tex"); ltc_lut_2 = resource_manager->load("ltc-lut-2.tex"); + + // Load IBL BRDF LUT texture + brdf_lut = resource_manager->load("brdf-lut.tex"); } void material_pass::render(render::context& ctx) @@ -315,36 +318,32 @@ void material_pass::evaluate_camera(const render::context& ctx) void material_pass::evaluate_lighting(const render::context& ctx) { // Reset light and shadow counts - ambient_light_count = 0; - point_light_count = 0; + light_probe_count = 0; directional_light_count = 0; directional_shadow_count = 0; spot_light_count = 0; point_light_count = 0; rectangle_light_count = 0; + const auto& light_probes = ctx.collection->get_objects(scene::light_probe::object_type_id); + for (const scene::object_base* object: light_probes) + { + if (!light_probe_count) + { + const scene::light_probe& light_probe = static_cast(*object); + ++light_probe_count; + light_probe_luminance_texture = light_probe.get_luminance_texture().get(); + light_probe_illuminance_texture = light_probe.get_illuminance_texture().get(); + } + } + const auto& lights = ctx.collection->get_objects(scene::light::object_type_id); for (const scene::object_base* object: lights) { const scene::light& light = static_cast(*object); switch (light.get_light_type()) - { - // Add ambient light - case scene::light_type::ambient: - { - const std::size_t index = ambient_light_count; - - ++ambient_light_count; - if (ambient_light_count > ambient_light_colors.size()) - { - ambient_light_colors.resize(ambient_light_count); - } - - ambient_light_colors[index] = static_cast(light).get_colored_illuminance() * ctx.camera->get_exposure_normalization(); - break; - } - + { // Add directional light case scene::light_type::directional: { @@ -376,7 +375,7 @@ void material_pass::evaluate_lighting(const render::context& ctx) directional_shadow_matrices.resize(directional_shadow_count); } - directional_shadow_maps[index] = directional_light.get_shadow_framebuffer()->get_depth_attachment(); + directional_shadow_maps[index] = static_cast(directional_light.get_shadow_framebuffer()->get_depth_attachment()); directional_shadow_biases[index] = directional_light.get_shadow_bias(); directional_shadow_splits[index] = &directional_light.get_shadow_cascade_distances(); directional_shadow_matrices[index] = &directional_light.get_shadow_cascade_matrices(); @@ -458,12 +457,12 @@ void material_pass::evaluate_lighting(const render::context& ctx) } // Generate lighting state hash - lighting_state_hash = std::hash{}(ambient_light_count); + lighting_state_hash = std::hash{}(light_probe_count); lighting_state_hash = hash::combine(lighting_state_hash, std::hash{}(directional_light_count)); lighting_state_hash = hash::combine(lighting_state_hash, std::hash{}(directional_shadow_count)); lighting_state_hash = hash::combine(lighting_state_hash, std::hash{}(point_light_count)); lighting_state_hash = hash::combine(lighting_state_hash, std::hash{}(spot_light_count)); - lighting_state_hash = hash::combine(lighting_state_hash, std::hash{}(point_light_count)); + lighting_state_hash = hash::combine(lighting_state_hash, std::hash{}(rectangle_light_count)); } void material_pass::evaluate_misc(const render::context& ctx) @@ -498,7 +497,7 @@ std::unique_ptr material_pass::generate_shader_program(const definitions["FRAGMENT_OUTPUT_COLOR"] = std::to_string(0); - definitions["AMBIENT_LIGHT_COUNT"] = std::to_string(ambient_light_count); + definitions["LIGHT_PROBE_COUNT"] = std::to_string(light_probe_count); definitions["DIRECTIONAL_LIGHT_COUNT"] = std::to_string(directional_light_count); definitions["DIRECTIONAL_SHADOW_COUNT"] = std::to_string(directional_shadow_count); definitions["POINT_LIGHT_COUNT"] = std::to_string(point_light_count); @@ -547,6 +546,7 @@ void material_pass::build_shader_command_buffer(std::vectorupdate(clip_depth);}); } + // LTC variables if (auto ltc_lut_1_var = shader_program.variable("ltc_lut_1")) { if (auto ltc_lut_2_var = shader_program.variable("ltc_lut_2")) @@ -562,12 +562,41 @@ void material_pass::build_shader_command_buffer(std::vectorupdate(*brdf_lut); + } + ); + } + + // Update light probe variables + if (light_probe_count) { - if (auto ambient_light_colors_var = shader_program.variable("ambient_light_colors")) + if (auto light_probe_luminance_texture_var = shader_program.variable("light_probe_luminance_texture")) { - command_buffer.emplace_back([&, ambient_light_colors_var](){ambient_light_colors_var->update(std::span{ambient_light_colors.data(), ambient_light_count});}); + command_buffer.emplace_back + ( + [&, light_probe_luminance_texture_var]() + { + light_probe_luminance_texture_var->update(*light_probe_luminance_texture); + } + ); + } + + if (auto light_probe_illuminance_texture_var = shader_program.variable("light_probe_illuminance_texture")) + { + command_buffer.emplace_back + ( + [&, light_probe_illuminance_texture_var]() + { + light_probe_illuminance_texture_var->update(*light_probe_illuminance_texture); + } + ); } } diff --git a/src/engine/render/passes/material-pass.hpp b/src/engine/render/passes/material-pass.hpp index 877d264..f7c8790 100644 --- a/src/engine/render/passes/material-pass.hpp +++ b/src/engine/render/passes/material-pass.hpp @@ -97,9 +97,10 @@ private: float2 clip_depth; float log_depth_coef; - // Ambient lights - std::vector ambient_light_colors; - std::size_t ambient_light_count; + // Light probes + const gl::texture_cube* light_probe_luminance_texture{}; + const gl::texture_1d* light_probe_illuminance_texture{}; + std::size_t light_probe_count; // Point lights std::vector point_light_colors; @@ -134,6 +135,9 @@ private: std::shared_ptr ltc_lut_1; std::shared_ptr ltc_lut_2; + // IBL + std::shared_ptr brdf_lut; + // Misc float time; float timestep; diff --git a/src/engine/render/passes/sky-pass.cpp b/src/engine/render/passes/sky-pass.cpp index c20dd12..4b3eed4 100644 --- a/src/engine/render/passes/sky-pass.cpp +++ b/src/engine/render/passes/sky-pass.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -54,7 +55,6 @@ sky_pass::sky_pass(gl::rasterizer* rasterizer, const gl::framebuffer* framebuffe sky_model(nullptr), sky_material(nullptr), sky_model_vao(nullptr), - sky_lut_shader_program(nullptr), moon_model(nullptr), moon_model_vao(nullptr), moon_material(nullptr), @@ -77,7 +77,6 @@ sky_pass::sky_pass(gl::rasterizer* rasterizer, const gl::framebuffer* framebuffe moon_planetlight_direction_tween(float3{0, 0, 0}, math::lerp), moon_planetlight_illuminance_tween(float3{0, 0, 0}, math::lerp), moon_illuminance_tween(float3{0.0f, 0.0f, 0.0f}, math::lerp), - render_transmittance_lut(false), magnification(1.0f) { // Build quad VBO and VAO @@ -86,8 +85,6 @@ sky_pass::sky_pass(gl::rasterizer* rasterizer, const gl::framebuffer* framebuffe {-1.0f, 1.0f}, {-1.0f, -1.0f}, { 1.0f, 1.0f}, - { 1.0f, 1.0f}, - {-1.0f, -1.0f}, { 1.0f, -1.0f} }; @@ -109,63 +106,97 @@ sky_pass::sky_pass(gl::rasterizer* rasterizer, const gl::framebuffer* framebuffe // Bind vertex attributes to VAO quad_vao->bind(render::vertex_attribute::position, position_attribute); - // Create transmittance LUT texture and framebuffer (32F color, no depth) - transmittance_lut_texture = std::make_unique(256, 64, gl::pixel_type::float_32, gl::pixel_format::rgb); - transmittance_lut_texture->set_wrapping(gl::texture_wrapping::extend, gl::texture_wrapping::extend); - transmittance_lut_texture->set_filters(gl::texture_min_filter::linear, gl::texture_mag_filter::linear); - transmittance_lut_texture->set_max_anisotropy(0.0f); - transmittance_lut_framebuffer = std::make_unique(transmittance_lut_texture->get_width(), transmittance_lut_texture->get_height()); - transmittance_lut_framebuffer->attach(gl::framebuffer_attachment_type::color, transmittance_lut_texture.get()); - transmittance_lut_resolution = {static_cast(transmittance_lut_texture->get_width()), static_cast(transmittance_lut_texture->get_height())}; + // Transmittance LUT + { + // Construct transmittance LUT texture + m_transmittance_lut_texture = std::make_unique(m_transmittance_lut_resolution.x(), m_transmittance_lut_resolution.y(), gl::pixel_type::float_32, gl::pixel_format::rgb); + m_transmittance_lut_texture->set_wrapping(gl::texture_wrapping::extend, gl::texture_wrapping::extend); + m_transmittance_lut_texture->set_filters(gl::texture_min_filter::linear, gl::texture_mag_filter::linear); + m_transmittance_lut_texture->set_max_anisotropy(0.0f); + + // Construct transmittance LUT framebuffer and attach texture + m_transmittance_lut_framebuffer = std::make_unique(m_transmittance_lut_resolution.x(), m_transmittance_lut_resolution.y()); + m_transmittance_lut_framebuffer->attach(gl::framebuffer_attachment_type::color, m_transmittance_lut_texture.get()); + + // Load transmittance LUT shader template + m_transmittance_lut_shader_template = resource_manager->load("sky-transmittance-lut.glsl"); + + // Build transmittance LUT shader program + rebuild_transmittance_lut_shader_program(); + + // Build transmittance LUT command buffer + rebuild_transmittance_lut_command_buffer(); + } - // Load transmittance LUT shader template - transmittance_lut_shader_template = resource_manager->load("transmittance-lut.glsl"); + // Multiscattering LUT + { + // Construct multiscattering LUT texture + m_multiscattering_lut_texture = std::make_unique(m_multiscattering_lut_resolution.x(), m_multiscattering_lut_resolution.y(), gl::pixel_type::float_32, gl::pixel_format::rgb); + m_multiscattering_lut_texture->set_wrapping(gl::texture_wrapping::extend, gl::texture_wrapping::extend); + m_multiscattering_lut_texture->set_filters(gl::texture_min_filter::linear, gl::texture_mag_filter::linear); + m_multiscattering_lut_texture->set_max_anisotropy(0.0f); + + // Construct multiscattering LUT framebuffer and attach texture + m_multiscattering_lut_framebuffer = std::make_unique(m_multiscattering_lut_resolution.x(), m_multiscattering_lut_resolution.y()); + m_multiscattering_lut_framebuffer->attach(gl::framebuffer_attachment_type::color, m_multiscattering_lut_texture.get()); + + // Load multiscattering LUT shader template + m_multiscattering_lut_shader_template = resource_manager->load("sky-multiscattering-lut.glsl"); + + // Build multiscattering LUT shader program + rebuild_multiscattering_lut_shader_program(); + + // Build multiscattering LUT command buffer + rebuild_multiscattering_lut_command_buffer(); + } - // Build transmittance LUT shader program - transmittance_lut_shader_program = transmittance_lut_shader_template->build - ( - { - {"TRANSMITTANCE_LUT_SAMPLES", std::to_string(40)} - } - ); - if (!transmittance_lut_shader_program->linked()) + // Luminance LUT { - debug::log::error("Failed to build transmittance LUT shader program: {}", transmittance_lut_shader_program->info()); - debug::log::warning("{}", transmittance_lut_shader_template->configure(gl::shader_stage::vertex)); + // Construct luminance LUT texture + m_luminance_lut_texture = std::make_unique(m_luminance_lut_resolution.x(), m_luminance_lut_resolution.y(), gl::pixel_type::float_32, gl::pixel_format::rgb); + m_luminance_lut_texture->set_wrapping(gl::texture_wrapping::extend, gl::texture_wrapping::extend); + m_luminance_lut_texture->set_filters(gl::texture_min_filter::linear, gl::texture_mag_filter::linear); + m_luminance_lut_texture->set_max_anisotropy(0.0f); + + // Construct luminance LUT framebuffer and attach texture + m_luminance_lut_framebuffer = std::make_unique(m_luminance_lut_resolution.x(), m_luminance_lut_resolution.y()); + m_luminance_lut_framebuffer->attach(gl::framebuffer_attachment_type::color, m_luminance_lut_texture.get()); + + // Load luminance LUT shader template + m_luminance_lut_shader_template = resource_manager->load("sky-luminance-lut.glsl"); + + // Build luminance LUT shader program + rebuild_luminance_lut_shader_program(); + + // Build luminance LUT command buffer + rebuild_luminance_lut_command_buffer(); } - // Build transmittance LUT command buffer - rebuild_transmittance_lut_command_buffer(); + // Load sky probe shader template + m_sky_probe_shader_template = resource_manager->load("sky-probe.glsl"); - // Create sky LUT texture and framebuffer (32F color, no depth) - int sky_lut_width = 200; - int sky_lut_height = 100; - sky_lut_resolution = {static_cast(sky_lut_width), static_cast(sky_lut_height)}; - sky_lut_texture = std::make_unique(sky_lut_width, sky_lut_height, gl::pixel_type::float_32, gl::pixel_format::rgb); - sky_lut_texture->set_wrapping(gl::texture_wrapping::extend, gl::texture_wrapping::extend); - sky_lut_texture->set_filters(gl::texture_min_filter::linear, gl::texture_mag_filter::linear); - sky_lut_texture->set_max_anisotropy(0.0f); - sky_lut_framebuffer = std::make_unique(sky_lut_width, sky_lut_height); - sky_lut_framebuffer->attach(gl::framebuffer_attachment_type::color, sky_lut_texture.get()); + // Build sky probe shader program + m_sky_probe_shader_program = m_sky_probe_shader_template->build({}); + if (!m_sky_probe_shader_program->linked()) + { + debug::log::error("Failed to build sky probe shader program: {}", m_sky_probe_shader_program->info()); + debug::log::warning("{}", m_sky_probe_shader_template->configure(gl::shader_stage::vertex)); + } - // Load sky LUT shader template - sky_lut_shader_template = resource_manager->load("sky-illuminance-lut.glsl"); + // Load cubemap downsample shader template + m_cubemap_downsample_shader_template = resource_manager->load("cubemap-downsample.glsl"); - // Build sky LUT shader program - sky_lut_shader_program = sky_lut_shader_template->build - ( - { - {"SKY_ILLUMINANCE_SAMPLES", std::to_string(30)} - } - ); - if (!sky_lut_shader_program->linked()) + // Build cubemap downsample shader program + m_cubemap_downsample_shader_program = m_cubemap_downsample_shader_template->build({}); + if (!m_cubemap_downsample_shader_program->linked()) { - debug::log::error("Failed to by sky LUT shader program: {}", sky_lut_shader_program->info()); - debug::log::warning("{}", sky_lut_shader_template->configure(gl::shader_stage::vertex)); + debug::log::error("Failed to build cubemap downsample shader program: {}", m_cubemap_downsample_shader_program->info()); + debug::log::warning("{}", m_cubemap_downsample_shader_template->configure(gl::shader_stage::vertex)); } - // Build sky LUT command buffer - rebuild_sky_lut_command_buffer(); + // Load moon textures + m_moon_albedo_map = resource_manager->load("moon-albedo.tex"); + m_moon_normal_map = resource_manager->load("moon-normal.tex"); } void sky_pass::render(render::context& ctx) @@ -176,17 +207,26 @@ void sky_pass::render(render::context& ctx) glEnable(GL_CULL_FACE); glCullFace(GL_BACK); - // Render transmittance LUT if transmittance parameters have been altered. - if (render_transmittance_lut) + // Render transmittance LUT (if parameters have changed) + if (m_render_transmittance_lut) + { + for (const auto& command: m_transmittance_lut_command_buffer) + { + command(); + } + + m_render_transmittance_lut = false; + } + + // Render multiscattering LUT (if parameters have changed) + if (m_render_multiscattering_lut) { - // Render transmittance LUT - for (const auto& command: transmittance_lut_command_buffer) + for (const auto& command: m_multiscattering_lut_command_buffer) { command(); } - // Don't render transmittance LUT next frame unless parameters have changed. - render_transmittance_lut = false; + m_render_multiscattering_lut = false; } // Construct matrices @@ -198,6 +238,7 @@ void sky_pass::render(render::context& ctx) const float4x4& projection = camera.get_projection(); float4x4 view_projection = projection * view; float4x4 model_view_projection = projection * model_view; + camera_exposure = camera.get_exposure_normalization(); // Interpolate observer position observer_position = observer_position_tween.interpolate(ctx.alpha); @@ -224,18 +265,30 @@ void sky_pass::render(render::context& ctx) float sun_y = color::aces::ap1.luminance(sun_transmitted_illuminance); float moon_y = color::aces::ap1.luminance(moon_transmitted_illuminance); - dominant_light_direction = (sun_y > moon_y) ? sun_direction : moon_direction; - dominant_light_illuminance = (sun_y > moon_y) ? sun_illuminance : moon_illuminance; - if (moon_y > sun_y) + // if (math::max(sun_illuminance) > math::max(moon_illuminance)) + // { + dominant_light_direction = sun_direction; + dominant_light_illuminance = sun_illuminance; + // } + // else + // { + // dominant_light_direction = moon_direction; + // dominant_light_illuminance = moon_illuminance; + // } + + + // Render luminance LUT + // if (m_render_luminance_lut) { - sun_luminance *= 0.0f; + for (const auto& command: m_luminance_lut_command_buffer) + { + command(); + } } - camera_exposure = camera.get_exposure_normalization(); - - // Render sky illuminance LUT - for (const auto& command: sky_lut_command_buffer) + // Render sky probe + for (const auto& command: m_sky_probe_command_buffer) { command(); } @@ -267,10 +320,14 @@ void sky_pass::render(render::context& ctx) atmosphere_radii_var->update(atmosphere_radii); if (observer_position_var) observer_position_var->update(observer_position); - if (sky_illuminance_lut_var) - sky_illuminance_lut_var->update(*sky_lut_texture); - if (sky_illuminance_lut_resolution_var) - sky_illuminance_lut_resolution_var->update(sky_lut_resolution); + if (sky_transmittance_lut_var) + sky_transmittance_lut_var->update(*m_transmittance_lut_texture); + if (sky_transmittance_lut_resolution_var) + sky_transmittance_lut_resolution_var->update(math::vector2(m_transmittance_lut_resolution)); + if (sky_luminance_lut_var) + sky_luminance_lut_var->update(*m_luminance_lut_texture); + if (sky_luminance_lut_resolution_var) + sky_luminance_lut_resolution_var->update(math::vector2(m_luminance_lut_resolution)); //sky_material->update(ctx.alpha); @@ -278,33 +335,14 @@ void sky_pass::render(render::context& ctx) } glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE); - //glBlendFunc(GL_ONE, GL_ONE); + // glBlendFunc(GL_SRC_ALPHA, GL_ONE); + glBlendFunc(GL_ONE, GL_ONE); - // Draw stars - if (star_shader_program) - { - float star_distance = (camera.get_clip_near() + camera.get_clip_far()) * 0.5f; - - model = float4x4(float3x3(icrf_to_eus.r)); - model = math::scale(model, {star_distance, star_distance, star_distance}); - - model_view = view * model; - - rasterizer->use_program(*star_shader_program); - if (star_model_view_var) - star_model_view_var->update(model_view); - if (star_projection_var) - star_projection_var->update(projection); - if (star_distance_var) - star_distance_var->update(star_distance); - if (star_exposure_var) - star_exposure_var->update(camera_exposure); - - //star_material->update(ctx.alpha); - - rasterizer->draw_arrays(*stars_model_vao, stars_model_drawing_mode, stars_model_start_index, stars_model_index_count); - } + // Flag moon pixels in stencil buffer + glEnable(GL_STENCIL_TEST); + glStencilMask(0xff); + glStencilFunc(GL_ALWAYS, 1, 0xff); + glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); // Draw moon model //if (moon_position.y() >= -moon_angular_radius) @@ -338,10 +376,150 @@ void sky_pass::render(render::context& ctx) moon_planetlight_direction_var->update(math::normalize(moon_planetlight_direction_tween.interpolate(ctx.alpha))); if (moon_planetlight_illuminance_var) moon_planetlight_illuminance_var->update(moon_planetlight_illuminance_tween.interpolate(ctx.alpha) * camera_exposure); + if (moon_albedo_map_var && m_moon_albedo_map) + moon_albedo_map_var->update(*m_moon_albedo_map); + if (moon_normal_map_var && m_moon_normal_map) + moon_normal_map_var->update(*m_moon_normal_map); + if (moon_observer_position_var) + moon_observer_position_var->update(observer_position); + if (moon_sky_transmittance_lut_var) + moon_sky_transmittance_lut_var->update(*m_transmittance_lut_texture); + if (moon_atmosphere_radii_var) + moon_atmosphere_radii_var->update(atmosphere_radii); //moon_material->update(ctx.alpha); rasterizer->draw_arrays(*moon_model_vao, moon_model_drawing_mode, moon_model_start_index, moon_model_index_count); } + + // Prevent stars from being drawn in front of the moon + glStencilMask(0x00); + glStencilFunc(GL_NOTEQUAL, 1, 0xff); + + // Draw stars + if (star_shader_program) + { + float star_distance = (camera.get_clip_near() + camera.get_clip_far()) * 0.5f; + + model = float4x4(float3x3(icrf_to_eus.r)); + model = math::scale(model, {star_distance, star_distance, star_distance}); + + model_view_projection = view_projection * model; + + rasterizer->use_program(*star_shader_program); + if (star_model_view_projection_var) + star_model_view_projection_var->update(model_view_projection); + if (star_distance_var) + star_distance_var->update(star_distance); + if (star_exposure_var) + star_exposure_var->update(camera_exposure); + if (star_inv_resolution_var) + star_inv_resolution_var->update(1.0f / resolution); + + //star_material->update(ctx.alpha); + + rasterizer->draw_arrays(*stars_model_vao, stars_model_drawing_mode, stars_model_start_index, stars_model_index_count); + } + + glDisable(GL_STENCIL_TEST); +} + +void sky_pass::set_transmittance_lut_sample_count(std::uint16_t count) +{ + if (m_transmittance_lut_sample_count != count) + { + m_transmittance_lut_sample_count = count; + + // Rebuild transmittance LUT shader program and command buffer + rebuild_transmittance_lut_shader_program(); + rebuild_transmittance_lut_command_buffer(); + + // Trigger rendering of transmittance LUT + m_render_transmittance_lut = true; + } +} + +void sky_pass::set_transmittance_lut_resolution(const math::vector2& resolution) +{ + if (m_transmittance_lut_resolution.x() != resolution.x() || m_transmittance_lut_resolution.y() != resolution.y()) + { + m_transmittance_lut_resolution = resolution; + m_transmittance_lut_texture->resize(resolution.x(), resolution.y(), nullptr); + m_transmittance_lut_framebuffer->resize({resolution.x(), resolution.y()}); + + // Trigger rendering of transmittance LUT + m_render_transmittance_lut = true; + } +} + +void sky_pass::set_multiscattering_lut_direction_sample_count(std::uint16_t count) +{ + if (m_multiscattering_lut_direction_sample_count != count) + { + m_multiscattering_lut_direction_sample_count = count; + + // Rebuild multiscattering LUT shader program and command buffer + rebuild_multiscattering_lut_shader_program(); + rebuild_multiscattering_lut_command_buffer(); + + // Trigger rendering of multiscattering LUT + m_render_multiscattering_lut = true; + } +} + +void sky_pass::set_multiscattering_lut_scatter_sample_count(std::uint16_t count) +{ + if (m_multiscattering_lut_scatter_sample_count != count) + { + m_multiscattering_lut_scatter_sample_count = count; + + // Rebuild multiscattering LUT shader program and command buffer + rebuild_multiscattering_lut_shader_program(); + rebuild_multiscattering_lut_command_buffer(); + + // Trigger rendering of multiscattering LUT + m_render_multiscattering_lut = true; + } +} + +void sky_pass::set_multiscattering_lut_resolution(const math::vector2& resolution) +{ + if (m_multiscattering_lut_resolution.x() != resolution.x() || m_multiscattering_lut_resolution.y() != resolution.y()) + { + m_multiscattering_lut_resolution = resolution; + m_multiscattering_lut_texture->resize(resolution.x(), resolution.y(), nullptr); + m_multiscattering_lut_framebuffer->resize({resolution.x(), resolution.y()}); + + // Trigger rendering of multiscattering LUT + m_render_multiscattering_lut = true; + } +} + +void sky_pass::set_luminance_lut_sample_count(std::uint16_t count) +{ + if (m_luminance_lut_sample_count != count) + { + m_luminance_lut_sample_count = count; + + // Rebuild luminance LUT shader program and command buffer + rebuild_luminance_lut_shader_program(); + rebuild_luminance_lut_command_buffer(); + + // Trigger rendering of luminance LUT + m_render_luminance_lut = true; + } +} + +void sky_pass::set_luminance_lut_resolution(const math::vector2& resolution) +{ + if (m_luminance_lut_resolution.x() != resolution.x() || m_luminance_lut_resolution.y() != resolution.y()) + { + m_luminance_lut_resolution = resolution; + m_luminance_lut_texture->resize(resolution.x(), resolution.y(), nullptr); + m_luminance_lut_framebuffer->resize({resolution.x(), resolution.y()}); + + // Trigger rendering of luminance LUT + m_render_luminance_lut = true; + } } void sky_pass::set_sky_model(std::shared_ptr model) @@ -361,7 +539,6 @@ void sky_pass::set_sky_model(std::shared_ptr model) sky_model_index_count = group.index_count; } - if (sky_material) { sky_shader_program = sky_material->get_shader_template()->build(); @@ -376,8 +553,10 @@ void sky_pass::set_sky_model(std::shared_ptr model) sun_angular_radius_var = sky_shader_program->variable("sun_angular_radius"); atmosphere_radii_var = sky_shader_program->variable("atmosphere_radii"); observer_position_var = sky_shader_program->variable("observer_position"); - sky_illuminance_lut_var = sky_shader_program->variable("sky_illuminance_lut"); - sky_illuminance_lut_resolution_var = sky_shader_program->variable("sky_illuminance_lut_resolution"); + sky_transmittance_lut_var = sky_shader_program->variable("sky_transmittance_lut"); + sky_transmittance_lut_resolution_var = sky_shader_program->variable("sky_transmittance_lut_resolution"); + sky_luminance_lut_var = sky_shader_program->variable("sky_luminance_lut"); + sky_luminance_lut_resolution_var = sky_shader_program->variable("sky_luminance_lut_resolution"); } else { @@ -423,6 +602,11 @@ void sky_pass::set_moon_model(std::shared_ptr model) moon_sunlight_illuminance_var = moon_shader_program->variable("sunlight_illuminance"); moon_planetlight_direction_var = moon_shader_program->variable("planetlight_direction"); moon_planetlight_illuminance_var = moon_shader_program->variable("planetlight_illuminance"); + moon_albedo_map_var = moon_shader_program->variable("albedo_map"); + moon_normal_map_var = moon_shader_program->variable("normal_map"); + moon_observer_position_var = moon_shader_program->variable("observer_position"); + moon_sky_transmittance_lut_var = moon_shader_program->variable("sky_transmittance_lut"); + moon_atmosphere_radii_var = moon_shader_program->variable("atmosphere_radii"); } else { @@ -451,6 +635,7 @@ void sky_pass::set_stars_model(std::shared_ptr model) stars_model_drawing_mode = group.drawing_mode; stars_model_start_index = group.start_index; stars_model_index_count = group.index_count; + star_material = group.material.get(); } if (star_material) @@ -459,10 +644,10 @@ void sky_pass::set_stars_model(std::shared_ptr model) if (star_shader_program->linked()) { - star_model_view_var = star_shader_program->variable("model_view"); - star_projection_var = star_shader_program->variable("projection"); + star_model_view_projection_var = star_shader_program->variable("model_view_projection"); star_distance_var = star_shader_program->variable("star_distance"); - star_exposure_var = star_shader_program->variable("camera.exposure"); + star_exposure_var = star_shader_program->variable("camera_exposure"); + star_inv_resolution_var = star_shader_program->variable("inv_resolution"); } else { @@ -530,23 +715,27 @@ void sky_pass::set_sun_angular_radius(float radius) void sky_pass::set_planet_radius(float radius) { - atmosphere_radii.x() = radius; - atmosphere_radii.y() = atmosphere_radii.x() + atmosphere_upper_limit; - atmosphere_radii.z() = atmosphere_radii.y() * atmosphere_radii.y(); + atmosphere_radii[0] = radius; + atmosphere_radii[1] = atmosphere_radii[0] + atmosphere_upper_limit; + atmosphere_radii[2] = atmosphere_radii[0] * atmosphere_radii[0]; + atmosphere_radii[3] = atmosphere_radii[1] * atmosphere_radii[1]; + observer_position_tween[1] = {0.0f, atmosphere_radii.x() + observer_elevation, 0.0f}; - // Trigger transmittance LUT render - render_transmittance_lut = true; + // Trigger transmittance and multiscattering LUT render + m_render_transmittance_lut = true; + m_render_multiscattering_lut = true; } void sky_pass::set_atmosphere_upper_limit(float limit) { atmosphere_upper_limit = limit; - atmosphere_radii.y() = atmosphere_radii.x() + atmosphere_upper_limit; - atmosphere_radii.z() = atmosphere_radii.y() * atmosphere_radii.y(); + atmosphere_radii[1] = atmosphere_radii[0] + atmosphere_upper_limit; + atmosphere_radii[3] = atmosphere_radii[1] * atmosphere_radii[1]; - // Trigger transmittance LUT render - render_transmittance_lut = true; + // Trigger transmittance and multiscattering LUT render + m_render_transmittance_lut = true; + m_render_multiscattering_lut = true; } void sky_pass::set_observer_elevation(float elevation) @@ -565,8 +754,9 @@ void sky_pass::set_rayleigh_parameters(float scale_height, const float3& scatter scattering.z() }; - // Trigger transmittance LUT render - render_transmittance_lut = true; + // Trigger transmittance and multiscattering LUT render + m_render_transmittance_lut = true; + m_render_multiscattering_lut = true; } void sky_pass::set_mie_parameters(float scale_height, float scattering, float extinction, float anisotropy) @@ -579,8 +769,9 @@ void sky_pass::set_mie_parameters(float scale_height, float scattering, float ex anisotropy }; - // Trigger transmittance LUT render - render_transmittance_lut = true; + // Trigger transmittance and multiscattering LUT render + m_render_transmittance_lut = true; + m_render_multiscattering_lut = true; } void sky_pass::set_ozone_parameters(float lower_limit, float upper_limit, float mode, const float3& absorption) @@ -593,13 +784,22 @@ void sky_pass::set_ozone_parameters(float lower_limit, float upper_limit, float }; ozone_absorption = absorption; - // Trigger transmittance LUT render - render_transmittance_lut = true; + // Trigger transmittance and multiscattering LUT render + m_render_transmittance_lut = true; + m_render_multiscattering_lut = true; +} + +void sky_pass::set_airglow_luminance(const float3& luminance) +{ + airglow_luminance = luminance; } -void sky_pass::set_airglow_illuminance(const float3& illuminance) +void sky_pass::set_ground_albedo(const float3& albedo) { - airglow_illuminance = illuminance; + m_ground_albedo = albedo; + + // Trigger multiscattering LUT render + m_render_multiscattering_lut = true; } void sky_pass::set_moon_position(const float3& position) @@ -643,150 +843,394 @@ void sky_pass::set_moon_illuminance(const float3& illuminance, const float3& tra moon_transmitted_illuminance = transmitted_illuminance; } -void sky_pass::set_transmittance_lut_resolution(std::uint16_t width, std::uint16_t height) +void sky_pass::set_sky_probe(std::shared_ptr probe) { - transmittance_lut_texture->resize(width, height, nullptr); - transmittance_lut_framebuffer->resize({transmittance_lut_texture->get_width(), transmittance_lut_texture->get_height()}); - transmittance_lut_resolution = {static_cast(width), static_cast(height)}; + m_sky_probe = probe; - // Trigger transmittance LUT render - render_transmittance_lut = true; + if (m_sky_probe && m_sky_probe->get_luminance_texture()) + { + auto& luminance_texture = *m_sky_probe->get_luminance_texture(); + + std::uint16_t face_size = luminance_texture.get_face_size(); + const std::uint8_t mip_count = static_cast(std::bit_width(face_size)); + + m_sky_probe_framebuffers.resize(mip_count); + for (std::uint8_t i = 0; i < mip_count; ++i) + { + m_sky_probe_framebuffers[i] = std::make_unique(face_size, face_size); + m_sky_probe_framebuffers[i]->attach(gl::framebuffer_attachment_type::color, &luminance_texture, i); + face_size >>= 1; + } + } + else + { + m_sky_probe_framebuffers.clear(); + } + + rebuild_sky_probe_command_buffer(); +} + +void sky_pass::rebuild_transmittance_lut_shader_program() +{ + m_transmittance_lut_shader_program = m_transmittance_lut_shader_template->build + ( + { + {"SAMPLE_COUNT", std::to_string(m_transmittance_lut_sample_count)} + } + ); + if (!m_transmittance_lut_shader_program->linked()) + { + debug::log::error("Failed to build sky transmittance LUT shader program: {}", m_transmittance_lut_shader_program->info()); + debug::log::warning("{}", m_transmittance_lut_shader_template->configure(gl::shader_stage::vertex)); + } } void sky_pass::rebuild_transmittance_lut_command_buffer() { - transmittance_lut_command_buffer.clear(); + m_transmittance_lut_command_buffer.clear(); - if (!transmittance_lut_shader_program->linked() || !transmittance_lut_texture) + if (!m_transmittance_lut_shader_program->linked() || !m_transmittance_lut_texture) { return; } // Bind framebuffer and shader program - transmittance_lut_command_buffer.emplace_back + m_transmittance_lut_command_buffer.emplace_back ( [&]() { - rasterizer->set_viewport(0, 0, transmittance_lut_texture->get_width(), transmittance_lut_texture->get_height()); - rasterizer->use_framebuffer(*transmittance_lut_framebuffer); - rasterizer->use_program(*transmittance_lut_shader_program); + rasterizer->set_viewport(0, 0, m_transmittance_lut_resolution.x(), m_transmittance_lut_resolution.y()); + rasterizer->use_framebuffer(*m_transmittance_lut_framebuffer); + rasterizer->use_program(*m_transmittance_lut_shader_program); } ); // Update shader variables - if (auto atmosphere_radii_var = transmittance_lut_shader_program->variable("atmosphere_radii")) + if (auto atmosphere_radii_var = m_transmittance_lut_shader_program->variable("atmosphere_radii")) { - transmittance_lut_command_buffer.emplace_back([&, atmosphere_radii_var](){atmosphere_radii_var->update(atmosphere_radii);}); + m_transmittance_lut_command_buffer.emplace_back([&, atmosphere_radii_var](){atmosphere_radii_var->update(atmosphere_radii);}); } - if (auto rayleigh_parameters_var = transmittance_lut_shader_program->variable("rayleigh_parameters")) + if (auto rayleigh_parameters_var = m_transmittance_lut_shader_program->variable("rayleigh_parameters")) { - transmittance_lut_command_buffer.emplace_back([&, rayleigh_parameters_var](){rayleigh_parameters_var->update(rayleigh_parameters);}); + m_transmittance_lut_command_buffer.emplace_back([&, rayleigh_parameters_var](){rayleigh_parameters_var->update(rayleigh_parameters);}); } - if (auto mie_parameters_var = transmittance_lut_shader_program->variable("mie_parameters")) + if (auto mie_parameters_var = m_transmittance_lut_shader_program->variable("mie_parameters")) { - transmittance_lut_command_buffer.emplace_back([&, mie_parameters_var](){mie_parameters_var->update(mie_parameters);}); + m_transmittance_lut_command_buffer.emplace_back([&, mie_parameters_var](){mie_parameters_var->update(mie_parameters);}); } - if (auto ozone_distribution_var = transmittance_lut_shader_program->variable("ozone_distribution")) + if (auto ozone_distribution_var = m_transmittance_lut_shader_program->variable("ozone_distribution")) { - transmittance_lut_command_buffer.emplace_back([&, ozone_distribution_var](){ozone_distribution_var->update(ozone_distribution);}); + m_transmittance_lut_command_buffer.emplace_back([&, ozone_distribution_var](){ozone_distribution_var->update(ozone_distribution);}); } - if (auto ozone_absorption_var = transmittance_lut_shader_program->variable("ozone_absorption")) + if (auto ozone_absorption_var = m_transmittance_lut_shader_program->variable("ozone_absorption")) { - transmittance_lut_command_buffer.emplace_back([&, ozone_absorption_var](){ozone_absorption_var->update(ozone_absorption);}); + m_transmittance_lut_command_buffer.emplace_back([&, ozone_absorption_var](){ozone_absorption_var->update(ozone_absorption);}); } - if (auto resolution_var = transmittance_lut_shader_program->variable("resolution")) + if (auto resolution_var = m_transmittance_lut_shader_program->variable("resolution")) { - transmittance_lut_command_buffer.emplace_back([&, resolution_var](){resolution_var->update(transmittance_lut_resolution);}); + m_transmittance_lut_command_buffer.emplace_back([&, resolution_var](){resolution_var->update(math::vector2(m_transmittance_lut_resolution));}); } // Draw quad - transmittance_lut_command_buffer.emplace_back + m_transmittance_lut_command_buffer.emplace_back ( [&]() { - rasterizer->draw_arrays(*quad_vao, gl::drawing_mode::triangles, 0, 6); + rasterizer->draw_arrays(*quad_vao, gl::drawing_mode::triangle_strip, 0, 4); } ); } -void sky_pass::rebuild_sky_lut_command_buffer() +void sky_pass::rebuild_multiscattering_lut_shader_program() { - sky_lut_command_buffer.clear(); + m_multiscattering_lut_shader_program = m_multiscattering_lut_shader_template->build + ( + { + {"DIRECTION_SAMPLE_COUNT", std::to_string(m_multiscattering_lut_direction_sample_count)}, + {"SCATTER_SAMPLE_COUNT", std::to_string(m_multiscattering_lut_scatter_sample_count)} + } + ); + if (!m_multiscattering_lut_shader_program->linked()) + { + debug::log::error("Failed to build sky multiscattering LUT shader program: {}", m_multiscattering_lut_shader_program->info()); + debug::log::warning("{}", m_multiscattering_lut_shader_template->configure(gl::shader_stage::vertex)); + } +} + +void sky_pass::rebuild_multiscattering_lut_command_buffer() +{ + m_multiscattering_lut_command_buffer.clear(); - if (!sky_lut_shader_program->linked() || !sky_lut_texture) + if (!m_multiscattering_lut_shader_program->linked() || !m_multiscattering_lut_texture) { return; } // Bind framebuffer and shader program - sky_lut_command_buffer.emplace_back + m_multiscattering_lut_command_buffer.emplace_back ( [&]() { - rasterizer->set_viewport(0, 0, sky_lut_texture->get_width(), sky_lut_texture->get_height()); - rasterizer->use_framebuffer(*sky_lut_framebuffer); - rasterizer->use_program(*sky_lut_shader_program); + rasterizer->set_viewport(0, 0, m_multiscattering_lut_resolution.x(), m_multiscattering_lut_resolution.y()); + rasterizer->use_framebuffer(*m_multiscattering_lut_framebuffer); + rasterizer->use_program(*m_multiscattering_lut_shader_program); } ); // Update shader variables - if (auto light_direction_var = sky_lut_shader_program->variable("light_direction")) + if (auto atmosphere_radii_var = m_multiscattering_lut_shader_program->variable("atmosphere_radii")) + { + m_multiscattering_lut_command_buffer.emplace_back([&, atmosphere_radii_var](){atmosphere_radii_var->update(atmosphere_radii);}); + } + if (auto rayleigh_parameters_var = m_multiscattering_lut_shader_program->variable("rayleigh_parameters")) + { + m_multiscattering_lut_command_buffer.emplace_back([&, rayleigh_parameters_var](){rayleigh_parameters_var->update(rayleigh_parameters);}); + } + if (auto mie_parameters_var = m_multiscattering_lut_shader_program->variable("mie_parameters")) + { + m_multiscattering_lut_command_buffer.emplace_back([&, mie_parameters_var](){mie_parameters_var->update(mie_parameters);}); + } + if (auto ozone_distribution_var = m_multiscattering_lut_shader_program->variable("ozone_distribution")) + { + m_multiscattering_lut_command_buffer.emplace_back([&, ozone_distribution_var](){ozone_distribution_var->update(ozone_distribution);}); + } + if (auto ozone_absorption_var = m_multiscattering_lut_shader_program->variable("ozone_absorption")) + { + m_multiscattering_lut_command_buffer.emplace_back([&, ozone_absorption_var](){ozone_absorption_var->update(ozone_absorption);}); + } + if (auto ground_albedo_var = m_multiscattering_lut_shader_program->variable("ground_albedo")) { - sky_lut_command_buffer.emplace_back([&, light_direction_var](){light_direction_var->update(dominant_light_direction);}); + m_multiscattering_lut_command_buffer.emplace_back([&, ground_albedo_var](){ground_albedo_var->update(m_ground_albedo);}); } - if (auto light_illuminance_var = sky_lut_shader_program->variable("light_illuminance")) + if (auto resolution_var = m_multiscattering_lut_shader_program->variable("resolution")) { - sky_lut_command_buffer.emplace_back([&, light_illuminance_var](){light_illuminance_var->update(dominant_light_illuminance);}); + m_multiscattering_lut_command_buffer.emplace_back([&, resolution_var](){resolution_var->update(math::vector2(m_multiscattering_lut_resolution));}); } - if (auto atmosphere_radii_var = sky_lut_shader_program->variable("atmosphere_radii")) + if (auto transmittance_lut_var = m_multiscattering_lut_shader_program->variable("transmittance_lut")) { - sky_lut_command_buffer.emplace_back([&, atmosphere_radii_var](){atmosphere_radii_var->update(atmosphere_radii);}); + m_multiscattering_lut_command_buffer.emplace_back([&, transmittance_lut_var](){transmittance_lut_var->update(*m_transmittance_lut_texture);}); } - if (auto observer_position_var = sky_lut_shader_program->variable("observer_position")) + + // Draw quad + m_multiscattering_lut_command_buffer.emplace_back + ( + [&]() + { + rasterizer->draw_arrays(*quad_vao, gl::drawing_mode::triangle_strip, 0, 4); + } + ); +} + +void sky_pass::rebuild_luminance_lut_shader_program() +{ + m_luminance_lut_shader_program = m_luminance_lut_shader_template->build + ( + { + {"SAMPLE_COUNT", std::to_string(m_luminance_lut_sample_count)} + } + ); + if (!m_luminance_lut_shader_program->linked()) { - sky_lut_command_buffer.emplace_back([&, observer_position_var](){observer_position_var->update(observer_position);}); + debug::log::error("Failed to build sky luminance LUT shader program: {}", m_luminance_lut_shader_program->info()); + debug::log::warning("{}", m_luminance_lut_shader_template->configure(gl::shader_stage::vertex)); } - if (auto rayleigh_parameters_var = sky_lut_shader_program->variable("rayleigh_parameters")) +} + +void sky_pass::rebuild_luminance_lut_command_buffer() +{ + m_luminance_lut_command_buffer.clear(); + + if (!m_luminance_lut_shader_program->linked() || !m_luminance_lut_texture) { - sky_lut_command_buffer.emplace_back([&, rayleigh_parameters_var](){rayleigh_parameters_var->update(rayleigh_parameters);}); + return; } - if (auto mie_parameters_var = sky_lut_shader_program->variable("mie_parameters")) + + // Bind framebuffer and shader program + m_luminance_lut_command_buffer.emplace_back + ( + [&]() + { + rasterizer->set_viewport(0, 0, m_luminance_lut_resolution.x(), m_luminance_lut_resolution.y()); + rasterizer->use_framebuffer(*m_luminance_lut_framebuffer); + rasterizer->use_program(*m_luminance_lut_shader_program); + } + ); + + // Update shader variables + if (auto light_direction_var = m_luminance_lut_shader_program->variable("light_direction")) { - sky_lut_command_buffer.emplace_back([&, mie_parameters_var](){mie_parameters_var->update(mie_parameters);}); + m_luminance_lut_command_buffer.emplace_back([&, light_direction_var](){light_direction_var->update(dominant_light_direction);}); } - if (auto ozone_distribution_var = sky_lut_shader_program->variable("ozone_distribution")) + if (auto light_illuminance_var = m_luminance_lut_shader_program->variable("light_illuminance")) { - sky_lut_command_buffer.emplace_back([&, ozone_distribution_var](){ozone_distribution_var->update(ozone_distribution);}); + m_luminance_lut_command_buffer.emplace_back([&, light_illuminance_var](){light_illuminance_var->update(dominant_light_illuminance);}); } - if (auto ozone_absorption_var = sky_lut_shader_program->variable("ozone_absorption")) + if (auto atmosphere_radii_var = m_luminance_lut_shader_program->variable("atmosphere_radii")) { - sky_lut_command_buffer.emplace_back([&, ozone_absorption_var](){ozone_absorption_var->update(ozone_absorption);}); + m_luminance_lut_command_buffer.emplace_back([&, atmosphere_radii_var](){atmosphere_radii_var->update(atmosphere_radii);}); } - if (auto airglow_illuminance_var = sky_lut_shader_program->variable("airglow_illuminance")) + if (auto observer_position_var = m_luminance_lut_shader_program->variable("observer_position")) { - sky_lut_command_buffer.emplace_back([&, airglow_illuminance_var](){airglow_illuminance_var->update(airglow_illuminance * camera_exposure);}); + m_luminance_lut_command_buffer.emplace_back([&, observer_position_var](){observer_position_var->update(observer_position);}); } - if (auto resolution_var = sky_lut_shader_program->variable("resolution")) + if (auto rayleigh_parameters_var = m_luminance_lut_shader_program->variable("rayleigh_parameters")) { - sky_lut_command_buffer.emplace_back([&, resolution_var](){resolution_var->update(sky_lut_resolution);}); + m_luminance_lut_command_buffer.emplace_back([&, rayleigh_parameters_var](){rayleigh_parameters_var->update(rayleigh_parameters);}); } - if (auto transmittance_lut_var = sky_lut_shader_program->variable("transmittance_lut")) + if (auto mie_parameters_var = m_luminance_lut_shader_program->variable("mie_parameters")) { - sky_lut_command_buffer.emplace_back([&, transmittance_lut_var](){transmittance_lut_var->update(*transmittance_lut_texture);}); + m_luminance_lut_command_buffer.emplace_back([&, mie_parameters_var](){mie_parameters_var->update(mie_parameters);}); } - if (auto transmittance_lut_resolution_var = sky_lut_shader_program->variable("transmittance_lut_resolution")) + if (auto ozone_distribution_var = m_luminance_lut_shader_program->variable("ozone_distribution")) { - sky_lut_command_buffer.emplace_back([&, transmittance_lut_resolution_var](){transmittance_lut_resolution_var->update(transmittance_lut_resolution);}); + m_luminance_lut_command_buffer.emplace_back([&, ozone_distribution_var](){ozone_distribution_var->update(ozone_distribution);}); + } + if (auto ozone_absorption_var = m_luminance_lut_shader_program->variable("ozone_absorption")) + { + m_luminance_lut_command_buffer.emplace_back([&, ozone_absorption_var](){ozone_absorption_var->update(ozone_absorption);}); + } + if (auto airglow_luminance_var = m_luminance_lut_shader_program->variable("airglow_luminance")) + { + m_luminance_lut_command_buffer.emplace_back([&, airglow_luminance_var](){airglow_luminance_var->update(airglow_luminance * camera_exposure);}); + } + if (auto resolution_var = m_luminance_lut_shader_program->variable("resolution")) + { + m_luminance_lut_command_buffer.emplace_back([&, resolution_var](){resolution_var->update(math::vector2(m_luminance_lut_resolution));}); + } + if (auto transmittance_lut_var = m_luminance_lut_shader_program->variable("transmittance_lut")) + { + m_luminance_lut_command_buffer.emplace_back([&, transmittance_lut_var](){transmittance_lut_var->update(*m_transmittance_lut_texture);}); + } + if (auto multiscattering_lut_var = m_luminance_lut_shader_program->variable("multiscattering_lut")) + { + m_luminance_lut_command_buffer.emplace_back([&, multiscattering_lut_var](){multiscattering_lut_var->update(*m_multiscattering_lut_texture);}); } // Draw quad - sky_lut_command_buffer.emplace_back + m_luminance_lut_command_buffer.emplace_back + ( + [&]() + { + rasterizer->draw_arrays(*quad_vao, gl::drawing_mode::triangle_strip, 0, 4); + } + ); +} + +void sky_pass::rebuild_sky_probe_command_buffer() +{ + m_sky_probe_command_buffer.clear(); + + if (!m_sky_probe_shader_program->linked() || m_sky_probe_framebuffers.empty()) + { + return; + } + + // Bind sky probe framebuffer and shader program + m_sky_probe_command_buffer.emplace_back ( [&]() { - rasterizer->draw_arrays(*quad_vao, gl::drawing_mode::triangles, 0, 6); + const auto resolution = m_sky_probe->get_luminance_texture()->get_face_size(); + rasterizer->set_viewport(0, 0, resolution, resolution); + rasterizer->use_framebuffer(*m_sky_probe_framebuffers[0]); + rasterizer->use_program(*m_sky_probe_shader_program); } ); + + if (auto luminance_lut_var = m_sky_probe_shader_program->variable("luminance_lut")) + { + m_sky_probe_command_buffer.emplace_back([&, luminance_lut_var](){luminance_lut_var->update(*m_luminance_lut_texture);}); + } + if (auto light_direction_var = m_sky_probe_shader_program->variable("light_direction")) + { + m_sky_probe_command_buffer.emplace_back([&, light_direction_var](){light_direction_var->update(dominant_light_direction);}); + } + if (auto observer_position_var = m_sky_probe_shader_program->variable("observer_position")) + { + m_sky_probe_command_buffer.emplace_back([&, observer_position_var](){observer_position_var->update(observer_position);}); + } + if (auto atmosphere_radii_var = m_sky_probe_shader_program->variable("atmosphere_radii")) + { + m_sky_probe_command_buffer.emplace_back([&, atmosphere_radii_var](){atmosphere_radii_var->update(atmosphere_radii);}); + } + + // Draw point + m_sky_probe_command_buffer.emplace_back + ( + [&]() + { + rasterizer->draw_arrays(*quad_vao, gl::drawing_mode::points, 0, 1); + m_sky_probe->set_luminance_outdated(true); + m_sky_probe->set_illuminance_outdated(true); + } + ); + + if (!m_cubemap_downsample_shader_program->linked()) + { + return; + } + + auto cubemap_var = m_cubemap_downsample_shader_program->variable("cubemap"); + if (!cubemap_var) + { + return; + } + + m_sky_probe_command_buffer.emplace_back + ( + [&, cubemap_var]() + { + // Bind downsample shader program + rasterizer->use_program(*m_cubemap_downsample_shader_program); + + // Change texture filter mode to linear to prevent undefined access when reading and writing to different mip levels of the same texture + // m_sky_probe->get_luminance_texture()->set_min_filter(gl::texture_min_filter::linear); + + // Update cubemap shader variable + cubemap_var->update(*m_sky_probe->get_luminance_texture()); + } + ); + + for (std::size_t i = 1; i < m_sky_probe_framebuffers.size(); ++i) + { + const auto resolution = m_sky_probe->get_luminance_texture()->get_face_size() >> i; + + // Bind sky probe mip framebuffer + m_sky_probe_command_buffer.emplace_back + ( + [&, resolution, i]() + { + rasterizer->set_viewport(0, 0, resolution, resolution); + + // Restrict mipmap range + const std::uint8_t base_mip_level = static_cast(i - 1); + m_sky_probe->get_luminance_texture()->set_mipmap_range(base_mip_level, base_mip_level); + + rasterizer->use_framebuffer(*m_sky_probe_framebuffers[i]); + rasterizer->draw_arrays(*quad_vao, gl::drawing_mode::points, 0, 1); + } + ); + } + + // Restore mipmap range + m_sky_probe_command_buffer.emplace_back + ( + [&]() + { + m_sky_probe->get_luminance_texture()->set_mipmap_range(0, 255); + } + ); + + + + // Restore texture filter mode + // m_sky_probe_command_buffer.emplace_back + // ( + // [&]() + // { + // m_sky_probe->get_luminance_texture()->set_min_filter(gl::texture_min_filter::linear_mipmap_linear); + // } + // ); } } // namespace render diff --git a/src/engine/render/passes/sky-pass.hpp b/src/engine/render/passes/sky-pass.hpp index a4fd6ef..0f3b6cf 100644 --- a/src/engine/render/passes/sky-pass.hpp +++ b/src/engine/render/passes/sky-pass.hpp @@ -33,6 +33,7 @@ #include #include #include +#include class resource_manager; @@ -43,6 +44,8 @@ class model; /** * + * + * @see Hillaire, Sébastien. "A scalable and production ready sky and atmosphere rendering technique." Computer Graphics Forum. Vol. 39. No. 4. 2020. */ class sky_pass: public pass { @@ -51,6 +54,128 @@ public: virtual ~sky_pass() = default; void render(render::context& ctx) override; + /// @name Transmittance LUT + /// @{ + + /** + * Sets the number of transmittance integration samples. + * + * @param count Integration sample count. + * + * @note Triggers rebuilding of transmittance LUT shader. + * @note Triggers rendering of transmittance LUT. + */ + void set_transmittance_lut_sample_count(std::uint16_t count); + + /** + * Sets the resolution of the transmittance LUT. + * + * @param resolution Resolution of the transmittance LUT texture, in pixels. + * + * @note Triggers rendering of transmittance LUT. + */ + void set_transmittance_lut_resolution(const math::vector2& resolution); + + /// Returns the number of transmittance integration samples. + [[nodiscard]] inline std::uint16_t get_transmittance_lut_sample_count() const noexcept + { + return m_transmittance_lut_sample_count; + } + + /// Returns the resolution of the transmittance LUT texture, in pixels. + [[nodiscard]] inline const math::vector2& get_transmittance_lut_resolution() const noexcept + { + return m_transmittance_lut_resolution; + } + + /// @} + /// @name Multiscattering LUT + /// @{ + + /** + * Sets the number of multiscattering directions to sample. + * + * @param count Multiscattering direction sample count. + * + * @note Triggers rebuilding of multiscattering LUT shader. + * @note Triggers rendering of multiscattering LUT. + */ + void set_multiscattering_lut_direction_sample_count(std::uint16_t count); + + /** + * Sets the number of multiscattering scatter events to sample per sample direction. + * + * @param count Multiscattering scatter sample count. + * + * @note Triggers rebuilding of multiscattering LUT shader. + * @note Triggers rendering of multiscattering LUT. + */ + void set_multiscattering_lut_scatter_sample_count(std::uint16_t count); + + /** + * Sets the resolution of the multiscattering LUT. + * + * @param resolution Resolution of the multiscattering LUT texture, in pixels. + * + * @note Triggers rendering of multiscattering LUT. + */ + void set_multiscattering_lut_resolution(const math::vector2& resolution); + + /// Returns the number of multiscattering direction samples. + [[nodiscard]] inline std::uint16_t get_multiscattering_lut_direction_sample_count() const noexcept + { + return m_multiscattering_lut_direction_sample_count; + } + + /// Returns the number of multiscattering scatter samples per direction. + [[nodiscard]] inline std::uint16_t get_multiscattering_lut_scatter_sample_count() const noexcept + { + return m_multiscattering_lut_scatter_sample_count; + } + + /// Returns the resolution of the multiscattering LUT texture, in pixels. + [[nodiscard]] inline const math::vector2& get_multiscattering_lut_resolution() const noexcept + { + return m_multiscattering_lut_resolution; + } + + /// @} + /// @name Luminance LUT + /// @{ + + /** + * Sets the number of luminance integration samples. + * + * @param count Integration sample count. + * + * @note Triggers rebuilding of luminance LUT shader. + * @note Triggers rendering of luminance LUT. + */ + void set_luminance_lut_sample_count(std::uint16_t count); + + /** + * Sets the resolution of the luminance LUT. + * + * @param resolution Resolution of the luminance LUT texture, in pixels. + * + * @note Triggers rendering of luminance LUT. + */ + void set_luminance_lut_resolution(const math::vector2& resolution); + + /// Returns the number of luminance integration samples. + [[nodiscard]] inline std::uint16_t get_luminance_lut_sample_count() const noexcept + { + return m_luminance_lut_sample_count; + } + + /// Returns the resolution of the luminance LUT texture, in pixels. + [[nodiscard]] inline const math::vector2& get_luminance_lut_resolution() const noexcept + { + return m_luminance_lut_resolution; + } + + /// @} + void update_tweens(); void set_magnification(float scale); @@ -71,7 +196,8 @@ public: void set_rayleigh_parameters(float scale_height, const float3& scattering); void set_mie_parameters(float scale_height, float scattering, float extinction, float anisotropy); void set_ozone_parameters(float lower_limit, float upper_limit, float mode, const float3& absorption); - void set_airglow_illuminance(const float3& illuminance); + void set_airglow_luminance(const float3& luminance); + void set_ground_albedo(const float3& albedo); void set_moon_position(const float3& position); void set_moon_rotation(const math::quaternion& rotation); @@ -82,35 +208,63 @@ public: void set_moon_planetlight_illuminance(const float3& illuminance); void set_moon_illuminance(const float3& illuminance, const float3& transmitted_illuminance); - /** - * Sets the resolution of transmittance LUT. - * - * @param width Transmittance LUT width, in pixels. - * @param height Transmittance LUT height, in pixels. - */ - void set_transmittance_lut_resolution(std::uint16_t width, std::uint16_t height); + + + void set_sky_probe(std::shared_ptr probe); private: + void rebuild_transmittance_lut_shader_program(); void rebuild_transmittance_lut_command_buffer(); + void rebuild_multiscattering_lut_shader_program(); + void rebuild_multiscattering_lut_command_buffer(); + void rebuild_luminance_lut_shader_program(); + void rebuild_luminance_lut_command_buffer(); + void rebuild_sky_lut_command_buffer(); + void rebuild_sky_probe_command_buffer(); std::unique_ptr quad_vbo; std::unique_ptr quad_vao; - std::unique_ptr transmittance_lut_texture; - std::unique_ptr transmittance_lut_framebuffer; - float2 transmittance_lut_resolution; - std::shared_ptr transmittance_lut_shader_template; - std::unique_ptr transmittance_lut_shader_program; - bool render_transmittance_lut; - std::vector> transmittance_lut_command_buffer; - - std::unique_ptr sky_lut_texture; - std::unique_ptr sky_lut_framebuffer; - std::shared_ptr sky_lut_shader_template; - std::unique_ptr sky_lut_shader_program; - float2 sky_lut_resolution; - std::vector> sky_lut_command_buffer; + // Transmittance + std::uint16_t m_transmittance_lut_sample_count{40}; + math::vector2 m_transmittance_lut_resolution{256, 64}; + std::unique_ptr m_transmittance_lut_texture; + std::unique_ptr m_transmittance_lut_framebuffer; + std::shared_ptr m_transmittance_lut_shader_template; + std::unique_ptr m_transmittance_lut_shader_program; + std::vector> m_transmittance_lut_command_buffer; + bool m_render_transmittance_lut{false}; + + // Multiscattering + std::uint16_t m_multiscattering_lut_direction_sample_count{64}; + std::uint16_t m_multiscattering_lut_scatter_sample_count{20}; + math::vector2 m_multiscattering_lut_resolution{32, 32}; + std::unique_ptr m_multiscattering_lut_texture; + std::unique_ptr m_multiscattering_lut_framebuffer; + std::shared_ptr m_multiscattering_lut_shader_template; + std::unique_ptr m_multiscattering_lut_shader_program; + std::vector> m_multiscattering_lut_command_buffer; + bool m_render_multiscattering_lut{false}; + + // Luminance + std::uint16_t m_luminance_lut_sample_count{30}; + math::vector2 m_luminance_lut_resolution{200, 100}; + std::unique_ptr m_luminance_lut_texture; + std::unique_ptr m_luminance_lut_framebuffer; + std::shared_ptr m_luminance_lut_shader_template; + std::unique_ptr m_luminance_lut_shader_program; + std::vector> m_luminance_lut_command_buffer; + bool m_render_luminance_lut{false}; + + // Sky probe + std::shared_ptr m_sky_probe; + std::vector> m_sky_probe_framebuffers; + std::shared_ptr m_sky_probe_shader_template; + std::unique_ptr m_sky_probe_shader_program; + std::shared_ptr m_cubemap_downsample_shader_template; + std::unique_ptr m_cubemap_downsample_shader_program; + std::vector> m_sky_probe_command_buffer; float3 dominant_light_direction; float3 dominant_light_illuminance; @@ -126,8 +280,10 @@ private: const gl::shader_variable* sun_angular_radius_var; const gl::shader_variable* atmosphere_radii_var; const gl::shader_variable* observer_position_var; - const gl::shader_variable* sky_illuminance_lut_var; - const gl::shader_variable* sky_illuminance_lut_resolution_var; + const gl::shader_variable* sky_transmittance_lut_var; + const gl::shader_variable* sky_transmittance_lut_resolution_var; + const gl::shader_variable* sky_luminance_lut_var; + const gl::shader_variable* sky_luminance_lut_resolution_var; std::shared_ptr moon_shader_program; const gl::shader_variable* moon_model_var; @@ -138,6 +294,11 @@ private: const gl::shader_variable* moon_sunlight_illuminance_var; const gl::shader_variable* moon_planetlight_direction_var; const gl::shader_variable* moon_planetlight_illuminance_var; + const gl::shader_variable* moon_albedo_map_var; + const gl::shader_variable* moon_normal_map_var; + const gl::shader_variable* moon_observer_position_var; + const gl::shader_variable* moon_sky_transmittance_lut_var; + const gl::shader_variable* moon_atmosphere_radii_var; std::shared_ptr sky_model; const material* sky_material; @@ -152,6 +313,9 @@ private: gl::drawing_mode moon_model_drawing_mode; std::size_t moon_model_start_index; std::size_t moon_model_index_count; + std::shared_ptr m_moon_albedo_map; + std::shared_ptr m_moon_normal_map; + std::shared_ptr stars_model; const material* star_material; @@ -160,10 +324,10 @@ private: std::size_t stars_model_start_index; std::size_t stars_model_index_count; std::unique_ptr star_shader_program; - const gl::shader_variable* star_model_view_var; - const gl::shader_variable* star_projection_var; + const gl::shader_variable* star_model_view_projection_var; const gl::shader_variable* star_exposure_var; const gl::shader_variable* star_distance_var; + const gl::shader_variable* star_inv_resolution_var; float2 mouse_position; @@ -186,14 +350,15 @@ private: float sun_angular_radius; float atmosphere_upper_limit; - float3 atmosphere_radii; + float4 atmosphere_radii; float observer_elevation; tween observer_position_tween; float4 rayleigh_parameters; float4 mie_parameters; float3 ozone_distribution; float3 ozone_absorption; - float3 airglow_illuminance; + float3 airglow_luminance; + math::vector3 m_ground_albedo{}; float magnification; }; diff --git a/src/engine/render/renderer.cpp b/src/engine/render/renderer.cpp index 3ec2bb6..46add24 100644 --- a/src/engine/render/renderer.cpp +++ b/src/engine/render/renderer.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -37,19 +38,23 @@ namespace render { -renderer::renderer() -{ - culling_stage = std::make_unique(); - queue_stage = std::make_unique(); +renderer::renderer(gl::rasterizer& rasterizer, ::resource_manager& resource_manager) +{ + m_light_probe_stage = std::make_unique(rasterizer, resource_manager); + m_culling_stage = std::make_unique(); + m_queue_stage = std::make_unique(); } void renderer::render(float t, float dt, float alpha, const scene::collection& collection) { // Init render context - ctx.collection = &collection; - ctx.t = t; - ctx.dt = dt; - ctx.alpha = alpha; + m_ctx.collection = &collection; + m_ctx.t = t; + m_ctx.dt = dt; + m_ctx.alpha = alpha; + + // Execute light probe stage + m_light_probe_stage->execute(m_ctx); // Get list of cameras to be sorted const auto& cameras = collection.get_objects(scene::camera::object_type_id); @@ -67,20 +72,20 @@ void renderer::render(float t, float dt, float alpha, const scene::collection& c } // Update render context camera - ctx.camera = &camera; + m_ctx.camera = &camera; // Clear render queues - ctx.objects.clear(); - ctx.operations.clear(); + m_ctx.objects.clear(); + m_ctx.operations.clear(); // Execute culling stage - culling_stage->execute(ctx); + m_culling_stage->execute(m_ctx); // Execute queue stage - queue_stage->execute(ctx); + m_queue_stage->execute(m_ctx); // Pass render context to the camera's compositor - compositor->composite(ctx); + compositor->composite(m_ctx); } } diff --git a/src/engine/render/renderer.hpp b/src/engine/render/renderer.hpp index 88d7f7a..901f320 100644 --- a/src/engine/render/renderer.hpp +++ b/src/engine/render/renderer.hpp @@ -23,7 +23,10 @@ #include #include #include +#include #include +#include +#include #include namespace render { @@ -36,8 +39,11 @@ class renderer public: /** * Constructs a renderer. + * + * @param rasterizer GL rasterizer. + * @param resource_maanger Resource manager for loading shader templates. */ - renderer(); + renderer(gl::rasterizer& rasterizer, ::resource_manager& resource_manager); /** * Renders a collection of scene objects. @@ -50,10 +56,10 @@ public: void render(float t, float dt, float alpha, const scene::collection& collection); private: - render::context ctx; - - std::unique_ptr culling_stage; - std::unique_ptr queue_stage; + render::context m_ctx; + std::unique_ptr m_light_probe_stage; + std::unique_ptr m_culling_stage; + std::unique_ptr m_queue_stage; }; } // namespace render diff --git a/src/engine/render/stages/light-probe-stage.cpp b/src/engine/render/stages/light-probe-stage.cpp new file mode 100644 index 0000000..bebd39b --- /dev/null +++ b/src/engine/render/stages/light-probe-stage.cpp @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace render { + +light_probe_stage::light_probe_stage(gl::rasterizer& rasterizer, ::resource_manager& resource_manager): + m_rasterizer(&rasterizer) +{ + // Build quad VBO and VAO + { + const math::vector2 vertex_positions[] = + { + {-1.0f, 1.0f}, + {-1.0f, -1.0f}, + { 1.0f, 1.0f}, + { 1.0f, -1.0f} + }; + + const auto vertex_data = std::as_bytes(std::span{vertex_positions}); + const std::size_t vertex_size = 2; + const std::size_t vertex_stride = sizeof(float) * vertex_size; + + m_quad_vbo = std::make_unique(gl::buffer_usage::static_draw, vertex_data.size(), vertex_data); + m_quad_vao = std::make_unique(); + + // Define position vertex attribute + gl::vertex_attribute position_attribute; + position_attribute.buffer = m_quad_vbo.get(); + position_attribute.offset = 0; + position_attribute.stride = vertex_stride; + position_attribute.type = gl::vertex_attribute_type::float_32; + position_attribute.components = 2; + + // Bind vertex attributes to VAO + m_quad_vao->bind(render::vertex_attribute::position, position_attribute); + } + + // Load cubemap to spherical harmonics shader template + m_cubemap_to_sh_shader_template = resource_manager.load("cubemap-to-sh.glsl"); + + // Build cubemap to spherical harmonics shader program + rebuild_cubemap_to_sh_shader_program(); +} + +void light_probe_stage::execute(render::context& ctx) +{ + // Get all light probes in the collection + const auto& light_probes = ctx.collection->get_objects(scene::light_probe::object_type_id); + + bool state_bound = false; + + // For each light probe + std::for_each + ( + std::execution::seq, + std::begin(light_probes), + std::end(light_probes), + [&](scene::object_base* object) + { + scene::light_probe& light_probe = static_cast(*object); + if (!light_probe.is_illuminance_outdated() && !m_reproject_sh) + { + return; + } + + // Setup viewport and bind cubemap to spherical harmonics shader program + if (!state_bound) + { + glDisable(GL_BLEND); + m_rasterizer->set_viewport(0, 0, 12, 1); + m_rasterizer->use_program(*m_cubemap_to_sh_shader_program); + state_bound = true; + } + + // Bind light probe illuminance framebuffer + m_rasterizer->use_framebuffer(*light_probe.get_illuminance_framebuffer()); + + // Update cubemap to spherical harmonics cubemap variable with light probe luminance texture + m_cubemap_var->update(*light_probe.get_luminance_texture()); + + // Draw quad + m_rasterizer->draw_arrays(*m_quad_vao, gl::drawing_mode::triangle_strip, 0, 4); + + // Mark light probe illuminance as current + light_probe.set_illuminance_outdated(false); + } + ); + + m_reproject_sh = false; +} + +void light_probe_stage::set_sh_sample_count(std::size_t count) +{ + if (m_sh_sample_count != count) + { + m_sh_sample_count = count; + sh_parameters_changed(); + } +} + +void light_probe_stage::rebuild_cubemap_to_sh_shader_program() +{ + m_cubemap_to_sh_shader_program = m_cubemap_to_sh_shader_template->build({{"SAMPLE_COUNT", std::to_string(m_sh_sample_count)}}); + if (!m_cubemap_to_sh_shader_program->linked()) + { + debug::log::error("Failed to build cubemap to spherical harmonics shader program: {}", m_cubemap_to_sh_shader_program->info()); + debug::log::warning("{}", m_cubemap_to_sh_shader_template->configure(gl::shader_stage::vertex)); + m_cubemap_var = nullptr; + + throw std::runtime_error("Failed to build cubemap to spherical harmonics shader program."); + } + else + { + m_cubemap_var = m_cubemap_to_sh_shader_program->variable("cubemap"); + if (!m_cubemap_var) + { + throw std::runtime_error("Cubemap to spherical harmonics shader program has no `cubemap` variable."); + } + } +} + +void light_probe_stage::sh_parameters_changed() +{ + rebuild_cubemap_to_sh_shader_program(); + m_reproject_sh = true; +} + +} // namespace render diff --git a/src/engine/render/stages/light-probe-stage.hpp b/src/engine/render/stages/light-probe-stage.hpp new file mode 100644 index 0000000..7c523a2 --- /dev/null +++ b/src/engine/render/stages/light-probe-stage.hpp @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_RENDER_LIGHT_PROBE_STAGE_HPP +#define ANTKEEPER_RENDER_LIGHT_PROBE_STAGE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace render { + +/** + * Updates light probes. + */ +class light_probe_stage: public stage +{ +public: + /** + * Constructs a light probe stage. + * + * @param rasterizer GL rasterizer. + * @param resource_manager Resource manager for loading shader templates. + * + * @exception std::runtime_error Failed to build cubemap to spherical harmonics shader program. + * @exception std::runtime_error Cubemap to spherical harmonics shader program has no `cubemap` variable. + */ + light_probe_stage(gl::rasterizer& rasterizer, ::resource_manager& resource_manager); + + void execute(render::context& ctx) override; + + /** + * Sets the number of samples to use when projecting cubemaps into spherical harmonics. + * + * @param count Sample count. + * + * @warning Triggers rebuilding of cubemap to spherical harmonics shader program. + * @warning Triggers recalculation of the illuminance of all light probes on next call to `execute()`. + * + * @exception std::runtime_error Failed to build cubemap to spherical harmonics shader program. + * @exception std::runtime_error Cubemap to spherical harmonics shader program has no `cubemap` variable. + */ + void set_sh_sample_count(std::size_t count); + + /// Returns the number of samples used when projecting cubemaps into spherical harmonics. + [[nodiscard]] inline std::size_t get_sh_sample_count() const noexcept + { + return m_sh_sample_count; + } + +private: + void rebuild_cubemap_to_sh_shader_program(); + void sh_parameters_changed(); + + gl::rasterizer* m_rasterizer; + std::unique_ptr m_quad_vbo; + std::unique_ptr m_quad_vao; + std::shared_ptr m_cubemap_to_sh_shader_template; + std::unique_ptr m_cubemap_to_sh_shader_program; + const gl::shader_variable* m_cubemap_var{}; + std::size_t m_sh_sample_count{1024}; + bool m_reproject_sh{true}; +}; + +} // namespace render + +#endif // ANTKEEPER_RENDER_LIGHT_PROBE_STAGE_HPP diff --git a/src/engine/scene/ambient-light.cpp b/src/engine/scene/ambient-light.cpp deleted file mode 100644 index 9ed076a..0000000 --- a/src/engine/scene/ambient-light.cpp +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2023 Christopher J. Howard - * - * This file is part of Antkeeper source code. - * - * Antkeeper source code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper source code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper source code. If not, see . - */ - -#include - -namespace scene { - -void ambient_light::color_updated() -{ - m_colored_illuminance = m_color * m_illuminance; -} - -void ambient_light::illuminance_updated() -{ - m_colored_illuminance = m_color * m_illuminance; -} - -} // namespace scene diff --git a/src/engine/scene/ambient-light.hpp b/src/engine/scene/ambient-light.hpp deleted file mode 100644 index ce944c4..0000000 --- a/src/engine/scene/ambient-light.hpp +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2023 Christopher J. Howard - * - * This file is part of Antkeeper source code. - * - * Antkeeper source code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper source code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper source code. If not, see . - */ - -#ifndef ANTKEEPER_SCENE_AMBIENT_LIGHT_HPP -#define ANTKEEPER_SCENE_AMBIENT_LIGHT_HPP - -#include -#include - -namespace scene { - -/** - * Omnidirectional source of illuminance. - */ -class ambient_light: public light -{ -public: - [[nodiscard]] inline light_type get_light_type() const noexcept override - { - return light_type::ambient; - } - - /** - * Sets the color of the light. - * - * @param color Light color. - */ - inline void set_color(const math::vector3& color) noexcept - { - m_color = color; - color_updated(); - } - - /** - * Sets the illuminance of the light on a surface perpendicular to the light direction. - * - * @param illuminance Illuminance on a surface perpendicular to the light direction. - */ - inline void set_illuminance(float illuminance) noexcept - { - m_illuminance = illuminance; - illuminance_updated(); - } - - /// Returns the color of the light. - [[nodiscard]] inline const math::vector3& get_color() const noexcept - { - return m_color; - } - - /// Returns the illuminance of the light on a surface perpendicular to the light direction. - [[nodiscard]] inline float get_illuminance() const noexcept - { - return m_illuminance; - } - - /// Returns the color-modulated illuminance of the light on a surface perpendicular to the light direction. - [[nodiscard]] inline const math::vector3& get_colored_illuminance() const noexcept - { - return m_colored_illuminance; - } - -private: - void color_updated(); - void illuminance_updated(); - - math::vector3 m_color{1.0f, 1.0f, 1.0f}; - float m_illuminance{}; - math::vector3 m_colored_illuminance{}; -}; - -} // namespace scene - -#endif // ANTKEEPER_SCENE_AMBIENT_LIGHT_HPP diff --git a/src/engine/scene/camera.cpp b/src/engine/scene/camera.cpp index 6812073..0ddad8d 100644 --- a/src/engine/scene/camera.cpp +++ b/src/engine/scene/camera.cpp @@ -63,18 +63,38 @@ float3 camera::unproject(const float3& window, const float4& viewport) const return math::vector(result) * (1.0f / result[3]); } -void camera::set_perspective(float fov, float aspect_ratio, float clip_near, float clip_far) +void camera::set_perspective(float vertical_fov, float aspect_ratio, float clip_near, float clip_far) { m_orthographic = false; // Update perspective projection parameters - m_fov = fov; + m_vertical_fov = vertical_fov; m_aspect_ratio = aspect_ratio; m_clip_near = clip_near; m_clip_far = clip_far; // Recalculate projection matrix - m_projection = math::perspective_half_z(m_fov, m_aspect_ratio, m_clip_far, m_clip_near); + m_projection = math::perspective_half_z(m_vertical_fov, m_aspect_ratio, m_clip_far, m_clip_near); + + // Recalculate view-projection matrix + m_view_projection = m_projection * m_view; + m_inverse_view_projection = math::inverse(m_view_projection); + + // Recalculate view frustum + update_frustum(); +} + +void camera::set_vertical_fov(float vertical_fov) +{ + if (m_orthographic) + { + return; + } + + m_vertical_fov = vertical_fov; + + // Recalculate projection matrix + m_projection = math::perspective_half_z(m_vertical_fov, m_aspect_ratio, m_clip_far, m_clip_near); // Recalculate view-projection matrix m_view_projection = m_projection * m_view; @@ -110,7 +130,8 @@ void camera::set_orthographic(float clip_left, float clip_right, float clip_bott void camera::set_exposure_value(float ev100) { m_exposure_value = ev100; - m_exposure_normalization = 1.0f / (std::exp2(m_exposure_value) * 1.2f); + // m_exposure_normalization = 1.0f / (std::exp2(m_exposure_value) * 1.2f); + m_exposure_normalization = 1.0f / (std::exp2(m_exposure_value)); } void camera::transformed() diff --git a/src/engine/scene/camera.hpp b/src/engine/scene/camera.hpp index 178fd6b..566c9de 100644 --- a/src/engine/scene/camera.hpp +++ b/src/engine/scene/camera.hpp @@ -68,13 +68,20 @@ public: /** * Sets the camera's projection matrix using perspective projection. * - * @param fov Vertical field of view. + * @param vertical_fov Vertical field of view, in radians. * @param aspect_ratio Aspect ratio. * @param clip_near Distance to near clipping plane. * @param clip_far Distance to far clipping plane. */ - void set_perspective(float fov, float aspect_ratio, float clip_near, float clip_far); - + void set_perspective(float vertical_fov, float aspect_ratio, float clip_near, float clip_far); + + /** + * Sets the camera's vertical field of view. + * + * @param vertical_fov Vertical field of view, in radians. + */ + void set_vertical_fov(float vertical_fov); + /** * Sets the camera's projection matrix using orthographic projection. * @@ -181,10 +188,10 @@ public: return m_clip_far; } - /// Returns the camera's field of view, in radians. - [[nodiscard]] inline float get_fov() const noexcept + /// Returns the camera's vertical field of view, in radians. + [[nodiscard]] inline float get_vertical_fov() const noexcept { - return m_fov; + return m_vertical_fov; } /// Returns the camera's aspect ratio. @@ -262,7 +269,7 @@ private: float m_clip_top{1.0f}; float m_clip_near{-1.0f}; float m_clip_far{1.0f}; - float m_fov{math::half_pi}; + float m_vertical_fov{math::half_pi}; float m_aspect_ratio{1.0f}; float m_exposure_value{0.0f}; float m_exposure_normalization{1.0f / 1.2f}; diff --git a/src/engine/scene/light-probe.cpp b/src/engine/scene/light-probe.cpp new file mode 100644 index 0000000..f4b48ac --- /dev/null +++ b/src/engine/scene/light-probe.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#include + +namespace scene { + +light_probe::light_probe() +{ + // Allocate illuminance texture + m_illuminance_texture = std::make_shared(12, gl::pixel_type::float_32, gl::pixel_format::rgba); + m_illuminance_texture->set_filters(gl::texture_min_filter::nearest, gl::texture_mag_filter::nearest); + + // Allocate and init illuminance framebuffer + m_illuminance_framebuffer = std::make_shared(12, 1); + m_illuminance_framebuffer->attach(gl::framebuffer_attachment_type::color, m_illuminance_texture.get()); + + // Init illuminance matrices + m_illuminance_matrices[0] = {}; + m_illuminance_matrices[1] = {}; + m_illuminance_matrices[2] = {}; +} + +void light_probe::update_illuminance_matrices() +{ + m_illuminance_texture->read(std::as_writable_bytes(std::span{m_illuminance_matrices}), gl::pixel_type::float_32, gl::pixel_format::rgba, 0); +} + +void light_probe::set_luminance_texture(std::shared_ptr texture) +{ + if (m_luminance_texture != texture) + { + m_luminance_texture = texture; + set_luminance_outdated(true); + set_illuminance_outdated(true); + } +} + +void light_probe::set_luminance_outdated(bool outdated) +{ + m_luminance_outdated = outdated; +} + +void light_probe::set_illuminance_outdated(bool outdated) +{ + m_illuminance_outdated = outdated; +} + +void light_probe::transformed() +{ + m_bounds = {get_translation(), get_translation()}; +} + +} // namespace scene diff --git a/src/engine/scene/light-probe.hpp b/src/engine/scene/light-probe.hpp new file mode 100644 index 0000000..0322cfa --- /dev/null +++ b/src/engine/scene/light-probe.hpp @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_SCENE_LIGHT_PROBE_HPP +#define ANTKEEPER_SCENE_LIGHT_PROBE_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace scene { + +/** + * + */ +class light_probe: public object +{ +public: + /// Constructs a light probe. + light_probe(); + + /** + * Updates the light probe's illuminance matrices from its illuminance texture. + * + * @warning Reads texture data from the GPU. + */ + void update_illuminance_matrices(); + + /** + * Sets the light probe's luminance texture. + * + * @param texture Luminance cubemap texture. + * + * @note Marks the light probe's luminance as outdated if the luminance texture has changed. + * @note Marks the light probe's illuminance as outdated if the luminance texture has changed. + */ + void set_luminance_texture(std::shared_ptr texture); + + /** + * Marks the light probe's luminance as either outdated or current. + * + * @param outdated `true` if the light probe's luminance is outdated, `false` otherwise. + */ + void set_luminance_outdated(bool outdated); + + /** + * Marks the light probe's illuminance as either outdated or current. + * + * @param outdated `true` if the light probe's illuminance is outdated, `false` otherwise. + */ + void set_illuminance_outdated(bool outdated); + + /// Returns the light probe's luminance texture. + [[nodiscard]] inline const std::shared_ptr& get_luminance_texture() const noexcept + { + return m_luminance_texture; + } + + /** + * Returns the light probe's illuminance texture. + * + * The illuminance texture is a 12x1 RGBA floating-point LUT which encodes the column vectors of three spherical harmonics illuminance matrices in the layout `R0,R1,R2,R3,G0,G1,G2,G3,B0,B1,B2,B3`. The matrices `R`, `G`, and `B` can be used to recover illuminance of the red, green, and blue color channels, respectively, for a given surface normal, `n`, as follows: `(dot(n, R * n), dot(n, G * n), dot(n, B * n))`, where `n = (x, y, z, 1)`. + */ + [[nodiscard]] inline const std::shared_ptr& get_illuminance_texture() const noexcept + { + return m_illuminance_texture; + } + + /// Returns the light probe's illuminance framebuffer. + [[nodiscard]] inline const std::shared_ptr& get_illuminance_framebuffer() const noexcept + { + return m_illuminance_framebuffer; + } + + /** + * Returns the light probe's illuminance matrices. + * + * @return Red, green, and blue illuminance matrices. + * + * @warning The light probe's illuminance matrices must first be updated. + * + * @see light_probe::update_illuminance_matrices() + */ + [[nodiscard]] inline std::span, 3> get_illuminance_matrices() const noexcept + { + return m_illuminance_matrices; + } + + /// Returns `true` if the light probe's luminance is outdated. + [[nodiscard]] inline bool is_luminance_outdated() const noexcept + { + return m_luminance_outdated; + } + + /// Returns `true` if the light probe's illuminance is outdated. + [[nodiscard]] inline bool is_illuminance_outdated() const noexcept + { + return m_illuminance_outdated; + } + + [[nodiscard]] inline const aabb_type& get_bounds() const noexcept override + { + return m_bounds; + } + +private: + void transformed() override; + aabb_type m_bounds{}; + std::shared_ptr m_luminance_texture; + std::shared_ptr m_illuminance_texture; + std::shared_ptr m_illuminance_framebuffer; + math::matrix4 m_illuminance_matrices[3]; + bool m_luminance_outdated{}; + bool m_illuminance_outdated{}; +}; + +} // namespace scene + +#endif // ANTKEEPER_SCENE_LIGHT_PROBE_HPP diff --git a/src/engine/scene/light-type.hpp b/src/engine/scene/light-type.hpp index d80cd6c..be32d66 100644 --- a/src/engine/scene/light-type.hpp +++ b/src/engine/scene/light-type.hpp @@ -27,9 +27,6 @@ namespace scene { /// Light types. enum class light_type: std::uint8_t { - /// Ambient light. - ambient, - /// Directional light. directional, @@ -40,10 +37,7 @@ enum class light_type: std::uint8_t point, /// Rectangle light. - rectangle, - - /// Sky light. - sky + rectangle }; } // namespace scene diff --git a/src/engine/scene/light.hpp b/src/engine/scene/light.hpp index 17fdc1a..82f8664 100644 --- a/src/engine/scene/light.hpp +++ b/src/engine/scene/light.hpp @@ -34,14 +34,14 @@ public: /// Returns an enumeration denoting the light object type. [[nodiscard]] virtual light_type get_light_type() const noexcept = 0; - inline const aabb_type& get_bounds() const noexcept override + [[nodiscard]] inline const aabb_type& get_bounds() const noexcept override { return m_bounds; } private: - virtual void transformed(); - aabb_type m_bounds{{0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}}; + void transformed() override; + aabb_type m_bounds{}; }; } // namespace scene diff --git a/src/engine/scene/sky-light.cpp b/src/engine/scene/sky-light.cpp deleted file mode 100644 index 30f4ca0..0000000 --- a/src/engine/scene/sky-light.cpp +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2023 Christopher J. Howard - * - * This file is part of Antkeeper source code. - * - * Antkeeper source code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper source code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper source code. If not, see . - */ - -#include - -namespace scene { - - -} // namespace scene diff --git a/src/engine/scene/sky-light.hpp b/src/engine/scene/sky-light.hpp deleted file mode 100644 index 9f19f94..0000000 --- a/src/engine/scene/sky-light.hpp +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2023 Christopher J. Howard - * - * This file is part of Antkeeper source code. - * - * Antkeeper source code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper source code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper source code. If not, see . - */ - -#ifndef ANTKEEPER_SCENE_SKY_LIGHT_HPP -#define ANTKEEPER_SCENE_SKY_LIGHT_HPP - -#include -#include - -namespace scene { - -/** - * - */ -class sky_light: public light -{ -public: - [[nodiscard]] inline light_type get_light_type() const noexcept override - { - return light_type::sky; - } - - - -private: - -}; - -} // namespace scene - -#endif // ANTKEEPER_SCENE_SKY_LIGHT_HPP diff --git a/src/game/components/atmosphere-component.hpp b/src/game/components/atmosphere-component.hpp index 797b351..e4e5fd7 100644 --- a/src/game/components/atmosphere-component.hpp +++ b/src/game/components/atmosphere-component.hpp @@ -74,8 +74,8 @@ struct atmosphere_component /// (Dependent) Ozone absorption coefficients. double3 ozone_absorption; - /// Airglow illuminance, in lux. - double3 airglow_illuminance; + /// Airglow luminance, in cd/m^2. + double3 airglow_luminance; }; diff --git a/src/game/controls.cpp b/src/game/controls.cpp index c09d4e0..7619f7e 100644 --- a/src/game/controls.cpp +++ b/src/game/controls.cpp @@ -159,6 +159,15 @@ void reset_control_profile(::control_profile& profile) // Save camera mappings.emplace("save_camera", std::make_unique(nullptr, input::scancode::left_ctrl, 0, false)); mappings.emplace("save_camera", std::make_unique(nullptr, input::scancode::right_ctrl, 0, false)); + + // Adjust exposure + mappings.emplace("adjust_exposure", std::make_unique(nullptr, input::scancode::b, 0, false)); + + // Adjust time + mappings.emplace("adjust_time", std::make_unique(nullptr, input::scancode::t, 0, false)); + + // Adjust time + mappings.emplace("adjust_zoom", std::make_unique(nullptr, input::scancode::z, 0, false)); } void apply_control_profile(::game& ctx, const ::control_profile& profile) @@ -215,6 +224,9 @@ void apply_control_profile(::game& ctx, const ::control_profile& profile) add_mappings(ctx.keeper_action_map, ctx.camera_9_action, "camera_9"); add_mappings(ctx.keeper_action_map, ctx.camera_10_action, "camera_10"); add_mappings(ctx.keeper_action_map, ctx.save_camera_action, "save_camera"); + add_mappings(ctx.keeper_action_map, ctx.adjust_exposure_action, "adjust_exposure"); + add_mappings(ctx.keeper_action_map, ctx.adjust_time_action, "adjust_time"); + add_mappings(ctx.keeper_action_map, ctx.adjust_zoom_action, "adjust_zoom"); } void update_control_profile(::game& ctx, ::control_profile& profile) @@ -295,6 +307,9 @@ void update_control_profile(::game& ctx, ::control_profile& profile) add_mappings(ctx.keeper_action_map, ctx.camera_9_action, "camera_9"); add_mappings(ctx.keeper_action_map, ctx.camera_10_action, "camera_10"); add_mappings(ctx.keeper_action_map, ctx.save_camera_action, "save_camera"); + add_mappings(ctx.keeper_action_map, ctx.adjust_exposure_action, "adjust_exposure"); + add_mappings(ctx.keeper_action_map, ctx.adjust_time_action, "adjust_time"); + add_mappings(ctx.keeper_action_map, ctx.adjust_zoom_action, "adjust_zoom"); } void setup_window_controls(::game& ctx) @@ -608,5 +623,8 @@ void disable_keeper_controls(::game& ctx) ctx.mouse_grip_action.reset(); ctx.mouse_zoom_action.reset(); ctx.focus_action.reset(); + ctx.adjust_exposure_action.reset(); + ctx.adjust_time_action.reset(); + ctx.adjust_zoom_action.reset(); } diff --git a/src/game/game.cpp b/src/game/game.cpp index 52f32f9..e15098e 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -737,11 +737,12 @@ void game::setup_rendering() surface_clear_pass = std::make_unique(window->get_rasterizer(), hdr_framebuffer.get()); surface_clear_pass->set_clear_color({0.0f, 0.0f, 0.0f, 1.0f}); - surface_clear_pass->set_cleared_buffers(true, true, false); surface_clear_pass->set_clear_depth(-1.0f); + surface_clear_pass->set_clear_stencil(0); + surface_clear_pass->set_cleared_buffers(true, true, true); sky_pass = std::make_unique(window->get_rasterizer(), hdr_framebuffer.get(), resource_manager.get()); - sky_pass->set_magnification(3.0f); + // sky_pass->set_magnification(3.0f); ground_pass = std::make_unique(window->get_rasterizer(), hdr_framebuffer.get(), resource_manager.get()); @@ -786,7 +787,7 @@ void game::setup_rendering() } // Create renderer - renderer = std::make_unique(); + renderer = std::make_unique(*window->get_rasterizer(), *resource_manager); debug::log::trace("Set up rendering"); } @@ -987,7 +988,7 @@ void game::setup_ui() // Update camera projection matrix surface_camera->set_perspective ( - surface_camera->get_fov(), + surface_camera->get_vertical_fov(), viewport_aspect_ratio, surface_camera->get_clip_near(), surface_camera->get_clip_far() @@ -1085,6 +1086,7 @@ void game::setup_systems() // RGB wavelengths for atmospheric scatteering rgb_wavelengths = {680, 550, 440}; + // rgb_wavelengths = {602.21436, 541.0647, 448.14404}; // Setup atmosphere system atmosphere_system = std::make_unique<::atmosphere_system>(*entity_registry); diff --git a/src/game/game.hpp b/src/game/game.hpp index b27add4..2259519 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -51,7 +51,6 @@ #include #include #include -#include #include #include #include @@ -239,6 +238,9 @@ public: input::action camera_9_action; input::action camera_10_action; input::action save_camera_action; + input::action adjust_exposure_action; + input::action adjust_time_action; + input::action adjust_zoom_action; std::vector> window_action_subscriptions; std::vector> menu_action_subscriptions; @@ -332,11 +334,9 @@ public: std::shared_ptr surface_camera; std::unique_ptr sun_light; std::unique_ptr moon_light; - std::unique_ptr sky_light; std::unique_ptr underground_scene; std::shared_ptr underground_camera; std::unique_ptr underground_directional_light; - std::unique_ptr underground_ambient_light; std::unique_ptr underground_rectangle_light; scene::collection* active_scene; diff --git a/src/game/loaders/entity-archetype-loader.cpp b/src/game/loaders/entity-archetype-loader.cpp index 76fb032..173645c 100644 --- a/src/game/loaders/entity-archetype-loader.cpp +++ b/src/game/loaders/entity-archetype-loader.cpp @@ -68,12 +68,12 @@ static bool load_component_atmosphere(entity::archetype& archetype, const json& if (element.contains("ozone_mode")) component.ozone_mode = element["ozone_mode"].get(); - if (element.contains("airglow_illuminance")) + if (element.contains("airglow_luminance")) { - const auto& airglow_illuminance = element["airglow_illuminance"]; - component.airglow_illuminance.x() = airglow_illuminance[0].get(); - component.airglow_illuminance.y() = airglow_illuminance[1].get(); - component.airglow_illuminance.z() = airglow_illuminance[2].get(); + const auto& airglow_luminance = element["airglow_luminance"]; + component.airglow_luminance.x() = airglow_luminance[0].get(); + component.airglow_luminance.y() = airglow_luminance[1].get(); + component.airglow_luminance.z() = airglow_luminance[2].get(); } archetype.stamps.push_back diff --git a/src/game/states/nest-selection-state.cpp b/src/game/states/nest-selection-state.cpp index 90638b7..7b1e9ae 100644 --- a/src/game/states/nest-selection-state.cpp +++ b/src/game/states/nest-selection-state.cpp @@ -207,7 +207,7 @@ nest_selection_state::nest_selection_state(::game& ctx): ctx.ui_clear_pass->set_cleared_buffers(false, true, false); // Set world time - ::world::set_time(ctx, 2022, 6, 21, 18, 0, 0.0); + ::world::set_time(ctx, 2022, 6, 21, 12, 0, 0.0); // Init time scale double time_scale = 60.0; @@ -296,6 +296,24 @@ nest_selection_state::nest_selection_state(::game& ctx): ctx.entity_registry->emplace(spring_eid, std::move(spring)); */ + sky_probe = std::make_shared(); + sky_probe->set_luminance_texture(std::make_shared(384, 512, gl::pixel_type::float_16, gl::pixel_format::rgb)); + ctx.sky_pass->set_sky_probe(sky_probe); + ctx.surface_scene->add_object(*sky_probe); + + // Create sphere + auto sphere_eid = ctx.entity_registry->create(); + auto sphere_static_mesh = std::make_shared(ctx.resource_manager->load("sphere.mdl")); + ctx.entity_registry->emplace(sphere_eid, std::move(sphere_static_mesh), std::uint8_t{1}); + ctx.entity_registry->patch + ( + sphere_eid, + [&](auto& component) + { + component.object->set_translation({0.0f, 10.0f, 0.0f}); + } + ); + // Queue enable game controls ctx.function_queue.push ( @@ -494,6 +512,102 @@ void nest_selection_state::setup_controls() ) ); + // Enable/toggle mouse look + action_subscriptions.emplace_back + ( + ctx.mouse_pick_action.get_activated_channel().subscribe + ( + [&](const auto& event) + { + mouse_drag = true; + } + ) + ); + + // Disable mouse look + action_subscriptions.emplace_back + ( + ctx.mouse_pick_action.get_deactivated_channel().subscribe + ( + [&](const auto& event) + { + mouse_drag = false; + } + ) + ); + + // Enable/toggle adjust exposure + action_subscriptions.emplace_back + ( + ctx.adjust_exposure_action.get_activated_channel().subscribe + ( + [&](const auto& event) + { + adjust_exposure = true; + } + ) + ); + + // Disable adjust exposure + action_subscriptions.emplace_back + ( + ctx.adjust_exposure_action.get_deactivated_channel().subscribe + ( + [&](const auto& event) + { + adjust_exposure = false; + } + ) + ); + + // Enable/toggle adjust time + action_subscriptions.emplace_back + ( + ctx.adjust_time_action.get_activated_channel().subscribe + ( + [&](const auto& event) + { + adjust_time = true; + } + ) + ); + + // Disable adjust time + action_subscriptions.emplace_back + ( + ctx.adjust_time_action.get_deactivated_channel().subscribe + ( + [&](const auto& event) + { + adjust_time = false; + } + ) + ); + + // Enable/toggle adjust zoom + action_subscriptions.emplace_back + ( + ctx.adjust_zoom_action.get_activated_channel().subscribe + ( + [&](const auto& event) + { + adjust_zoom = true; + } + ) + ); + + // Disable adjust zoom + action_subscriptions.emplace_back + ( + ctx.adjust_zoom_action.get_deactivated_channel().subscribe + ( + [&](const auto& event) + { + adjust_zoom = false; + } + ) + ); + constexpr float movement_speed = 200.0f; auto move_first_person_camera_rig = [&](const float2& direction, float speed) @@ -545,6 +659,42 @@ void nest_selection_state::setup_controls() ( [&, move_first_person_camera_rig](const auto& event) { + if (adjust_time) + { + const double sensitivity = 1.0 / static_cast(ctx.window->get_viewport_size().x()); + const double t = ctx.astronomy_system->get_time(); + ::world::set_time(ctx, t + static_cast(event.difference.x()) * sensitivity); + + /* + sky_probe->update_illuminance_matrices(); + + const auto matrices = sky_probe->get_illuminance_matrices(); + for (std::size_t i = 0; i < 3; ++i) + { + const auto m = matrices[i]; + debug::log::warning("\nmat4({},{},{},{},\n{},{},{},{},\n{},{},{},{},\n{},{},{},{});", m[0][0], m[0][1], m[0][2], m[0][3], m[1][0], m[1][1], m[1][2], m[1][3], m[2][0], m[2][1], m[2][2], m[2][3], m[3][0], m[3][1], m[3][2], m[3][3]); + } + */ + } + + if (adjust_exposure) + { + const float sensitivity = 8.0f / static_cast(ctx.window->get_viewport_size().y()); + ctx.surface_camera->set_exposure_value(ctx.surface_camera->get_exposure_value() + static_cast(event.difference.y()) * sensitivity); + } + + if (adjust_zoom) + { + const float sensitivity = math::radians(45.0f) / static_cast(ctx.window->get_viewport_size().y()); + const float min_hfov = math::radians(1.0f); + const float max_hfov = math::radians(90.0f); + + const float aspect_ratio = ctx.surface_camera->get_aspect_ratio(); + const float hfov = std::min(std::max(math::horizontal_fov(ctx.surface_camera->get_vertical_fov(), aspect_ratio) + static_cast(event.difference.y()) * sensitivity, min_hfov), max_hfov); + const float vfov = math::vertical_fov(hfov, aspect_ratio); + ctx.surface_camera->set_vertical_fov(vfov); + } + if (!mouse_look) { return; diff --git a/src/game/states/nest-selection-state.hpp b/src/game/states/nest-selection-state.hpp index 3e4cd92..392ab3e 100644 --- a/src/game/states/nest-selection-state.hpp +++ b/src/game/states/nest-selection-state.hpp @@ -25,6 +25,7 @@ #include #include #include +#include class nest_selection_state: public game_state { @@ -48,6 +49,10 @@ private: std::shared_ptr worker_model; bool mouse_look{false}; + bool mouse_drag{false}; + bool adjust_exposure{false}; + bool adjust_time{false}; + bool adjust_zoom{false}; bool moving{false}; float2 movement_direction{0.0f, 0.0f}; @@ -74,6 +79,8 @@ private: double first_person_camera_pitch{0.0}; std::shared_ptr larva_ik_solver; + + std::shared_ptr sky_probe; }; #endif // ANTKEEPER_NEST_SELECTION_STATE_HPP diff --git a/src/game/states/nest-view-state.cpp b/src/game/states/nest-view-state.cpp index ad21b96..e98b12d 100644 --- a/src/game/states/nest-view-state.cpp +++ b/src/game/states/nest-view-state.cpp @@ -85,7 +85,7 @@ nest_view_state::nest_view_state(::game& ctx): game_state(ctx) { - debug::log::trace("Entering nest selection state..."); + debug::log::trace("Entering nest view state..."); // Create world if not yet created if (ctx.entities.find("earth") == ctx.entities.end()) @@ -125,11 +125,10 @@ nest_view_state::nest_view_state(::game& ctx): // ctx.underground_directional_light->set_shadow_cascade_distribution(0.8f); // ctx.underground_scene->add_object(*ctx.underground_directional_light); - // Create ambient light - ctx.underground_ambient_light = std::make_unique(); - ctx.underground_ambient_light->set_color({1.0f, 1.0f, 1.0f}); - ctx.underground_ambient_light->set_illuminance(0.075f); - ctx.underground_scene->add_object(*ctx.underground_ambient_light); + ctx.underground_clear_pass->set_clear_color({0.214f, 0.214f, 0.214f, 1.0f}); + light_probe = std::make_shared(); + light_probe->set_luminance_texture(ctx.resource_manager->load("grey-furnace.tex")); + ctx.underground_scene->add_object(*light_probe); //const float color_temperature = 5000.0f; //const math::vector3 light_color = color::aces::ap1.from_xyz * color::cat::matrix(color::illuminant::deg2::d50, color::aces::white_point) * color::cct::to_xyz(color_temperature); @@ -183,6 +182,14 @@ nest_view_state::nest_view_state(::game& ctx): // Create cocoon auto cocoon_eid = ctx.entity_registry->create(); ctx.entity_registry->emplace(cocoon_eid, std::make_shared(worker_phenome.cocoon->model), std::uint8_t{2}); + ctx.entity_registry->patch + ( + cocoon_eid, + [&](auto& component) + { + component.object->set_translation({-5.0f, 0.0f, 5.0f}); + } + ); // Create larva auto larva_eid = ctx.entity_registry->create(); @@ -206,7 +213,7 @@ nest_view_state::nest_view_state(::game& ctx): suzanne_eid, [&](auto& component) { - component.object->set_translation({0.0f, 0.0f, 0.0f}); + component.object->set_translation({-13.0f, 0.0f, -5.0f}); } ); @@ -264,12 +271,12 @@ nest_view_state::nest_view_state(::game& ctx): // Refresh frame scheduler ctx.frame_scheduler.refresh(); - debug::log::trace("Entered nest selection state"); + debug::log::trace("Entered nest view state"); } nest_view_state::~nest_view_state() { - debug::log::trace("Exiting nest selection state..."); + debug::log::trace("Exiting nest view state..."); // Disable game controls ::disable_game_controls(ctx); @@ -277,7 +284,7 @@ nest_view_state::~nest_view_state() destroy_third_person_camera_rig(); - debug::log::trace("Exited nest selection state"); + debug::log::trace("Exited nest view state"); } void nest_view_state::create_third_person_camera_rig() diff --git a/src/game/states/nest-view-state.hpp b/src/game/states/nest-view-state.hpp index 3d34e49..39cf077 100644 --- a/src/game/states/nest-view-state.hpp +++ b/src/game/states/nest-view-state.hpp @@ -29,6 +29,7 @@ #include #include #include +#include class nest_view_state: public game_state { @@ -105,6 +106,7 @@ private: std::vector> camera_presets{10}; std::shared_ptr light_rectangle_emissive; + std::shared_ptr light_probe; }; #endif // ANTKEEPER_NEST_VIEW_STATE_HPP diff --git a/src/game/systems/astronomy-system.cpp b/src/game/systems/astronomy-system.cpp index 221dc03..dfd3c8e 100644 --- a/src/game/systems/astronomy-system.cpp +++ b/src/game/systems/astronomy-system.cpp @@ -47,7 +47,6 @@ astronomy_system::astronomy_system(entity::registry& registry): reference_body_eid(entt::null), transmittance_samples(0), sun_light(nullptr), - sky_light(nullptr), moon_light(nullptr), sky_pass(nullptr), starlight_illuminance{0, 0, 0} @@ -91,8 +90,6 @@ astronomy_system::~astronomy_system() void astronomy_system::update(float t, float dt) { - double3 sky_light_illuminance = {0.0, 0.0, 0.0}; - // Add scaled timestep to current time set_time(time_days + dt * time_scale); @@ -119,6 +116,11 @@ void astronomy_system::update(float t, float dt) if (!reference_body || !reference_orbit) return; + // if (sky_pass) + // { + // sky_pass->set_ground_albedo(math::vector3{1.0f, 1.0f, 1.0f} * static_cast(reference_body->albedo)); + // } + // Update ICRF to EUS transformation update_icrf_to_eus(*reference_body, *reference_orbit); @@ -183,7 +185,7 @@ void astronomy_system::update(float t, float dt) const geom::ray ray = {{0, 0, 0}, observer_blackbody_direction_eus}; // Integrate atmospheric spectral transmittance factor between observer and blackbody - const double3 transmittance = integrate_transmittance(*observer, *reference_body, *reference_atmosphere, ray); + double3 transmittance = integrate_transmittance(*observer, *reference_body, *reference_atmosphere, ray); // Attenuate illuminance from blackbody reaching observer by spectral transmittance factor observer_blackbody_transmitted_illuminance *= transmittance; @@ -203,25 +205,16 @@ void astronomy_system::update(float t, float dt) ); sun_light->set_illuminance(static_cast(math::max(observer_blackbody_transmitted_illuminance))); - sun_light->set_color(math::vector3(observer_blackbody_transmitted_illuminance / math::max(observer_blackbody_transmitted_illuminance))); - } - - // Update sky light - if (sky_light != nullptr) - { - // Calculate sky illuminance - double3 blackbody_position_enu_spherical = physics::orbit::frame::enu::spherical(enu_to_eus.inverse() * blackbody_position_eus); - const double sky_illuminance = 25000.0 * std::max(0.0, std::sin(blackbody_position_enu_spherical.y())); - - // Add sky illuminance to sky light illuminance - sky_light_illuminance += {sky_illuminance, sky_illuminance, sky_illuminance}; - - // Add starlight illuminance to sky light illuminance - sky_light_illuminance += starlight_illuminance; - // Update sky light - sky_light->set_illuminance(static_cast(math::max(sky_light_illuminance))); - sky_light->set_color(math::vector3(sky_light_illuminance / math::max(sky_light_illuminance))); + const auto max_component = math::max(observer_blackbody_transmitted_illuminance); + if (max_component > 0.0) + { + sun_light->set_color(math::vector3(observer_blackbody_transmitted_illuminance / max_component)); + } + else + { + sun_light->set_color({}); + } } // Upload blackbody params to sky pass @@ -274,8 +267,8 @@ void astronomy_system::update(float t, float dt) double3 observer_reflector_transmittance = {1, 1, 1}; if (reference_atmosphere) { - const geom::ray ray = {{0, 0, 0}, observer_reflector_direction_eus}; - observer_reflector_transmittance = integrate_transmittance(*observer, *reference_body, *reference_atmosphere, ray); + // const geom::ray ray = {{0, 0, 0}, observer_reflector_direction_eus}; + // observer_reflector_transmittance = integrate_transmittance(*observer, *reference_body, *reference_atmosphere, ray); } // Measure luminance of observer reference body as seen by reflector @@ -302,14 +295,23 @@ void astronomy_system::update(float t, float dt) this->sky_pass->set_moon_illuminance(float3(observer_reflector_illuminance / observer_reflector_transmittance), float3(observer_reflector_illuminance)); } - if (this->moon_light) + if (moon_light) { const float3 reflector_up_eus = float3(icrf_to_eus.r * double3{0, 0, 1}); - this->moon_light->set_illuminance(static_cast(math::max(observer_reflector_illuminance))); - this->moon_light->set_color(math::vector3(observer_reflector_illuminance / math::max(observer_reflector_illuminance))); + moon_light->set_illuminance(static_cast(math::max(observer_reflector_illuminance))); + + const auto max_component = math::max(observer_reflector_illuminance); + if (max_component > 0.0) + { + moon_light->set_color(math::vector3(observer_reflector_illuminance / max_component)); + } + else + { + moon_light->set_color({}); + } - this->moon_light->set_rotation + moon_light->set_rotation ( math::look_rotation ( @@ -356,11 +358,6 @@ void astronomy_system::set_sun_light(scene::directional_light* light) sun_light = light; } -void astronomy_system::set_sky_light(scene::ambient_light* light) -{ - sky_light = light; -} - void astronomy_system::set_moon_light(scene::directional_light* light) { moon_light = light; @@ -391,9 +388,15 @@ void astronomy_system::set_sky_pass(::render::sky_pass* pass) const auto reference_body = registry.try_get(reference_body_eid); if (reference_body) + { sky_pass->set_planet_radius(static_cast(reference_body->radius)); + // sky_pass->set_ground_albedo(math::vector3{1.0f, 1.0f, 1.0f} * static_cast(reference_body->albedo)); + } else + { sky_pass->set_planet_radius(0.0f); + // sky_pass->set_ground_albedo({0.0f, 0.0f, 0.0f}); + } } } } @@ -560,7 +563,7 @@ void astronomy_system::update_icrf_to_eus(const ::celestial_body_component& body double3 astronomy_system::integrate_transmittance(const ::observer_component& observer, const ::celestial_body_component& body, const ::atmosphere_component& atmosphere, geom::ray ray) const { - double3 transmittance = {1, 1, 1}; + math::vector3 transmittance = {1, 1, 1}; // Make ray height relative to center of reference body ray.origin.y() += body.radius + observer.elevation; @@ -572,24 +575,47 @@ double3 astronomy_system::integrate_transmittance(const ::observer_component& ob // Check for intersection between the ray and atmosphere auto intersection = geom::intersection(ray, atmosphere_sphere); - if (intersection) + if (intersection && std::get<1>(*intersection) > 0.0) { - // Get point of intersection - const double3 intersection_point = ray.extrapolate(std::get<1>(*intersection)); + // Determine height at ray origin and cosine of the angle between the ray direction and local zenith direction at ray origin + const double height = math::length(ray.origin); + const double cos_view_zenith = math::dot(ray.direction, ray.origin) / height; + + // Precalculate terms re-used in sample height calculation + const double sqr_height = height * height; + const double height_cos_view_zenith = height * cos_view_zenith; + const double two_height_cos_view_zenith = 2.0 * height_cos_view_zenith; + + // Get distance to upper limit of atmosphere + const double sample_end_distance = std::get<1>(*intersection); + + // Integrate atmospheric particle densities + math::vector3 densities{}; + double previous_sample_distance = 0.0; + for (std::size_t i = 0; i < transmittance_samples; ++i) + { + // Determine distance along sample ray to sample point and length of the sample + const double sample_distance = (static_cast(i) + 0.5) / static_cast(transmittance_samples) * sample_end_distance; + const double sample_length = sample_distance - previous_sample_distance; + previous_sample_distance = sample_distance; + + // Calculate sample elevation + const double sample_height = std::sqrt(sample_distance * sample_distance + sqr_height + two_height_cos_view_zenith * sample_distance); + const double sample_elevation = sample_height - body.radius; + + // Weigh and sum atmospheric particle densities at sample elevation + densities.x() += physics::gas::atmosphere::density::exponential(1.0, sample_elevation, atmosphere.rayleigh_scale_height) * sample_length; + densities.y() += physics::gas::atmosphere::density::exponential(1.0, sample_elevation, atmosphere.mie_scale_height) * sample_length; + densities.x() += physics::gas::atmosphere::density::triangular(1.0, sample_elevation, atmosphere.ozone_lower_limit, atmosphere.ozone_upper_limit, atmosphere.ozone_mode) * sample_length; + } - // Integrate optical of Rayleigh, Mie, and ozone particles - const double optical_depth_r = physics::gas::atmosphere::optical_depth_exp(ray.origin, intersection_point, body.radius, atmosphere.rayleigh_scale_height, transmittance_samples); - const double optical_depth_m = physics::gas::atmosphere::optical_depth_exp(ray.origin, intersection_point, body.radius, atmosphere.mie_scale_height, transmittance_samples); - const double optical_depth_o = physics::gas::atmosphere::optical_depth_tri(ray.origin, intersection_point, body.radius, atmosphere.ozone_lower_limit, atmosphere.ozone_upper_limit, atmosphere.ozone_mode, transmittance_samples); + // Calculate extinction coefficients from integrated atmospheric particle densities + const math::vector3 extinction = densities.x() * atmosphere.rayleigh_scattering + + densities.y() * atmosphere.mie_extinction + + densities.z() * atmosphere.ozone_absorption; - // Calculate transmittance factor due to scattering and absorption - const double3 extinction_r = atmosphere.rayleigh_scattering * optical_depth_r; - const double extinction_m = atmosphere.mie_extinction * optical_depth_m; - const double3 extinction_o = atmosphere.ozone_absorption * optical_depth_o; - transmittance = extinction_r + double3{extinction_m, extinction_m, extinction_m} + extinction_o; - transmittance.x() = std::exp(-transmittance.x()); - transmittance.y() = std::exp(-transmittance.y()); - transmittance.z() = std::exp(-transmittance.z()); + // Calculate transmittance factor from extinction coefficients + transmittance = {std::exp(-extinction.x()), std::exp(-extinction.y()), std::exp(-extinction.z())}; } return transmittance; diff --git a/src/game/systems/astronomy-system.hpp b/src/game/systems/astronomy-system.hpp index bcba74b..d8a39a8 100644 --- a/src/game/systems/astronomy-system.hpp +++ b/src/game/systems/astronomy-system.hpp @@ -23,7 +23,6 @@ #include "game/systems/updatable-system.hpp" #include #include -#include #include #include #include @@ -81,11 +80,15 @@ public: void set_transmittance_samples(std::size_t samples); void set_sun_light(scene::directional_light* light); - void set_sky_light(scene::ambient_light* light); void set_moon_light(scene::directional_light* light); void set_starlight_illuminance(const double3& illuminance); void set_sky_pass(::render::sky_pass* pass); + [[nodiscard]] inline double get_time() const noexcept + { + return time_days; + } + private: void on_observer_modified(entity::registry& registry, entity::id entity_id); void on_observer_destroyed(entity::registry& registry, entity::id entity_id); @@ -152,7 +155,6 @@ private: math::transformation::se3 icrf_to_eus; scene::directional_light* sun_light; - scene::ambient_light* sky_light; scene::directional_light* moon_light; ::render::sky_pass* sky_pass; double3 starlight_illuminance; diff --git a/src/game/systems/atmosphere-system.cpp b/src/game/systems/atmosphere-system.cpp index 5a25734..e774340 100644 --- a/src/game/systems/atmosphere-system.cpp +++ b/src/game/systems/atmosphere-system.cpp @@ -144,7 +144,7 @@ void atmosphere_system::update_sky_pass() sky_pass->set_rayleigh_parameters(static_cast(component->rayleigh_scale_height), math::vector(component->rayleigh_scattering)); sky_pass->set_mie_parameters(static_cast(component->mie_scale_height), static_cast(component->mie_scattering), static_cast(component->mie_extinction), static_cast(component->mie_anisotropy)); sky_pass->set_ozone_parameters(static_cast(component->ozone_lower_limit), static_cast(component->ozone_upper_limit), static_cast(component->ozone_mode), math::vector(component->ozone_absorption)); - sky_pass->set_airglow_illuminance(math::vector(component->airglow_illuminance)); + sky_pass->set_airglow_luminance(math::vector(component->airglow_luminance)); } void atmosphere_system::on_atmosphere_construct(entity::registry& registry, entity::id entity_id) diff --git a/src/game/world.cpp b/src/game/world.cpp index 11913b9..f754804 100644 --- a/src/game/world.cpp +++ b/src/game/world.cpp @@ -63,7 +63,6 @@ #include #include #include -#include #include #include #include @@ -169,7 +168,7 @@ void set_time(::game& ctx, double t) ctx.astronomy_system->set_time(t); ctx.orbit_system->set_time(t); - debug::log::info("Set time to UT1 {}", t); + // debug::log::info("Set time to UT1 {}", t); } catch (const std::exception& e) { @@ -368,16 +367,11 @@ void create_sun(::game& ctx) ctx.sun_light->set_shadow_cascade_coverage(0.15f); ctx.sun_light->set_shadow_cascade_distribution(0.8f); - // Create sky ambient light scene object - ctx.sky_light = std::make_unique(); - // Add sun light scene objects to surface scene ctx.surface_scene->add_object(*ctx.sun_light); - ctx.surface_scene->add_object(*ctx.sky_light); // Pass direct sun light scene object to shadow map pass and astronomy system ctx.astronomy_system->set_sun_light(ctx.sun_light.get()); - ctx.astronomy_system->set_sky_light(ctx.sky_light.get()); } debug::log::trace("Generated Sun"); @@ -517,6 +511,7 @@ void enter_ecoregion(::game& ctx, const ecoregion& ecoregion) // Setup sky ctx.sky_pass->set_sky_model(ctx.resource_manager->load("celestial-hemisphere.mdl")); + ctx.sky_pass->set_ground_albedo(ecoregion.terrain_albedo); auto terrestrial_hemisphere_model = ctx.resource_manager->load("terrestrial-hemisphere.mdl"); terrestrial_hemisphere_model->get_groups().front().material = ecoregion.horizon_material; ctx.ground_pass->set_ground_model(terrestrial_hemisphere_model);