mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-21 23:26:24 +02:00
Simulate and light the smoke on the GPU and make rain and snow of particles
This commit is contained in:
@@ -393,6 +393,7 @@ if (WITH_OPENGL_MODERN)
|
||||
"rendering/opengl46/opengl46shadowfit.cpp"
|
||||
"rendering/opengl46/opengl46precipitation.cpp"
|
||||
"rendering/opengl46/opengl46particles.cpp"
|
||||
"rendering/opengl46/opengl46gpuparticles.cpp"
|
||||
"rendering/opengl46/opengl46terrain.cpp"
|
||||
"rendering/opengl46/opengl46grass.cpp"
|
||||
"rendering/opengl46/opengl46renderer.cpp")
|
||||
|
||||
@@ -259,6 +259,32 @@ are no longer loaded.
|
||||
| `gfx.moon.disc` | `1.0` | 0..16: brightness of the moon disc |
|
||||
| `gfx.moonlight` | `1.0` | 0..10: how much the moon lights the scenery at night (its direct light, the night light that comes with it and the moonlit sky); 0: no moonlight |
|
||||
|
||||
### Particles
|
||||
|
||||
- **Smoke** of vehicles and scenery models is simulated and drawn on the graphics card. The sources (the `smokesource_`
|
||||
points of the models and their `data/*.txt` templates, unchanged) work out on the processor only how many particles they
|
||||
give off and what these start with, as before; the particles themselves live in a buffer of the graphics card and are
|
||||
moved there once per step of the simulation, by the same rules as on the processor. The particles of a step are spread
|
||||
along the way the source went in it, so the smoke of a fast train no longer comes out in clumps. Each view leaves out
|
||||
what it doesn't see and lights the rest with the sun (with the shadows of the terrain, models and clouds), the sky and
|
||||
the lamps and headlights, as the surfaces are lit: smoke in the shade is darker, smoke against the sun glows, smoke in
|
||||
the beam of headlights at night is lit by it.
|
||||
- **Rain and snow** are particles in three boxes around the camera (10, 24 and 60 metres), more of them the heavier the
|
||||
rain or snow (the overcast above 1). The drops and flakes fall, drift with the wind, the flakes flutter, and each is
|
||||
drawn as a streak of its motion against the camera: they slant when the train moves. They are lit like the smoke, so the
|
||||
rain shows in the headlights and under the lamps, and fogged. The cab still hides them as before. They don't stop under
|
||||
roofs or in tunnels yet (neither did the old cylinder).
|
||||
- **Soft particles**: smoke, rain and snow fade where they near the scenery behind them instead of being cut by it
|
||||
(the depth of the view drawn so far is copied for it once per view).
|
||||
|
||||
| Entry | Default | Meaning |
|
||||
|---|---|---|
|
||||
| `gfx.smoke.gpu yes/no` | `yes` | the smoke on the graphics card, see above; `no`: simulated on the processor and drawn as before |
|
||||
| `gfx.smoke.lighting yes/no` | `yes` | the smoke of the graphics card lit by the sun, the sky and the lamps; `no`: in the colour the processor gave it, as before |
|
||||
| `gfx.particles.soft` | `1.0` | 0..4: how far in front of the scenery the particles fade out, as a share of the half size of a particle of smoke, and times 0.25 m for rain and snow; 0: cut, as before |
|
||||
| `gfx.precipitation.gpu yes/no` | `yes` | rain and snow as particles, see above; `no`: the textured cylinder, as before |
|
||||
| `gfx.precipitation.particles` | `65536` | 0..1048576: the particles of the heaviest rain or snow; lighter rain has fewer, down to 15% of it |
|
||||
|
||||
### Models drawn from the GPU
|
||||
|
||||
Many models of the scenery (trees, bushes, poles, fences: models without lights and animations) are drawn in batches, one
|
||||
@@ -447,6 +473,11 @@ editor goes through the first `dynamiclights` headlights, the reflections throug
|
||||
under `gpu steps:` are measured each from its own pair of marks, and the first mark of a pair can be put while the graphics
|
||||
card still works on what came before, so a short step right after a heavy one may show some of that work; the parts don't.
|
||||
Like the other timings, they are of a frame a few frames back.
|
||||
- **Particles** window (next to the Weather window): the switches above. Shift+F12 shows the `smoke of the gpu:` row
|
||||
(particles drawn, as counted on the graphics card a few frames before, the places for the particles and the sources they
|
||||
are shared by) and, while it rains or snows, `rain of the gpu:` / `snow of the gpu:` (drawn of how many); the timings
|
||||
`particles:` give the GPU time of the step of the smoke, of the smoke of the view (culling, light and drawing) and of the
|
||||
rain or snow.
|
||||
- `gfx.gldebug yes` turns on the OpenGL debug output (`KHR_debug`); messages go to the log.
|
||||
|
||||
## Building
|
||||
|
||||
@@ -29,6 +29,12 @@ void gl::glsl_common_setup()
|
||||
"#define HIZ_GROUP_SIZE " + std::to_string(HIZ_GROUP_SIZE) + "\n" +
|
||||
"#define SHADOW_FIT_GROUP_SIZE " + std::to_string(SHADOW_FIT_GROUP_SIZE) + "\n" +
|
||||
"#define SHADOW_FIT_SSBO_BINDING " + std::to_string(SHADOW_FIT_SSBO_BINDING) + "\n" +
|
||||
"#define PARTICLE_GROUP_SIZE " + std::to_string(PARTICLE_GROUP_SIZE) + "\n" +
|
||||
"#define PARTICLE_SSBO_BINDING " + std::to_string(PARTICLE_SSBO_BINDING) + "\n" +
|
||||
"#define PARTICLE_SOURCE_SSBO_BINDING " + std::to_string(PARTICLE_SOURCE_SSBO_BINDING) + "\n" +
|
||||
"#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 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" +
|
||||
|
||||
44
gl/ubo.h
44
gl/ubo.h
@@ -496,6 +496,50 @@ namespace gl
|
||||
const GLuint SHADOW_FIT_GROUP_SIZE = 16;
|
||||
const GLuint SHADOW_FIT_SSBO_BINDING = 9;
|
||||
|
||||
// the particles of the gpu (opengl46_gpu_particles, particles_*.comp, particle_common.glsl): work group of their passes and the
|
||||
// binding points of their buffers. the points are ones of the instance culling, which binds its buffers before every use; the
|
||||
// particles bind their own before their passes. the draws read the particles as attributes of instances, not from storage
|
||||
const GLuint PARTICLE_GROUP_SIZE = 64;
|
||||
const GLuint PARTICLE_SSBO_BINDING = 9; // the smoke particles
|
||||
const GLuint PARTICLE_SOURCE_SSBO_BINDING = 10; // their sources, as the last update of the simulation left them
|
||||
const GLuint PARTICLE_OWNER_SSBO_BINDING = 11; // the source of every place of the particles
|
||||
const GLuint PARTICLE_OUTPUT_SSBO_BINDING = 12; // what the draw reads, a particle_draw per particle drawn
|
||||
const GLuint PARTICLE_COMMAND_SSBO_BINDING = 13; // the indirect draw command, whose instances the passes count
|
||||
|
||||
// layout std430: GLSL smoke_particle_s
|
||||
struct particle_smoke
|
||||
{
|
||||
glm::vec4 position;
|
||||
glm::vec4 velocity;
|
||||
glm::vec4 state;
|
||||
};
|
||||
static_assert(sizeof(particle_smoke) == 48, "bad size of particle_smoke");
|
||||
|
||||
// layout std430: GLSL smoke_source_s
|
||||
struct particle_smoke_source
|
||||
{
|
||||
glm::uvec4 pool;
|
||||
glm::vec4 location;
|
||||
glm::vec4 previous;
|
||||
glm::vec4 launch;
|
||||
glm::vec4 initial;
|
||||
glm::vec4 sizechange;
|
||||
glm::vec4 opacitychange;
|
||||
glm::vec4 motion;
|
||||
glm::vec4 color;
|
||||
glm::vec4 oldcolor;
|
||||
};
|
||||
static_assert(sizeof(particle_smoke_source) == 160, "bad size of particle_smoke_source");
|
||||
|
||||
// layout std430: GLSL particle_draw_s, read by the draws as attributes of instances
|
||||
struct particle_draw
|
||||
{
|
||||
glm::vec4 place;
|
||||
glm::vec4 shape;
|
||||
glm::vec4 color;
|
||||
};
|
||||
static_assert(sizeof(particle_draw) == 48, "bad size of particle_draw");
|
||||
|
||||
// layout std430: a bucket of a scene cell queued in the pass (GLSL: instance_cull_job)
|
||||
struct instance_cull_job
|
||||
{
|
||||
|
||||
472
rendering/opengl46/opengl46gpuparticles.cpp
Normal file
472
rendering/opengl46/opengl46gpuparticles.cpp
Normal file
@@ -0,0 +1,472 @@
|
||||
/*
|
||||
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/opengl46gpuparticles.h"
|
||||
|
||||
#include "rendering/particles.h"
|
||||
#include "simulation/simulationenvironment.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/Logs.h"
|
||||
#include "utilities/Timer.h"
|
||||
#include "utilities/utilities.h"
|
||||
#include "gl/vao.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// places of the smoke at most, all sources together (48 bytes each, as many again for the draw); the sources past it get none
|
||||
std::uint32_t constexpr MAX_SMOKE_PLACES{1u << 19};
|
||||
// the origin of the smoke moves by whole steps when the camera gets this far from it
|
||||
double constexpr ORIGIN_STEP{256.0};
|
||||
double constexpr ORIGIN_REACH{1024.0};
|
||||
// how much the smoke scatters the light onwards (Henyey-Greenstein)
|
||||
float constexpr SMOKE_FORWARD{0.3f};
|
||||
|
||||
// the boxes of the rain and snow around the camera: their sizes (metres) and the shares of the particles in each
|
||||
std::array<float, 3> constexpr BOX_SIZE{10.f, 24.f, 60.f};
|
||||
std::array<float, 3> constexpr BOX_SHARE{0.2f, 0.35f, 0.45f};
|
||||
|
||||
// what the rain and the snow are like: the speed of the fall (m/s), how long a streak lasts (s, the eye's or the camera's
|
||||
// exposure), opacity, how much a drop or a flake scatters the light onwards and how bright it is against a white surface
|
||||
struct precipitation_kind
|
||||
{
|
||||
float fall;
|
||||
float exposure;
|
||||
float opacity;
|
||||
float forward;
|
||||
float brightness;
|
||||
};
|
||||
precipitation_kind constexpr RAIN{7.f, 0.025f, 0.5f, 0.6f, 0.6f};
|
||||
precipitation_kind constexpr SNOW{1.1f, 0.01f, 0.85f, 0.25f, 0.9f};
|
||||
// the share of the wind of the environment the drops and flakes drift with (as the old cylinder leant with it)
|
||||
double constexpr WIND_SHARE{0.5};
|
||||
// the camera faster than this from a frame to the next was moved, not driven or flown
|
||||
double constexpr CAMERA_JUMP{120.0};
|
||||
|
||||
std::unique_ptr<gl::program> make_program(std::vector<std::string> const &Names)
|
||||
{
|
||||
std::vector<std::unique_ptr<gl::shader>> shaders;
|
||||
std::vector<std::reference_wrapper<const gl::shader>> references;
|
||||
for (auto const &name : Names)
|
||||
{
|
||||
shaders.emplace_back(std::make_unique<gl::shader>(name));
|
||||
references.emplace_back(*shaders.back());
|
||||
}
|
||||
return std::make_unique<gl::program>(references);
|
||||
}
|
||||
|
||||
std::uint32_t groups(std::uint32_t const Count)
|
||||
{
|
||||
return (Count + gl::PARTICLE_GROUP_SIZE - 1) / gl::PARTICLE_GROUP_SIZE;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
opengl46_gpu_particles::~opengl46_gpu_particles()
|
||||
{
|
||||
// the vertex array goes with the context; deleted here only if it's still there
|
||||
if ((m_vao != 0) && (::glfwGetCurrentContext() != nullptr))
|
||||
::glDeleteVertexArrays(1, &m_vao);
|
||||
}
|
||||
|
||||
bool opengl46_gpu_particles::init()
|
||||
{
|
||||
if (m_state != 0)
|
||||
return m_state > 0;
|
||||
m_state = -1;
|
||||
try
|
||||
{
|
||||
m_smokesimulate = make_program({"particles_smoke_simulate.comp"});
|
||||
m_smokeprepare = make_program({"particles_smoke_prepare.comp"});
|
||||
m_smokedraw = make_program({"particles_smoke.vert", "particles_smoke.frag"});
|
||||
m_precipitationprepare = make_program({"particles_precipitation.comp"});
|
||||
m_precipitationdraw = make_program({"particles_precipitation.vert", "particles_precipitation.frag"});
|
||||
}
|
||||
catch (gl::shader_exception const &e)
|
||||
{
|
||||
ErrorLog("particles: can't use the programs of the particles of the gpu, the smoke stays on the cpu: " + std::string(e.what()));
|
||||
return false;
|
||||
}
|
||||
|
||||
// the attributes of the instances: three vec4 of gl::particle_draw
|
||||
::glCreateVertexArrays(1, &m_vao);
|
||||
for (GLuint attribute = 0; attribute < 3; ++attribute)
|
||||
{
|
||||
::glEnableVertexArrayAttrib(m_vao, attribute);
|
||||
::glVertexArrayAttribFormat(m_vao, attribute, 4, GL_FLOAT, GL_FALSE, attribute * sizeof(glm::vec4));
|
||||
::glVertexArrayAttribBinding(m_vao, attribute, 0);
|
||||
}
|
||||
::glVertexArrayBindingDivisor(m_vao, 0, 1);
|
||||
|
||||
m_smokecommand = std::make_unique<gl::ssbo>(4 * sizeof(GLuint), gl::PARTICLE_COMMAND_SSBO_BINDING);
|
||||
m_precipitationcommand = std::make_unique<gl::ssbo>(4 * sizeof(GLuint), gl::PARTICLE_COMMAND_SSBO_BINDING);
|
||||
m_smokereadback = std::make_unique<gl::readback>(4 * sizeof(GLuint));
|
||||
m_precipitationreadback = std::make_unique<gl::readback>(4 * sizeof(GLuint));
|
||||
m_state = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool opengl46_gpu_particles::smoke_available()
|
||||
{
|
||||
return init();
|
||||
}
|
||||
|
||||
bool opengl46_gpu_particles::precipitation_available()
|
||||
{
|
||||
return init();
|
||||
}
|
||||
|
||||
void opengl46_gpu_particles::clear()
|
||||
{
|
||||
m_pool.clear();
|
||||
m_requested.clear();
|
||||
m_poolfull = false;
|
||||
m_sourcerecords.clear();
|
||||
m_places = 0;
|
||||
m_particles.reset();
|
||||
m_sources.reset();
|
||||
m_owners.reset();
|
||||
m_smokeoutput.reset();
|
||||
m_precipitationoutput.reset();
|
||||
m_precipitationcapacity = 0;
|
||||
m_originset = false;
|
||||
m_lastupdate = 0;
|
||||
}
|
||||
|
||||
void opengl46_gpu_particles::layout(particle_manager &Particles)
|
||||
{
|
||||
auto const &sources{Particles.sequence()};
|
||||
// the sources already laid out which asked for the same places since
|
||||
std::size_t kept{0};
|
||||
while ((kept < m_requested.size()) && (kept < sources.size()) && (m_requested[kept] == sources[kept].max_particles()))
|
||||
++kept;
|
||||
if ((kept == m_requested.size()) && (kept == sources.size()) && m_particles)
|
||||
return;
|
||||
if (kept < m_requested.size())
|
||||
{
|
||||
// a source went away or changed: all anew
|
||||
kept = 0;
|
||||
m_pool.clear();
|
||||
m_requested.clear();
|
||||
m_places = 0;
|
||||
m_poolfull = false;
|
||||
}
|
||||
auto const oldplaces{m_places};
|
||||
|
||||
auto first{m_places};
|
||||
m_pool.resize(sources.size());
|
||||
m_requested.resize(sources.size());
|
||||
for (std::size_t i = kept; i < sources.size(); ++i)
|
||||
{
|
||||
m_requested[i] = sources[i].max_particles();
|
||||
auto const size{static_cast<std::uint32_t>(std::min<std::size_t>(m_requested[i], MAX_SMOKE_PLACES))};
|
||||
m_pool[i] = pool_source{};
|
||||
if (first + size > MAX_SMOKE_PLACES)
|
||||
{
|
||||
if (false == m_poolfull)
|
||||
WriteLog("particles: the smoke of some sources left out, over " + std::to_string(MAX_SMOKE_PLACES) + " places of particles");
|
||||
m_poolfull = true;
|
||||
continue;
|
||||
}
|
||||
m_pool[i].first = first;
|
||||
m_pool[i].size = size;
|
||||
first += size;
|
||||
}
|
||||
m_places = first;
|
||||
|
||||
std::vector<std::uint32_t> owners(std::max<std::uint32_t>(m_places, 1), 0);
|
||||
for (std::size_t i = 0; i < m_pool.size(); ++i)
|
||||
std::fill_n(owners.begin() + m_pool[i].first, m_pool[i].size, static_cast<std::uint32_t>(i));
|
||||
|
||||
auto const places{static_cast<GLsizeiptr>(std::max<std::uint32_t>(m_places, 1))};
|
||||
// the particles of the places kept go over to the new storage, the new places are dead
|
||||
auto particles{std::make_unique<gl::ssbo>(places * static_cast<GLsizeiptr>(sizeof(gl::particle_smoke)), gl::PARTICLE_SSBO_BINDING)};
|
||||
::glClearNamedBufferData(*particles, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, nullptr);
|
||||
if (m_particles && (oldplaces > 0) && (kept > 0))
|
||||
::glCopyNamedBufferSubData(*m_particles, *particles, 0, 0, static_cast<GLsizeiptr>(oldplaces) * static_cast<GLsizeiptr>(sizeof(gl::particle_smoke)));
|
||||
m_particles = std::move(particles);
|
||||
|
||||
auto const ensure = [](std::unique_ptr<gl::ssbo> &Buffer, GLsizeiptr const Size, GLuint const Binding) {
|
||||
if (!Buffer)
|
||||
Buffer = std::make_unique<gl::ssbo>(Size, Binding);
|
||||
else
|
||||
Buffer->resize(Size);
|
||||
};
|
||||
ensure(m_owners, places * static_cast<GLsizeiptr>(sizeof(std::uint32_t)), gl::PARTICLE_OWNER_SSBO_BINDING);
|
||||
ensure(m_smokeoutput, places * static_cast<GLsizeiptr>(sizeof(gl::particle_draw)), gl::PARTICLE_OUTPUT_SSBO_BINDING);
|
||||
ensure(m_sources, static_cast<GLsizeiptr>(std::max<std::size_t>(sources.size(), 1) * sizeof(gl::particle_smoke_source)), gl::PARTICLE_SOURCE_SSBO_BINDING);
|
||||
m_owners->update(owners.data(), 0, static_cast<GLsizeiptr>(owners.size() * sizeof(std::uint32_t)));
|
||||
m_sourcerecords.resize(sources.size(), gl::particle_smoke_source{});
|
||||
}
|
||||
|
||||
void opengl46_gpu_particles::simulate_smoke(particle_manager &Particles, glm::dvec3 const &Camera)
|
||||
{
|
||||
if (false == init())
|
||||
return;
|
||||
layout(Particles);
|
||||
if (Particles.updates() == m_lastupdate)
|
||||
return;
|
||||
m_lastupdate = Particles.updates();
|
||||
if (m_places == 0)
|
||||
return;
|
||||
|
||||
// the origin of the positions near the camera; when it moves, the particles move by as much the other way
|
||||
glm::vec3 shift{0.f};
|
||||
auto const snapped{glm::floor(Camera / ORIGIN_STEP) * ORIGIN_STEP};
|
||||
if (false == m_originset)
|
||||
{
|
||||
m_origin = snapped;
|
||||
m_originset = true;
|
||||
}
|
||||
else if (glm::length(Camera - m_origin) > ORIGIN_REACH)
|
||||
{
|
||||
shift = glm::vec3{m_origin - snapped};
|
||||
m_origin = snapped;
|
||||
}
|
||||
|
||||
// the light of the smoke as the cpu works it out, for gfx.smoke.lighting off
|
||||
auto const oldlight{(glm::vec3{Global.DayLight.ambient} + 0.35f * glm::vec3{Global.DayLight.diffuse}) * simulation::Environment.light_intensity()};
|
||||
auto const &sources{Particles.sequence()};
|
||||
m_smokealive = 0;
|
||||
for (std::size_t i = 0; i < sources.size(); ++i)
|
||||
{
|
||||
auto const &state{sources[i].gpu()};
|
||||
auto &pool{m_pool[i]};
|
||||
auto &record{m_sourcerecords[i]};
|
||||
auto const spawns{(pool.size > 0) ? std::min(state.spawns, pool.size) : 0u};
|
||||
record.pool = glm::uvec4{pool.first, pool.size, pool.head, spawns};
|
||||
record.location = glm::vec4{glm::vec3{state.location - m_origin}, 0.f};
|
||||
record.previous = glm::vec4{glm::vec3{state.previous - m_origin}, 0.f};
|
||||
record.launch = glm::vec4{state.inclination[0], state.inclination[1], state.velocity[0], state.velocity[1]};
|
||||
record.initial = glm::vec4{state.size[0], state.size[1], state.opacity[0], state.opacity[1]};
|
||||
record.sizechange = glm::vec4{state.sizechange[0], state.sizechange[1], state.sizechange[2], 0.f};
|
||||
record.opacitychange = glm::vec4{state.opacitychange[0], state.opacitychange[1], state.opacitychange[2], 0.f};
|
||||
record.motion = glm::vec4{state.rise, state.damping, state.risemin, 0.f};
|
||||
record.color = glm::vec4{glm::clamp(state.color, glm::vec3{0.f}, glm::vec3{1.f}), 0.f};
|
||||
record.oldcolor = glm::vec4{glm::clamp(state.color * oldlight, glm::vec3{0.f}, glm::vec3{1.f}), 0.f};
|
||||
if (pool.size > 0)
|
||||
{
|
||||
pool.head = (pool.head + spawns) % pool.size;
|
||||
if (spawns > 0)
|
||||
m_smokealive += pool.size;
|
||||
}
|
||||
}
|
||||
if (false == m_sourcerecords.empty())
|
||||
m_sources->update(m_sourcerecords.data(), 0, static_cast<GLsizeiptr>(m_sourcerecords.size() * sizeof(gl::particle_smoke_source)));
|
||||
|
||||
m_simulatetimer.begin();
|
||||
m_particles->bind_base();
|
||||
m_sources->bind_base();
|
||||
m_owners->bind_base();
|
||||
auto const wind{glm::vec3{simulation::Environment.wind()}};
|
||||
auto &program{*m_smokesimulate};
|
||||
::glProgramUniform1ui(program, 0, m_places);
|
||||
::glProgramUniform4f(program, 1, static_cast<float>(Particles.timedelta()), shift.x, shift.y, shift.z);
|
||||
::glProgramUniform4f(program, 2, wind.x, wind.y, wind.z, 0.f);
|
||||
::glProgramUniform1ui(program, 3, static_cast<GLuint>(m_lastupdate));
|
||||
program.dispatch(groups(m_places));
|
||||
::glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
m_simulatetimer.end();
|
||||
}
|
||||
|
||||
std::array<glm::vec4, 5> opengl46_gpu_particles::planes(view const &View)
|
||||
{
|
||||
// the sides and the near side of the clip space, w >= 0 (the far plane is at infinity)
|
||||
auto const matrix{glm::transpose(View.projection * View.modelview)};
|
||||
std::array<glm::vec4, 5> result{matrix[3] + matrix[0], matrix[3] - matrix[0], matrix[3] + matrix[1], matrix[3] - matrix[1], matrix[3]};
|
||||
for (auto &plane : result)
|
||||
{
|
||||
auto const length{glm::length(glm::vec3{plane})};
|
||||
if (length > 0.f)
|
||||
plane /= length;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void opengl46_gpu_particles::collect(gl::readback &Readback, std::size_t &Drawn)
|
||||
{
|
||||
GLuint command[4];
|
||||
if (Readback.pop(command))
|
||||
Drawn = command[1];
|
||||
}
|
||||
|
||||
void opengl46_gpu_particles::draw(GLuint const Output, GLuint const Command, gl::program &Program)
|
||||
{
|
||||
auto const culling{::glIsEnabled(GL_CULL_FACE) == GL_TRUE};
|
||||
if (culling)
|
||||
::glDisable(GL_CULL_FACE);
|
||||
Program.bind();
|
||||
// the vertex array is bound behind the back of gl::vao, which is told nothing is bound afterwards
|
||||
gl::vao::unbind();
|
||||
::glVertexArrayVertexBuffer(m_vao, 0, Output, 0, sizeof(gl::particle_draw));
|
||||
::glBindVertexArray(m_vao);
|
||||
::glBindBuffer(GL_DRAW_INDIRECT_BUFFER, Command);
|
||||
::glDrawArraysIndirect(GL_TRIANGLE_STRIP, nullptr);
|
||||
::glBindBuffer(GL_DRAW_INDIRECT_BUFFER, 0);
|
||||
gl::vao::unbind();
|
||||
gl::program::unbind();
|
||||
if (culling)
|
||||
::glEnable(GL_CULL_FACE);
|
||||
}
|
||||
|
||||
std::size_t opengl46_gpu_particles::render_smoke(view const &View)
|
||||
{
|
||||
if ((false == init()) || (m_places == 0))
|
||||
return 0;
|
||||
collect(*m_smokereadback, m_smokedrawn);
|
||||
|
||||
m_smoketimer.begin();
|
||||
GLuint const command[4]{4, 0, 0, 0};
|
||||
m_smokecommand->update(command, 0, sizeof(command));
|
||||
m_particles->bind_base();
|
||||
m_sources->bind_base();
|
||||
m_owners->bind_base();
|
||||
m_smokeoutput->bind_base();
|
||||
m_smokecommand->bind_base();
|
||||
auto &program{*m_smokeprepare};
|
||||
auto const origin{glm::vec3{m_origin - View.camera}};
|
||||
auto const viewplanes{planes(View)};
|
||||
::glProgramUniform1ui(program, 0, m_places);
|
||||
::glProgramUniform3f(program, 1, origin.x, origin.y, origin.z);
|
||||
::glProgramUniformMatrix4fv(program, 2, 1, GL_FALSE, glm::value_ptr(View.modelview));
|
||||
::glProgramUniform4f(program, 3, Global.gfx_smoke_lighting ? 1.f : 0.f, SMOKE_FORWARD, static_cast<float>(m_places), 0.f);
|
||||
::glProgramUniform4fv(program, 4, 5, glm::value_ptr(viewplanes[0]));
|
||||
program.dispatch(groups(m_places));
|
||||
::glMemoryBarrier(GL_COMMAND_BARRIER_BIT | GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT);
|
||||
|
||||
::glProgramUniform4f(*m_smokedraw, 0, View.soft ? Global.gfx_particles_soft : 0.f, 0.f, 0.f, 0.f);
|
||||
draw(*m_smokeoutput, *m_smokecommand, *m_smokedraw);
|
||||
m_smoketimer.end();
|
||||
|
||||
m_smokereadback->push(*m_smokecommand);
|
||||
return m_smokedrawn;
|
||||
}
|
||||
|
||||
std::size_t opengl46_gpu_particles::render_precipitation(view const &View, bool const Snow, float const Intensity, unsigned int const Frame)
|
||||
{
|
||||
if (false == init())
|
||||
return 0;
|
||||
collect(*m_precipitationreadback, m_precipitationdrawn);
|
||||
|
||||
auto const &kind{Snow ? SNOW : RAIN};
|
||||
// once per frame: the time moves on, the particles drift with the wind and fall, the camera has its velocity
|
||||
if ((false == m_framestarted) || (Frame != m_frame))
|
||||
{
|
||||
m_framestarted = true;
|
||||
m_frame = Frame;
|
||||
auto const seconds{Timer::GetDeltaTime()};
|
||||
if (seconds > 0.0)
|
||||
{
|
||||
auto const wind{glm::dvec3{simulation::Environment.wind()} * WIND_SHARE};
|
||||
for (std::size_t box = 0; box < BOX_SIZE.size(); ++box)
|
||||
{
|
||||
auto const size{static_cast<double>(BOX_SIZE[box])};
|
||||
m_drift[box] = glm::mod(m_drift[box] + wind * seconds, size);
|
||||
m_fall[box] = std::fmod(m_fall[box] + kind.fall * seconds, 16.0 * size);
|
||||
}
|
||||
m_time = std::fmod(m_time + seconds, 3600.0);
|
||||
if (m_camerastarted)
|
||||
{
|
||||
auto const velocity{(View.camera - m_lastcamera) / seconds};
|
||||
if (glm::length(velocity) < CAMERA_JUMP)
|
||||
m_cameravelocity = glm::mix(m_cameravelocity, velocity, 1.0 - std::exp(-10.0 * seconds));
|
||||
}
|
||||
}
|
||||
m_lastcamera = View.camera;
|
||||
m_camerastarted = true;
|
||||
}
|
||||
|
||||
// the particles, more of them in heavier rain or snow
|
||||
auto const most{static_cast<std::uint32_t>(std::max(Global.gfx_precipitation_particles, 0))};
|
||||
auto const count{static_cast<std::uint32_t>(most * std::clamp(0.15f + 0.85f * Intensity, 0.f, 1.f))};
|
||||
m_precipitationcount = count;
|
||||
m_precipitationsnow = Snow;
|
||||
if (count == 0)
|
||||
return 0;
|
||||
if (m_precipitationcapacity < count)
|
||||
{
|
||||
m_precipitationcapacity = std::max(count, most);
|
||||
auto const size{static_cast<GLsizeiptr>(m_precipitationcapacity) * static_cast<GLsizeiptr>(sizeof(gl::particle_draw))};
|
||||
if (!m_precipitationoutput)
|
||||
m_precipitationoutput = std::make_unique<gl::ssbo>(size, gl::PARTICLE_OUTPUT_SSBO_BINDING);
|
||||
else
|
||||
m_precipitationoutput->resize(size);
|
||||
}
|
||||
|
||||
m_precipitationtimer.begin();
|
||||
GLuint const command[4]{4, 0, 0, 0};
|
||||
m_precipitationcommand->update(command, 0, sizeof(command));
|
||||
m_precipitationoutput->bind_base();
|
||||
m_precipitationcommand->bind_base();
|
||||
auto &program{*m_precipitationprepare};
|
||||
auto const second{static_cast<std::uint32_t>(count * BOX_SHARE[0])};
|
||||
auto const third{static_cast<std::uint32_t>(count * (BOX_SHARE[0] + BOX_SHARE[1]))};
|
||||
::glProgramUniform4ui(program, 0, second, third, count, Snow ? 1u : 0u);
|
||||
std::array<glm::vec4, 3> offsets;
|
||||
for (std::size_t box = 0; box < BOX_SIZE.size(); ++box)
|
||||
{
|
||||
auto const size{static_cast<double>(BOX_SIZE[box])};
|
||||
offsets[box] = glm::vec4{glm::vec3{glm::mod(m_drift[box] - glm::mod(View.camera, size), size)}, static_cast<float>(m_fall[box])};
|
||||
}
|
||||
::glProgramUniform4fv(program, 1, 3, glm::value_ptr(offsets[0]));
|
||||
::glProgramUniform4f(program, 4, BOX_SIZE[0], BOX_SIZE[1], BOX_SIZE[2], 0.f);
|
||||
auto const velocity{glm::vec3{glm::dvec3{simulation::Environment.wind()} * WIND_SHARE - m_cameravelocity}};
|
||||
::glProgramUniform4f(program, 5, velocity.x, velocity.y, velocity.z, kind.fall);
|
||||
::glProgramUniform4f(program, 6, static_cast<float>(m_time), kind.exposure, kind.opacity, static_cast<float>(m_precipitationcapacity));
|
||||
::glProgramUniformMatrix4fv(program, 7, 1, GL_FALSE, glm::value_ptr(View.modelview));
|
||||
::glProgramUniform4f(program, 11, 1.f, kind.forward, kind.brightness, 0.f);
|
||||
auto const viewplanes{planes(View)};
|
||||
::glProgramUniform4fv(program, 12, 5, glm::value_ptr(viewplanes[0]));
|
||||
program.dispatch(groups(count));
|
||||
::glMemoryBarrier(GL_COMMAND_BARRIER_BIT | GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT);
|
||||
|
||||
// the width of a pixel a metre in front of the camera, for the least width of a streak
|
||||
auto const pixel{(View.height > 0) && (View.projection[1][1] != 0.f) ? 2.f / (std::abs(View.projection[1][1]) * static_cast<float>(View.height)) : 0.f};
|
||||
::glProgramUniform4f(*m_precipitationdraw, 0, View.soft ? 0.25f * Global.gfx_particles_soft : 0.f, 0.f, 0.f, 0.f);
|
||||
::glProgramUniform4f(*m_precipitationdraw, 1, pixel, 0.f, 0.f, 0.f);
|
||||
draw(*m_precipitationoutput, *m_precipitationcommand, *m_precipitationdraw);
|
||||
m_precipitationtimer.end();
|
||||
|
||||
m_precipitationreadback->push(*m_precipitationcommand);
|
||||
return m_precipitationdrawn;
|
||||
}
|
||||
|
||||
std::string opengl46_gpu_particles::info() const
|
||||
{
|
||||
if (m_state <= 0)
|
||||
return {};
|
||||
std::string text;
|
||||
if (m_places > 0)
|
||||
text += " smoke of the gpu: " + std::to_string(m_smokedrawn) + " drawn, " + std::to_string(m_places) + " places of " + std::to_string(m_pool.size()) + " sources ("
|
||||
+ std::to_string(m_smokealive) + " of the ones which bore lately)" + (m_poolfull ? ", some sources left out" : "") + "\n";
|
||||
if (m_precipitationcount > 0)
|
||||
text += std::string(" ") + (m_precipitationsnow ? "snow" : "rain") + " of the gpu: " + std::to_string(m_precipitationdrawn) + " of " + std::to_string(m_precipitationcount)
|
||||
+ " drawn\n";
|
||||
return text;
|
||||
}
|
||||
|
||||
std::string opengl46_gpu_particles::info_times()
|
||||
{
|
||||
std::string text;
|
||||
auto const add = [&text](gl::gpu_timer &Timer, char const *Name) {
|
||||
if (auto const time = Timer.result())
|
||||
text += std::string(text.empty() ? " particles:" : ",") + " " + Name + " " + to_string(static_cast<double>(*time) / 1000000.0, 3) + " ms";
|
||||
};
|
||||
if (m_places > 0)
|
||||
{
|
||||
add(m_simulatetimer, "smoke simulation");
|
||||
add(m_smoketimer, "smoke");
|
||||
}
|
||||
if (m_precipitationcount > 0)
|
||||
add(m_precipitationtimer, m_precipitationsnow ? "snow" : "rain");
|
||||
if (false == text.empty())
|
||||
text += "\n";
|
||||
return text;
|
||||
}
|
||||
137
rendering/opengl46/opengl46gpuparticles.h
Normal file
137
rendering/opengl46/opengl46gpuparticles.h
Normal file
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
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 <string>
|
||||
#include <vector>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "gl/shader.h"
|
||||
#include "gl/ssbo.h"
|
||||
#include "gl/ubo.h"
|
||||
#include "gl/query.h"
|
||||
#include "gl/readback.h"
|
||||
|
||||
class particle_manager;
|
||||
|
||||
// particles of the gpu for the opengl 4.6 renderer: the smoke and the rain or snow.
|
||||
//
|
||||
// smoke: the sources (particle_manager in its gpu mode) work out on the cpu only how many particles they bear in an update of
|
||||
// the simulation and what these start from; the particles are kept in a storage buffer, a ring of places for each source, and
|
||||
// simulated by a compute pass once per update (particles_smoke_simulate.comp). in every colour pass which draws them another
|
||||
// compute pass (particles_smoke_prepare.comp) leaves out the dead ones and the ones out of the view, lights the rest with the
|
||||
// lights of the pass and adds them to an indirect draw of billboards (particles_smoke.vert/.frag).
|
||||
//
|
||||
// rain and snow: particles in three boxes around the camera, each repeated along the world axes, which keep no state: their
|
||||
// place follows from their number, how far the wind took them and how far they fell (particles_precipitation.comp), drawn as
|
||||
// streaks of their motion against the camera (particles_precipitation.vert/.frag).
|
||||
//
|
||||
// the positions of the smoke are kept from an origin near the camera, moved by whole steps as the camera goes, so they keep
|
||||
// their precision far from the centre of the scenery
|
||||
class opengl46_gpu_particles
|
||||
{
|
||||
public:
|
||||
opengl46_gpu_particles() = default;
|
||||
~opengl46_gpu_particles();
|
||||
opengl46_gpu_particles(opengl46_gpu_particles const &) = delete;
|
||||
opengl46_gpu_particles &operator=(opengl46_gpu_particles const &) = delete;
|
||||
|
||||
// what a colour pass draws with
|
||||
struct view
|
||||
{
|
||||
glm::mat4 modelview; // world axes around the camera to the view space of the pass
|
||||
glm::mat4 projection;
|
||||
glm::dvec3 camera;
|
||||
int height{0}; // of the target, pixels
|
||||
bool soft{false}; // the resolved depth of the scene drawn so far is bound to gl::SCENE_DEPTH_TEX
|
||||
};
|
||||
|
||||
// whether the programs could be made; without them the smoke stays on the cpu and the rain is the old cylinder
|
||||
bool smoke_available();
|
||||
bool precipitation_available();
|
||||
// once per update of the particle manager (in its gpu mode): the births and the change of the smoke. Camera: of the view
|
||||
void simulate_smoke(particle_manager &Particles, glm::dvec3 const &Camera);
|
||||
// draws the smoke the view sees, with the texture bound to unit 0. returns: particles drawn, as read back a few frames later
|
||||
std::size_t render_smoke(view const &View);
|
||||
// draws the rain (Snow false) or the snow of the intensity 0-1. Frame: number of the frame, the time moves on once per frame.
|
||||
// returns: particles drawn, read back
|
||||
std::size_t render_precipitation(view const &View, bool Snow, float Intensity, unsigned int Frame);
|
||||
// drops what is on the gpu, e.g. when the context goes
|
||||
void clear();
|
||||
|
||||
std::string info() const;
|
||||
std::string info_times();
|
||||
|
||||
private:
|
||||
struct pool_source
|
||||
{
|
||||
std::uint32_t first{0};
|
||||
std::uint32_t size{0};
|
||||
std::uint32_t head{0};
|
||||
};
|
||||
|
||||
bool init();
|
||||
// the places of the sources in the storage: sources added after the others get theirs after the places already given, and
|
||||
// the particles stay; otherwise all is laid out anew
|
||||
void layout(particle_manager &Particles);
|
||||
// reads back the instances counted by a pass; Drawn is left as it was when nothing came back yet
|
||||
static void collect(gl::readback &Readback, std::size_t &Drawn);
|
||||
// the planes of the view of the pass, for the culling of the compute passes
|
||||
static std::array<glm::vec4, 5> planes(view const &View);
|
||||
void draw(GLuint Output, GLuint Command, gl::program &Program);
|
||||
|
||||
int m_state{0}; // 0: not set up yet, 1: ready, -1: can't be used
|
||||
std::unique_ptr<gl::program> m_smokesimulate;
|
||||
std::unique_ptr<gl::program> m_smokeprepare;
|
||||
std::unique_ptr<gl::program> m_smokedraw;
|
||||
std::unique_ptr<gl::program> m_precipitationprepare;
|
||||
std::unique_ptr<gl::program> m_precipitationdraw;
|
||||
GLuint m_vao{0}; // the attributes of the instances, from the buffer of the draw bound to it before the draw
|
||||
|
||||
// smoke
|
||||
std::vector<pool_source> m_pool;
|
||||
std::vector<std::size_t> m_requested; // the sizes the sources asked for
|
||||
std::vector<gl::particle_smoke_source> m_sourcerecords;
|
||||
std::uint32_t m_places{0};
|
||||
bool m_poolfull{false}; // some sources got no places
|
||||
std::unique_ptr<gl::ssbo> m_particles;
|
||||
std::unique_ptr<gl::ssbo> m_sources;
|
||||
std::unique_ptr<gl::ssbo> m_owners;
|
||||
std::unique_ptr<gl::ssbo> m_smokeoutput;
|
||||
std::unique_ptr<gl::ssbo> m_smokecommand;
|
||||
std::unique_ptr<gl::readback> m_smokereadback;
|
||||
std::uint64_t m_lastupdate{0};
|
||||
glm::dvec3 m_origin{0.0};
|
||||
bool m_originset{false};
|
||||
std::size_t m_smokedrawn{0};
|
||||
std::size_t m_smokealive{0}; // places of the sources which bore particles lately
|
||||
gl::gpu_timer m_simulatetimer;
|
||||
gl::gpu_timer m_smoketimer;
|
||||
|
||||
// rain and snow
|
||||
std::unique_ptr<gl::ssbo> m_precipitationoutput;
|
||||
std::unique_ptr<gl::ssbo> m_precipitationcommand;
|
||||
std::unique_ptr<gl::readback> m_precipitationreadback;
|
||||
std::uint32_t m_precipitationcapacity{0};
|
||||
unsigned int m_frame{0};
|
||||
bool m_framestarted{false};
|
||||
std::array<glm::dvec3, 3> m_drift{}; // how far the wind took the particles of each box, within the box
|
||||
std::array<double, 3> m_fall{}; // how far they fell, within 16 sizes of the box
|
||||
double m_time{0.0}; // seconds, for the flutter of the flakes
|
||||
glm::dvec3 m_lastcamera{0.0};
|
||||
glm::dvec3 m_cameravelocity{0.0};
|
||||
bool m_camerastarted{false};
|
||||
std::size_t m_precipitationdrawn{0};
|
||||
std::uint32_t m_precipitationcount{0};
|
||||
bool m_precipitationsnow{false};
|
||||
gl::gpu_timer m_precipitationtimer;
|
||||
};
|
||||
@@ -865,6 +865,7 @@ void opengl46_renderer::SwapBuffers()
|
||||
m_debugtimestext += m_localshadows.info_times();
|
||||
if( Global.gfx_shadow_sdsm )
|
||||
m_debugtimestext += m_shadowfit.info_times();
|
||||
m_debugtimestext += m_gpuparticles.info_times();
|
||||
m_debugtimestext += "uilayer: " + to_string( Timer::subsystem.gfx_gui.average(), 2 ) + " ms\n";
|
||||
if( DebugModeFlag )
|
||||
m_debugtimestext += m_textures.info();
|
||||
@@ -964,6 +965,7 @@ 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_instancecull.info();
|
||||
if( Global.gfx_gpucull_shadows ) {
|
||||
for( std::size_t cascade = 0; cascade < m_shadowcull.size(); ++cascade ) {
|
||||
@@ -1062,6 +1064,21 @@ void opengl46_renderer::draw_debug_ui()
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
if (ImGui::Begin("Particles"))
|
||||
{
|
||||
ImGui::Checkbox("smoke simulated and drawn on the gpu (gfx.smoke.gpu)", &Global.gfx_smoke_gpu);
|
||||
if (Global.gfx_smoke_gpu)
|
||||
ImGui::Checkbox("smoke lit by the sun, the sky and the lamps (gfx.smoke.lighting)", &Global.gfx_smoke_lighting);
|
||||
ImGui::Checkbox("rain and snow as particles of the gpu (gfx.precipitation.gpu)", &Global.gfx_precipitation_gpu);
|
||||
if (Global.gfx_precipitation_gpu)
|
||||
ImGui::SliderInt("particles of the heaviest rain or snow (gfx.precipitation.particles)", &Global.gfx_precipitation_particles, 0, 262144);
|
||||
ImGui::SliderFloat("soft particles, share of their size, 0: off (gfx.particles.soft)", &Global.gfx_particles_soft, 0.f, 4.f, "%.2f");
|
||||
auto const info{m_gpuparticles.info()};
|
||||
if (!info.empty())
|
||||
ImGui::TextUnformatted(info.c_str() + 1);
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
if (ImGui::Begin("Sky and exposure"))
|
||||
{
|
||||
m_sky.draw_debug_ui();
|
||||
@@ -1464,6 +1481,12 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode)
|
||||
Render_Alpha(simulation::Region);
|
||||
Render_editor_overlay();
|
||||
|
||||
// the depth drawn so far, which the particles of the gpu fade in front of
|
||||
m_particlesoft =
|
||||
( Global.gfx_particles_soft > 0.f )
|
||||
&& ( ( Global.Smoke && Global.gfx_smoke_gpu && ( false == simulation::Particles.sequence().empty() ) ) || ( Global.gfx_precipitation_gpu && ( Global.Overcast > 1.f ) ) )
|
||||
&& Setup_Particles_Depth( vp );
|
||||
|
||||
// particles
|
||||
Render_particles();
|
||||
|
||||
@@ -6897,10 +6920,59 @@ void opengl46_renderer::Render_particles()
|
||||
model_ubo->update(model_ubs);
|
||||
|
||||
Bind_Texture(0, m_smoketexture);
|
||||
// the smoke of the gpu, when its programs could be made; otherwise the particle manager simulates it on the cpu. a switch
|
||||
// takes effect from the next update of the manager
|
||||
auto const gpu{Global.gfx_smoke_gpu && m_gpuparticles.smoke_available()};
|
||||
simulation::Particles.gpu(gpu);
|
||||
if (gpu)
|
||||
{
|
||||
if (Global.Smoke)
|
||||
{
|
||||
m_gpuparticles.simulate_smoke(simulation::Particles, m_renderpass.pass_camera.position());
|
||||
m_renderpass.draw_stats.particles = static_cast<int>(m_gpuparticles.render_smoke(Particles_View()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
m_particlerenderer.update(m_renderpass.pass_camera);
|
||||
m_renderpass.draw_stats.particles = m_particlerenderer.render();
|
||||
}
|
||||
|
||||
opengl46_gpu_particles::view opengl46_renderer::Particles_View() const
|
||||
{
|
||||
GLint viewport[4];
|
||||
::glGetIntegerv(GL_VIEWPORT, viewport);
|
||||
opengl46_gpu_particles::view view;
|
||||
view.modelview = OpenGLMatrices.data(GL_MODELVIEW);
|
||||
view.projection = OpenGLMatrices.data(GL_PROJECTION);
|
||||
view.camera = m_renderpass.pass_camera.position();
|
||||
view.height = viewport[3];
|
||||
view.soft = m_particlesoft;
|
||||
return view;
|
||||
}
|
||||
|
||||
bool opengl46_renderer::Setup_Particles_Depth(viewport_config &Viewport)
|
||||
{
|
||||
// the fading works out the distance from the depth as the contact shadows do (Contact_Shadows_Wanted())
|
||||
if (Global.gfx_skippipeline || Global.bWireFrame || (Viewport.msaa_fb == nullptr))
|
||||
return false;
|
||||
auto const &projection{scene_ubs.projection};
|
||||
if ((projection[2][3] == 0.f) || (projection[2][2] != 0.f) || (projection[3][2] <= 0.f))
|
||||
return false;
|
||||
if (false == Resolved_Depth_Target(Viewport))
|
||||
return false;
|
||||
// making the target binds it
|
||||
Viewport.msaa_fb->bind();
|
||||
// the scissor test would limit the blit
|
||||
auto const scissor{::glIsEnabled(GL_SCISSOR_TEST) == GL_TRUE};
|
||||
if (scissor)
|
||||
::glDisable(GL_SCISSOR_TEST);
|
||||
::glBlitNamedFramebuffer(*Viewport.msaa_fb, *Viewport.clusters_depth_fb, 0, 0, Viewport.width, Viewport.height, 0, 0, Viewport.width, Viewport.height, GL_DEPTH_BUFFER_BIT, GL_NEAREST);
|
||||
if (scissor)
|
||||
::glEnable(GL_SCISSOR_TEST);
|
||||
::glBindTextureUnit(gl::SCENE_DEPTH_TEX, Viewport.clusters_depth_tex->id);
|
||||
return true;
|
||||
}
|
||||
|
||||
void opengl46_renderer::Render_vr_models()
|
||||
{
|
||||
if (!vr)
|
||||
@@ -6927,6 +6999,15 @@ void opengl46_renderer::Render_precipitation()
|
||||
return;
|
||||
}
|
||||
|
||||
if (Global.gfx_precipitation_gpu && m_gpuparticles.precipitation_available())
|
||||
{
|
||||
// the particles of the gpu: the overcast above 1 is how heavy the rain or snow is
|
||||
model_ubs.set_modelview(OpenGLMatrices.data(GL_MODELVIEW));
|
||||
model_ubo->update(model_ubs);
|
||||
m_gpuparticles.render_precipitation(Particles_View(), Global.Weather == "snow:", std::clamp(Global.Overcast - 1.f, 0.f, 1.f), m_framestamp);
|
||||
return;
|
||||
}
|
||||
|
||||
::glPushMatrix();
|
||||
// tilt the precipitation cone against the velocity vector for crude motion blur
|
||||
// include current wind vector while at it
|
||||
|
||||
@@ -13,6 +13,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "rendering/openglcamera.h"
|
||||
#include "rendering/opengl46/opengl46light.h"
|
||||
#include "rendering/opengl46/opengl46particles.h"
|
||||
#include "rendering/opengl46/opengl46gpuparticles.h"
|
||||
#include "rendering/opengl46/opengl46skydome.h"
|
||||
#include "rendering/opengl46/opengl46precipitation.h"
|
||||
#include "simulation/simulationenvironment.h"
|
||||
@@ -447,6 +448,9 @@ class opengl46_renderer : public gfx_renderer {
|
||||
void Setup_Contact_Shadows( viewport_config &Viewport );
|
||||
// makes the target of the resolved depth of the viewport if it isn't there yet; false if it can't be made
|
||||
bool Resolved_Depth_Target( viewport_config &Viewport );
|
||||
// resolves the depth drawn so far in the colour pass of the viewport for the soft particles (gfx.particles.soft) and binds it
|
||||
// to gl::SCENE_DEPTH_TEX; false when the viewport has no such depth
|
||||
bool Setup_Particles_Depth( viewport_config &Viewport );
|
||||
// the direction the sun shadows are cast in (the sun, its height capped by gfx.shadow.angle.min), and the rotation from
|
||||
// the world to the space of the light of the cascades: x and y across the maps, z along the light
|
||||
glm::vec3 Shadow_Light_Vector() const;
|
||||
@@ -495,6 +499,8 @@ class opengl46_renderer : public gfx_renderer {
|
||||
void Render(TMemCell *Memcell);
|
||||
void Render_particles();
|
||||
void Render_precipitation();
|
||||
// the view of the colour pass being drawn, for the particles of the gpu
|
||||
opengl46_gpu_particles::view Particles_View() const;
|
||||
// the grass of the heightmap terrain around the camera of the main view; Future: the matrix of the motion vectors
|
||||
void Render_grass(glm::mat4 const &Future);
|
||||
void Render_vr_models();
|
||||
@@ -588,6 +594,8 @@ class opengl46_renderer : public gfx_renderer {
|
||||
opengl46_skydome m_skydomerenderer;
|
||||
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)
|
||||
bool m_particlesoft{false}; // the depth for the soft particles is bound in the colour pass being drawn
|
||||
|
||||
unsigned int m_framestamp; // id of currently rendered gfx frame
|
||||
float m_framerate;
|
||||
|
||||
@@ -152,15 +152,9 @@ smoke_source::bind( TAnimModel const *Node ) {
|
||||
owner_type::none );
|
||||
}
|
||||
|
||||
// updates state of owned particles
|
||||
// adds the particles to spawn in an update of Timedelta to m_spawncount
|
||||
void
|
||||
smoke_source::update( double const Timedelta, bool const Onlydespawn ) {
|
||||
|
||||
// prepare bounding box for new pass
|
||||
// TODO: include bounding box in the bounding_area class
|
||||
bounding_box boundingbox {
|
||||
glm::dvec3{ std::numeric_limits<double>::max() },
|
||||
glm::dvec3{ std::numeric_limits<double>::lowest() } };
|
||||
smoke_source::accumulate_spawns( double const Timedelta, bool const Onlydespawn ) {
|
||||
|
||||
m_spawncount = (
|
||||
( ( false == Global.Smoke ) || ( true == Onlydespawn ) ) ?
|
||||
@@ -209,6 +203,19 @@ smoke_source::update( double const Timedelta, bool const Onlydespawn ) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// updates state of owned particles
|
||||
void
|
||||
smoke_source::update( double const Timedelta, bool const Onlydespawn ) {
|
||||
|
||||
// prepare bounding box for new pass
|
||||
// TODO: include bounding box in the bounding_area class
|
||||
bounding_box boundingbox {
|
||||
glm::dvec3{ std::numeric_limits<double>::max() },
|
||||
glm::dvec3{ std::numeric_limits<double>::lowest() } };
|
||||
|
||||
accumulate_spawns( Timedelta, Onlydespawn );
|
||||
|
||||
// update spawned particles
|
||||
for( auto particleiterator { std::begin( m_particles ) }; particleiterator != std::end( m_particles ); ++particleiterator ) {
|
||||
@@ -329,23 +336,37 @@ smoke_source::initialize( smoke_particle &Particle ) {
|
||||
Particle.position = location();
|
||||
|
||||
if( m_ownertype == owner_type::vehicle ) {
|
||||
Particle.opacity *= static_cast<float>(m_owner.vehicle->MoverParameters->dizel_fill);
|
||||
float velocityscale, opacityscale;
|
||||
launch_scales( velocityscale, opacityscale );
|
||||
Particle.opacity *= opacityscale;
|
||||
Particle.velocity *= velocityscale;
|
||||
}
|
||||
}
|
||||
|
||||
// shares of the launch velocity and of the opacity of a particle born now, which the owner gives
|
||||
void
|
||||
smoke_source::launch_scales( float &Velocity, float &Opacity ) const {
|
||||
|
||||
Velocity = 1.f;
|
||||
Opacity = 1.f;
|
||||
if( m_ownertype != owner_type::vehicle ) { return; }
|
||||
|
||||
Opacity = static_cast<float>(m_owner.vehicle->MoverParameters->dizel_fill);
|
||||
if (m_owner.vehicle->MoverParameters->EngineType == TEngineType::DieselElectric)
|
||||
{
|
||||
if (m_owner.vehicle->MoverParameters->dizel_spinup == true)
|
||||
{
|
||||
Particle.velocity *= 0.38*(((m_owner.vehicle->MoverParameters->enrot)/2)*0.5); // / m_owner.vehicle->MoverParameters->dizel_fill *0.01)) ;
|
||||
Velocity = static_cast<float>(0.38*(((m_owner.vehicle->MoverParameters->enrot)/2)*0.5)); // / m_owner.vehicle->MoverParameters->dizel_fill *0.01)) ;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Particle.velocity *= ((((m_owner.vehicle->MoverParameters->enrot)/60)*0.6) * (m_owner.vehicle->MoverParameters->EnginePower *0.03)); // / m_owner.vehicle->MoverParameters->dizel_fill *0.01)) ;
|
||||
Velocity = static_cast<float>((((m_owner.vehicle->MoverParameters->enrot)/60)*0.6) * (m_owner.vehicle->MoverParameters->EnginePower *0.03)); // / m_owner.vehicle->MoverParameters->dizel_fill *0.01)) ;
|
||||
//Particle.velocity *= m_owner.vehicle->GetVelocity(); // / m_owner.vehicle->MoverParameters->dizel_fill *0.01)) ;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// updates state of provided particle and bounding box. returns: true if particle is still alive afterwards, false otherwise
|
||||
@@ -385,6 +406,58 @@ smoke_source::update( smoke_particle &Particle, bounding_box &Boundingbox, doubl
|
||||
return true;
|
||||
}
|
||||
|
||||
// the same as update() for the particles simulated on the gpu: the births of the update and what the particles start from and
|
||||
// change by, worked out as the cpu does it in update(), initialize() and update( Particle ). the particles of the cpu are dropped
|
||||
void
|
||||
smoke_source::update_gpu( double const Timedelta, bool const Onlydespawn ) {
|
||||
|
||||
m_particles.clear();
|
||||
|
||||
accumulate_spawns( Timedelta, Onlydespawn );
|
||||
// the cpu fills the free places, then replaces the oldest; a full source can't take more than its size in an update and
|
||||
// drops the rest of whole particles, as the cpu does
|
||||
auto const spawns { std::min( std::floor( m_spawncount ), static_cast<float>( m_max_particles ) ) };
|
||||
m_spawncount -= spawns;
|
||||
m_spawncount -= std::floor( m_spawncount );
|
||||
m_gpu.spawns = static_cast<std::uint32_t>( std::max( spawns, 0.f ) );
|
||||
|
||||
auto const here { location() };
|
||||
m_gpu.previous = ( m_gpu.started ? m_gpu.location : here );
|
||||
m_gpu.location = here;
|
||||
m_gpu.started = true;
|
||||
|
||||
float velocityscale, opacityscale;
|
||||
launch_scales( velocityscale, opacityscale );
|
||||
for( int limit = value_limit::min; limit <= value_limit::max; ++limit ) {
|
||||
m_gpu.inclination[ limit ] = glm::radians( m_emitter.inclination[ limit ] );
|
||||
m_gpu.velocity[ limit ] = m_emitter.velocity[ limit ] * velocityscale;
|
||||
m_gpu.size[ limit ] = m_emitter.size[ limit ];
|
||||
m_gpu.opacity[ limit ] = m_emitter.opacity[ limit ] / Global.SmokeFidelity * opacityscale;
|
||||
}
|
||||
m_gpu.sizechange[ 0 ] = m_sizemodifier.value_change();
|
||||
m_gpu.sizechange[ 1 ] = m_sizemodifier.limit( value_limit::min );
|
||||
m_gpu.sizechange[ 2 ] = m_sizemodifier.limit( value_limit::max );
|
||||
m_gpu.opacitychange[ 0 ] = m_opacitymodifier.value_change();
|
||||
m_gpu.opacitychange[ 1 ] = m_opacitymodifier.limit( value_limit::min );
|
||||
m_gpu.opacitychange[ 2 ] = m_opacitymodifier.limit( value_limit::max );
|
||||
// crude smoke dispersion simulation, as in update( Particle )
|
||||
if( m_ownertype == owner_type::vehicle ) {
|
||||
m_gpu.rise = static_cast<float>( 0.025 * std::min( 0.f, Global.AirTemperature - 90 ) );
|
||||
m_gpu.damping = static_cast<float>( 0.05 * pow( m_owner.vehicle->GetVelocity() * 1, 0.4 ) * Global.Overcast );
|
||||
m_gpu.risemin = std::numeric_limits<float>::lowest();
|
||||
}
|
||||
else {
|
||||
m_gpu.rise = static_cast<float>( 0.005 * std::min( 0.f, Global.AirTemperature - 10 ) );
|
||||
m_gpu.damping = static_cast<float>( 0.050 * Global.Overcast );
|
||||
m_gpu.risemin = static_cast<float>( 0.25 * ( 2.f - Global.Overcast ) );
|
||||
}
|
||||
m_gpu.color = m_emitter.color;
|
||||
|
||||
// the renderer culls the particles one by one; the area is where they come from, for the distance of the source
|
||||
m_area.center = here;
|
||||
m_area.radius = 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// adds a new particle source of specified type, placing it in specified world location
|
||||
@@ -437,13 +510,33 @@ particle_manager::update() {
|
||||
|
||||
if( timedelta == 0.0 ) { return; }
|
||||
|
||||
++m_updates;
|
||||
m_timedelta = timedelta;
|
||||
|
||||
auto const distancethreshold { 2 * Global.BaseDrawRange * Global.fDistanceFactor }; // to reduce workload distant enough sources won't spawn new particles
|
||||
|
||||
for( auto &source : m_sources ) {
|
||||
|
||||
auto const viewerdistance { glm::length( source.area().center - glm::dvec3{ Global.pCamera.Pos } ) - source.area().radius };
|
||||
|
||||
source.update( timedelta, viewerdistance > distancethreshold );
|
||||
if( m_gpu ) {
|
||||
source.update_gpu( timedelta, viewerdistance > distancethreshold );
|
||||
}
|
||||
else {
|
||||
source.update( timedelta, viewerdistance > distancethreshold );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
particle_manager::gpu( bool const Gpu ) {
|
||||
|
||||
if( Gpu == m_gpu ) { return; }
|
||||
m_gpu = Gpu;
|
||||
for( auto &source : m_sources ) {
|
||||
source.m_particles.clear();
|
||||
source.m_gpu.started = false;
|
||||
source.m_gpu.spawns = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,9 @@ public:
|
||||
m_valuechangemodifier == nullptr ?
|
||||
m_valuechange :
|
||||
m_valuechange / *m_valuechangemodifier ); }
|
||||
Type_
|
||||
limit( value_limit const Limit ) const {
|
||||
return m_valuelimits[ Limit ]; }
|
||||
|
||||
private:
|
||||
//types
|
||||
@@ -81,6 +84,25 @@ class smoke_source {
|
||||
public:
|
||||
// types
|
||||
using particle_sequence = std::vector<smoke_particle>;
|
||||
// what the particles simulated on the gpu (particle_manager::gpu(), opengl46_gpu_particles) take from the source in an
|
||||
// update: how many are born, where and how they start, and how they change, all as the cpu update would do it
|
||||
struct gpu_state {
|
||||
std::uint32_t spawns { 0 }; // particles born in the last update, at most max_particles()
|
||||
glm::dvec3 location { 0.0 }; // of the emitter at the last update...
|
||||
glm::dvec3 previous { 0.0 }; // ...and at the one before; the particles of an update are born along the way between
|
||||
bool started { false };
|
||||
float inclination[ 2 ] { 0.f, 0.f }; // radians
|
||||
float velocity[ 2 ] { 0.f, 0.f }; // m/s, with the share the owner gives
|
||||
float size[ 2 ] { 0.f, 0.f };
|
||||
float opacity[ 2 ] { 0.f, 0.f }; // with the smoke fidelity and the share the owner gives
|
||||
float sizechange[ 3 ] { 0.f, 0.f, 0.f }; // per second, min, max
|
||||
float opacitychange[ 3 ] { 0.f, 0.f, 0.f }; // per second (with the fidelity), min, max
|
||||
// the vertical velocity: changes by (rise - damping) * itself per second, and doesn't fall below risemin
|
||||
float rise { 0.f };
|
||||
float damping { 0.f };
|
||||
float risemin { std::numeric_limits<float>::lowest() };
|
||||
glm::vec3 color { 0.f };
|
||||
};
|
||||
// methods
|
||||
bool
|
||||
deserialize( cParser &Input );
|
||||
@@ -93,6 +115,15 @@ public:
|
||||
// updates state of owned particles
|
||||
void
|
||||
update( double const Timedelta, bool const Onlydespawn );
|
||||
// the same for particles simulated on the gpu: only the births and the state of the source they start from
|
||||
void
|
||||
update_gpu( double const Timedelta, bool const Onlydespawn );
|
||||
gpu_state const &
|
||||
gpu() const {
|
||||
return m_gpu; }
|
||||
std::size_t
|
||||
max_particles() const {
|
||||
return m_max_particles; }
|
||||
glm::vec3 const &
|
||||
color() const {
|
||||
return m_emitter.color; }
|
||||
@@ -131,6 +162,12 @@ private:
|
||||
// imports member data pair from the config file
|
||||
bool
|
||||
deserialize_mapping( cParser &Input );
|
||||
// adds the particles to spawn in an update of Timedelta to m_spawncount
|
||||
void
|
||||
accumulate_spawns( double const Timedelta, bool const Onlydespawn );
|
||||
// shares of the launch velocity and of the opacity of a particle born now, which the owner gives
|
||||
void
|
||||
launch_scales( float &Velocity, float &Opacity ) const;
|
||||
void
|
||||
initialize( smoke_particle &Particle );
|
||||
// updates state of provided particle and bounding box. returns: true if particle is still alive afterwards, false otherwise
|
||||
@@ -158,6 +195,7 @@ private:
|
||||
particle_sequence m_particles; // collection of spawned particles
|
||||
std::size_t m_max_particles; // maximum number of particles existing
|
||||
scene::bounding_area m_area; // bounding sphere of owned particles
|
||||
gpu_state m_gpu; // for the particles simulated on the gpu
|
||||
};
|
||||
|
||||
|
||||
@@ -182,6 +220,20 @@ public:
|
||||
// updates state of all owned emitters
|
||||
void
|
||||
update();
|
||||
// whether the renderer simulates the particles on the gpu: the sources then work out only their births (smoke_source::gpu());
|
||||
// the renderer sets it before the next update. the particles simulated on the cpu are dropped when it's switched on
|
||||
void
|
||||
gpu( bool const Gpu );
|
||||
bool
|
||||
gpu() const {
|
||||
return m_gpu; }
|
||||
// number of the updates in which the time went on, and the time of the last one: the gpu simulates once per update
|
||||
std::uint64_t
|
||||
updates() const {
|
||||
return m_updates; }
|
||||
double
|
||||
timedelta() const {
|
||||
return m_timedelta; }
|
||||
// data access
|
||||
source_sequence &
|
||||
sequence() {
|
||||
@@ -198,6 +250,9 @@ private:
|
||||
// members
|
||||
source_map m_sourcetemplates; // cached particle emitter configurations
|
||||
source_sequence m_sources; // all owned particle emitters
|
||||
bool m_gpu { false };
|
||||
std::uint64_t m_updates { 0 };
|
||||
double m_timedelta { 0.0 };
|
||||
};
|
||||
|
||||
|
||||
|
||||
89
shaders/particle_common.glsl
Normal file
89
shaders/particle_common.glsl
Normal file
@@ -0,0 +1,89 @@
|
||||
// storage of the particles of the gpu (opengl46_gpu_particles, gl::particle_* in gl/ubo.h) for the compute passes
|
||||
|
||||
#ifndef PARTICLE_COMMON_GLSL
|
||||
#define PARTICLE_COMMON_GLSL
|
||||
|
||||
// a particle of smoke; it's dead (or its place was never used) when its opacity is 0 or less
|
||||
struct smoke_particle_s
|
||||
{
|
||||
vec4 position; // xyz: metres from the origin of the particles (world axes), w: rotation, radians
|
||||
vec4 velocity; // xyz: m/s, w: size of the billboard, metres
|
||||
vec4 state; // x: opacity, y: age, seconds
|
||||
};
|
||||
|
||||
// a source of smoke as the last update of the simulation left it (smoke_source::gpu_state)
|
||||
struct smoke_source_s
|
||||
{
|
||||
uvec4 pool; // x: first place of the source in the storage, y: number of its places, z: place of the first particle born in the update, w: particles born
|
||||
vec4 location; // xyz: emitter at the update, from the origin
|
||||
vec4 previous; // xyz: emitter at the update before, from the origin
|
||||
vec4 launch; // xy: inclination, radians, zw: speed, m/s
|
||||
vec4 initial; // xy: size, zw: opacity
|
||||
vec4 sizechange; // x: per second, y: min, z: max
|
||||
vec4 opacitychange; // x: per second, y: min, z: max
|
||||
vec4 motion; // x: rise, y: damping (per second, of the vertical velocity), z: least vertical velocity
|
||||
vec4 color; // rgb: colour of the smoke, lit by the lights of the pass
|
||||
vec4 oldcolor; // rgb: colour lit as the cpu does it, without the lights (gfx.smoke.lighting off)
|
||||
};
|
||||
|
||||
// what a draw of a particle reads, as attributes of an instance
|
||||
struct particle_draw_s
|
||||
{
|
||||
vec4 place; // xyz: metres from the camera (world axes), w: size (smoke) or width (rain, snow)
|
||||
vec4 shape; // x: rotation (smoke); xyz: from the head of the streak to its tail (rain, snow)
|
||||
vec4 color; // rgb: lit colour, a: opacity
|
||||
};
|
||||
|
||||
// the indirect draw command of the particles; the compute passes count the instances
|
||||
layout(std430, binding = PARTICLE_COMMAND_SSBO_BINDING) buffer particle_command_ssbo
|
||||
{
|
||||
uint particle_draw_vertices;
|
||||
uint particle_draw_instances;
|
||||
uint particle_draw_first;
|
||||
uint particle_draw_base;
|
||||
};
|
||||
|
||||
layout(std430, binding = PARTICLE_OUTPUT_SSBO_BINDING) writeonly buffer particle_output_ssbo
|
||||
{
|
||||
particle_draw_s particle_draws[];
|
||||
};
|
||||
|
||||
// pcg hash
|
||||
uint particle_hash(uint value)
|
||||
{
|
||||
uint state = value * 747796405U + 2891336453U;
|
||||
uint word = ((state >> ((state >> 28U) + 4U)) ^ state) * 277803737U;
|
||||
return (word >> 22U) ^ word;
|
||||
}
|
||||
|
||||
// next of a sequence of random numbers 0-1
|
||||
float particle_random(inout uint state)
|
||||
{
|
||||
state = particle_hash(state);
|
||||
return float(state >> 8U) * (1.0 / 16777216.0);
|
||||
}
|
||||
|
||||
// whether a sphere (centre from the camera along the world axes) is within the planes of the view
|
||||
bool particle_visible(vec4 planes[5], vec3 centre, float radius)
|
||||
{
|
||||
for (int i = 0; i < 5; i++)
|
||||
if (dot(planes[i].xyz, centre) + planes[i].w < -radius)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// adds a particle to the draw; Capacity: the places of the storage of the draws
|
||||
void particle_emit(vec3 place, float size, vec3 shape, vec3 color, float opacity, uint capacity)
|
||||
{
|
||||
uint index = atomicAdd(particle_draw_instances, 1U);
|
||||
if (index >= capacity)
|
||||
{
|
||||
atomicAdd(particle_draw_instances, 0xFFFFFFFFU);
|
||||
return;
|
||||
}
|
||||
particle_draws[index].place = vec4(place, size);
|
||||
particle_draws[index].shape = vec4(shape, 0.0);
|
||||
particle_draws[index].color = vec4(color, opacity);
|
||||
}
|
||||
|
||||
#endif
|
||||
146
shaders/particle_light.glsl
Normal file
146
shaders/particle_light.glsl
Normal file
@@ -0,0 +1,146 @@
|
||||
// light of the particles of the gpu (opengl46_gpu_particles: smoke, rain, snow) at a point of the view space of the pass, as a
|
||||
// particle takes it as a whole, at its middle: the ambient light, the sun (the moon at night) held back by the shadow map and
|
||||
// by the clouds, and the lamps and headlights of the light cluster the point is in (the first lights of lights[] without the
|
||||
// clusters). each light is scattered towards the camera by the phase of the particle, Forward: 0 evenly, towards 1 mostly
|
||||
// onwards. the scale is the one of the shading of the surfaces (light_common.glsl): a white particle takes as much light as
|
||||
// a white face turned half way to it. for the compute passes; needs <common> and <weather_common.glsl>
|
||||
|
||||
#ifndef PARTICLE_LIGHT_GLSL
|
||||
#define PARTICLE_LIGHT_GLSL
|
||||
|
||||
#if SHADOWMAP_ENABLED
|
||||
uniform sampler2DArrayShadow shadowmap;
|
||||
#endif
|
||||
uniform sampler2D headlightmap;
|
||||
layout(binding = HEADLIGHT_SHADOW_TEX) uniform sampler2DArrayShadow headlight_shadowmap;
|
||||
|
||||
// AMBIENT_SCALE and SUN_DIFFUSE_SCALE of light_common.glsl
|
||||
const float PARTICLE_AMBIENT_SCALE = 0.65;
|
||||
const float PARTICLE_SUN_SCALE = 1.5;
|
||||
|
||||
// Henyey-Greenstein, 1 for every angle when G is 0
|
||||
float particle_phase(float cosangle, float g)
|
||||
{
|
||||
float g2 = g * g;
|
||||
return (1.0 - g2) / pow(max(1.0 + g2 - 2.0 * g * cosangle, 1e-4), 1.5);
|
||||
}
|
||||
|
||||
// share of the sunlight the shadow map holds back at a point of the view space, one comparison (as fog_inject.comp)
|
||||
float particle_sun_shadow(vec3 position)
|
||||
{
|
||||
#if SHADOWMAP_ENABLED
|
||||
float span_distance = dot(position, position);
|
||||
uint cascade;
|
||||
for (cascade = 0U; cascade < MAX_CASCADES; cascade++)
|
||||
if (span_distance <= cascade_end[cascade])
|
||||
break;
|
||||
for (; cascade < MAX_CASCADES; cascade++)
|
||||
{
|
||||
vec4 lightpos = lightview[cascade] * vec4(position, 1.0);
|
||||
vec3 coords = lightpos.xyz / lightpos.w;
|
||||
if (all(greaterThanEqual(coords.xy, vec2(0.0))) && all(lessThanEqual(coords.xy, vec2(1.0))))
|
||||
{
|
||||
if (coords.z < 0.0)
|
||||
return 0.0;
|
||||
return texture(shadowmap, vec4(coords.xy, float(cascade), coords.z + 0.0001 * float(cascade + 1U)));
|
||||
}
|
||||
// outside the map: lit, unless the cascades are fitted to the depth of the view (gfx.shadow.sdsm), whose maps cover only
|
||||
// the area of the surfaces seen; then the first further cascade whose map holds the point
|
||||
if (shadow_fit.x <= 0.0)
|
||||
return 0.0;
|
||||
}
|
||||
return 0.0;
|
||||
#else
|
||||
return 0.0;
|
||||
#endif
|
||||
}
|
||||
|
||||
// light of a lamp or of the beam of headlights reaching a point of the view space (as fog_inject.comp; the beams with their
|
||||
// shadow maps, the lamps without)
|
||||
vec3 particle_lamp(light_s light, vec3 position)
|
||||
{
|
||||
if (light.type == LIGHT_HEADLIGHTS)
|
||||
{
|
||||
vec4 projected = light.headlight_projection * vec4(position, 1.0);
|
||||
vec3 coords = projected.xyz / projected.w;
|
||||
if (coords.z < 0.0 || coords.z > 1.0 || any(lessThan(coords.xy, vec2(0.0))) || any(greaterThan(coords.xy, vec2(1.0))))
|
||||
return vec3(0.0);
|
||||
float span_distance = length(light.pos - position);
|
||||
float atten = 1.0 / (1.0 + light.linear * span_distance + light.quadratic * span_distance * span_distance);
|
||||
atten *= mix(1.0, 0.0, clamp((coords.z - 0.998) * 500.0, 0.0, 1.0));
|
||||
vec3 beam = textureLod(headlightmap, coords.xy, 1.0).rgb * light.headlight_weights.rgb;
|
||||
float intensity = max(max(beam.r, beam.g), beam.b);
|
||||
if ((light.headlight_weights.w > 0.5) && (intensity > 0.0))
|
||||
{
|
||||
uint map = uint(light.headlight_weights.w - 0.5);
|
||||
vec4 shadowpos = headlight_shadow[map] * vec4(position, 1.0);
|
||||
if (shadowpos.w > 0.0)
|
||||
{
|
||||
vec2 shadowcoords = shadowpos.xy / shadowpos.w;
|
||||
if (all(greaterThanEqual(shadowcoords, vec2(0.0))) && all(lessThanEqual(shadowcoords, vec2(1.0))))
|
||||
{
|
||||
float texel = headlight_shadow_params[map].x * shadowpos.w;
|
||||
float reference = shadowpos.z / max(shadowpos.w - texel * 1.5, 1e-3);
|
||||
intensity *= 1.0 - texture(headlight_shadowmap, vec4(shadowcoords, float(map), reference));
|
||||
}
|
||||
}
|
||||
}
|
||||
return light.color * (atten * intensity * light.intensity);
|
||||
}
|
||||
vec3 tolight = light.pos - position;
|
||||
float distance2 = dot(tolight, tolight);
|
||||
float edge = clamp(1.0 - distance2 * distance2 * (light.quadratic * light.quadratic), 0.0, 1.0);
|
||||
float atten = edge * edge / max(distance2, 0.01);
|
||||
if (light.type == LIGHT_SPOT)
|
||||
atten *= smoothstep(light.out_cutoff, light.in_cutoff, dot(-tolight * inversesqrt(max(distance2, 1e-8)), normalize(light.dir)));
|
||||
return light.color * (atten * light.intensity);
|
||||
}
|
||||
|
||||
// the light cluster of a point of the view space in front of the camera, as fragment_cluster() of light_common.glsl finds it for
|
||||
// the fragment drawn there
|
||||
uint particle_cluster(vec3 position)
|
||||
{
|
||||
vec4 clip = cluster_projection * vec4(position, 1.0);
|
||||
vec2 screen = clip.xy / clip.w * 0.5 + 0.5;
|
||||
ivec2 tile = clamp(ivec2(screen * vec2(CLUSTER_GRID_X, CLUSTER_GRID_Y)), ivec2(0), ivec2(CLUSTER_GRID_X - 1, CLUSTER_GRID_Y - 1));
|
||||
int slice = clamp(int(floor(cluster_slice_bias + log(max(clip.w, 1e-4)) * cluster_slice_scale)), 0, CLUSTER_GRID_Z - 1);
|
||||
return uint(tile.x + CLUSTER_GRID_X * (tile.y + CLUSTER_GRID_Y * slice));
|
||||
}
|
||||
|
||||
// Position: of the particle in the view space; World: its offset from the camera along the world axes
|
||||
vec3 particle_light(vec3 position, vec3 world, float forward)
|
||||
{
|
||||
vec3 tocamera = -normalize(position);
|
||||
vec3 light = ambient * PARTICLE_AMBIENT_SCALE;
|
||||
if (lights_count > 0U)
|
||||
{
|
||||
float sunlit = cloud_shadow(world) * (1.0 - particle_sun_shadow(position));
|
||||
if (sunlit > 0.0)
|
||||
light += lights[0].color * (0.5 * PARTICLE_SUN_SCALE * lights[0].intensity * sunlit * particle_phase(dot(normalize(lights[0].dir), tocamera), forward));
|
||||
}
|
||||
vec3 lamps = vec3(0.0);
|
||||
if (lights_clustered != 0U)
|
||||
{
|
||||
if ((cluster_valid != 0U) && (position.z < 0.0))
|
||||
{
|
||||
cluster_range_s range = cluster_ranges[particle_cluster(position)];
|
||||
for (uint i = 0U; i < range.count; i++)
|
||||
{
|
||||
light_s lamp = lights[cluster_light_indices[range.offset + i]];
|
||||
vec3 fromlamp = position - lamp.pos;
|
||||
lamps += particle_lamp(lamp, position) * particle_phase(dot(fromlamp, tocamera) * inversesqrt(max(dot(fromlamp, fromlamp), 1e-8)), forward);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (uint i = 1U; i < lights_count; i++)
|
||||
{
|
||||
vec3 fromlamp = position - lights[i].pos;
|
||||
lamps += particle_lamp(lights[i], position) * particle_phase(dot(fromlamp, tocamera) * inversesqrt(max(dot(fromlamp, fromlamp), 1e-8)), forward);
|
||||
}
|
||||
}
|
||||
return light + lamps * 0.5;
|
||||
}
|
||||
|
||||
#endif
|
||||
24
shaders/particle_soft.glsl
Normal file
24
shaders/particle_soft.glsl
Normal file
@@ -0,0 +1,24 @@
|
||||
// soft particles (opengl46_gpu_particles): a particle fades where it nears what is behind it, instead of being cut by it.
|
||||
// the resolved depth of the scene drawn so far (gl::SCENE_DEPTH_TEX), reversed with the far plane at infinity: the distance
|
||||
// along the axis of the view is the near plane over the depth, and the sky, which writes no depth, stays at 0
|
||||
|
||||
#ifndef PARTICLE_SOFT_GLSL
|
||||
#define PARTICLE_SOFT_GLSL
|
||||
|
||||
layout(binding = SCENE_DEPTH_TEX) uniform sampler2D particle_scene_depth;
|
||||
|
||||
// share of the particle left at the fragment, 0-1: Range (metres) is how far in front of the scene it fades out over, 0 for none.
|
||||
// Fragdistance: of the fragment along the axis of the view
|
||||
float particle_soft_fade(float range, float fragdistance)
|
||||
{
|
||||
if (range <= 0.0)
|
||||
return 1.0;
|
||||
float depth = texelFetch(particle_scene_depth, ivec2(gl_FragCoord.xy), 0).r;
|
||||
if (depth <= 0.0)
|
||||
return 1.0;
|
||||
float scene = projection[3][2] / depth;
|
||||
float share = clamp((scene - fragdistance) / range, 0.0, 1.0);
|
||||
return share * share * (3.0 - 2.0 * share);
|
||||
}
|
||||
|
||||
#endif
|
||||
75
shaders/particles_precipitation.comp
Normal file
75
shaders/particles_precipitation.comp
Normal file
@@ -0,0 +1,75 @@
|
||||
// rain and snow of the gpu (opengl46_gpu_particles), in every colour pass which draws them: a thread per drop or flake. the
|
||||
// particles fill three boxes around the camera, a small dense one and two larger ones, each repeated along the world axes:
|
||||
// a particle keeps its place in the box, moved by how far the wind took them all and how far it fell, so nothing is kept
|
||||
// from frame to frame. what the camera sees is lit (particle_light.glsl) and drawn as a streak from where it is to where
|
||||
// it was a moment ago, seen from the moving camera (particles_precipitation.vert)
|
||||
|
||||
#include <common>
|
||||
#include <weather_common.glsl>
|
||||
#include <particle_light.glsl>
|
||||
#include <particle_common.glsl>
|
||||
|
||||
layout(local_size_x = PARTICLE_GROUP_SIZE) in;
|
||||
|
||||
// x, y: first particles of the second and of the third box, z: particles, w: 1 for snow, 0 for rain
|
||||
layout(location = 0) uniform uvec4 counts;
|
||||
// xyz: how far the wind took the particles less where the camera is, in each box (metres, 0 to the size of the box); w: how
|
||||
// far they fell in it, 0 to 16 sizes of the box
|
||||
layout(location = 1) uniform vec4 offsets[3];
|
||||
// xyz: size of each box, metres; w unused
|
||||
layout(location = 4) uniform vec4 sizes;
|
||||
// xyz: velocity of the wind less the one of the camera, m/s; w: speed of the fall
|
||||
layout(location = 5) uniform vec4 velocity;
|
||||
// x: seconds (for the flutter of the flakes), y: how long a streak lasts, seconds, z: opacity, w: places for the draws
|
||||
layout(location = 6) uniform vec4 timing;
|
||||
// the world axes around the camera to the view space of the pass
|
||||
layout(location = 7) uniform mat4 view;
|
||||
// y: how much a particle scatters the light onwards, z: brightness
|
||||
layout(location = 11) uniform vec4 options;
|
||||
layout(location = 12) uniform vec4 planes[5];
|
||||
|
||||
void main()
|
||||
{
|
||||
uint index = gl_GlobalInvocationID.x;
|
||||
if (index >= counts.z)
|
||||
return;
|
||||
uint box = (index < counts.x) ? 0U : (index < counts.y) ? 1U : 2U;
|
||||
float size = sizes[box];
|
||||
bool snow = counts.w != 0U;
|
||||
|
||||
uint state = particle_hash(index * 3U + 0x9E3779B9U);
|
||||
vec3 home = vec3(particle_random(state), particle_random(state), particle_random(state)) * size;
|
||||
// the speed of the fall is (16 + k) / 16 of the common one, so a whole number of sizes of the box (16 of them) of the common
|
||||
// fall is a whole number of them for every particle, and the fall can be kept within 16 sizes
|
||||
float pace = (16.0 + floor(particle_random(state) * 7.0) - 3.0) / 16.0;
|
||||
float width = snow ? mix(0.004, 0.012, particle_random(state)) : mix(0.0012, 0.0025, particle_random(state));
|
||||
vec3 move = vec3(velocity.xyz) + vec3(0.0, -velocity.w * pace, 0.0);
|
||||
vec3 place = home + offsets[box].xyz - vec3(0.0, offsets[box].w * pace, 0.0);
|
||||
if (snow)
|
||||
{
|
||||
// the flakes flutter about their way
|
||||
float amplitude = mix(0.1, 0.35, particle_random(state));
|
||||
vec2 frequency = mix(vec2(1.5), vec2(5.0), vec2(particle_random(state), particle_random(state)));
|
||||
vec2 phase = vec2(particle_random(state), particle_random(state)) * 6.2831853;
|
||||
vec2 angle = frequency * timing.x + phase;
|
||||
place.xz += amplitude * sin(angle);
|
||||
move.xz += amplitude * frequency * cos(angle);
|
||||
}
|
||||
// within the box around the camera
|
||||
vec3 world = mod(place, size) - 0.5 * size;
|
||||
float opacity = timing.z;
|
||||
// fading at the walls of the box, where the particles wrap around, and right at the camera
|
||||
vec3 walls = abs(world) / (0.5 * size);
|
||||
opacity *= 1.0 - smoothstep(0.7, 1.0, max(max(walls.x, walls.y), walls.z));
|
||||
opacity *= smoothstep(0.15, 0.6, length(world));
|
||||
if (opacity <= 0.002)
|
||||
return;
|
||||
vec3 tail = -move * timing.y;
|
||||
float streak = length(tail);
|
||||
if (streak > 3.0)
|
||||
tail *= 3.0 / streak;
|
||||
if (false == particle_visible(planes, world + 0.5 * tail, 0.5 * length(tail) + width))
|
||||
return;
|
||||
vec3 color = particle_light((view * vec4(world, 1.0)).xyz, world, options.y);
|
||||
particle_emit(world, width, tail, color * options.z, opacity, uint(timing.w));
|
||||
}
|
||||
46
shaders/particles_precipitation.frag
Normal file
46
shaders/particles_precipitation.frag
Normal file
@@ -0,0 +1,46 @@
|
||||
// rain and snow of the gpu (opengl46_gpu_particles, particles_precipitation.vert): a streak rounded at its ends, fainter towards
|
||||
// its tail, in the colour the compute pass lit it with, faded in front of the scene, fogged
|
||||
|
||||
in vec4 f_pos;
|
||||
in vec4 f_color;
|
||||
in vec4 f_streak;
|
||||
in float f_soft;
|
||||
|
||||
in vec4 f_clip_pos;
|
||||
in vec4 f_clip_future_pos;
|
||||
|
||||
#include <common>
|
||||
#include <apply_fog.glsl>
|
||||
#include <tonemapping.glsl>
|
||||
#include <particle_soft.glsl>
|
||||
|
||||
layout(location = 0) out vec4 out_color;
|
||||
#if MOTIONBLUR_ENABLED
|
||||
layout(location = 1) out vec4 out_motion;
|
||||
#endif
|
||||
|
||||
void main()
|
||||
{
|
||||
// distance from the axis of the streak, in its radii
|
||||
float along = clamp(f_streak.y, 0.0, f_streak.w);
|
||||
float offaxis = length(vec2(f_streak.x, f_streak.y - along)) / max(f_streak.z, 1e-6);
|
||||
float alpha = f_color.a * (1.0 - smoothstep(0.35, 1.0, offaxis));
|
||||
alpha *= mix(1.0, 0.4, along / max(f_streak.w, 1e-6));
|
||||
alpha *= particle_soft_fade(f_soft, -f_pos.z);
|
||||
if (alpha <= 0.0)
|
||||
discard;
|
||||
vec4 color = vec4(apply_fog(f_color.rgb), alpha);
|
||||
#if POSTFX_ENABLED
|
||||
out_color = color;
|
||||
#else
|
||||
out_color = tonemap(color);
|
||||
#endif
|
||||
#if MOTIONBLUR_ENABLED
|
||||
{
|
||||
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;
|
||||
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;
|
||||
|
||||
out_motion = vec4(a - b, 0.0f, alpha * alpha_mult);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
58
shaders/particles_precipitation.vert
Normal file
58
shaders/particles_precipitation.vert
Normal file
@@ -0,0 +1,58 @@
|
||||
// rain and snow of the gpu (opengl46_gpu_particles): a streak per drop or flake the compute pass added to the draw, from where
|
||||
// it is to where it was a moment ago as the moving camera saw it, turned to face the camera, rounded at its ends. a particle
|
||||
// thinner than a pixel and a half is drawn that wide and as much fainter, so the distant ones don't flicker
|
||||
|
||||
layout(location = 0) in vec4 v_place; // xyz: the head, metres from the camera (world axes), w: width
|
||||
layout(location = 1) in vec4 v_shape; // xyz: from the head to the tail
|
||||
layout(location = 2) in vec4 v_color; // rgb: lit colour, a: opacity
|
||||
|
||||
out vec4 f_pos;
|
||||
out vec4 f_color;
|
||||
// x: across the streak, y: along it from the head, metres; z: its radius, w: its length
|
||||
out vec4 f_streak;
|
||||
out float f_soft;
|
||||
|
||||
out vec4 f_clip_pos;
|
||||
out vec4 f_clip_future_pos;
|
||||
|
||||
#include <common>
|
||||
|
||||
// x: metres it fades out over in front of the scene, 0: none
|
||||
layout(location = 0) uniform vec4 particle_soft;
|
||||
// x: width of a pixel one metre in front of the camera, metres
|
||||
layout(location = 1) uniform vec4 particle_pixel;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec3 head = (modelview * vec4(v_place.xyz, 1.0)).xyz;
|
||||
vec3 axis = mat3(modelview) * v_shape.xyz;
|
||||
float span = length(axis);
|
||||
vec3 direction = (span > 1e-5) ? axis / span : vec3(0.0, -1.0, 0.0);
|
||||
vec3 middle = head + 0.5 * axis;
|
||||
|
||||
float width = v_place.w;
|
||||
float alpha = v_color.a;
|
||||
float least = 1.5 * particle_pixel.x * max(-middle.z, 1e-3);
|
||||
if (width < least)
|
||||
{
|
||||
alpha *= width / least;
|
||||
width = least;
|
||||
}
|
||||
float radius = 0.5 * width;
|
||||
vec3 side = cross(direction, normalize(-middle));
|
||||
float sidelength = length(side);
|
||||
side = (sidelength > 1e-4) ? side / sidelength : vec3(1.0, 0.0, 0.0);
|
||||
|
||||
// a triangle strip of four corners: across, along
|
||||
vec2 corner = vec2(float(gl_VertexID & 1), float(gl_VertexID >> 1));
|
||||
float across = (corner.x * 2.0 - 1.0) * radius;
|
||||
float along = mix(-radius, span + radius, corner.y);
|
||||
f_pos = vec4(head + direction * along + side * across, 1.0);
|
||||
f_clip_pos = projection * f_pos;
|
||||
f_clip_future_pos = (projection * future) * f_pos;
|
||||
|
||||
gl_Position = f_clip_pos;
|
||||
f_streak = vec4(across, along, radius, span);
|
||||
f_color = vec4(v_color.rgb, alpha);
|
||||
f_soft = particle_soft.x;
|
||||
}
|
||||
42
shaders/particles_smoke.frag
Normal file
42
shaders/particles_smoke.frag
Normal file
@@ -0,0 +1,42 @@
|
||||
// the smoke of the gpu (opengl46_gpu_particles, particles_smoke.vert): the texture of the smoke in the colour the compute pass
|
||||
// lit the particle with, faded in front of the scene, fogged
|
||||
|
||||
in vec4 f_color;
|
||||
in vec2 f_coord;
|
||||
in vec4 f_pos;
|
||||
in float f_soft;
|
||||
|
||||
in vec4 f_clip_pos;
|
||||
in vec4 f_clip_future_pos;
|
||||
|
||||
#texture (tex1, 0, sRGB_A)
|
||||
uniform sampler2D tex1;
|
||||
|
||||
#include <common>
|
||||
#include <apply_fog.glsl>
|
||||
#include <tonemapping.glsl>
|
||||
#include <particle_soft.glsl>
|
||||
|
||||
layout(location = 0) out vec4 out_color;
|
||||
#if MOTIONBLUR_ENABLED
|
||||
layout(location = 1) out vec4 out_motion;
|
||||
#endif
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 tex_color = texture(tex1, f_coord) * f_color;
|
||||
tex_color.a *= particle_soft_fade(f_soft, -f_pos.z);
|
||||
#if POSTFX_ENABLED
|
||||
out_color = vec4(apply_fog(tex_color.rgb), tex_color.a);
|
||||
#else
|
||||
out_color = tonemap(vec4(apply_fog(tex_color.rgb), tex_color.a));
|
||||
#endif
|
||||
#if MOTIONBLUR_ENABLED
|
||||
{
|
||||
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;
|
||||
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;
|
||||
|
||||
out_motion = vec4(a - b, 0.0f, tex_color.a * alpha_mult);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
37
shaders/particles_smoke.vert
Normal file
37
shaders/particles_smoke.vert
Normal file
@@ -0,0 +1,37 @@
|
||||
// the smoke of the gpu (opengl46_gpu_particles): a billboard per particle the compute pass added to the draw, turned about the
|
||||
// axis of the view by the rotation of the particle (as opengl46_particles builds them on the cpu)
|
||||
|
||||
layout(location = 0) in vec4 v_place; // xyz: metres from the camera (world axes), w: size
|
||||
layout(location = 1) in vec4 v_shape; // x: rotation, radians
|
||||
layout(location = 2) in vec4 v_color; // rgb: lit colour, a: opacity
|
||||
|
||||
out vec4 f_pos;
|
||||
out vec4 f_color;
|
||||
out vec2 f_coord;
|
||||
// metres in front of the scene the particle fades out over, see particle_soft.glsl
|
||||
out float f_soft;
|
||||
|
||||
out vec4 f_clip_pos;
|
||||
out vec4 f_clip_future_pos;
|
||||
|
||||
#include <common>
|
||||
|
||||
// x: share of the size of the particle it fades out over in front of the scene (gfx.particles.soft), 0: none
|
||||
layout(location = 0) uniform vec4 particle_soft;
|
||||
|
||||
void main()
|
||||
{
|
||||
// a triangle strip of four corners
|
||||
vec2 corner = vec2(float(gl_VertexID & 1), float(gl_VertexID >> 1)) - 0.5;
|
||||
float c = cos(v_shape.x);
|
||||
float s = sin(v_shape.x);
|
||||
vec2 offset = vec2(c * corner.x - s * corner.y, s * corner.x + c * corner.y) * v_place.w;
|
||||
f_pos = modelview * vec4(v_place.xyz, 1.0) + vec4(offset, 0.0, 0.0);
|
||||
f_clip_pos = projection * f_pos;
|
||||
f_clip_future_pos = (projection * future) * f_pos;
|
||||
|
||||
gl_Position = f_clip_pos;
|
||||
f_coord = corner + 0.5;
|
||||
f_color = v_color;
|
||||
f_soft = particle_soft.x * 0.5 * v_place.w;
|
||||
}
|
||||
52
shaders/particles_smoke_prepare.comp
Normal file
52
shaders/particles_smoke_prepare.comp
Normal file
@@ -0,0 +1,52 @@
|
||||
// the smoke of the gpu (opengl46_gpu_particles), in every colour pass which draws it: a thread per place of the storage. the
|
||||
// living particles the view sees are lit (particle_light.glsl) and added to the draw (particles_smoke.vert)
|
||||
|
||||
#include <common>
|
||||
#include <weather_common.glsl>
|
||||
#include <particle_light.glsl>
|
||||
#include <particle_common.glsl>
|
||||
|
||||
layout(local_size_x = PARTICLE_GROUP_SIZE) in;
|
||||
|
||||
layout(std430, binding = PARTICLE_SSBO_BINDING) readonly buffer particle_ssbo
|
||||
{
|
||||
smoke_particle_s particles[];
|
||||
};
|
||||
layout(std430, binding = PARTICLE_SOURCE_SSBO_BINDING) readonly buffer particle_source_ssbo
|
||||
{
|
||||
smoke_source_s sources[];
|
||||
};
|
||||
layout(std430, binding = PARTICLE_OWNER_SSBO_BINDING) readonly buffer particle_owner_ssbo
|
||||
{
|
||||
uint owners[];
|
||||
};
|
||||
|
||||
layout(location = 0) uniform uint places;
|
||||
// xyz: the origin of the particles less the camera, metres along the world axes
|
||||
layout(location = 1) uniform vec3 origin;
|
||||
// the world axes around the camera to the view space of the pass
|
||||
layout(location = 2) uniform mat4 view;
|
||||
// x: 1 when the smoke is lit by the lights of the pass, y: how much it scatters the light onwards, z: places for the draws
|
||||
layout(location = 3) uniform vec4 options;
|
||||
// planes of the view (world axes around the camera): a point is inside when dot(xyz, point) + w >= 0
|
||||
layout(location = 4) uniform vec4 planes[5];
|
||||
|
||||
void main()
|
||||
{
|
||||
uint place = gl_GlobalInvocationID.x;
|
||||
if (place >= places)
|
||||
return;
|
||||
smoke_particle_s particle = particles[place];
|
||||
if (particle.state.x <= 0.0)
|
||||
return;
|
||||
vec3 world = particle.position.xyz + origin;
|
||||
float size = particle.velocity.w;
|
||||
// the corners of the billboard turned anyhow
|
||||
if (false == particle_visible(planes, world, size * 0.7072))
|
||||
return;
|
||||
smoke_source_s source = sources[owners[place]];
|
||||
vec3 color = source.oldcolor.rgb;
|
||||
if (options.x > 0.5)
|
||||
color = source.color.rgb * particle_light((view * vec4(world, 1.0)).xyz, world, options.y);
|
||||
particle_emit(world, size, vec3(particle.position.w, 0.0, 0.0), color, clamp(particle.state.x, 0.0, 1.0), uint(options.z));
|
||||
}
|
||||
84
shaders/particles_smoke_simulate.comp
Normal file
84
shaders/particles_smoke_simulate.comp
Normal file
@@ -0,0 +1,84 @@
|
||||
// the smoke of the gpu (opengl46_gpu_particles), once per update of the simulation: a thread per place of the storage. the
|
||||
// particles the sources had born in the update take the places after the ones of the last update (each source keeps its
|
||||
// places as a ring, the oldest give way), spread along the way the emitter went since the update before; the living ones
|
||||
// change as smoke_source::update() changes them on the cpu
|
||||
|
||||
#include <common>
|
||||
#include <particle_common.glsl>
|
||||
|
||||
layout(local_size_x = PARTICLE_GROUP_SIZE) in;
|
||||
|
||||
layout(std430, binding = PARTICLE_SSBO_BINDING) buffer particle_ssbo
|
||||
{
|
||||
smoke_particle_s particles[];
|
||||
};
|
||||
layout(std430, binding = PARTICLE_SOURCE_SSBO_BINDING) readonly buffer particle_source_ssbo
|
||||
{
|
||||
smoke_source_s sources[];
|
||||
};
|
||||
// the source of every place
|
||||
layout(std430, binding = PARTICLE_OWNER_SSBO_BINDING) readonly buffer particle_owner_ssbo
|
||||
{
|
||||
uint owners[];
|
||||
};
|
||||
|
||||
layout(location = 0) uniform uint places;
|
||||
// x: seconds of the update, yzw: added to the positions, as the origin moved
|
||||
layout(location = 1) uniform vec4 step;
|
||||
// xyz: the wind, m/s; w unused
|
||||
layout(location = 2) uniform vec4 wind;
|
||||
// x: number of the update, for the random numbers
|
||||
layout(location = 3) uniform uint seed;
|
||||
|
||||
// smoke_source::update( Particle ); false when the particle is dead
|
||||
bool advance(inout smoke_particle_s particle, smoke_source_s source, float seconds)
|
||||
{
|
||||
particle.state.x = clamp(particle.state.x + source.opacitychange.x * seconds, source.opacitychange.y, source.opacitychange.z);
|
||||
if (particle.state.x <= 0.0)
|
||||
return false;
|
||||
particle.velocity.w = clamp(particle.velocity.w + source.sizechange.x * seconds, source.sizechange.y, source.sizechange.z);
|
||||
// crude smoke dispersion
|
||||
particle.velocity.y += source.motion.x * particle.velocity.y * seconds;
|
||||
particle.velocity.y -= source.motion.y * particle.velocity.y * seconds;
|
||||
particle.velocity.y = max(source.motion.z, particle.velocity.y);
|
||||
particle.position.xyz += particle.velocity.xyz * seconds;
|
||||
particle.position.xyz += 0.1 * particle.state.y * wind.xyz * seconds;
|
||||
particle.state.y += seconds;
|
||||
return true;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
uint place = gl_GlobalInvocationID.x;
|
||||
if (place >= places)
|
||||
return;
|
||||
smoke_source_s source = sources[owners[place]];
|
||||
smoke_particle_s particle = particles[place];
|
||||
particle.position.xyz += step.yzw;
|
||||
|
||||
uint size = source.pool.y;
|
||||
uint order = (place - source.pool.x + size - source.pool.z) % size;
|
||||
if (order < source.pool.w)
|
||||
{
|
||||
// born in the update: smoke_source::particle_emitter::initialize()
|
||||
uint state = particle_hash(place ^ particle_hash(seed));
|
||||
float polar = mix(source.launch.x, source.launch.y, particle_random(state));
|
||||
float azimuth = radians(mix(-180.0, 180.0, particle_random(state)));
|
||||
vec3 direction = vec3(-sin(polar) * sin(azimuth), cos(polar), sin(polar) * cos(azimuth));
|
||||
particle.velocity.xyz = direction * mix(source.launch.z, source.launch.w, particle_random(state));
|
||||
particle.position.w = radians(mix(0.0, 360.0, particle_random(state)));
|
||||
particle.velocity.w = mix(source.initial.x, source.initial.y, particle_random(state));
|
||||
particle.state = vec4(mix(source.initial.z, source.initial.w, particle_random(state)), 0.0, 0.0, 0.0);
|
||||
// the births spread over the update: where the emitter was then, and the time since
|
||||
float share = (float(order) + 0.5) / float(source.pool.w);
|
||||
particle.position.xyz = mix(source.previous.xyz, source.location.xyz, share);
|
||||
if (false == advance(particle, source, step.x * (1.0 - share)))
|
||||
particle.state.x = 0.0;
|
||||
}
|
||||
else if (particle.state.x > 0.0)
|
||||
{
|
||||
if (false == advance(particle, source, step.x))
|
||||
particle.state.x = 0.0;
|
||||
}
|
||||
particles[place] = particle;
|
||||
}
|
||||
@@ -1556,6 +1556,33 @@ global_settings::ConfigParse_gfx( cParser &Parser, std::string_view const Token
|
||||
Parser >> gfx_grass_density;
|
||||
gfx_grass_density = std::clamp(gfx_grass_density, 0.f, 4.f);
|
||||
}
|
||||
else if (Token == "gfx.smoke.gpu")
|
||||
{
|
||||
Parser.getTokens(1);
|
||||
Parser >> gfx_smoke_gpu;
|
||||
}
|
||||
else if (Token == "gfx.smoke.lighting")
|
||||
{
|
||||
Parser.getTokens(1);
|
||||
Parser >> gfx_smoke_lighting;
|
||||
}
|
||||
else if (Token == "gfx.particles.soft")
|
||||
{
|
||||
Parser.getTokens(1);
|
||||
Parser >> gfx_particles_soft;
|
||||
gfx_particles_soft = std::clamp(gfx_particles_soft, 0.f, 4.f);
|
||||
}
|
||||
else if (Token == "gfx.precipitation.gpu")
|
||||
{
|
||||
Parser.getTokens(1);
|
||||
Parser >> gfx_precipitation_gpu;
|
||||
}
|
||||
else if (Token == "gfx.precipitation.particles")
|
||||
{
|
||||
Parser.getTokens(1);
|
||||
Parser >> gfx_precipitation_particles;
|
||||
gfx_precipitation_particles = std::clamp(gfx_precipitation_particles, 0, 1 << 20);
|
||||
}
|
||||
else if (Token == "gfx.bufferstreaming")
|
||||
{
|
||||
Parser.getTokens(1);
|
||||
@@ -2074,6 +2101,11 @@ global_settings::export_as_text( std::ostream &Output ) const {
|
||||
export_as_text( Output, "gfx.grass.distance", gfx_grass_distance );
|
||||
export_as_text( Output, "gfx.grass.fadedistance", gfx_grass_fadedistance );
|
||||
export_as_text( Output, "gfx.grass.density", gfx_grass_density );
|
||||
export_as_text( Output, "gfx.smoke.gpu", gfx_smoke_gpu );
|
||||
export_as_text( Output, "gfx.smoke.lighting", gfx_smoke_lighting );
|
||||
export_as_text( Output, "gfx.particles.soft", gfx_particles_soft );
|
||||
export_as_text( Output, "gfx.precipitation.gpu", gfx_precipitation_gpu );
|
||||
export_as_text( Output, "gfx.precipitation.particles", gfx_precipitation_particles );
|
||||
export_as_text( Output, "gfx.bufferstreaming", gfx_bufferstreaming );
|
||||
export_as_text( Output, "gfx.postfx.motionblur.enabled", gfx_postfx_motionblur_enabled );
|
||||
export_as_text( Output, "gfx.postfx.motionblur.shutter", gfx_postfx_motionblur_shutter );
|
||||
|
||||
@@ -332,6 +332,17 @@ struct global_settings {
|
||||
// (opengl 4.6) the grass thins out from gfx_grass_distance to here, where it ends
|
||||
float gfx_grass_fadedistance = 70.f;
|
||||
float gfx_grass_density = 1.f;
|
||||
// (opengl 4.6) the smoke is simulated and drawn on the gpu (opengl46_gpu_particles), lit by the sun, the sky and the lamps
|
||||
// when gfx_smoke_lighting; false: simulated on the cpu and drawn as before
|
||||
bool gfx_smoke_gpu = true;
|
||||
bool gfx_smoke_lighting = true;
|
||||
// (opengl 4.6) the particles of the gpu fade where they near what is behind them, over this share of their size (smoke) or of
|
||||
// a few decimetres (rain, snow), instead of being cut by it; 0: cut, as before
|
||||
float gfx_particles_soft = 1.f;
|
||||
// (opengl 4.6) rain and snow as particles around the camera, lit like the smoke (opengl46_gpu_particles), at most this many of
|
||||
// them in the heaviest rain or snow; false: the textured cylinder, as before
|
||||
bool gfx_precipitation_gpu = true;
|
||||
int gfx_precipitation_particles = 65536;
|
||||
// (opengl 4.6) the model block of every draw and the matrices of instances are written to persistently mapped buffers,
|
||||
// one for each of the last frames, instead of with glBufferSubData. read at the start
|
||||
bool gfx_bufferstreaming = true;
|
||||
|
||||
Reference in New Issue
Block a user