17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 17:59:19 +02:00
Files
maszyna/betterRenderer/renderer/source/sky.cpp
maj00r 26501b0fd7 hoist standard includes above everything else
Ordering them before the imports in the same file was not enough: a
project header on line 1 can import a module several levels down whose
global module fragment already pulled in the same standard header, and
that include guard does not cross the module boundary. Putting them
first means the guard is set before anything can import.

Skips vendored single-header libraries and anything inside an #if.
2026-09-10 01:02:17 +02:00

479 lines
18 KiB
C++

#include <array>
#include <limits>
#include <memory>
#include "sky.h"
#include <nvrhi/utils.h>
#include "environment.h"
#include "nvrendererbackend.h"
#include "imgui/imgui.h"
import eu07.simulation.simulationenvironment;
import eu07.simulation.simulationtime;
import eu07.glm;
namespace {
static const float PI = 3.14159265358979323846f;
static const float INV_PI = 0.31830988618379067154f;
static const float INV_4PI = 0.25f * INV_PI;
static const float PHASE_ISOTROPIC = INV_4PI;
static const float RAYLEIGH_PHASE_SCALE = (3.f / 16.f) * INV_PI;
static const float g = 0.8f;
static const float gg = g * g;
static const float EARTH_RADIUS = 6371.0e3f; // km
static const float ATMOSPHERE_THICKNESS = 100.0e3f; // km
static const float ATMOSPHERE_RADIUS = EARTH_RADIUS + ATMOSPHERE_THICKNESS;
static const float EXPOSURE = -4.0;
// Extraterrestial Solar Irradiance Spectra, units W * m^-2 * nm^-1
// https://www.nrel.gov/grid/solar-resource/spectra.html
static const glm::vec4 sun_spectral_irradiance =
glm::vec4(1.679, 1.828, 1.986, 1.307);
// Rayleigh scattering coefficient at sea level, units km^-1
// "Rayleigh-scattering calculations for the terrestrial atmosphere"
// by Anthony Bucholtz (1995).
static const glm::vec4 molecular_scattering_coefficient_base =
glm::vec4(6.605e-6, 1.067e-5, 1.842e-5, 3.156e-5);
// Fog scattering/extinction cross section, units m^2 / molecules
// Mie theory results for IOR of 1.333. Particle size is a log normal
// distribution of mean diameter=15 and std deviation=0.4
static const glm::vec4 fog_scattering_cross_section =
glm::vec4(5.015e-10, 4.987e-10, 4.966e-10, 4.949e-10);
// Ozone absorption cross section, units m^2 / molecules
// "High spectral resolution ozone absorption cross-sections"
// by V. Gorshelev et al. (2014).
static const glm::vec4 ozone_absorption_cross_section =
glm::vec4(3.472e-25, 3.914e-25, 1.349e-25, 11.03e-27);
static const glm::mat4x3 M = {
137.672389239975, -8.632904716299537, -1.7181567391931372,
32.549094028629234, 91.29801417199785, -12.005406444382531,
-38.91428392614275, 34.31665471469816, 29.89044807197628,
8.572844237945445, -11.103384660054624, 117.47585277566478};
} // namespace
Sky::Sky(NvRenderer* renderer, MaEnvironment *environment)
: m_backend(renderer->GetBackend()), MaResourceRegistry(renderer), m_environment(environment) {
m_transmittance_pass = std::make_shared<SkyTransmittancePass>(this);
m_aerial_lut = std::make_shared<SkyAerialLut>(this);
}
void Sky::Init() {
InitResourceRegistry();
m_sky_constants = m_backend->GetDevice()->createBuffer(
nvrhi::utils::CreateVolatileConstantBufferDesc(sizeof(SkyConstants),
"Sky constants", 16)
.setInitialState(nvrhi::ResourceStates::ConstantBuffer)
.setKeepInitialState(true));
m_transmittance_pass->Init();
m_aerial_lut->Init();
RegisterResource(true, "sky_aerial_lut", m_aerial_lut->m_lut,
nvrhi::ResourceType::Texture_SRV);
}
void Sky::Render(nvrhi::ICommandList* command_list,
const glm::dmat4& projection, const glm::dmat4& view) {
UpdateConstants(command_list);
m_transmittance_pass->Render(command_list);
m_aerial_lut->Render(command_list, projection, view);
}
float Sky::GetOzoneMean() const {
float month = glm::saturate(simulation::Time.year_day() / 366.f) * 12.f + .5f;
float month_fract = glm::fract(month);
int month_l = glm::floor(month);
static std::array<float, 12> ozone_means{347.f, 370.f, 381.f, 384.f,
372.f, 352.f, 333.f, 317.f,
298.f, 285.f, 290.f, 315.f};
return glm::lerp(ozone_means[month_l], ozone_means[(month_l + 1) % 12],
month_fract);
}
glm::vec4 Sky::GetAerosolAbsorptionCrossSection() const {
switch (m_aerosol_preset) {
case AerosolPreset::Rural:
return {5.0393e-23f, 8.0765e-23f, 1.3823e-22f, 2.3383e-22f};
case AerosolPreset::Urban:
return {2.8722e-24f, 4.6168e-24f, 7.9706e-24f, 1.3578e-23f};
default:
return glm::vec4(0.f);
}
}
glm::vec4 Sky::GetAerosolScatteringCrossSection() const {
switch (m_aerosol_preset) {
case AerosolPreset::Rural:
return {2.6004e-22f, 2.4844e-22f, 2.8362e-22f, 2.7494e-22f};
case AerosolPreset::Urban:
return {1.5908e-22f, 1.7711e-22f, 2.0942e-22f, 2.4033e-22f};
default:
return glm::vec4(0.f);
}
}
float Sky::GetAerosolBaseDensity() const {
switch (m_aerosol_preset) {
case AerosolPreset::Rural:
return 8.544e15f;
case AerosolPreset::Urban:
return 1.3681e17f;
default:
return 0.f;
}
}
float Sky::GetAerosolBackgroundDividedByBaseDensity() const {
switch (m_aerosol_preset) {
case AerosolPreset::Rural:
return 2e3f / GetAerosolBaseDensity();
case AerosolPreset::Urban:
return 2e3f / GetAerosolBaseDensity();
default:
return 0.f;
}
}
float Sky::GetAerosolHeightScale() const {
switch (m_aerosol_preset) {
case AerosolPreset::Rural:
return .73f;
case AerosolPreset::Urban:
return .73f;
default:
return 0.f;
}
}
float Sky::GetFogDensity() const {
return 3e7f * glm::pow(0.99937f, glm::min(3e7f, m_visibility));
}
void Sky::UpdateConstants(nvrhi::ICommandList* command_list) {
SkyConstants constants{};
constants.g_AerosolAbsorptionCrossSection =
GetAerosolAbsorptionCrossSection();
constants.g_AerosolScatteringCrossSection =
GetAerosolScatteringCrossSection();
constants.g_AerosolHeightScale = GetAerosolHeightScale();
constants.g_AerosolBaseDensity = GetAerosolBaseDensity();
constants.g_AerosolBackgroundDividedByBaseDensity =
GetAerosolBackgroundDividedByBaseDensity();
constants.g_GroundAlbedo = glm::vec4{m_ground_albedo};
constants.g_AerosolTurbidity = m_aerosol_turbidity;
constants.g_OzoneMean = GetOzoneMean();
constants.g_FogDensity = GetFogDensity();
constants.g_FogHeightOffset = m_fog_height_offset;
constants.g_FogHeightScale = m_fog_height_scale;
command_list->writeBuffer(m_sky_constants, &constants, sizeof(constants));
}
void Sky::OnGui() {
if (!m_gui_active) return;
if (ImGui::Begin("Sky settings", &m_gui_active,
ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::SliderFloat("Visibility", &m_visibility, 10.f, 3e4f);
ImGui::SliderFloat("Fog Offset", &m_fog_height_offset, 0.f, 10.f);
ImGui::SliderFloat("Fog Height", &m_fog_height_scale, .01f, 10.f);
if (ImGui::BeginCombo("Aerosol Type",
GetAerosolTypeDesc(m_aerosol_preset))) {
for (int i = 0; i < static_cast<int>(AerosolPreset::Num); ++i) {
AerosolPreset preset = static_cast<AerosolPreset>(i);
if (ImGui::Selectable(GetAerosolTypeDesc(preset),
preset == m_aerosol_preset)) {
m_aerosol_preset = preset;
}
}
ImGui::EndCombo();
}
ImGui::SliderFloat("Aerosol Turbidity", &m_aerosol_turbidity, 0., 10.);
ImGui::SliderFloat("Ground Albedo", &m_ground_albedo, 0., 10.);
ImGui::End();
}
}
void Sky::ShowGui() { m_gui_active = true; }
const char* Sky::GetAerosolTypeDesc(AerosolPreset preset) {
switch (preset) {
case AerosolPreset::Rural:
return "Rural";
case AerosolPreset::Urban:
return "Urban";
default:
return "INVALID";
}
}
glm::vec4 Sky::GetMolecularScatteringCoefficient(float h) const {
return molecular_scattering_coefficient_base *
glm::exp(-0.07771971f * glm::pow(h, 1.16364243f));
}
glm::vec4 Sky::GetMolecularAbsorptionCoefficient(float h) const {
h += 1e-4f; // Avoid division by 0
float t = glm::log(h) - 3.22261f;
float density = 3.78547397e17f * (1.f / h) * glm::exp(-t * t * 5.55555555f);
return ozone_absorption_cross_section * GetOzoneMean() * density;
}
glm::vec4 Sky::GetFogScatteringCoefficient(float h) const {
if (GetFogDensity() > 0.f) {
return fog_scattering_cross_section * GetFogDensity() *
glm::min(1.f,
glm::exp((-h + m_fog_height_offset) / m_fog_height_scale));
} else {
return glm::vec4{0.f};
}
}
glm::vec3 Sky::CalcSunColor() const {
glm::vec4 transmittance =
ComputeTransmittance(simulation::Environment.sun().getDirection(), 32);
return LinearSrgbFromSpectralSamples(sun_spectral_irradiance *
transmittance) *
glm::exp(EXPOSURE);
}
glm::vec3 Sky::CalcMoonColor() const {
float phase = glm::acos(dot(simulation::Environment.sun().getDirection(),
simulation::Environment.moon().getDirection())) *
glm::one_over_pi<float>();
glm::vec4 transmittance =
ComputeTransmittance(simulation::Environment.moon().getDirection(), 32);
return .07f * phase *
LinearSrgbFromSpectralSamples(sun_spectral_irradiance *
transmittance) *
glm::exp(EXPOSURE);
}
void Sky::CalcLighting(glm::vec3& direction, glm::vec3& color) const {
const glm::vec3 moon = CalcMoonColor();
const glm::vec3 sun = CalcSunColor();
if (dot(moon, moon) > dot(sun, sun)) {
direction = simulation::Environment.moon().getDirection();
color = moon;
} else {
direction = simulation::Environment.sun().getDirection();
color = sun;
}
}
glm::vec3 Sky::LinearSrgbFromSpectralSamples(glm::vec4 L) {
glm::vec3 c = M * L;
return c.r * glm::vec3(0.4123908f, 0.21263901f, 0.01933082f) +
c.g * glm::vec3(0.35758434f, 0.71516868f, 0.11919478f) +
c.b * glm::vec3(0.18048079f, 0.07219232f, 0.95053215f);
}
float Sky::RaySphereIntersection(glm::vec3 ro, glm::vec3 rd, float radius) {
float b = glm::dot(ro, rd);
float c = glm::dot(ro, ro) - radius * radius;
if (c > 0.f && b > 0.f) return -1.f;
float d = b * b - c;
if (d < 0.f) return -1.f;
if (d > b * b) return (-b + glm::sqrt(d));
return (-b - glm::sqrt(d));
}
float Sky::GetAerosolDensity(float h) const {
return GetAerosolBaseDensity() * (exp(-h / GetAerosolHeightScale()) +
GetAerosolBackgroundDividedByBaseDensity());
}
void Sky::GetAtmosphereCollisionCoefficients(
float h, glm::vec4& aerosol_absorption, glm::vec4& aerosol_scattering,
glm::vec4& molecular_absorption, glm::vec4& molecular_scattering,
glm::vec4& fog_scattering, glm::vec4& extinction) const {
h = glm::max(h, 1.e-3f); // In case height is negative
h *= 1.e-3f;
float aerosol_density = GetAerosolDensity(h);
aerosol_absorption = GetAerosolAbsorptionCrossSection() * aerosol_density *
m_aerosol_turbidity;
aerosol_scattering = GetAerosolScatteringCrossSection() * aerosol_density *
m_aerosol_turbidity;
molecular_absorption = GetMolecularAbsorptionCoefficient(h);
molecular_scattering = GetMolecularScatteringCoefficient(h);
fog_scattering = GetFogScatteringCoefficient(h);
extinction = aerosol_absorption + aerosol_scattering + molecular_absorption +
molecular_scattering + fog_scattering;
}
glm::vec4 Sky::ComputeTransmittance(const glm::vec3& ray_dir, int steps) const {
glm::vec3 ray_origin{0.f, EARTH_RADIUS + Global.pCamera.Pos.y, 0.f};
glm::vec4 transmittance{1.f};
float t_max = std::numeric_limits<float>::max();
float atmos_dist =
RaySphereIntersection(ray_origin, ray_dir, ATMOSPHERE_RADIUS);
if (RaySphereIntersection(ray_origin, ray_dir, EARTH_RADIUS) >= 0.0) {
return glm::vec4(0.f, 0.f, 0.f, 0.f);
}
t_max = glm::min(t_max, atmos_dist);
float dt = t_max / float(steps);
for (int i = 0; i < steps; ++i) {
float t = (float(i) + 0.5) * dt;
glm::vec3 x_t = ray_origin + ray_dir * t;
float distance_to_earth_center = glm::length(x_t);
float altitude = distance_to_earth_center - EARTH_RADIUS;
glm::vec4 aerosol_absorption, aerosol_scattering;
glm::vec4 molecular_absorption, molecular_scattering;
glm::vec4 fog_scattering;
glm::vec4 extinction;
GetAtmosphereCollisionCoefficients(
altitude, aerosol_absorption, aerosol_scattering, molecular_absorption,
molecular_scattering, fog_scattering, extinction);
glm::vec4 step_transmittance = glm::exp(-dt * extinction);
transmittance *= step_transmittance;
}
return transmittance;
}
void SkyTransmittancePass::Init() {
m_pixel_shader =
m_backend->CreateShader("sky_transmittance", nvrhi::ShaderType::Pixel);
m_output = m_backend->GetDevice()->createTexture(
nvrhi::TextureDesc()
.setFormat(nvrhi::Format::RGBA8_UNORM)
.setWidth(128)
.setHeight(128)
.setIsRenderTarget(true)
.setInitialState(nvrhi::ResourceStates::ShaderResource)
.setKeepInitialState(true));
nvrhi::utils::CreateBindingSetAndLayout(
m_backend->GetDevice(), nvrhi::ShaderType::Pixel, 0,
nvrhi::BindingSetDesc().addItem(
nvrhi::BindingSetItem::ConstantBuffer(13, m_sky->m_sky_constants)),
m_binding_layout, m_binding_set);
m_framebuffer = m_backend->GetDevice()->createFramebuffer(
nvrhi::FramebufferDesc().addColorAttachment(m_output));
FullScreenPass::Init();
}
void SkyTransmittancePass::CreatePipelineDesc(
nvrhi::GraphicsPipelineDesc& pipeline_desc) {
FullScreenPass::CreatePipelineDesc(pipeline_desc);
pipeline_desc.setPixelShader(m_pixel_shader);
pipeline_desc.addBindingLayout(m_binding_layout);
}
void SkyTransmittancePass::Render(nvrhi::ICommandList* command_list) {
command_list->beginMarker("Sky transmittance");
nvrhi::GraphicsState graphics_state;
InitState(graphics_state);
graphics_state.addBindingSet(m_binding_set);
command_list->setGraphicsState(graphics_state);
Draw(command_list);
command_list->endMarker();
}
nvrhi::IFramebuffer* SkyTransmittancePass::GetFramebuffer() {
return m_framebuffer;
}
void SkyAerialLut::Init() {
m_sky_width = 256;
m_sky_height = 512;
m_lut_width = 128;
m_lut_height = 256;
m_lut_slices = 16;
m_cloud_texture = m_sky->m_environment->m_clouds_texture;
m_constant_buffer = m_sky->m_backend->GetDevice()->createBuffer(
nvrhi::utils::CreateVolatileConstantBufferDesc(
sizeof(DispatchConstants), "Sky Aerial LUT Dispatch Constants", 16));
m_lut = m_sky->m_backend->GetDevice()->createTexture(
nvrhi::TextureDesc()
.setDimension(nvrhi::TextureDimension::Texture2DArray)
.setWidth(m_lut_width)
.setHeight(m_lut_height)
.setArraySize(m_lut_slices)
.setFormat(nvrhi::Format::RGBA16_FLOAT)
.setIsUAV(true)
.setInitialState(nvrhi::ResourceStates::Common)
.setKeepInitialState(true)
.setDebugName("Sky Aerial LUT"));
m_sky_texture = m_sky->m_backend->GetDevice()->createTexture(
nvrhi::TextureDesc()
.setWidth(m_sky_width)
.setHeight(m_sky_height)
.setFormat(nvrhi::Format::RGBA16_FLOAT)
.setIsUAV(true)
.setInitialState(nvrhi::ResourceStates::Common)
.setKeepInitialState(true)
.setDebugName("Sky Texture"));
}
void SkyAerialLut::Render(nvrhi::ICommandList* command_list,
const glm::dmat4& projection,
const glm::dmat4& view) {
if (!m_pso_sky) {
auto shader_lut = m_sky->m_backend->CreateShader(
"sky_aerial_lut", nvrhi::ShaderType::Compute);
auto shader_sky =
m_sky->m_backend->CreateShader("sky", nvrhi::ShaderType::Compute);
auto sampler = m_sky->m_backend->GetDevice()->createSampler(
nvrhi::SamplerDesc()
.setAllAddressModes(nvrhi::SamplerAddressMode::ClampToEdge)
.setAllFilters(true));
nvrhi::BindingLayoutHandle binding_layout_lut;
nvrhi::BindingLayoutHandle binding_layout_sky;
nvrhi::utils::CreateBindingSetAndLayout(
m_sky->m_backend->GetDevice(), nvrhi::ShaderType::Compute, 0,
nvrhi::BindingSetDesc()
.addItem(
nvrhi::BindingSetItem::ConstantBuffer(0, m_constant_buffer))
.addItem(nvrhi::BindingSetItem::ConstantBuffer(
13, m_sky->m_sky_constants))
.addItem(nvrhi::BindingSetItem::Texture_UAV(0, m_lut))
.addItem(nvrhi::BindingSetItem::Texture_SRV(
13, m_sky->m_transmittance_pass->m_output))
.addItem(nvrhi::BindingSetItem::Texture_SRV(15, m_sky->m_environment->m_clouds_texture))
.addItem(nvrhi::BindingSetItem::Sampler(13, sampler)),
binding_layout_lut, m_bindings_lut);
nvrhi::utils::CreateBindingSetAndLayout(
m_sky->m_backend->GetDevice(), nvrhi::ShaderType::Compute, 0,
nvrhi::BindingSetDesc()
.addItem(
nvrhi::BindingSetItem::ConstantBuffer(0, m_constant_buffer))
.addItem(nvrhi::BindingSetItem::ConstantBuffer(
13, m_sky->m_sky_constants))
.addItem(nvrhi::BindingSetItem::Texture_UAV(1, m_sky_texture))
.addItem(nvrhi::BindingSetItem::Texture_SRV(
13, m_sky->m_transmittance_pass->m_output))
.addItem(nvrhi::BindingSetItem::Sampler(13, sampler)),
binding_layout_sky, m_bindings_sky);
m_pso_lut = m_sky->m_backend->GetDevice()->createComputePipeline(
nvrhi::ComputePipelineDesc()
.addBindingLayout(binding_layout_lut)
.setComputeShader(shader_lut));
m_pso_sky = m_sky->m_backend->GetDevice()->createComputePipeline(
nvrhi::ComputePipelineDesc()
.addBindingLayout(binding_layout_sky)
.setComputeShader(shader_sky));
}
{
DispatchConstants constants{};
constants.g_InverseView = static_cast<glm::mat3>(glm::inverse(view));
constants.g_InverseProjection = glm::inverse(projection);
constants.g_SunDir = simulation::Environment.sun().getDirection();
constants.g_MoonDir = simulation::Environment.moon().getDirection();
constants.g_Altitude = Global.pCamera.Pos.y;
constants.g_MaxDepth = Global.BaseDrawRange * Global.fDistanceFactor;
command_list->writeBuffer(m_constant_buffer, &constants, sizeof(constants));
}
command_list->setComputeState(
nvrhi::ComputeState().setPipeline(m_pso_lut).addBindingSet(
m_bindings_lut));
command_list->dispatch((m_lut_width + 7) / 8, (m_lut_height + 7) / 8, 1);
command_list->setComputeState(
nvrhi::ComputeState().setPipeline(m_pso_sky).addBindingSet(
m_bindings_sky));
command_list->dispatch((m_sky_width + 7) / 8, (m_sky_height + 7) / 8, 1);
}