mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-22 02:56:24 +02:00
Simulate and light the smoke on the GPU and make rain and snow of particles
This commit is contained in:
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 };
|
||||
};
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user