17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-28 22:47:28 +01:00

Shade the sky and the sun by the scenery around the camera instead of the track environment flags

This commit is contained in:
2026-10-27 17:11:06 +01:00
parent 2961ce1445
commit c44b243698
9 changed files with 209 additions and 142 deletions

View File

@@ -282,8 +282,9 @@ the height of the highest surface (the top) and of the lowest one facing down (t
bridge seen from below, a roof). Taken are the shapes (the terrain, the heightmap terrain, roads, platforms) and the opaque parts
of the models at their nearest level of detail; not the vehicles, the tracks, the catenary, nor what is see-through (trees).
Things narrower than about three texels (poles, masts, fences) are left out. From the heights around, each texel gets:
- the share of the sky a surface facing up sees above the top, and under the ceiling (light comes in under the vault from the
portal, along the tunnel);
- the share of the sky seen above the top, and under the ceiling: near it and a metre above the ground around (the ground under
a high viaduct sees much of the sky on its sides, the vault of a tunnel lets light in only along the tunnel from the portal); a
point under the ceiling gets a blend of the two by its height;
- the heights between which the sun (the moon at night) comes in: above the top what is higher than the shadow of the hills
and buildings towards it, under the ceiling what the rays reach under the vault from the portal (a low sun shines a little way
into a tunnel).
@@ -304,7 +305,7 @@ terrain, models shown or hidden by events); the sun is followed every quarter of
Cost: on the main thread, the triangles of a tile gathered when it comes in (one tile a frame, more at the start), a fraction
of a millisecond; in the background, a few to some tens of milliseconds per tile, which doesn't hold the frame up. On the GPU,
two textures (`medium`: 3 MB, `high`: 12 MB) and five texture reads per pixel in the shading. With `no` nothing of it is made
two textures (`medium`: 6 MB, `high`: 24 MB) and eight texture gathers per pixel in the shading. With `no` nothing of it is made
and the scenery is lit as in the open everywhere.
| Entry | Default | Meaning |

View File

@@ -92,8 +92,9 @@ namespace gl
const size_t CAB_PROBE_TEX = MAX_TEXTURES + HELPER_TEXTURES + 15;
const size_t CAB_PROBE_ALBEDO_TEX = MAX_TEXTURES + HELPER_TEXTURES + 16;
// the occlusion of the sky and of the sun by the scenery around the camera (gfx::sky_occlusion, gl::scene_ubs::sky_occlusion_*):
// the height of the top of the scenery (R32F) and the shares of the sky and the limits of the key light (RGBA16F), toroidal maps
// read texel by texel with textureGather. bound directly, like the ones above
// the heights of the scenery (RGBA32F: the top, the ceiling, the limit of the key light under it, the height of the deep share of
// the sky under it) and the shares of the sky and the limit of the key light above the top (RGBA16F), toroidal maps read texel by
// texel with textureGather. bound directly, like the ones above
const size_t SKY_OCCLUSION_HEIGHT_TEX = MAX_TEXTURES + HELPER_TEXTURES + 17;
const size_t SKY_OCCLUSION_TEX = MAX_TEXTURES + HELPER_TEXTURES + 18;

View File

