mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-26 20:34:54 +01:00
Improve rain shader
This commit is contained in:
@@ -318,6 +318,7 @@ set(SOURCES
|
||||
"rendering/common/terrainmaterials.cpp"
|
||||
"rendering/common/grassfield.cpp"
|
||||
"rendering/common/gpuparticles.cpp"
|
||||
"rendering/common/windscreenwater.cpp"
|
||||
"rendering/common/locallights.cpp"
|
||||
"rendering/common/skylight.cpp"
|
||||
"rendering/common/weatherstate.cpp"
|
||||
@@ -416,6 +417,7 @@ if (WITH_OPENGL_MODERN)
|
||||
"rendering/opengl46/opengl46precipitation.cpp"
|
||||
"rendering/opengl46/opengl46particles.cpp"
|
||||
"rendering/opengl46/opengl46gpuparticles.cpp"
|
||||
"rendering/opengl46/opengl46water.cpp"
|
||||
"rendering/opengl46/opengl46terrain.cpp"
|
||||
"rendering/opengl46/opengl46grass.cpp"
|
||||
"rendering/opengl46/opengl46backend.cpp"
|
||||
@@ -461,6 +463,7 @@ if (WITH_VULKAN)
|
||||
"rendering/vulkan14/vulkan14terrain.cpp"
|
||||
"rendering/vulkan14/vulkan14grass.cpp"
|
||||
"rendering/vulkan14/vulkan14particles.cpp"
|
||||
"rendering/vulkan14/vulkan14water.cpp"
|
||||
"rendering/vulkan14/vulkan14debug.cpp"
|
||||
"rendering/vulkan14/vulkan14commands.cpp"
|
||||
"rendering/vulkan14/vulkan14raytracing.cpp"
|
||||
|
||||
@@ -40,6 +40,16 @@ void gl::glsl_common_setup()
|
||||
"#define PARTICLE_OWNER_SSBO_BINDING " + std::to_string(PARTICLE_OWNER_SSBO_BINDING) + "\n" +
|
||||
"#define PARTICLE_OUTPUT_SSBO_BINDING " + std::to_string(PARTICLE_OUTPUT_SSBO_BINDING) + "\n" +
|
||||
"#define PARTICLE_COMMAND_SSBO_BINDING " + std::to_string(PARTICLE_COMMAND_SSBO_BINDING) + "\n" +
|
||||
"#define WATER_SLOTS " + std::to_string(WATER_SLOTS) + "\n" +
|
||||
"#define WATER_DROPS " + std::to_string(WATER_DROPS) + "\n" +
|
||||
"#define WATER_GRID " + std::to_string(WATER_GRID) + "\n" +
|
||||
"#define WATER_CELL_DROPS " + std::to_string(WATER_CELL_DROPS) + "\n" +
|
||||
"#define WATER_TRAIL " + std::to_string(WATER_TRAIL) + "\n" +
|
||||
"#define WATER_MAP " + std::to_string(WATER_MAP) + "\n" +
|
||||
"#define WATER_GROUP_SIZE " + std::to_string(WATER_GROUP_SIZE) + "\n" +
|
||||
"#define WATER_SSBO_BINDING " + std::to_string(WATER_SSBO_BINDING) + "\n" +
|
||||
"#define WATER_UBO_BINDING " + std::to_string(WATER_UBO_BINDING) + "\n" +
|
||||
"#define WATER_MASK_TEX " + std::to_string(WATER_MASK_TEX) + "\n" +
|
||||
"#define INSTANCE_COUNTER_HEADER " + std::to_string(INSTANCE_COUNTER_HEADER) + "\n" +
|
||||
"#define INSTANCE_JOB_SLEEPERS " + std::to_string(INSTANCE_JOB_SLEEPERS) + "U\n" +
|
||||
"#define LIGHT_SSBO_BINDING " + std::to_string(LIGHT_SSBO_BINDING) + "\n" +
|
||||
|
||||
94
gl/ubo.h
94
gl/ubo.h
@@ -5,6 +5,8 @@
|
||||
#include "buffer.h"
|
||||
#include <glm/glm.hpp>
|
||||
#include <cstddef>
|
||||
#include <array>
|
||||
#include <utility>
|
||||
|
||||
#define UBS_PAD(x) uint8_t PAD[x]
|
||||
|
||||
@@ -124,9 +126,12 @@ namespace gl
|
||||
// the reflection of the surroundings the screen space reflections and the ray tracing don't find, for every pass: x: albedo
|
||||
// of the ground below the horizon of the environment map (gfx.ambient.groundalbedo), yzw unused
|
||||
glm::vec4 env_params;
|
||||
// the water on the windscreens (mat_rain_windscreen): xyz the velocity of the air against the vehicle of the camera (the
|
||||
// share of the wind the particles of the rain drift with, less the velocity of the vehicle), world space, m/s; w unused
|
||||
glm::vec4 rain_wind;
|
||||
};
|
||||
|
||||
static_assert(sizeof(scene_ubs) == 688 + 80 * MAX_HEADLIGHT_SHADOWS, "bad size of ubs");
|
||||
static_assert(sizeof(scene_ubs) == 704 + 80 * MAX_HEADLIGHT_SHADOWS, "bad size of ubs");
|
||||
|
||||
const size_t MAX_PARAMS = 3;
|
||||
|
||||
@@ -565,6 +570,93 @@ namespace gl
|
||||
};
|
||||
static_assert(sizeof(particle_draw) == 48, "bad size of particle_draw");
|
||||
|
||||
// the water on the windscreens (gfx::windscreen_water, water_*.comp, water.glsl, mat_rain_windscreen.frag): the glasses of the
|
||||
// cab of the train simulated at once (each a material of mat_rain_windscreen), the drops a glass holds, the cells of the grid
|
||||
// the drops are looked up by along each coordinate of the texture of the glass (uv) and the drops a cell lists, the texels of
|
||||
// the map of the wetted paths and of the map of the glass (its axes, the blades of the wipers) along each coordinate, the work
|
||||
// group of the passes
|
||||
const GLuint WATER_SLOTS = 4;
|
||||
const GLuint WATER_DROPS = 65536;
|
||||
const GLuint WATER_GRID = 128;
|
||||
const GLuint WATER_CELL_DROPS = 32;
|
||||
const GLuint WATER_TRAIL = 1024;
|
||||
const GLuint WATER_MAP = 256;
|
||||
const GLuint WATER_GROUP_SIZE = 64;
|
||||
// the binding points of the opengl renderer: the storage of the water (read by the glasses, written by the passes), the block
|
||||
// of the step of the simulation (water_ubs) and the texture unit of the mask of the wipers the map is baked from. the storage
|
||||
// point is used by nothing else, bound with the colour passes
|
||||
const GLuint WATER_SSBO_BINDING = 16;
|
||||
const GLuint WATER_UBO_BINDING = 6;
|
||||
const size_t WATER_MASK_TEX = MAX_TEXTURES + HELPER_TEXTURES + 13;
|
||||
// the parts of the storage of the water (std430, GLSL water_ssbo), offsets in bytes: the header of every glass (vec4), the
|
||||
// counts of every glass (uvec4), the drops (two vec4 each), the axes of the map (two vec4 a texel), the blades of the map (vec4
|
||||
// a texel), the state of the drops (uint), the drops they flow into (uint), what they gather (four int), what they keep
|
||||
// (vec4), then the cells of the grids (a count and the drops of each cell) and the wetted paths (uint)
|
||||
const size_t WATER_SLOT_DROPS = static_cast<size_t>(WATER_SLOTS) * WATER_DROPS;
|
||||
const size_t WATER_SLOT_TEXELS = static_cast<size_t>(WATER_SLOTS) * WATER_MAP * WATER_MAP;
|
||||
const size_t WATER_HEADER_OFFSET = 0;
|
||||
const size_t WATER_COUNTS_OFFSET = WATER_HEADER_OFFSET + 16 * WATER_SLOTS;
|
||||
const size_t WATER_DROPS_OFFSET = WATER_COUNTS_OFFSET + 16 * WATER_SLOTS;
|
||||
const size_t WATER_AXES_OFFSET = WATER_DROPS_OFFSET + 32 * WATER_SLOT_DROPS;
|
||||
const size_t WATER_BLADES_OFFSET = WATER_AXES_OFFSET + 32 * WATER_SLOT_TEXELS;
|
||||
const size_t WATER_STATE_OFFSET = WATER_BLADES_OFFSET + 16 * WATER_SLOT_TEXELS;
|
||||
const size_t WATER_TARGET_OFFSET = WATER_STATE_OFFSET + 4 * WATER_SLOT_DROPS;
|
||||
const size_t WATER_GATHER_OFFSET = WATER_TARGET_OFFSET + 4 * WATER_SLOT_DROPS;
|
||||
const size_t WATER_EXTRA_OFFSET = WATER_GATHER_OFFSET + 16 * WATER_SLOT_DROPS;
|
||||
const size_t WATER_CELLS_OFFSET = WATER_EXTRA_OFFSET + 16 * WATER_SLOT_DROPS;
|
||||
const size_t WATER_CELLS_SIZE = 4 * static_cast<size_t>(WATER_SLOTS) * WATER_GRID * WATER_GRID * (1 + WATER_CELL_DROPS);
|
||||
const size_t WATER_TRAIL_OFFSET = WATER_CELLS_OFFSET + WATER_CELLS_SIZE;
|
||||
const size_t WATER_STORAGE_SIZE = WATER_TRAIL_OFFSET + 4 * static_cast<size_t>(WATER_SLOTS) * WATER_TRAIL * WATER_TRAIL;
|
||||
// the parts of a glass in the storage: its drops and what the passes keep of them, its map (the axes the second part), its
|
||||
// cells and its paths, its header and its counts, (offset, size) in bytes
|
||||
inline std::array<std::pair<size_t, size_t>, 11> water_slot_parts(size_t const Slot)
|
||||
{
|
||||
auto const drops{static_cast<size_t>(WATER_DROPS)};
|
||||
auto const texels{static_cast<size_t>(WATER_MAP) * WATER_MAP};
|
||||
auto const cells{4 * static_cast<size_t>(WATER_GRID) * WATER_GRID * (1 + WATER_CELL_DROPS)};
|
||||
auto const trail{4 * static_cast<size_t>(WATER_TRAIL) * WATER_TRAIL};
|
||||
return {{{WATER_DROPS_OFFSET + 32 * drops * Slot, 32 * drops},
|
||||
{WATER_AXES_OFFSET + 32 * texels * Slot, 32 * texels},
|
||||
{WATER_BLADES_OFFSET + 16 * texels * Slot, 16 * texels},
|
||||
{WATER_STATE_OFFSET + 4 * drops * Slot, 4 * drops},
|
||||
{WATER_TARGET_OFFSET + 4 * drops * Slot, 4 * drops},
|
||||
{WATER_GATHER_OFFSET + 16 * drops * Slot, 16 * drops},
|
||||
{WATER_EXTRA_OFFSET + 16 * drops * Slot, 16 * drops},
|
||||
{WATER_CELLS_OFFSET + cells * Slot, cells},
|
||||
{WATER_TRAIL_OFFSET + trail * Slot, trail},
|
||||
{WATER_HEADER_OFFSET + 16 * Slot, 16},
|
||||
{WATER_COUNTS_OFFSET + 16 * Slot, 16}}};
|
||||
}
|
||||
|
||||
// layout std140: the step of the simulation of the water on the windscreens (GLSL water_ubo), every vector in the space of the
|
||||
// model of the cab
|
||||
struct water_ubs
|
||||
{
|
||||
// x: seconds of the step, y: seconds the simulation runs (the wetted paths are marked with it), z: how heavy the rain or
|
||||
// the snow is, 0 .. 1, w: 1 for snow
|
||||
glm::vec4 step;
|
||||
// xyz: up, w: the temperature of the air, degrees Celsius
|
||||
glm::vec4 up;
|
||||
// xyz: the air against the cab (the share of the wind the rain drifts with, less the velocity of the vehicle), m/s; w: how
|
||||
// fast the rain or the snow falls, m/s
|
||||
glm::vec4 air;
|
||||
// the blades of the wipers (the channels of their mask) now and at the step before: 0 at rest .. 1 at the far end of
|
||||
// their sweep, below 0 where there's none
|
||||
glm::vec4 blades;
|
||||
glm::vec4 blades_before;
|
||||
// the drops of the rain: x the smallest diameter which lands as a drop of its own, mm (the smaller ones are the mist of the
|
||||
// glass), y: 1 / the mean of the diameters above it, 1 / mm (they go as Marshall-Palmer), zw: unused
|
||||
glm::vec4 rain;
|
||||
// x: random seed of the step, y: the most landings tried on a glass in the step, z: the glass the passes for a single glass
|
||||
// work on (the baking of the mask of the wipers)
|
||||
glm::uvec4 counts;
|
||||
// every glass: x the landings tried in the step, y 1 / the most metres squared of the glass a unit of its uv holds (the
|
||||
// landings tried at the densest place are all taken), z 1 when it's simulated, w the size of its drops (the param
|
||||
// rain_scale of its material)
|
||||
glm::vec4 slots[WATER_SLOTS];
|
||||
};
|
||||
static_assert(sizeof(water_ubs) == 112 + 16 * WATER_SLOTS, "bad size of water_ubs");
|
||||
|
||||
// layout std430: a bucket of a scene cell queued in the pass (GLSL: instance_cull_job)
|
||||
struct instance_cull_job
|
||||
{
|
||||
|
||||
447
rendering/common/windscreenwater.cpp
Normal file
447
rendering/common/windscreenwater.cpp
Normal file
@@ -0,0 +1,447 @@
|
||||
/*
|
||||
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/common/windscreenwater.h"
|
||||
|
||||
#include "model/Model3d.h"
|
||||
#include "model/material.h"
|
||||
#include "rendering/renderer.h"
|
||||
#include "simulation/simulation.h"
|
||||
#include "simulation/simulationenvironment.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/Logs.h"
|
||||
#include "vehicle/DynObj.h"
|
||||
#include "vehicle/Train.h"
|
||||
|
||||
namespace gfx
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// the param of mat_rain_windscreen the slot of a glass goes to (water_slot: param[2].z) and the one of the size of its drops
|
||||
// (rain_scale: param[2].y)
|
||||
std::size_t constexpr SLOT_PARAM{2};
|
||||
// how fast the rain and the snow fall, m/s (as the particles of the rain and the snow, gfx::gpu_particles)
|
||||
float constexpr RAIN_FALL{7.f};
|
||||
float constexpr SNOW_FALL{1.1f};
|
||||
// the share of the wind the rain drifts with (as the particles of the rain and the snow)
|
||||
double constexpr WIND_SHARE{0.5};
|
||||
// the smallest raindrop landing as a drop of its own, mm: the smaller ones are the mist of the wet glass
|
||||
float constexpr LEAST_DROP{0.5f};
|
||||
// the water a flake of snow brings, mm3 on average (water_spawn.comp: 0.3 .. 2.5)
|
||||
float constexpr FLAKE_WATER{1.4f};
|
||||
// the most landings tried on a glass in a step
|
||||
std::uint32_t constexpr MOST_TRIES{1024};
|
||||
// the longest step, seconds: a longer frame slows the water down rather than let it jump
|
||||
double constexpr LONGEST_STEP{0.05};
|
||||
|
||||
// whether the material is a glass: one of mat_rain_windscreen, with a mask of the wipers
|
||||
bool is_glass(opengl_material const *Material)
|
||||
{
|
||||
return (Material != nullptr) && (Material->shader != nullptr) && (Material->shader->texture_conf.count("wipermask") > 0) && (Material->shader->param_conf.count("water_slot") > 0);
|
||||
}
|
||||
|
||||
// calls Visit for every submodel of the branch, its children and its siblings
|
||||
template <typename Visit> void visit_submodels(TSubModel *Submodel, Visit &&Visitor)
|
||||
{
|
||||
while (Submodel != nullptr)
|
||||
{
|
||||
Visitor(Submodel);
|
||||
visit_submodels(Submodel->ChildGet(), Visitor);
|
||||
Submodel = Submodel->NextGet();
|
||||
}
|
||||
}
|
||||
|
||||
// the vertices of a submodel, wherever they are kept
|
||||
gfx::vertex_array const &submodel_vertices(TSubModel const *Submodel)
|
||||
{
|
||||
return (Submodel->m_geometry.handle != null_handle ? GfxRenderer->Vertices(Submodel->m_geometry.handle) : Submodel->Vertices);
|
||||
}
|
||||
|
||||
// the transformation of a submodel to the space of the model
|
||||
glm::mat4 submodel_transform(TSubModel const *Submodel)
|
||||
{
|
||||
float4x4 matrix;
|
||||
Submodel->ParentMatrix(&matrix);
|
||||
return glm::make_mat4(matrix.readArray());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool windscreen_water::available()
|
||||
{
|
||||
if (m_state == 0)
|
||||
{
|
||||
m_state = (gpu_init() ? 1 : -1);
|
||||
if (m_state < 0)
|
||||
{
|
||||
WriteLog("windscreen water: the simulation can't be used, the windscreens make their water up");
|
||||
}
|
||||
}
|
||||
return m_state > 0;
|
||||
}
|
||||
|
||||
void windscreen_water::release()
|
||||
{
|
||||
assign(false);
|
||||
m_glasses = {};
|
||||
m_glasscount = 0;
|
||||
m_vehicle = nullptr;
|
||||
m_cab = nullptr;
|
||||
}
|
||||
|
||||
void windscreen_water::reset()
|
||||
{
|
||||
release();
|
||||
m_state = 0;
|
||||
}
|
||||
|
||||
void windscreen_water::assign(bool const Assigned)
|
||||
{
|
||||
if (false == bool(m_host.material))
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (std::size_t slot = 0; slot < m_glasscount; ++slot)
|
||||
{
|
||||
auto const &glass{m_glasses[slot]};
|
||||
if (auto *material{m_host.material(glass.material)}; is_glass(material))
|
||||
{
|
||||
material->params[SLOT_PARAM].z = (Assigned && glass.baked ? static_cast<float>(slot + 1) : 0.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void windscreen_water::rasterize(TSubModel *Submodel, material_handle const Material, glm::vec3 const &Inside, std::vector<glm::vec4> &Axes) const
|
||||
{
|
||||
if ((Submodel->m_material != Material) || (Submodel->eType != GL_TRIANGLES))
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto const &vertices{submodel_vertices(Submodel)};
|
||||
if (vertices.size() < 3)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto const transform{submodel_transform(Submodel)};
|
||||
gfx::index_array const *indices{nullptr};
|
||||
if ((Submodel->m_geometry.handle != null_handle) && (Submodel->m_geometry.index_count > 0))
|
||||
{
|
||||
indices = &GfxRenderer->Indices(Submodel->m_geometry.handle);
|
||||
}
|
||||
else if (false == Submodel->Indices.empty())
|
||||
{
|
||||
indices = &Submodel->Indices;
|
||||
}
|
||||
auto const count{indices != nullptr ? indices->size() : vertices.size()};
|
||||
auto const size{static_cast<int>(gl::WATER_MAP)};
|
||||
for (std::size_t first = 0; first + 2 < count; first += 3)
|
||||
{
|
||||
std::array<glm::vec3, 3> position;
|
||||
std::array<glm::vec2, 3> uv;
|
||||
bool valid{true};
|
||||
for (std::size_t corner = 0; corner < 3; ++corner)
|
||||
{
|
||||
auto const index{indices != nullptr ? static_cast<std::size_t>((*indices)[first + corner]) : first + corner};
|
||||
if (index >= vertices.size())
|
||||
{
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
position[corner] = glm::vec3{transform * glm::vec4{vertices[index].position, 1.f}};
|
||||
uv[corner] = vertices[index].texture;
|
||||
}
|
||||
if (false == valid)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// the steps along the glass for the steps of u and v: the edges of the triangle in both spaces
|
||||
glm::mat2 const uvedges{uv[1] - uv[0], uv[2] - uv[0]};
|
||||
auto const det{glm::determinant(uvedges)};
|
||||
if (std::abs(det) < 1e-12f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
auto const inverse{glm::inverse(uvedges)};
|
||||
auto const e1{position[1] - position[0]};
|
||||
auto const e2{position[2] - position[0]};
|
||||
// [tu tv] = [e1 e2] * inverse
|
||||
auto const tu{e1 * inverse[0][0] + e2 * inverse[0][1]};
|
||||
auto const tv{e1 * inverse[1][0] + e2 * inverse[1][1]};
|
||||
auto const centre{(position[0] + position[1] + position[2]) / 3.f};
|
||||
auto const out{glm::dot(glm::cross(tu, tv), centre - Inside) >= 0.f ? 1.f : -1.f};
|
||||
// a triangle laid on a repeat of the texture is taken to the first one (the mask of the wipers repeats with it)
|
||||
auto const shift{glm::floor((uv[0] + uv[1] + uv[2]) / 3.f)};
|
||||
for (auto &corner : uv)
|
||||
{
|
||||
corner -= shift;
|
||||
}
|
||||
auto const lower{glm::max(glm::ivec2{glm::floor(glm::min(glm::min(uv[0], uv[1]), uv[2]) * static_cast<float>(size))}, glm::ivec2{0})};
|
||||
auto const upper{glm::min(glm::ivec2{glm::ceil(glm::max(glm::max(uv[0], uv[1]), uv[2]) * static_cast<float>(size))}, glm::ivec2{size - 1})};
|
||||
for (int y = lower.y; y <= upper.y; ++y)
|
||||
{
|
||||
for (int x = lower.x; x <= upper.x; ++x)
|
||||
{
|
||||
// the middle of the texel inside the triangle (barycentric)
|
||||
auto const point{(glm::vec2{static_cast<float>(x), static_cast<float>(y)} + 0.5f) / static_cast<float>(size)};
|
||||
auto const weights{inverse * (point - uv[0])};
|
||||
if ((weights.x < -1e-4f) || (weights.y < -1e-4f) || (weights.x + weights.y > 1.f + 1e-4f))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
auto const texel{static_cast<std::size_t>(y) * gl::WATER_MAP + static_cast<std::size_t>(x)};
|
||||
Axes[texel * 2] = glm::vec4{tu, 1.f};
|
||||
Axes[texel * 2 + 1] = glm::vec4{tv, out};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void windscreen_water::build(TModel3d &Cab)
|
||||
{
|
||||
auto *root{Cab.GetSMRoot()};
|
||||
if ((root == nullptr) || (false == bool(m_host.material)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
// the materials of the glasses, in the order they come in the model; and a point inside the cab, the middle of the box
|
||||
// around the whole model (the glasses are around it)
|
||||
std::vector<material_handle> materials;
|
||||
glm::vec3 lower{std::numeric_limits<float>::max()};
|
||||
glm::vec3 upper{std::numeric_limits<float>::lowest()};
|
||||
visit_submodels(root, [&](TSubModel *Submodel) {
|
||||
auto const &vertices{submodel_vertices(Submodel)};
|
||||
if (false == vertices.empty())
|
||||
{
|
||||
auto const transform{submodel_transform(Submodel)};
|
||||
for (auto const &vertex : vertices)
|
||||
{
|
||||
auto const position{glm::vec3{transform * glm::vec4{vertex.position, 1.f}}};
|
||||
lower = glm::min(lower, position);
|
||||
upper = glm::max(upper, position);
|
||||
}
|
||||
}
|
||||
auto const material{Submodel->m_material};
|
||||
if ((material != null_handle) && (material > 0) && (Submodel->eType == GL_TRIANGLES) && (std::find(materials.begin(), materials.end(), material) == materials.end()) && is_glass(m_host.material(material)))
|
||||
{
|
||||
materials.push_back(material);
|
||||
}
|
||||
});
|
||||
if (materials.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (materials.size() > gl::WATER_SLOTS)
|
||||
{
|
||||
WriteLog("windscreen water: the cab has " + std::to_string(materials.size()) + " glasses, the water of the first " + std::to_string(gl::WATER_SLOTS) + " is simulated");
|
||||
materials.resize(gl::WATER_SLOTS);
|
||||
}
|
||||
auto const inside{(lower + upper) * 0.5f};
|
||||
auto const texels{static_cast<std::size_t>(gl::WATER_MAP) * gl::WATER_MAP};
|
||||
auto const size{static_cast<int>(gl::WATER_MAP)};
|
||||
for (auto const material : materials)
|
||||
{
|
||||
std::vector<glm::vec4> axes(texels * 2, glm::vec4{0.f});
|
||||
visit_submodels(root, [&](TSubModel *Submodel) { rasterize(Submodel, material, inside, axes); });
|
||||
auto &glass{m_glasses[m_glasscount]};
|
||||
glass = windscreen_water::glass{};
|
||||
glass.material = material;
|
||||
auto const *opengl{m_host.material(material)};
|
||||
glass.mask = opengl->textures[opengl->shader->texture_conf.at("wipermask").id];
|
||||
for (std::size_t texel = 0; texel < texels; ++texel)
|
||||
{
|
||||
if (axes[texel * 2].w > 0.f)
|
||||
{
|
||||
auto const density{glm::length(glm::cross(glm::vec3{axes[texel * 2]}, glm::vec3{axes[texel * 2 + 1]}))};
|
||||
glass.densest = std::max(glass.densest, density);
|
||||
glass.area += density / static_cast<float>(texels);
|
||||
}
|
||||
}
|
||||
if (glass.densest <= 0.f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// the edges spread out by two texels, so the filtering of the map near them takes the glass along
|
||||
for (int pass = 0; pass < 2; ++pass)
|
||||
{
|
||||
auto const source{axes};
|
||||
for (int y = 0; y < size; ++y)
|
||||
{
|
||||
for (int x = 0; x < size; ++x)
|
||||
{
|
||||
auto const texel{static_cast<std::size_t>(y * size + x)};
|
||||
if (source[texel * 2].w > 0.f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (int k = 0; k < 8; ++k)
|
||||
{
|
||||
static int const dx[]{1, -1, 0, 0, 1, 1, -1, -1};
|
||||
static int const dy[]{0, 0, 1, -1, 1, -1, 1, -1};
|
||||
auto const nx{x + dx[k]};
|
||||
auto const ny{y + dy[k]};
|
||||
if ((nx < 0) || (ny < 0) || (nx >= size) || (ny >= size))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
auto const neighbour{static_cast<std::size_t>(ny * size + nx)};
|
||||
if (source[neighbour * 2].w > 0.f)
|
||||
{
|
||||
axes[texel * 2] = glm::vec4{glm::vec3{source[neighbour * 2]}, 0.5f};
|
||||
axes[texel * 2 + 1] = source[neighbour * 2 + 1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
gpu_setup(m_glasscount, axes);
|
||||
WriteLog("windscreen water: glass " + std::to_string(m_glasscount + 1) + " of the cab, material " + opengl->GetName() + ", " + std::to_string(glass.area) + " m2");
|
||||
++m_glasscount;
|
||||
}
|
||||
}
|
||||
|
||||
void windscreen_water::update(double const Seconds)
|
||||
{
|
||||
if (false == available())
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto const *vehicle{simulation::Train != nullptr ? simulation::Train->Dynamic() : nullptr};
|
||||
auto *cab{vehicle != nullptr ? vehicle->mdKabina : nullptr};
|
||||
if ((vehicle != m_vehicle) || (cab != m_cab))
|
||||
{
|
||||
// another cab: its glasses dry
|
||||
release();
|
||||
m_vehicle = vehicle;
|
||||
m_cab = cab;
|
||||
m_bladesbefore = glm::vec4{-1.f};
|
||||
if (cab != nullptr)
|
||||
{
|
||||
build(*cab);
|
||||
}
|
||||
}
|
||||
if (m_glasscount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
gl::water_ubs block{};
|
||||
// the blades of the wipers of the vehicle, as the glasses read them (the channels of the mask follow the wipers of the cab
|
||||
// the vehicle is driven from)
|
||||
glm::vec4 blades{-1.f};
|
||||
if (auto const cabactive{vehicle->MoverParameters->CabActive}; cabactive != 0)
|
||||
{
|
||||
auto const count{static_cast<int>(vehicle->dWiperPos.size())};
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
if (i < count)
|
||||
{
|
||||
auto const index{cabactive > 0 ? i : count - 1 - i};
|
||||
blades[i] = static_cast<float>(std::clamp(vehicle->dWiperPos[index], 0.0, 1.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
// the space of the model of the cab, drawn with the matrix of the vehicle
|
||||
auto const toworld{glm::dmat3{vehicle->mMatrix}};
|
||||
auto const tomodel{glm::inverse(toworld)};
|
||||
auto const up{glm::normalize(glm::vec3{tomodel * glm::dvec3{0.0, 1.0, 0.0}})};
|
||||
auto const air{glm::vec3{tomodel * (glm::dvec3{simulation::Environment.wind()} * WIND_SHARE - vehicle->VectorFront() * vehicle->MoverParameters->V)}};
|
||||
auto const heavy{std::clamp(Global.Overcast - 1.f, 0.f, 1.f)};
|
||||
auto const snow{Global.Weather == "snow:"};
|
||||
auto const fall{snow ? SNOW_FALL : RAIN_FALL};
|
||||
|
||||
// the masks of the wipers baked into the maps once the textures are there
|
||||
for (std::size_t slot = 0; slot < m_glasscount; ++slot)
|
||||
{
|
||||
auto &glass{m_glasses[slot]};
|
||||
if (false == glass.baked)
|
||||
{
|
||||
block.counts = glm::uvec4{0u, 0u, static_cast<std::uint32_t>(slot), 0u};
|
||||
glass.baked = gpu_bake(slot, glass.mask, block);
|
||||
}
|
||||
}
|
||||
assign(true);
|
||||
|
||||
auto const seconds{std::clamp(Seconds, 0.0, LONGEST_STEP)};
|
||||
if (seconds <= 0.0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_time += seconds;
|
||||
++m_step;
|
||||
|
||||
// the drops landing: as many raindrops a second come to a square metre of the ground as the rain gives water (from light rain at
|
||||
// 1 mm/h to a downpour at 30 mm/h) in the drops of the Marshall-Palmer distribution above the least landing as a drop of its own
|
||||
// (exponential, the mean diameter falling with the rain); a glass gets them in the share it faces the rain coming at it
|
||||
// against the cab (water_spawn.comp)
|
||||
float perground{0.f}; // the drops coming to a square metre of the ground in a second
|
||||
auto const rate{1.f + 29.f * std::pow(heavy, 1.5f)}; // mm/h
|
||||
auto const lambda{4.1f * std::pow(rate, -0.21f)}; // 1 / mm
|
||||
if (heavy > 0.f)
|
||||
{
|
||||
auto const water{rate / 3600.f * 1e6f}; // mm3 a second on a square metre
|
||||
if (snow)
|
||||
{
|
||||
perground = water * 0.25f / FLAKE_WATER;
|
||||
}
|
||||
else
|
||||
{
|
||||
// the share of the water in the drops above the least one, and their mean volume
|
||||
auto const x{lambda * LEAST_DROP};
|
||||
auto const share{std::exp(-x) * (1.f + x + x * x / 2.f + x * x * x / 6.f)};
|
||||
auto const d0{LEAST_DROP};
|
||||
auto const mean{0.5236f * (d0 * d0 * d0 + 3.f * d0 * d0 / lambda + 6.f * d0 / (lambda * lambda) + 6.f / (lambda * lambda * lambda))};
|
||||
perground = water * share / mean;
|
||||
}
|
||||
}
|
||||
// the landings tried on a glass: as if the whole of its uv held the glass at its densest, the ones at the less dense places and
|
||||
// facing the rain less are left out. the share of a landing left over goes to the next step
|
||||
auto const coming{glm::length(air - up * fall)};
|
||||
std::uint32_t most{0};
|
||||
m_tries = 0;
|
||||
for (std::size_t slot = 0; slot < m_glasscount; ++slot)
|
||||
{
|
||||
auto &glass{m_glasses[slot]};
|
||||
auto const *material{m_host.material(glass.material)};
|
||||
auto const simulated{glass.baked && is_glass(material)};
|
||||
glass.landings += static_cast<double>(perground / fall * coming * glass.densest) * seconds;
|
||||
auto const whole{std::floor(glass.landings)};
|
||||
glass.landings -= whole;
|
||||
auto const tries{simulated ? static_cast<std::uint32_t>(std::min<double>(whole, MOST_TRIES)) : 0u};
|
||||
most = std::max(most, tries);
|
||||
m_tries += tries;
|
||||
auto const scale{(material != nullptr) && std::isfinite(material->params[SLOT_PARAM].y) ? std::max(material->params[SLOT_PARAM].y, 0.05f) : 1.f};
|
||||
block.slots[slot] = glm::vec4{static_cast<float>(tries), 1.f / glass.densest, simulated ? 1.f : 0.f, scale};
|
||||
}
|
||||
|
||||
block.step = glm::vec4{static_cast<float>(seconds), static_cast<float>(m_time), heavy, snow ? 1.f : 0.f};
|
||||
block.up = glm::vec4{up, Global.AirTemperature};
|
||||
block.air = glm::vec4{air, fall};
|
||||
block.blades = blades;
|
||||
block.blades_before = glm::vec4{
|
||||
m_bladesbefore.x < 0.f ? blades.x : m_bladesbefore.x, m_bladesbefore.y < 0.f ? blades.y : m_bladesbefore.y, m_bladesbefore.z < 0.f ? blades.z : m_bladesbefore.z, m_bladesbefore.w < 0.f ? blades.w : m_bladesbefore.w};
|
||||
block.rain = glm::vec4{LEAST_DROP, lambda, 1.f, 0.f};
|
||||
block.counts = glm::uvec4{m_step * 2654435761u, most, 0u, 0u};
|
||||
m_bladesbefore = blades;
|
||||
gpu_step(block, most);
|
||||
}
|
||||
|
||||
std::string windscreen_water::info() const
|
||||
{
|
||||
if ((m_state <= 0) || (m_glasscount == 0))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
return "windscreen water: " + std::to_string(m_glasscount) + " glasses, " + std::to_string(m_tries) + " drops landing tried in the frame\n";
|
||||
}
|
||||
|
||||
} // namespace gfx
|
||||
113
rendering/common/windscreenwater.h
Normal file
113
rendering/common/windscreenwater.h
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
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 <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "gl/ubo.h"
|
||||
#include "interfaces/ITexture.h"
|
||||
#include "utilities/Classes.h"
|
||||
|
||||
struct opengl_material;
|
||||
class TModel3d;
|
||||
class TSubModel;
|
||||
class TDynamicObject;
|
||||
|
||||
namespace gfx
|
||||
{
|
||||
|
||||
// the water on the windscreens of the cab of the train, simulated on the gpu, whichever api runs it.
|
||||
//
|
||||
// a glass is a material of mat_rain_windscreen (one with a mask of the wipers) in the model of the cab of the train, up to
|
||||
// gl::WATER_SLOTS of them; the material is told its slot in its param water_slot (1 + the slot, the drawing of the glass reads
|
||||
// the water of the slot where it's there, mat_rain_windscreen.frag). the water lies in the coordinates of the texture of the glass
|
||||
// (uv), as the mask does. the map of a glass is made here from the triangles of the model with its material: for the texels of
|
||||
// the uv on the glass, the step along the glass (metres, the space of the model of the cab) for a step of u and of v, and which
|
||||
// way is out of the cab; the passes of the gpu bake the mask of the wipers into it. once a frame the step of the simulation is
|
||||
// worked out here (the time, the rain or snow, gravity and the air against the cab in the space of its model, the blades) and
|
||||
// the passes run it (water_*.comp: the landing drops, the drops moving and carried by the blades, the grids, the drops flowing
|
||||
// together); what they keep and the glasses read is the storage of the water (water.glsl, gl::WATER_*_OFFSET)
|
||||
class windscreen_water
|
||||
{
|
||||
public:
|
||||
// what the renderer gives: the materials of its manager, which get their slots
|
||||
struct host
|
||||
{
|
||||
std::function<opengl_material *(material_handle)> material;
|
||||
};
|
||||
|
||||
windscreen_water() = default;
|
||||
virtual ~windscreen_water() = default;
|
||||
windscreen_water(windscreen_water const &) = delete;
|
||||
windscreen_water &operator=(windscreen_water const &) = delete;
|
||||
|
||||
void set_host(host Host)
|
||||
{
|
||||
m_host = std::move(Host);
|
||||
}
|
||||
// whether the passes could be made
|
||||
bool available();
|
||||
// once a frame, before the colour passes: the cab of the train followed, the step of the simulation run. Seconds: of the
|
||||
// simulation since the frame before (0 when it's paused)
|
||||
void update(double Seconds);
|
||||
// the materials let go of their slots, the glasses forgotten (the storage stays as it is, unused)
|
||||
void release();
|
||||
std::string info() const;
|
||||
|
||||
protected:
|
||||
// the glasses forgotten and the programs taken as not made yet (the renderer let go of them), made again when next needed
|
||||
void reset();
|
||||
// the programs and the storage; false: the simulation can't be used, the glasses make their water up
|
||||
virtual bool gpu_init() = 0;
|
||||
// the slot set up for a glass: its drops, its grid and its paths cleared, its header saying it isn't simulated (until the
|
||||
// first step), the axes of its map (two vec4 a texel, gl::WATER_MAP x WATER_MAP texels, see water.glsl) put in the storage
|
||||
virtual void gpu_setup(std::size_t Slot, std::vector<glm::vec4> const &Axes) = 0;
|
||||
// the mask of the wipers of a glass (a texture of the renderer) baked into its map; false when the texture isn't ready yet
|
||||
// (tried again with the next frame)
|
||||
virtual bool gpu_bake(std::size_t Slot, texture_handle Mask, gl::water_ubs const &Block) = 0;
|
||||
// the step of the simulation of the glasses Block says are simulated; Tries: the most landings tried on a glass
|
||||
virtual void gpu_step(gl::water_ubs const &Block, std::uint32_t Tries) = 0;
|
||||
|
||||
private:
|
||||
struct glass
|
||||
{
|
||||
material_handle material{null_handle};
|
||||
texture_handle mask{null_handle};
|
||||
float densest{0.f}; // the most metres squared a unit of the uv holds
|
||||
float area{0.f}; // metres squared of the glass
|
||||
double landings{0.0}; // the share of a landing left over from the steps before
|
||||
bool baked{false};
|
||||
};
|
||||
|
||||
// the glasses of the model of a cab found and set up
|
||||
void build(TModel3d &Cab);
|
||||
// the triangles of a glass laid into its map; Inside: a point inside the cab
|
||||
void rasterize(TSubModel *Submodel, material_handle Material, glm::vec3 const &Inside, std::vector<glm::vec4> &Axes) const;
|
||||
// the slots given to the materials of the glasses (every frame: a material made anew takes its default params)
|
||||
void assign(bool Assigned);
|
||||
|
||||
host m_host;
|
||||
int m_state{0}; // 0: not set up yet, 1: ready, -1: can't be used
|
||||
TDynamicObject const *m_vehicle{nullptr};
|
||||
TModel3d const *m_cab{nullptr};
|
||||
std::array<glass, gl::WATER_SLOTS> m_glasses{};
|
||||
std::size_t m_glasscount{0};
|
||||
glm::vec4 m_bladesbefore{-1.f};
|
||||
double m_time{0.0}; // seconds the simulation runs
|
||||
std::uint32_t m_step{0};
|
||||
std::uint32_t m_tries{0}; // landings tried in the last step, all glasses
|
||||
};
|
||||
|
||||
} // namespace gfx
|
||||
@@ -86,6 +86,7 @@ bool opengl46_renderer::Init(GLFWwindow *Window)
|
||||
m_window = Window;
|
||||
|
||||
gl::glsl_common_setup();
|
||||
m_windscreenwater.set_host({[this](material_handle const Material) { return &m_materials.material(Material); }});
|
||||
|
||||
if (true == Global.ScaleSpecularValues)
|
||||
{
|
||||
@@ -1086,7 +1087,8 @@ void opengl46_renderer::SwapBuffers()
|
||||
+ ( Global.gfx_lights_clustered ? "clustered" : "first " + std::to_string( std::max( Global.DynamicLightCount, 0 ) ) )
|
||||
+ "\n" + m_locallights.info()
|
||||
+ m_localshadows.info()
|
||||
+ m_gpuparticles.info();
|
||||
+ m_gpuparticles.info()
|
||||
+ m_windscreenwater.info();
|
||||
if( auto const cullstats = ( m_viewports.empty() || !( Global.gfx_lights_clustered || Global.gfx_lights_clusterheatmap ) ? std::nullopt : m_viewports.front()->clusters->stats() ) ) {
|
||||
statstext +=
|
||||
"\ncluster lists: " + std::to_string( cullstats->index_count )
|
||||
@@ -1599,6 +1601,16 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode)
|
||||
float percipitation_intensity = glm::saturate(Global.Overcast - 1.);
|
||||
scene_ubs.rain_params.x = percipitation_intensity; // % amount of droplets
|
||||
scene_ubs.rain_params.y = glm::mix(15., 1., percipitation_intensity); // Regeneration time
|
||||
scene_ubs.rain_params.z = (Global.Weather == "snow:" ? 1.f : 0.f); // flakes on the glass in place of the drops
|
||||
scene_ubs.rain_params.w = Global.AirTemperature; // how fast the flakes melt
|
||||
{
|
||||
// the air the drops on the glass are blown with: the wind as the particles drift with it, less the velocity of the
|
||||
// vehicle (V is along the front of the vehicle)
|
||||
glm::dvec3 air{glm::dvec3{simulation::Environment.wind()} * 0.5};
|
||||
if (TDynamicObject const *owner = Global.pCamera.m_owner)
|
||||
air -= owner->VectorFront() * owner->MoverParameters->V;
|
||||
scene_ubs.rain_wind = glm::vec4{glm::vec3{air}, 0.f};
|
||||
}
|
||||
if (TDynamicObject const *owner = Global.pCamera.m_owner; owner && !!owner->MoverParameters->CabActive)
|
||||
{
|
||||
for (int i = 0; i < 4; ++i)
|
||||
@@ -1643,6 +1655,10 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode)
|
||||
scene_ubs.inv_view = glm::inverse( glm::mat4{ glm::mat3{ m_colorpass.pass_camera.modelview() } } );
|
||||
scene_ubo->update(scene_ubs);
|
||||
scene_ubo->bind_uniform();
|
||||
// the water on the windscreens of the cab: a step of its simulation once a frame, its storage for the glasses of every view
|
||||
if (vp.main)
|
||||
m_windscreenwater.update(Timer::GetDeltaTime());
|
||||
m_windscreenwater.bind();
|
||||
|
||||
if (Global.bWireFrame)
|
||||
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
|
||||
|
||||
@@ -14,6 +14,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "rendering/opengl46/opengl46light.h"
|
||||
#include "rendering/opengl46/opengl46particles.h"
|
||||
#include "rendering/opengl46/opengl46gpuparticles.h"
|
||||
#include "rendering/opengl46/opengl46water.h"
|
||||
#include "rendering/opengl46/opengl46skydome.h"
|
||||
#include "rendering/opengl46/opengl46precipitation.h"
|
||||
#include "simulation/simulationenvironment.h"
|
||||
@@ -533,6 +534,7 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
|
||||
opengl46_precipitation m_precipitationrenderer;
|
||||
opengl46_particles m_particlerenderer; // particle visualization subsystem
|
||||
opengl46_gpu_particles m_gpuparticles; // the smoke, rain and snow of the gpu (gfx.smoke.gpu, gfx.precipitation.gpu)
|
||||
opengl46_windscreen_water m_windscreenwater; // the water on the windscreens of the cab of the train
|
||||
bool m_particlesoft{false}; // the depth for the soft particles is bound in the colour pass being drawn
|
||||
|
||||
float m_framerate;
|
||||
|
||||
116
rendering/opengl46/opengl46water.cpp
Normal file
116
rendering/opengl46/opengl46water.cpp
Normal file
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
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/opengl46water.h"
|
||||
|
||||
#include "rendering/renderer.h"
|
||||
#include "utilities/Logs.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
GLuint groups(std::size_t const Count)
|
||||
{
|
||||
return static_cast<GLuint>((Count + gl::WATER_GROUP_SIZE - 1) / gl::WATER_GROUP_SIZE);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool opengl46_windscreen_water::gpu_init()
|
||||
{
|
||||
try
|
||||
{
|
||||
std::array<char const *, pass::count> const names{"water_trail.comp", "water_spawn.comp", "water_move.comp", "water_clear.comp", "water_grid.comp",
|
||||
"water_merge.comp", "water_gather.comp", "water_settle.comp", "water_bake.comp"};
|
||||
for (std::size_t i = 0; i < names.size(); ++i)
|
||||
{
|
||||
gl::shader shader(names[i]);
|
||||
m_programs[i] = std::make_unique<gl::program>(std::vector<std::reference_wrapper<const gl::shader>>{shader});
|
||||
}
|
||||
}
|
||||
catch (gl::shader_exception const &e)
|
||||
{
|
||||
ErrorLog("windscreen water: can't use the programs of the simulation: " + std::string(e.what()));
|
||||
return false;
|
||||
}
|
||||
m_storage = std::make_unique<gl::ssbo>(static_cast<GLsizeiptr>(gl::WATER_STORAGE_SIZE), gl::WATER_SSBO_BINDING);
|
||||
::glClearNamedBufferData(*m_storage, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, nullptr);
|
||||
m_block = std::make_unique<gl::ubo>(sizeof(gl::water_ubs), gl::WATER_UBO_BINDING);
|
||||
return true;
|
||||
}
|
||||
|
||||
void opengl46_windscreen_water::bind() const
|
||||
{
|
||||
if (m_storage)
|
||||
m_storage->bind_base();
|
||||
}
|
||||
|
||||
void opengl46_windscreen_water::gpu_setup(std::size_t const Slot, std::vector<glm::vec4> const &Axes)
|
||||
{
|
||||
if (!m_storage)
|
||||
return;
|
||||
// the frames before may still draw with the storage
|
||||
::glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT);
|
||||
// the drops, the grid, the paths, the header and the counts of the slot cleared, the axes of the map put in
|
||||
for (auto const &part : gl::water_slot_parts(Slot))
|
||||
::glClearNamedBufferSubData(*m_storage, GL_R32UI, static_cast<GLintptr>(part.first), static_cast<GLsizeiptr>(part.second), GL_RED_INTEGER, GL_UNSIGNED_INT, nullptr);
|
||||
auto const axes{gl::water_slot_parts(Slot)[1]};
|
||||
m_storage->update(Axes.data(), static_cast<GLintptr>(axes.first), static_cast<GLsizeiptr>(std::min(axes.second, Axes.size() * sizeof(glm::vec4))));
|
||||
::glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT);
|
||||
}
|
||||
|
||||
bool opengl46_windscreen_water::gpu_bake(std::size_t const Slot, texture_handle const Mask, gl::water_ubs const &Block)
|
||||
{
|
||||
if ((!m_storage) || (Mask == null_handle))
|
||||
return false;
|
||||
auto &texture{GfxRenderer->Texture(Mask)};
|
||||
if ((false == texture.create()) || (false == texture.get_is_ready()) || (texture.get_id() == 0))
|
||||
return false;
|
||||
m_storage->bind_base();
|
||||
m_block->update(Block);
|
||||
m_block->bind_uniform();
|
||||
::glBindTextureUnit(static_cast<GLuint>(gl::WATER_MASK_TEX), static_cast<GLuint>(texture.get_id()));
|
||||
m_programs[pass::bake]->dispatch(groups(static_cast<std::size_t>(gl::WATER_MAP) * gl::WATER_MAP));
|
||||
::glBindTextureUnit(static_cast<GLuint>(gl::WATER_MASK_TEX), 0);
|
||||
::glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
gl::program::unbind();
|
||||
return true;
|
||||
}
|
||||
|
||||
void opengl46_windscreen_water::gpu_step(gl::water_ubs const &Block, std::uint32_t const Tries)
|
||||
{
|
||||
if (!m_storage)
|
||||
return;
|
||||
m_storage->bind_base();
|
||||
m_block->update(Block);
|
||||
m_block->bind_uniform();
|
||||
auto const slots{static_cast<GLuint>(gl::WATER_SLOTS)};
|
||||
auto const drops{groups(gl::WATER_DROPS)};
|
||||
auto const cells{groups(static_cast<std::size_t>(gl::WATER_GRID) * gl::WATER_GRID)};
|
||||
auto const run = [&](pass const Pass, GLuint const Groups) {
|
||||
if (Groups == 0)
|
||||
return;
|
||||
m_programs[Pass]->dispatch(Groups, slots);
|
||||
::glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
};
|
||||
// the wetted paths the blades went over in the step wiped
|
||||
if (Block.blades != Block.blades_before)
|
||||
run(pass::trail, groups(static_cast<std::size_t>(gl::WATER_TRAIL) * gl::WATER_TRAIL));
|
||||
run(pass::spawn, groups(Tries));
|
||||
run(pass::move, drops);
|
||||
run(pass::clear, cells);
|
||||
run(pass::grid, drops);
|
||||
run(pass::merge, drops);
|
||||
run(pass::gather, drops);
|
||||
run(pass::settle, drops);
|
||||
run(pass::clear, cells);
|
||||
run(pass::grid, drops);
|
||||
gl::program::unbind();
|
||||
}
|
||||
55
rendering/opengl46/opengl46water.h
Normal file
55
rendering/opengl46/opengl46water.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
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 "rendering/common/windscreenwater.h"
|
||||
#include "gl/shader.h"
|
||||
#include "gl/ssbo.h"
|
||||
#include "gl/ubo.h"
|
||||
|
||||
// the water on the windscreens for the opengl 4.6 renderer (gfx::windscreen_water): the storage of the water, the block of the
|
||||
// step and the compute programs
|
||||
class opengl46_windscreen_water : public gfx::windscreen_water
|
||||
{
|
||||
public:
|
||||
opengl46_windscreen_water() = default;
|
||||
~opengl46_windscreen_water() override = default;
|
||||
|
||||
// the storage bound to its point for the glasses drawn next (the bindings are kept by every context of its own)
|
||||
void bind() const;
|
||||
|
||||
protected:
|
||||
bool gpu_init() override;
|
||||
void gpu_setup(std::size_t Slot, std::vector<glm::vec4> const &Axes) override;
|
||||
bool gpu_bake(std::size_t Slot, texture_handle Mask, gl::water_ubs const &Block) override;
|
||||
void gpu_step(gl::water_ubs const &Block, std::uint32_t Tries) override;
|
||||
|
||||
private:
|
||||
// the passes of the step, in their order
|
||||
enum pass
|
||||
{
|
||||
trail,
|
||||
spawn,
|
||||
move,
|
||||
clear,
|
||||
grid,
|
||||
merge,
|
||||
gather,
|
||||
settle,
|
||||
bake,
|
||||
count
|
||||
};
|
||||
std::array<std::unique_ptr<gl::program>, pass::count> m_programs;
|
||||
std::unique_ptr<gl::ssbo> m_storage;
|
||||
std::unique_ptr<gl::ubo> m_block;
|
||||
};
|
||||
@@ -71,7 +71,9 @@ constexpr std::uint32_t HEADLIGHT_SHADOW_TEX = 23;
|
||||
constexpr std::uint32_t SCENE_DEPTH_TEX = 24;
|
||||
// the image of the last frame of the main view with its mipmaps, which the screen space reflections take their colour from
|
||||
constexpr std::uint32_t SSR_HISTORY_TEX = 25;
|
||||
constexpr std::uint32_t PASS_BINDING_COUNT = 26;
|
||||
// the water on the windscreens (gfx::windscreen_water): the drops, their grids and the wetted paths the glasses read
|
||||
constexpr std::uint32_t WATER_SSBO = 26;
|
||||
constexpr std::uint32_t PASS_BINDING_COUNT = 27;
|
||||
// the offsets of the bindings above with dynamic offsets, in the order of the bindings
|
||||
constexpr std::uint32_t PASS_DYNAMIC_OFFSETS = 6;
|
||||
// the textures of a material take the bindings of their units (#pragma texture(name, unit, ...)), 0 .. MAX_TEXTURES - 1
|
||||
|
||||
@@ -31,6 +31,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "rendering/vulkan14/vulkan14terrain.h"
|
||||
#include "rendering/vulkan14/vulkan14grass.h"
|
||||
#include "rendering/vulkan14/vulkan14particles.h"
|
||||
#include "rendering/vulkan14/vulkan14water.h"
|
||||
#include "rendering/vulkan14/vulkan14commands.h"
|
||||
#include "rendering/vulkan14/vulkan14raytracing.h"
|
||||
#include "rendering/vulkan14/vulkan14dlss.h"
|
||||
@@ -397,6 +398,8 @@ struct scene_state
|
||||
bool grass_tried{false};
|
||||
// the smoke and the rain or snow of the gpu
|
||||
gpu_particles particles;
|
||||
// the water on the windscreens of the cab of the train; its storage is held by the pass sets (WATER_SSBO)
|
||||
windscreen_water water;
|
||||
// the ray traced reflections (gfx.raytracing), on a device with ray queries; its set is set RT_SET of the pipelines of the scene
|
||||
std::unique_ptr<ray_tracing> raytracing;
|
||||
// the heatmap of the light clusters (gfx.lights.clusterheatmap), made when it's first drawn
|
||||
|
||||
@@ -168,7 +168,7 @@ std::string vulkan14_renderer::scene_info()
|
||||
line(" shapes: ", colour.shapes, shadowstats.shapes) + " traction: " + to_string(colour.traction, 7) + "\n" + " lines: " + to_string(colour.lines, 7) + "\n" +
|
||||
"particles: " + to_string(colour.particles, 7) + "\nlights: " + to_string(static_cast<int>(m_lights_lit), 7) + " (" + std::to_string(m_lights_dropped) + " over capacity), " +
|
||||
(Global.gfx_lights_clustered ? "clustered" : "first " + std::to_string(std::max(Global.DynamicLightCount, 0))) + "\n" + m_locallights.info() + scene.lamps.info() +
|
||||
scene.particles.info();
|
||||
scene.particles.info() + scene.water.info();
|
||||
auto const &stats{scene.clusters.stats};
|
||||
if (stats.valid && (Global.gfx_lights_clustered || Global.gfx_lights_clusterheatmap))
|
||||
{
|
||||
|
||||
@@ -990,6 +990,8 @@ void vulkan14_renderer::render_scene()
|
||||
{
|
||||
auto &ctx{*m_context};
|
||||
auto &scene{ctx.scene};
|
||||
// the water on the windscreens of the cab: a step of its simulation once a frame, before the passes which draw the glasses
|
||||
scene.water.update(Timer::GetDeltaTime());
|
||||
if (scene.targets.size() < m_viewports.size())
|
||||
{
|
||||
scene.targets.resize(m_viewports.size());
|
||||
@@ -1406,6 +1408,16 @@ void vulkan14_renderer::render_viewport(std::size_t const Index)
|
||||
float percipitation_intensity = glm::saturate(Global.Overcast - 1.);
|
||||
scene_ubs.rain_params.x = percipitation_intensity;
|
||||
scene_ubs.rain_params.y = glm::mix(15., 1., percipitation_intensity);
|
||||
scene_ubs.rain_params.z = (Global.Weather == "snow:" ? 1.f : 0.f); // flakes on the glass in place of the drops
|
||||
scene_ubs.rain_params.w = Global.AirTemperature; // how fast the flakes melt
|
||||
{
|
||||
// the air the drops on the glass are blown with: the wind as the particles drift with it, less the velocity of the
|
||||
// vehicle (V is along the front of the vehicle)
|
||||
glm::dvec3 air{glm::dvec3{simulation::Environment.wind()} * 0.5};
|
||||
if (TDynamicObject const *owner = Global.pCamera.m_owner)
|
||||
air -= owner->VectorFront() * owner->MoverParameters->V;
|
||||
scene_ubs.rain_wind = glm::vec4{glm::vec3{air}, 0.f};
|
||||
}
|
||||
if (TDynamicObject const *owner = Global.pCamera.m_owner; owner && !!owner->MoverParameters->CabActive)
|
||||
{
|
||||
for (int i = 0; i < 4; ++i)
|
||||
|
||||
@@ -150,6 +150,7 @@ VkDescriptorType pass_binding_type(std::uint32_t const Binding)
|
||||
case CLUSTER_RANGE_SSBO:
|
||||
case CLUSTER_INDEX_SSBO:
|
||||
case SKY_SSBO:
|
||||
case WATER_SSBO:
|
||||
return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
||||
case ATMOSPHERE_UBO:
|
||||
case WEATHER_HORIZON_UBO:
|
||||
@@ -708,6 +709,40 @@ bool vulkan14_renderer::init_scene(std::string &Reason)
|
||||
scene.particles.init(device, ctx.pipelines, ctx.shaders, scene.pass_layout, ctx.memory, ctx.uploads, ctx.deletions, ctx.ring, ctx.device.caps().limits.minUniformBufferOffsetAlignment, frames,
|
||||
std::move(host));
|
||||
}
|
||||
{
|
||||
vk14::windscreen_water::host host;
|
||||
host.commands = [this]() {
|
||||
suspend_rendering();
|
||||
return m_context->commands;
|
||||
};
|
||||
host.texture = [this](texture_handle const Texture, VkImageView &View, VkSampler &Sampler) {
|
||||
auto &ctx{*m_context};
|
||||
if ((Texture == null_handle) || (false == bool(ctx.textures)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto &source{ctx.textures->mark_as_used(Texture)};
|
||||
if (false == source.create())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto const *entry{ctx.images.find(source.get_id())};
|
||||
if (entry == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
View = entry->target->view();
|
||||
Sampler = entry->sampler;
|
||||
return true;
|
||||
};
|
||||
if (false == scene.water.init(device, ctx.pipelines, ctx.shaders, scene.pass_layout, ctx.memory, ctx.uploads, ctx.deletions, ctx.ring, ctx.device.caps().limits.minUniformBufferOffsetAlignment,
|
||||
std::move(host)))
|
||||
{
|
||||
Reason = "no storage of the water on the windscreens";
|
||||
return false;
|
||||
}
|
||||
scene.water.set_host({[this](material_handle const Material) { return &m_materials.material(Material); }});
|
||||
}
|
||||
scene.terrain->init(ctx.resources, [this](vk14::command_stream *const Commands, VkImageView const Target, VkFormat const Format, VkExtent2D const Extent, VkImageView const Source, VkSampler const Sampler) {
|
||||
auto &scene{m_context->scene};
|
||||
auto const pipeline{post_pipeline("copy.frag", static_cast<int>(Format))};
|
||||
@@ -779,6 +814,7 @@ void vulkan14_renderer::destroy_scene()
|
||||
scene.terrain.reset();
|
||||
scene.grass.destroy();
|
||||
scene.particles.destroy();
|
||||
scene.water.destroy();
|
||||
scene.grass_program.reset();
|
||||
scene.grass_tried = false;
|
||||
scene.cluster_heatmap.reset();
|
||||
@@ -931,6 +967,7 @@ void vulkan14_renderer::write_pass_sets(vk14::instance_cull *Cull)
|
||||
VkDescriptorBufferInfo const atmosphereblock{scene.sky.atmosphere.handle(), 0, sizeof(gl::atmosphere_ubs)};
|
||||
VkDescriptorBufferInfo const weatherblock{ctx.ring.handle(), 0, offsetof(gl::weather_ubs, fog_horizon)};
|
||||
VkDescriptorBufferInfo const horizonblock{scene.weather_horizon.handle(), 0, sizeof(glm::vec4) * gl::FOG_HORIZON_SAMPLES};
|
||||
VkDescriptorBufferInfo const water{scene.water.storage(), 0, VK_WHOLE_SIZE};
|
||||
VkDescriptorImageInfo const headlightsinfo{scene.headlights_sampler, scene.headlights_view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
|
||||
std::vector<VkWriteDescriptorSet> writes;
|
||||
std::vector<std::unique_ptr<VkDescriptorImageInfo>> images;
|
||||
@@ -964,6 +1001,7 @@ void vulkan14_renderer::write_pass_sets(vk14::instance_cull *Cull)
|
||||
write(handle, ATMOSPHERE_UBO, &atmosphereblock, nullptr);
|
||||
write(handle, WEATHER_UBO, &weatherblock, nullptr);
|
||||
write(handle, WEATHER_HORIZON_UBO, &horizonblock, nullptr);
|
||||
write(handle, WATER_SSBO, &water, nullptr);
|
||||
write(handle, HEADLIGHT_TEX, nullptr, &headlightsinfo);
|
||||
write(handle, SHADOW_TEX, nullptr, image(ctx.sampling.shadow, views[SHADOW_TEX]));
|
||||
write(handle, ENV_TEX, nullptr, image(ctx.sampling.clamp_linear, views[ENV_TEX]));
|
||||
|
||||
@@ -802,6 +802,7 @@ std::string binding_preamble(bool const Raytracing)
|
||||
define("HEADLIGHT_SHADOW_TEX", HEADLIGHT_SHADOW_TEX);
|
||||
define("SCENE_DEPTH_TEX", SCENE_DEPTH_TEX);
|
||||
define("SSR_HISTORY_TEX", SSR_HISTORY_TEX);
|
||||
define("WATER_SSBO", WATER_SSBO);
|
||||
define("PASS_BINDING_COUNT", PASS_BINDING_COUNT);
|
||||
define("MATERIAL_TEXTURE_FIRST", MATERIAL_TEXTURE_FIRST);
|
||||
define("CL_CLUSTER_UBO", CL_CLUSTER_UBO);
|
||||
@@ -865,6 +866,13 @@ std::string binding_preamble(bool const Raytracing)
|
||||
define("SHADOW_FIT_GROUP_SIZE", gl::SHADOW_FIT_GROUP_SIZE);
|
||||
preamble += "#define LOCAL_SHADOW_NEAR " + std::to_string(gfx::local_shadows::shadow_near) + "\n";
|
||||
define("PARTICLE_GROUP_SIZE", gl::PARTICLE_GROUP_SIZE);
|
||||
define("WATER_SLOTS", gl::WATER_SLOTS);
|
||||
define("WATER_DROPS", gl::WATER_DROPS);
|
||||
define("WATER_GRID", gl::WATER_GRID);
|
||||
define("WATER_CELL_DROPS", gl::WATER_CELL_DROPS);
|
||||
define("WATER_TRAIL", gl::WATER_TRAIL);
|
||||
define("WATER_MAP", gl::WATER_MAP);
|
||||
define("WATER_GROUP_SIZE", gl::WATER_GROUP_SIZE);
|
||||
return preamble;
|
||||
}
|
||||
|
||||
|
||||
244
rendering/vulkan14/vulkan14water.cpp
Normal file
244
rendering/vulkan14/vulkan14water.cpp
Normal file
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
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/vulkan14/vulkan14water.h"
|
||||
#include "rendering/vulkan14/vulkan14commands.h"
|
||||
#include "utilities/Logs.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace vk14
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
std::uint32_t groups(std::size_t const Count)
|
||||
{
|
||||
return static_cast<std::uint32_t>((Count + gl::WATER_GROUP_SIZE - 1) / gl::WATER_GROUP_SIZE);
|
||||
}
|
||||
|
||||
VkBufferUsageFlags constexpr storage_usage{VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT};
|
||||
|
||||
} // namespace
|
||||
|
||||
windscreen_water::~windscreen_water()
|
||||
{
|
||||
// the materials may be gone already
|
||||
set_host({});
|
||||
destroy();
|
||||
}
|
||||
|
||||
bool windscreen_water::init(VkDevice const Device, pipeline_library &Pipelines, shader_library &Shaders, VkDescriptorSetLayout const Passlayout, allocator &Memory, uploader &Uploads,
|
||||
deletion_queue &Deletions, frame_ring &Ring, VkDeviceSize const Alignment, host Host)
|
||||
{
|
||||
m_device = Device;
|
||||
m_pipelines = &Pipelines;
|
||||
m_shaders = &Shaders;
|
||||
m_passlayout = Passlayout;
|
||||
m_memory = &Memory;
|
||||
m_uploads = &Uploads;
|
||||
m_deletions = &Deletions;
|
||||
m_ring = &Ring;
|
||||
m_alignment = std::max<VkDeviceSize>(Alignment, 16);
|
||||
m_host = std::move(Host);
|
||||
m_storage = std::make_shared<buffer>();
|
||||
m_full = m_storage->create(Memory, static_cast<VkDeviceSize>(gl::WATER_STORAGE_SIZE), storage_usage, memory_kind::device, "windscreen water");
|
||||
if (false == m_full)
|
||||
{
|
||||
// the pass sets need a buffer to hold: a small one, which no glass reads (none gets a slot)
|
||||
ErrorLog("windscreen water: no storage of " + std::to_string(gl::WATER_STORAGE_SIZE / (1024 * 1024)) + " MiB, the windscreens make their water up");
|
||||
m_storage = std::make_shared<buffer>();
|
||||
if (false == m_storage->create(Memory, 65536, storage_usage, memory_kind::device, "windscreen water"))
|
||||
{
|
||||
m_storage.reset();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void windscreen_water::destroy()
|
||||
{
|
||||
if (m_device == VK_NULL_HANDLE)
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (auto &kernel : m_kernels)
|
||||
{
|
||||
kernel.destroy(m_device);
|
||||
}
|
||||
m_storage.reset();
|
||||
m_device = VK_NULL_HANDLE;
|
||||
m_cleared = false;
|
||||
reset();
|
||||
}
|
||||
|
||||
bool windscreen_water::gpu_init()
|
||||
{
|
||||
if ((m_device == VK_NULL_HANDLE) || (false == m_full) || (false == bool(m_host.commands)) || (false == bool(m_host.texture)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
std::array<char const *, pass::count> const names{"water_trail.comp", "water_spawn.comp", "water_move.comp", "water_clear.comp", "water_grid.comp",
|
||||
"water_merge.comp", "water_gather.comp", "water_settle.comp", "water_bake.comp"};
|
||||
for (std::size_t i = 0; i < names.size(); ++i)
|
||||
{
|
||||
if (false == m_kernels[i].create(m_device, *m_pipelines, m_shaders->get(names[i]), m_passlayout, {}, names[i]))
|
||||
{
|
||||
throw gfx::shader_exception(std::string("no compute pipeline of ") + names[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (gfx::shader_exception const &e)
|
||||
{
|
||||
ErrorLog("windscreen water: can't use the programs of the simulation: " + std::string(e.what()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void windscreen_water::begin(command_stream *const Commands)
|
||||
{
|
||||
// the glasses of the frames before read the storage, the fills and the copies write it
|
||||
memory_barrier(Commands, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_STORAGE_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT,
|
||||
VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_2_CLEAR_BIT, VK_ACCESS_2_SHADER_STORAGE_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT);
|
||||
if (false == m_cleared)
|
||||
{
|
||||
// nothing in the storage yet
|
||||
m_cleared = true;
|
||||
Commands->fill_buffer(m_storage->handle(), 0, VK_WHOLE_SIZE, 0);
|
||||
compute_barrier(Commands);
|
||||
}
|
||||
}
|
||||
|
||||
void windscreen_water::gpu_setup(std::size_t const Slot, std::vector<glm::vec4> const &Axes)
|
||||
{
|
||||
auto const commands{m_host.commands()};
|
||||
if ((commands == nullptr) || (false == m_full))
|
||||
{
|
||||
return;
|
||||
}
|
||||
begin(commands);
|
||||
// the drops, the grid, the paths, the header and the counts of the slot cleared with the commands of the frame
|
||||
auto const parts{gl::water_slot_parts(Slot)};
|
||||
for (std::size_t part = 0; part < parts.size(); ++part)
|
||||
{
|
||||
if (part != 1)
|
||||
{
|
||||
commands->fill_buffer(m_storage->handle(), static_cast<VkDeviceSize>(parts[part].first), static_cast<VkDeviceSize>(parts[part].second), 0);
|
||||
}
|
||||
}
|
||||
// the axes of the map copied after that, from a staging buffer kept until the gpu is done with the frame
|
||||
auto const size{std::min<VkDeviceSize>(static_cast<VkDeviceSize>(parts[1].second), static_cast<VkDeviceSize>(Axes.size() * sizeof(glm::vec4)))};
|
||||
auto staging{std::make_shared<buffer>()};
|
||||
if ((false == staging->create(*m_memory, size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, memory_kind::upload, "windscreen water map")) || (staging->mapped() == nullptr))
|
||||
{
|
||||
ErrorLog("windscreen water: no staging buffer for the map of a glass");
|
||||
compute_barrier(commands);
|
||||
return;
|
||||
}
|
||||
std::memcpy(staging->mapped(), Axes.data(), static_cast<std::size_t>(size));
|
||||
staging->flush(0, size);
|
||||
VkBufferCopy2 region{};
|
||||
region.sType = VK_STRUCTURE_TYPE_BUFFER_COPY_2;
|
||||
region.dstOffset = static_cast<VkDeviceSize>(parts[1].first);
|
||||
region.size = size;
|
||||
VkCopyBufferInfo2 copy{};
|
||||
copy.sType = VK_STRUCTURE_TYPE_COPY_BUFFER_INFO_2;
|
||||
copy.srcBuffer = staging->handle();
|
||||
copy.dstBuffer = m_storage->handle();
|
||||
copy.regionCount = 1;
|
||||
copy.pRegions = ®ion;
|
||||
commands->copy_buffer2(©);
|
||||
compute_barrier(commands);
|
||||
m_deletions->push(m_uploads->current_value(), [staging]() {});
|
||||
}
|
||||
|
||||
ring_slice windscreen_water::block(gl::water_ubs const &Block)
|
||||
{
|
||||
auto const slice{m_ring->allocate(sizeof(gl::water_ubs), m_alignment)};
|
||||
if (slice)
|
||||
{
|
||||
std::memcpy(slice.data, &Block, sizeof(gl::water_ubs));
|
||||
}
|
||||
return slice;
|
||||
}
|
||||
|
||||
bool windscreen_water::gpu_bake(std::size_t const Slot, texture_handle const Mask, gl::water_ubs const &Block)
|
||||
{
|
||||
auto const commands{m_host.commands()};
|
||||
if ((commands == nullptr) || (false == m_full) || (Mask == null_handle))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
VkImageView view{VK_NULL_HANDLE};
|
||||
VkSampler sampler{VK_NULL_HANDLE};
|
||||
if ((false == m_host.texture(Mask, view, sampler)) || (view == VK_NULL_HANDLE))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto const slice{block(Block)};
|
||||
if (false == bool(slice))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
begin(commands);
|
||||
m_kernels[pass::bake].run(commands,
|
||||
{buffer_descriptor(m_storage->handle(), 0, VK_WHOLE_SIZE), buffer_descriptor(slice.buffer, slice.offset, sizeof(gl::water_ubs)),
|
||||
image_descriptor(sampler, view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)},
|
||||
groups(static_cast<std::size_t>(gl::WATER_MAP) * gl::WATER_MAP));
|
||||
compute_barrier(commands);
|
||||
return true;
|
||||
}
|
||||
|
||||
void windscreen_water::gpu_step(gl::water_ubs const &Block, std::uint32_t const Tries)
|
||||
{
|
||||
auto const commands{m_host.commands()};
|
||||
if ((commands == nullptr) || (false == m_full))
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto const slice{block(Block)};
|
||||
if (false == bool(slice))
|
||||
{
|
||||
return;
|
||||
}
|
||||
begin(commands);
|
||||
std::vector<descriptor> const descriptors{buffer_descriptor(m_storage->handle(), 0, VK_WHOLE_SIZE), buffer_descriptor(slice.buffer, slice.offset, sizeof(gl::water_ubs))};
|
||||
auto const run = [&](pass const Pass, std::uint32_t const Groups) {
|
||||
if (Groups == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_kernels[Pass].run(commands, descriptors, Groups, gl::WATER_SLOTS);
|
||||
compute_barrier(commands);
|
||||
};
|
||||
auto const drops{groups(gl::WATER_DROPS)};
|
||||
auto const cells{groups(static_cast<std::size_t>(gl::WATER_GRID) * gl::WATER_GRID)};
|
||||
// the wetted paths the blades went over in the step wiped
|
||||
if (Block.blades != Block.blades_before)
|
||||
{
|
||||
run(pass::trail, groups(static_cast<std::size_t>(gl::WATER_TRAIL) * gl::WATER_TRAIL));
|
||||
}
|
||||
run(pass::spawn, groups(Tries));
|
||||
run(pass::move, drops);
|
||||
run(pass::clear, cells);
|
||||
run(pass::grid, drops);
|
||||
run(pass::merge, drops);
|
||||
run(pass::gather, drops);
|
||||
run(pass::settle, drops);
|
||||
run(pass::clear, cells);
|
||||
run(pass::grid, drops);
|
||||
}
|
||||
|
||||
} // namespace vk14
|
||||
96
rendering/vulkan14/vulkan14water.h
Normal file
96
rendering/vulkan14/vulkan14water.h
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
|
||||
// the water on the windscreens in the vulkan renderer (gfx::windscreen_water, opengl46_windscreen_water): the storage of the water,
|
||||
// which the pass sets hold for the glasses (WATER_SSBO), and the compute kernels of the simulation, run with the commands of the
|
||||
// frame before its passes (outside a rendering). the kernels push everything they read (set COMPUTE_SET: the storage, the block of
|
||||
// the step in the ring of the frame, the mask of the wipers being baked)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rendering/common/windscreenwater.h"
|
||||
#include "rendering/vulkan14/vulkan14compute.h"
|
||||
#include "rendering/vulkan14/vulkan14texture.h"
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
namespace vk14
|
||||
{
|
||||
|
||||
class windscreen_water : public gfx::windscreen_water
|
||||
{
|
||||
public:
|
||||
// what the renderer does for the water
|
||||
struct host
|
||||
{
|
||||
// the commands of the frame, outside a rendering
|
||||
std::function<command_stream *()> commands;
|
||||
// the view and the sampler of a texture of the renderer, made when needed; false when it isn't ready
|
||||
std::function<bool(texture_handle, VkImageView &, VkSampler &)> texture;
|
||||
};
|
||||
|
||||
windscreen_water() = default;
|
||||
~windscreen_water() override;
|
||||
|
||||
// called once, before the pass sets are made: the storage made (the pass sets hold it from then on). false: no storage
|
||||
bool init(VkDevice Device, pipeline_library &Pipelines, shader_library &Shaders, VkDescriptorSetLayout Passlayout, allocator &Memory, uploader &Uploads, deletion_queue &Deletions, frame_ring &Ring,
|
||||
VkDeviceSize Alignment, host Host);
|
||||
// everything goes (the device is idle)
|
||||
void destroy();
|
||||
// the storage the pass sets hold
|
||||
VkBuffer storage() const
|
||||
{
|
||||
return m_storage ? m_storage->handle() : VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool gpu_init() override;
|
||||
void gpu_setup(std::size_t Slot, std::vector<glm::vec4> const &Axes) override;
|
||||
bool gpu_bake(std::size_t Slot, texture_handle Mask, gl::water_ubs const &Block) override;
|
||||
void gpu_step(gl::water_ubs const &Block, std::uint32_t Tries) override;
|
||||
|
||||
private:
|
||||
// the passes of the step, in their order
|
||||
enum pass
|
||||
{
|
||||
trail,
|
||||
spawn,
|
||||
move,
|
||||
clear,
|
||||
grid,
|
||||
merge,
|
||||
gather,
|
||||
settle,
|
||||
bake,
|
||||
count
|
||||
};
|
||||
// the block of the step in the ring of the frame; empty when the ring is full
|
||||
ring_slice block(gl::water_ubs const &Block);
|
||||
// the frames before are done with the storage before the commands of this one write it
|
||||
void begin(command_stream *Commands);
|
||||
|
||||
VkDevice m_device{VK_NULL_HANDLE};
|
||||
pipeline_library *m_pipelines{nullptr};
|
||||
shader_library *m_shaders{nullptr};
|
||||
VkDescriptorSetLayout m_passlayout{VK_NULL_HANDLE};
|
||||
allocator *m_memory{nullptr};
|
||||
uploader *m_uploads{nullptr};
|
||||
deletion_queue *m_deletions{nullptr};
|
||||
frame_ring *m_ring{nullptr};
|
||||
VkDeviceSize m_alignment{256};
|
||||
host m_host;
|
||||
std::array<compute_kernel, pass::count> m_kernels;
|
||||
std::shared_ptr<buffer> m_storage;
|
||||
bool m_full{false}; // the storage is as large as the simulation needs (else a stand-in, the glasses make their water up)
|
||||
bool m_cleared{false};
|
||||
};
|
||||
|
||||
} // namespace vk14
|
||||
@@ -49,6 +49,16 @@
|
||||
#define PARTICLE_OWNER_SSBO_BINDING 11
|
||||
#define PARTICLE_OUTPUT_SSBO_BINDING 12
|
||||
#define PARTICLE_COMMAND_SSBO_BINDING 13
|
||||
#define WATER_SLOTS 4
|
||||
#define WATER_DROPS 65536
|
||||
#define WATER_GRID 128
|
||||
#define WATER_CELL_DROPS 32
|
||||
#define WATER_TRAIL 1024
|
||||
#define WATER_MAP 256
|
||||
#define WATER_GROUP_SIZE 64
|
||||
#define WATER_SSBO_BINDING 16
|
||||
#define WATER_UBO_BINDING 6
|
||||
#define WATER_MASK_TEX 25
|
||||
#define INSTANCE_COUNTER_HEADER 8
|
||||
#define INSTANCE_JOB_SLEEPERS 1U
|
||||
#define LIGHT_SSBO_BINDING 0
|
||||
@@ -297,6 +307,8 @@ layout(std140, binding = SCENE_UBO_BINDING) uniform scene_ubo
|
||||
vec4 ssr_params;
|
||||
vec4 ssr_params2;
|
||||
vec4 env_params;
|
||||
// the air against the vehicle of the camera, see gl::scene_ubs
|
||||
vec4 rain_wind;
|
||||
};
|
||||
|
||||
#ifdef STAGE_VERTEX
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
113
shaders/ogl46/water.glsl
Normal file
113
shaders/ogl46/water.glsl
Normal file
@@ -0,0 +1,113 @@
|
||||
// the water on the windscreens (gfx::windscreen_water, gl::WATER_* in gl/ubo.h): what the passes of the simulation (water_*.comp)
|
||||
// keep and the glasses (mat_rain_windscreen.frag) read. a glass is a material of mat_rain_windscreen in the cab of the train, a
|
||||
// slot of the storage; its drops lie in the coordinates of its texture (uv), as its mask of the wipers does, so they stay where
|
||||
// they lie on the glass whatever the vehicle and the camera do. the map of the glass tells what a step of the uv is along the
|
||||
// glass (metres, in the space of the model of the cab) and where the blades go over it; the drops are looked up through a grid
|
||||
// of cells over the uv, each listing the drops which reach into it; the wetted paths are marked on a map of their own with the
|
||||
// time the water went there
|
||||
|
||||
#ifndef WATER_GLSL
|
||||
#define WATER_GLSL
|
||||
|
||||
struct water_drop_s
|
||||
{
|
||||
// xy: the place, uv; z: the water, mm3 (0: no drop there); w: seconds it lies on the glass
|
||||
vec4 place;
|
||||
// xy: the velocity, uv per second; z: random 0 .. 1, its shape; w: 0 .. 1 how far it melted from a flake of snow (1: water)
|
||||
vec4 motion;
|
||||
};
|
||||
|
||||
#if defined(STAGE_COMPUTE)
|
||||
layout(std430, binding = WATER_SSBO_BINDING) buffer water_ssbo
|
||||
#else
|
||||
layout(std430, binding = WATER_SSBO_BINDING) readonly buffer water_ssbo
|
||||
#endif
|
||||
{
|
||||
// every glass: x 1 when it's simulated, y seconds its simulation runs (the time of the wetted paths), zw unused
|
||||
vec4 water_header[WATER_SLOTS];
|
||||
// every glass: x the drops on it, yzw unused
|
||||
uvec4 water_counts_of[WATER_SLOTS];
|
||||
water_drop_s water_drops[WATER_SLOTS * WATER_DROPS];
|
||||
// the map, two for a texel: xyz the step along the glass for a step of u (metres), w 1 on the glass, 0.5 next to it (the edges
|
||||
// spread out, for the filtering), 0 elsewhere; xyz the same for v, w 1 when the cross product of the two points out of the cab,
|
||||
// -1 when it points in
|
||||
vec4 water_axes[WATER_SLOTS * WATER_MAP * WATER_MAP * 2];
|
||||
// the mask of the wipers baked into the map: the place of a blade over its sweep at which it goes over the texel, 0 where it
|
||||
// doesn't reach
|
||||
vec4 water_blades[WATER_SLOTS * WATER_MAP * WATER_MAP];
|
||||
// every drop: 0 the place is free, else bit 0 set, bits 1 .. 3 the blade carrying it (1 + its number, 0 none), bit 4 set when
|
||||
// the blade carries it towards the far end of its sweep
|
||||
uint water_state[WATER_SLOTS * WATER_DROPS];
|
||||
// the drop it flows into (itself when none)
|
||||
uint water_target[WATER_SLOTS * WATER_DROPS];
|
||||
// what flowed into it: the water (1e-4 mm3) and the moment of the places of the drops about its own (1e-6 uv mm3)
|
||||
int water_gather[WATER_SLOTS * WATER_DROPS * 4];
|
||||
// x: the way it went since it left a droplet behind, metres; y: the way it goes on before it leaves the next one, metres
|
||||
vec4 water_extra[WATER_SLOTS * WATER_DROPS];
|
||||
// the grids of the glasses, WATER_GRID x WATER_GRID cells each: the counts of the drops of the cells, then the drops of each
|
||||
// cell (WATER_CELL_DROPS for a cell, the ones over the count aren't there). then the wetted paths of the glasses,
|
||||
// WATER_TRAIL x WATER_TRAIL each: in the upper 24 bits 1 + the time the water went over the texel last, 1/2 s (0: never),
|
||||
// in the lower 8 how near the middle of the path the texel is, 0 .. 255 (filtered between the texels the edges of the paths
|
||||
// come out smooth wherever they go across the texels; the time is coarse, so a drop going along leaves the nearness of its
|
||||
// whole passage, not of the step which happened to come last)
|
||||
uint water_cells[];
|
||||
};
|
||||
|
||||
// the radius of the footprint of a drop holding Volume mm3 of water, metres: a cap of a sphere meeting the glass at 40 degrees
|
||||
float water_radius(float volume)
|
||||
{
|
||||
return 0.001 * pow(1.675 * max(volume, 0.0), 1.0 / 3.0);
|
||||
}
|
||||
|
||||
// the diameter of a flake of snow holding Volume mm3 of water, metres
|
||||
float water_flake(float volume)
|
||||
{
|
||||
return 0.006 * pow(max(volume, 0.0), 1.0 / 3.0);
|
||||
}
|
||||
|
||||
// where the grid of a glass begins in water_cells: the count of a cell at the base + the cell, its drops from the base +
|
||||
// WATER_GRID * WATER_GRID + the cell * WATER_CELL_DROPS
|
||||
uint water_grid_base(uint slot)
|
||||
{
|
||||
return slot * uint(WATER_GRID * WATER_GRID * (1 + WATER_CELL_DROPS));
|
||||
}
|
||||
|
||||
uint water_trail_base(uint slot)
|
||||
{
|
||||
return uint(WATER_SLOTS * WATER_GRID * WATER_GRID * (1 + WATER_CELL_DROPS)) + slot * uint(WATER_TRAIL * WATER_TRAIL);
|
||||
}
|
||||
|
||||
// the cell of the grid holding a place of the glass
|
||||
uint water_cell(vec2 uv)
|
||||
{
|
||||
ivec2 cell = clamp(ivec2(floor(uv * float(WATER_GRID))), ivec2(0), ivec2(WATER_GRID - 1));
|
||||
return uint(cell.y * WATER_GRID + cell.x);
|
||||
}
|
||||
|
||||
// how fresh the wetted path is at a texel of the map of the paths, 1 just wetted in its middle .. 0 dry long ago or never; Now:
|
||||
// seconds the simulation of the glass runs, Lasting: seconds in which the freshness falls to 1/e
|
||||
float water_wetted(uint slot, ivec2 texel, float now, float lasting)
|
||||
{
|
||||
texel = clamp(texel, ivec2(0), ivec2(WATER_TRAIL - 1));
|
||||
uint value = water_cells[water_trail_base(slot) + uint(texel.y * WATER_TRAIL + texel.x)];
|
||||
uint stamp = value >> 8u;
|
||||
if (stamp == 0u)
|
||||
return 0.0;
|
||||
float age = max(now - float(stamp - 1u) / 2.0, 0.0);
|
||||
return exp(-age / lasting) * (float(value & 255u) / 255.0);
|
||||
}
|
||||
|
||||
// the same filtered between the texels at a place of the glass
|
||||
float water_wetted_at(uint slot, vec2 uv, float now, float lasting)
|
||||
{
|
||||
vec2 p = uv * float(WATER_TRAIL) - 0.5;
|
||||
ivec2 i = ivec2(floor(p));
|
||||
vec2 f = p - vec2(i);
|
||||
float a = water_wetted(slot, i, now, lasting);
|
||||
float b = water_wetted(slot, i + ivec2(1, 0), now, lasting);
|
||||
float c = water_wetted(slot, i + ivec2(0, 1), now, lasting);
|
||||
float d = water_wetted(slot, i + ivec2(1, 1), now, lasting);
|
||||
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
|
||||
}
|
||||
|
||||
#endif
|
||||
24
shaders/ogl46/water_bake.comp
Normal file
24
shaders/ogl46/water_bake.comp
Normal file
@@ -0,0 +1,24 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// the water on the windscreens (water_sim.glsl): the mask of the wipers of a glass baked into its map, once, when the glass is
|
||||
// set up. a thread per texel of the map; the glass is water_counts.z
|
||||
|
||||
#include "common.glsl"
|
||||
#include "water_sim.glsl"
|
||||
|
||||
layout(local_size_x = WATER_GROUP_SIZE) in;
|
||||
|
||||
// the mask of the wipers of the material of the glass (wipermask)
|
||||
layout(binding = WATER_MASK_TEX) uniform sampler2D water_mask;
|
||||
|
||||
void main()
|
||||
{
|
||||
uint slot = water_counts.z;
|
||||
uint texel = gl_GlobalInvocationID.x;
|
||||
if ((slot >= uint(WATER_SLOTS)) || (texel >= uint(WATER_MAP * WATER_MAP)))
|
||||
return;
|
||||
vec2 uv = (vec2(float(texel % uint(WATER_MAP)), float(texel / uint(WATER_MAP))) + 0.5) / float(WATER_MAP);
|
||||
// as much of the mask as a texel of the map covers
|
||||
float lod = log2(max(float(textureSize(water_mask, 0).x) / float(WATER_MAP), 1.0));
|
||||
water_blades[slot * uint(WATER_MAP * WATER_MAP) + texel] = textureLod(water_mask, uv, lod);
|
||||
}
|
||||
18
shaders/ogl46/water_clear.comp
Normal file
18
shaders/ogl46/water_clear.comp
Normal file
@@ -0,0 +1,18 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// the water on the windscreens (water_sim.glsl): the counts of the cells of the grids emptied before the drops are put in them
|
||||
// (water_grid.comp). a thread per cell, a row of groups per glass
|
||||
|
||||
#include "common.glsl"
|
||||
#include "water_sim.glsl"
|
||||
|
||||
layout(local_size_x = WATER_GROUP_SIZE) in;
|
||||
|
||||
void main()
|
||||
{
|
||||
uint slot = gl_WorkGroupID.y;
|
||||
uint cell = gl_GlobalInvocationID.x;
|
||||
if ((water_slots[slot].z < 0.5) || (cell >= uint(WATER_GRID * WATER_GRID)))
|
||||
return;
|
||||
water_cells[water_grid_base(slot) + cell] = 0u;
|
||||
}
|
||||
40
shaders/ogl46/water_gather.comp
Normal file
40
shaders/ogl46/water_gather.comp
Normal file
@@ -0,0 +1,40 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// the water on the windscreens (water_sim.glsl): the drops flow into the ones water_merge.comp found for them, where those don't
|
||||
// flow on themselves (they do in a later step): the water and the moment of the places of the drops gathered by the one they
|
||||
// flow into (water_settle.comp moves it to the middle of the water), the drops gone. a thread per drop, a row of groups per glass
|
||||
|
||||
#include "common.glsl"
|
||||
#include "water_sim.glsl"
|
||||
|
||||
layout(local_size_x = WATER_GROUP_SIZE) in;
|
||||
|
||||
void main()
|
||||
{
|
||||
uint slot = gl_WorkGroupID.y;
|
||||
uint place = gl_GlobalInvocationID.x;
|
||||
if ((water_slots[slot].z < 0.5) || (place >= uint(WATER_DROPS)))
|
||||
return;
|
||||
uint base = slot * uint(WATER_DROPS);
|
||||
uint index = base + place;
|
||||
uint target = water_target[index];
|
||||
if ((target == place) || (target >= uint(WATER_DROPS)) || (water_target[base + target] != target))
|
||||
return;
|
||||
water_drop_s drop = water_drops[index];
|
||||
water_drop_s into = water_drops[base + target];
|
||||
if ((drop.place.z <= 0.0) || (into.place.z <= 0.0))
|
||||
return;
|
||||
// with what flowed into the drop itself in the step (the landings)
|
||||
float landed = float(water_gather[index * 4u]) * 1e-4;
|
||||
vec2 moment = vec2(float(water_gather[index * 4u + 1u]), float(water_gather[index * 4u + 2u])) * 1e-6;
|
||||
float volume = drop.place.z + landed;
|
||||
vec2 offset = drop.place.xy - into.place.xy;
|
||||
atomicAdd(water_gather[(base + target) * 4u], int(volume * 1e4));
|
||||
atomicAdd(water_gather[(base + target) * 4u + 1u], int((offset.x * volume + moment.x) * 1e6));
|
||||
atomicAdd(water_gather[(base + target) * 4u + 2u], int((offset.y * volume + moment.y) * 1e6));
|
||||
// the blade carrying the drop carries what it flows into
|
||||
uint carried = water_state[index] & 0x1eu;
|
||||
if ((carried != 0u) && ((water_state[base + target] & 0x0eu) == 0u))
|
||||
atomicOr(water_state[base + target], carried);
|
||||
water_remove(index);
|
||||
}
|
||||
52
shaders/ogl46/water_grid.comp
Normal file
52
shaders/ogl46/water_grid.comp
Normal file
@@ -0,0 +1,52 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// the water on the windscreens (water_sim.glsl): every drop put in the cells of the grid it reaches into, as far as the glasses
|
||||
// draw it (a sliding one is drawn out behind itself) and as far as the drops it may flow together with are. a thread per drop,
|
||||
// a row of groups per glass
|
||||
|
||||
#include "common.glsl"
|
||||
#include "water_sim.glsl"
|
||||
|
||||
layout(local_size_x = WATER_GROUP_SIZE) in;
|
||||
|
||||
void main()
|
||||
{
|
||||
uint slot = gl_WorkGroupID.y;
|
||||
uint place = gl_GlobalInvocationID.x;
|
||||
if ((water_slots[slot].z < 0.5) || (place >= uint(WATER_DROPS)))
|
||||
return;
|
||||
water_drop_s drop = water_drops[slot * uint(WATER_DROPS) + place];
|
||||
if (drop.place.z <= 0.0)
|
||||
return;
|
||||
water_frame_s frame = water_frame(slot, drop.place.xy);
|
||||
float lu = length(frame.tu);
|
||||
float lv = length(frame.tv);
|
||||
if ((lu < 1e-6) || (lv < 1e-6))
|
||||
return;
|
||||
float reach;
|
||||
if (drop.motion.w < 1.0)
|
||||
{
|
||||
reach = 0.6 * water_flake(drop.place.z);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool sliding = length(frame.tu * drop.motion.x + frame.tv * drop.motion.y) > 0.002;
|
||||
reach = water_radius(drop.place.z) * (sliding ? 3.6 : 2.2);
|
||||
}
|
||||
vec2 extent = vec2(reach / lu, reach / lv);
|
||||
ivec2 lower = clamp(ivec2(floor((drop.place.xy - extent) * float(WATER_GRID))), ivec2(0), ivec2(WATER_GRID - 1));
|
||||
ivec2 upper = clamp(ivec2(floor((drop.place.xy + extent) * float(WATER_GRID))), ivec2(0), ivec2(WATER_GRID - 1));
|
||||
// a drop can't be larger than this many cells, it would only flood the lists
|
||||
upper = min(upper, lower + ivec2(6));
|
||||
uint base = water_grid_base(slot);
|
||||
for (int y = lower.y; y <= upper.y; ++y)
|
||||
{
|
||||
for (int x = lower.x; x <= upper.x; ++x)
|
||||
{
|
||||
uint cell = uint(y * WATER_GRID + x);
|
||||
uint count = atomicAdd(water_cells[base + cell], 1u);
|
||||
if (count < uint(WATER_CELL_DROPS))
|
||||
water_cells[base + uint(WATER_GRID * WATER_GRID) + cell * uint(WATER_CELL_DROPS) + count] = place;
|
||||
}
|
||||
}
|
||||
}
|
||||
63
shaders/ogl46/water_merge.comp
Normal file
63
shaders/ogl46/water_merge.comp
Normal file
@@ -0,0 +1,63 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// the water on the windscreens (water_sim.glsl): the drops which touch flow together, the way surface tension pulls them. every
|
||||
// drop finds the largest of the drops it touches which are larger than itself (by the cell of the grid its middle is in, which
|
||||
// lists every drop reaching into it); the water goes over to that one in water_gather.comp. flakes of snow don't flow, but the
|
||||
// ones a blade pushes pile up. a thread per drop, a row of groups per glass
|
||||
|
||||
#include "common.glsl"
|
||||
#include "water_sim.glsl"
|
||||
|
||||
layout(local_size_x = WATER_GROUP_SIZE) in;
|
||||
|
||||
void main()
|
||||
{
|
||||
uint slot = gl_WorkGroupID.y;
|
||||
uint place = gl_GlobalInvocationID.x;
|
||||
if ((water_slots[slot].z < 0.5) || (place >= uint(WATER_DROPS)))
|
||||
return;
|
||||
uint index = slot * uint(WATER_DROPS) + place;
|
||||
water_drop_s drop = water_drops[index];
|
||||
water_target[index] = place;
|
||||
if (drop.place.z <= 0.0)
|
||||
return;
|
||||
bool flake = drop.motion.w < 1.0;
|
||||
bool carried = ((water_state[index] >> 1u) & 7u) != 0u;
|
||||
if (flake && (false == carried))
|
||||
return;
|
||||
water_frame_s frame = water_frame(slot, drop.place.xy);
|
||||
float size = flake ? 0.4 * water_flake(drop.place.z) : water_radius(drop.place.z);
|
||||
uint cell = water_cell(drop.place.xy);
|
||||
uint base = water_grid_base(slot);
|
||||
uint count = min(water_cells[base + cell], uint(WATER_CELL_DROPS));
|
||||
uint best = place;
|
||||
float most = drop.place.z;
|
||||
for (uint k = 0u; k < count; ++k)
|
||||
{
|
||||
uint other = water_cells[base + uint(WATER_GRID * WATER_GRID) + cell * uint(WATER_CELL_DROPS) + k];
|
||||
if (other == place)
|
||||
continue;
|
||||
uint otherindex = slot * uint(WATER_DROPS) + other;
|
||||
water_drop_s neighbour = water_drops[otherindex];
|
||||
float volume = neighbour.place.z;
|
||||
if ((volume < most) || ((volume == most) && (other > best)) || (volume <= 0.0))
|
||||
continue;
|
||||
if ((volume == drop.place.z) && (other > place))
|
||||
continue;
|
||||
bool otherflake = neighbour.motion.w < 1.0;
|
||||
if (otherflake != flake)
|
||||
continue;
|
||||
// the snow a blade pushes piles up into lumps a few centimetres across, not more
|
||||
if (otherflake && ((((water_state[otherindex] >> 1u) & 7u) == 0u) || (volume + drop.place.z > 20.0)))
|
||||
continue;
|
||||
vec2 step = neighbour.place.xy - drop.place.xy;
|
||||
float apart = length(frame.tu * step.x + frame.tv * step.y);
|
||||
float reach = size + (otherflake ? 0.4 * water_flake(volume) : water_radius(volume));
|
||||
if (apart < 0.92 * reach)
|
||||
{
|
||||
best = other;
|
||||
most = volume;
|
||||
}
|
||||
}
|
||||
water_target[index] = best;
|
||||
}
|
||||
254
shaders/ogl46/water_move.comp
Normal file
254
shaders/ogl46/water_move.comp
Normal file
@@ -0,0 +1,254 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// the water on the windscreens (water_sim.glsl): every drop through the step. a thread per drop, a row of groups per glass.
|
||||
// a blade going over the drop takes it along in front of itself until it turns back at the end of its sweep or stops, or until
|
||||
// the drop is pushed out of the sweep; a drop of its own slides when gravity and the air pull it harder than its line of
|
||||
// contact holds it, wetting its path and leaving droplets behind; a flake melts into a drop
|
||||
|
||||
#include "common.glsl"
|
||||
#include "water_sim.glsl"
|
||||
|
||||
layout(local_size_x = WATER_GROUP_SIZE) in;
|
||||
|
||||
// how fresh the wetted path is at a place of the glass
|
||||
float water_fresh(uint slot, vec2 uv)
|
||||
{
|
||||
return water_wetted_at(slot, uv, water_step.y, 8.0);
|
||||
}
|
||||
|
||||
// the path from one place to another marked wetted, Halfwidth (metres) to the sides: the texels around it get the time and how
|
||||
// near they are to the middle of the path
|
||||
void water_wet_path(uint slot, water_frame_s frame, vec2 from, vec2 to, float halfwidth)
|
||||
{
|
||||
uint stamp = (uint(water_step.y * 2.0) + 1u) << 8u;
|
||||
uint base = water_trail_base(slot);
|
||||
float texels = float(WATER_TRAIL);
|
||||
// the half width in texels, a texel and a half more for the filtering
|
||||
float reach = halfwidth / max(min(length(frame.tu), length(frame.tv)), 1e-6) * texels + 1.5;
|
||||
int spread = clamp(int(ceil(reach)), 1, 3);
|
||||
vec2 a = from * texels - 0.5;
|
||||
vec2 b = to * texels - 0.5;
|
||||
int steps = clamp(int(ceil(length(b - a) * 2.0)), 1, 24);
|
||||
for (int s = 1; s <= steps; ++s)
|
||||
{
|
||||
vec2 point = mix(a, b, float(s) / float(steps));
|
||||
ivec2 centre = ivec2(floor(point + 0.5));
|
||||
for (int y = -spread; y <= spread; ++y)
|
||||
{
|
||||
for (int x = -spread; x <= spread; ++x)
|
||||
{
|
||||
ivec2 texel = centre + ivec2(x, y);
|
||||
if (any(lessThan(texel, ivec2(0))) || any(greaterThanEqual(texel, ivec2(WATER_TRAIL))))
|
||||
continue;
|
||||
float near = 1.0 - length(vec2(texel) - point) / reach;
|
||||
if (near <= 0.0)
|
||||
continue;
|
||||
atomicMax(water_cells[base + uint(texel.y * WATER_TRAIL + texel.x)], stamp | uint(near * 255.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
uint slot = gl_WorkGroupID.y;
|
||||
uint place = gl_GlobalInvocationID.x;
|
||||
if ((water_slots[slot].z < 0.5) || (place >= uint(WATER_DROPS)))
|
||||
return;
|
||||
uint index = slot * uint(WATER_DROPS) + place;
|
||||
water_drop_s drop = water_drops[index];
|
||||
if (drop.place.z <= 0.0)
|
||||
return;
|
||||
float seconds = water_step.x;
|
||||
uint state = water_state[index];
|
||||
vec4 extra = water_extra[index];
|
||||
vec2 uv = drop.place.xy;
|
||||
water_frame_s frame = water_frame(slot, uv);
|
||||
if (frame.valid < 0.25)
|
||||
{
|
||||
water_remove(index);
|
||||
return;
|
||||
}
|
||||
uint seed = water_pcg4d(uvec4(water_counts.x, slot, place, 9u)).x;
|
||||
|
||||
// a flake melts into a drop: the glass is warmed by the cab, the faster the warmer the air
|
||||
bool flake = drop.motion.w < 1.0;
|
||||
if (flake)
|
||||
{
|
||||
float warm = water_up.w;
|
||||
float melt = (warm > 0.5) ? mix(3.0, 10.0, drop.motion.z) / (1.0 + 0.25 * warm) : mix(15.0, 45.0, drop.motion.z);
|
||||
drop.motion.w = min(drop.motion.w + seconds / melt, 1.0);
|
||||
flake = drop.motion.w < 1.0;
|
||||
}
|
||||
|
||||
// the blades: one going over the drop in the step takes it along
|
||||
uint carried = (state >> 1u) & 7u;
|
||||
float way = (((state >> 4u) & 1u) != 0u) ? 1.0 : -1.0;
|
||||
vec4 mask = water_blades_at(slot, uv);
|
||||
vec4 reached = water_blades_texel(slot, uv);
|
||||
for (int k = 0; k < 4; ++k)
|
||||
{
|
||||
float before = water_blades_before[k];
|
||||
float now = water_blades_now[k];
|
||||
if ((before < 0.0) || (now < 0.0))
|
||||
{
|
||||
if (carried == uint(k + 1))
|
||||
carried = 0u;
|
||||
continue;
|
||||
}
|
||||
float moved = now - before;
|
||||
if (carried == uint(k + 1))
|
||||
{
|
||||
// the blade turned back at the end of its sweep or stopped: the water stays where it brought it
|
||||
if ((moved * way <= 0.0) || (now >= 0.995) || (now <= 0.005))
|
||||
carried = 0u;
|
||||
}
|
||||
// a blade goes over a drop which was ahead of it (by a little more than nothing: the water it left where it turned back at
|
||||
// the end of its sweep isn't taken back) and is behind it now
|
||||
else if ((carried == 0u) && (moved != 0.0) && (reached[k] >= 0.001) && ((mask[k] - before) * sign(moved) > 0.003) && ((mask[k] - now) * sign(moved) <= 0.0))
|
||||
{
|
||||
carried = uint(k + 1);
|
||||
way = (moved > 0.0) ? 1.0 : -1.0;
|
||||
// the blade doesn't take all of it: now and then a droplet stays behind
|
||||
vec4 r = water_random(seed, 5u, place, slot);
|
||||
if ((false == flake) && (drop.place.z > 1.0) && (r.x < 0.35))
|
||||
{
|
||||
float left = mix(0.02, 0.08, r.y);
|
||||
if (water_add(slot, seed ^ 0x9e3779b9u, uv, left, 1.0, r.z, false))
|
||||
drop.place.z -= left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vec3 velocity = frame.tu * drop.motion.x + frame.tv * drop.motion.y;
|
||||
if (carried != 0u)
|
||||
{
|
||||
// in front of the blade by its own size and the lip of the bead: the place where the mask is as far ahead of the blade,
|
||||
// found along the gradient of the mask (along the arc of the blade)
|
||||
int k = int(carried) - 1;
|
||||
float radius = flake ? 0.5 * water_flake(drop.place.z) : water_radius(drop.place.z);
|
||||
float a = dot(frame.tu, frame.tu);
|
||||
float b = dot(frame.tu, frame.tv);
|
||||
float c = dot(frame.tv, frame.tv);
|
||||
float det = max(a * c - b * b, 1e-20);
|
||||
float h = 1.0 / float(WATER_MAP);
|
||||
bool edge = false;
|
||||
for (int step = 0; step < 4; ++step)
|
||||
{
|
||||
// at the edge of the sweep (the end of the blade) the water squeezes out and stays
|
||||
if ((water_blades_texel(slot, uv + vec2(h, 0.0))[k] < 0.001) || (water_blades_texel(slot, uv - vec2(h, 0.0))[k] < 0.001) ||
|
||||
(water_blades_texel(slot, uv + vec2(0.0, h))[k] < 0.001) || (water_blades_texel(slot, uv - vec2(0.0, h))[k] < 0.001))
|
||||
{
|
||||
edge = true;
|
||||
break;
|
||||
}
|
||||
float m = water_blades_at(slot, uv)[k];
|
||||
vec2 gradient = vec2(water_blades_at(slot, uv + vec2(h, 0.0))[k] - water_blades_at(slot, uv - vec2(h, 0.0))[k],
|
||||
water_blades_at(slot, uv + vec2(0.0, h))[k] - water_blades_at(slot, uv - vec2(0.0, h))[k]) / (2.0 * h);
|
||||
float g2 = dot(gradient, gradient);
|
||||
if (g2 < 1e-6)
|
||||
break;
|
||||
// how fast the mask changes along the glass, per metre
|
||||
float permetre = sqrt(max((c * gradient.x * gradient.x - 2.0 * b * gradient.x * gradient.y + a * gradient.y * gradient.y) / det, 0.0));
|
||||
float target = water_blades_now[k] + way * (0.0015 + radius) * permetre;
|
||||
vec2 delta = gradient * ((target - m) / g2);
|
||||
float length_delta = length(delta);
|
||||
if (length_delta > 0.02)
|
||||
delta *= 0.02 / length_delta;
|
||||
uv += delta;
|
||||
if (length_delta < 1e-5)
|
||||
break;
|
||||
}
|
||||
// pushed out of the sweep it stays at its edge
|
||||
if (edge || (water_blades_texel(slot, uv)[k] < 0.001))
|
||||
carried = 0u;
|
||||
velocity = vec3(0.0);
|
||||
}
|
||||
else if (false == flake)
|
||||
{
|
||||
float volume = drop.place.z;
|
||||
float radius = water_radius(volume);
|
||||
vec3 n = frame.normal;
|
||||
vec3 up = water_up.xyz;
|
||||
// gravity along the glass
|
||||
float weight = 1000.0 * WATER_GRAVITY * volume * 1e-9;
|
||||
vec3 force = -(up - n * dot(up, n)) * weight;
|
||||
// the drag of the air going along the glass, with a share of the air meeting it head on turned up along it
|
||||
vec3 air = water_air.xyz;
|
||||
vec3 rise = up - n * dot(up, n);
|
||||
rise = (length(rise) > 0.1) ? normalize(rise) : vec3(0.0);
|
||||
vec3 flow = (air - n * dot(air, n) + rise * (max(-dot(air, n), 0.0) * WATER_HEAD_ON)) * WATER_BOUNDARY;
|
||||
float area = 2.0 * radius * (0.364 * radius) * 0.8;
|
||||
force += flow * (0.6 * area * length(flow));
|
||||
// the hold of the line of contact, uneven over the glass, much less on a path wetted lately
|
||||
vec2 spread = uv * vec2(length(frame.tu), length(frame.tv));
|
||||
float uneven = mix(0.55, 1.45, water_noise(spread / 0.003, 3u + slot));
|
||||
float hold = WATER_TENSION * 2.0 * radius * WATER_HOLD * uneven * mix(1.0, 0.3, water_fresh(slot, uv)) * mix(0.85, 1.15, drop.motion.z);
|
||||
float pull = length(force);
|
||||
vec3 target = vec3(0.0);
|
||||
if (pull > hold)
|
||||
{
|
||||
vec3 dir = force / pull;
|
||||
// the way bent towards the side which holds the drop less: the wetter one (the water keeps to the wetted paths) and the
|
||||
// one where the glass is cleaner, so it meanders
|
||||
vec3 side = cross(n, dir);
|
||||
float offset = max(radius, 0.0015);
|
||||
vec2 leftuv = uv + water_to_uv(frame, side * offset);
|
||||
vec2 rightuv = uv - water_to_uv(frame, side * offset);
|
||||
float wetter = water_fresh(slot, leftuv) - water_fresh(slot, rightuv);
|
||||
float cleaner = water_noise(rightuv * vec2(length(frame.tu), length(frame.tv)) / 0.003, 3u + slot) - water_noise(leftuv * vec2(length(frame.tu), length(frame.tv)) / 0.003, 3u + slot);
|
||||
float wander = water_noise(spread / 0.02 + vec2(17.0 * drop.motion.z), 11u + slot) - 0.5;
|
||||
dir = normalize(dir + side * (wetter * 0.6 + cleaner * 0.5 + wander * 0.35));
|
||||
target = dir * min((pull - hold) / (WATER_VISCOUS * radius), 1.5);
|
||||
}
|
||||
velocity += (target - velocity) * (1.0 - exp(-seconds / ((pull > hold) ? 0.05 : 0.015)));
|
||||
velocity -= n * dot(velocity, n);
|
||||
vec3 moved = velocity * seconds;
|
||||
float travel = length(moved);
|
||||
if (travel > 1e-7)
|
||||
{
|
||||
vec2 next = uv + water_to_uv(frame, moved);
|
||||
water_wet_path(slot, frame, uv, next, 0.4 * radius);
|
||||
// a sliding drop leaves droplets behind as it goes, and thins; a large one breaks up the faster (a lot of water runs as
|
||||
// a stream of beads rather than as one big drop)
|
||||
extra.x += travel;
|
||||
if ((extra.x >= extra.y) && (volume > 1.5))
|
||||
{
|
||||
vec4 r = water_random(seed, 6u, place, slot);
|
||||
float left = clamp(volume * mix(0.01, 0.03, r.x) * (1.0 + volume / 40.0), 0.01, 0.25 * volume);
|
||||
vec3 behind = -(velocity / max(length(velocity), 1e-6)) * ((radius + 1.2 * water_radius(left)) * 1.05);
|
||||
if (water_add(slot, seed ^ 0x85ebca6bu, uv + water_to_uv(frame, behind), left, 1.0, r.y, false))
|
||||
drop.place.z -= left;
|
||||
extra.x = 0.0;
|
||||
extra.y = mix(0.002, 0.005, r.z) / (1.0 + volume / 100.0);
|
||||
}
|
||||
uv = next;
|
||||
}
|
||||
// without the rain the water dries, slowly
|
||||
if (water_step.z <= 0.0)
|
||||
drop.place.z -= seconds * 0.004 * radius * 1000.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
velocity = vec3(0.0);
|
||||
}
|
||||
|
||||
if ((drop.place.z < WATER_LEAST) || any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0))))
|
||||
{
|
||||
water_remove(index);
|
||||
return;
|
||||
}
|
||||
water_frame_s there = water_frame(slot, uv);
|
||||
if (there.valid < 0.25)
|
||||
{
|
||||
// off the glass, it falls off
|
||||
water_remove(index);
|
||||
return;
|
||||
}
|
||||
drop.place.xy = uv;
|
||||
drop.place.w += seconds;
|
||||
drop.motion.xy = water_to_uv(there, velocity);
|
||||
water_drops[index] = drop;
|
||||
water_extra[index] = extra;
|
||||
water_state[index] = 1u | (carried << 1u) | ((way > 0.0) ? 16u : 0u);
|
||||
}
|
||||
36
shaders/ogl46/water_settle.comp
Normal file
36
shaders/ogl46/water_settle.comp
Normal file
@@ -0,0 +1,36 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// the water on the windscreens (water_sim.glsl): the drops which gathered others take their water, move to the middle of it and
|
||||
// slow down as much as the still water slows them; the header of the glass tells the glasses it's simulated. a thread per drop,
|
||||
// a row of groups per glass
|
||||
|
||||
#include "common.glsl"
|
||||
#include "water_sim.glsl"
|
||||
|
||||
layout(local_size_x = WATER_GROUP_SIZE) in;
|
||||
|
||||
void main()
|
||||
{
|
||||
uint slot = gl_WorkGroupID.y;
|
||||
uint place = gl_GlobalInvocationID.x;
|
||||
if ((water_slots[slot].z < 0.5) || (place >= uint(WATER_DROPS)))
|
||||
return;
|
||||
if (place == 0u)
|
||||
water_header[slot] = vec4(1.0, water_step.y, 0.0, 0.0);
|
||||
uint index = slot * uint(WATER_DROPS) + place;
|
||||
int gathered = water_gather[index * 4u];
|
||||
if (gathered == 0)
|
||||
return;
|
||||
vec2 moment = vec2(float(water_gather[index * 4u + 1u]), float(water_gather[index * 4u + 2u])) * 1e-6;
|
||||
water_gather[index * 4u] = 0;
|
||||
water_gather[index * 4u + 1u] = 0;
|
||||
water_gather[index * 4u + 2u] = 0;
|
||||
water_drop_s drop = water_drops[index];
|
||||
if (drop.place.z <= 0.0)
|
||||
return;
|
||||
float volume = drop.place.z + float(gathered) * 1e-4;
|
||||
drop.place.xy += moment / volume;
|
||||
drop.motion.xy *= drop.place.z / volume;
|
||||
drop.place.z = volume;
|
||||
water_drops[index] = drop;
|
||||
}
|
||||
279
shaders/ogl46/water_sim.glsl
Normal file
279
shaders/ogl46/water_sim.glsl
Normal file
@@ -0,0 +1,279 @@
|
||||
// what the passes of the simulation of the water on the windscreens share (water_*.comp): the block of the step, the random
|
||||
// numbers, the map of the glass, the forces on a drop
|
||||
//
|
||||
// the physics (the drops are caps of spheres flat on the glass, 40 degrees at their edge, untreated weathered glass):
|
||||
// - a drop lies held by its line of contact: the hold is the surface tension times the width of the drop times the difference
|
||||
// of the cosines of its angles at the front and the rear (WATER_HOLD), so it grows with the radius, while the weight grows
|
||||
// with the cube of it. on a vertical glass a drop slides once its footprint is about 7 mm across (25 mm3), on a glass leaning
|
||||
// back later. the hold is uneven over the glass (dirt, scratches: the drops stop and go) and much less where the water went
|
||||
// lately (a wetted path: the next drops follow it and it becomes a rivulet)
|
||||
// - the air going along the glass drags the drops (with the area they stand up into it, within the layer of slow air at the
|
||||
// glass), and of the air meeting the glass head on a share turns up along it: at speed the water goes up the glass, the sooner
|
||||
// the more it leans back
|
||||
// - a sliding drop is held back by the viscosity in proportion to its speed and radius; it leaves droplets behind as it goes
|
||||
// (it thins and stops in the end) and wets its path
|
||||
// - drops which touch flow together into one (surface tension), the water and the momentum of the places kept
|
||||
// - a raindrop hits the glass far above the speed at which it splashes: it spreads and throws droplets around, which stay
|
||||
// - a blade going over a drop takes it along in front of itself; the water it gathers flows together into a bead along it, left
|
||||
// at the ends of its sweep (where it runs down as streams) and pushed off where the glass ends
|
||||
|
||||
#ifndef WATER_SIM_GLSL
|
||||
#define WATER_SIM_GLSL
|
||||
|
||||
#include "water.glsl"
|
||||
|
||||
// gl::water_ubs, every vector in the space of the model of the cab
|
||||
layout(std140, binding = WATER_UBO_BINDING) uniform water_ubo
|
||||
{
|
||||
// x: seconds of the step, y: seconds the simulation runs, z: how heavy the rain or the snow is 0 .. 1, w: 1 for snow
|
||||
vec4 water_step;
|
||||
// xyz: up, w: the temperature of the air, degrees Celsius
|
||||
vec4 water_up;
|
||||
// xyz: the air against the cab, m/s; w: how fast the rain or the snow falls, m/s
|
||||
vec4 water_air;
|
||||
// the blades now and at the step before: 0 .. 1 across their sweep, below 0 where there's none
|
||||
vec4 water_blades_now;
|
||||
vec4 water_blades_before;
|
||||
// x: the smallest diameter of a raindrop landing as a drop of its own, mm, y: 1 / the mean of the diameters above it, 1 / mm
|
||||
vec4 water_rain;
|
||||
// x: random seed of the step, y: the most landings tried on a glass, z: the glass a single glass pass works on
|
||||
uvec4 water_counts;
|
||||
// every glass: x the landings tried in the step, y 1 / the most metres squared a unit of its uv holds, z 1 when it's simulated,
|
||||
// w the size of its drops (rain_scale of its material)
|
||||
vec4 water_slots[WATER_SLOTS];
|
||||
};
|
||||
|
||||
const float WATER_GRAVITY = 9.81;
|
||||
// the surface tension of water, N/m, and the share of it a drop is held by for the width of its footprint
|
||||
const float WATER_TENSION = 0.072;
|
||||
const float WATER_HOLD = 0.5;
|
||||
// the viscous drag of a sliding drop for its radius and speed, N s / m2
|
||||
const float WATER_VISCOUS = 1.3;
|
||||
// the share of the speed of the air the drops feel in the slow layer at the glass, and of the air meeting the glass head on
|
||||
// which turns up along it
|
||||
const float WATER_BOUNDARY = 0.3;
|
||||
const float WATER_HEAD_ON = 0.35;
|
||||
// the drops below this much water (mm3) dry at once; a raindrop or a droplet of a splash smaller than WATER_SEEN (0.5 mm across
|
||||
// its footprint, hardly a pixel) landing where there's no drop is left to the mist of the glass
|
||||
const float WATER_LEAST = 0.004;
|
||||
const float WATER_SEEN = 0.075;
|
||||
|
||||
uvec4 water_pcg4d(uvec4 v)
|
||||
{
|
||||
v = v * 1664525u + 1013904223u;
|
||||
v.x += v.y * v.w;
|
||||
v.y += v.z * v.x;
|
||||
v.z += v.x * v.y;
|
||||
v.w += v.y * v.z;
|
||||
v ^= v >> 16u;
|
||||
v.x += v.y * v.w;
|
||||
v.y += v.z * v.x;
|
||||
v.z += v.x * v.y;
|
||||
v.w += v.y * v.z;
|
||||
return v;
|
||||
}
|
||||
|
||||
// four random numbers 0 .. 1
|
||||
vec4 water_random(uint a, uint b, uint c, uint d)
|
||||
{
|
||||
return vec4(water_pcg4d(uvec4(a, b, c, d))) * (1.0 / 4294967296.0);
|
||||
}
|
||||
|
||||
// value noise 0 .. 1
|
||||
float water_noise(vec2 p, uint layer)
|
||||
{
|
||||
vec2 i = floor(p);
|
||||
vec2 f = p - i;
|
||||
vec2 u = f * f * (3.0 - 2.0 * f);
|
||||
uvec2 c = uvec2(ivec2(i));
|
||||
float a = water_random(c.x, c.y, layer, 0u).x;
|
||||
float b = water_random(c.x + 1u, c.y, layer, 0u).x;
|
||||
float d = water_random(c.x, c.y + 1u, layer, 0u).x;
|
||||
float e = water_random(c.x + 1u, c.y + 1u, layer, 0u).x;
|
||||
return mix(mix(a, b, u.x), mix(d, e, u.x), u.y);
|
||||
}
|
||||
|
||||
// the glass at a place of the uv: the steps along it for the steps of u and v (metres), the way out of the cab and whether the
|
||||
// place is on the glass (1), next to it (0.5) or off it (0)
|
||||
struct water_frame_s
|
||||
{
|
||||
vec3 tu;
|
||||
vec3 tv;
|
||||
vec3 normal;
|
||||
float valid;
|
||||
};
|
||||
|
||||
water_frame_s water_frame(uint slot, vec2 uv)
|
||||
{
|
||||
water_frame_s frame = water_frame_s(vec3(0.0), vec3(0.0), vec3(0.0, 0.0, 1.0), 0.0);
|
||||
if (any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0))))
|
||||
return frame;
|
||||
uint base = slot * uint(WATER_MAP * WATER_MAP);
|
||||
vec2 p = uv * float(WATER_MAP) - 0.5;
|
||||
ivec2 i = ivec2(floor(p));
|
||||
vec2 f = p - vec2(i);
|
||||
float total = 0.0;
|
||||
float side = 0.0;
|
||||
for (int k = 0; k < 4; ++k)
|
||||
{
|
||||
ivec2 texel = clamp(i + ivec2(k & 1, k >> 1), ivec2(0), ivec2(WATER_MAP - 1));
|
||||
uint index = base + uint(texel.y * WATER_MAP + texel.x);
|
||||
vec4 a = water_axes[index * 2u];
|
||||
vec4 b = water_axes[index * 2u + 1u];
|
||||
if (a.w <= 0.0)
|
||||
continue;
|
||||
float w = (((k & 1) != 0) ? f.x : 1.0 - f.x) * (((k >> 1) != 0) ? f.y : 1.0 - f.y) + 1e-4;
|
||||
frame.tu += a.xyz * w;
|
||||
frame.tv += b.xyz * w;
|
||||
side += b.w * w;
|
||||
total += w;
|
||||
}
|
||||
if (total <= 0.0)
|
||||
return frame;
|
||||
frame.tu /= total;
|
||||
frame.tv /= total;
|
||||
vec3 n = cross(frame.tu, frame.tv);
|
||||
float length_n = length(n);
|
||||
if (length_n < 1e-12)
|
||||
return frame;
|
||||
frame.normal = n * (((side < 0.0) ? -1.0 : 1.0) / length_n);
|
||||
// on the glass by the texel the place is in
|
||||
ivec2 nearest = clamp(ivec2(floor(uv * float(WATER_MAP))), ivec2(0), ivec2(WATER_MAP - 1));
|
||||
frame.valid = water_axes[(base + uint(nearest.y * WATER_MAP + nearest.x)) * 2u].w;
|
||||
return frame;
|
||||
}
|
||||
|
||||
// a step along the glass (metres, the space of the cab) as a step of the uv
|
||||
vec2 water_to_uv(water_frame_s frame, vec3 step)
|
||||
{
|
||||
float a = dot(frame.tu, frame.tu);
|
||||
float b = dot(frame.tu, frame.tv);
|
||||
float c = dot(frame.tv, frame.tv);
|
||||
float det = a * c - b * b;
|
||||
if (abs(det) < 1e-20)
|
||||
return vec2(0.0);
|
||||
vec2 r = vec2(dot(frame.tu, step), dot(frame.tv, step));
|
||||
return vec2(c * r.x - b * r.y, a * r.y - b * r.x) / det;
|
||||
}
|
||||
|
||||
// the blades at a place of the uv, filtered between the texels of the map
|
||||
vec4 water_blades_at(uint slot, vec2 uv)
|
||||
{
|
||||
uint base = slot * uint(WATER_MAP * WATER_MAP);
|
||||
vec2 p = clamp(uv, vec2(0.0), vec2(1.0)) * float(WATER_MAP) - 0.5;
|
||||
ivec2 i = ivec2(floor(p));
|
||||
vec2 f = p - vec2(i);
|
||||
vec4 result = vec4(0.0);
|
||||
for (int k = 0; k < 4; ++k)
|
||||
{
|
||||
ivec2 texel = clamp(i + ivec2(k & 1, k >> 1), ivec2(0), ivec2(WATER_MAP - 1));
|
||||
float w = (((k & 1) != 0) ? f.x : 1.0 - f.x) * (((k >> 1) != 0) ? f.y : 1.0 - f.y);
|
||||
result += water_blades[base + uint(texel.y * WATER_MAP + texel.x)] * w;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// the blades at the texel of the map holding a place of the uv, unfiltered: where a blade reaches it's at least 0.001 (the
|
||||
// filtered values fall off across a texel at the edge of the sweep)
|
||||
vec4 water_blades_texel(uint slot, vec2 uv)
|
||||
{
|
||||
ivec2 texel = clamp(ivec2(floor(uv * float(WATER_MAP))), ivec2(0), ivec2(WATER_MAP - 1));
|
||||
return water_blades[slot * uint(WATER_MAP * WATER_MAP) + uint(texel.y * WATER_MAP + texel.x)];
|
||||
}
|
||||
|
||||
// a free place for a drop of the glass, taken; ~0u when none was found
|
||||
uint water_claim(uint slot, uint seed)
|
||||
{
|
||||
for (uint k = 0u; k < 8u; ++k)
|
||||
{
|
||||
uint place = water_pcg4d(uvec4(seed, k, slot, 77u)).x % uint(WATER_DROPS);
|
||||
if (atomicCompSwap(water_state[slot * uint(WATER_DROPS) + place], 0u, 1u) == 0u)
|
||||
return place;
|
||||
}
|
||||
return ~0u;
|
||||
}
|
||||
|
||||
// a new drop of the glass: Volume mm3 at a place, a flake of snow when Melted is below 1. false when the glass is full. the
|
||||
// landing rain and snow get only nine tenths of the places, so there are always some for the droplets the moving drops leave
|
||||
bool water_add(uint slot, uint seed, vec2 uv, float volume, float melted, float shape, bool landing)
|
||||
{
|
||||
if ((volume < WATER_LEAST) || any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0))))
|
||||
return false;
|
||||
if (landing && (water_counts_of[slot].x >= uint(WATER_DROPS) / 10u * 9u))
|
||||
return false;
|
||||
uint place = water_claim(slot, seed);
|
||||
if (place == ~0u)
|
||||
return false;
|
||||
atomicAdd(water_counts_of[slot].x, 1u);
|
||||
uint index = slot * uint(WATER_DROPS) + place;
|
||||
water_drops[index].place = vec4(uv, volume, 0.0);
|
||||
water_drops[index].motion = vec4(0.0, 0.0, shape, melted);
|
||||
water_extra[index] = vec4(0.0, 0.004 * (0.6 + 0.8 * shape), 0.0, 0.0);
|
||||
water_target[index] = place;
|
||||
for (uint k = 0u; k < 4u; ++k)
|
||||
water_gather[index * 4u + k] = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
// the slot of the drop on the glass freed, with what was flowing into it
|
||||
void water_remove(uint index)
|
||||
{
|
||||
water_drops[index].place.z = 0.0;
|
||||
water_state[index] = 0u;
|
||||
atomicAdd(water_counts_of[index / uint(WATER_DROPS)].x, ~0u);
|
||||
for (uint k = 0u; k < 4u; ++k)
|
||||
water_gather[index * 4u + k] = 0;
|
||||
}
|
||||
|
||||
// Volume mm3 of water flowing into a drop (index in the storage) from Offset (uv) away from its middle, taken by
|
||||
// water_settle.comp
|
||||
void water_pour(uint index, float volume, vec2 offset)
|
||||
{
|
||||
atomicAdd(water_gather[index * 4u], int(volume * 1e4));
|
||||
atomicAdd(water_gather[index * 4u + 1u], int(offset.x * volume * 1e6));
|
||||
atomicAdd(water_gather[index * 4u + 2u], int(offset.y * volume * 1e6));
|
||||
}
|
||||
|
||||
// the drop lying at a place of the glass which water of Radius (metres) landing there touches, the largest of them, by the grid
|
||||
// of the step before (~0u: none)
|
||||
uint water_landing_on(uint slot, water_frame_s frame, vec2 uv, float radius)
|
||||
{
|
||||
uint cell = water_cell(uv);
|
||||
uint base = water_grid_base(slot);
|
||||
uint count = min(water_cells[base + cell], uint(WATER_CELL_DROPS));
|
||||
uint best = ~0u;
|
||||
float most = 0.0;
|
||||
for (uint k = 0u; k < count; ++k)
|
||||
{
|
||||
uint other = water_cells[base + uint(WATER_GRID * WATER_GRID) + cell * uint(WATER_CELL_DROPS) + k];
|
||||
water_drop_s drop = water_drops[slot * uint(WATER_DROPS) + other];
|
||||
if ((drop.place.z <= most) || (drop.motion.w < 1.0))
|
||||
continue;
|
||||
vec2 step = drop.place.xy - uv;
|
||||
if (length(frame.tu * step.x + frame.tv * step.y) < 0.9 * (radius + water_radius(drop.place.z)))
|
||||
{
|
||||
best = other;
|
||||
most = drop.place.z;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
// water landing on the glass: it flows into a drop it touches, else it's a new drop where it's large enough to be seen
|
||||
void water_land(uint slot, uint seed, water_frame_s frame, vec2 uv, float volume, float shape)
|
||||
{
|
||||
if (any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0))))
|
||||
return;
|
||||
uint onto = water_landing_on(slot, frame, uv, water_radius(volume));
|
||||
if (onto != ~0u)
|
||||
{
|
||||
uint index = slot * uint(WATER_DROPS) + onto;
|
||||
water_pour(index, volume, uv - water_drops[index].place.xy);
|
||||
}
|
||||
else if (volume >= WATER_SEEN)
|
||||
{
|
||||
water_add(slot, seed, uv, volume, 1.0, shape, true);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
65
shaders/ogl46/water_spawn.comp
Normal file
65
shaders/ogl46/water_spawn.comp
Normal file
@@ -0,0 +1,65 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// the water on the windscreens (water_sim.glsl): the rain or the snow landing on the glasses in the step. a thread per landing
|
||||
// tried, a row of groups per glass: a place of the uv picked at random is taken as often as the glass there gets hit (the
|
||||
// metres squared the place holds, how much it faces the rain coming at it), so the landings go evenly over the glass. a raindrop
|
||||
// spreads and throws a splash of droplets around, along the way it came, which flow into the drops they land on (water_settle.comp
|
||||
// takes the water); a flake sticks where it lands
|
||||
|
||||
#include "common.glsl"
|
||||
#include "water_sim.glsl"
|
||||
|
||||
layout(local_size_x = WATER_GROUP_SIZE) in;
|
||||
|
||||
void main()
|
||||
{
|
||||
uint slot = gl_WorkGroupID.y;
|
||||
uint landing = gl_GlobalInvocationID.x;
|
||||
if ((water_slots[slot].z < 0.5) || (float(landing) >= water_slots[slot].x))
|
||||
return;
|
||||
vec4 r = water_random(water_counts.x, slot, landing, 0u);
|
||||
vec2 uv = r.xy;
|
||||
water_frame_s frame = water_frame(slot, uv);
|
||||
if (frame.valid < 0.75)
|
||||
return;
|
||||
// the rain coming at the glass, against the cab
|
||||
vec3 coming = water_air.xyz - water_up.xyz * water_air.w;
|
||||
float speed = length(coming);
|
||||
if (speed < 1e-3)
|
||||
return;
|
||||
float facing = max(-dot(coming, frame.normal), 0.0) / speed;
|
||||
float density = length(cross(frame.tu, frame.tv));
|
||||
if (r.z >= density * water_slots[slot].y * facing)
|
||||
return;
|
||||
float scale = max(water_slots[slot].w, 0.05);
|
||||
vec4 s = water_random(water_counts.x, slot, landing, 1u);
|
||||
uint seed = water_pcg4d(uvec4(water_counts.x, slot, landing, 2u)).x;
|
||||
if (water_step.w > 0.5)
|
||||
{
|
||||
// a flake, a few mm of snow holding a little water
|
||||
water_add(slot, seed, uv, mix(0.3, 2.5, s.x) * scale * scale * scale, 0.0, s.y, true);
|
||||
return;
|
||||
}
|
||||
// the diameter of the raindrop, mm: above the least landing as a drop of its own they go as Marshall-Palmer (exponential)
|
||||
float diameter = min(water_rain.x - log(max(r.w, 1e-6)) / max(water_rain.y, 0.1), 5.0) * scale;
|
||||
float drop = 0.5236 * diameter * diameter * diameter;
|
||||
// most of it stays where it hit, the rest is thrown around as droplets, forward along the glass
|
||||
float kept = mix(0.55, 0.85, s.x);
|
||||
water_land(slot, seed, frame, uv, drop * kept, s.y);
|
||||
vec3 along = coming - frame.normal * dot(coming, frame.normal);
|
||||
float glancing = length(along) / speed;
|
||||
vec3 forward = (glancing > 1e-3) ? normalize(along) : normalize(frame.tu);
|
||||
vec3 across = cross(frame.normal, forward);
|
||||
float radius = water_radius(drop * kept);
|
||||
int splash = 2 + int(s.z * 4.0);
|
||||
for (int k = 0; k < splash; ++k)
|
||||
{
|
||||
vec4 e = water_random(water_counts.x, slot, landing, 3u + uint(k));
|
||||
float angle = e.x * 6.2831853;
|
||||
vec3 way = cos(angle) * forward + sin(angle) * across + forward * (1.5 * glancing);
|
||||
way = (length(way) > 1e-3) ? normalize(way) : forward;
|
||||
float distance = radius * (1.3 + 2.5 * e.y) * (1.0 + glancing);
|
||||
float volume = drop * (1.0 - kept) / float(splash) * mix(0.4, 1.6, e.z);
|
||||
water_land(slot, seed + 101u * uint(k + 1), frame, uv + water_to_uv(frame, way * distance), volume, e.w);
|
||||
}
|
||||
}
|
||||
34
shaders/ogl46/water_trail.comp
Normal file
34
shaders/ogl46/water_trail.comp
Normal file
@@ -0,0 +1,34 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// the water on the windscreens (water_sim.glsl): the blades going over the wetted paths in the step wipe them. a thread per texel
|
||||
// of the map of the paths, a row of groups per glass
|
||||
|
||||
#include "common.glsl"
|
||||
#include "water_sim.glsl"
|
||||
|
||||
layout(local_size_x = WATER_GROUP_SIZE) in;
|
||||
|
||||
void main()
|
||||
{
|
||||
uint slot = gl_WorkGroupID.y;
|
||||
uint texel = gl_GlobalInvocationID.x;
|
||||
if ((water_slots[slot].z < 0.5) || (texel >= uint(WATER_TRAIL * WATER_TRAIL)))
|
||||
return;
|
||||
uint index = water_trail_base(slot) + texel;
|
||||
if (water_cells[index] == 0u)
|
||||
return;
|
||||
vec2 uv = (vec2(float(texel % uint(WATER_TRAIL)), float(texel / uint(WATER_TRAIL))) + 0.5) / float(WATER_TRAIL);
|
||||
vec4 mask = water_blades_at(slot, uv);
|
||||
for (int k = 0; k < 4; ++k)
|
||||
{
|
||||
float before = water_blades_before[k];
|
||||
float now = water_blades_now[k];
|
||||
if ((mask[k] < 0.001) || (before < 0.0) || (now < 0.0) || (before == now))
|
||||
continue;
|
||||
if ((mask[k] > min(before, now)) && (mask[k] <= max(before, now)))
|
||||
{
|
||||
water_cells[index] = 0u;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,8 @@
|
||||
#define HEADLIGHT_SHADOW_TEX 23
|
||||
#define SCENE_DEPTH_TEX 24
|
||||
#define SSR_HISTORY_TEX 25
|
||||
#define PASS_BINDING_COUNT 26
|
||||
#define WATER_SSBO 26
|
||||
#define PASS_BINDING_COUNT 27
|
||||
#define MATERIAL_TEXTURE_FIRST 0
|
||||
#define CL_CLUSTER_UBO 0
|
||||
#define CL_AABB_SSBO 1
|
||||
@@ -121,6 +122,13 @@
|
||||
#define SHADOW_FIT_GROUP_SIZE 16
|
||||
#define LOCAL_SHADOW_NEAR 0.150000
|
||||
#define PARTICLE_GROUP_SIZE 64
|
||||
#define WATER_SLOTS 4
|
||||
#define WATER_DROPS 65536
|
||||
#define WATER_GRID 128
|
||||
#define WATER_CELL_DROPS 32
|
||||
#define WATER_TRAIL 1024
|
||||
#define WATER_MAP 256
|
||||
#define WATER_GROUP_SIZE 64
|
||||
#endif
|
||||
|
||||
#include "settings.glsl"
|
||||
@@ -271,6 +279,8 @@ layout(std140, set = PASS_SET, binding = SCENE_UBO) uniform scene_ubo
|
||||
vec4 ssr_params;
|
||||
vec4 ssr_params2;
|
||||
vec4 env_params;
|
||||
// the air against the vehicle of the camera, see gl::scene_ubs
|
||||
vec4 rain_wind;
|
||||
};
|
||||
|
||||
#ifndef LAYOUT_COMPUTE
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
114
shaders/vk14/water.glsl
Normal file
114
shaders/vk14/water.glsl
Normal file
@@ -0,0 +1,114 @@
|
||||
// the water on the windscreens (gfx::windscreen_water, gl::WATER_* in gl/ubo.h, the vulkan renderer: vk14::windscreen_water): what the passes of the simulation (water_*.comp)
|
||||
// keep and the glasses (mat_rain_windscreen.frag) read. a glass is a material of mat_rain_windscreen in the cab of the train, a
|
||||
// slot of the storage; its drops lie in the coordinates of its texture (uv), as its mask of the wipers does, so they stay where
|
||||
// they lie on the glass whatever the vehicle and the camera do. the map of the glass tells what a step of the uv is along the
|
||||
// glass (metres, in the space of the model of the cab) and where the blades go over it; the drops are looked up through a grid
|
||||
// of cells over the uv, each listing the drops which reach into it; the wetted paths are marked on a map of their own with the
|
||||
// time the water went there
|
||||
|
||||
#ifndef WATER_GLSL
|
||||
#define WATER_GLSL
|
||||
|
||||
struct water_drop_s
|
||||
{
|
||||
// xy: the place, uv; z: the water, mm3 (0: no drop there); w: seconds it lies on the glass
|
||||
vec4 place;
|
||||
// xy: the velocity, uv per second; z: random 0 .. 1, its shape; w: 0 .. 1 how far it melted from a flake of snow (1: water)
|
||||
vec4 motion;
|
||||
};
|
||||
|
||||
#if defined(STAGE_COMPUTE)
|
||||
// the compute kernels: set COMPUTE_SET pushed with the dispatch, the storage at 0, the block of the step at 1 (water_sim.glsl)
|
||||
layout(std430, set = COMPUTE_SET, binding = 0) buffer water_ssbo
|
||||
#else
|
||||
layout(std430, set = PASS_SET, binding = WATER_SSBO) readonly buffer water_ssbo
|
||||
#endif
|
||||
{
|
||||
// every glass: x 1 when it's simulated, y seconds its simulation runs (the time of the wetted paths), zw unused
|
||||
vec4 water_header[WATER_SLOTS];
|
||||
// every glass: x the drops on it, yzw unused
|
||||
uvec4 water_counts_of[WATER_SLOTS];
|
||||
water_drop_s water_drops[WATER_SLOTS * WATER_DROPS];
|
||||
// the map, two for a texel: xyz the step along the glass for a step of u (metres), w 1 on the glass, 0.5 next to it (the edges
|
||||
// spread out, for the filtering), 0 elsewhere; xyz the same for v, w 1 when the cross product of the two points out of the cab,
|
||||
// -1 when it points in
|
||||
vec4 water_axes[WATER_SLOTS * WATER_MAP * WATER_MAP * 2];
|
||||
// the mask of the wipers baked into the map: the place of a blade over its sweep at which it goes over the texel, 0 where it
|
||||
// doesn't reach
|
||||
vec4 water_blades[WATER_SLOTS * WATER_MAP * WATER_MAP];
|
||||
// every drop: 0 the place is free, else bit 0 set, bits 1 .. 3 the blade carrying it (1 + its number, 0 none), bit 4 set when
|
||||
// the blade carries it towards the far end of its sweep
|
||||
uint water_state[WATER_SLOTS * WATER_DROPS];
|
||||
// the drop it flows into (itself when none)
|
||||
uint water_target[WATER_SLOTS * WATER_DROPS];
|
||||
// what flowed into it: the water (1e-4 mm3) and the moment of the places of the drops about its own (1e-6 uv mm3)
|
||||
int water_gather[WATER_SLOTS * WATER_DROPS * 4];
|
||||
// x: the way it went since it left a droplet behind, metres; y: the way it goes on before it leaves the next one, metres
|
||||
vec4 water_extra[WATER_SLOTS * WATER_DROPS];
|
||||
// the grids of the glasses, WATER_GRID x WATER_GRID cells each: the counts of the drops of the cells, then the drops of each
|
||||
// cell (WATER_CELL_DROPS for a cell, the ones over the count aren't there). then the wetted paths of the glasses,
|
||||
// WATER_TRAIL x WATER_TRAIL each: in the upper 24 bits 1 + the time the water went over the texel last, 1/2 s (0: never),
|
||||
// in the lower 8 how near the middle of the path the texel is, 0 .. 255 (filtered between the texels the edges of the paths
|
||||
// come out smooth wherever they go across the texels; the time is coarse, so a drop going along leaves the nearness of its
|
||||
// whole passage, not of the step which happened to come last)
|
||||
uint water_cells[];
|
||||
};
|
||||
|
||||
// the radius of the footprint of a drop holding Volume mm3 of water, metres: a cap of a sphere meeting the glass at 40 degrees
|
||||
float water_radius(float volume)
|
||||
{
|
||||
return 0.001 * pow(1.675 * max(volume, 0.0), 1.0 / 3.0);
|
||||
}
|
||||
|
||||
// the diameter of a flake of snow holding Volume mm3 of water, metres
|
||||
float water_flake(float volume)
|
||||
{
|
||||
return 0.006 * pow(max(volume, 0.0), 1.0 / 3.0);
|
||||
}
|
||||
|
||||
// where the grid of a glass begins in water_cells: the count of a cell at the base + the cell, its drops from the base +
|
||||
// WATER_GRID * WATER_GRID + the cell * WATER_CELL_DROPS
|
||||
uint water_grid_base(uint slot)
|
||||
{
|
||||
return slot * uint(WATER_GRID * WATER_GRID * (1 + WATER_CELL_DROPS));
|
||||
}
|
||||
|
||||
uint water_trail_base(uint slot)
|
||||
{
|
||||
return uint(WATER_SLOTS * WATER_GRID * WATER_GRID * (1 + WATER_CELL_DROPS)) + slot * uint(WATER_TRAIL * WATER_TRAIL);
|
||||
}
|
||||
|
||||
// the cell of the grid holding a place of the glass
|
||||
uint water_cell(vec2 uv)
|
||||
{
|
||||
ivec2 cell = clamp(ivec2(floor(uv * float(WATER_GRID))), ivec2(0), ivec2(WATER_GRID - 1));
|
||||
return uint(cell.y * WATER_GRID + cell.x);
|
||||
}
|
||||
|
||||
// how fresh the wetted path is at a texel of the map of the paths, 1 just wetted in its middle .. 0 dry long ago or never; Now:
|
||||
// seconds the simulation of the glass runs, Lasting: seconds in which the freshness falls to 1/e
|
||||
float water_wetted(uint slot, ivec2 texel, float now, float lasting)
|
||||
{
|
||||
texel = clamp(texel, ivec2(0), ivec2(WATER_TRAIL - 1));
|
||||
uint value = water_cells[water_trail_base(slot) + uint(texel.y * WATER_TRAIL + texel.x)];
|
||||
uint stamp = value >> 8u;
|
||||
if (stamp == 0u)
|
||||
return 0.0;
|
||||
float age = max(now - float(stamp - 1u) / 2.0, 0.0);
|
||||
return exp(-age / lasting) * (float(value & 255u) / 255.0);
|
||||
}
|
||||
|
||||
// the same filtered between the texels at a place of the glass
|
||||
float water_wetted_at(uint slot, vec2 uv, float now, float lasting)
|
||||
{
|
||||
vec2 p = uv * float(WATER_TRAIL) - 0.5;
|
||||
ivec2 i = ivec2(floor(p));
|
||||
vec2 f = p - vec2(i);
|
||||
float a = water_wetted(slot, i, now, lasting);
|
||||
float b = water_wetted(slot, i + ivec2(1, 0), now, lasting);
|
||||
float c = water_wetted(slot, i + ivec2(0, 1), now, lasting);
|
||||
float d = water_wetted(slot, i + ivec2(1, 1), now, lasting);
|
||||
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
|
||||
}
|
||||
|
||||
#endif
|
||||
27
shaders/vk14/water_bake.comp
Normal file
27
shaders/vk14/water_bake.comp
Normal file
@@ -0,0 +1,27 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// the water on the windscreens (water_sim.glsl): the mask of the wipers of a glass baked into its map, once, when the glass is
|
||||
// set up. a thread per texel of the map; the glass is water_counts.z
|
||||
|
||||
// none of the pass set: everything the kernel reads is pushed with its dispatch (set COMPUTE_SET, water_sim.glsl), so it runs
|
||||
// before the passes of the frame set theirs up
|
||||
#define LAYOUT_UI 1
|
||||
#include "common.glsl"
|
||||
#include "water_sim.glsl"
|
||||
|
||||
layout(local_size_x = WATER_GROUP_SIZE) in;
|
||||
|
||||
// the mask of the wipers of the material of the glass (wipermask)
|
||||
layout(set = COMPUTE_SET, binding = 2) uniform sampler2D water_mask;
|
||||
|
||||
void main()
|
||||
{
|
||||
uint slot = water_counts.z;
|
||||
uint texel = gl_GlobalInvocationID.x;
|
||||
if ((slot >= uint(WATER_SLOTS)) || (texel >= uint(WATER_MAP * WATER_MAP)))
|
||||
return;
|
||||
vec2 uv = (vec2(float(texel % uint(WATER_MAP)), float(texel / uint(WATER_MAP))) + 0.5) / float(WATER_MAP);
|
||||
// as much of the mask as a texel of the map covers
|
||||
float lod = log2(max(float(textureSize(water_mask, 0).x) / float(WATER_MAP), 1.0));
|
||||
water_blades[slot * uint(WATER_MAP * WATER_MAP) + texel] = textureLod(water_mask, uv, lod);
|
||||
}
|
||||
21
shaders/vk14/water_clear.comp
Normal file
21
shaders/vk14/water_clear.comp
Normal file
@@ -0,0 +1,21 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// the water on the windscreens (water_sim.glsl): the counts of the cells of the grids emptied before the drops are put in them
|
||||
// (water_grid.comp). a thread per cell, a row of groups per glass
|
||||
|
||||
// none of the pass set: everything the kernel reads is pushed with its dispatch (set COMPUTE_SET, water_sim.glsl), so it runs
|
||||
// before the passes of the frame set theirs up
|
||||
#define LAYOUT_UI 1
|
||||
#include "common.glsl"
|
||||
#include "water_sim.glsl"
|
||||
|
||||
layout(local_size_x = WATER_GROUP_SIZE) in;
|
||||
|
||||
void main()
|
||||
{
|
||||
uint slot = gl_WorkGroupID.y;
|
||||
uint cell = gl_GlobalInvocationID.x;
|
||||
if ((water_slots[slot].z < 0.5) || (cell >= uint(WATER_GRID * WATER_GRID)))
|
||||
return;
|
||||
water_cells[water_grid_base(slot) + cell] = 0u;
|
||||
}
|
||||
43
shaders/vk14/water_gather.comp
Normal file
43
shaders/vk14/water_gather.comp
Normal file
@@ -0,0 +1,43 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// the water on the windscreens (water_sim.glsl): the drops flow into the ones water_merge.comp found for them, where those don't
|
||||
// flow on themselves (they do in a later step): the water and the moment of the places of the drops gathered by the one they
|
||||
// flow into (water_settle.comp moves it to the middle of the water), the drops gone. a thread per drop, a row of groups per glass
|
||||
|
||||
// none of the pass set: everything the kernel reads is pushed with its dispatch (set COMPUTE_SET, water_sim.glsl), so it runs
|
||||
// before the passes of the frame set theirs up
|
||||
#define LAYOUT_UI 1
|
||||
#include "common.glsl"
|
||||
#include "water_sim.glsl"
|
||||
|
||||
layout(local_size_x = WATER_GROUP_SIZE) in;
|
||||
|
||||
void main()
|
||||
{
|
||||
uint slot = gl_WorkGroupID.y;
|
||||
uint place = gl_GlobalInvocationID.x;
|
||||
if ((water_slots[slot].z < 0.5) || (place >= uint(WATER_DROPS)))
|
||||
return;
|
||||
uint base = slot * uint(WATER_DROPS);
|
||||
uint index = base + place;
|
||||
uint target = water_target[index];
|
||||
if ((target == place) || (target >= uint(WATER_DROPS)) || (water_target[base + target] != target))
|
||||
return;
|
||||
water_drop_s drop = water_drops[index];
|
||||
water_drop_s into = water_drops[base + target];
|
||||
if ((drop.place.z <= 0.0) || (into.place.z <= 0.0))
|
||||
return;
|
||||
// with what flowed into the drop itself in the step (the landings)
|
||||
float landed = float(water_gather[index * 4u]) * 1e-4;
|
||||
vec2 moment = vec2(float(water_gather[index * 4u + 1u]), float(water_gather[index * 4u + 2u])) * 1e-6;
|
||||
float volume = drop.place.z + landed;
|
||||
vec2 offset = drop.place.xy - into.place.xy;
|
||||
atomicAdd(water_gather[(base + target) * 4u], int(volume * 1e4));
|
||||
atomicAdd(water_gather[(base + target) * 4u + 1u], int((offset.x * volume + moment.x) * 1e6));
|
||||
atomicAdd(water_gather[(base + target) * 4u + 2u], int((offset.y * volume + moment.y) * 1e6));
|
||||
// the blade carrying the drop carries what it flows into
|
||||
uint carried = water_state[index] & 0x1eu;
|
||||
if ((carried != 0u) && ((water_state[base + target] & 0x0eu) == 0u))
|
||||
atomicOr(water_state[base + target], carried);
|
||||
water_remove(index);
|
||||
}
|
||||
55
shaders/vk14/water_grid.comp
Normal file
55
shaders/vk14/water_grid.comp
Normal file
@@ -0,0 +1,55 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// the water on the windscreens (water_sim.glsl): every drop put in the cells of the grid it reaches into, as far as the glasses
|
||||
// draw it (a sliding one is drawn out behind itself) and as far as the drops it may flow together with are. a thread per drop,
|
||||
// a row of groups per glass
|
||||
|
||||
// none of the pass set: everything the kernel reads is pushed with its dispatch (set COMPUTE_SET, water_sim.glsl), so it runs
|
||||
// before the passes of the frame set theirs up
|
||||
#define LAYOUT_UI 1
|
||||
#include "common.glsl"
|
||||
#include "water_sim.glsl"
|
||||
|
||||
layout(local_size_x = WATER_GROUP_SIZE) in;
|
||||
|
||||
void main()
|
||||
{
|
||||
uint slot = gl_WorkGroupID.y;
|
||||
uint place = gl_GlobalInvocationID.x;
|
||||
if ((water_slots[slot].z < 0.5) || (place >= uint(WATER_DROPS)))
|
||||
return;
|
||||
water_drop_s drop = water_drops[slot * uint(WATER_DROPS) + place];
|
||||
if (drop.place.z <= 0.0)
|
||||
return;
|
||||
water_frame_s frame = water_frame(slot, drop.place.xy);
|
||||
float lu = length(frame.tu);
|
||||
float lv = length(frame.tv);
|
||||
if ((lu < 1e-6) || (lv < 1e-6))
|
||||
return;
|
||||
float reach;
|
||||
if (drop.motion.w < 1.0)
|
||||
{
|
||||
reach = 0.6 * water_flake(drop.place.z);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool sliding = length(frame.tu * drop.motion.x + frame.tv * drop.motion.y) > 0.002;
|
||||
reach = water_radius(drop.place.z) * (sliding ? 3.6 : 2.2);
|
||||
}
|
||||
vec2 extent = vec2(reach / lu, reach / lv);
|
||||
ivec2 lower = clamp(ivec2(floor((drop.place.xy - extent) * float(WATER_GRID))), ivec2(0), ivec2(WATER_GRID - 1));
|
||||
ivec2 upper = clamp(ivec2(floor((drop.place.xy + extent) * float(WATER_GRID))), ivec2(0), ivec2(WATER_GRID - 1));
|
||||
// a drop can't be larger than this many cells, it would only flood the lists
|
||||
upper = min(upper, lower + ivec2(6));
|
||||
uint base = water_grid_base(slot);
|
||||
for (int y = lower.y; y <= upper.y; ++y)
|
||||
{
|
||||
for (int x = lower.x; x <= upper.x; ++x)
|
||||
{
|
||||
uint cell = uint(y * WATER_GRID + x);
|
||||
uint count = atomicAdd(water_cells[base + cell], 1u);
|
||||
if (count < uint(WATER_CELL_DROPS))
|
||||
water_cells[base + uint(WATER_GRID * WATER_GRID) + cell * uint(WATER_CELL_DROPS) + count] = place;
|
||||
}
|
||||
}
|
||||
}
|
||||
66
shaders/vk14/water_merge.comp
Normal file
66
shaders/vk14/water_merge.comp
Normal file
@@ -0,0 +1,66 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// the water on the windscreens (water_sim.glsl): the drops which touch flow together, the way surface tension pulls them. every
|
||||
// drop finds the largest of the drops it touches which are larger than itself (by the cell of the grid its middle is in, which
|
||||
// lists every drop reaching into it); the water goes over to that one in water_gather.comp. flakes of snow don't flow, but the
|
||||
// ones a blade pushes pile up. a thread per drop, a row of groups per glass
|
||||
|
||||
// none of the pass set: everything the kernel reads is pushed with its dispatch (set COMPUTE_SET, water_sim.glsl), so it runs
|
||||
// before the passes of the frame set theirs up
|
||||
#define LAYOUT_UI 1
|
||||
#include "common.glsl"
|
||||
#include "water_sim.glsl"
|
||||
|
||||
layout(local_size_x = WATER_GROUP_SIZE) in;
|
||||
|
||||
void main()
|
||||
{
|
||||
uint slot = gl_WorkGroupID.y;
|
||||
uint place = gl_GlobalInvocationID.x;
|
||||
if ((water_slots[slot].z < 0.5) || (place >= uint(WATER_DROPS)))
|
||||
return;
|
||||
uint index = slot * uint(WATER_DROPS) + place;
|
||||
water_drop_s drop = water_drops[index];
|
||||
water_target[index] = place;
|
||||
if (drop.place.z <= 0.0)
|
||||
return;
|
||||
bool flake = drop.motion.w < 1.0;
|
||||
bool carried = ((water_state[index] >> 1u) & 7u) != 0u;
|
||||
if (flake && (false == carried))
|
||||
return;
|
||||
water_frame_s frame = water_frame(slot, drop.place.xy);
|
||||
float size = flake ? 0.4 * water_flake(drop.place.z) : water_radius(drop.place.z);
|
||||
uint cell = water_cell(drop.place.xy);
|
||||
uint base = water_grid_base(slot);
|
||||
uint count = min(water_cells[base + cell], uint(WATER_CELL_DROPS));
|
||||
uint best = place;
|
||||
float most = drop.place.z;
|
||||
for (uint k = 0u; k < count; ++k)
|
||||
{
|
||||
uint other = water_cells[base + uint(WATER_GRID * WATER_GRID) + cell * uint(WATER_CELL_DROPS) + k];
|
||||
if (other == place)
|
||||
continue;
|
||||
uint otherindex = slot * uint(WATER_DROPS) + other;
|
||||
water_drop_s neighbour = water_drops[otherindex];
|
||||
float volume = neighbour.place.z;
|
||||
if ((volume < most) || ((volume == most) && (other > best)) || (volume <= 0.0))
|
||||
continue;
|
||||
if ((volume == drop.place.z) && (other > place))
|
||||
continue;
|
||||
bool otherflake = neighbour.motion.w < 1.0;
|
||||
if (otherflake != flake)
|
||||
continue;
|
||||
// the snow a blade pushes piles up into lumps a few centimetres across, not more
|
||||
if (otherflake && ((((water_state[otherindex] >> 1u) & 7u) == 0u) || (volume + drop.place.z > 20.0)))
|
||||
continue;
|
||||
vec2 step = neighbour.place.xy - drop.place.xy;
|
||||
float apart = length(frame.tu * step.x + frame.tv * step.y);
|
||||
float reach = size + (otherflake ? 0.4 * water_flake(volume) : water_radius(volume));
|
||||
if (apart < 0.92 * reach)
|
||||
{
|
||||
best = other;
|
||||
most = volume;
|
||||
}
|
||||
}
|
||||
water_target[index] = best;
|
||||
}
|
||||
257
shaders/vk14/water_move.comp
Normal file
257
shaders/vk14/water_move.comp
Normal file
@@ -0,0 +1,257 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// the water on the windscreens (water_sim.glsl): every drop through the step. a thread per drop, a row of groups per glass.
|
||||
// a blade going over the drop takes it along in front of itself until it turns back at the end of its sweep or stops, or until
|
||||
// the drop is pushed out of the sweep; a drop of its own slides when gravity and the air pull it harder than its line of
|
||||
// contact holds it, wetting its path and leaving droplets behind; a flake melts into a drop
|
||||
|
||||
// none of the pass set: everything the kernel reads is pushed with its dispatch (set COMPUTE_SET, water_sim.glsl), so it runs
|
||||
// before the passes of the frame set theirs up
|
||||
#define LAYOUT_UI 1
|
||||
#include "common.glsl"
|
||||
#include "water_sim.glsl"
|
||||
|
||||
layout(local_size_x = WATER_GROUP_SIZE) in;
|
||||
|
||||
// how fresh the wetted path is at a place of the glass
|
||||
float water_fresh(uint slot, vec2 uv)
|
||||
{
|
||||
return water_wetted_at(slot, uv, water_step.y, 8.0);
|
||||
}
|
||||
|
||||
// the path from one place to another marked wetted, Halfwidth (metres) to the sides: the texels around it get the time and how
|
||||
// near they are to the middle of the path
|
||||
void water_wet_path(uint slot, water_frame_s frame, vec2 from, vec2 to, float halfwidth)
|
||||
{
|
||||
uint stamp = (uint(water_step.y * 2.0) + 1u) << 8u;
|
||||
uint base = water_trail_base(slot);
|
||||
float texels = float(WATER_TRAIL);
|
||||
// the half width in texels, a texel and a half more for the filtering
|
||||
float reach = halfwidth / max(min(length(frame.tu), length(frame.tv)), 1e-6) * texels + 1.5;
|
||||
int spread = clamp(int(ceil(reach)), 1, 3);
|
||||
vec2 a = from * texels - 0.5;
|
||||
vec2 b = to * texels - 0.5;
|
||||
int steps = clamp(int(ceil(length(b - a) * 2.0)), 1, 24);
|
||||
for (int s = 1; s <= steps; ++s)
|
||||
{
|
||||
vec2 point = mix(a, b, float(s) / float(steps));
|
||||
ivec2 centre = ivec2(floor(point + 0.5));
|
||||
for (int y = -spread; y <= spread; ++y)
|
||||
{
|
||||
for (int x = -spread; x <= spread; ++x)
|
||||
{
|
||||
ivec2 texel = centre + ivec2(x, y);
|
||||
if (any(lessThan(texel, ivec2(0))) || any(greaterThanEqual(texel, ivec2(WATER_TRAIL))))
|
||||
continue;
|
||||
float near = 1.0 - length(vec2(texel) - point) / reach;
|
||||
if (near <= 0.0)
|
||||
continue;
|
||||
atomicMax(water_cells[base + uint(texel.y * WATER_TRAIL + texel.x)], stamp | uint(near * 255.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
uint slot = gl_WorkGroupID.y;
|
||||
uint place = gl_GlobalInvocationID.x;
|
||||
if ((water_slots[slot].z < 0.5) || (place >= uint(WATER_DROPS)))
|
||||
return;
|
||||
uint index = slot * uint(WATER_DROPS) + place;
|
||||
water_drop_s drop = water_drops[index];
|
||||
if (drop.place.z <= 0.0)
|
||||
return;
|
||||
float seconds = water_step.x;
|
||||
uint state = water_state[index];
|
||||
vec4 extra = water_extra[index];
|
||||
vec2 uv = drop.place.xy;
|
||||
water_frame_s frame = water_frame(slot, uv);
|
||||
if (frame.valid < 0.25)
|
||||
{
|
||||
water_remove(index);
|
||||
return;
|
||||
}
|
||||
uint seed = water_pcg4d(uvec4(water_counts.x, slot, place, 9u)).x;
|
||||
|
||||
// a flake melts into a drop: the glass is warmed by the cab, the faster the warmer the air
|
||||
bool flake = drop.motion.w < 1.0;
|
||||
if (flake)
|
||||
{
|
||||
float warm = water_up.w;
|
||||
float melt = (warm > 0.5) ? mix(3.0, 10.0, drop.motion.z) / (1.0 + 0.25 * warm) : mix(15.0, 45.0, drop.motion.z);
|
||||
drop.motion.w = min(drop.motion.w + seconds / melt, 1.0);
|
||||
flake = drop.motion.w < 1.0;
|
||||
}
|
||||
|
||||
// the blades: one going over the drop in the step takes it along
|
||||
uint carried = (state >> 1u) & 7u;
|
||||
float way = (((state >> 4u) & 1u) != 0u) ? 1.0 : -1.0;
|
||||
vec4 mask = water_blades_at(slot, uv);
|
||||
vec4 reached = water_blades_texel(slot, uv);
|
||||
for (int k = 0; k < 4; ++k)
|
||||
{
|
||||
float before = water_blades_before[k];
|
||||
float now = water_blades_now[k];
|
||||
if ((before < 0.0) || (now < 0.0))
|
||||
{
|
||||
if (carried == uint(k + 1))
|
||||
carried = 0u;
|
||||
continue;
|
||||
}
|
||||
float moved = now - before;
|
||||
if (carried == uint(k + 1))
|
||||
{
|
||||
// the blade turned back at the end of its sweep or stopped: the water stays where it brought it
|
||||
if ((moved * way <= 0.0) || (now >= 0.995) || (now <= 0.005))
|
||||
carried = 0u;
|
||||
}
|
||||
// a blade goes over a drop which was ahead of it (by a little more than nothing: the water it left where it turned back at
|
||||
// the end of its sweep isn't taken back) and is behind it now
|
||||
else if ((carried == 0u) && (moved != 0.0) && (reached[k] >= 0.001) && ((mask[k] - before) * sign(moved) > 0.003) && ((mask[k] - now) * sign(moved) <= 0.0))
|
||||
{
|
||||
carried = uint(k + 1);
|
||||
way = (moved > 0.0) ? 1.0 : -1.0;
|
||||
// the blade doesn't take all of it: now and then a droplet stays behind
|
||||
vec4 r = water_random(seed, 5u, place, slot);
|
||||
if ((false == flake) && (drop.place.z > 1.0) && (r.x < 0.35))
|
||||
{
|
||||
float left = mix(0.02, 0.08, r.y);
|
||||
if (water_add(slot, seed ^ 0x9e3779b9u, uv, left, 1.0, r.z, false))
|
||||
drop.place.z -= left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vec3 velocity = frame.tu * drop.motion.x + frame.tv * drop.motion.y;
|
||||
if (carried != 0u)
|
||||
{
|
||||
// in front of the blade by its own size and the lip of the bead: the place where the mask is as far ahead of the blade,
|
||||
// found along the gradient of the mask (along the arc of the blade)
|
||||
int k = int(carried) - 1;
|
||||
float radius = flake ? 0.5 * water_flake(drop.place.z) : water_radius(drop.place.z);
|
||||
float a = dot(frame.tu, frame.tu);
|
||||
float b = dot(frame.tu, frame.tv);
|
||||
float c = dot(frame.tv, frame.tv);
|
||||
float det = max(a * c - b * b, 1e-20);
|
||||
float h = 1.0 / float(WATER_MAP);
|
||||
bool edge = false;
|
||||
for (int step = 0; step < 4; ++step)
|
||||
{
|
||||
// at the edge of the sweep (the end of the blade) the water squeezes out and stays
|
||||
if ((water_blades_texel(slot, uv + vec2(h, 0.0))[k] < 0.001) || (water_blades_texel(slot, uv - vec2(h, 0.0))[k] < 0.001) ||
|
||||
(water_blades_texel(slot, uv + vec2(0.0, h))[k] < 0.001) || (water_blades_texel(slot, uv - vec2(0.0, h))[k] < 0.001))
|
||||
{
|
||||
edge = true;
|
||||
break;
|
||||
}
|
||||
float m = water_blades_at(slot, uv)[k];
|
||||
vec2 gradient = vec2(water_blades_at(slot, uv + vec2(h, 0.0))[k] - water_blades_at(slot, uv - vec2(h, 0.0))[k],
|
||||
water_blades_at(slot, uv + vec2(0.0, h))[k] - water_blades_at(slot, uv - vec2(0.0, h))[k]) / (2.0 * h);
|
||||
float g2 = dot(gradient, gradient);
|
||||
if (g2 < 1e-6)
|
||||
break;
|
||||
// how fast the mask changes along the glass, per metre
|
||||
float permetre = sqrt(max((c * gradient.x * gradient.x - 2.0 * b * gradient.x * gradient.y + a * gradient.y * gradient.y) / det, 0.0));
|
||||
float target = water_blades_now[k] + way * (0.0015 + radius) * permetre;
|
||||
vec2 delta = gradient * ((target - m) / g2);
|
||||
float length_delta = length(delta);
|
||||
if (length_delta > 0.02)
|
||||
delta *= 0.02 / length_delta;
|
||||
uv += delta;
|
||||
if (length_delta < 1e-5)
|
||||
break;
|
||||
}
|
||||
// pushed out of the sweep it stays at its edge
|
||||
if (edge || (water_blades_texel(slot, uv)[k] < 0.001))
|
||||
carried = 0u;
|
||||
velocity = vec3(0.0);
|
||||
}
|
||||
else if (false == flake)
|
||||
{
|
||||
float volume = drop.place.z;
|
||||
float radius = water_radius(volume);
|
||||
vec3 n = frame.normal;
|
||||
vec3 up = water_up.xyz;
|
||||
// gravity along the glass
|
||||
float weight = 1000.0 * WATER_GRAVITY * volume * 1e-9;
|
||||
vec3 force = -(up - n * dot(up, n)) * weight;
|
||||
// the drag of the air going along the glass, with a share of the air meeting it head on turned up along it
|
||||
vec3 air = water_air.xyz;
|
||||
vec3 rise = up - n * dot(up, n);
|
||||
rise = (length(rise) > 0.1) ? normalize(rise) : vec3(0.0);
|
||||
vec3 flow = (air - n * dot(air, n) + rise * (max(-dot(air, n), 0.0) * WATER_HEAD_ON)) * WATER_BOUNDARY;
|
||||
float area = 2.0 * radius * (0.364 * radius) * 0.8;
|
||||
force += flow * (0.6 * area * length(flow));
|
||||
// the hold of the line of contact, uneven over the glass, much less on a path wetted lately
|
||||
vec2 spread = uv * vec2(length(frame.tu), length(frame.tv));
|
||||
float uneven = mix(0.55, 1.45, water_noise(spread / 0.003, 3u + slot));
|
||||
float hold = WATER_TENSION * 2.0 * radius * WATER_HOLD * uneven * mix(1.0, 0.3, water_fresh(slot, uv)) * mix(0.85, 1.15, drop.motion.z);
|
||||
float pull = length(force);
|
||||
vec3 target = vec3(0.0);
|
||||
if (pull > hold)
|
||||
{
|
||||
vec3 dir = force / pull;
|
||||
// the way bent towards the side which holds the drop less: the wetter one (the water keeps to the wetted paths) and the
|
||||
// one where the glass is cleaner, so it meanders
|
||||
vec3 side = cross(n, dir);
|
||||
float offset = max(radius, 0.0015);
|
||||
vec2 leftuv = uv + water_to_uv(frame, side * offset);
|
||||
vec2 rightuv = uv - water_to_uv(frame, side * offset);
|
||||
float wetter = water_fresh(slot, leftuv) - water_fresh(slot, rightuv);
|
||||
float cleaner = water_noise(rightuv * vec2(length(frame.tu), length(frame.tv)) / 0.003, 3u + slot) - water_noise(leftuv * vec2(length(frame.tu), length(frame.tv)) / 0.003, 3u + slot);
|
||||
float wander = water_noise(spread / 0.02 + vec2(17.0 * drop.motion.z), 11u + slot) - 0.5;
|
||||
dir = normalize(dir + side * (wetter * 0.6 + cleaner * 0.5 + wander * 0.35));
|
||||
target = dir * min((pull - hold) / (WATER_VISCOUS * radius), 1.5);
|
||||
}
|
||||
velocity += (target - velocity) * (1.0 - exp(-seconds / ((pull > hold) ? 0.05 : 0.015)));
|
||||
velocity -= n * dot(velocity, n);
|
||||
vec3 moved = velocity * seconds;
|
||||
float travel = length(moved);
|
||||
if (travel > 1e-7)
|
||||
{
|
||||
vec2 next = uv + water_to_uv(frame, moved);
|
||||
water_wet_path(slot, frame, uv, next, 0.4 * radius);
|
||||
// a sliding drop leaves droplets behind as it goes, and thins; a large one breaks up the faster (a lot of water runs as
|
||||
// a stream of beads rather than as one big drop)
|
||||
extra.x += travel;
|
||||
if ((extra.x >= extra.y) && (volume > 1.5))
|
||||
{
|
||||
vec4 r = water_random(seed, 6u, place, slot);
|
||||
float left = clamp(volume * mix(0.01, 0.03, r.x) * (1.0 + volume / 40.0), 0.01, 0.25 * volume);
|
||||
vec3 behind = -(velocity / max(length(velocity), 1e-6)) * ((radius + 1.2 * water_radius(left)) * 1.05);
|
||||
if (water_add(slot, seed ^ 0x85ebca6bu, uv + water_to_uv(frame, behind), left, 1.0, r.y, false))
|
||||
drop.place.z -= left;
|
||||
extra.x = 0.0;
|
||||
extra.y = mix(0.002, 0.005, r.z) / (1.0 + volume / 100.0);
|
||||
}
|
||||
uv = next;
|
||||
}
|
||||
// without the rain the water dries, slowly
|
||||
if (water_step.z <= 0.0)
|
||||
drop.place.z -= seconds * 0.004 * radius * 1000.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
velocity = vec3(0.0);
|
||||
}
|
||||
|
||||
if ((drop.place.z < WATER_LEAST) || any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0))))
|
||||
{
|
||||
water_remove(index);
|
||||
return;
|
||||
}
|
||||
water_frame_s there = water_frame(slot, uv);
|
||||
if (there.valid < 0.25)
|
||||
{
|
||||
// off the glass, it falls off
|
||||
water_remove(index);
|
||||
return;
|
||||
}
|
||||
drop.place.xy = uv;
|
||||
drop.place.w += seconds;
|
||||
drop.motion.xy = water_to_uv(there, velocity);
|
||||
water_drops[index] = drop;
|
||||
water_extra[index] = extra;
|
||||
water_state[index] = 1u | (carried << 1u) | ((way > 0.0) ? 16u : 0u);
|
||||
}
|
||||
39
shaders/vk14/water_settle.comp
Normal file
39
shaders/vk14/water_settle.comp
Normal file
@@ -0,0 +1,39 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// the water on the windscreens (water_sim.glsl): the drops which gathered others take their water, move to the middle of it and
|
||||
// slow down as much as the still water slows them; the header of the glass tells the glasses it's simulated. a thread per drop,
|
||||
// a row of groups per glass
|
||||
|
||||
// none of the pass set: everything the kernel reads is pushed with its dispatch (set COMPUTE_SET, water_sim.glsl), so it runs
|
||||
// before the passes of the frame set theirs up
|
||||
#define LAYOUT_UI 1
|
||||
#include "common.glsl"
|
||||
#include "water_sim.glsl"
|
||||
|
||||
layout(local_size_x = WATER_GROUP_SIZE) in;
|
||||
|
||||
void main()
|
||||
{
|
||||
uint slot = gl_WorkGroupID.y;
|
||||
uint place = gl_GlobalInvocationID.x;
|
||||
if ((water_slots[slot].z < 0.5) || (place >= uint(WATER_DROPS)))
|
||||
return;
|
||||
if (place == 0u)
|
||||
water_header[slot] = vec4(1.0, water_step.y, 0.0, 0.0);
|
||||
uint index = slot * uint(WATER_DROPS) + place;
|
||||
int gathered = water_gather[index * 4u];
|
||||
if (gathered == 0)
|
||||
return;
|
||||
vec2 moment = vec2(float(water_gather[index * 4u + 1u]), float(water_gather[index * 4u + 2u])) * 1e-6;
|
||||
water_gather[index * 4u] = 0;
|
||||
water_gather[index * 4u + 1u] = 0;
|
||||
water_gather[index * 4u + 2u] = 0;
|
||||
water_drop_s drop = water_drops[index];
|
||||
if (drop.place.z <= 0.0)
|
||||
return;
|
||||
float volume = drop.place.z + float(gathered) * 1e-4;
|
||||
drop.place.xy += moment / volume;
|
||||
drop.motion.xy *= drop.place.z / volume;
|
||||
drop.place.z = volume;
|
||||
water_drops[index] = drop;
|
||||
}
|
||||
279
shaders/vk14/water_sim.glsl
Normal file
279
shaders/vk14/water_sim.glsl
Normal file
@@ -0,0 +1,279 @@
|
||||
// what the passes of the simulation of the water on the windscreens share (water_*.comp): the block of the step, the random
|
||||
// numbers, the map of the glass, the forces on a drop
|
||||
//
|
||||
// the physics (the drops are caps of spheres flat on the glass, 40 degrees at their edge, untreated weathered glass):
|
||||
// - a drop lies held by its line of contact: the hold is the surface tension times the width of the drop times the difference
|
||||
// of the cosines of its angles at the front and the rear (WATER_HOLD), so it grows with the radius, while the weight grows
|
||||
// with the cube of it. on a vertical glass a drop slides once its footprint is about 7 mm across (25 mm3), on a glass leaning
|
||||
// back later. the hold is uneven over the glass (dirt, scratches: the drops stop and go) and much less where the water went
|
||||
// lately (a wetted path: the next drops follow it and it becomes a rivulet)
|
||||
// - the air going along the glass drags the drops (with the area they stand up into it, within the layer of slow air at the
|
||||
// glass), and of the air meeting the glass head on a share turns up along it: at speed the water goes up the glass, the sooner
|
||||
// the more it leans back
|
||||
// - a sliding drop is held back by the viscosity in proportion to its speed and radius; it leaves droplets behind as it goes
|
||||
// (it thins and stops in the end) and wets its path
|
||||
// - drops which touch flow together into one (surface tension), the water and the momentum of the places kept
|
||||
// - a raindrop hits the glass far above the speed at which it splashes: it spreads and throws droplets around, which stay
|
||||
// - a blade going over a drop takes it along in front of itself; the water it gathers flows together into a bead along it, left
|
||||
// at the ends of its sweep (where it runs down as streams) and pushed off where the glass ends
|
||||
|
||||
#ifndef WATER_SIM_GLSL
|
||||
#define WATER_SIM_GLSL
|
||||
|
||||
#include "water.glsl"
|
||||
|
||||
// gl::water_ubs, every vector in the space of the model of the cab
|
||||
layout(std140, set = COMPUTE_SET, binding = 1) uniform water_ubo
|
||||
{
|
||||
// x: seconds of the step, y: seconds the simulation runs, z: how heavy the rain or the snow is 0 .. 1, w: 1 for snow
|
||||
vec4 water_step;
|
||||
// xyz: up, w: the temperature of the air, degrees Celsius
|
||||
vec4 water_up;
|
||||
// xyz: the air against the cab, m/s; w: how fast the rain or the snow falls, m/s
|
||||
vec4 water_air;
|
||||
// the blades now and at the step before: 0 .. 1 across their sweep, below 0 where there's none
|
||||
vec4 water_blades_now;
|
||||
vec4 water_blades_before;
|
||||
// x: the smallest diameter of a raindrop landing as a drop of its own, mm, y: 1 / the mean of the diameters above it, 1 / mm
|
||||
vec4 water_rain;
|
||||
// x: random seed of the step, y: the most landings tried on a glass, z: the glass a single glass pass works on
|
||||
uvec4 water_counts;
|
||||
// every glass: x the landings tried in the step, y 1 / the most metres squared a unit of its uv holds, z 1 when it's simulated,
|
||||
// w the size of its drops (rain_scale of its material)
|
||||
vec4 water_slots[WATER_SLOTS];
|
||||
};
|
||||
|
||||
const float WATER_GRAVITY = 9.81;
|
||||
// the surface tension of water, N/m, and the share of it a drop is held by for the width of its footprint
|
||||
const float WATER_TENSION = 0.072;
|
||||
const float WATER_HOLD = 0.5;
|
||||
// the viscous drag of a sliding drop for its radius and speed, N s / m2
|
||||
const float WATER_VISCOUS = 1.3;
|
||||
// the share of the speed of the air the drops feel in the slow layer at the glass, and of the air meeting the glass head on
|
||||
// which turns up along it
|
||||
const float WATER_BOUNDARY = 0.3;
|
||||
const float WATER_HEAD_ON = 0.35;
|
||||
// the drops below this much water (mm3) dry at once; a raindrop or a droplet of a splash smaller than WATER_SEEN (0.5 mm across
|
||||
// its footprint, hardly a pixel) landing where there's no drop is left to the mist of the glass
|
||||
const float WATER_LEAST = 0.004;
|
||||
const float WATER_SEEN = 0.075;
|
||||
|
||||
uvec4 water_pcg4d(uvec4 v)
|
||||
{
|
||||
v = v * 1664525u + 1013904223u;
|
||||
v.x += v.y * v.w;
|
||||
v.y += v.z * v.x;
|
||||
v.z += v.x * v.y;
|
||||
v.w += v.y * v.z;
|
||||
v ^= v >> 16u;
|
||||
v.x += v.y * v.w;
|
||||
v.y += v.z * v.x;
|
||||
v.z += v.x * v.y;
|
||||
v.w += v.y * v.z;
|
||||
return v;
|
||||
}
|
||||
|
||||
// four random numbers 0 .. 1
|
||||
vec4 water_random(uint a, uint b, uint c, uint d)
|
||||
{
|
||||
return vec4(water_pcg4d(uvec4(a, b, c, d))) * (1.0 / 4294967296.0);
|
||||
}
|
||||
|
||||
// value noise 0 .. 1
|
||||
float water_noise(vec2 p, uint layer)
|
||||
{
|
||||
vec2 i = floor(p);
|
||||
vec2 f = p - i;
|
||||
vec2 u = f * f * (3.0 - 2.0 * f);
|
||||
uvec2 c = uvec2(ivec2(i));
|
||||
float a = water_random(c.x, c.y, layer, 0u).x;
|
||||
float b = water_random(c.x + 1u, c.y, layer, 0u).x;
|
||||
float d = water_random(c.x, c.y + 1u, layer, 0u).x;
|
||||
float e = water_random(c.x + 1u, c.y + 1u, layer, 0u).x;
|
||||
return mix(mix(a, b, u.x), mix(d, e, u.x), u.y);
|
||||
}
|
||||
|
||||
// the glass at a place of the uv: the steps along it for the steps of u and v (metres), the way out of the cab and whether the
|
||||
// place is on the glass (1), next to it (0.5) or off it (0)
|
||||
struct water_frame_s
|
||||
{
|
||||
vec3 tu;
|
||||
vec3 tv;
|
||||
vec3 normal;
|
||||
float valid;
|
||||
};
|
||||
|
||||
water_frame_s water_frame(uint slot, vec2 uv)
|
||||
{
|
||||
water_frame_s frame = water_frame_s(vec3(0.0), vec3(0.0), vec3(0.0, 0.0, 1.0), 0.0);
|
||||
if (any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0))))
|
||||
return frame;
|
||||
uint base = slot * uint(WATER_MAP * WATER_MAP);
|
||||
vec2 p = uv * float(WATER_MAP) - 0.5;
|
||||
ivec2 i = ivec2(floor(p));
|
||||
vec2 f = p - vec2(i);
|
||||
float total = 0.0;
|
||||
float side = 0.0;
|
||||
for (int k = 0; k < 4; ++k)
|
||||
{
|
||||
ivec2 texel = clamp(i + ivec2(k & 1, k >> 1), ivec2(0), ivec2(WATER_MAP - 1));
|
||||
uint index = base + uint(texel.y * WATER_MAP + texel.x);
|
||||
vec4 a = water_axes[index * 2u];
|
||||
vec4 b = water_axes[index * 2u + 1u];
|
||||
if (a.w <= 0.0)
|
||||
continue;
|
||||
float w = (((k & 1) != 0) ? f.x : 1.0 - f.x) * (((k >> 1) != 0) ? f.y : 1.0 - f.y) + 1e-4;
|
||||
frame.tu += a.xyz * w;
|
||||
frame.tv += b.xyz * w;
|
||||
side += b.w * w;
|
||||
total += w;
|
||||
}
|
||||
if (total <= 0.0)
|
||||
return frame;
|
||||
frame.tu /= total;
|
||||
frame.tv /= total;
|
||||
vec3 n = cross(frame.tu, frame.tv);
|
||||
float length_n = length(n);
|
||||
if (length_n < 1e-12)
|
||||
return frame;
|
||||
frame.normal = n * (((side < 0.0) ? -1.0 : 1.0) / length_n);
|
||||
// on the glass by the texel the place is in
|
||||
ivec2 nearest = clamp(ivec2(floor(uv * float(WATER_MAP))), ivec2(0), ivec2(WATER_MAP - 1));
|
||||
frame.valid = water_axes[(base + uint(nearest.y * WATER_MAP + nearest.x)) * 2u].w;
|
||||
return frame;
|
||||
}
|
||||
|
||||
// a step along the glass (metres, the space of the cab) as a step of the uv
|
||||
vec2 water_to_uv(water_frame_s frame, vec3 step)
|
||||
{
|
||||
float a = dot(frame.tu, frame.tu);
|
||||
float b = dot(frame.tu, frame.tv);
|
||||
float c = dot(frame.tv, frame.tv);
|
||||
float det = a * c - b * b;
|
||||
if (abs(det) < 1e-20)
|
||||
return vec2(0.0);
|
||||
vec2 r = vec2(dot(frame.tu, step), dot(frame.tv, step));
|
||||
return vec2(c * r.x - b * r.y, a * r.y - b * r.x) / det;
|
||||
}
|
||||
|
||||
// the blades at a place of the uv, filtered between the texels of the map
|
||||
vec4 water_blades_at(uint slot, vec2 uv)
|
||||
{
|
||||
uint base = slot * uint(WATER_MAP * WATER_MAP);
|
||||
vec2 p = clamp(uv, vec2(0.0), vec2(1.0)) * float(WATER_MAP) - 0.5;
|
||||
ivec2 i = ivec2(floor(p));
|
||||
vec2 f = p - vec2(i);
|
||||
vec4 result = vec4(0.0);
|
||||
for (int k = 0; k < 4; ++k)
|
||||
{
|
||||
ivec2 texel = clamp(i + ivec2(k & 1, k >> 1), ivec2(0), ivec2(WATER_MAP - 1));
|
||||
float w = (((k & 1) != 0) ? f.x : 1.0 - f.x) * (((k >> 1) != 0) ? f.y : 1.0 - f.y);
|
||||
result += water_blades[base + uint(texel.y * WATER_MAP + texel.x)] * w;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// the blades at the texel of the map holding a place of the uv, unfiltered: where a blade reaches it's at least 0.001 (the
|
||||
// filtered values fall off across a texel at the edge of the sweep)
|
||||
vec4 water_blades_texel(uint slot, vec2 uv)
|
||||
{
|
||||
ivec2 texel = clamp(ivec2(floor(uv * float(WATER_MAP))), ivec2(0), ivec2(WATER_MAP - 1));
|
||||
return water_blades[slot * uint(WATER_MAP * WATER_MAP) + uint(texel.y * WATER_MAP + texel.x)];
|
||||
}
|
||||
|
||||
// a free place for a drop of the glass, taken; ~0u when none was found
|
||||
uint water_claim(uint slot, uint seed)
|
||||
{
|
||||
for (uint k = 0u; k < 8u; ++k)
|
||||
{
|
||||
uint place = water_pcg4d(uvec4(seed, k, slot, 77u)).x % uint(WATER_DROPS);
|
||||
if (atomicCompSwap(water_state[slot * uint(WATER_DROPS) + place], 0u, 1u) == 0u)
|
||||
return place;
|
||||
}
|
||||
return ~0u;
|
||||
}
|
||||
|
||||
// a new drop of the glass: Volume mm3 at a place, a flake of snow when Melted is below 1. false when the glass is full. the
|
||||
// landing rain and snow get only nine tenths of the places, so there are always some for the droplets the moving drops leave
|
||||
bool water_add(uint slot, uint seed, vec2 uv, float volume, float melted, float shape, bool landing)
|
||||
{
|
||||
if ((volume < WATER_LEAST) || any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0))))
|
||||
return false;
|
||||
if (landing && (water_counts_of[slot].x >= uint(WATER_DROPS) / 10u * 9u))
|
||||
return false;
|
||||
uint place = water_claim(slot, seed);
|
||||
if (place == ~0u)
|
||||
return false;
|
||||
atomicAdd(water_counts_of[slot].x, 1u);
|
||||
uint index = slot * uint(WATER_DROPS) + place;
|
||||
water_drops[index].place = vec4(uv, volume, 0.0);
|
||||
water_drops[index].motion = vec4(0.0, 0.0, shape, melted);
|
||||
water_extra[index] = vec4(0.0, 0.004 * (0.6 + 0.8 * shape), 0.0, 0.0);
|
||||
water_target[index] = place;
|
||||
for (uint k = 0u; k < 4u; ++k)
|
||||
water_gather[index * 4u + k] = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
// the slot of the drop on the glass freed, with what was flowing into it
|
||||
void water_remove(uint index)
|
||||
{
|
||||
water_drops[index].place.z = 0.0;
|
||||
water_state[index] = 0u;
|
||||
atomicAdd(water_counts_of[index / uint(WATER_DROPS)].x, ~0u);
|
||||
for (uint k = 0u; k < 4u; ++k)
|
||||
water_gather[index * 4u + k] = 0;
|
||||
}
|
||||
|
||||
// Volume mm3 of water flowing into a drop (index in the storage) from Offset (uv) away from its middle, taken by
|
||||
// water_settle.comp
|
||||
void water_pour(uint index, float volume, vec2 offset)
|
||||
{
|
||||
atomicAdd(water_gather[index * 4u], int(volume * 1e4));
|
||||
atomicAdd(water_gather[index * 4u + 1u], int(offset.x * volume * 1e6));
|
||||
atomicAdd(water_gather[index * 4u + 2u], int(offset.y * volume * 1e6));
|
||||
}
|
||||
|
||||
// the drop lying at a place of the glass which water of Radius (metres) landing there touches, the largest of them, by the grid
|
||||
// of the step before (~0u: none)
|
||||
uint water_landing_on(uint slot, water_frame_s frame, vec2 uv, float radius)
|
||||
{
|
||||
uint cell = water_cell(uv);
|
||||
uint base = water_grid_base(slot);
|
||||
uint count = min(water_cells[base + cell], uint(WATER_CELL_DROPS));
|
||||
uint best = ~0u;
|
||||
float most = 0.0;
|
||||
for (uint k = 0u; k < count; ++k)
|
||||
{
|
||||
uint other = water_cells[base + uint(WATER_GRID * WATER_GRID) + cell * uint(WATER_CELL_DROPS) + k];
|
||||
water_drop_s drop = water_drops[slot * uint(WATER_DROPS) + other];
|
||||
if ((drop.place.z <= most) || (drop.motion.w < 1.0))
|
||||
continue;
|
||||
vec2 step = drop.place.xy - uv;
|
||||
if (length(frame.tu * step.x + frame.tv * step.y) < 0.9 * (radius + water_radius(drop.place.z)))
|
||||
{
|
||||
best = other;
|
||||
most = drop.place.z;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
// water landing on the glass: it flows into a drop it touches, else it's a new drop where it's large enough to be seen
|
||||
void water_land(uint slot, uint seed, water_frame_s frame, vec2 uv, float volume, float shape)
|
||||
{
|
||||
if (any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0))))
|
||||
return;
|
||||
uint onto = water_landing_on(slot, frame, uv, water_radius(volume));
|
||||
if (onto != ~0u)
|
||||
{
|
||||
uint index = slot * uint(WATER_DROPS) + onto;
|
||||
water_pour(index, volume, uv - water_drops[index].place.xy);
|
||||
}
|
||||
else if (volume >= WATER_SEEN)
|
||||
{
|
||||
water_add(slot, seed, uv, volume, 1.0, shape, true);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
68
shaders/vk14/water_spawn.comp
Normal file
68
shaders/vk14/water_spawn.comp
Normal file
@@ -0,0 +1,68 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// the water on the windscreens (water_sim.glsl): the rain or the snow landing on the glasses in the step. a thread per landing
|
||||
// tried, a row of groups per glass: a place of the uv picked at random is taken as often as the glass there gets hit (the
|
||||
// metres squared the place holds, how much it faces the rain coming at it), so the landings go evenly over the glass. a raindrop
|
||||
// spreads and throws a splash of droplets around, along the way it came, which flow into the drops they land on (water_settle.comp
|
||||
// takes the water); a flake sticks where it lands
|
||||
|
||||
// none of the pass set: everything the kernel reads is pushed with its dispatch (set COMPUTE_SET, water_sim.glsl), so it runs
|
||||
// before the passes of the frame set theirs up
|
||||
#define LAYOUT_UI 1
|
||||
#include "common.glsl"
|
||||
#include "water_sim.glsl"
|
||||
|
||||
layout(local_size_x = WATER_GROUP_SIZE) in;
|
||||
|
||||
void main()
|
||||
{
|
||||
uint slot = gl_WorkGroupID.y;
|
||||
uint landing = gl_GlobalInvocationID.x;
|
||||
if ((water_slots[slot].z < 0.5) || (float(landing) >= water_slots[slot].x))
|
||||
return;
|
||||
vec4 r = water_random(water_counts.x, slot, landing, 0u);
|
||||
vec2 uv = r.xy;
|
||||
water_frame_s frame = water_frame(slot, uv);
|
||||
if (frame.valid < 0.75)
|
||||
return;
|
||||
// the rain coming at the glass, against the cab
|
||||
vec3 coming = water_air.xyz - water_up.xyz * water_air.w;
|
||||
float speed = length(coming);
|
||||
if (speed < 1e-3)
|
||||
return;
|
||||
float facing = max(-dot(coming, frame.normal), 0.0) / speed;
|
||||
float density = length(cross(frame.tu, frame.tv));
|
||||
if (r.z >= density * water_slots[slot].y * facing)
|
||||
return;
|
||||
float scale = max(water_slots[slot].w, 0.05);
|
||||
vec4 s = water_random(water_counts.x, slot, landing, 1u);
|
||||
uint seed = water_pcg4d(uvec4(water_counts.x, slot, landing, 2u)).x;
|
||||
if (water_step.w > 0.5)
|
||||
{
|
||||
// a flake, a few mm of snow holding a little water
|
||||
water_add(slot, seed, uv, mix(0.3, 2.5, s.x) * scale * scale * scale, 0.0, s.y, true);
|
||||
return;
|
||||
}
|
||||
// the diameter of the raindrop, mm: above the least landing as a drop of its own they go as Marshall-Palmer (exponential)
|
||||
float diameter = min(water_rain.x - log(max(r.w, 1e-6)) / max(water_rain.y, 0.1), 5.0) * scale;
|
||||
float drop = 0.5236 * diameter * diameter * diameter;
|
||||
// most of it stays where it hit, the rest is thrown around as droplets, forward along the glass
|
||||
float kept = mix(0.55, 0.85, s.x);
|
||||
water_land(slot, seed, frame, uv, drop * kept, s.y);
|
||||
vec3 along = coming - frame.normal * dot(coming, frame.normal);
|
||||
float glancing = length(along) / speed;
|
||||
vec3 forward = (glancing > 1e-3) ? normalize(along) : normalize(frame.tu);
|
||||
vec3 across = cross(frame.normal, forward);
|
||||
float radius = water_radius(drop * kept);
|
||||
int splash = 2 + int(s.z * 4.0);
|
||||
for (int k = 0; k < splash; ++k)
|
||||
{
|
||||
vec4 e = water_random(water_counts.x, slot, landing, 3u + uint(k));
|
||||
float angle = e.x * 6.2831853;
|
||||
vec3 way = cos(angle) * forward + sin(angle) * across + forward * (1.5 * glancing);
|
||||
way = (length(way) > 1e-3) ? normalize(way) : forward;
|
||||
float distance = radius * (1.3 + 2.5 * e.y) * (1.0 + glancing);
|
||||
float volume = drop * (1.0 - kept) / float(splash) * mix(0.4, 1.6, e.z);
|
||||
water_land(slot, seed + 101u * uint(k + 1), frame, uv + water_to_uv(frame, way * distance), volume, e.w);
|
||||
}
|
||||
}
|
||||
37
shaders/vk14/water_trail.comp
Normal file
37
shaders/vk14/water_trail.comp
Normal file
@@ -0,0 +1,37 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// the water on the windscreens (water_sim.glsl): the blades going over the wetted paths in the step wipe them. a thread per texel
|
||||
// of the map of the paths, a row of groups per glass
|
||||
|
||||
// none of the pass set: everything the kernel reads is pushed with its dispatch (set COMPUTE_SET, water_sim.glsl), so it runs
|
||||
// before the passes of the frame set theirs up
|
||||
#define LAYOUT_UI 1
|
||||
#include "common.glsl"
|
||||
#include "water_sim.glsl"
|
||||
|
||||
layout(local_size_x = WATER_GROUP_SIZE) in;
|
||||
|
||||
void main()
|
||||
{
|
||||
uint slot = gl_WorkGroupID.y;
|
||||
uint texel = gl_GlobalInvocationID.x;
|
||||
if ((water_slots[slot].z < 0.5) || (texel >= uint(WATER_TRAIL * WATER_TRAIL)))
|
||||
return;
|
||||
uint index = water_trail_base(slot) + texel;
|
||||
if (water_cells[index] == 0u)
|
||||
return;
|
||||
vec2 uv = (vec2(float(texel % uint(WATER_TRAIL)), float(texel / uint(WATER_TRAIL))) + 0.5) / float(WATER_TRAIL);
|
||||
vec4 mask = water_blades_at(slot, uv);
|
||||
for (int k = 0; k < 4; ++k)
|
||||
{
|
||||
float before = water_blades_before[k];
|
||||
float now = water_blades_now[k];
|
||||
if ((mask[k] < 0.001) || (before < 0.0) || (now < 0.0) || (before == now))
|
||||
continue;
|
||||
if ((mask[k] > min(before, now)) && (mask[k] <= max(before, now)))
|
||||
{
|
||||
water_cells[index] = 0u;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user