mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 02:49:19 +02:00
Add sky light, physical atmosphere, auto exposure and bloom to the GL 4.6 renderer
This commit is contained in:
@@ -260,6 +260,7 @@ set(SOURCES
|
||||
"gl/query.cpp"
|
||||
"gl/ssbo.cpp"
|
||||
"gl/cluster_grid.cpp"
|
||||
"gl/readback.cpp"
|
||||
|
||||
"imgui/imgui.cpp"
|
||||
"imgui/imgui_demo.cpp"
|
||||
@@ -333,6 +334,8 @@ if (WITH_OPENGL_MODERN)
|
||||
"rendering/opengl46/opengl46geometrybank.cpp"
|
||||
"rendering/opengl46/opengl46light.cpp"
|
||||
"rendering/opengl46/opengl46skydome.cpp"
|
||||
"rendering/opengl46/opengl46sky.cpp"
|
||||
"rendering/opengl46/opengl46exposure.cpp"
|
||||
"rendering/opengl46/opengl46precipitation.cpp"
|
||||
"rendering/opengl46/opengl46particles.cpp"
|
||||
"rendering/opengl46/opengl46terrain.cpp"
|
||||
@@ -686,4 +689,4 @@ if(ENABLE_AVX2)
|
||||
else()
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE -mavx2)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -49,6 +49,38 @@ Old `eu07.ini` files still load. These entries are read and ignored, with a line
|
||||
| `gfx.lights.clustered yes/no` | `yes` | `yes`: every lit vehicle headlight within 1 km lights the surroundings (see below). `no`: the shading goes through the first `dynamiclights` headlights only, as the old renderer did; useful to compare images or to work around a problem |
|
||||
| `gfx.lights.clusterheatmap yes/no` | `no` | debug view: the number of lights in each cluster, drawn over the image |
|
||||
|
||||
### Sky light and exposure
|
||||
|
||||
All of these are optional; a missing entry takes the default. The switches can also be changed while the simulator
|
||||
runs, in the **Sky and exposure** debug window. With every switch set to `no` the image is as before these options
|
||||
existed.
|
||||
|
||||
| Entry | Default | Meaning |
|
||||
|---|---|---|
|
||||
| `gfx.ambient.sh yes/no` | `yes` | the ambient light depends on where a surface faces: more from the bright part of the sky, less from the ground below. The average stays the ambient light the simulation works out |
|
||||
| `gfx.ambient.sh.strength` | `1.0` | 0..2: how much the ambient light differs between directions (0 = uniform, as before) |
|
||||
| `gfx.ambient.groundalbedo` | `0.1` | 0..1: how much light the ground below the horizon gives back (to the directional ambient light and to the atmosphere) |
|
||||
| `gfx.envmap.ggx yes/no` | `yes` | rough reflections read the environment map prefiltered with the GGX lobe (made in a compute pass after each update of the map) instead of its plain mipmaps |
|
||||
| `gfx.atmosphere yes/no` | `yes` | the sky, the colour of the sunlight, the ambient light and the fog colour come from a physical model of the atmosphere (Hillaire 2020) during the day; the old sky takes over between 10 degrees of sun elevation and 6 degrees below the horizon, and at night. Render only: the simulation keeps its own values |
|
||||
| `gfx.atmosphere.sky` | `1.0` | brightness of the drawn sky and of the fog colour (1 = calibrated against the old sky) |
|
||||
| `gfx.atmosphere.ambient` | `1.0` | brightness of the ambient light from the atmosphere |
|
||||
| `gfx.atmosphere.sun` | `1.0` | brightness of the sunlight from the atmosphere |
|
||||
| `gfx.atmosphere.haze` | `1.0` | amount of aerosols on a clear day (whiter, hazier sky); the weather adds to it with the overcast |
|
||||
| `gfx.atmosphere.altitude` | `200` | height of the scenery above the sea level, metres |
|
||||
| `gfx.postfx.autoexposure yes/no` | `yes` | the exposure follows the average brightness of the image (luminance histogram, compute pass) |
|
||||
| `gfx.postfx.autoexposure.key` | `0.3` | average luminance of the image the exposure is set for |
|
||||
| `gfx.postfx.autoexposure.min` | `-1.0` | the most the exposure darkens a bright image, EV |
|
||||
| `gfx.postfx.autoexposure.max` | `0.5` | the most the exposure brightens a dark image, EV. Kept low on purpose: tunnels and the night are not meant to be brightened much |
|
||||
| `gfx.postfx.autoexposure.speed.dark` | `1.0` | rate of adaptation to a darker image, 1/s |
|
||||
| `gfx.postfx.autoexposure.speed.bright` | `3.0` | rate of adaptation to a brighter image, 1/s |
|
||||
| `gfx.postfx.autoexposure.compensation` | `0.0` | fixed exposure correction, EV; applies also with the automatic exposure off |
|
||||
| `gfx.postfx.bloom yes/no` | `yes` | glow around bright parts of the image |
|
||||
| `gfx.postfx.bloom.intensity` | `0.04` | share of the glow in the image |
|
||||
| `gfx.postfx.bloom.threshold` | `0.0` | luminance below which nothing glows; 0 = everything contributes a little (physically based) |
|
||||
|
||||
The automatic exposure and the bloom need the post processing pipeline (`gfx.skippipeline no`, the default).
|
||||
Model previews of the editor use none of these.
|
||||
|
||||
### Changed option
|
||||
|
||||
`dynamiclights` (0..7) used to limit the number of headlight sets that light the surroundings. It is still
|
||||
@@ -81,6 +113,10 @@ editor goes through the first `dynamiclights` headlights, the reflections throug
|
||||
(`overflow`, with its peak). A non-zero overflow means some cluster held more than 64 lights.
|
||||
- **Light clusters** window (in debug mode, menu *Windows*, item *Headlight config* opens the renderer debug
|
||||
windows): the switch for clustered lights, the heat map, the grid state and the same counters.
|
||||
- **Sky and exposure** window (next to the Light clusters window): the switches and scales above, the share of the
|
||||
atmosphere, the colours of the sun, ambient light and fog in use, and the measured luminance and exposure.
|
||||
Shift+F12 shows the GPU time of `atmosphere:` (look-up tables, only when the sun moved or the weather changed),
|
||||
`envmap ggx:` (prefiltering, with each update of the environment map), `exposure:` and `bloom:`.
|
||||
- `gfx.gldebug yes` turns on the OpenGL debug output (`KHR_debug`); messages go to the log.
|
||||
|
||||
## Building
|
||||
|
||||
@@ -27,6 +27,8 @@ public:
|
||||
return m_vertices; }
|
||||
std::vector<glm::vec3> & colors() {
|
||||
return m_colours; }
|
||||
std::vector<glm::vec3> const & colors() const {
|
||||
return m_colours; }
|
||||
std::vector<std::uint16_t> const & indices() const {
|
||||
return m_indices; }
|
||||
auto const & is_dirty() const { return m_dirty; }
|
||||
|
||||
@@ -29,6 +29,19 @@ void gl::cubemap::alloc(GLint format, int width, int height, GLenum components,
|
||||
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
|
||||
}
|
||||
|
||||
void gl::cubemap::alloc_levels(GLenum format, int size, int levels) const
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_CUBE_MAP, *this);
|
||||
glTexStorage2D(GL_TEXTURE_CUBE_MAP, levels, format, size, size);
|
||||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_LEVEL, levels - 1);
|
||||
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
|
||||
}
|
||||
|
||||
void gl::cubemap::bind(int unit) const
|
||||
{
|
||||
glActiveTexture(unit);
|
||||
|
||||
@@ -13,6 +13,9 @@ namespace gl
|
||||
~cubemap();
|
||||
|
||||
void alloc(GLint format, int width, int height, GLenum components, GLenum type) const;
|
||||
// immutable storage of square faces with the given number of mipmap levels, filtered between the levels.
|
||||
// the levels are meant to be written by compute shaders (glBindImageTexture), so format has to be an image format
|
||||
void alloc_levels(GLenum format, int size, int levels) const;
|
||||
void bind(int unit) const;
|
||||
void generate_mipmaps() const;
|
||||
};
|
||||
|
||||
@@ -28,6 +28,25 @@ void gl::glsl_common_setup()
|
||||
"#define CLUSTER_RANGE_SSBO_BINDING " + std::to_string(CLUSTER_RANGE_SSBO_BINDING) + "\n" +
|
||||
"#define CLUSTER_INDEX_SSBO_BINDING " + std::to_string(CLUSTER_INDEX_SSBO_BINDING) + "\n" +
|
||||
"#define CLUSTER_COUNTER_SSBO_BINDING " + std::to_string(CLUSTER_COUNTER_SSBO_BINDING) + "\n" +
|
||||
"#define ENVMAP_GGX_LEVELS " + std::to_string(ENVMAP_GGX_LEVELS) + "\n" +
|
||||
"#define ENVMAP_GGX_ROUGHNESS_MIN " + std::to_string(ENVMAP_GGX_ROUGHNESS_MIN) + "\n" +
|
||||
"#define ENVMAP_GGX_GROUP_SIZE " + std::to_string(ENVMAP_GGX_GROUP_SIZE) + "\n" +
|
||||
"#define SKY_SSBO_BINDING " + std::to_string(SKY_SSBO_BINDING) + "\n" +
|
||||
"#define EXPOSURE_SSBO_BINDING " + std::to_string(EXPOSURE_SSBO_BINDING) + "\n" +
|
||||
"#define EXPOSURE_HISTOGRAM_BINS " + std::to_string(EXPOSURE_HISTOGRAM_BINS) + "\n" +
|
||||
"#define EXPOSURE_HISTOGRAM_GROUP_SIZE " + std::to_string(EXPOSURE_HISTOGRAM_GROUP_SIZE) + "\n" +
|
||||
"#define ATMOSPHERE_UBO_BINDING " + std::to_string(ATMOSPHERE_UBO_BINDING) + "\n" +
|
||||
"#define ATMOSPHERE_RESULT_SSBO_BINDING " + std::to_string(ATMOSPHERE_RESULT_SSBO_BINDING) + "\n" +
|
||||
"#define ATMOSPHERE_TRANSMITTANCE_WIDTH " + std::to_string(ATMOSPHERE_TRANSMITTANCE_WIDTH) + "\n" +
|
||||
"#define ATMOSPHERE_TRANSMITTANCE_HEIGHT " + std::to_string(ATMOSPHERE_TRANSMITTANCE_HEIGHT) + "\n" +
|
||||
"#define ATMOSPHERE_MULTISCATTERING_SIZE " + std::to_string(ATMOSPHERE_MULTISCATTERING_SIZE) + "\n" +
|
||||
"#define ATMOSPHERE_SKYVIEW_WIDTH " + std::to_string(ATMOSPHERE_SKYVIEW_WIDTH) + "\n" +
|
||||
"#define ATMOSPHERE_SKYVIEW_HEIGHT " + std::to_string(ATMOSPHERE_SKYVIEW_HEIGHT) + "\n" +
|
||||
"#define ATMOSPHERE_AMBIENT_GROUP_SIZE " + std::to_string(ATMOSPHERE_AMBIENT_GROUP_SIZE) + "\n" +
|
||||
"#define ATMOSPHERE_TRANSMITTANCE_UNIT " + std::to_string(ATMOSPHERE_TRANSMITTANCE_UNIT) + "\n" +
|
||||
"#define ATMOSPHERE_MULTISCATTERING_UNIT " + std::to_string(ATMOSPHERE_MULTISCATTERING_UNIT) + "\n" +
|
||||
"#define ATMOSPHERE_SKYVIEW_UNIT " + std::to_string(ATMOSPHERE_SKYVIEW_UNIT) + "\n" +
|
||||
"#define SKY_VIEW_TEX " + std::to_string(SKY_VIEW_TEX) + "\n" +
|
||||
R"STRING(
|
||||
const uint LIGHT_SPOT = 0U;
|
||||
const uint LIGHT_POINT = 1U;
|
||||
@@ -68,9 +87,20 @@ void gl::glsl_common_setup()
|
||||
uint lights_count;
|
||||
// 1 if the shading takes the lights from the list of the cluster of a fragment, see cluster_ubo
|
||||
uint lights_clustered;
|
||||
// scale of the directional part of the sky ambient light, see sky_ssbo
|
||||
float sky_ambient_factor;
|
||||
// 1 if rough reflections come from the prefiltered environment map
|
||||
uint env_ggx;
|
||||
|
||||
light_s lights[];
|
||||
};
|
||||
|
||||
// directional part of the sky ambient light: real spherical harmonics (bands 1 and 2, band 0 is ambient) of the light
|
||||
// a surface facing a world space direction gets, in units of ambient. see gl::sky_data
|
||||
layout(std430, binding = SKY_SSBO_BINDING) readonly buffer sky_ssbo
|
||||
{
|
||||
vec4 sky_ambient_sh[9];
|
||||
};
|
||||
#endif
|
||||
|
||||
// light clusters, see gl/cluster_grid.h
|
||||
|
||||
51
gl/readback.cpp
Normal file
51
gl/readback.cpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#include "stdafx.h"
|
||||
#include "readback.h"
|
||||
|
||||
gl::readback::readback(GLsizeiptr size) : m_size(size) {}
|
||||
|
||||
gl::readback::~readback()
|
||||
{
|
||||
for (auto &slot : m_slots)
|
||||
{
|
||||
// fences first, they belong to the copies
|
||||
slot.copied.reset();
|
||||
if (slot.buffer != 0)
|
||||
glDeleteBuffers(1, &slot.buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void gl::readback::push(GLuint Source, GLintptr Offset)
|
||||
{
|
||||
if (m_used >= SLOTS)
|
||||
return;
|
||||
|
||||
auto &slot{m_slots[(m_first + m_used) % SLOTS]};
|
||||
if (slot.buffer == 0)
|
||||
{
|
||||
glCreateBuffers(1, &slot.buffer);
|
||||
glNamedBufferStorage(slot.buffer, m_size, nullptr, GL_CLIENT_STORAGE_BIT);
|
||||
}
|
||||
glCopyNamedBufferSubData(Source, slot.buffer, Offset, 0, m_size);
|
||||
slot.copied = std::make_unique<fence>();
|
||||
++m_used;
|
||||
}
|
||||
|
||||
bool gl::readback::pop(void *Data)
|
||||
{
|
||||
bool result{false};
|
||||
// oldest first, as long as the gpu has finished the copies; the newest finished one ends in Data
|
||||
while (m_used > 0)
|
||||
{
|
||||
auto &slot{m_slots[m_first]};
|
||||
if (!slot.copied->is_signalled())
|
||||
break;
|
||||
|
||||
glGetNamedBufferSubData(slot.buffer, 0, m_size, Data);
|
||||
slot.copied.reset();
|
||||
result = true;
|
||||
|
||||
m_first = (m_first + 1) % SLOTS;
|
||||
--m_used;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
40
gl/readback.h
Normal file
40
gl/readback.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
#include "object.h"
|
||||
#include "fence.h"
|
||||
|
||||
namespace gl
|
||||
{
|
||||
// copies of a small range of a buffer the gpu writes, for the cpu to read a few frames later without waiting for the
|
||||
// gpu: a queue of buffers, each with the fence of the copy into it. if the gpu is so far behind that every buffer is
|
||||
// still waiting, the copy is skipped
|
||||
class readback
|
||||
{
|
||||
public:
|
||||
explicit readback(GLsizeiptr size);
|
||||
~readback();
|
||||
readback(const readback &) = delete;
|
||||
readback &operator=(const readback &) = delete;
|
||||
|
||||
// queues a copy of size bytes of Source, from Offset
|
||||
void push(GLuint Source, GLintptr Offset = 0);
|
||||
// writes the newest copy which the gpu has finished to Data and returns true; false if none was finished since the last call
|
||||
bool pop(void *Data);
|
||||
|
||||
private:
|
||||
static constexpr size_t SLOTS = 4;
|
||||
struct slot
|
||||
{
|
||||
GLuint buffer = 0;
|
||||
std::unique_ptr<fence> copied;
|
||||
};
|
||||
GLsizeiptr m_size;
|
||||
std::array<slot, SLOTS> m_slots;
|
||||
// oldest slot in use and the number of slots in use
|
||||
size_t m_first = 0;
|
||||
size_t m_used = 0;
|
||||
};
|
||||
} // namespace gl
|
||||
112
gl/ubo.h
112
gl/ubo.h
@@ -47,6 +47,17 @@ namespace gl
|
||||
const size_t SHADOW_TEX = MAX_TEXTURES + 0;
|
||||
const size_t ENV_TEX = MAX_TEXTURES + 1;
|
||||
const size_t HEADLIGHT_TEX = MAX_TEXTURES + 2;
|
||||
// environment map prefiltered with the GGX lobe, see envmapping.glsl
|
||||
const size_t ENV_GGX_TEX = MAX_TEXTURES + 3;
|
||||
// the prefiltered map: size of its first level and the number of levels. level k holds the reflection of a surface of
|
||||
// perceptual roughness ENVMAP_GGX_ROUGHNESS_MIN + (1 - ENVMAP_GGX_ROUGHNESS_MIN) * k / (ENVMAP_GGX_LEVELS - 1); smoother
|
||||
// surfaces read the mipmaps of the plain map, which differ little from the lobe there
|
||||
const size_t ENVMAP_GGX_SIZE = 256;
|
||||
const size_t ENVMAP_GGX_LEVELS = 7;
|
||||
const float ENVMAP_GGX_ROUGHNESS_MIN = 0.2f;
|
||||
const size_t ENVMAP_GGX_GROUP_SIZE = 8;
|
||||
// sky-view table of the atmosphere (sky_atmosphere.frag). past the units the texture cache keeps track of, bound directly
|
||||
const size_t SKY_VIEW_TEX = MAX_TEXTURES + HELPER_TEXTURES + 0;
|
||||
|
||||
struct scene_ubs
|
||||
{
|
||||
@@ -173,8 +184,13 @@ namespace gl
|
||||
// 1 if the shading takes the lights of a fragment from the list of its cluster (lights_count is not used then), 0 if not.
|
||||
// set per pass: the cluster grid exists only for the color pass of a perspective view
|
||||
uint32_t lights_clustered;
|
||||
// scale of the directional part of the sky ambient light (sky_ssbo): the factor the ambient light of the object
|
||||
// is scaled with (tunnels), 0 where the ambient light is not the sky's (model previews)
|
||||
float sky_ambient_factor;
|
||||
// 1 if the shading reads rough reflections from the prefiltered environment map, 0 if from the mipmaps of the plain one
|
||||
uint32_t env_ggx;
|
||||
// lights[] begins at a multiple of 16 bytes
|
||||
uint32_t padding[3];
|
||||
uint32_t padding;
|
||||
|
||||
light_element lights[MAX_LIGHTS];
|
||||
};
|
||||
@@ -184,6 +200,8 @@ namespace gl
|
||||
static_assert(offsetof(light_data, fog_color) == 16, "bad layout of light_data");
|
||||
static_assert(offsetof(light_data, lights_count) == 28, "bad layout of light_data");
|
||||
static_assert(offsetof(light_data, lights_clustered) == 32, "bad layout of light_data");
|
||||
static_assert(offsetof(light_data, sky_ambient_factor) == 36, "bad layout of light_data");
|
||||
static_assert(offsetof(light_data, env_ggx) == 40, "bad layout of light_data");
|
||||
static_assert(offsetof(light_data, lights) == 48, "bad layout of light_data");
|
||||
static_assert(sizeof(light_data) == 48 + sizeof(light_element) * MAX_LIGHTS, "bad size of light_data");
|
||||
|
||||
@@ -271,5 +289,97 @@ namespace gl
|
||||
static_assert(offsetof(cluster_counters, index_count) == 0, "bad layout of cluster_counters");
|
||||
static_assert(offsetof(cluster_counters, overflow_count) == 4, "bad layout of cluster_counters");
|
||||
|
||||
// binding points of the sky, exposure and atmosphere buffers: layout(binding = ...) in glsl_common
|
||||
const GLuint SKY_SSBO_BINDING = 5;
|
||||
const GLuint EXPOSURE_SSBO_BINDING = 6;
|
||||
const GLuint ATMOSPHERE_RESULT_SSBO_BINDING = 7;
|
||||
const GLuint ATMOSPHERE_UBO_BINDING = 4;
|
||||
|
||||
// number of coefficients of the spherical harmonics of order 2 (bands 0..2)
|
||||
const size_t SKY_SH_COEFFICIENTS = 9;
|
||||
|
||||
// layout std430: directional part of the sky ambient light (GLSL: sky_ssbo). the coefficients of the real spherical
|
||||
// harmonics of the light a surface facing a world space direction gets, in units of light_data.ambient, without the
|
||||
// constant band (that one is light_data.ambient itself); rgb used, w is padding
|
||||
struct sky_data
|
||||
{
|
||||
glm::vec4 ambient_sh[SKY_SH_COEFFICIENTS];
|
||||
};
|
||||
|
||||
static_assert(sizeof(sky_data) == 144, "bad size of sky_data");
|
||||
|
||||
// layout std430: luminance histogram of the image and the exposure worked out from it (GLSL: exposure_ssbo)
|
||||
const GLuint EXPOSURE_HISTOGRAM_BINS = 256;
|
||||
const GLuint EXPOSURE_HISTOGRAM_GROUP_SIZE = 16;
|
||||
|
||||
struct exposure_data
|
||||
{
|
||||
uint32_t histogram[EXPOSURE_HISTOGRAM_BINS];
|
||||
// luminance the exposure is adapted to, the time-smoothed average of the image
|
||||
float adapted_luminance;
|
||||
// multiplier the tonemapping applies to the image
|
||||
float exposure;
|
||||
// average luminance of the last image
|
||||
float average_luminance;
|
||||
// 0 until the first average was taken, the adaptation starts from it
|
||||
uint32_t valid;
|
||||
};
|
||||
|
||||
static_assert(sizeof(exposure_data) == 4 * EXPOSURE_HISTOGRAM_BINS + 16, "bad size of exposure_data");
|
||||
static_assert(offsetof(exposure_data, adapted_luminance) == 4 * EXPOSURE_HISTOGRAM_BINS, "bad layout of exposure_data");
|
||||
|
||||
// sizes of the look-up tables of the atmosphere (Hillaire 2020)
|
||||
const GLuint ATMOSPHERE_TRANSMITTANCE_WIDTH = 256;
|
||||
const GLuint ATMOSPHERE_TRANSMITTANCE_HEIGHT = 64;
|
||||
const GLuint ATMOSPHERE_MULTISCATTERING_SIZE = 32;
|
||||
const GLuint ATMOSPHERE_SKYVIEW_WIDTH = 192;
|
||||
const GLuint ATMOSPHERE_SKYVIEW_HEIGHT = 108;
|
||||
// work group of atmosphere_ambient.comp, which projects the sky on the spherical harmonics in one group
|
||||
const GLuint ATMOSPHERE_AMBIENT_GROUP_SIZE = 256;
|
||||
// binding points of the look-up tables in the compute passes (images and samplers)
|
||||
const GLuint ATMOSPHERE_TRANSMITTANCE_UNIT = 0;
|
||||
const GLuint ATMOSPHERE_MULTISCATTERING_UNIT = 1;
|
||||
const GLuint ATMOSPHERE_SKYVIEW_UNIT = 2;
|
||||
|
||||
// layout std140: parameters of the atmosphere (GLSL: atmosphere_ubo). distances in kilometres, coefficients per kilometre
|
||||
struct atmosphere_ubs
|
||||
{
|
||||
// rgb: rayleigh scattering, w: rayleigh scale height
|
||||
glm::vec4 rayleigh;
|
||||
// x: mie scattering, y: mie extinction, z: mie scale height, w: mie phase asymmetry
|
||||
glm::vec4 mie;
|
||||
// rgb: ozone absorption, w: altitude of the middle of the ozone layer
|
||||
glm::vec4 ozone;
|
||||
// rgb: albedo of the ground, w: half width of the ozone layer
|
||||
glm::vec4 ground;
|
||||
// world space direction towards the sun (y up), w unused
|
||||
glm::vec4 sun_direction;
|
||||
float bottom_radius;
|
||||
float top_radius;
|
||||
// distance of the observer from the centre of the planet
|
||||
float observer_radius;
|
||||
float padding;
|
||||
};
|
||||
|
||||
static_assert(sizeof(atmosphere_ubs) == 96, "bad size of atmosphere_ubs");
|
||||
|
||||
// layout std430: what atmosphere_ambient.comp works out for the cpu (GLSL: atmosphere_result_ssbo). radiance with
|
||||
// the sun of illuminance 1
|
||||
struct atmosphere_result
|
||||
{
|
||||
// radiance arriving at the observer from every direction, ground included, as real spherical harmonics (world, y up)
|
||||
glm::vec4 radiance_sh[SKY_SH_COEFFICIENTS];
|
||||
// transmittance of the atmosphere towards the sun at the observer
|
||||
glm::vec4 sun_transmittance;
|
||||
// average radiance of the sky just above the horizon
|
||||
glm::vec4 horizon;
|
||||
// average radiance of the upper hemisphere
|
||||
glm::vec4 average;
|
||||
// transmittance of the atmosphere straight up from the observer: the colour of the sun high in the sky
|
||||
glm::vec4 zenith_transmittance;
|
||||
};
|
||||
|
||||
static_assert(sizeof(atmosphere_result) == 16 * (SKY_SH_COEFFICIENTS + 4), "bad size of atmosphere_result");
|
||||
|
||||
#pragma pack(pop)
|
||||
}
|
||||
|
||||
175
rendering/opengl46/opengl46exposure.cpp
Normal file
175
rendering/opengl46/opengl46exposure.cpp
Normal file
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/opengl46/opengl46exposure.h"
|
||||
|
||||
#include "model/Texture.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/utilities.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// luminance range of the histogram, log2: 2^-12 .. 2^6. darker pixels count as black, brighter ones as the brightest
|
||||
float const HISTOGRAM_LOG_MIN{-12.f};
|
||||
float const HISTOGRAM_LOG_MAX{6.f};
|
||||
// the bloom chain goes down this many levels at most, and not below this size
|
||||
std::size_t const BLOOM_LEVELS{6};
|
||||
int const BLOOM_MIN_SIZE{8};
|
||||
// format of the levels: no alpha, no negative values, half the memory of rgba16f
|
||||
GLint const BLOOM_FORMAT{GL_R11F_G11F_B10F};
|
||||
// soft knee of the threshold, a fraction of it
|
||||
float const BLOOM_KNEE{0.5f};
|
||||
|
||||
} // namespace
|
||||
|
||||
void opengl46_exposure::init()
|
||||
{
|
||||
auto const make_compute = [](std::string const &Name) {
|
||||
gl::shader shader(Name);
|
||||
return std::make_unique<gl::program>(std::vector<std::reference_wrapper<const gl::shader>>{shader});
|
||||
};
|
||||
m_histogram_program = make_compute("exposure_histogram.comp");
|
||||
m_average_program = make_compute("exposure_average.comp");
|
||||
m_bloom_down = std::make_unique<gl::postfx>("bloom_down");
|
||||
m_bloom_up = std::make_unique<gl::postfx>("bloom_up");
|
||||
|
||||
m_exposure_ssbo = std::make_unique<gl::ssbo>(sizeof(gl::exposure_data), gl::EXPOSURE_SSBO_BINDING);
|
||||
gl::exposure_data data{};
|
||||
data.adapted_luminance = Global.gfx_postfx_autoexposure_key;
|
||||
data.exposure = 1.f;
|
||||
data.average_luminance = Global.gfx_postfx_autoexposure_key;
|
||||
data.valid = 0;
|
||||
m_exposure_ssbo->update(data);
|
||||
}
|
||||
|
||||
void opengl46_exposure::bind()
|
||||
{
|
||||
m_exposure_ssbo->bind_base();
|
||||
}
|
||||
|
||||
std::unique_ptr<opengl46_exposure::bloom_chain> opengl46_exposure::make_bloom_chain(int const Width, int const Height)
|
||||
{
|
||||
auto chain{std::make_unique<bloom_chain>()};
|
||||
glm::ivec2 size{Width, Height};
|
||||
for (std::size_t level = 0; level < BLOOM_LEVELS; ++level)
|
||||
{
|
||||
size = glm::max(size / 2, glm::ivec2{1});
|
||||
if (level > 0 && std::min(size.x, size.y) < BLOOM_MIN_SIZE)
|
||||
break;
|
||||
chain->sizes.push_back(size);
|
||||
auto &texture{chain->down.emplace_back(std::make_unique<opengl_texture>())};
|
||||
texture->alloc_rendertarget(BLOOM_FORMAT, GL_RGB, size.x, size.y, 1, 1, GL_CLAMP_TO_EDGE);
|
||||
auto &framebuffer{chain->down_fb.emplace_back(std::make_unique<gl::framebuffer>())};
|
||||
framebuffer->attach(*texture, GL_COLOR_ATTACHMENT0);
|
||||
if (!framebuffer->is_complete())
|
||||
return nullptr;
|
||||
}
|
||||
for (std::size_t level = 0; level + 1 < chain->sizes.size(); ++level)
|
||||
{
|
||||
auto const &size{chain->sizes[level]};
|
||||
auto &texture{chain->up.emplace_back(std::make_unique<opengl_texture>())};
|
||||
texture->alloc_rendertarget(BLOOM_FORMAT, GL_RGB, size.x, size.y, 1, 1, GL_CLAMP_TO_EDGE);
|
||||
auto &framebuffer{chain->up_fb.emplace_back(std::make_unique<gl::framebuffer>())};
|
||||
framebuffer->attach(*texture, GL_COLOR_ATTACHMENT0);
|
||||
if (!framebuffer->is_complete())
|
||||
return nullptr;
|
||||
}
|
||||
return chain->sizes.empty() ? nullptr : std::move(chain);
|
||||
}
|
||||
|
||||
void opengl46_exposure::measure(opengl_texture &Image, double const Deltatime)
|
||||
{
|
||||
float data[4];
|
||||
if (m_readback.pop(data))
|
||||
std::copy(std::begin(data), std::end(data), std::begin(m_readback_data));
|
||||
|
||||
m_exposure_timer.begin();
|
||||
m_exposure_ssbo->bind_base();
|
||||
Image.bind(0);
|
||||
auto const bins{static_cast<float>(gl::EXPOSURE_HISTOGRAM_BINS - 2)};
|
||||
glProgramUniform2f(*m_histogram_program, 0, HISTOGRAM_LOG_MIN, bins / (HISTOGRAM_LOG_MAX - HISTOGRAM_LOG_MIN));
|
||||
auto const groupsize{2 * static_cast<int>(gl::EXPOSURE_HISTOGRAM_GROUP_SIZE)};
|
||||
m_histogram_program->dispatch((Image.get_width() + groupsize - 1) / groupsize, (Image.get_height() + groupsize - 1) / groupsize);
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
|
||||
glProgramUniform2f(*m_average_program, 0, HISTOGRAM_LOG_MIN, (HISTOGRAM_LOG_MAX - HISTOGRAM_LOG_MIN) / bins);
|
||||
glProgramUniform4f(*m_average_program, 1,
|
||||
static_cast<float>(std::clamp(Deltatime, 0.0, 1.0)),
|
||||
Global.gfx_postfx_autoexposure_speed_dark,
|
||||
Global.gfx_postfx_autoexposure_speed_bright,
|
||||
Global.gfx_postfx_autoexposure_key);
|
||||
glProgramUniform2f(*m_average_program, 2, std::exp2(Global.gfx_postfx_autoexposure_min), std::exp2(Global.gfx_postfx_autoexposure_max));
|
||||
m_average_program->dispatch(1);
|
||||
// read by the tonemapping, and copied for the debug window
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT);
|
||||
m_exposure_timer.end();
|
||||
|
||||
m_readback.push(*m_exposure_ssbo, offsetof(gl::exposure_data, adapted_luminance));
|
||||
opengl_texture::reset_unit_cache();
|
||||
}
|
||||
|
||||
opengl_texture *opengl46_exposure::bloom(bloom_chain &Chain, opengl_texture &Image, gl::ubo &Modelubo, gl::model_ubs &Model)
|
||||
{
|
||||
m_bloom_timer.begin();
|
||||
// down, the first step from the image itself
|
||||
for (std::size_t level = 0; level < Chain.sizes.size(); ++level)
|
||||
{
|
||||
Model.param[0] = glm::vec4{(level == 0 ? 1.f : 0.f), Global.gfx_postfx_bloom_threshold, BLOOM_KNEE, 0.f};
|
||||
Modelubo.update(Model);
|
||||
glViewport(0, 0, Chain.sizes[level].x, Chain.sizes[level].y);
|
||||
m_bloom_down->apply(level == 0 ? Image : *Chain.down[level - 1], Chain.down_fb[level].get());
|
||||
}
|
||||
// up, from the lowest level
|
||||
for (std::size_t level = Chain.up.size(); level-- > 0;)
|
||||
{
|
||||
auto &lower{(level + 1 < Chain.up.size()) ? *Chain.up[level + 1] : *Chain.down[level + 1]};
|
||||
glViewport(0, 0, Chain.sizes[level].x, Chain.sizes[level].y);
|
||||
m_bloom_up->apply({&lower, Chain.down[level].get()}, Chain.up_fb[level].get());
|
||||
}
|
||||
m_bloom_timer.end();
|
||||
opengl_texture::reset_unit_cache();
|
||||
return Chain.up.empty() ? Chain.down.front().get() : Chain.up.front().get();
|
||||
}
|
||||
|
||||
glm::vec4 opengl46_exposure::tonemap_params(std::size_t const Bloomlevels) const
|
||||
{
|
||||
auto const bloom{(Global.gfx_postfx_bloom && Bloomlevels > 0) ? Global.gfx_postfx_bloom_intensity : 0.f};
|
||||
return glm::vec4{
|
||||
bloom,
|
||||
Global.gfx_postfx_autoexposure ? 1.f : 0.f,
|
||||
std::exp2(Global.gfx_postfx_autoexposure_compensation),
|
||||
Bloomlevels > 0 ? 1.f / static_cast<float>(Bloomlevels) : 0.f};
|
||||
}
|
||||
|
||||
std::string opengl46_exposure::info_times()
|
||||
{
|
||||
std::string text;
|
||||
if (auto const time = m_exposure_timer.result())
|
||||
text += " exposure: " + to_string(static_cast<double>(*time) / 1000000.0, 3) + " ms\n";
|
||||
if (auto const time = m_bloom_timer.result())
|
||||
text += " bloom: " + to_string(static_cast<double>(*time) / 1000000.0, 3) + " ms\n";
|
||||
return text;
|
||||
}
|
||||
|
||||
void opengl46_exposure::draw_debug_ui()
|
||||
{
|
||||
ImGui::Checkbox("automatic exposure (gfx.postfx.autoexposure)", &Global.gfx_postfx_autoexposure);
|
||||
ImGui::SliderFloat("key", &Global.gfx_postfx_autoexposure_key, 0.02f, 2.f, "%.3f", ImGuiSliderFlags_Logarithmic);
|
||||
ImGui::SliderFloat("min EV", &Global.gfx_postfx_autoexposure_min, -4.f, 0.f);
|
||||
ImGui::SliderFloat("max EV", &Global.gfx_postfx_autoexposure_max, 0.f, 4.f);
|
||||
ImGui::SliderFloat("adaptation to dark (1/s)", &Global.gfx_postfx_autoexposure_speed_dark, 0.05f, 10.f);
|
||||
ImGui::SliderFloat("adaptation to bright (1/s)", &Global.gfx_postfx_autoexposure_speed_bright, 0.05f, 10.f);
|
||||
ImGui::SliderFloat("compensation EV", &Global.gfx_postfx_autoexposure_compensation, -4.f, 4.f);
|
||||
ImGui::Text("average luminance %.3f, adapted %.3f, exposure %.3f (%+.2f EV)", m_readback_data[2], m_readback_data[0], m_readback_data[1], std::log2(std::max(m_readback_data[1], 1e-6f)));
|
||||
ImGui::Checkbox("bloom (gfx.postfx.bloom)", &Global.gfx_postfx_bloom);
|
||||
ImGui::SliderFloat("bloom intensity", &Global.gfx_postfx_bloom_intensity, 0.f, 0.5f);
|
||||
ImGui::SliderFloat("bloom threshold", &Global.gfx_postfx_bloom_threshold, 0.f, 10.f);
|
||||
}
|
||||
78
rendering/opengl46/opengl46exposure.h
Normal file
78
rendering/opengl46/opengl46exposure.h
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "gl/ubo.h"
|
||||
#include "gl/ssbo.h"
|
||||
#include "gl/shader.h"
|
||||
#include "gl/postfx.h"
|
||||
#include "gl/framebuffer.h"
|
||||
#include "gl/query.h"
|
||||
#include "gl/readback.h"
|
||||
|
||||
class opengl_texture;
|
||||
|
||||
// automatic exposure and bloom of the post processing of the opengl 4.6 renderer:
|
||||
// - a luminance histogram of the final hdr image in a compute pass, and the exposure adapted to its average over time,
|
||||
// within the limits of gfx.postfx.autoexposure.* (exposure_*.comp). the exposure stays on the gpu, the tonemapping reads it
|
||||
// - bloom: the image taken down a chain of half size levels and back up, each level added (postfx_bloom_*.frag)
|
||||
// both are switched by Global.gfx_postfx_autoexposure and gfx_postfx_bloom; off, the tonemapping gets the image as before
|
||||
class opengl46_exposure
|
||||
{
|
||||
public:
|
||||
// the levels of the bloom chain of one viewport
|
||||
struct bloom_chain
|
||||
{
|
||||
std::vector<std::unique_ptr<opengl_texture>> down;
|
||||
std::vector<std::unique_ptr<gl::framebuffer>> down_fb;
|
||||
// one fewer than down: the lowest level goes up as it is
|
||||
std::vector<std::unique_ptr<opengl_texture>> up;
|
||||
std::vector<std::unique_ptr<gl::framebuffer>> up_fb;
|
||||
std::vector<glm::ivec2> sizes;
|
||||
};
|
||||
|
||||
opengl46_exposure() = default;
|
||||
opengl46_exposure(const opengl46_exposure &) = delete;
|
||||
opengl46_exposure &operator=(const opengl46_exposure &) = delete;
|
||||
|
||||
// creates the programs and the buffer. throws gl::shader_exception
|
||||
void init();
|
||||
// binds the buffer the tonemapping reads; bindings are context state, so done in every context
|
||||
void bind();
|
||||
// the bloom chain for an image of the given size; null if it could not be made
|
||||
static std::unique_ptr<bloom_chain> make_bloom_chain(int const Width, int const Height);
|
||||
// measures the luminance of the image and adapts the exposure to it. Deltatime: seconds since the last measurement
|
||||
void measure(opengl_texture &Image, double const Deltatime);
|
||||
// takes the image down and up the chain; returns the result, which the tonemapping blends in. leaves the viewport
|
||||
// and the framebuffer binding changed. Model is the model_ubs the post processing shaders read the parameters from
|
||||
opengl_texture *bloom(bloom_chain &Chain, opengl_texture &Image, gl::ubo &Modelubo, gl::model_ubs &Model);
|
||||
// parameters of the tonemapping (postfx_tonemapping.frag), with bloom from a chain of Bloomlevels levels (0: none)
|
||||
glm::vec4 tonemap_params(std::size_t const Bloomlevels) const;
|
||||
// lines for the gpu times of the debug panel
|
||||
std::string info_times();
|
||||
// controls of the debug window
|
||||
void draw_debug_ui();
|
||||
|
||||
private:
|
||||
std::unique_ptr<gl::program> m_histogram_program;
|
||||
std::unique_ptr<gl::program> m_average_program;
|
||||
std::unique_ptr<gl::postfx> m_bloom_down;
|
||||
std::unique_ptr<gl::postfx> m_bloom_up;
|
||||
std::unique_ptr<gl::ssbo> m_exposure_ssbo;
|
||||
// the exposure and luminances, read back for the debug window only
|
||||
gl::readback m_readback{4 * sizeof(float)};
|
||||
float m_readback_data[4]{1.f, 1.f, 1.f, 0.f};
|
||||
gl::gpu_timer m_exposure_timer;
|
||||
gl::gpu_timer m_bloom_timer;
|
||||
};
|
||||
@@ -181,6 +181,17 @@ bool opengl46_renderer::Init(GLFWwindow *Window)
|
||||
light_ssbo->bind_base();
|
||||
instance_ubo = std::make_unique<gl::ubo>(sizeof(gl::instance_ubs), 3, GL_STREAM_DRAW);
|
||||
|
||||
try
|
||||
{
|
||||
m_sky.init();
|
||||
m_exposure.init();
|
||||
}
|
||||
catch (gl::shader_exception const &e)
|
||||
{
|
||||
ErrorLog("invalid shader: " + std::string(e.what()));
|
||||
return false;
|
||||
}
|
||||
|
||||
// better initialize with 0 to not crash driver/whole system
|
||||
// when we forget
|
||||
memset(&light_data, 0, sizeof(light_data));
|
||||
@@ -462,6 +473,8 @@ bool opengl46_renderer::init_viewport(viewport_config &vp)
|
||||
model_ubo->bind_uniform();
|
||||
scene_ubo->bind_uniform();
|
||||
light_ssbo->bind_base();
|
||||
m_sky.bind();
|
||||
m_exposure.bind();
|
||||
|
||||
vp.clusters = std::make_unique<gl::cluster_grid>();
|
||||
// the material shaders declare the cluster blocks whether they read them or not, and drawing with an active storage
|
||||
@@ -546,6 +559,11 @@ bool opengl46_renderer::init_viewport(viewport_config &vp)
|
||||
|
||||
if (!vp.main2_fb->is_complete())
|
||||
return false;
|
||||
|
||||
// made whether the bloom is on or not, it can be switched on in the debug window
|
||||
vp.bloom = opengl46_exposure::make_bloom_chain(vp.width, vp.height);
|
||||
if (!vp.bloom)
|
||||
ErrorLog("bloom framebuffer setup failed, no bloom");
|
||||
}
|
||||
|
||||
if (vp.custom_backbuffer)
|
||||
@@ -588,6 +606,10 @@ bool opengl46_renderer::Render()
|
||||
// quantize sun angle to reduce shadow crawl
|
||||
auto const quantizationstep{0.004f};
|
||||
m_sunlight.direction = glm::normalize(quantizationstep * glm::roundEven(m_sunlight.direction * (1.f / quantizationstep)));
|
||||
// the light of the sky for the render: colours of the sun, ambient light and fog from the atmosphere where it is
|
||||
// used, the directional part of the ambient light
|
||||
m_fogcolor = Global.FogColor;
|
||||
m_sky.update(m_sunlight, m_fogcolor, simulation::Environment.skydome(), simulation::Environment.sun().getDirection(), simulation::Environment.sun().getAngle());
|
||||
}
|
||||
// generate new frame
|
||||
opengl_texture::reset_unit_cache();
|
||||
@@ -669,6 +691,8 @@ void opengl46_renderer::SwapBuffers()
|
||||
m_debugtimestext += " clusters: " + to_string(static_cast<double>(*clusterstime) / 1000000.0, 3) + " ms\n";
|
||||
if (auto const culltime = m_cullstimer.result())
|
||||
m_debugtimestext += " cull: " + to_string(static_cast<double>(*culltime) / 1000000.0, 3) + " ms\n";
|
||||
m_debugtimestext += m_sky.info_times();
|
||||
m_debugtimestext += m_exposure.info_times();
|
||||
m_debugtimestext += "uilayer: " + to_string( Timer::subsystem.gfx_gui.average(), 2 ) + " ms\n";
|
||||
if( DebugModeFlag )
|
||||
m_debugtimestext += m_textures.info();
|
||||
@@ -756,6 +780,14 @@ void opengl46_renderer::draw_debug_ui()
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
if (ImGui::Begin("Sky and exposure"))
|
||||
{
|
||||
m_sky.draw_debug_ui();
|
||||
ImGui::Separator();
|
||||
m_exposure.draw_debug_ui();
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2S(400, 400));
|
||||
if (ImGui::Begin("Pickbuffer") && m_pick_tex) {
|
||||
ImGui::Image((ImTextureID)(intptr_t)(m_pick_tex->id), ImGui::GetContentRegionAvail(), ImVec2(0, 1.0), ImVec2(1.0, 0));
|
||||
@@ -873,6 +905,9 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode)
|
||||
Timer::subsystem.gfx_reflections.start();
|
||||
if (Render_reflections(vp))
|
||||
m_renderpass = m_colorpass; // restore color pass settings
|
||||
// the prefiltered map is made with every update of the environment map, or once the switch is turned on
|
||||
if (Global.gfx_envmap_ggx && m_environmentready && (m_sky.prefiltered() == nullptr))
|
||||
m_sky.prefilter(*m_env_tex);
|
||||
Timer::subsystem.gfx_reflections.stop();
|
||||
setup_env_map(m_env_tex.get());
|
||||
}
|
||||
@@ -956,6 +991,8 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode)
|
||||
glDebug("render environment");
|
||||
|
||||
scene_ubs.projection = OpenGLMatrices.data(GL_PROJECTION);
|
||||
// back to the rotation of the camera after the faces of the environment map, which are drawn with their own
|
||||
scene_ubs.inv_view = glm::inverse(glm::mat4{glm::mat3{m_colorpass.pass_camera.modelview()}});
|
||||
scene_ubo->update(scene_ubs);
|
||||
Render(&simulation::Environment);
|
||||
|
||||
@@ -1083,6 +1120,22 @@ if (!Global.gfx_skippipeline)
|
||||
m_pfx_ssao_apply->apply({ vp.main_tex.get(), vp.ssao_blur_tex.get() }, vp.main2_fb.get());
|
||||
}
|
||||
|
||||
// the exposure follows the main view; the other viewports use it too
|
||||
if (Global.gfx_postfx_autoexposure && vp.main)
|
||||
m_exposure.measure(*vp.main2_tex, Timer::GetDeltaRenderTime());
|
||||
opengl_texture *bloomtexture{nullptr};
|
||||
std::size_t bloomlevels{0};
|
||||
if (Global.gfx_postfx_bloom && vp.bloom)
|
||||
{
|
||||
bloomtexture = m_exposure.bloom(*vp.bloom, *vp.main2_tex, *model_ubo, model_ubs);
|
||||
bloomlevels = vp.bloom->sizes.size();
|
||||
}
|
||||
model_ubs.param[0] = m_exposure.tonemap_params(bloomlevels);
|
||||
model_ubo->update(model_ubs);
|
||||
std::vector<opengl_texture *> tonemapinput{vp.main2_tex.get()};
|
||||
if (bloomtexture != nullptr)
|
||||
tonemapinput.push_back(bloomtexture);
|
||||
|
||||
if (!Global.gfx_shadergamma)
|
||||
glEnable(GL_FRAMEBUFFER_SRGB);
|
||||
|
||||
@@ -1093,14 +1146,14 @@ if (!Global.gfx_skippipeline)
|
||||
if( Global.gfx_postfx_chromaticaberration_enabled ) {
|
||||
glViewport(0, 0, vp.width, vp.height);
|
||||
vp.main_fb->setup_drawing(1);
|
||||
m_pfx_tonemapping->apply( *vp.main2_tex, vp.main_fb.get() );
|
||||
m_pfx_tonemapping->apply( tonemapinput, vp.main_fb.get() );
|
||||
|
||||
glViewport(0, 0, target_size.x, target_size.y);
|
||||
m_pfx_chromaticaberration->apply( *vp.main_tex, target );
|
||||
}
|
||||
else {
|
||||
glViewport(0, 0, target_size.x, target_size.y);
|
||||
m_pfx_tonemapping->apply( *vp.main2_tex, target );
|
||||
m_pfx_tonemapping->apply( tonemapinput, target );
|
||||
}
|
||||
|
||||
if (Global.gfx_lights_clusterheatmap && vp.clusters->valid())
|
||||
@@ -1235,6 +1288,8 @@ if (!Global.gfx_skippipeline)
|
||||
|
||||
setup_shadow_empty_map();
|
||||
scene_ubs.projection = OpenGLMatrices.data(GL_PROJECTION);
|
||||
// world space directions of the shading of the face (the sky, the directional ambient light)
|
||||
scene_ubs.inv_view = glm::inverse(glm::mat4{glm::mat3{m_renderpass.pass_camera.modelview()}});
|
||||
scene_ubo->update(scene_ubs);
|
||||
Render(&simulation::Environment);
|
||||
// opaque parts...
|
||||
@@ -1478,6 +1533,8 @@ bool opengl46_renderer::Render_reflections(viewport_config &vp)
|
||||
{
|
||||
m_env_tex->generate_mipmaps();
|
||||
m_environmentready = true;
|
||||
if (Global.gfx_envmap_ggx)
|
||||
m_sky.prefilter(*m_env_tex);
|
||||
}
|
||||
m_env_fb->detach(GL_COLOR_ATTACHMENT0);
|
||||
|
||||
@@ -1899,6 +1956,15 @@ void opengl46_renderer::setup_env_map(gl::cubemap *tex)
|
||||
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
}
|
||||
// rough reflections read the prefiltered map along with the real environment map; with the empty stand-in (the reflection
|
||||
// pass) or without the map the shading keeps to the mipmaps of what is bound
|
||||
auto const *prefiltered{(tex != nullptr && tex == m_env_tex.get()) ? m_sky.prefiltered() : nullptr};
|
||||
m_envmap_bound = (prefiltered != nullptr);
|
||||
if (prefiltered != nullptr)
|
||||
prefiltered->bind(GL_TEXTURE0 + gl::ENV_GGX_TEX);
|
||||
else if (m_empty_cubemap)
|
||||
m_empty_cubemap->bind(GL_TEXTURE0 + gl::ENV_GGX_TEX);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
opengl_texture::reset_unit_cache();
|
||||
}
|
||||
|
||||
@@ -1937,6 +2003,7 @@ void opengl46_renderer::setup_sunlight_intensity( float const Factor ) {
|
||||
m_sunlight.apply_intensity( Factor );
|
||||
light_data.lights[ 0 ].intensity = m_sunlight.factor * (1.0f - Global.Overcast * 0.33f);
|
||||
light_data.ambient = m_sunlight.ambient * m_sunlight.factor;
|
||||
light_data.sky_ambient_factor = ( Global.gfx_ambient_sh ? m_sunlight.factor : 0.f );
|
||||
upload_light_header();
|
||||
}
|
||||
|
||||
@@ -1967,10 +2034,14 @@ bool opengl46_renderer::Render(world_environment *Environment)
|
||||
{
|
||||
glScalef( 500.0f, 500.0f, 500.0f );
|
||||
model_ubs.set_modelview( OpenGLMatrices.data( GL_MODELVIEW ) );
|
||||
auto const atmosphere{ m_sky.atmosphere_weight() > 0.f };
|
||||
if( atmosphere ) {
|
||||
m_sky.setup_sky( model_ubs );
|
||||
}
|
||||
model_ubo->update( model_ubs );
|
||||
|
||||
m_skydomerenderer.update();
|
||||
m_skydomerenderer.render();
|
||||
m_skydomerenderer.render( atmosphere );
|
||||
// skydome uses a custom vbo which could potentially confuse the main geometry system. hardly elegant but, eh
|
||||
gfx::opengl46_vaogeometrybank::reset();
|
||||
}
|
||||
@@ -2002,6 +2073,7 @@ bool opengl46_renderer::Render(world_environment *Environment)
|
||||
auto const fogfactor{std::clamp(Global.fFogEnd / 2000.f, 0.f, 1.f)}; // stronger fog reduces opacity of the celestial bodies
|
||||
float const duskfactor = 1.0f - std::clamp(std::abs(Environment->m_sun.getAngle()), 0.0f, 12.0f) / 12.0f;
|
||||
glm::vec3 suncolor = glm::mix(glm::vec3(1.0f, 242.0f / 255.0f, 231.0f / 255.0f), glm::vec3(235.0f / 255.0f, 140.0f / 255.0f, 36.0f / 255.0f), duskfactor);
|
||||
suncolor = m_sky.sun_disc_color(suncolor);
|
||||
|
||||
// sun
|
||||
{
|
||||
@@ -5643,6 +5715,9 @@ void opengl46_renderer::Preview_Color(TAnimModel *Instance, float const Squaredi
|
||||
light_data.lights_total = 1;
|
||||
light_data.lights_count = 1;
|
||||
light_data.lights_clustered = 0;
|
||||
// a uniform ambient light, as the preview always had
|
||||
light_data.sky_ambient_factor = 0.f;
|
||||
light_data.env_ggx = 0;
|
||||
light_data.lights[0].type = gl::light_element::DIR;
|
||||
light_data.lights[0].dir = modelview * glm::vec4(m_sunlight.direction, 0.0f);
|
||||
light_data.lights[0].color = m_sunlight.diffuse;
|
||||
@@ -5855,6 +5930,9 @@ bool opengl46_renderer::Render_Preview(TAnimModel *Instance, int const Size, int
|
||||
glViewport(0, 0, targetsize, targetsize);
|
||||
if (!Global.gfx_shadergamma)
|
||||
glEnable(GL_FRAMEBUFFER_SRGB);
|
||||
// no bloom, no automatic exposure
|
||||
model_ubs.param[0] = glm::vec4{0.f, 0.f, 1.f, 0.f};
|
||||
model_ubo->update(model_ubs);
|
||||
m_pfx_tonemapping->apply(*m_preview.hdr_tex, m_preview.ldr_fb.get());
|
||||
if (!Global.gfx_shadergamma)
|
||||
glDisable(GL_FRAMEBUFFER_SRGB);
|
||||
@@ -6067,15 +6145,17 @@ void opengl46_renderer::Update_Lights(light_array &Lights, bool const Headlights
|
||||
m_lights_lit = static_cast<uint32_t>(light_i - 1);
|
||||
light_data.lights_count = 1 + std::min<uint32_t>(static_cast<uint32_t>(light_i - 1), static_cast<uint32_t>(std::max(Global.DynamicLightCount, 0)));
|
||||
light_data.lights_clustered = Clustered ? 1 : 0;
|
||||
light_data.env_ggx = ( Global.gfx_envmap_ggx && m_envmap_bound ) ? 1 : 0;
|
||||
// fill sunlight data
|
||||
light_data.ambient = m_sunlight.ambient * m_sunlight.factor;// *simulation::Environment.light_intensity();
|
||||
light_data.sky_ambient_factor = ( Global.gfx_ambient_sh ? m_sunlight.factor : 0.f );
|
||||
light_data.lights[0].type = gl::light_element::DIR;
|
||||
light_data.lights[0].dir = mv * glm::vec4(m_sunlight.direction, 0.0f);
|
||||
light_data.lights[0].color = m_sunlight.diffuse * m_sunlight.factor * simulation::Environment.light_intensity();
|
||||
light_data.lights[0].ambient = 0.0f;
|
||||
light_data.lights[0].intensity = 1.0f;
|
||||
// fill fog data
|
||||
light_data.fog_color = Global.FogColor;
|
||||
light_data.fog_color = m_fogcolor;
|
||||
if (Global.fFogEnd > 0)
|
||||
{
|
||||
m_fogrange = Global.fFogEnd / std::max(1.f, Global.Overcast * 2.f);
|
||||
|
||||
@@ -32,6 +32,8 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "gl/pbo.h"
|
||||
#include "gl/query.h"
|
||||
#include "rendering/opengl46/opengl46terrain.h"
|
||||
#include "rendering/opengl46/opengl46sky.h"
|
||||
#include "rendering/opengl46/opengl46exposure.h"
|
||||
|
||||
// bare-bones render controller, in lack of anything better yet
|
||||
class opengl46_renderer : public gfx_renderer {
|
||||
@@ -261,6 +263,9 @@ class opengl46_renderer : public gfx_renderer {
|
||||
std::unique_ptr<gl::framebuffer> backbuffer_fb;
|
||||
std::unique_ptr<opengl_texture> backbuffer_tex;
|
||||
|
||||
// levels of the bloom of the post processing; null without the pipeline
|
||||
std::unique_ptr<opengl46_exposure::bloom_chain> bloom;
|
||||
|
||||
// light cluster grid of the viewport
|
||||
std::unique_ptr<gl::cluster_grid> clusters;
|
||||
// depth of the finished scene resolved from the msaa buffer, for the cluster heatmap; made when it is first needed
|
||||
@@ -380,6 +385,14 @@ class opengl46_renderer : public gfx_renderer {
|
||||
texture_manager m_textures;
|
||||
opengl46_terrain_materials m_terrainmaterials{m_materials, m_textures};
|
||||
opengl46_light m_sunlight;
|
||||
// fog colour of the frame: the one of the simulation, or of the atmosphere (see opengl46_sky::update())
|
||||
glm::vec3 m_fogcolor{0.f};
|
||||
// light of the sky: directional ambient light, atmosphere, prefiltered environment map
|
||||
opengl46_sky m_sky;
|
||||
// automatic exposure and bloom of the post processing
|
||||
opengl46_exposure m_exposure;
|
||||
// true while the environment map bound for the shading is the real one (not the empty stand-in of the reflection pass)
|
||||
bool m_envmap_bound{false};
|
||||
/*
|
||||
float m_sunandviewangle; // cached dot product of sunlight and camera vectors
|
||||
*/
|
||||
|
||||
417
rendering/opengl46/opengl46sky.cpp
Normal file
417
rendering/opengl46/opengl46sky.cpp
Normal file
@@ -0,0 +1,417 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/opengl46/opengl46sky.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "environment/skydome.h"
|
||||
#include "model/Texture.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/utilities.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// irradiance of a radiance given as spherical harmonics, divided by pi (the units of the ambient light of the shading:
|
||||
// under a uniform sky of radiance L it is L): the coefficients of band l are scaled by A_l / pi (Ramamoorthi, Hanrahan 2001)
|
||||
float const SH_IRRADIANCE_BAND[3]{1.f, 2.f / 3.f, 0.25f};
|
||||
int const SH_BAND[gl::SKY_SH_COEFFICIENTS]{0, 1, 1, 1, 2, 2, 2, 2, 2};
|
||||
float const SH_Y00{0.282095f};
|
||||
float const SH_Y1{0.488603f};
|
||||
float const PI{3.14159265359f};
|
||||
|
||||
// real spherical harmonics of order 2 in a world space direction, in the order of light_common.glsl
|
||||
std::array<float, gl::SKY_SH_COEFFICIENTS> sh_basis(glm::vec3 const &n)
|
||||
{
|
||||
return {SH_Y00,
|
||||
SH_Y1 * n.y,
|
||||
SH_Y1 * n.z,
|
||||
SH_Y1 * n.x,
|
||||
1.092548f * n.x * n.y,
|
||||
1.092548f * n.y * n.z,
|
||||
0.315392f * (3.f * n.z * n.z - 1.f),
|
||||
1.092548f * n.x * n.z,
|
||||
0.546274f * (n.x * n.x - n.y * n.y)};
|
||||
}
|
||||
|
||||
float luminance(glm::vec3 const &Color)
|
||||
{
|
||||
return glm::dot(Color, glm::vec3{0.2126f, 0.7152f, 0.0722f});
|
||||
}
|
||||
|
||||
glm::vec3 desaturate(glm::vec3 const &Color, float const Amount)
|
||||
{
|
||||
return glm::mix(Color, glm::vec3{luminance(Color)}, Amount);
|
||||
}
|
||||
|
||||
// the response of color.frag to the vertex colours of the dome, so the light taken from the dome is the light it shows
|
||||
glm::vec3 sky_shoulder(glm::vec3 const &Color)
|
||||
{
|
||||
float const knee{0.25f};
|
||||
float const range{0.95f - knee};
|
||||
auto const excess{glm::max(Color - glm::vec3{knee}, glm::vec3{0.f})};
|
||||
return glm::min(Color, glm::vec3{knee}) + range * (glm::vec3{1.f} - glm::exp(-excess / range));
|
||||
}
|
||||
|
||||
// the atmosphere is calibrated against the old sky: these scales make a sky of the atmosphere, with the sun 15 to 60 degrees
|
||||
// high on a clear day, as bright as the old Perez dome is drawn on average, and the ambient light and the sunlight as strong
|
||||
// as the old ones, with the default ground albedo (measured on the tables worked out by the same shaders on a software
|
||||
// renderer, see the notes of the plan, stage S2)
|
||||
float const ATMOSPHERE_SKY_SCALE{16.f};
|
||||
float const ATMOSPHERE_AMBIENT_SCALE{44.f};
|
||||
float const ATMOSPHERE_SUN_SCALE{1.45f};
|
||||
// sun elevations (degrees) between which the sky goes over from the old model (night, twilight) to the atmosphere. the
|
||||
// atmosphere darkens at dusk much faster than the old model, which the rest of the lighting was tuned to
|
||||
float const ATMOSPHERE_FADE_START{-6.f};
|
||||
float const ATMOSPHERE_FADE_END{10.f};
|
||||
// the shading scales the sunlight and the ambient light by these (light_common.glsl), so the light the sun gives the
|
||||
// ground, in the units of the ambient light, is the diffuse colour times their ratio
|
||||
float const SHADING_SUN_TO_AMBIENT{1.5f / 0.65f};
|
||||
|
||||
// parameters of the earth's atmosphere after Hillaire 2020 (km, 1/km)
|
||||
glm::vec3 const RAYLEIGH_SCATTERING{5.802e-3f, 13.558e-3f, 33.1e-3f};
|
||||
float const RAYLEIGH_SCALE_HEIGHT{8.f};
|
||||
float const MIE_SCATTERING{3.996e-3f};
|
||||
float const MIE_EXTINCTION{4.440e-3f};
|
||||
float const MIE_SCALE_HEIGHT{1.2f};
|
||||
float const MIE_ASYMMETRY{0.8f};
|
||||
glm::vec3 const OZONE_ABSORPTION{0.650e-3f, 1.881e-3f, 0.085e-3f};
|
||||
float const OZONE_ALTITUDE{25.f};
|
||||
float const OZONE_HALF_WIDTH{15.f};
|
||||
float const BOTTOM_RADIUS{6360.f};
|
||||
float const TOP_RADIUS{6460.f};
|
||||
|
||||
} // namespace
|
||||
|
||||
opengl46_sky::~opengl46_sky()
|
||||
{
|
||||
GLuint const tables[]{m_transmittance_table, m_multiscattering_table, m_skyview_table};
|
||||
for (auto const table : tables)
|
||||
{
|
||||
if (table != 0)
|
||||
glDeleteTextures(1, &table);
|
||||
}
|
||||
}
|
||||
|
||||
GLuint opengl46_sky::create_table(GLsizei const Width, GLsizei const Height)
|
||||
{
|
||||
GLuint table{0};
|
||||
glCreateTextures(GL_TEXTURE_2D, 1, &table);
|
||||
glTextureStorage2D(table, 1, GL_RGBA16F, Width, Height);
|
||||
glTextureParameteri(table, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTextureParameteri(table, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTextureParameteri(table, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTextureParameteri(table, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
float const black[4]{0.f, 0.f, 0.f, 0.f};
|
||||
glClearTexImage(table, 0, GL_RGBA, GL_FLOAT, black);
|
||||
return table;
|
||||
}
|
||||
|
||||
void opengl46_sky::init()
|
||||
{
|
||||
auto const make_compute = [](std::string const &Name) {
|
||||
gl::shader shader(Name);
|
||||
return std::make_unique<gl::program>(std::vector<std::reference_wrapper<const gl::shader>>{shader});
|
||||
};
|
||||
m_transmittance_program = make_compute("atmosphere_transmittance.comp");
|
||||
m_multiscattering_program = make_compute("atmosphere_multiscattering.comp");
|
||||
m_skyview_program = make_compute("atmosphere_skyview.comp");
|
||||
m_ambient_program = make_compute("atmosphere_ambient.comp");
|
||||
m_prefilter_program = make_compute("envmap_prefilter.comp");
|
||||
|
||||
m_sky_ssbo = std::make_unique<gl::ssbo>(sizeof(gl::sky_data), gl::SKY_SSBO_BINDING);
|
||||
m_sky_ssbo->update(m_sky_data);
|
||||
m_atmosphere_ubo = std::make_unique<gl::ubo>(sizeof(gl::atmosphere_ubs), gl::ATMOSPHERE_UBO_BINDING);
|
||||
m_atmosphere_result_ssbo = std::make_unique<gl::ssbo>(sizeof(gl::atmosphere_result), gl::ATMOSPHERE_RESULT_SSBO_BINDING);
|
||||
|
||||
m_transmittance_table = create_table(gl::ATMOSPHERE_TRANSMITTANCE_WIDTH, gl::ATMOSPHERE_TRANSMITTANCE_HEIGHT);
|
||||
m_multiscattering_table = create_table(gl::ATMOSPHERE_MULTISCATTERING_SIZE, gl::ATMOSPHERE_MULTISCATTERING_SIZE);
|
||||
m_skyview_table = create_table(gl::ATMOSPHERE_SKYVIEW_WIDTH, gl::ATMOSPHERE_SKYVIEW_HEIGHT);
|
||||
|
||||
// a valid atmosphere for the sky shader from the start, the tables stay black until the first update
|
||||
m_atmosphere.rayleigh = glm::vec4{RAYLEIGH_SCATTERING, RAYLEIGH_SCALE_HEIGHT};
|
||||
m_atmosphere.mie = glm::vec4{MIE_SCATTERING, MIE_EXTINCTION, MIE_SCALE_HEIGHT, MIE_ASYMMETRY};
|
||||
m_atmosphere.ozone = glm::vec4{OZONE_ABSORPTION, OZONE_ALTITUDE};
|
||||
m_atmosphere.ground = glm::vec4{glm::vec3{Global.gfx_ambient_groundalbedo}, OZONE_HALF_WIDTH};
|
||||
m_atmosphere.sun_direction = glm::vec4{0.f, 1.f, 0.f, 0.f};
|
||||
m_atmosphere.bottom_radius = BOTTOM_RADIUS;
|
||||
m_atmosphere.top_radius = TOP_RADIUS;
|
||||
m_atmosphere.observer_radius = BOTTOM_RADIUS + 0.2f;
|
||||
m_atmosphere_ubo->update(m_atmosphere);
|
||||
}
|
||||
|
||||
void opengl46_sky::bind()
|
||||
{
|
||||
m_sky_ssbo->bind_base();
|
||||
m_atmosphere_ubo->bind_uniform();
|
||||
m_atmosphere_result_ssbo->bind_base();
|
||||
}
|
||||
|
||||
opengl46_sky::sh_coefficients opengl46_sky::dome_directional(CSkyDome const &Skydome, glm::vec3 const &Ambient, glm::vec3 const &Sunlight, float const Sunheight) const
|
||||
{
|
||||
sh_coefficients radiance{};
|
||||
auto const &vertices{Skydome.vertices()};
|
||||
auto const &colors{Skydome.colors()};
|
||||
// the vertices make a grid even in the elevation and the azimuth, so the solid angle of a vertex goes with the cosine of
|
||||
// its elevation. the bottom ring lies a little under the horizon and is taken as the horizon
|
||||
float totalweight{0.f};
|
||||
for (std::size_t i = 0; i < vertices.size() && i < colors.size(); ++i)
|
||||
{
|
||||
auto direction{glm::normalize(vertices[i])};
|
||||
// the dome darkens its colours towards the top and brightens the band above the horizon for the looks
|
||||
// (CSkyDome::RebuildColors), which would make the zenith almost black and the horizon the main source of light.
|
||||
// both are undone here, so the light comes from the sky the Perez model describes
|
||||
auto const height{std::max(direction.y, 0.05f)};
|
||||
auto const horizonband{std::clamp(1.f - height / 0.2f, 0.f, 1.f)};
|
||||
auto const looks{std::clamp(1.f - height * 0.75f, 0.25f, 1.f) * std::lerp(1.f, 1.5f + 0.75f * m_overcast, horizonband)};
|
||||
direction.y = std::max(direction.y, 0.f);
|
||||
direction = glm::normalize(direction);
|
||||
auto const weight{std::sqrt(std::max(1.f - direction.y * direction.y, 0.f))};
|
||||
auto const color{sky_shoulder(glm::pow(glm::max(colors[i] / looks, glm::vec3{0.f}), glm::vec3{2.2f}))};
|
||||
auto const basis{sh_basis(direction)};
|
||||
for (std::size_t c = 0; c < radiance.size(); ++c)
|
||||
radiance[c] += color * (basis[c] * weight);
|
||||
totalweight += weight;
|
||||
}
|
||||
if (totalweight <= 0.f)
|
||||
return {};
|
||||
for (auto &coefficient : radiance)
|
||||
coefficient *= 2.f * PI / totalweight;
|
||||
|
||||
// the ground, lit by the sky and the sun, fills the lower hemisphere with uniform light (see atmosphere_ambient.comp).
|
||||
// the dome is in its own units: the light of the sky on an upward facing surface is taken to be the ambient light,
|
||||
// which converts the sunlight to them
|
||||
auto const up{sh_basis(glm::vec3{0.f, 1.f, 0.f})};
|
||||
glm::vec3 skyup{0.f};
|
||||
for (std::size_t c = 0; c < radiance.size(); ++c)
|
||||
skyup += radiance[c] * (SH_IRRADIANCE_BAND[SH_BAND[c]] * up[c]);
|
||||
auto const tosky{luminance(skyup) / std::max(luminance(Ambient), 1e-4f)};
|
||||
auto const groundradiance{Global.gfx_ambient_groundalbedo * (skyup + Sunlight * SHADING_SUN_TO_AMBIENT * std::max(Sunheight, 0.f) * tosky)};
|
||||
radiance[0] += groundradiance * (SH_Y00 * 2.f * PI);
|
||||
radiance[1] += groundradiance * (SH_Y1 * -PI);
|
||||
|
||||
// to the irradiance, scaled so its average over all directions is the ambient light, channel by channel
|
||||
sh_coefficients directional{};
|
||||
glm::vec3 const average{radiance[0] * SH_Y00};
|
||||
auto const scale{Ambient / glm::max(average, glm::vec3{1e-4f})};
|
||||
for (std::size_t c = 1; c < radiance.size(); ++c)
|
||||
directional[c] = radiance[c] * SH_IRRADIANCE_BAND[SH_BAND[c]] * scale;
|
||||
return directional;
|
||||
}
|
||||
|
||||
void opengl46_sky::update_atmosphere(glm::vec3 const &Sundirection)
|
||||
{
|
||||
// aerosols grow with the turbidity the weather sets (3 clear .. 9 overcast)
|
||||
auto const turbidity{std::clamp(Global.fTurbidity, 3.f, 9.f)};
|
||||
auto const miescale{Global.gfx_atmosphere_haze * (1.f + 0.5f * (turbidity - 3.f))};
|
||||
glm::vec4 const medium{miescale, Global.gfx_ambient_groundalbedo, Global.gfx_atmosphere_altitude, 0.f};
|
||||
auto const mediumchanged{!m_tables_medium || *m_tables_medium != medium};
|
||||
// the sky-view table follows the sun when it moved by more than about a twentieth of a degree
|
||||
auto const sunchanged{!m_tables_sun || glm::dot(*m_tables_sun, Sundirection) < 0.9999995f};
|
||||
if (!mediumchanged && !sunchanged)
|
||||
return;
|
||||
|
||||
m_atmosphere.mie = glm::vec4{MIE_SCATTERING * miescale, MIE_EXTINCTION * miescale, MIE_SCALE_HEIGHT, MIE_ASYMMETRY};
|
||||
m_atmosphere.ground = glm::vec4{glm::vec3{Global.gfx_ambient_groundalbedo}, OZONE_HALF_WIDTH};
|
||||
m_atmosphere.sun_direction = glm::vec4{Sundirection, 0.f};
|
||||
m_atmosphere.observer_radius = BOTTOM_RADIUS + std::max(Global.gfx_atmosphere_altitude, 0.f) * 0.001f + 0.001f;
|
||||
m_atmosphere_ubo->update(m_atmosphere);
|
||||
m_atmosphere_ubo->bind_uniform();
|
||||
m_atmosphere_result_ssbo->bind_base();
|
||||
|
||||
m_atmosphere_timer.begin();
|
||||
if (mediumchanged)
|
||||
{
|
||||
glBindImageTexture(gl::ATMOSPHERE_TRANSMITTANCE_UNIT, m_transmittance_table, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA16F);
|
||||
m_transmittance_program->dispatch((gl::ATMOSPHERE_TRANSMITTANCE_WIDTH + 7) / 8, (gl::ATMOSPHERE_TRANSMITTANCE_HEIGHT + 7) / 8);
|
||||
glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT | GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
|
||||
|
||||
glBindTextureUnit(gl::ATMOSPHERE_TRANSMITTANCE_UNIT, m_transmittance_table);
|
||||
glBindImageTexture(gl::ATMOSPHERE_MULTISCATTERING_UNIT, m_multiscattering_table, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA16F);
|
||||
m_multiscattering_program->dispatch(gl::ATMOSPHERE_MULTISCATTERING_SIZE, gl::ATMOSPHERE_MULTISCATTERING_SIZE);
|
||||
glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT | GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
|
||||
}
|
||||
|
||||
glBindTextureUnit(gl::ATMOSPHERE_TRANSMITTANCE_UNIT, m_transmittance_table);
|
||||
glBindTextureUnit(gl::ATMOSPHERE_MULTISCATTERING_UNIT, m_multiscattering_table);
|
||||
glBindImageTexture(gl::ATMOSPHERE_SKYVIEW_UNIT, m_skyview_table, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA16F);
|
||||
m_skyview_program->dispatch((gl::ATMOSPHERE_SKYVIEW_WIDTH + 7) / 8, (gl::ATMOSPHERE_SKYVIEW_HEIGHT + 7) / 8);
|
||||
glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT | GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
|
||||
|
||||
glBindTextureUnit(gl::ATMOSPHERE_SKYVIEW_UNIT, m_skyview_table);
|
||||
m_ambient_program->dispatch(1);
|
||||
glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT | GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
m_atmosphere_timer.end();
|
||||
|
||||
// the units the passes used are below the ones of the texture cache, which is told they changed
|
||||
for (GLuint unit : {gl::ATMOSPHERE_TRANSMITTANCE_UNIT, gl::ATMOSPHERE_MULTISCATTERING_UNIT, gl::ATMOSPHERE_SKYVIEW_UNIT})
|
||||
glBindTextureUnit(unit, 0);
|
||||
opengl_texture::reset_unit_cache();
|
||||
|
||||
m_result_readback.push(*m_atmosphere_result_ssbo);
|
||||
m_tables_medium = medium;
|
||||
m_tables_sun = Sundirection;
|
||||
}
|
||||
|
||||
void opengl46_sky::update(opengl46_light &Sunlight, glm::vec3 &Fogcolor, CSkyDome const &Skydome, glm::vec3 const &Sundirection, float const Sunangle)
|
||||
{
|
||||
// the light the simulation set, which is kept where the atmosphere is not used
|
||||
glm::vec3 const oldambient{Sunlight.ambient};
|
||||
glm::vec3 const olddiffuse{Sunlight.diffuse};
|
||||
glm::vec3 const oldfog{Fogcolor};
|
||||
m_overcast = std::clamp(Global.Overcast, 0.f, 1.f);
|
||||
|
||||
// atmosphere
|
||||
m_weight = 0.f;
|
||||
if (Global.gfx_atmosphere)
|
||||
{
|
||||
update_atmosphere(glm::normalize(Sundirection));
|
||||
gl::atmosphere_result result;
|
||||
if (m_result_readback.pop(&result))
|
||||
m_result = result;
|
||||
if (m_result)
|
||||
m_weight = glm::smoothstep(ATMOSPHERE_FADE_START, ATMOSPHERE_FADE_END, Sunangle);
|
||||
}
|
||||
|
||||
sh_coefficients directional{};
|
||||
if (Global.gfx_ambient_sh)
|
||||
{
|
||||
// the dome changes with the sun, the weather and the light the simulation works out from them
|
||||
std::array<glm::vec4, 3> const key{glm::vec4{Sundirection, Sunlight.direction.y}, glm::vec4{oldambient, m_overcast}, glm::vec4{olddiffuse, Global.gfx_ambient_groundalbedo}};
|
||||
if (!m_dome_key || *m_dome_key != key)
|
||||
{
|
||||
m_dome_sh = dome_directional(Skydome, oldambient, olddiffuse, -Sunlight.direction.y);
|
||||
m_dome_key = key;
|
||||
}
|
||||
directional = m_dome_sh;
|
||||
}
|
||||
|
||||
m_sunlight = olddiffuse;
|
||||
m_ambient = oldambient;
|
||||
m_fog = oldfog;
|
||||
m_sun_disc_color = glm::vec3{1.f};
|
||||
if (m_weight > 0.f)
|
||||
{
|
||||
auto const &result{*m_result};
|
||||
// the camera's white balance: the sun high in the sky is white
|
||||
auto const balance{1.f / glm::max(glm::vec3{result.zenith_transmittance}, glm::vec3{1e-3f})};
|
||||
m_white_balance = balance / std::max(luminance(balance), 1e-4f);
|
||||
|
||||
// the atmosphere has no clouds: the overcast takes away the sunlight, greys the sky and evens out its light
|
||||
auto const sun{glm::vec3{result.sun_transmittance} * m_white_balance * (ATMOSPHERE_SUN_SCALE * Global.gfx_atmosphere_sun) * (1.f - 0.65f * m_overcast)};
|
||||
auto const ambientscale{ATMOSPHERE_AMBIENT_SCALE * Global.gfx_atmosphere_ambient};
|
||||
auto const ambient{desaturate(glm::vec3{result.radiance_sh[0]} * SH_Y00 * m_white_balance * ambientscale, 0.5f * m_overcast)};
|
||||
m_sky_scale = ATMOSPHERE_SKY_SCALE * Global.gfx_atmosphere_sky;
|
||||
auto const fog{desaturate(glm::vec3{result.horizon} * m_white_balance * m_sky_scale, 0.5f * m_overcast)};
|
||||
m_sky_flat_luminance = luminance(glm::vec3{result.average} * m_white_balance * m_sky_scale);
|
||||
|
||||
m_sunlight = glm::mix(olddiffuse, sun, m_weight);
|
||||
m_ambient = glm::mix(oldambient, ambient, m_weight);
|
||||
m_fog = glm::mix(oldfog, fog, m_weight);
|
||||
auto const sundisc{glm::vec3{result.sun_transmittance} * m_white_balance};
|
||||
m_sun_disc_color = sundisc / std::max({sundisc.r, sundisc.g, sundisc.b, 1e-4f});
|
||||
|
||||
if (Global.gfx_ambient_sh)
|
||||
{
|
||||
auto const evenness{1.f - 0.75f * m_overcast};
|
||||
for (std::size_t c = 1; c < directional.size(); ++c)
|
||||
{
|
||||
auto const atmosphere{desaturate(glm::vec3{result.radiance_sh[c]} * m_white_balance * (SH_IRRADIANCE_BAND[SH_BAND[c]] * ambientscale * evenness), 0.5f * m_overcast)};
|
||||
directional[c] = glm::mix(directional[c], atmosphere, m_weight);
|
||||
}
|
||||
}
|
||||
|
||||
Sunlight.diffuse = glm::vec4{m_sunlight, Sunlight.diffuse.a};
|
||||
Sunlight.ambient = glm::vec4{m_ambient, Sunlight.ambient.a};
|
||||
Fogcolor = m_fog;
|
||||
}
|
||||
|
||||
// directional part of the ambient light for the shading
|
||||
gl::sky_data skydata{};
|
||||
for (std::size_t c = 1; c < directional.size(); ++c)
|
||||
skydata.ambient_sh[c] = glm::vec4{directional[c] * Global.gfx_ambient_sh_strength, 0.f};
|
||||
if (!m_sky_data_uploaded || std::memcmp(&skydata, &m_sky_data, sizeof(skydata)) != 0)
|
||||
{
|
||||
m_sky_data = skydata;
|
||||
m_sky_ssbo->update(m_sky_data);
|
||||
m_sky_data_uploaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
void opengl46_sky::setup_sky(gl::model_ubs &Model) const
|
||||
{
|
||||
glBindTextureUnit(gl::SKY_VIEW_TEX, m_skyview_table);
|
||||
Model.param[0] = glm::vec4{m_weight, m_sky_scale, 0.75f * m_overcast, m_sky_flat_luminance};
|
||||
Model.param[1] = glm::vec4{m_white_balance, 0.f};
|
||||
}
|
||||
|
||||
glm::vec3 opengl46_sky::sun_disc_color(glm::vec3 const &Oldcolor) const
|
||||
{
|
||||
return glm::mix(Oldcolor, m_sun_disc_color, m_weight);
|
||||
}
|
||||
|
||||
void opengl46_sky::prefilter(gl::cubemap const &Source)
|
||||
{
|
||||
if (!m_prefiltered)
|
||||
{
|
||||
m_prefiltered = std::make_unique<gl::cubemap>();
|
||||
m_prefiltered->alloc_levels(GL_RGBA16F, static_cast<int>(gl::ENVMAP_GGX_SIZE), static_cast<int>(gl::ENVMAP_GGX_LEVELS));
|
||||
}
|
||||
|
||||
m_prefilter_timer.begin();
|
||||
Source.bind(GL_TEXTURE0 + gl::ENV_TEX);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
for (std::size_t level = 0; level < gl::ENVMAP_GGX_LEVELS; ++level)
|
||||
{
|
||||
auto const size{static_cast<int>(gl::ENVMAP_GGX_SIZE >> level)};
|
||||
auto const roughness{gl::ENVMAP_GGX_ROUGHNESS_MIN + (1.f - gl::ENVMAP_GGX_ROUGHNESS_MIN) * static_cast<float>(level) / static_cast<float>(gl::ENVMAP_GGX_LEVELS - 1)};
|
||||
glBindImageTexture(0, *m_prefiltered, static_cast<GLint>(level), GL_TRUE, 0, GL_WRITE_ONLY, GL_RGBA16F);
|
||||
glProgramUniform1f(*m_prefilter_program, 0, roughness);
|
||||
glProgramUniform1i(*m_prefilter_program, 1, size);
|
||||
glProgramUniform1f(*m_prefilter_program, 2, static_cast<float>(gl::ENVMAP_SIZE));
|
||||
glProgramUniform1i(*m_prefilter_program, 3, 64);
|
||||
auto const groups{static_cast<GLuint>((size + gl::ENVMAP_GGX_GROUP_SIZE - 1) / gl::ENVMAP_GGX_GROUP_SIZE)};
|
||||
m_prefilter_program->dispatch(groups, groups, 6);
|
||||
}
|
||||
glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT | GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
|
||||
m_prefilter_timer.end();
|
||||
glBindImageTexture(0, 0, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA16F);
|
||||
m_prefiltered_ready = true;
|
||||
}
|
||||
|
||||
std::string opengl46_sky::info_times()
|
||||
{
|
||||
std::string text;
|
||||
if (auto const time = m_atmosphere_timer.result())
|
||||
text += " atmosphere: " + to_string(static_cast<double>(*time) / 1000000.0, 3) + " ms\n";
|
||||
if (auto const time = m_prefilter_timer.result())
|
||||
text += " envmap ggx: " + to_string(static_cast<double>(*time) / 1000000.0, 3) + " ms\n";
|
||||
return text;
|
||||
}
|
||||
|
||||
void opengl46_sky::draw_debug_ui()
|
||||
{
|
||||
ImGui::Checkbox("directional ambient light (gfx.ambient.sh)", &Global.gfx_ambient_sh);
|
||||
ImGui::SliderFloat("directional strength", &Global.gfx_ambient_sh_strength, 0.f, 2.f);
|
||||
ImGui::SliderFloat("ground albedo", &Global.gfx_ambient_groundalbedo, 0.f, 1.f);
|
||||
ImGui::Checkbox("GGX prefiltered reflections (gfx.envmap.ggx)", &Global.gfx_envmap_ggx);
|
||||
ImGui::Checkbox("physical atmosphere (gfx.atmosphere)", &Global.gfx_atmosphere);
|
||||
ImGui::SliderFloat("sky", &Global.gfx_atmosphere_sky, 0.f, 4.f);
|
||||
ImGui::SliderFloat("ambient", &Global.gfx_atmosphere_ambient, 0.f, 4.f);
|
||||
ImGui::SliderFloat("sun", &Global.gfx_atmosphere_sun, 0.f, 4.f);
|
||||
ImGui::SliderFloat("haze", &Global.gfx_atmosphere_haze, 0.f, 8.f);
|
||||
ImGui::SliderFloat("altitude (m)", &Global.gfx_atmosphere_altitude, 0.f, 3000.f);
|
||||
ImGui::Text("atmosphere share %.2f%s", m_weight, (Global.gfx_atmosphere && !m_result) ? " (waiting for the first read back)" : "");
|
||||
ImGui::Text("sun %.3f %.3f %.3f", m_sunlight.r, m_sunlight.g, m_sunlight.b);
|
||||
ImGui::Text("ambient %.3f %.3f %.3f", m_ambient.r, m_ambient.g, m_ambient.b);
|
||||
ImGui::Text("fog %.3f %.3f %.3f", m_fog.r, m_fog.g, m_fog.b);
|
||||
ImGui::Text("up / down ambient %.3f / %.3f", luminance(m_ambient + glm::vec3{m_sky_data.ambient_sh[1]} * SH_Y1), luminance(m_ambient - glm::vec3{m_sky_data.ambient_sh[1]} * SH_Y1));
|
||||
}
|
||||
128
rendering/opengl46/opengl46sky.h
Normal file
128
rendering/opengl46/opengl46sky.h
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "gl/ubo.h"
|
||||
#include "gl/ssbo.h"
|
||||
#include "gl/shader.h"
|
||||
#include "gl/cubemap.h"
|
||||
#include "gl/query.h"
|
||||
#include "gl/readback.h"
|
||||
#include "rendering/opengl46/opengl46light.h"
|
||||
|
||||
class CSkyDome;
|
||||
|
||||
// light of the sky for the opengl 4.6 renderer:
|
||||
// - the directional part of the ambient light, as spherical harmonics in sky_ssbo, worked out from the colours of the sky
|
||||
// dome or from the physical atmosphere. the shading adds it to the uniform ambient light (light_common.glsl), so the
|
||||
// average of the ambient light stays what the rest of the renderer sets
|
||||
// - the physical atmosphere (Hillaire 2020, atmosphere_*.comp): look-up tables made in compute passes, the sky drawn from
|
||||
// them (sky_atmosphere.frag), and the sunlight, ambient light and fog colour read back from them a few frames later.
|
||||
// these are values for the render only: the simulation (Global.DayLight, fLuminance) keeps its own
|
||||
// - the environment map prefiltered with the GGX lobe, for rough reflections (envmap_prefilter.comp)
|
||||
// all of it is switched by Global.gfx_ambient_sh, gfx_atmosphere and gfx_envmap_ggx; off, the renderer works as before
|
||||
class opengl46_sky
|
||||
{
|
||||
public:
|
||||
opengl46_sky() = default;
|
||||
~opengl46_sky();
|
||||
opengl46_sky(const opengl46_sky &) = delete;
|
||||
opengl46_sky &operator=(const opengl46_sky &) = delete;
|
||||
|
||||
// creates the buffers, tables and programs. throws gl::shader_exception
|
||||
void init();
|
||||
// binds the buffers the shaders read; bindings are context state, so done in every context
|
||||
void bind();
|
||||
// works out the light of the sky for the frame and replaces with it the colours of Sunlight (diffuse, ambient) and
|
||||
// Fogcolor, which come in as the simulation set them. runs the compute passes, so it is called in the main context,
|
||||
// before the passes of the frame. Sundirection and Sunangle (degrees) are those of the sun, also when the moon is the key light
|
||||
void update(opengl46_light &Sunlight, glm::vec3 &Fogcolor, CSkyDome const &Skydome, glm::vec3 const &Sundirection, float const Sunangle);
|
||||
// share of the atmosphere in the sky of the frame; 0 when the old sky is drawn alone
|
||||
float atmosphere_weight() const
|
||||
{
|
||||
return m_weight;
|
||||
}
|
||||
// sets the shader parameters of the sky drawn with the atmosphere (sky_atmosphere.frag) and binds its table
|
||||
void setup_sky(gl::model_ubs &Model) const;
|
||||
// colour of the sun disc, the old one blended with the colour the atmosphere gives it
|
||||
glm::vec3 sun_disc_color(glm::vec3 const &Oldcolor) const;
|
||||
// prefilters the levels of the GGX map from the environment map, which has to have its mipmaps
|
||||
void prefilter(gl::cubemap const &Source);
|
||||
// the prefiltered map, null until it was made once
|
||||
gl::cubemap const *prefiltered() const
|
||||
{
|
||||
return m_prefiltered_ready ? m_prefiltered.get() : nullptr;
|
||||
}
|
||||
// lines for the gpu times of the debug panel
|
||||
std::string info_times();
|
||||
// controls of the debug window
|
||||
void draw_debug_ui();
|
||||
|
||||
private:
|
||||
// spherical harmonics of order 2, rgb
|
||||
using sh_coefficients = std::array<glm::vec3, gl::SKY_SH_COEFFICIENTS>;
|
||||
|
||||
// directional part of the ambient light from the colours of the dome, scaled so its average is Ambient
|
||||
sh_coefficients dome_directional(CSkyDome const &Skydome, glm::vec3 const &Ambient, glm::vec3 const &Sunlight, float const Sunheight) const;
|
||||
// sets up the parameters of the atmosphere for the frame and runs the passes whose input changed
|
||||
void update_atmosphere(glm::vec3 const &Sundirection);
|
||||
// creates an immutable 2d texture for a look-up table
|
||||
static GLuint create_table(GLsizei Width, GLsizei Height);
|
||||
|
||||
// buffers the shaders read
|
||||
std::unique_ptr<gl::ssbo> m_sky_ssbo;
|
||||
std::unique_ptr<gl::ubo> m_atmosphere_ubo;
|
||||
std::unique_ptr<gl::ssbo> m_atmosphere_result_ssbo;
|
||||
gl::sky_data m_sky_data{};
|
||||
bool m_sky_data_uploaded{false};
|
||||
|
||||
// atmosphere
|
||||
std::unique_ptr<gl::program> m_transmittance_program;
|
||||
std::unique_ptr<gl::program> m_multiscattering_program;
|
||||
std::unique_ptr<gl::program> m_skyview_program;
|
||||
std::unique_ptr<gl::program> m_ambient_program;
|
||||
GLuint m_transmittance_table{0};
|
||||
GLuint m_multiscattering_table{0};
|
||||
GLuint m_skyview_table{0};
|
||||
gl::atmosphere_ubs m_atmosphere{};
|
||||
// inputs the tables were last made for: the transmittance and multiple scattering tables depend on the medium only,
|
||||
// the sky-view table also on the sun
|
||||
std::optional<glm::vec4> m_tables_medium;
|
||||
std::optional<glm::vec3> m_tables_sun;
|
||||
gl::readback m_result_readback{sizeof(gl::atmosphere_result)};
|
||||
std::optional<gl::atmosphere_result> m_result;
|
||||
gl::gpu_timer m_atmosphere_timer;
|
||||
|
||||
// what update() made of the frame, for the sky, the sun disc and the debug window
|
||||
float m_weight{0.f};
|
||||
glm::vec3 m_white_balance{1.f};
|
||||
float m_sky_scale{0.f};
|
||||
float m_overcast{0.f};
|
||||
float m_sky_flat_luminance{0.f};
|
||||
glm::vec3 m_sun_disc_color{1.f};
|
||||
glm::vec3 m_sunlight{0.f};
|
||||
glm::vec3 m_ambient{0.f};
|
||||
glm::vec3 m_fog{0.f};
|
||||
|
||||
// cache of the directional ambient light of the dome, remade when its inputs change
|
||||
std::optional<std::array<glm::vec4, 3>> m_dome_key;
|
||||
sh_coefficients m_dome_sh{};
|
||||
|
||||
// prefiltered environment map
|
||||
std::unique_ptr<gl::program> m_prefilter_program;
|
||||
std::unique_ptr<gl::cubemap> m_prefiltered;
|
||||
bool m_prefiltered_ready{false};
|
||||
gl::gpu_timer m_prefilter_timer;
|
||||
};
|
||||
@@ -19,6 +19,8 @@ void opengl46_skydome::update() {
|
||||
gl::shader vert("vbocolor.vert");
|
||||
gl::shader frag("color.frag");
|
||||
m_shader.emplace(std::vector<std::reference_wrapper<const gl::shader>>({vert, frag}));
|
||||
gl::shader atmospherefrag("sky_atmosphere.frag");
|
||||
m_atmosphereshader.emplace(std::vector<std::reference_wrapper<const gl::shader>>({vert, atmospherefrag}));
|
||||
}
|
||||
|
||||
auto &skydome { simulation::Environment.skydome() };
|
||||
@@ -55,11 +57,16 @@ void opengl46_skydome::update() {
|
||||
}
|
||||
|
||||
// render skydome to screen
|
||||
void opengl46_skydome::render() {
|
||||
void opengl46_skydome::render( bool const Atmosphere ) {
|
||||
|
||||
if( ( !m_shader) || ( !m_vao ) ) { return; }
|
||||
|
||||
m_shader->bind();
|
||||
if( Atmosphere && m_atmosphereshader ) {
|
||||
m_atmosphereshader->bind();
|
||||
}
|
||||
else {
|
||||
m_shader->bind();
|
||||
}
|
||||
m_vao->bind();
|
||||
|
||||
auto const &skydome { simulation::Environment.skydome() };
|
||||
|
||||
@@ -23,8 +23,9 @@ public:
|
||||
// methods
|
||||
// updates data stores on the opengl end. NOTE: unbinds buffers
|
||||
void update();
|
||||
// draws the skydome
|
||||
void render();
|
||||
// draws the skydome. with Atmosphere the colours come from the tables of the physical atmosphere (sky_atmosphere.frag),
|
||||
// which the caller binds along with the parameters of the shader
|
||||
void render( bool const Atmosphere = false );
|
||||
|
||||
private:
|
||||
// members
|
||||
@@ -32,5 +33,6 @@ private:
|
||||
std::optional<gl::buffer> m_indexbuffer;
|
||||
std::optional<gl::buffer> m_coloursbuffer;
|
||||
std::optional<gl::program> m_shader;
|
||||
std::optional<gl::program> m_atmosphereshader;
|
||||
std::optional<gl::vao> m_vao;
|
||||
};
|
||||
|
||||
127
shaders/atmosphere_ambient.comp
Normal file
127
shaders/atmosphere_ambient.comp
Normal file
@@ -0,0 +1,127 @@
|
||||
// what the cpu needs from the atmosphere, worked out from the sky-view table in one work group: the light arriving at the
|
||||
// observer from every direction as spherical harmonics (the ground below the horizon lit by the sun and the sky), the
|
||||
// transmittance towards the sun, and the average light of the sky above the horizon and over the whole sky.
|
||||
// run after the sky-view table; the result is read back by the cpu a few frames later
|
||||
|
||||
#include <common>
|
||||
#include <atmosphere_common.glsl>
|
||||
|
||||
layout(binding = ATMOSPHERE_TRANSMITTANCE_UNIT) uniform sampler2D atmo_transmittance_lut;
|
||||
layout(binding = ATMOSPHERE_SKYVIEW_UNIT) uniform sampler2D atmo_skyview_lut;
|
||||
|
||||
layout(local_size_x = ATMOSPHERE_AMBIENT_GROUP_SIZE) in;
|
||||
|
||||
layout(std430, binding = ATMOSPHERE_RESULT_SSBO_BINDING) writeonly buffer atmosphere_result_ssbo
|
||||
{
|
||||
vec4 result_radiance_sh[9];
|
||||
vec4 result_sun_transmittance;
|
||||
vec4 result_horizon;
|
||||
vec4 result_average;
|
||||
vec4 result_zenith_transmittance;
|
||||
};
|
||||
|
||||
// directions of the upper hemisphere each invocation takes
|
||||
const uint DIRECTIONS_PER_INVOCATION = 4U;
|
||||
const uint DIRECTIONS = ATMOSPHERE_AMBIENT_GROUP_SIZE * DIRECTIONS_PER_INVOCATION;
|
||||
// the sky up to this sine of the elevation is the horizon (about 6 degrees)
|
||||
const float HORIZON_BAND = 0.1;
|
||||
|
||||
// partial sums: the 9 coefficients, the light on an upward facing surface, the horizon and its weight, the sky average
|
||||
const int SUMS = 12;
|
||||
shared vec4 shared_sums[ATMOSPHERE_AMBIENT_GROUP_SIZE];
|
||||
|
||||
void sh_basis(vec3 n, out float basis[9])
|
||||
{
|
||||
basis[0] = 0.282095;
|
||||
basis[1] = 0.488603 * n.y;
|
||||
basis[2] = 0.488603 * n.z;
|
||||
basis[3] = 0.488603 * n.x;
|
||||
basis[4] = 1.092548 * n.x * n.y;
|
||||
basis[5] = 1.092548 * n.y * n.z;
|
||||
basis[6] = 0.315392 * (3.0 * n.z * n.z - 1.0);
|
||||
basis[7] = 1.092548 * n.x * n.z;
|
||||
basis[8] = 0.546274 * (n.x * n.x - n.y * n.y);
|
||||
}
|
||||
|
||||
// sum of the value of every invocation, valid in invocation 0
|
||||
vec4 reduce(vec4 value)
|
||||
{
|
||||
uint index = gl_LocalInvocationID.x;
|
||||
barrier();
|
||||
shared_sums[index] = value;
|
||||
barrier();
|
||||
for (uint stride = ATMOSPHERE_AMBIENT_GROUP_SIZE / 2U; stride > 0U; stride >>= 1)
|
||||
{
|
||||
if (index < stride)
|
||||
shared_sums[index] += shared_sums[index + stride];
|
||||
barrier();
|
||||
}
|
||||
return shared_sums[0];
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
uint index = gl_LocalInvocationID.x;
|
||||
vec3 sundirection = normalize(atmo_sun_direction.xyz);
|
||||
|
||||
vec3 sums[SUMS];
|
||||
for (int i = 0; i < SUMS; ++i)
|
||||
sums[i] = vec3(0.0);
|
||||
|
||||
// even spread over the upper hemisphere: a fibonacci spiral in the sine of the elevation
|
||||
const float goldenangle = 2.39996323;
|
||||
const float solidangle = 2.0 * ATMO_PI / float(DIRECTIONS);
|
||||
for (uint k = 0U; k < DIRECTIONS_PER_INVOCATION; ++k)
|
||||
{
|
||||
uint i = index * DIRECTIONS_PER_INVOCATION + k;
|
||||
float y = (float(i) + 0.5) / float(DIRECTIONS);
|
||||
float r = sqrt(max(1.0 - y * y, 0.0));
|
||||
float azimuth = goldenangle * float(i);
|
||||
vec3 direction = vec3(r * cos(azimuth), y, r * sin(azimuth));
|
||||
|
||||
vec3 radiance = textureLod(atmo_skyview_lut, atmo_skyview_uv_world(direction, sundirection), 0.0).rgb;
|
||||
float basis[9];
|
||||
sh_basis(direction, basis);
|
||||
for (int c = 0; c < 9; ++c)
|
||||
sums[c] += radiance * (basis[c] * solidangle);
|
||||
sums[9] += radiance * (y * solidangle);
|
||||
if (y < HORIZON_BAND)
|
||||
{
|
||||
sums[10] += radiance;
|
||||
sums[11] += vec3(1.0);
|
||||
}
|
||||
}
|
||||
|
||||
vec3 total[SUMS];
|
||||
for (int i = 0; i < SUMS; ++i)
|
||||
total[i] = reduce(vec4(sums[i], 0.0)).rgb;
|
||||
|
||||
if (index != 0U)
|
||||
return;
|
||||
|
||||
// the transmittance towards the sun, zero once the planet is in the way
|
||||
float radius = atmo_observer_radius;
|
||||
vec3 origin = vec3(0.0, radius, 0.0);
|
||||
vec3 suntransmittance = texture(atmo_transmittance_lut, atmo_transmittance_uv(radius, sundirection.y)).rgb;
|
||||
if (atmo_ray_sphere_nearest(origin, sundirection, atmo_bottom_radius) >= 0.0)
|
||||
suntransmittance = vec3(0.0);
|
||||
|
||||
// the ground, a lambertian surface lit by the sun and the sky, fills the lower hemisphere with uniform light. its
|
||||
// spherical harmonics have only the constant term and the vertical one: for a uniform radiance L over the lower
|
||||
// hemisphere, 0.282095 * 2 pi * L and 0.488603 * (-pi) * L
|
||||
// the band 0 coefficient of the sky alone over the solid angle of the hemisphere
|
||||
vec3 skyaverage = total[0] / (0.282095 * 2.0 * ATMO_PI);
|
||||
vec3 irradiance = suntransmittance * max(sundirection.y, 0.0) + total[9];
|
||||
vec3 groundradiance = atmo_ground.rgb / ATMO_PI * irradiance;
|
||||
|
||||
total[0] += groundradiance * (0.282095 * 2.0 * ATMO_PI);
|
||||
total[1] += groundradiance * (0.488603 * -ATMO_PI);
|
||||
for (int c = 0; c < 9; ++c)
|
||||
result_radiance_sh[c] = vec4(total[c], 0.0);
|
||||
|
||||
result_sun_transmittance = vec4(suntransmittance, 0.0);
|
||||
result_horizon = vec4(total[10] / max(total[11].x, 1.0), 0.0);
|
||||
result_average = vec4(skyaverage, 0.0);
|
||||
// what the sun high in the sky looks like at the observer, for the white balance
|
||||
result_zenith_transmittance = vec4(texture(atmo_transmittance_lut, atmo_transmittance_uv(radius, 1.0)).rgb, 0.0);
|
||||
}
|
||||
194
shaders/atmosphere_common.glsl
Normal file
194
shaders/atmosphere_common.glsl
Normal file
@@ -0,0 +1,194 @@
|
||||
// physically based sky after S. Hillaire, "A Scalable and Production Ready Sky and Atmosphere Rendering Technique"
|
||||
// (EGSR 2020): transmittance, multiple scattering and sky-view look-up tables. distances in kilometres, coefficients per
|
||||
// kilometre. the planet is centred at the origin; the look-up tables are worked out in a frame with z up at the observer,
|
||||
// the world (y up) is mapped to it by the callers. radiance is that of a sun of illuminance 1
|
||||
|
||||
layout(std140, binding = ATMOSPHERE_UBO_BINDING) uniform atmosphere_ubo
|
||||
{
|
||||
// rgb: rayleigh scattering, w: rayleigh scale height
|
||||
vec4 atmo_rayleigh;
|
||||
// x: mie scattering, y: mie extinction, z: mie scale height, w: mie phase asymmetry
|
||||
vec4 atmo_mie;
|
||||
// rgb: ozone absorption, w: altitude of the middle of the ozone layer
|
||||
vec4 atmo_ozone;
|
||||
// rgb: albedo of the ground, w: half width of the ozone layer
|
||||
vec4 atmo_ground;
|
||||
// world space direction towards the sun (y up)
|
||||
vec4 atmo_sun_direction;
|
||||
float atmo_bottom_radius;
|
||||
float atmo_top_radius;
|
||||
// distance of the observer from the centre of the planet
|
||||
float atmo_observer_radius;
|
||||
};
|
||||
|
||||
const float ATMO_PI = 3.14159265359;
|
||||
|
||||
struct atmo_medium_s
|
||||
{
|
||||
vec3 scattering;
|
||||
vec3 extinction;
|
||||
vec3 rayleigh_scattering;
|
||||
vec3 mie_scattering;
|
||||
};
|
||||
|
||||
atmo_medium_s atmo_sample_medium(float radius)
|
||||
{
|
||||
float height = max(radius - atmo_bottom_radius, 0.0);
|
||||
float rayleighdensity = exp(-height / atmo_rayleigh.w);
|
||||
float miedensity = exp(-height / atmo_mie.z);
|
||||
float ozonedensity = max(0.0, 1.0 - abs(height - atmo_ozone.w) / atmo_ground.w);
|
||||
|
||||
atmo_medium_s medium;
|
||||
medium.rayleigh_scattering = atmo_rayleigh.rgb * rayleighdensity;
|
||||
medium.mie_scattering = vec3(atmo_mie.x * miedensity);
|
||||
medium.scattering = medium.rayleigh_scattering + medium.mie_scattering;
|
||||
medium.extinction = medium.rayleigh_scattering + vec3(atmo_mie.y * miedensity) + atmo_ozone.rgb * ozonedensity;
|
||||
return medium;
|
||||
}
|
||||
|
||||
// distance along a ray (unit direction) to the nearest point ahead of the origin where it meets the sphere, -1 if it does not
|
||||
float atmo_ray_sphere_nearest(vec3 origin, vec3 direction, float radius)
|
||||
{
|
||||
float b = dot(origin, direction);
|
||||
float c = dot(origin, origin) - radius * radius;
|
||||
float discriminant = b * b - c;
|
||||
if (discriminant < 0.0)
|
||||
return -1.0;
|
||||
float root = sqrt(discriminant);
|
||||
float t0 = -b - root;
|
||||
float t1 = -b + root;
|
||||
if (t0 < 0.0 && t1 < 0.0)
|
||||
return -1.0;
|
||||
if (t0 < 0.0)
|
||||
return t1;
|
||||
if (t1 < 0.0)
|
||||
return t0;
|
||||
return min(t0, t1);
|
||||
}
|
||||
|
||||
float atmo_rayleigh_phase(float cosangle)
|
||||
{
|
||||
return 3.0 / (16.0 * ATMO_PI) * (1.0 + cosangle * cosangle);
|
||||
}
|
||||
|
||||
// Cornette-Shanks
|
||||
float atmo_mie_phase(float cosangle)
|
||||
{
|
||||
float g = atmo_mie.w;
|
||||
float k = 3.0 / (8.0 * ATMO_PI) * (1.0 - g * g) / (2.0 + g * g);
|
||||
return k * (1.0 + cosangle * cosangle) / pow(max(1.0 + g * g - 2.0 * g * cosangle, 1e-4), 1.5);
|
||||
}
|
||||
|
||||
// texel centres of a table of the given resolution span [0, 1] of the parameter
|
||||
float atmo_unit_to_sub_uv(float u, float resolution)
|
||||
{
|
||||
return (u + 0.5 / resolution) * (resolution / (resolution + 1.0));
|
||||
}
|
||||
|
||||
float atmo_sub_uv_to_unit(float u, float resolution)
|
||||
{
|
||||
return (u - 0.5 / resolution) * (resolution / (resolution - 1.0));
|
||||
}
|
||||
|
||||
// transmittance table: x the distance to the top of the atmosphere along the view, y the height
|
||||
vec2 atmo_transmittance_uv(float radius, float cosviewzenith)
|
||||
{
|
||||
float H = sqrt(max(0.0, atmo_top_radius * atmo_top_radius - atmo_bottom_radius * atmo_bottom_radius));
|
||||
float rho = sqrt(max(0.0, radius * radius - atmo_bottom_radius * atmo_bottom_radius));
|
||||
float discriminant = radius * radius * (cosviewzenith * cosviewzenith - 1.0) + atmo_top_radius * atmo_top_radius;
|
||||
float d = max(0.0, -radius * cosviewzenith + sqrt(max(discriminant, 0.0)));
|
||||
float dmin = atmo_top_radius - radius;
|
||||
float dmax = rho + H;
|
||||
return vec2((d - dmin) / max(dmax - dmin, 1e-6), rho / H);
|
||||
}
|
||||
|
||||
void atmo_transmittance_params(vec2 uv, out float radius, out float cosviewzenith)
|
||||
{
|
||||
float H = sqrt(max(0.0, atmo_top_radius * atmo_top_radius - atmo_bottom_radius * atmo_bottom_radius));
|
||||
float rho = H * uv.y;
|
||||
radius = sqrt(rho * rho + atmo_bottom_radius * atmo_bottom_radius);
|
||||
float dmin = atmo_top_radius - radius;
|
||||
float dmax = rho + H;
|
||||
float d = dmin + uv.x * (dmax - dmin);
|
||||
cosviewzenith = (d == 0.0) ? 1.0 : (H * H - rho * rho - d * d) / (2.0 * radius * d);
|
||||
cosviewzenith = clamp(cosviewzenith, -1.0, 1.0);
|
||||
}
|
||||
|
||||
// multiple scattering table: x the cosine of the sun zenith angle, y the height
|
||||
vec2 atmo_multiscattering_uv(float radius, float cossunzenith)
|
||||
{
|
||||
vec2 uv = vec2(cossunzenith * 0.5 + 0.5, (radius - atmo_bottom_radius) / (atmo_top_radius - atmo_bottom_radius));
|
||||
return clamp(vec2(atmo_unit_to_sub_uv(uv.x, float(ATMOSPHERE_MULTISCATTERING_SIZE)), atmo_unit_to_sub_uv(uv.y, float(ATMOSPHERE_MULTISCATTERING_SIZE))), 0.0, 1.0);
|
||||
}
|
||||
|
||||
// sky-view table: x the angle between the view and the sun around the zenith (sqrt spaced), y the angle of the view from
|
||||
// the zenith, packed towards the horizon, which splits the table in the sky (lower half) and the ground (upper half)
|
||||
vec2 atmo_skyview_uv(bool intersectsground, float cosviewzenith, float coslightview, float radius)
|
||||
{
|
||||
float vhorizon = sqrt(max(radius * radius - atmo_bottom_radius * atmo_bottom_radius, 0.0));
|
||||
float cosbeta = vhorizon / radius;
|
||||
float beta = acos(clamp(cosbeta, -1.0, 1.0));
|
||||
float zenithhorizonangle = ATMO_PI - beta;
|
||||
|
||||
vec2 uv;
|
||||
if (!intersectsground)
|
||||
{
|
||||
float coord = acos(clamp(cosviewzenith, -1.0, 1.0)) / zenithhorizonangle;
|
||||
coord = 1.0 - coord;
|
||||
coord = sqrt(max(coord, 0.0));
|
||||
coord = 1.0 - coord;
|
||||
uv.y = coord * 0.5;
|
||||
}
|
||||
else
|
||||
{
|
||||
float coord = (acos(clamp(cosviewzenith, -1.0, 1.0)) - zenithhorizonangle) / max(beta, 1e-6);
|
||||
coord = sqrt(max(coord, 0.0));
|
||||
uv.y = coord * 0.5 + 0.5;
|
||||
}
|
||||
uv.x = sqrt(clamp(-coslightview * 0.5 + 0.5, 0.0, 1.0));
|
||||
return vec2(atmo_unit_to_sub_uv(uv.x, float(ATMOSPHERE_SKYVIEW_WIDTH)), atmo_unit_to_sub_uv(uv.y, float(ATMOSPHERE_SKYVIEW_HEIGHT)));
|
||||
}
|
||||
|
||||
void atmo_skyview_params(vec2 uv, float radius, out float cosviewzenith, out float coslightview)
|
||||
{
|
||||
uv = vec2(atmo_sub_uv_to_unit(uv.x, float(ATMOSPHERE_SKYVIEW_WIDTH)), atmo_sub_uv_to_unit(uv.y, float(ATMOSPHERE_SKYVIEW_HEIGHT)));
|
||||
uv = clamp(uv, 0.0, 1.0);
|
||||
|
||||
float vhorizon = sqrt(max(radius * radius - atmo_bottom_radius * atmo_bottom_radius, 0.0));
|
||||
float cosbeta = vhorizon / radius;
|
||||
float beta = acos(clamp(cosbeta, -1.0, 1.0));
|
||||
float zenithhorizonangle = ATMO_PI - beta;
|
||||
|
||||
if (uv.y < 0.5)
|
||||
{
|
||||
float coord = 2.0 * uv.y;
|
||||
coord = 1.0 - coord;
|
||||
coord *= coord;
|
||||
coord = 1.0 - coord;
|
||||
cosviewzenith = cos(zenithhorizonangle * coord);
|
||||
}
|
||||
else
|
||||
{
|
||||
float coord = uv.y * 2.0 - 1.0;
|
||||
coord *= coord;
|
||||
cosviewzenith = cos(zenithhorizonangle + beta * coord);
|
||||
}
|
||||
float coord = uv.x * uv.x;
|
||||
coslightview = -(coord * 2.0 - 1.0);
|
||||
}
|
||||
|
||||
// the same mapping for a world space view direction and sun direction (y up), as seen by the observer
|
||||
vec2 atmo_skyview_uv_world(vec3 viewdirection, vec3 sundirection)
|
||||
{
|
||||
float radius = atmo_observer_radius;
|
||||
vec3 origin = vec3(0.0, 0.0, radius);
|
||||
float cosviewzenith = viewdirection.y;
|
||||
// angle between the view and the sun around the zenith
|
||||
vec2 viewflat = viewdirection.xz;
|
||||
vec2 sunflat = sundirection.xz;
|
||||
float lengths = length(viewflat) * length(sunflat);
|
||||
float coslightview = (lengths > 1e-6) ? clamp(dot(viewflat, sunflat) / lengths, -1.0, 1.0) : 1.0;
|
||||
vec3 localview = vec3(sqrt(max(1.0 - cosviewzenith * cosviewzenith, 0.0)), 0.0, cosviewzenith);
|
||||
bool intersectsground = atmo_ray_sphere_nearest(origin, localview, atmo_bottom_radius) >= 0.0;
|
||||
return atmo_skyview_uv(intersectsground, cosviewzenith, coslightview, radius);
|
||||
}
|
||||
105
shaders/atmosphere_integrate.glsl
Normal file
105
shaders/atmosphere_integrate.glsl
Normal file
@@ -0,0 +1,105 @@
|
||||
// scattering along a ray through the atmosphere, see atmosphere_common.glsl. the includer declares the samplers
|
||||
// atmo_transmittance_lut and (when multiple scattering is used) atmo_multiscattering_lut
|
||||
|
||||
// above the ground by this much (km), so the test for the shadow of the planet does not hit the point itself
|
||||
const float ATMO_PLANET_RADIUS_OFFSET = 0.01;
|
||||
|
||||
struct atmo_scattering_s
|
||||
{
|
||||
// light scattered towards the origin
|
||||
vec3 luminance;
|
||||
// transmittance from the origin to the end of the ray
|
||||
vec3 transmittance;
|
||||
// light scattered once more, as if the light arriving at every point was 1 (multiple scattering table)
|
||||
vec3 multiscattering_as_1;
|
||||
};
|
||||
|
||||
vec3 atmo_transmittance_to_top(float radius, float cossunzenith)
|
||||
{
|
||||
return texture(atmo_transmittance_lut, atmo_transmittance_uv(radius, cossunzenith)).rgb;
|
||||
}
|
||||
|
||||
// Origin and Direction (unit) in the frame of the planet. with Ground the light the ground reflects at the end of the ray
|
||||
// is included. Physicalphase: rayleigh and mie phase functions, otherwise isotropic (multiple scattering table).
|
||||
// Multiscattering: light scattered more than once comes from the multiple scattering table
|
||||
atmo_scattering_s atmo_integrate(vec3 origin, vec3 direction, vec3 sundirection, bool ground, int samples, bool physicalphase, bool multiscattering)
|
||||
{
|
||||
atmo_scattering_s result;
|
||||
result.luminance = vec3(0.0);
|
||||
result.transmittance = vec3(1.0);
|
||||
result.multiscattering_as_1 = vec3(0.0);
|
||||
|
||||
float tbottom = atmo_ray_sphere_nearest(origin, direction, atmo_bottom_radius);
|
||||
float ttop = atmo_ray_sphere_nearest(origin, direction, atmo_top_radius);
|
||||
float tmax;
|
||||
if (tbottom < 0.0)
|
||||
{
|
||||
if (ttop < 0.0)
|
||||
return result;
|
||||
tmax = ttop;
|
||||
}
|
||||
else
|
||||
{
|
||||
tmax = (ttop > 0.0) ? min(ttop, tbottom) : tbottom;
|
||||
}
|
||||
|
||||
float cosangle = dot(direction, sundirection);
|
||||
const float uniformphase = 1.0 / (4.0 * ATMO_PI);
|
||||
float miephase = physicalphase ? atmo_mie_phase(cosangle) : uniformphase;
|
||||
float rayleighphase = physicalphase ? atmo_rayleigh_phase(cosangle) : uniformphase;
|
||||
|
||||
vec3 throughput = vec3(1.0);
|
||||
float invsamples = 1.0 / float(samples);
|
||||
for (int s = 0; s < samples; ++s)
|
||||
{
|
||||
// the steps grow with the distance: the medium near the observer matters most
|
||||
float t0 = float(s) * invsamples;
|
||||
float t1 = float(s + 1) * invsamples;
|
||||
t0 = t0 * t0 * tmax;
|
||||
t1 = t1 * t1 * tmax;
|
||||
float dt = t1 - t0;
|
||||
float t = t0 + dt * 0.3;
|
||||
|
||||
vec3 position = origin + t * direction;
|
||||
float radius = length(position);
|
||||
vec3 up = position / radius;
|
||||
atmo_medium_s medium = atmo_sample_medium(radius);
|
||||
vec3 extinction = max(medium.extinction, vec3(1e-7));
|
||||
vec3 sampletransmittance = exp(-extinction * dt);
|
||||
|
||||
float cossunzenith = dot(sundirection, up);
|
||||
vec3 suntransmittance = atmo_transmittance_to_top(radius, cossunzenith);
|
||||
float earthshadow = (atmo_ray_sphere_nearest(position + up * ATMO_PLANET_RADIUS_OFFSET, sundirection, atmo_bottom_radius) >= 0.0) ? 0.0 : 1.0;
|
||||
|
||||
vec3 multiscatteredluminance = vec3(0.0);
|
||||
#ifdef ATMO_MULTISCATTERING_LUT
|
||||
if (multiscattering)
|
||||
multiscatteredluminance = texture(atmo_multiscattering_lut, atmo_multiscattering_uv(radius, cossunzenith)).rgb;
|
||||
#endif
|
||||
|
||||
vec3 phasescattering = medium.mie_scattering * miephase + medium.rayleigh_scattering * rayleighphase;
|
||||
vec3 S = earthshadow * suntransmittance * phasescattering + multiscatteredluminance * medium.scattering;
|
||||
// analytic integral over the step of the light scattered at the sample, attenuated on the way to its start
|
||||
vec3 Sint = (S - S * sampletransmittance) / extinction;
|
||||
result.luminance += throughput * Sint;
|
||||
|
||||
vec3 MSint = (medium.scattering - medium.scattering * sampletransmittance) / extinction;
|
||||
result.multiscattering_as_1 += throughput * MSint;
|
||||
|
||||
throughput *= sampletransmittance;
|
||||
}
|
||||
|
||||
if (ground && tbottom > 0.0 && tmax == tbottom)
|
||||
{
|
||||
vec3 position = origin + tbottom * direction;
|
||||
float radius = length(position);
|
||||
vec3 up = position / radius;
|
||||
float cossunzenith = dot(sundirection, up);
|
||||
vec3 suntransmittance = atmo_transmittance_to_top(radius, cossunzenith);
|
||||
float NdotL = clamp(dot(up, sundirection), 0.0, 1.0);
|
||||
result.luminance += suntransmittance * throughput * NdotL * atmo_ground.rgb / ATMO_PI;
|
||||
}
|
||||
|
||||
result.transmittance = throughput;
|
||||
return result;
|
||||
}
|
||||
69
shaders/atmosphere_multiscattering.comp
Normal file
69
shaders/atmosphere_multiscattering.comp
Normal file
@@ -0,0 +1,69 @@
|
||||
// light scattered more than once, for the multiple scattering table (Hillaire 2020, section 5.5): for a point at a height
|
||||
// lit by the sun at an angle, the second order scattering from 64 directions around it, summed as a geometric series of
|
||||
// all the orders. one work group per texel, one invocation per direction. run after the transmittance table
|
||||
|
||||
#include <common>
|
||||
#include <atmosphere_common.glsl>
|
||||
|
||||
layout(binding = ATMOSPHERE_TRANSMITTANCE_UNIT) uniform sampler2D atmo_transmittance_lut;
|
||||
#include <atmosphere_integrate.glsl>
|
||||
|
||||
layout(local_size_x = 64) in;
|
||||
|
||||
layout(binding = ATMOSPHERE_MULTISCATTERING_UNIT, rgba16f) writeonly uniform image2D multiscattering_target;
|
||||
|
||||
const int MULTISCATTERING_SAMPLES = 20;
|
||||
const int SQRT_DIRECTIONS = 8;
|
||||
|
||||
shared vec3 shared_luminance[64];
|
||||
shared vec3 shared_multiscattering[64];
|
||||
|
||||
void main()
|
||||
{
|
||||
ivec2 texel = ivec2(gl_WorkGroupID.xy);
|
||||
uint index = gl_LocalInvocationID.x;
|
||||
|
||||
vec2 uv = (vec2(texel) + 0.5) / float(ATMOSPHERE_MULTISCATTERING_SIZE);
|
||||
uv = vec2(atmo_sub_uv_to_unit(uv.x, float(ATMOSPHERE_MULTISCATTERING_SIZE)), atmo_sub_uv_to_unit(uv.y, float(ATMOSPHERE_MULTISCATTERING_SIZE)));
|
||||
uv = clamp(uv, 0.0, 1.0);
|
||||
|
||||
float cossunzenith = uv.x * 2.0 - 1.0;
|
||||
vec3 sundirection = vec3(0.0, sqrt(max(1.0 - cossunzenith * cossunzenith, 0.0)), cossunzenith);
|
||||
float radius = atmo_bottom_radius + clamp(uv.y + 1e-4, 0.0, 1.0) * (atmo_top_radius - atmo_bottom_radius - 1e-3);
|
||||
vec3 origin = vec3(0.0, 0.0, radius);
|
||||
|
||||
// directions spread evenly over the sphere
|
||||
float a = (0.5 + float(index / uint(SQRT_DIRECTIONS))) / float(SQRT_DIRECTIONS);
|
||||
float b = (0.5 + float(index % uint(SQRT_DIRECTIONS))) / float(SQRT_DIRECTIONS);
|
||||
float theta = 2.0 * ATMO_PI * a;
|
||||
float phi = acos(clamp(1.0 - 2.0 * b, -1.0, 1.0));
|
||||
vec3 direction = vec3(cos(theta) * sin(phi), sin(theta) * sin(phi), cos(phi));
|
||||
|
||||
atmo_scattering_s result = atmo_integrate(origin, direction, sundirection, true, MULTISCATTERING_SAMPLES, false, false);
|
||||
|
||||
const float spheresolidangle = 4.0 * ATMO_PI;
|
||||
const float directions = float(SQRT_DIRECTIONS * SQRT_DIRECTIONS);
|
||||
shared_luminance[index] = result.luminance * spheresolidangle / directions;
|
||||
shared_multiscattering[index] = result.multiscattering_as_1 * spheresolidangle / directions;
|
||||
barrier();
|
||||
|
||||
for (uint stride = 32U; stride > 0U; stride >>= 1)
|
||||
{
|
||||
if (index < stride)
|
||||
{
|
||||
shared_luminance[index] += shared_luminance[index + stride];
|
||||
shared_multiscattering[index] += shared_multiscattering[index + stride];
|
||||
}
|
||||
barrier();
|
||||
}
|
||||
|
||||
if (index != 0U)
|
||||
return;
|
||||
|
||||
const float isotropicphase = 1.0 / spheresolidangle;
|
||||
vec3 inscatteredluminance = shared_luminance[0] * isotropicphase;
|
||||
vec3 multiscatteringas1 = shared_multiscattering[0] * isotropicphase;
|
||||
// every further order of scattering is the previous one times multiscatteringas1
|
||||
vec3 luminance = inscatteredluminance / max(vec3(1.0) - multiscatteringas1, vec3(1e-3));
|
||||
imageStore(multiscattering_target, texel, vec4(luminance, 1.0));
|
||||
}
|
||||
39
shaders/atmosphere_skyview.comp
Normal file
39
shaders/atmosphere_skyview.comp
Normal file
@@ -0,0 +1,39 @@
|
||||
// light of the sky seen by the observer in every direction, for the sky-view table (atmosphere_common.glsl), which the
|
||||
// sky, the ambient light and the fog colour are read from. one invocation per texel. run every frame the sky is drawn with
|
||||
// the atmosphere, after the transmittance and multiple scattering tables
|
||||
|
||||
#include <common>
|
||||
#include <atmosphere_common.glsl>
|
||||
|
||||
layout(binding = ATMOSPHERE_TRANSMITTANCE_UNIT) uniform sampler2D atmo_transmittance_lut;
|
||||
layout(binding = ATMOSPHERE_MULTISCATTERING_UNIT) uniform sampler2D atmo_multiscattering_lut;
|
||||
#define ATMO_MULTISCATTERING_LUT
|
||||
#include <atmosphere_integrate.glsl>
|
||||
|
||||
layout(local_size_x = 8, local_size_y = 8) in;
|
||||
|
||||
layout(binding = ATMOSPHERE_SKYVIEW_UNIT, rgba16f) writeonly uniform image2D skyview_target;
|
||||
|
||||
const int SKYVIEW_SAMPLES = 32;
|
||||
|
||||
void main()
|
||||
{
|
||||
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
|
||||
if (texel.x >= int(ATMOSPHERE_SKYVIEW_WIDTH) || texel.y >= int(ATMOSPHERE_SKYVIEW_HEIGHT))
|
||||
return;
|
||||
|
||||
vec2 uv = (vec2(texel) + 0.5) / vec2(ATMOSPHERE_SKYVIEW_WIDTH, ATMOSPHERE_SKYVIEW_HEIGHT);
|
||||
float radius = atmo_observer_radius;
|
||||
float cosviewzenith;
|
||||
float coslightview;
|
||||
atmo_skyview_params(uv, radius, cosviewzenith, coslightview);
|
||||
|
||||
float cossunzenith = clamp(atmo_sun_direction.y, -1.0, 1.0);
|
||||
vec3 sundirection = normalize(vec3(sqrt(max(1.0 - cossunzenith * cossunzenith, 0.0)), 0.0, cossunzenith));
|
||||
float sinviewzenith = sqrt(max(1.0 - cosviewzenith * cosviewzenith, 0.0));
|
||||
vec3 direction = vec3(sinviewzenith * coslightview, sinviewzenith * sqrt(max(1.0 - coslightview * coslightview, 0.0)), cosviewzenith);
|
||||
vec3 origin = vec3(0.0, 0.0, radius);
|
||||
|
||||
atmo_scattering_s result = atmo_integrate(origin, direction, sundirection, false, SKYVIEW_SAMPLES, true, true);
|
||||
imageStore(skyview_target, texel, vec4(result.luminance, 1.0));
|
||||
}
|
||||
40
shaders/atmosphere_transmittance.comp
Normal file
40
shaders/atmosphere_transmittance.comp
Normal file
@@ -0,0 +1,40 @@
|
||||
// transmittance from a point in the atmosphere to its top, for the transmittance table (atmosphere_common.glsl).
|
||||
// one invocation per texel. run when the parameters of the atmosphere change
|
||||
|
||||
#include <common>
|
||||
#include <atmosphere_common.glsl>
|
||||
|
||||
layout(local_size_x = 8, local_size_y = 8) in;
|
||||
|
||||
layout(binding = ATMOSPHERE_TRANSMITTANCE_UNIT, rgba16f) writeonly uniform image2D transmittance_target;
|
||||
|
||||
const int TRANSMITTANCE_SAMPLES = 40;
|
||||
|
||||
void main()
|
||||
{
|
||||
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
|
||||
if (texel.x >= int(ATMOSPHERE_TRANSMITTANCE_WIDTH) || texel.y >= int(ATMOSPHERE_TRANSMITTANCE_HEIGHT))
|
||||
return;
|
||||
|
||||
vec2 uv = (vec2(texel) + 0.5) / vec2(ATMOSPHERE_TRANSMITTANCE_WIDTH, ATMOSPHERE_TRANSMITTANCE_HEIGHT);
|
||||
float radius;
|
||||
float cosviewzenith;
|
||||
atmo_transmittance_params(uv, radius, cosviewzenith);
|
||||
|
||||
vec3 origin = vec3(0.0, 0.0, radius);
|
||||
vec3 direction = vec3(sqrt(max(1.0 - cosviewzenith * cosviewzenith, 0.0)), 0.0, cosviewzenith);
|
||||
|
||||
float tbottom = atmo_ray_sphere_nearest(origin, direction, atmo_bottom_radius);
|
||||
float ttop = atmo_ray_sphere_nearest(origin, direction, atmo_top_radius);
|
||||
float tmax = (tbottom > 0.0) ? ((ttop > 0.0) ? min(ttop, tbottom) : tbottom) : max(ttop, 0.0);
|
||||
|
||||
vec3 opticaldepth = vec3(0.0);
|
||||
float dt = tmax / float(TRANSMITTANCE_SAMPLES);
|
||||
for (int s = 0; s < TRANSMITTANCE_SAMPLES; ++s)
|
||||
{
|
||||
vec3 position = origin + (float(s) + 0.5) * dt * direction;
|
||||
opticaldepth += atmo_sample_medium(length(position)).extinction * dt;
|
||||
}
|
||||
|
||||
imageStore(transmittance_target, texel, vec4(exp(-opticaldepth), 1.0));
|
||||
}
|
||||
@@ -10,22 +10,7 @@ layout(location = 0) out vec4 out_color;
|
||||
layout(location = 1) out vec4 out_motion;
|
||||
#endif
|
||||
|
||||
// the skydome vertex colours carry Perez luminance scaled by a fixed exposure
|
||||
// (see CSkyDome::SetExposure), so the circumsolar region and the horizon band
|
||||
// routinely land well above 1.0. hard-clipping them, as this shader used to do,
|
||||
// flattened everything within ~20 degrees of the sun - and most of the lower sky -
|
||||
// into one solid white patch. compress the top end instead: below the knee the
|
||||
// response is unchanged, above it it rolls off towards SKY_SHOULDER_WHITE, so the
|
||||
// brightness gradient around the sun survives and the patch keeps a soft edge.
|
||||
const float SKY_SHOULDER_KNEE = 0.25;
|
||||
const float SKY_SHOULDER_WHITE = 0.95;
|
||||
|
||||
vec3 sky_shoulder(vec3 color)
|
||||
{
|
||||
const float range = SKY_SHOULDER_WHITE - SKY_SHOULDER_KNEE;
|
||||
vec3 excess = max(color - vec3(SKY_SHOULDER_KNEE), vec3(0.0));
|
||||
return min(color, vec3(SKY_SHOULDER_KNEE)) + range * (vec3(1.0) - exp(-excess / range));
|
||||
}
|
||||
#include <sky_shoulder.glsl>
|
||||
|
||||
void main()
|
||||
{
|
||||
|
||||
100
shaders/envmap_prefilter.comp
Normal file
100
shaders/envmap_prefilter.comp
Normal file
@@ -0,0 +1,100 @@
|
||||
// prefilters the environment map with the GGX lobe for rough reflections (split sum approximation, Karis 2013), one level
|
||||
// of the prefiltered map per dispatch. filtered importance sampling (Krivanek, Colbert 2008): each sample reads the mipmap
|
||||
// of the source whose texels are about as large as the solid angle the sample stands for, so a few dozen samples give a
|
||||
// smooth result. run when an update of the environment map is complete
|
||||
|
||||
#include <common>
|
||||
|
||||
layout(local_size_x = ENVMAP_GGX_GROUP_SIZE, local_size_y = ENVMAP_GGX_GROUP_SIZE) in;
|
||||
|
||||
layout(binding = 0, rgba16f) writeonly uniform imageCube prefilter_target;
|
||||
// the plain map with its box filtered mipmaps, on the unit gl::program binds envmap to
|
||||
uniform samplerCube envmap;
|
||||
|
||||
// perceptual roughness of the level being written
|
||||
layout(location = 0) uniform float roughness;
|
||||
// size of the level being written, and of the first level of the source
|
||||
layout(location = 1) uniform int target_size;
|
||||
layout(location = 2) uniform float source_size;
|
||||
layout(location = 3) uniform int sample_count;
|
||||
|
||||
const float PI = 3.14159265359;
|
||||
|
||||
// direction through the centre of a texel of a cube map face, in the order and orientation of GL_TEXTURE_CUBE_MAP_POSITIVE_X..
|
||||
vec3 cube_direction(ivec3 texel, int size)
|
||||
{
|
||||
vec2 uv = (vec2(texel.xy) + 0.5) / float(size) * 2.0 - 1.0;
|
||||
switch (texel.z)
|
||||
{
|
||||
case 0: return vec3(1.0, -uv.y, -uv.x);
|
||||
case 1: return vec3(-1.0, -uv.y, uv.x);
|
||||
case 2: return vec3(uv.x, 1.0, uv.y);
|
||||
case 3: return vec3(uv.x, -1.0, -uv.y);
|
||||
case 4: return vec3(uv.x, -uv.y, 1.0);
|
||||
default: return vec3(-uv.x, -uv.y, -1.0);
|
||||
}
|
||||
}
|
||||
|
||||
vec2 hammersley(uint i, uint n)
|
||||
{
|
||||
uint bits = bitfieldReverse(i);
|
||||
return vec2(float(i) / float(n), float(bits) * 2.3283064365386963e-10);
|
||||
}
|
||||
|
||||
// half vector around N for the GGX distribution of linear roughness a
|
||||
vec3 importance_sample_ggx(vec2 xi, float a, vec3 N)
|
||||
{
|
||||
float phi = 2.0 * PI * xi.x;
|
||||
float costheta = sqrt((1.0 - xi.y) / (1.0 + (a * a - 1.0) * xi.y));
|
||||
float sintheta = sqrt(max(1.0 - costheta * costheta, 0.0));
|
||||
vec3 H = vec3(sintheta * cos(phi), sintheta * sin(phi), costheta);
|
||||
vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0);
|
||||
vec3 tangentx = normalize(cross(up, N));
|
||||
vec3 tangenty = cross(N, tangentx);
|
||||
return tangentx * H.x + tangenty * H.y + N * H.z;
|
||||
}
|
||||
|
||||
float distribution_ggx(float NdotH, float a)
|
||||
{
|
||||
float a2 = a * a;
|
||||
float d = NdotH * NdotH * (a2 - 1.0) + 1.0;
|
||||
return a2 / (PI * d * d);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
ivec3 texel = ivec3(gl_GlobalInvocationID);
|
||||
if (texel.x >= target_size || texel.y >= target_size)
|
||||
return;
|
||||
|
||||
vec3 N = normalize(cube_direction(texel, target_size));
|
||||
// the view is taken along the normal, which is what the split sum approximation assumes
|
||||
vec3 V = N;
|
||||
float a = max(roughness * roughness, 1e-3);
|
||||
float texelsolidangle = 4.0 * PI / (6.0 * source_size * source_size);
|
||||
|
||||
vec3 sum = vec3(0.0);
|
||||
float weight = 0.0;
|
||||
uint count = uint(sample_count);
|
||||
for (uint i = 0U; i < count; ++i)
|
||||
{
|
||||
vec3 H = importance_sample_ggx(hammersley(i, count), a, N);
|
||||
float VdotH = dot(V, H);
|
||||
vec3 L = 2.0 * VdotH * H - V;
|
||||
float NdotL = dot(N, L);
|
||||
if (NdotL <= 0.0)
|
||||
continue;
|
||||
float NdotH = max(dot(N, H), 0.0);
|
||||
// pdf of L: D * NdotH / (4 * VdotH)
|
||||
float pdf = distribution_ggx(NdotH, a) * NdotH / max(4.0 * VdotH, 1e-4);
|
||||
float samplesolidangle = 1.0 / (float(count) * max(pdf, 1e-6));
|
||||
float level = max(0.5 * log2(samplesolidangle / texelsolidangle) + 1.0, 0.0);
|
||||
vec3 color = textureLod(envmap, L, level).rgb;
|
||||
if (any(isnan(color)) || any(isinf(color)))
|
||||
color = vec3(0.0);
|
||||
sum += max(color, vec3(0.0)) * NdotL;
|
||||
weight += NdotL;
|
||||
}
|
||||
|
||||
imageStore(prefilter_target, texel, vec4(sum / max(weight, 1e-4), 1.0));
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
#if ENVMAP_ENABLED
|
||||
uniform samplerCube envmap;
|
||||
// the same map prefiltered with the GGX lobe (envmap_prefilter.comp), used when env_ggx is 1
|
||||
uniform samplerCube envmap_ggx;
|
||||
#endif
|
||||
|
||||
vec3 envmap_color( vec3 normal )
|
||||
@@ -39,3 +41,25 @@ vec3 envmap_color_lod(vec3 fragnormal, float lod)
|
||||
return vec3(0.5); // was vec3(0.0), match the non-LOD fallback
|
||||
#endif
|
||||
}
|
||||
|
||||
// reflection of the surroundings in a surface of the given perceptual roughness (0 mirror, 1 matte)
|
||||
vec3 envmap_color_rough(vec3 fragnormal, float roughness)
|
||||
{
|
||||
// the plain map, its mipmaps (box filtered) standing for the roughness
|
||||
const float MAX_REFLECTION_LOD = 8.0;
|
||||
vec3 boxcolor = envmap_color_lod(fragnormal, roughness * MAX_REFLECTION_LOD);
|
||||
#if ENVMAP_ENABLED
|
||||
if (env_ggx == 0U || roughness <= ENVMAP_GGX_ROUGHNESS_MIN)
|
||||
return boxcolor;
|
||||
vec3 refvec = reflect(f_pos.xyz, fragnormal);
|
||||
refvec = vec3(inv_view * vec4(refvec, 0.0));
|
||||
float level = (roughness - ENVMAP_GGX_ROUGHNESS_MIN) / (1.0 - ENVMAP_GGX_ROUGHNESS_MIN) * float(ENVMAP_GGX_LEVELS - 1U);
|
||||
vec3 ggxcolor = textureLod(envmap_ggx, refvec, level).rgb;
|
||||
if (any(isnan(ggxcolor)) || any(isinf(ggxcolor))) ggxcolor = vec3(0.0);
|
||||
ggxcolor = max(ggxcolor, vec3(0.0));
|
||||
// the prefiltered map takes over over the first tenth of the roughness above its lowest level
|
||||
return mix(boxcolor, ggxcolor, clamp((roughness - ENVMAP_GGX_ROUGHNESS_MIN) * 10.0, 0.0, 1.0));
|
||||
#else
|
||||
return boxcolor;
|
||||
#endif
|
||||
}
|
||||
|
||||
79
shaders/exposure_average.comp
Normal file
79
shaders/exposure_average.comp
Normal file
@@ -0,0 +1,79 @@
|
||||
// exposure from the luminance histogram (exposure_histogram.comp): the average log2 luminance of the pixels between the
|
||||
// low and high percentile (the darkest and brightest parts of the image do not move the exposure), adapted over time to,
|
||||
// faster towards the brighter. the exposure brings the adapted luminance to the key, within the limits. one work group,
|
||||
// one invocation per bin; the histogram is cleared for the next frame
|
||||
|
||||
#include <common>
|
||||
|
||||
layout(local_size_x = EXPOSURE_HISTOGRAM_BINS) in;
|
||||
|
||||
layout(std430, binding = EXPOSURE_SSBO_BINDING) buffer exposure_ssbo
|
||||
{
|
||||
uint histogram[EXPOSURE_HISTOGRAM_BINS];
|
||||
float adapted_luminance;
|
||||
float exposure;
|
||||
float average_luminance;
|
||||
uint valid;
|
||||
};
|
||||
|
||||
// x: log2 of the lowest luminance binned, y: log2 luminance per bin
|
||||
layout(location = 0) uniform vec2 log_range;
|
||||
// x: time since the last frame (s), y: rate of adaptation to a darker image, z: to a brighter one (1/s), w: key
|
||||
layout(location = 1) uniform vec4 adaptation;
|
||||
// lowest and highest exposure (multipliers)
|
||||
layout(location = 2) uniform vec2 exposure_range;
|
||||
|
||||
const float LOW_PERCENTILE = 0.4;
|
||||
const float HIGH_PERCENTILE = 0.95;
|
||||
|
||||
shared float shared_counts[EXPOSURE_HISTOGRAM_BINS];
|
||||
|
||||
void main()
|
||||
{
|
||||
uint index = gl_LocalInvocationIndex;
|
||||
shared_counts[index] = float(histogram[index]);
|
||||
histogram[index] = 0U;
|
||||
barrier();
|
||||
|
||||
if (index != 0U)
|
||||
return;
|
||||
|
||||
float total = 0.0;
|
||||
for (uint bin = 1U; bin < EXPOSURE_HISTOGRAM_BINS; ++bin)
|
||||
total += shared_counts[bin];
|
||||
// an all black image (or none) leaves the exposure as it was
|
||||
if (total < 1.0)
|
||||
return;
|
||||
|
||||
float low = total * LOW_PERCENTILE;
|
||||
float high = total * HIGH_PERCENTILE;
|
||||
float counted = 0.0;
|
||||
float logsum = 0.0;
|
||||
float weight = 0.0;
|
||||
for (uint bin = 1U; bin < EXPOSURE_HISTOGRAM_BINS; ++bin)
|
||||
{
|
||||
float count = shared_counts[bin];
|
||||
// the part of the bin between the percentiles
|
||||
float inside = max(min(counted + count, high) - max(counted, low), 0.0);
|
||||
counted += count;
|
||||
float binluminance = log_range.x + (float(bin - 1U) + 0.5) * log_range.y;
|
||||
logsum += inside * binluminance;
|
||||
weight += inside;
|
||||
}
|
||||
float averagelog = (weight > 0.0) ? logsum / weight : log_range.x;
|
||||
average_luminance = exp2(averagelog);
|
||||
|
||||
if (valid == 0U || !(adapted_luminance > 0.0))
|
||||
{
|
||||
adapted_luminance = average_luminance;
|
||||
valid = 1U;
|
||||
}
|
||||
else
|
||||
{
|
||||
float adaptedlog = log2(adapted_luminance);
|
||||
float rate = (averagelog < adaptedlog) ? adaptation.y : adaptation.z;
|
||||
adaptedlog = mix(adaptedlog, averagelog, 1.0 - exp(-adaptation.x * rate));
|
||||
adapted_luminance = exp2(adaptedlog);
|
||||
}
|
||||
exposure = clamp(adaptation.w / adapted_luminance, exposure_range.x, exposure_range.y);
|
||||
}
|
||||
49
shaders/exposure_histogram.comp
Normal file
49
shaders/exposure_histogram.comp
Normal file
@@ -0,0 +1,49 @@
|
||||
// luminance histogram of the image for the automatic exposure: every second pixel in both directions, binned by the log2
|
||||
// of its luminance. bin 0 takes the black pixels, which the average leaves out. exposure_average.comp reads and clears it
|
||||
|
||||
#include <common>
|
||||
|
||||
layout(local_size_x = EXPOSURE_HISTOGRAM_GROUP_SIZE, local_size_y = EXPOSURE_HISTOGRAM_GROUP_SIZE) in;
|
||||
|
||||
layout(binding = 0) uniform sampler2D hdr_image;
|
||||
|
||||
layout(std430, binding = EXPOSURE_SSBO_BINDING) buffer exposure_ssbo
|
||||
{
|
||||
uint histogram[EXPOSURE_HISTOGRAM_BINS];
|
||||
float adapted_luminance;
|
||||
float exposure;
|
||||
float average_luminance;
|
||||
uint valid;
|
||||
};
|
||||
|
||||
// x: log2 of the lowest luminance binned, y: number of bins per unit of log2 luminance
|
||||
layout(location = 0) uniform vec2 log_range;
|
||||
|
||||
shared uint shared_histogram[EXPOSURE_HISTOGRAM_BINS];
|
||||
|
||||
void main()
|
||||
{
|
||||
uint index = gl_LocalInvocationIndex;
|
||||
shared_histogram[index] = 0U;
|
||||
barrier();
|
||||
|
||||
ivec2 pixel = ivec2(gl_GlobalInvocationID.xy) * 2;
|
||||
ivec2 size = textureSize(hdr_image, 0);
|
||||
if (pixel.x < size.x && pixel.y < size.y)
|
||||
{
|
||||
vec3 color = texelFetch(hdr_image, pixel, 0).rgb;
|
||||
float luminance = dot(color, vec3(0.2126, 0.7152, 0.0722));
|
||||
uint bin = 0U;
|
||||
// also false for a NaN
|
||||
if (luminance > 1e-6)
|
||||
{
|
||||
float position = clamp((log2(luminance) - log_range.x) * log_range.y, 0.0, float(EXPOSURE_HISTOGRAM_BINS - 2U));
|
||||
bin = 1U + uint(position);
|
||||
}
|
||||
atomicAdd(shared_histogram[bin], 1U);
|
||||
}
|
||||
barrier();
|
||||
|
||||
if (shared_histogram[index] != 0U)
|
||||
atomicAdd(histogram[index], shared_histogram[index]);
|
||||
}
|
||||
@@ -308,12 +308,39 @@ vec3 material_diffuse()
|
||||
return (value > 0.0 ? diffusecolor * (param[1].x / value) : vec3(0.0));
|
||||
}
|
||||
|
||||
// directional part of the sky ambient light (sky_ssbo, bands 1 and 2 of the spherical harmonics) for a world space direction
|
||||
vec3 sky_ambient_directional(vec3 n)
|
||||
{
|
||||
return sky_ambient_sh[1].rgb * (0.488603 * n.y)
|
||||
+ sky_ambient_sh[2].rgb * (0.488603 * n.z)
|
||||
+ sky_ambient_sh[3].rgb * (0.488603 * n.x)
|
||||
+ sky_ambient_sh[4].rgb * (1.092548 * n.x * n.y)
|
||||
+ sky_ambient_sh[5].rgb * (1.092548 * n.y * n.z)
|
||||
+ sky_ambient_sh[6].rgb * (0.315392 * (3.0 * n.z * n.z - 1.0))
|
||||
+ sky_ambient_sh[7].rgb * (1.092548 * n.x * n.z)
|
||||
+ sky_ambient_sh[8].rgb * (0.546274 * (n.x * n.x - n.y * n.y));
|
||||
}
|
||||
|
||||
// adds the directional part of the sky light to the ambient light the material starts from (the uniform ambient, or a share
|
||||
// of it, like the water takes). the same share of the directional part is added, so with the directional part at zero
|
||||
// (gfx.ambient.sh off) the result is what it always was
|
||||
vec3 add_sky_ambient(vec3 basecolor, vec3 fragnormal)
|
||||
{
|
||||
if (sky_ambient_factor == 0.0)
|
||||
return basecolor;
|
||||
float share = dot(basecolor, vec3(1.0)) / max(dot(ambient, vec3(1.0)), 1e-4);
|
||||
vec3 worldnormal = normalize(mat3(inv_view) * fragnormal);
|
||||
return max(basecolor + sky_ambient_directional(worldnormal) * (sky_ambient_factor * share), vec3(0.0));
|
||||
}
|
||||
|
||||
// [0] - diffuse, [1] - specular
|
||||
// do magic here
|
||||
vec3 apply_lights(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float reflectivity, float specularity, float shadowtone)
|
||||
{
|
||||
// Diffuse colour is applied once, to the albedo; the light terms below are not scaled by it again
|
||||
texturecolor *= material_diffuse();
|
||||
// the sky lights the faces turned up and towards the bright part of the sky more than the ones turned to the ground
|
||||
fragcolor = add_sky_ambient(fragcolor, fragnormal);
|
||||
// Scale ambient before it gets tinted by the texture.
|
||||
// Sun, headlights and emission are added afterwards so they are NOT
|
||||
// attenuated by AMBIENT_SCALE - this only dims the indirect term.
|
||||
@@ -326,9 +353,8 @@ vec3 apply_lights(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float refl
|
||||
vec3 F0 = mix(vec3(0.04), texturecolor, metalic);
|
||||
vec3 fresnel = F0 + (1.0 - F0) * pow(1.0 - NdotV, 5.0);
|
||||
|
||||
const float MAX_REFLECTION_LOD = 8.0;
|
||||
float env_roughness = 1.0 - clamp(glossiness / max(abs(param[1].w), 1.0), 0.0, 1.0);
|
||||
vec3 envcolor = envmap_color_lod(fragnormal, env_roughness * MAX_REFLECTION_LOD);
|
||||
vec3 envcolor = envmap_color_rough(fragnormal, env_roughness);
|
||||
|
||||
// Pre-integrated env BRDF: roughness/F0/view-dependent specular scale.
|
||||
// Replaces the old raw `fresnel` weighting so matte surfaces stop
|
||||
|
||||
@@ -49,9 +49,8 @@ vec3 apply_lights_sunless(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, fl
|
||||
vec3 F0 = mix(vec3(0.04), texturecolor, metalic);
|
||||
vec3 fresnel = F0 + (1.0 - F0) * pow(1.0 - NdotV, 5.0);
|
||||
|
||||
const float MAX_REFLECTION_LOD = 8.0;
|
||||
float env_roughness = 1.0 - clamp(glossiness / max(abs(param[1].w), 1.0), 0.0, 1.0);
|
||||
vec3 envcolor = envmap_color_lod(fragnormal, env_roughness * MAX_REFLECTION_LOD);
|
||||
vec3 envcolor = envmap_color_rough(fragnormal, env_roughness);
|
||||
|
||||
// Pre-integrated env BRDF (matches apply_lights): roughness-attenuated
|
||||
// reflection so matte cab surfaces no longer mirror the environment.
|
||||
|
||||
77
shaders/postfx_bloom_down.frag
Normal file
77
shaders/postfx_bloom_down.frag
Normal file
@@ -0,0 +1,77 @@
|
||||
// one step down the bloom chain: a 13 tap filter (Jimenez, "Next generation post processing in Call of Duty: Advanced
|
||||
// Warfare", 2014) from a level twice as large. the first step, from the image itself, weighs the groups of taps by their
|
||||
// brightness (Karis average) so single bright pixels do not flicker, and applies the threshold.
|
||||
// param[0].x: 1 for the first step, param[0].y: threshold (0: none), param[0].z: soft knee
|
||||
|
||||
in vec2 f_coords;
|
||||
layout(location = 0) out vec4 out_color;
|
||||
|
||||
#texture (source_tex, 0, RGB)
|
||||
uniform sampler2D source_tex;
|
||||
|
||||
#include <common>
|
||||
|
||||
vec3 tap(vec2 uv)
|
||||
{
|
||||
vec3 color = texture(source_tex, uv).rgb;
|
||||
if (any(isnan(color)) || any(isinf(color)))
|
||||
return vec3(0.0);
|
||||
return clamp(color, vec3(0.0), vec3(65000.0));
|
||||
}
|
||||
|
||||
float karis_weight(vec3 color)
|
||||
{
|
||||
return 1.0 / (1.0 + dot(color, vec3(0.2126, 0.7152, 0.0722)));
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 texel = 1.0 / vec2(textureSize(source_tex, 0));
|
||||
vec2 uv = f_coords;
|
||||
|
||||
vec3 a = tap(uv + texel * vec2(-2.0, 2.0));
|
||||
vec3 b = tap(uv + texel * vec2(0.0, 2.0));
|
||||
vec3 c = tap(uv + texel * vec2(2.0, 2.0));
|
||||
vec3 d = tap(uv + texel * vec2(-2.0, 0.0));
|
||||
vec3 e = tap(uv);
|
||||
vec3 f = tap(uv + texel * vec2(2.0, 0.0));
|
||||
vec3 g = tap(uv + texel * vec2(-2.0, -2.0));
|
||||
vec3 h = tap(uv + texel * vec2(0.0, -2.0));
|
||||
vec3 i = tap(uv + texel * vec2(2.0, -2.0));
|
||||
vec3 j = tap(uv + texel * vec2(-1.0, 1.0));
|
||||
vec3 k = tap(uv + texel * vec2(1.0, 1.0));
|
||||
vec3 l = tap(uv + texel * vec2(-1.0, -1.0));
|
||||
vec3 m = tap(uv + texel * vec2(1.0, -1.0));
|
||||
|
||||
vec3 color;
|
||||
if (param[0].x > 0.5)
|
||||
{
|
||||
vec3 g0 = (j + k + l + m) * 0.25;
|
||||
vec3 g1 = (a + b + d + e) * 0.25;
|
||||
vec3 g2 = (b + c + e + f) * 0.25;
|
||||
vec3 g3 = (d + e + g + h) * 0.25;
|
||||
vec3 g4 = (e + f + h + i) * 0.25;
|
||||
float w0 = 0.5 * karis_weight(g0);
|
||||
float w1 = 0.125 * karis_weight(g1);
|
||||
float w2 = 0.125 * karis_weight(g2);
|
||||
float w3 = 0.125 * karis_weight(g3);
|
||||
float w4 = 0.125 * karis_weight(g4);
|
||||
color = (g0 * w0 + g1 * w1 + g2 * w2 + g3 * w3 + g4 * w4) / (w0 + w1 + w2 + w3 + w4);
|
||||
|
||||
float threshold = param[0].y;
|
||||
if (threshold > 0.0)
|
||||
{
|
||||
// quadratic soft knee below the threshold
|
||||
float knee = max(threshold * param[0].z, 1e-4);
|
||||
float brightness = max(color.r, max(color.g, color.b));
|
||||
float soft = clamp(brightness - threshold + knee, 0.0, 2.0 * knee);
|
||||
soft = soft * soft / (4.0 * knee);
|
||||
color *= max(soft, brightness - threshold) / max(brightness, 1e-4);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
color = e * 0.125 + (a + c + g + i) * 0.03125 + (b + d + f + h) * 0.0625 + (j + k + l + m) * 0.125;
|
||||
}
|
||||
out_color = vec4(color, 1.0);
|
||||
}
|
||||
29
shaders/postfx_bloom_up.frag
Normal file
29
shaders/postfx_bloom_up.frag
Normal file
@@ -0,0 +1,29 @@
|
||||
// one step up the bloom chain: the level below (half as large, with all the levels under it already added) blurred up with
|
||||
// a 3x3 tent filter and added to this level of the way down
|
||||
|
||||
in vec2 f_coords;
|
||||
layout(location = 0) out vec4 out_color;
|
||||
|
||||
#texture (lower_tex, 0, RGB)
|
||||
uniform sampler2D lower_tex;
|
||||
|
||||
#texture (current_tex, 1, RGB)
|
||||
uniform sampler2D current_tex;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 texel = 1.0 / vec2(textureSize(lower_tex, 0));
|
||||
vec2 uv = f_coords;
|
||||
|
||||
vec3 sum = texture(lower_tex, uv).rgb * 4.0;
|
||||
sum += (texture(lower_tex, uv + texel * vec2(0.0, 1.0)).rgb
|
||||
+ texture(lower_tex, uv + texel * vec2(-1.0, 0.0)).rgb
|
||||
+ texture(lower_tex, uv + texel * vec2(1.0, 0.0)).rgb
|
||||
+ texture(lower_tex, uv + texel * vec2(0.0, -1.0)).rgb) * 2.0;
|
||||
sum += texture(lower_tex, uv + texel * vec2(-1.0, 1.0)).rgb
|
||||
+ texture(lower_tex, uv + texel * vec2(1.0, 1.0)).rgb
|
||||
+ texture(lower_tex, uv + texel * vec2(-1.0, -1.0)).rgb
|
||||
+ texture(lower_tex, uv + texel * vec2(1.0, -1.0)).rgb;
|
||||
|
||||
out_color = vec4(texture(current_tex, uv).rgb + sum / 16.0, 1.0);
|
||||
}
|
||||
@@ -5,11 +5,35 @@ layout(location = 0) out vec4 out_color;
|
||||
#texture (tex1, 0, RGB)
|
||||
uniform sampler2D tex1;
|
||||
|
||||
// glow of the bright parts of the image (postfx_bloom_*.frag)
|
||||
#texture (bloom_tex, 1, RGB)
|
||||
uniform sampler2D bloom_tex;
|
||||
|
||||
#include <common>
|
||||
|
||||
// exposure worked out by exposure_average.comp
|
||||
layout(std430, binding = EXPOSURE_SSBO_BINDING) readonly buffer exposure_ssbo
|
||||
{
|
||||
uint histogram[EXPOSURE_HISTOGRAM_BINS];
|
||||
float adapted_luminance;
|
||||
float exposure;
|
||||
float average_luminance;
|
||||
uint valid;
|
||||
};
|
||||
|
||||
#include <tonemapping.glsl>
|
||||
|
||||
// param[0].x: share of the bloom in the image (0: no bloom, the texture is not read), param[0].y: 1 if the automatic
|
||||
// exposure is applied, param[0].z: fixed exposure multiplier, param[0].w: scale of the bloom texture (it holds the sum of
|
||||
// the levels of the chain)
|
||||
void main()
|
||||
{
|
||||
vec2 texcoord = f_coords;
|
||||
vec3 hdr_color = texture(tex1, texcoord).xyz;
|
||||
out_color = tonemap(vec4(hdr_color, 1.0));
|
||||
}
|
||||
if (param[0].x > 0.0)
|
||||
hdr_color = mix(hdr_color, texture(bloom_tex, texcoord).rgb * param[0].w, param[0].x);
|
||||
float exposurescale = param[0].z;
|
||||
if (param[0].y > 0.5 && exposure > 0.0)
|
||||
exposurescale *= exposure;
|
||||
out_color = tonemap(vec4(hdr_color * exposurescale, 1.0));
|
||||
}
|
||||
|
||||
54
shaders/sky_atmosphere.frag
Normal file
54
shaders/sky_atmosphere.frag
Normal file
@@ -0,0 +1,54 @@
|
||||
// the sky dome drawn with the light of the physical atmosphere (atmosphere_common.glsl): the sky-view table looked up in
|
||||
// the direction of each fragment. the colour of the old dome (vertex colours, see color.frag) is kept for the night,
|
||||
// which the atmosphere does not cover (no moon, no stars), and the two are blended by the height of the sun.
|
||||
// param[0].x: share of the atmosphere, param[0].y: brightness of the atmosphere, param[0].z: how much the overcast flattens
|
||||
// the sky, param[0].w: luminance the flattened sky tends to. param[1].rgb: white balance of the atmosphere
|
||||
|
||||
in vec3 f_color;
|
||||
in vec4 f_pos;
|
||||
|
||||
#include <common>
|
||||
#include <atmosphere_common.glsl>
|
||||
#include <apply_fog.glsl>
|
||||
#include <tonemapping.glsl>
|
||||
#include <sky_shoulder.glsl>
|
||||
|
||||
layout(binding = SKY_VIEW_TEX) uniform sampler2D sky_view_lut;
|
||||
|
||||
layout(location = 0) out vec4 out_color;
|
||||
#if MOTIONBLUR_ENABLED
|
||||
layout(location = 1) out vec4 out_motion;
|
||||
#endif
|
||||
|
||||
// the brightest the sky gets, a guard against the circumsolar peak of the mie scattering blowing up the bloom
|
||||
const float SKY_RADIANCE_MAX = 64.0;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec3 domecolor = sky_shoulder(pow(max(f_color.rgb, vec3(0.0)), vec3(2.2)));
|
||||
|
||||
// the dome is drawn around the camera with the rotation of the view only, so the view space position is the direction
|
||||
vec3 direction = normalize(mat3(inv_view) * f_pos.xyz);
|
||||
// the bottom of the dome reaches a little under the horizon, where the table holds the dark air in front of the ground;
|
||||
// it gets the light of the horizon instead, as the old dome did
|
||||
direction = normalize(vec3(direction.x, max(direction.y, 0.002), direction.z));
|
||||
vec3 sundirection = normalize(atmo_sun_direction.xyz);
|
||||
vec3 atmosphere = textureLod(sky_view_lut, atmo_skyview_uv_world(direction, sundirection), 0.0).rgb;
|
||||
atmosphere *= param[1].rgb * param[0].y;
|
||||
// an overcast sky is greyer and more even than the clear one the atmosphere describes
|
||||
vec3 overcast = vec3(param[0].w) * (0.75 + 0.5 * clamp(direction.y, 0.0, 1.0));
|
||||
atmosphere = mix(atmosphere, overcast, param[0].z);
|
||||
atmosphere = min(atmosphere, vec3(SKY_RADIANCE_MAX));
|
||||
|
||||
vec3 col = mix(domecolor, atmosphere, param[0].x);
|
||||
if (any(isnan(col)) || any(isinf(col)))
|
||||
col = domecolor;
|
||||
#if POSTFX_ENABLED
|
||||
out_color = vec4(apply_fog(col), 1.0f);
|
||||
#else
|
||||
out_color = tonemap(vec4(apply_fog(col), 1.0f));
|
||||
#endif
|
||||
#if MOTIONBLUR_ENABLED
|
||||
out_motion = vec4(0.0f);
|
||||
#endif
|
||||
}
|
||||
16
shaders/sky_shoulder.glsl
Normal file
16
shaders/sky_shoulder.glsl
Normal file
@@ -0,0 +1,16 @@
|
||||
// the skydome vertex colours carry Perez luminance scaled by a fixed exposure
|
||||
// (see CSkyDome::SetExposure), so the circumsolar region and the horizon band
|
||||
// routinely land well above 1.0. hard-clipping them, as this shader used to do,
|
||||
// flattened everything within ~20 degrees of the sun - and most of the lower sky -
|
||||
// into one solid white patch. compress the top end instead: below the knee the
|
||||
// response is unchanged, above it it rolls off towards SKY_SHOULDER_WHITE, so the
|
||||
// brightness gradient around the sun survives and the patch keeps a soft edge.
|
||||
const float SKY_SHOULDER_KNEE = 0.25;
|
||||
const float SKY_SHOULDER_WHITE = 0.95;
|
||||
|
||||
vec3 sky_shoulder(vec3 color)
|
||||
{
|
||||
const float range = SKY_SHOULDER_WHITE - SKY_SHOULDER_KNEE;
|
||||
vec3 excess = max(color - vec3(SKY_SHOULDER_KNEE), vec3(0.0));
|
||||
return min(color, vec3(SKY_SHOULDER_KNEE)) + range * (vec3(1.0) - exp(-excess / range));
|
||||
}
|
||||
@@ -1534,6 +1534,10 @@ global_settings::ConfigParse_gfx( cParser &Parser, std::string_view const Token
|
||||
Parser >> gfx_shadow_rank_cutoff;
|
||||
gfx_shadow_rank_cutoff = std::clamp(gfx_shadow_rank_cutoff, 1, 3);
|
||||
}
|
||||
else if (ConfigParse_gfx_sky(Parser, Token))
|
||||
{
|
||||
// parsed
|
||||
}
|
||||
else
|
||||
{
|
||||
tokenparsed = false;
|
||||
@@ -1542,6 +1546,43 @@ global_settings::ConfigParse_gfx( cParser &Parser, std::string_view const Token
|
||||
return tokenparsed;
|
||||
}
|
||||
|
||||
bool
|
||||
global_settings::ConfigParse_gfx_sky( cParser &Parser, std::string_view const Token ) {
|
||||
|
||||
// switches take yes/no (or true/false, 1/0), scales take a number. a missing entry keeps the default
|
||||
auto const readswitch = [&]( bool &Value ) {
|
||||
Parser.getTokens( 1 );
|
||||
Parser >> Value; };
|
||||
auto const readvalue = [&]( float &Value, float const Min, float const Max ) {
|
||||
Parser.getTokens( 1 );
|
||||
Parser >> Value;
|
||||
Value = std::clamp( Value, Min, Max ); };
|
||||
|
||||
if( Token == "gfx.ambient.sh" ) { readswitch( gfx_ambient_sh ); }
|
||||
else if( Token == "gfx.ambient.sh.strength" ) { readvalue( gfx_ambient_sh_strength, 0.f, 2.f ); }
|
||||
else if( Token == "gfx.ambient.groundalbedo" ) { readvalue( gfx_ambient_groundalbedo, 0.f, 1.f ); }
|
||||
else if( Token == "gfx.envmap.ggx" ) { readswitch( gfx_envmap_ggx ); }
|
||||
else if( Token == "gfx.atmosphere" ) { readswitch( gfx_atmosphere ); }
|
||||
else if( Token == "gfx.atmosphere.sky" ) { readvalue( gfx_atmosphere_sky, 0.f, 16.f ); }
|
||||
else if( Token == "gfx.atmosphere.ambient" ) { readvalue( gfx_atmosphere_ambient, 0.f, 16.f ); }
|
||||
else if( Token == "gfx.atmosphere.sun" ) { readvalue( gfx_atmosphere_sun, 0.f, 16.f ); }
|
||||
else if( Token == "gfx.atmosphere.haze" ) { readvalue( gfx_atmosphere_haze, 0.f, 32.f ); }
|
||||
else if( Token == "gfx.atmosphere.altitude" ) { readvalue( gfx_atmosphere_altitude, 0.f, 9000.f ); }
|
||||
else if( Token == "gfx.postfx.autoexposure" ) { readswitch( gfx_postfx_autoexposure ); }
|
||||
else if( Token == "gfx.postfx.autoexposure.key" ) { readvalue( gfx_postfx_autoexposure_key, 0.001f, 100.f ); }
|
||||
else if( Token == "gfx.postfx.autoexposure.min" ) { readvalue( gfx_postfx_autoexposure_min, -16.f, 0.f ); }
|
||||
else if( Token == "gfx.postfx.autoexposure.max" ) { readvalue( gfx_postfx_autoexposure_max, 0.f, 16.f ); }
|
||||
else if( Token == "gfx.postfx.autoexposure.speed.dark" ) { readvalue( gfx_postfx_autoexposure_speed_dark, 0.01f, 100.f ); }
|
||||
else if( Token == "gfx.postfx.autoexposure.speed.bright" ) { readvalue( gfx_postfx_autoexposure_speed_bright, 0.01f, 100.f ); }
|
||||
else if( Token == "gfx.postfx.autoexposure.compensation" ) { readvalue( gfx_postfx_autoexposure_compensation, -16.f, 16.f ); }
|
||||
else if( Token == "gfx.postfx.bloom" ) { readswitch( gfx_postfx_bloom ); }
|
||||
else if( Token == "gfx.postfx.bloom.intensity" ) { readvalue( gfx_postfx_bloom_intensity, 0.f, 1.f ); }
|
||||
else if( Token == "gfx.postfx.bloom.threshold" ) { readvalue( gfx_postfx_bloom_threshold, 0.f, 1000.f ); }
|
||||
else { return false; }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
global_settings::export_as_text( std::ostream &Output ) const {
|
||||
|
||||
@@ -1778,6 +1819,26 @@ global_settings::export_as_text( std::ostream &Output ) const {
|
||||
export_as_text( Output, "gfx.shadergamma", gfx_shadergamma );
|
||||
export_as_text( Output, "gfx.shadow.angle.min", gfx_shadow_angle_min );
|
||||
export_as_text( Output, "gfx.shadow.rank.cutoff", gfx_shadow_rank_cutoff );
|
||||
export_as_text( Output, "gfx.ambient.sh", gfx_ambient_sh );
|
||||
export_as_text( Output, "gfx.ambient.sh.strength", gfx_ambient_sh_strength );
|
||||
export_as_text( Output, "gfx.ambient.groundalbedo", gfx_ambient_groundalbedo );
|
||||
export_as_text( Output, "gfx.envmap.ggx", gfx_envmap_ggx );
|
||||
export_as_text( Output, "gfx.atmosphere", gfx_atmosphere );
|
||||
export_as_text( Output, "gfx.atmosphere.sky", gfx_atmosphere_sky );
|
||||
export_as_text( Output, "gfx.atmosphere.ambient", gfx_atmosphere_ambient );
|
||||
export_as_text( Output, "gfx.atmosphere.sun", gfx_atmosphere_sun );
|
||||
export_as_text( Output, "gfx.atmosphere.haze", gfx_atmosphere_haze );
|
||||
export_as_text( Output, "gfx.atmosphere.altitude", gfx_atmosphere_altitude );
|
||||
export_as_text( Output, "gfx.postfx.autoexposure", gfx_postfx_autoexposure );
|
||||
export_as_text( Output, "gfx.postfx.autoexposure.key", gfx_postfx_autoexposure_key );
|
||||
export_as_text( Output, "gfx.postfx.autoexposure.min", gfx_postfx_autoexposure_min );
|
||||
export_as_text( Output, "gfx.postfx.autoexposure.max", gfx_postfx_autoexposure_max );
|
||||
export_as_text( Output, "gfx.postfx.autoexposure.speed.dark", gfx_postfx_autoexposure_speed_dark );
|
||||
export_as_text( Output, "gfx.postfx.autoexposure.speed.bright", gfx_postfx_autoexposure_speed_bright );
|
||||
export_as_text( Output, "gfx.postfx.autoexposure.compensation", gfx_postfx_autoexposure_compensation );
|
||||
export_as_text( Output, "gfx.postfx.bloom", gfx_postfx_bloom );
|
||||
export_as_text( Output, "gfx.postfx.bloom.intensity", gfx_postfx_bloom_intensity );
|
||||
export_as_text( Output, "gfx.postfx.bloom.threshold", gfx_postfx_bloom_threshold );
|
||||
export_as_text( Output, "python.enabled", python_enabled );
|
||||
export_as_text( Output, "python.threadedupload", python_threadedupload );
|
||||
export_as_text( Output, "python.uploadmain", python_uploadmain );
|
||||
|
||||
@@ -302,6 +302,29 @@ struct global_settings {
|
||||
float gfx_shadow_angle_min { -0.2f };
|
||||
int gfx_shadow_rank_cutoff { 3 };
|
||||
|
||||
// sky light and exposure of the opengl 4.6 renderer. the switches can also be changed at run time in the debug window
|
||||
// "Sky and exposure"; the scales are multipliers of the calibrated defaults (1 = as calibrated)
|
||||
bool gfx_ambient_sh { true }; // the ambient light depends on where a surface faces: the sky above, the ground below
|
||||
float gfx_ambient_sh_strength { 1.f }; // 0 = uniform ambient light as before, 1 = full directional difference
|
||||
float gfx_ambient_groundalbedo { 0.1f }; // how much light the ground below the horizon gives back
|
||||
bool gfx_envmap_ggx { true }; // rough reflections from the environment map prefiltered with the GGX lobe
|
||||
bool gfx_atmosphere { true }; // sky, sun, ambient light and fog colour from the physical atmosphere (Hillaire 2020)
|
||||
float gfx_atmosphere_sky { 1.f }; // brightness of the drawn sky and of the fog
|
||||
float gfx_atmosphere_ambient { 1.f }; // brightness of the ambient light
|
||||
float gfx_atmosphere_sun { 1.f }; // brightness of the sunlight
|
||||
float gfx_atmosphere_haze { 1.f }; // amount of aerosols (mie scattering) on a clear day; grows with the overcast
|
||||
float gfx_atmosphere_altitude { 200.f }; // height of the scenery above the sea level, in metres
|
||||
bool gfx_postfx_autoexposure { true }; // the exposure follows the brightness of the image, within limits
|
||||
float gfx_postfx_autoexposure_key { 0.3f }; // average luminance of the image the exposure is set for
|
||||
float gfx_postfx_autoexposure_min { -1.f }; // lowest exposure correction, EV (darkening of bright images)
|
||||
float gfx_postfx_autoexposure_max { 0.5f }; // highest exposure correction, EV (brightening of dark images, tunnels)
|
||||
float gfx_postfx_autoexposure_speed_dark { 1.f }; // rate of adaptation to a darker image, 1/s
|
||||
float gfx_postfx_autoexposure_speed_bright { 3.f }; // rate of adaptation to a brighter image, 1/s
|
||||
float gfx_postfx_autoexposure_compensation { 0.f }; // fixed exposure correction, EV, applied with and without the adaptation
|
||||
bool gfx_postfx_bloom { true }; // glow around bright parts of the image
|
||||
float gfx_postfx_bloom_intensity { 0.04f }; // share of the blurred image in the result
|
||||
float gfx_postfx_bloom_threshold { 0.f }; // luminance below which nothing glows, 0 = everything contributes
|
||||
|
||||
float ui_fontsize = 13.0f;
|
||||
float ui_scale = 1.0f;
|
||||
bool ui_viewports = true; // panels of the ui can be dragged out of the simulator window, into windows of their own
|
||||
@@ -375,6 +398,8 @@ struct global_settings {
|
||||
bool ConfigParseHardware(cParser& Parser, const std::string& token);
|
||||
bool ConfigParseDebug(cParser& Parser, const std::string& token);
|
||||
bool ConfigParse_gfx( cParser &parser, std::string_view const Token );
|
||||
// sky light and exposure switches of the opengl 4.6 renderer (gfx.ambient.*, gfx.envmap.ggx, gfx.atmosphere*, gfx.postfx.autoexposure*, gfx.postfx.bloom*)
|
||||
bool ConfigParse_gfx_sky( cParser &parser, std::string_view const Token );
|
||||
// sends basic content of the class in legacy (text) format to provided stream
|
||||
void
|
||||
export_as_text( std::ostream &Output ) const;
|
||||
|
||||
Reference in New Issue
Block a user