@@ -39,14 +39,20 @@ bool has_ceiling(float const Ceiling)
{
return Ceiling < 0.5f * NONE_CEILING;
}
// the limits of the heights between which the key light is let through, relative to the top: lit always, never
// the height above the top over which the key light comes, relative to the top: lit always, never
float const LIT_ABOVE_ALWAYS{-1000.f};
float const LIT_BELOW_ALWAYS{1000.f};
float const LIT_NEVER_BELOW{-1000.f};
float const LIT_ABOVE_NEVER{1000.f};
// the height under the ceiling below which the key light comes in, world space: lit always, never
float const LIT_BELOW_ALWAYS{1.0e4f};
float const LIT_NEVER_BELOW{-1.0e4f};
// how high above the top the share of the sky above it is worked out (a surface on the ground isn't held back by the slope it's on),
// and how far below the ceiling the share under it (the middle of the height under a vault of a tunnel or a deck of a bridge)
// and how far below the ceiling the share under it is worked out near the ceiling; the other share under it is worked out a metre
// above the ground around (the ground under a viaduct sees the sky on the sides much more than a point right under the deck), at
// most this far below the ceiling
float const ABOVE_TOP{1.f};
float const BELOW_CEILING{2.5f};
float const DEEP_BELOW_CEILING_MAX{30.f};
float const DEEP_BELOW_CEILING_UNKNOWN{6.f};
// a fragment this far below the top of its texel is under it, fully this much further. as in light_common.glsl, sky_occlusion()
float const COVER_START{0.75f};
float const COVER_SPAN{1.f};
@@ -81,31 +87,47 @@ std::uint64_t hash_mix_double(std::uint64_t Hash, double const Value)
return hash_mix(Hash, bits);
}
// the share of the sky of an elevation band, weighed by the cosine on a surface facing up, from the tangents of its ends
// the share of the sky of an elevation band, from the tangents of its ends: the share of the solid angle of the hemisphere, not
// weighed by the cosine (the ambient light lights the surfaces facing every way, the low sky lights the walls and the sides of what
// stands under a roof)
float sky_share(float const Tangent)
{
if (Tangent <= 0.f)
return 0.f;
if (Tangent >= 1.0e6f)
return 1.f;
auto const squared{Tangent * Tangent};
return squared / (1.f + squared);
return Tangent / std::sqrt(1.f + Tangent * Tangent);
}
// the values of a texel nothing is known of or which holds nothing: the heights (no top, no ceiling, the key light under the ceiling
// always, the deep share under it at no height) and the occlusion (the whole sky, the key light always)
glm::vec4 const NEUTRAL_HEIGHTS{NONE_TOP, NONE_CEILING, LIT_BELOW_ALWAYS, NONE_TOP};
glm::vec4 const NEUTRAL_OCCLUSION{1.f, 1.f, 1.f, LIT_ABOVE_ALWAYS};
float smoothstep(float const Edge0, float const Edge1, float const Value)
{
auto const t{std::clamp((Value - Edge0) / (Edge1 - Edge0), 0.f, 1.f)};
return t * t * (3.f - 2.f * t);
}
// the occlusion of a point at Height, over a texel of the given top and occlusion values: the share of the sky and of the key
// light. the same as light_common.glsl, sky_occlusion_texel()
glm::vec2 texel_occlusion(float const Top, glm::vec4 const &Occlusion, float const Height)
// the occlusion of a point at Height, over a texel of the given heights (the top, the ceiling, the limit of the key light under the
// ceiling, the height of the deep share under it) and occlusion values (the share above the top, under the ceiling near it and deep,
// the limit of the key light above the top): the share of the sky and of the key light. the same as sky_occlusion.glsl
glm::vec2 texel_occlusion(glm::vec4 const &Heights, glm::vec4 const &Occlusion, float const Height)
{
auto const cover{std::clamp((Top - Height - COVER_START) / COVER_SPAN, 0.f, 1.f)};
auto const sky{Occlusion.x + (Occlusion.y - Occlusion.x) * cover};
auto const suntop{smoothstep(-SUN_SOFTNESS, SUN_SOFTNESS, Height - (Top + Occlusion.z))};
auto const sununder{smoothstep(-SUN_SOFTNESS, SUN_SOFTNESS, (Top + Occlusion.w) - Height)};
auto const top{Heights.x};
auto const ceiling{Heights.y};
auto const cover{std::clamp((top - Height - COVER_START) / COVER_SPAN, 0.f, 1.f)};
auto under{0.f};
if (has_ceiling(ceiling))
{
auto const nearheight{ceiling - BELOW_CEILING};
auto const t{std::clamp((Height - Heights.w) / std::max(nearheight - Heights.w, 1e-3f), 0.f, 1.f)};
under = Occlusion.z + (Occlusion.y - Occlusion.z) * t;
}
auto const sky{Occlusion.x + (under - Occlusion.x) * cover};
auto const suntop{smoothstep(-SUN_SOFTNESS, SUN_SOFTNESS, Height - (top + Occlusion.w))};
auto const sununder{smoothstep(-SUN_SOFTNESS, SUN_SOFTNESS, Heights.z - Height)};
return {sky, suntop + (sununder - suntop) * cover};
}
@@ -217,21 +239,19 @@ void sky_occlusion::start(int const Level)
auto const count{static_cast<std::size_t>(m_layout.tiles * m_layout.tiles)};
m_slots.assign(count, slot{});
auto const texels{static_cast<std::size_t>(side) * side};
m_heights.assign(texels, NONE_TOP);
m_occlusion.assign(texels, glm::vec4{1.f, 1.f, LIT_ABOVE_ALWAYS, LIT_BELOW_ALWAYS});
m_heights.assign(texels, NEUTRAL_HEIGHTS);
m_occlusion.assign(texels, NEUTRAL_OCCLUSION);
auto const tiletexels{static_cast<std::size_t>(m_layout.tile_texels) * m_layout.tile_texels};
m_neutralheights.assign(tiletexels, NONE_TOP);
m_neutralheights.assign(tiletexels, NEUTRAL_HEIGHTS);
m_neutralocclusion.resize(tiletexels * 4);
for (std::size_t i = 0; i < tiletexels; ++i)
{
m_neutralocclusion[i * 4 + 0] = glm::packHalf1x16(1.f);
m_neutralocclusion[i * 4 + 1] = glm::packHalf1x16(1.f);
m_neutralocclusion[i * 4 + 2] = glm::packHalf1x16(LIT_ABOVE_ALWAYS);
m_neutralocclusion[i * 4 + 3] = glm::packHalf1x16(LIT_BELOW_ALWAYS);
for (int c = 0; c < 4; ++c)
m_neutralocclusion[i * 4 + c] = glm::packHalf1x16(NEUTRAL_OCCLUSION[c]);
}
for (int index = 0; index < static_cast<int>(count); ++index)
{
gpu_upload((index % m_layout.tiles) * m_layout.tile_texels, (index / m_layout.tiles) * m_layout.tile_texels, m_layout.tile_texels, m_neutralheights.data(), m_neutralocclusion.data());
gpu_upload((index % m_layout.tiles) * m_layout.tile_texels, (index / m_layout.tiles) * m_layout.tile_texels, m_layout.tile_texels, glm::value_ptr(m_neutralheights.front()), m_neutralocclusion.data());
}
m_rawtop.assign(texels, NONE_TOP);
m_rawceiling.assign(texels, NONE_CEILING);
@@ -305,10 +325,10 @@ void sky_occlusion::neutral(int const Slot)
for (int z = 0; z < px; ++z)
{
auto const row{static_cast<std::size_t>(z0 + z) * m_side + x0};
std::fill_n(m_heights.begin() + row, px, NONE_TOP);
std::fill_n(m_occlusion.begin() + row, px, glm::vec4{1.f, 1.f, LIT_ABOVE_ALWAYS, LIT_BELOW_ALWAYS});
std::fill_n(m_heights.begin() + row, px, NEUTRAL_HEIGHTS);
std::fill_n(m_occlusion.begin() + row, px, NEUTRAL_OCCLUSION);
}
gpu_upload(x0, z0, px, m_neutralheights.data(), m_neutralocclusion.data());
gpu_upload(x0, z0, px, glm::value_ptr(m_neutralheights.front()), m_neutralocclusion.data());
++m_uploaded;
}
@@ -327,7 +347,7 @@ void sky_occlusion::apply(tile_result &Result)
for (int x = 0; x < px; ++x)
{
auto const source{static_cast<std::size_t>(z) * px + x};
m_heights[row + x] = Result.heights[source];
m_heights[row + x] = glm::vec4{Result.heights[source * 4 + 0], Result.heights[source * 4 + 1], Result.heights[source * 4 + 2], Result.heights[source * 4 + 3]};
m_occlusion[row + x] = glm::vec4{glm::unpackHalf1x16(Result.occlusion[source * 4 + 0]), glm::unpackHalf1x16(Result.occlusion[source * 4 + 1]),
glm::unpackHalf1x16(Result.occlusion[source * 4 + 2]), glm::unpackHalf1x16(Result.occlusion[source * 4 + 3])};
}
@@ -1077,8 +1097,9 @@ void sky_occlusion::occlude(int const Slot, tile_result &Result)
auto const rise{overhead ? 1e6f : keylight.y / across};
// the share of the sky seen from the height Origin over a texel, the search around it. a ceiling above the origin lets the sky
// through below its underside, what has none blocks it from the horizon up to its top
auto const sky = [&](int const X, int const Z, float const Origin) {
// through below its underside, what has none blocks it from the horizon up to its top. Ground, when given, gets the lowest top
// without a ceiling met below the origin (the ground around what stands over the texel), or keeps its value when there's none
auto const sky = [&](int const X, int const Z, float const Origin, float *Ground) {
float sum{0.f};
for (auto const &direction : directions)
{
@@ -1095,7 +1116,9 @@ void sky_occlusion::occlude(int const Slot, tile_result &Result)
high = std::min(high, (c - Origin) / distance);
else if (t > Origin)
low = std::max(low, (t - Origin) / distance);
if (low >= high)
else if ((Ground != nullptr) && has_top(t) && (false == has_ceiling(c)))
*Ground = std::min(*Ground, t);
if ((low >= high) && (Ground == nullptr))
break;
}
sum += std::max(0.f, sky_share(high) - sky_share(low));
@@ -1103,7 +1126,7 @@ void sky_occlusion::occlude(int const Slot, tile_result &Result)
return sum / static_cast<float>(directions.size());
};
Result.heights.resize(static_cast<std::size_t>(px) * px);
Result.heights.resize(static_cast<std::size_t>(px) * px * 4);
Result.occlusion.resize(static_cast<std::size_t>(px) * px * 4);
for (int z = 0; z < px; ++z)
{
@@ -1112,18 +1135,22 @@ void sky_occlusion::occlude(int const Slot, tile_result &Result)
auto const index{at(x, z)};
auto const t0{top[index]};
auto const c0{ceiling[index]};
glm::vec4 value{1.f, 1.f, LIT_ABOVE_ALWAYS, LIT_BELOW_ALWAYS};
auto heights{NEUTRAL_HEIGHTS};
auto value{NEUTRAL_OCCLUSION};
if (has_top(t0))
{
heights.x = t0;
auto const origin{t0 + ABOVE_TOP};
value.x = sky(x, z, origin);
// under a top with no ceiling is the inside of what is solid (a hill, a wall seen edge on): no sky, no sun
value.x = sky(x, z, origin, nullptr);
// under a top with no ceiling is the inside of what is solid (a hill, a wall seen edge on): no sky, no key light
value.y = 0.f;
value.z = 0.f;
heights.z = (sun ? LIT_NEVER_BELOW : LIT_BELOW_ALWAYS);
// the key light above the top: the height above which the rays towards it pass over all
if (false == sun)
value.z = LIT_ABOVE_ALWAYS;
value.w = LIT_ABOVE_ALWAYS;
else if (false == keyup)
value.z = LIT_BELOW_ALWAYS;
value.w = LIT_ABOVE_NEVER;
else if (false == overhead)
{
auto shadow{NONE_TOP};
@@ -1138,19 +1165,25 @@ void sky_occlusion::occlude(int const Slot, tile_result &Result)
continue;
shadow = std::max(shadow, top[sample] - drop);
}
value.z = has_top(shadow) ? std::clamp(shadow - t0, LIT_ABOVE_ALWAYS, LIT_BELOW_ALWAYS) : LIT_ABOVE_ALWAYS;
value.w = has_top(shadow) ? std::clamp(shadow - t0, LIT_ABOVE_ALWAYS, LIT_ABOVE_NEVER) : LIT_ABOVE_ALWAYS;
}
else
value.z = LIT_ABOVE_ALWAYS;
value.w = LIT_ABOVE_ALWAYS;
if (has_ceiling(c0))
{
// under the ceiling
// under the ceiling: the share near it, and the one a metre above the ground around (the deeper the space under the
// ceiling, the more of the sky its bottom sees on the sides), which the shading blends by the height of the point
heights.y = c0;
auto const under{c0 - BELOW_CEILING};
value.y = sky(x, z, under);
auto ground{std::numeric_limits<float>::max()};
value.y = sky(x, z, under, &ground);
auto const deep{ground < c0 ? std::clamp(ground + 1.f, c0 - DEEP_BELOW_CEILING_MAX, under) : c0 - DEEP_BELOW_CEILING_UNKNOWN};
heights.w = deep;
value.z = (deep < under - 0.25f) ? sky(x, z, deep, nullptr) : value.y;
if (false == sun)
value.w = LIT_BELOW_ALWAYS;
heights.z = LIT_BELOW_ALWAYS;
else if (false == keyup)
value.w = LIT_NEVER_BELOW;
heights.z = LIT_NEVER_BELOW;
else
{
// the height below which the rays towards the key light pass under the ceilings, out to the open
@@ -1176,18 +1209,16 @@ void sky_occlusion::occlude(int const Slot, tile_result &Result)
}
}
// the rays which don't come out within the reach stay in the tunnel
value.w = (out && has_top(limit)) ? std::clamp(limit - t0, LIT_NEVER_BELOW, LIT_BELOW_ALWAYS) : LIT_NEVER_BELOW;
heights.z = (out && has_top(limit)) ? limit : LIT_NEVER_BELOW;
}
}
else
value.w = (sun ? LIT_NEVER_BELOW : LIT_BELOW_ALWAYS);
}
auto const target{static_cast<std::size_t>(z) * px + x};
Result.heights[target] = t0;
Result.occlusion[target * 4 + 0] = glm::packHalf1x16(value.x);
Result.occlusion[target * 4 + 1] = glm::packHalf1x16(value.y);
Result.occlusion[target * 4 + 2] = glm::packHalf1x16(value.z);
Result.occlusion[target * 4 + 3] = glm::packHalf1x16(value.w);
for (int c = 0; c < 4; ++c)
{
Result.heights[target * 4 + c] = heights[c];
Result.occlusion[target * 4 + c] = glm::packHalf1x16(value[c]);
}
}
}
}
@@ -1250,7 +1281,8 @@ void sky_occlusion::draw_debug_ui()
auto const x{wrap(static_cast<int>(std::floor(wx / texel)), m_side)};
auto const z{wrap(static_cast<int>(std::floor(wz / texel)), m_side)};
auto const index{static_cast<std::size_t>(z) * m_side + x};
auto const top{m_heights[index]};
auto const &heights{m_heights[index]};
auto const top{heights.x};
auto const &occlusion{m_occlusion[index]};
float value{0.f};
switch (m_previewmode)
@@ -1259,10 +1291,11 @@ void sky_occlusion::draw_debug_ui()
value = occlusion.x;
break;
case 2:
value = occlusion.y;
// near the ground under them
value = occlusion.z;
break;
case 3:
value = texel_occlusion(top, occlusion, top + 0.1f).y;
value = texel_occlusion(heights, occlusion, top + 0.1f).y;
break;
default:
value = has_top(top) ? std::clamp(0.5f + static_cast<float>(top - camera.y) / 40.f, 0.f, 1.f) : 0.f;

View File

@@ -117,11 +117,13 @@ class sky_occlusion
void draw_debug_ui();
protected:
// the textures of Side x Side texels: the height of the top (one 32 bit float per texel) and the occlusion (four 16 bit floats);
// false when they can't be made
// the textures of Side x Side texels: the heights (four 32 bit floats per texel: the top, the ceiling, the height below which the
// key light comes in under the ceiling, the height the deep share of the sky under the ceiling is worked out at) and the occlusion
// (four 16 bit floats: the share of the sky above the top, under the ceiling near it and deep, the height above the top over which
// the key light comes); false when they can't be made
virtual bool gpu_allocate(int const Side) = 0;
virtual void gpu_release() = 0;
// copies a square of Size x Size texels at X, Y of the textures: Heights one float per texel, Occlusion four halves per texel,
// copies a square of Size x Size texels at X, Y of the textures: Heights four floats per texel, Occlusion four halves per texel,
// rows along x, one after another along z
virtual void gpu_upload(int const X, int const Y, int const Size, float const *Heights, std::uint16_t const *Occlusion) = 0;
@@ -141,7 +143,7 @@ class sky_occlusion
std::uint32_t generation{0};
std::vector<triangle> triangles;
};
// what it gives back: the top and the occlusion of the texels of a tile
// what it gives back: the heights (four per texel) and the occlusion of the texels of a tile
struct tile_result
{
int slot{0};
@@ -193,11 +195,11 @@ class sky_occlusion
glm::ivec2 m_cameratile{0};
glm::dvec3 m_camera{0.0};
glm::vec3 m_keylight{0.f, 1.f, 0.f}; // the direction the thread was given last
// the copy of the maps the cpu samples (sample()): the top, and the occlusion as floats
std::vector<float> m_heights;
// the copy of the maps the cpu samples (sample()): the heights, and the occlusion as floats
std::vector<glm::vec4> m_heights;
std::vector<glm::vec4> m_occlusion;
// neutral tiles, uploaded when a tile comes into the square
std::vector<float> m_neutralheights;
std::vector<glm::vec4> m_neutralheights;
std::vector<std::uint16_t> m_neutralocclusion;
std::size_t m_checkslot{0}; // the slot whose signature is checked next
scene::basic_region const *m_region{nullptr};

View File

@@ -42,11 +42,11 @@ opengl46_sky_occlusion::~opengl46_sky_occlusion()
void opengl46_sky_occlusion::init()
{
// a sampler with no texture to read from is undefined, also where the shading doesn't read it
m_emptyheights = make_map(GL_R32F, 1);
float const none{-1.0e4f};
glClearTexImage(m_emptyheights, 0, GL_RED, GL_FLOAT, &none);
m_emptyheights = make_map(GL_RGBA32F, 1);
float const none[4]{-1.0e4f, 1.0e4f, 1.0e4f, -1.0e4f};
glClearTexImage(m_emptyheights, 0, GL_RGBA, GL_FLOAT, none);
m_emptyocclusion = make_map(GL_RGBA16F, 1);
float const open[4]{1.f, 1.f, -1000.f, 1000.f};
float const open[4]{1.f, 1.f, 1.f, -1000.f};
glClearTexImage(m_emptyocclusion, 0, GL_RGBA, GL_FLOAT, open);
bind();
}
@@ -60,7 +60,7 @@ void opengl46_sky_occlusion::bind() const
bool opengl46_sky_occlusion::gpu_allocate(int const Side)
{
gpu_release();
m_heights = make_map(GL_R32F, Side);
m_heights = make_map(GL_RGBA32F, Side);
m_occlusion = make_map(GL_RGBA16F, Side);
if ((m_heights == 0) || (m_occlusion == 0))
return false;
@@ -90,7 +90,7 @@ void opengl46_sky_occlusion::gpu_upload(int const X, int const Y, int const Size
glGetIntegerv(GL_UNPACK_ALIGNMENT, &alignment);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glTextureSubImage2D(m_heights, 0, X, Y, Size, Size, GL_RED, GL_FLOAT, Heights);
glTextureSubImage2D(m_heights, 0, X, Y, Size, Size, GL_RGBA, GL_FLOAT, Heights);
glTextureSubImage2D(m_occlusion, 0, X, Y, Size, Size, GL_RGBA, GL_HALF_FLOAT, Occlusion);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, static_cast<GLuint>(unpack));
glPixelStorei(GL_UNPACK_ALIGNMENT, alignment);

View File

@@ -684,7 +684,7 @@ bool vulkan14_renderer::init_scene(std::string &Reason)
}
return true;
};
if ((false == standin(scene.skyocclusion_heights_empty, VK_FORMAT_R32_SFLOAT, "empty sky occlusion heights")) || (false == standin(scene.skyocclusion_empty, VK_FORMAT_R16G16B16A16_SFLOAT, "empty sky occlusion")))
if ((false == standin(scene.skyocclusion_heights_empty, VK_FORMAT_R32G32B32A32_SFLOAT, "empty sky occlusion heights")) || (false == standin(scene.skyocclusion_empty, VK_FORMAT_R16G16B16A16_SFLOAT, "empty sky occlusion")))
{
Reason = "no stand-ins of the maps of the occlusion of the sky";
return false;

View File

@@ -63,7 +63,7 @@ bool sky_occlusion_maps::gpu_allocate(int const Side)
}
return true;
};
if ((false == make(m_heights, VK_FORMAT_R32_SFLOAT, "sky occlusion heights")) || (false == make(m_occlusion, VK_FORMAT_R16G16B16A16_SFLOAT, "sky occlusion")))
if ((false == make(m_heights, VK_FORMAT_R32G32B32A32_SFLOAT, "sky occlusion heights")) || (false == make(m_occlusion, VK_FORMAT_R16G16B16A16_SFLOAT, "sky occlusion")))
{
let_go();
return false;
@@ -99,9 +99,9 @@ void sky_occlusion_maps::gpu_upload(int const X, int const Y, int const Size, fl
}
auto const texels{static_cast<std::size_t>(Size) * Size};
auto const offset{m_data.size()};
m_data.resize(offset + texels * (sizeof(float) + 4 * sizeof(std::uint16_t)));
std::memcpy(m_data.data() + offset, Heights, texels * sizeof(float));
std::memcpy(m_data.data() + offset + texels * sizeof(float), Occlusion, texels * 4 * sizeof(std::uint16_t));
m_data.resize(offset + texels * (4 * sizeof(float) + 4 * sizeof(std::uint16_t)));
std::memcpy(m_data.data() + offset, Heights, texels * 4 * sizeof(float));
std::memcpy(m_data.data() + offset + texels * 4 * sizeof(float), Occlusion, texels * 4 * sizeof(std::uint16_t));
m_squares.push_back(square{X, Y, Size, offset});
}
@@ -131,7 +131,7 @@ void sky_occlusion_maps::flush(command_stream *Commands)
region.imageOffset = {entry.x, entry.y, 0};
region.imageExtent = {static_cast<std::uint32_t>(entry.size), static_cast<std::uint32_t>(entry.size), 1};
heights.push_back(region);
region.bufferOffset += static_cast<VkDeviceSize>(entry.size) * entry.size * sizeof(float);
region.bufferOffset += static_cast<VkDeviceSize>(entry.size) * entry.size * 4 * sizeof(float);
occlusion.push_back(region);
}
auto copy = [&](image &Target, std::vector<VkBufferImageCopy2> const &Regions) {

View File

@@ -1,8 +1,9 @@
// the occlusion of the sky and of the sun by the scenery around the camera (gfx.skyvisibility, gfx::sky_occlusion, the track T):
// toroidal maps of the square around the camera, read texel by texel. SKY_OCCLUSION_HEIGHT_TEX holds the height of the top of the
// scenery over a texel (the highest surface, world space), SKY_OCCLUSION_TEX for the texel: x the share of the sky a surface facing
// up sees above the top, y the same under the ceiling (the vault of a tunnel, the deck of a bridge), z the height above the top over
// which the key light (the sun, the moon) comes, w the height above the top under which it comes in under the ceilings.
// toroidal maps of the square around the camera, read texel by texel. SKY_OCCLUSION_HEIGHT_TEX holds for a texel, world space: x the
// height of the top of the scenery (the highest surface), y of the ceiling (the lowest surface facing down: the vault of a tunnel, the
// deck of a bridge; 1e4 for none), z the height under the ceiling below which the key light (the sun, the moon) comes in, w the height
// the deep share of the sky under the ceiling was worked out at (a metre above the ground around). SKY_OCCLUSION_TEX: x the share of
// the sky above the top, y under the ceiling near it, z deep under it, w the height above the top over which the key light comes.
// the same as gfx::sky_occlusion::sample() of rendering/common/skyocclusion.cpp, which the cpu works out the light around the
// vehicles with: a change here goes there as well
#ifndef SKY_OCCLUSION_GLSL
@@ -11,12 +12,27 @@
layout(binding = SKY_OCCLUSION_HEIGHT_TEX) uniform sampler2D sky_occlusion_height;
layout(binding = SKY_OCCLUSION_TEX) uniform sampler2D sky_occlusion_map;
// a point this far below the top of a texel is under it, fully this much further; half the height over which the key light comes
// in at the edge of its shadow
// a point this far below the top of a texel is under it, fully this much further; how far below the ceiling the near share under
// it was worked out; half the height over which the key light comes in at the edge of its shadow
const float SKY_OCCLUSION_COVER_START = 0.75;
const float SKY_OCCLUSION_COVER_SPAN = 1.0;
const float SKY_OCCLUSION_BELOW_CEILING = 2.5;
const float SKY_OCCLUSION_SUN_SOFTNESS = 0.6;
// the coordinates of the gather of the four texels around a world point (offset from the camera) and the bilinear weights of
// the texels in the order of a gather: (0, 1), (1, 1), (1, 0), (0, 0). false out of the square (Fade 1)
bool sky_occlusion_texels(vec2 world, out vec2 uv, out vec4 weights, out float fade)
{
fade = clamp((max(abs(world.x - sky_occlusion_center.x), abs(world.y - sky_occlusion_center.y)) - sky_occlusion_params.z) / max(sky_occlusion_params.w, 1e-3), 0.0, 1.0);
float texel = sky_occlusion_params.x / sky_occlusion_params.y;
vec2 coords = (world + sky_occlusion_origin.xy) / texel - 0.5;
vec2 base = floor(coords);
vec2 f = coords - base;
uv = (base + 1.0) / sky_occlusion_params.y;
weights = vec4((1.0 - f.x) * f.y, f.x * f.y, f.x * (1.0 - f.y), (1.0 - f.x) * (1.0 - f.y));
return fade < 1.0;
}
// the share of the sky (x) and of the key light (y) a point gets, 0..1. World: its offset from the camera along the world axes,
// Worldnormal: the normal of its surface in the world space (zero for a point in the air), the texels are read a little in front of
// the surface (a wall reads the ground in front of it, not the roof above it)
@@ -24,30 +40,32 @@ vec2 sky_occlusion_world(vec3 world, vec3 worldnormal)
{
if (sky_occlusion_origin.w <= 0.0)
return vec2(1.0);
float fade = clamp((max(abs(world.x - sky_occlusion_center.x), abs(world.z - sky_occlusion_center.y)) - sky_occlusion_params.z) / max(sky_occlusion_params.w, 1e-3), 0.0, 1.0);
if (fade >= 1.0)
vec3 point = world + worldnormal * (0.75 * sky_occlusion_params.x / sky_occlusion_params.y);
vec2 uv;
vec4 weights;
float fade;
if (false == sky_occlusion_texels(point.xz, uv, weights, fade))
return vec2(1.0);
float texel = sky_occlusion_params.x / sky_occlusion_params.y;
vec3 point = world + worldnormal * (0.75 * texel);
vec2 coords = (point.xz + sky_occlusion_origin.xy) / texel - 0.5;
vec2 base = floor(coords);
vec2 f = coords - base;
vec2 uv = (base + 1.0) / sky_occlusion_params.y;
// the four texels around the point, each with its own top: under one of them a point gets what is under it, above it what is
// above, and the four are blended as bilinear filtering would
// the four texels around the point, each with its own heights: under the top of one a point gets what is under it (between the
// near and the deep share, by its height), above it what is above, and the four are blended as bilinear filtering would
vec4 tops = textureGather(sky_occlusion_height, uv, 0);
vec4 ceilings = textureGather(sky_occlusion_height, uv, 1);
vec4 litbelow = textureGather(sky_occlusion_height, uv, 2);
vec4 deeps = textureGather(sky_occlusion_height, uv, 3);
vec4 skytop = textureGather(sky_occlusion_map, uv, 0);
vec4 skyunder = textureGather(sky_occlusion_map, uv, 1);
vec4 litabove = textureGather(sky_occlusion_map, uv, 2);
vec4 litbelow = textureGather(sky_occlusion_map, uv, 3);
float height = point.y + sky_occlusion_origin.z;
vec4 cover = clamp((tops - vec4(height) - SKY_OCCLUSION_COVER_START) / SKY_OCCLUSION_COVER_SPAN, 0.0, 1.0);
vec4 skynear = textureGather(sky_occlusion_map, uv, 1);
vec4 skydeep = textureGather(sky_occlusion_map, uv, 2);
vec4 litabove = textureGather(sky_occlusion_map, uv, 3);
vec4 height = vec4(point.y + sky_occlusion_origin.z);
vec4 cover = clamp((tops - height - SKY_OCCLUSION_COVER_START) / SKY_OCCLUSION_COVER_SPAN, 0.0, 1.0);
vec4 nearheight = ceilings - SKY_OCCLUSION_BELOW_CEILING;
vec4 skyunder = mix(skydeep, skynear, clamp((height - deeps) / max(nearheight - deeps, vec4(1e-3)), 0.0, 1.0));
// no ceiling: the inside of what is solid
skyunder *= step(ceilings, vec4(5000.0));
vec4 sky = mix(skytop, skyunder, cover);
vec4 suntop = smoothstep(vec4(-SKY_OCCLUSION_SUN_SOFTNESS), vec4(SKY_OCCLUSION_SUN_SOFTNESS), vec4(height) - (tops + litabove));
vec4 sununder = smoothstep(vec4(-SKY_OCCLUSION_SUN_SOFTNESS), vec4(SKY_OCCLUSION_SUN_SOFTNESS), (tops + litbelow) - vec4(height));
vec4 suntop = smoothstep(vec4(-SKY_OCCLUSION_SUN_SOFTNESS), vec4(SKY_OCCLUSION_SUN_SOFTNESS), height - (tops + litabove));
vec4 sununder = smoothstep(vec4(-SKY_OCCLUSION_SUN_SOFTNESS), vec4(SKY_OCCLUSION_SUN_SOFTNESS), litbelow - height);
vec4 sun = mix(suntop, sununder, cover);
// the order of the texels of a gather: (0, 1), (1, 1), (1, 0), (0, 0)
vec4 weights = vec4((1.0 - f.x) * f.y, f.x * f.y, f.x * (1.0 - f.y), (1.0 - f.x) * (1.0 - f.y));
return mix(vec2(dot(sky, weights), dot(sun, weights)), vec2(1.0), fade);
}
@@ -57,16 +75,13 @@ float sky_occlusion_cover(vec3 world)
{
if (sky_occlusion_origin.w <= 0.0)
return 0.0;
float fade = clamp((max(abs(world.x - sky_occlusion_center.x), abs(world.z - sky_occlusion_center.y)) - sky_occlusion_params.z) / max(sky_occlusion_params.w, 1e-3), 0.0, 1.0);
if (fade >= 1.0)
vec2 uv;
vec4 weights;
float fade;
if (false == sky_occlusion_texels(world.xz, uv, weights, fade))
return 0.0;
float texel = sky_occlusion_params.x / sky_occlusion_params.y;
vec2 coords = (world.xz + sky_occlusion_origin.xy) / texel - 0.5;
vec2 base = floor(coords);
vec2 f = coords - base;
vec4 tops = textureGather(sky_occlusion_height, (base + 1.0) / sky_occlusion_params.y, 0);
vec4 tops = textureGather(sky_occlusion_height, uv, 0);
vec4 cover = clamp((tops - vec4(world.y + sky_occlusion_origin.z) - SKY_OCCLUSION_COVER_START) / SKY_OCCLUSION_COVER_SPAN, 0.0, 1.0);
vec4 weights = vec4((1.0 - f.x) * f.y, f.x * f.y, f.x * (1.0 - f.y), (1.0 - f.x) * (1.0 - f.y));
return dot(cover, weights) * (1.0 - fade);
}

View File

@@ -1,8 +1,9 @@
// the occlusion of the sky and of the sun by the scenery around the camera (gfx.skyvisibility, gfx::sky_occlusion, the track T):
// toroidal maps of the square around the camera, read texel by texel. SKY_OCCLUSION_HEIGHT_TEX holds the height of the top of the
// scenery over a texel (the highest surface, world space), SKY_OCCLUSION_TEX for the texel: x the share of the sky a surface facing
// up sees above the top, y the same under the ceiling (the vault of a tunnel, the deck of a bridge), z the height above the top over
// which the key light (the sun, the moon) comes, w the height above the top under which it comes in under the ceilings.
// toroidal maps of the square around the camera, read texel by texel. SKY_OCCLUSION_HEIGHT_TEX holds for a texel, world space: x the
// height of the top of the scenery (the highest surface), y of the ceiling (the lowest surface facing down: the vault of a tunnel, the
// deck of a bridge; 1e4 for none), z the height under the ceiling below which the key light (the sun, the moon) comes in, w the height
// the deep share of the sky under the ceiling was worked out at (a metre above the ground around). SKY_OCCLUSION_TEX: x the share of
// the sky above the top, y under the ceiling near it, z deep under it, w the height above the top over which the key light comes.
// the same as gfx::sky_occlusion::sample() of rendering/common/skyocclusion.cpp, which the cpu works out the light around the
// vehicles with: a change here goes there as well
#ifndef SKY_OCCLUSION_GLSL
@@ -11,12 +12,27 @@
layout(set = PASS_SET, binding = SKY_OCCLUSION_HEIGHT_TEX) uniform sampler2D sky_occlusion_height;
layout(set = PASS_SET, binding = SKY_OCCLUSION_TEX) uniform sampler2D sky_occlusion_map;
// a point this far below the top of a texel is under it, fully this much further; half the height over which the key light comes
// in at the edge of its shadow
// a point this far below the top of a texel is under it, fully this much further; how far below the ceiling the near share under
// it was worked out; half the height over which the key light comes in at the edge of its shadow
const float SKY_OCCLUSION_COVER_START = 0.75;
const float SKY_OCCLUSION_COVER_SPAN = 1.0;
const float SKY_OCCLUSION_BELOW_CEILING = 2.5;
const float SKY_OCCLUSION_SUN_SOFTNESS = 0.6;
// the coordinates of the gather of the four texels around a world point (offset from the camera) and the bilinear weights of
// the texels in the order of a gather: (0, 1), (1, 1), (1, 0), (0, 0). false out of the square (Fade 1)
bool sky_occlusion_texels(vec2 world, out vec2 uv, out vec4 weights, out float fade)
{
fade = clamp((max(abs(world.x - sky_occlusion_center.x), abs(world.y - sky_occlusion_center.y)) - sky_occlusion_params.z) / max(sky_occlusion_params.w, 1e-3), 0.0, 1.0);
float texel = sky_occlusion_params.x / sky_occlusion_params.y;
vec2 coords = (world + sky_occlusion_origin.xy) / texel - 0.5;
vec2 base = floor(coords);
vec2 f = coords - base;
uv = (base + 1.0) / sky_occlusion_params.y;
weights = vec4((1.0 - f.x) * f.y, f.x * f.y, f.x * (1.0 - f.y), (1.0 - f.x) * (1.0 - f.y));
return fade < 1.0;
}
// the share of the sky (x) and of the key light (y) a point gets, 0..1. World: its offset from the camera along the world axes,
// Worldnormal: the normal of its surface in the world space (zero for a point in the air), the texels are read a little in front of
// the surface (a wall reads the ground in front of it, not the roof above it)
@@ -24,30 +40,32 @@ vec2 sky_occlusion_world(vec3 world, vec3 worldnormal)
{
if (sky_occlusion_origin.w <= 0.0)
return vec2(1.0);
float fade = clamp((max(abs(world.x - sky_occlusion_center.x), abs(world.z - sky_occlusion_center.y)) - sky_occlusion_params.z) / max(sky_occlusion_params.w, 1e-3), 0.0, 1.0);
if (fade >= 1.0)
vec3 point = world + worldnormal * (0.75 * sky_occlusion_params.x / sky_occlusion_params.y);
vec2 uv;
vec4 weights;
float fade;
if (false == sky_occlusion_texels(point.xz, uv, weights, fade))
return vec2(1.0);
float texel = sky_occlusion_params.x / sky_occlusion_params.y;
vec3 point = world + worldnormal * (0.75 * texel);
vec2 coords = (point.xz + sky_occlusion_origin.xy) / texel - 0.5;
vec2 base = floor(coords);
vec2 f = coords - base;
vec2 uv = (base + 1.0) / sky_occlusion_params.y;
// the four texels around the point, each with its own top: under one of them a point gets what is under it, above it what is
// above, and the four are blended as bilinear filtering would
// the four texels around the point, each with its own heights: under the top of one a point gets what is under it (between the
// near and the deep share, by its height), above it what is above, and the four are blended as bilinear filtering would
vec4 tops = textureGather(sky_occlusion_height, uv, 0);
vec4 ceilings = textureGather(sky_occlusion_height, uv, 1);
vec4 litbelow = textureGather(sky_occlusion_height, uv, 2);
vec4 deeps = textureGather(sky_occlusion_height, uv, 3);
vec4 skytop = textureGather(sky_occlusion_map, uv, 0);
vec4 skyunder = textureGather(sky_occlusion_map, uv, 1);
vec4 litabove = textureGather(sky_occlusion_map, uv, 2);
vec4 litbelow = textureGather(sky_occlusion_map, uv, 3);
float height = point.y + sky_occlusion_origin.z;
vec4 cover = clamp((tops - vec4(height) - SKY_OCCLUSION_COVER_START) / SKY_OCCLUSION_COVER_SPAN, 0.0, 1.0);
vec4 skynear = textureGather(sky_occlusion_map, uv, 1);
vec4 skydeep = textureGather(sky_occlusion_map, uv, 2);
vec4 litabove = textureGather(sky_occlusion_map, uv, 3);
vec4 height = vec4(point.y + sky_occlusion_origin.z);
vec4 cover = clamp((tops - height - SKY_OCCLUSION_COVER_START) / SKY_OCCLUSION_COVER_SPAN, 0.0, 1.0);
vec4 nearheight = ceilings - SKY_OCCLUSION_BELOW_CEILING;
vec4 skyunder = mix(skydeep, skynear, clamp((height - deeps) / max(nearheight - deeps, vec4(1e-3)), 0.0, 1.0));
// no ceiling: the inside of what is solid
skyunder *= step(ceilings, vec4(5000.0));
vec4 sky = mix(skytop, skyunder, cover);
vec4 suntop = smoothstep(vec4(-SKY_OCCLUSION_SUN_SOFTNESS), vec4(SKY_OCCLUSION_SUN_SOFTNESS), vec4(height) - (tops + litabove));
vec4 sununder = smoothstep(vec4(-SKY_OCCLUSION_SUN_SOFTNESS), vec4(SKY_OCCLUSION_SUN_SOFTNESS), (tops + litbelow) - vec4(height));
vec4 suntop = smoothstep(vec4(-SKY_OCCLUSION_SUN_SOFTNESS), vec4(SKY_OCCLUSION_SUN_SOFTNESS), height - (tops + litabove));
vec4 sununder = smoothstep(vec4(-SKY_OCCLUSION_SUN_SOFTNESS), vec4(SKY_OCCLUSION_SUN_SOFTNESS), litbelow - height);
vec4 sun = mix(suntop, sununder, cover);
// the order of the texels of a gather: (0, 1), (1, 1), (1, 0), (0, 0)
vec4 weights = vec4((1.0 - f.x) * f.y, f.x * f.y, f.x * (1.0 - f.y), (1.0 - f.x) * (1.0 - f.y));
return mix(vec2(dot(sky, weights), dot(sun, weights)), vec2(1.0), fade);
}
@@ -57,16 +75,13 @@ float sky_occlusion_cover(vec3 world)
{
if (sky_occlusion_origin.w <= 0.0)
return 0.0;
float fade = clamp((max(abs(world.x - sky_occlusion_center.x), abs(world.z - sky_occlusion_center.y)) - sky_occlusion_params.z) / max(sky_occlusion_params.w, 1e-3), 0.0, 1.0);
if (fade >= 1.0)
vec2 uv;
vec4 weights;
float fade;
if (false == sky_occlusion_texels(world.xz, uv, weights, fade))
return 0.0;
float texel = sky_occlusion_params.x / sky_occlusion_params.y;
vec2 coords = (world.xz + sky_occlusion_origin.xy) / texel - 0.5;
vec2 base = floor(coords);
vec2 f = coords - base;
vec4 tops = textureGather(sky_occlusion_height, (base + 1.0) / sky_occlusion_params.y, 0);
vec4 tops = textureGather(sky_occlusion_height, uv, 0);
vec4 cover = clamp((tops - vec4(world.y + sky_occlusion_origin.z) - SKY_OCCLUSION_COVER_START) / SKY_OCCLUSION_COVER_SPAN, 0.0, 1.0);
vec4 weights = vec4((1.0 - f.x) * f.y, f.x * f.y, f.x * (1.0 - f.y), (1.0 - f.x) * (1.0 - f.y));
return dot(cover, weights) * (1.0 - fade);
}