Features completed cpu particle

This commit is contained in:
Benau 2017-10-14 00:19:59 +08:00
parent 63ecbefbdd
commit 0066722dab
21 changed files with 1312 additions and 1136 deletions

View File

@ -23,7 +23,7 @@
<size min="0.10"
max="0.30" />
<color min="40 40 255"
<color min="255 255 255"
max="255 255 255" />
<!-- How much time in milliseconds before the particle is fully faded out -->

View File

@ -0,0 +1,15 @@
uniform sampler2D tex;
in vec2 tc;
in vec4 pc;
out vec4 FragColor;
void main(void)
{
vec4 color = texture(tex, tc);
if (color.a < 0.5)
{
discard;
}
FragColor = color * pc;
}

View File

@ -0,0 +1,89 @@
uniform vec3 color_from;
uniform vec3 color_to;
uniform int flips;
#ifdef Explicit_Attrib_Location_Usable
layout(location = 0) in vec3 Position;
layout(location = 1) in float lifetime;
layout(location = 2) in float size;
layout(location = 3) in vec2 Texcoord;
layout(location = 4) in vec2 quadcorner;
layout(location = 5) in vec3 rotationvec;
layout(location = 6) in float anglespeed;
#else
in vec3 Position;
in float lifetime;
in float size;
in vec2 Texcoord;
in vec2 quadcorner;
in vec3 rotationvec;
float anglespeed;
#endif
out vec2 tc;
out vec4 pc;
void main(void)
{
if (size == 0.0)
{
return;
}
tc = Texcoord;
pc = vec4(vec3(color_from + (color_to - color_from) * lifetime), 1.0);
#if !defined(sRGB_Framebuffer_Usable) && !defined(Advanced_Lighting_Enabled)
pc.rgb = pow(pc.rgb, vec3(1. / 2.2));
#endif
vec4 viewpos = vec4(0.);
if (flips == 1)
{
// from http://jeux.developpez.com/faq/math
float angle = lifetime * anglespeed;
float sin_a = sin(angle / 2.);
float cos_a = cos(angle / 2.);
vec4 quaternion = normalize(vec4(rotationvec * sin_a, cos_a));
float xx = quaternion.x * quaternion.x;
float xy = quaternion.x * quaternion.y;
float xz = quaternion.x * quaternion.z;
float xw = quaternion.x * quaternion.w;
float yy = quaternion.y * quaternion.y;
float yz = quaternion.y * quaternion.z;
float yw = quaternion.y * quaternion.w;
float zz = quaternion.z * quaternion.z;
float zw = quaternion.z * quaternion.w;
vec4 col1 = vec4(
1. - 2. * ( yy + zz ),
2. * ( xy + zw ),
2. * ( xz - yw ),
0.);
vec4 col2 = vec4(
2. * ( xy - zw ),
1. - 2. * ( xx + zz ),
2. * ( yz + xw ),
0.);
vec4 col3 = vec4(
2. * ( xz + yw ),
2. * ( yz - xw ),
1. - 2. * ( xx + yy ),
0.);
vec4 col4 = vec4(0., 0., 0., 1.);
mat4 rotationMatrix = mat4(col1, col2, col3, col4);
vec3 newquadcorner = size * vec3(quadcorner, 0.);
newquadcorner = (rotationMatrix * vec4(newquadcorner, 0.)).xyz;
viewpos = ViewMatrix * vec4(Position + newquadcorner, 1.0);
}
else
{
viewpos = ViewMatrix * vec4(Position, 1.0);
viewpos += size * vec4(quadcorner, 0., 0.);
}
gl_Position = ProjectionMatrix * viewpos;
}

View File

@ -1,72 +0,0 @@
#ifdef Explicit_Attrib_Location_Usable
layout(location=0) in vec3 Position;
layout(location = 1) in float lifetime;
layout(location = 2) in float size;
layout(location = 3) in vec2 Texcoord;
layout(location = 4) in vec2 quadcorner;
layout(location = 5) in vec3 rotationvec;
layout(location = 6) in float anglespeed;
#else
in vec3 Position;
in float lifetime;
in float size;
in vec2 Texcoord;
in vec2 quadcorner;
in vec3 rotationvec;
float anglespeed;
#endif
out float lf;
out vec2 tc;
out vec4 pc;
void main(void)
{
tc = Texcoord;
lf = lifetime;
vec3 newposition = Position;
// from http://jeux.developpez.com/faq/math
float angle = lf * anglespeed;
float sin_a = sin(angle / 2.);
float cos_a = cos(angle / 2.);
vec4 quaternion = normalize(vec4(rotationvec * sin_a, cos_a));
float xx = quaternion.x * quaternion.x;
float xy = quaternion.x * quaternion.y;
float xz = quaternion.x * quaternion.z;
float xw = quaternion.x * quaternion.w;
float yy = quaternion.y * quaternion.y;
float yz = quaternion.y * quaternion.z;
float yw = quaternion.y * quaternion.w;
float zz = quaternion.z * quaternion.z;
float zw = quaternion.z * quaternion.w;
vec4 col1 = vec4(
1. - 2. * ( yy + zz ),
2. * ( xy + zw ),
2. * ( xz - yw ),
0.);
vec4 col2 = vec4(
2. * ( xy - zw ),
1. - 2. * ( xx + zz ),
2. * ( yz + xw ),
0.);
vec4 col3 = vec4(
2. * ( xz + yw ),
2. * ( yz - xw ),
1. - 2. * ( xx + yy ),
0.);
vec4 col4 = vec4(0., 0., 0., 1.);
mat4 rotationMatrix = mat4(col1, col2, col3, col4);
vec3 newquadcorner = size * vec3(quadcorner, 0.);
newquadcorner = (rotationMatrix * vec4(newquadcorner, 0.)).xyz;
vec4 viewpos = ViewMatrix * vec4(newposition + newquadcorner, 1.0);
gl_Position = ProjectionMatrix * viewpos;
pc = vec4(1.);
}

View File

@ -1,37 +0,0 @@
uniform vec3 color_from;
uniform vec3 color_to;
#ifdef Explicit_Attrib_Location_Usable
layout(location=0) in vec3 Position;
layout(location = 1) in float lifetime;
layout(location = 2) in float size;
layout(location=3) in vec2 Texcoord;
layout(location = 4) in vec2 quadcorner;
#else
in vec3 Position;
in float lifetime;
in float size;
in vec2 Texcoord;
in vec2 quadcorner;
#endif
out float lf;
out vec2 tc;
out vec4 pc;
void main(void)
{
tc = Texcoord;
lf = lifetime;
pc = vec4(vec3(color_from + (color_to - color_from) * lf), 1.0) * smoothstep(1., 0.8, lf);
#if !defined(sRGB_Framebuffer_Usable) && !defined(Advanced_Lighting_Enabled)
pc.rgb = pow(pc.rgb, vec3(1. / 2.2));
#endif
vec3 newposition = Position;
vec4 viewpos = ViewMatrix * vec4(newposition, 1.0);
viewpos += size * vec4(quadcorner, 0., 0.);
gl_Position = ProjectionMatrix * viewpos;
}

View File

@ -1,71 +0,0 @@
uniform int dt;
uniform mat4 sourcematrix;
uniform int level;
uniform float size_increase_factor;
uniform float track_x;
uniform float track_z;
uniform float track_x_len;
uniform float track_z_len;
#ifndef GL_ES
uniform samplerBuffer heightmap;
#else
uniform sampler2D heightmap;
#endif
#ifdef Explicit_Attrib_Location_Usable
layout (location = 4) in vec3 particle_position_initial;
layout (location = 5) in float lifetime_initial;
layout (location = 6) in vec3 particle_velocity_initial;
layout (location = 7) in float size_initial;
layout (location = 0) in vec3 particle_position;
layout (location = 1) in float lifetime;
layout (location = 2) in vec3 particle_velocity;
layout (location = 3) in float size;
#else
in vec3 particle_position_initial;
in float lifetime_initial;
in vec3 particle_velocity_initial;
in float size_initial;
in vec3 particle_position;
in float lifetime;
in vec3 particle_velocity;
in float size;
#endif
out vec3 new_particle_position;
out float new_lifetime;
out vec3 new_particle_velocity;
out float new_size;
void main(void)
{
bool reset = false;
float i_as_float = clamp(256. * (particle_position.x - track_x) / track_x_len, 0., 255.);
float j_as_float = clamp(256. * (particle_position.z - track_z) / track_z_len, 0., 255.);
int i = int(i_as_float);
int j = int(j_as_float);
#ifndef GL_ES
float h = particle_position.y - texelFetch(heightmap, i * 256 + j).r;
#else
float h = particle_position.y - texelFetch(heightmap, ivec2(j, i), 0).r;
#endif
reset = h < 0.;
vec4 initialposition = sourcematrix * vec4(particle_position_initial, 1.0);
vec4 adjusted_initial_velocity = sourcematrix * vec4(particle_position_initial + particle_velocity_initial, 1.0) - initialposition;
float adjusted_lifetime = lifetime + (float(dt)/lifetime_initial);
reset = reset || (adjusted_lifetime > 1.) && (gl_VertexID <= level);
reset = reset || (lifetime < 0.);
new_particle_position = !reset ? particle_position + particle_velocity.xyz * float(dt) : initialposition.xyz;
new_lifetime = !reset ? adjusted_lifetime : 0.;
new_particle_velocity = !reset ? particle_velocity : adjusted_initial_velocity.xyz;
new_size = !reset ? mix(size_initial, size_initial * size_increase_factor, adjusted_lifetime) : size_initial;
gl_Position = vec4(0.);
}

View File

@ -1,94 +0,0 @@
uniform int dt;
uniform mat4 previous_frame_sourcematrix;
uniform mat4 sourcematrix;
uniform int level;
uniform float size_increase_factor;
#ifdef Explicit_Attrib_Location_Usable
layout (location = 4) in vec3 particle_position_initial;
layout (location = 5) in float lifetime_initial;
layout (location = 6) in vec3 particle_velocity_initial;
layout (location = 7) in float size_initial;
layout (location = 0) in vec3 particle_position;
layout (location = 1) in float lifetime;
layout (location = 2) in vec3 particle_velocity;
layout (location = 3) in float size;
#ifdef Needs_Vertex_Id_Workaround
layout (location = 8) in int vertex_id;
#endif
#else
in vec3 particle_position_initial;
in float lifetime_initial;
in vec3 particle_velocity_initial;
in float size_initial;
in vec3 particle_position;
in float lifetime;
in vec3 particle_velocity;
in float size;
#ifdef Needs_Vertex_Id_Workaround
in int vertex_id;
#endif
#endif
out vec3 new_particle_position;
out float new_lifetime;
out vec3 new_particle_velocity;
out float new_size;
void main(void)
{
float updated_lifetime = lifetime + (float(dt)/lifetime_initial);
if (updated_lifetime > 1.)
{
#ifdef Needs_Vertex_Id_Workaround
if (vertex_id < level)
#else
if (gl_VertexID < level)
#endif
{
float dt_from_last_frame = fract(updated_lifetime) * lifetime_initial;
float coeff = dt_from_last_frame / float(dt);
vec4 previous_frame_position = previous_frame_sourcematrix * vec4(particle_position_initial, 1.0);
vec4 current_frame_position = sourcematrix * vec4(particle_position_initial, 1.0);
vec4 updated_initialposition = mix(current_frame_position,
previous_frame_position,
coeff);
vec4 updated_initial_velocity = mix(sourcematrix * vec4(particle_velocity_initial, 0.0),
previous_frame_sourcematrix * vec4(particle_velocity_initial, 0.0),
coeff);
//+ (current_frame_position - previous_frame_position) / dt;
//To be accurate, emitter speed should be added.
//But the simple formula ( (current_frame_position - previous_frame_position) / dt ) with a constant speed
//between 2 frames creates visual artifacts when the framerate is low, and a more accurate formula would need
//more complex computations.
new_particle_position = updated_initialposition.xyz + dt_from_last_frame * updated_initial_velocity.xyz;
new_particle_velocity = updated_initial_velocity.xyz;
new_lifetime = fract(updated_lifetime);
new_size = mix(size_initial, size_initial * size_increase_factor, fract(updated_lifetime));
}
else
{
new_lifetime = fract(updated_lifetime);
new_size = 0.0;
}
}
else
{
new_particle_position = particle_position + particle_velocity.xyz * float(dt);
new_particle_velocity = particle_velocity;
new_lifetime = updated_lifetime;
new_size = (size == 0.0) ? 0. : mix(size_initial, size_initial * size_increase_factor, updated_lifetime);
}
gl_Position = vec4(0.);
}

View File

@ -1,21 +1,19 @@
uniform sampler2D tex;
uniform sampler2D dtex;
uniform mat4 invproj;
in float lf;
in vec2 tc;
in vec4 pc;
out vec4 FragColor;
#stk_include "utils/getPosFromUVDepth.frag"
void main(void)
{
vec2 xy = gl_FragCoord.xy / screen;
float FragZ = gl_FragCoord.z;
vec4 FragmentPos = getPosFromUVDepth(vec3(xy, FragZ), InverseProjectionMatrix);
float EnvZ = texture(dtex, xy).x;
vec4 EnvPos = getPosFromUVDepth(vec3(xy, EnvZ), InverseProjectionMatrix);
float alpha = clamp((EnvPos.z - FragmentPos.z) * 0.3, 0., 1.);
FragColor = texture(tex, tc) * pc * alpha;
}
uniform sampler2D tex;
uniform sampler2D dtex;
in vec2 tc;
in vec4 pc;
out vec4 FragColor;
#stk_include "utils/getPosFromUVDepth.frag"
void main(void)
{
vec2 xy = gl_FragCoord.xy / screen;
float FragZ = gl_FragCoord.z;
vec4 FragmentPos = getPosFromUVDepth(vec3(xy, FragZ), InverseProjectionMatrix);
float EnvZ = texture(dtex, xy).x;
vec4 EnvPos = getPosFromUVDepth(vec3(xy, EnvZ), InverseProjectionMatrix);
float alpha = clamp((EnvPos.z - FragmentPos.z) * 0.3, 0., 1.);
FragColor = texture(tex, tc) * pc * alpha;
}

View File

@ -0,0 +1,91 @@
uniform vec3 color_from;
uniform vec3 color_to;
uniform int flips;
#ifdef Explicit_Attrib_Location_Usable
layout(location = 0) in vec3 Position;
layout(location = 1) in float lifetime;
layout(location = 2) in float size;
layout(location = 3) in vec2 Texcoord;
layout(location = 4) in vec2 quadcorner;
layout(location = 5) in vec3 rotationvec;
layout(location = 6) in float anglespeed;
#else
in vec3 Position;
in float lifetime;
in float size;
in vec2 Texcoord;
in vec2 quadcorner;
in vec3 rotationvec;
float anglespeed;
#endif
out float lf;
out vec2 tc;
out vec4 pc;
void main(void)
{
if (size == 0.0)
{
return;
}
tc = Texcoord;
lf = lifetime;
pc = vec4(vec3(color_from + (color_to - color_from) * lf), 1.0) * smoothstep(1., 0.8, lf);
#if !defined(sRGB_Framebuffer_Usable) && !defined(Advanced_Lighting_Enabled)
pc.rgb = pow(pc.rgb, vec3(1. / 2.2));
#endif
vec4 viewpos = vec4(0.);
if (flips == 1)
{
// from http://jeux.developpez.com/faq/math
float angle = lifetime * anglespeed;
float sin_a = sin(angle / 2.);
float cos_a = cos(angle / 2.);
vec4 quaternion = normalize(vec4(rotationvec * sin_a, cos_a));
float xx = quaternion.x * quaternion.x;
float xy = quaternion.x * quaternion.y;
float xz = quaternion.x * quaternion.z;
float xw = quaternion.x * quaternion.w;
float yy = quaternion.y * quaternion.y;
float yz = quaternion.y * quaternion.z;
float yw = quaternion.y * quaternion.w;
float zz = quaternion.z * quaternion.z;
float zw = quaternion.z * quaternion.w;
vec4 col1 = vec4(
1. - 2. * ( yy + zz ),
2. * ( xy + zw ),
2. * ( xz - yw ),
0.);
vec4 col2 = vec4(
2. * ( xy - zw ),
1. - 2. * ( xx + zz ),
2. * ( yz + xw ),
0.);
vec4 col3 = vec4(
2. * ( xz + yw ),
2. * ( yz - xw ),
1. - 2. * ( xx + yy ),
0.);
vec4 col4 = vec4(0., 0., 0., 1.);
mat4 rotationMatrix = mat4(col1, col2, col3, col4);
vec3 newquadcorner = size * vec3(quadcorner, 0.);
newquadcorner = (rotationMatrix * vec4(newquadcorner, 0.)).xyz;
viewpos = ViewMatrix * vec4(Position + newquadcorner, 1.0);
}
else
{
viewpos = ViewMatrix * vec4(Position, 1.0);
viewpos += size * vec4(quadcorner, 0., 0.);
}
gl_Position = ProjectionMatrix * viewpos;
}

View File

@ -0,0 +1,306 @@
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2017 SuperTuxKart-Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "graphics/cpu_particle_manager.hpp"
#include "graphics/stk_particle.hpp"
#include "graphics/irr_driver.hpp"
#include "graphics/material.hpp"
#include "graphics/material_manager.hpp"
#include "graphics/shared_gpu_objects.hpp"
#include "utils/log.hpp"
#include <algorithm>
#ifndef SERVER_ONLY
#include "graphics/texture_shader.hpp"
// ============================================================================
/** A Shader to render particles.
*/
class ParticleRenderer : public TextureShader
<ParticleRenderer, 2, video::SColorf, video::SColorf, int>
{
public:
ParticleRenderer()
{
loadProgram(PARTICLES_RENDERING,
GL_VERTEX_SHADER, "simple_particle.vert",
GL_FRAGMENT_SHADER, "simple_particle.frag");
assignUniforms("color_from", "color_to", "flips");
assignSamplerNames(0, "tex", ST_TRILINEAR_ANISOTROPIC_FILTERED,
1, "dtex", ST_NEAREST_FILTERED);
} // ParticleRenderer
}; // ParticleRenderer
// ============================================================================
/** A Shader to render alpha-test particles.
*/
class AlphaTestParticleRenderer : public TextureShader
<AlphaTestParticleRenderer, 1, video::SColorf, video::SColorf, int>
{
public:
AlphaTestParticleRenderer()
{
loadProgram(PARTICLES_RENDERING,
GL_VERTEX_SHADER, "alphatest_particle.vert",
GL_FRAGMENT_SHADER, "alphatest_particle.frag");
assignUniforms("color_from", "color_to", "flips");
assignSamplerNames(0, "tex", ST_TRILINEAR_ANISOTROPIC_FILTERED);
} // AlphaTestParticleRenderer
}; // AlphaTestParticleRenderer
// ============================================================================
CPUParticleManager::~CPUParticleManager()
{
for (auto& p : m_gl_particles)
{
glDeleteVertexArrays(1, &std::get<0>(p.second));
glDeleteBuffers(1, &std::get<1>(p.second));
}
STKParticle::destroyFlipsBuffer();
} // ~CPUParticleManager
// ----------------------------------------------------------------------------
void CPUParticleManager::addParticleNode(STKParticle* node)
{
if (node->getMaterialCount() != 1)
{
Log::error("CPUParticleManager", "More than 1 material");
return;
}
video::ITexture* t = node->getMaterial(0).getTexture(0);
assert(t != NULL);
std::string tex_name = t->getName().getPtr();
Material* m = NULL;
if (m_material_map.find(tex_name) == m_material_map.end())
{
m = material_manager->getMaterialFor(t);
m_material_map[tex_name] = m;
if (m == NULL)
{
Log::error("CPUParticleManager", "Missing material");
}
}
m = m_material_map.at(tex_name);
if (m == NULL)
{
return;
}
m_particles_queue[tex_name].push_back(node);
} // addParticleNode
// ----------------------------------------------------------------------------
void CPUParticleManager::generateAll()
{
for (auto& p : m_particles_queue)
{
if (p.second.empty())
{
continue;
}
bool flips = false;
for (auto& q : p.second)
{
if (q->getFlips() && !flips)
{
flips = q->getFlips();
}
q->generate(&m_particles_generated[p.first]);
}
if (flips)
{
STKParticle::updateFlips(m_particles_queue[p.first].size() *
m_particles_queue[p.first][0]->getMaxCount());
}
}
} // generateAll
// ----------------------------------------------------------------------------
void CPUParticleManager::uploadAll()
{
for (auto& p : m_particles_generated)
{
if (p.second.empty())
{
continue;
}
bool first_upload = false;
if (m_gl_particles.find(p.first) == m_gl_particles.end())
{
first_upload = true;
m_gl_particles[p.first] = std::make_tuple(0, 0, 100);
}
if (first_upload)
{
glGenBuffers(1, &std::get<1>(m_gl_particles[p.first]));
glBindBuffer(GL_ARRAY_BUFFER,
std::get<1>(m_gl_particles[p.first]));
glBufferData(GL_ARRAY_BUFFER, 2000, NULL, GL_DYNAMIC_DRAW);
glGenVertexArrays(1, &std::get<0>(m_gl_particles[p.first]));
glBindVertexArray(std::get<0>(m_gl_particles[p.first]));
glBindBuffer(GL_ARRAY_BUFFER,
SharedGPUObjects::getParticleQuadVBO());
glEnableVertexAttribArray(4);
glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, 16, 0);
glEnableVertexAttribArray(3);
glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, 16, (void*)8);
glBindBuffer(GL_ARRAY_BUFFER,
std::get<1>(m_gl_particles[p.first]));
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 20, 0);
glVertexAttribDivisorARB(0, 1);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 1, GL_FLOAT, GL_FALSE, 20, (void*)12);
glVertexAttribDivisorARB(1, 1);
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, 20, (void*)16);
glVertexAttribDivisorARB(2, 1);
bool flips = false;
for (unsigned i = 0; i < m_particles_queue[p.first].size(); i++)
{
if (m_particles_queue[p.first][i]->getFlips())
{
flips = true;
break;
}
}
if (flips)
{
glBindBuffer(GL_ARRAY_BUFFER, STKParticle::getFlipsBuffer());
glEnableVertexAttribArray(5);
glVertexAttribPointer(5, 3, GL_FLOAT, GL_FALSE, 16, 0);
glVertexAttribDivisorARB(5, 1);
glEnableVertexAttribArray(6);
glVertexAttribPointer(6, 1, GL_FLOAT, GL_FALSE, 16, (void*)12);
glVertexAttribDivisorARB(6, 1);
}
glBindVertexArray(0);
}
glBindBuffer(GL_ARRAY_BUFFER, std::get<1>(m_gl_particles[p.first]));
// Check "real" particle buffer size in opengl
if (std::get<2>(m_gl_particles[p.first]) <
m_particles_generated[p.first].size())
{
std::get<2>(m_gl_particles[p.first]) =
m_particles_generated[p.first].size() * 2;
glBufferData(GL_ARRAY_BUFFER,
m_particles_generated[p.first].size() * 2 * 20, NULL,
GL_DYNAMIC_DRAW);
}
void* ptr = glMapBufferRange(GL_ARRAY_BUFFER, 0,
m_particles_generated[p.first].size() * 20, GL_MAP_WRITE_BIT |
GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
memcpy(ptr, m_particles_generated[p.first].data(),
m_particles_generated[p.first].size() * 20);
glUnmapBuffer(GL_ARRAY_BUFFER);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
} // uploadAll
// ----------------------------------------------------------------------------
void CPUParticleManager::drawAll()
{
std::vector<std::pair<Material*, std::string> > particle_drawn;
for (auto& p : m_particles_generated)
{
if (!p.second.empty())
{
particle_drawn.emplace_back(m_material_map.at(p.first), p.first);
}
}
std::sort(particle_drawn.begin(), particle_drawn.end(),
[](const std::pair<Material*, std::string>& a,
const std::pair<Material*, std::string>& b)->bool
{
return a.first->getShaderType() > b.first->getShaderType();
});
Material::ShaderType st = Material::SHADERTYPE_COUNT;
for (auto& p : particle_drawn)
{
bool flips = false;
for (unsigned i = 0; i < m_particles_queue[p.second].size(); i++)
{
if (m_particles_queue[p.second][i]->getFlips())
{
flips = true;
break;
}
}
Material* cur_mat = p.first;
if (cur_mat->getShaderType() != st)
{
st = cur_mat->getShaderType();
if (cur_mat->getShaderType() == Material::SHADERTYPE_ADDITIVE)
{
ParticleRenderer::getInstance()->use();
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE);
glDisable(GL_CULL_FACE);
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ONE, GL_ONE);
}
else if (cur_mat
->getShaderType() == Material::SHADERTYPE_ALPHA_BLEND)
{
ParticleRenderer::getInstance()->use();
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE);
glDisable(GL_CULL_FACE);
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
}
else
{
AlphaTestParticleRenderer::getInstance()->use();
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glDepthMask(GL_TRUE);
glDisable(GL_CULL_FACE);
glDisable(GL_BLEND);
}
}
if (cur_mat->getShaderType() == Material::SHADERTYPE_ADDITIVE ||
cur_mat->getShaderType() == Material::SHADERTYPE_ALPHA_BLEND)
{
ParticleRenderer::getInstance()->setTextureUnits
(cur_mat->getTexture()->getOpenGLTextureName(),
irr_driver->getDepthStencilTexture());
ParticleRenderer::getInstance()->setUniforms(
m_particles_queue.at(p.second)[0]->getColorFrom(),
m_particles_queue.at(p.second)[0]->getColorTo(), flips);
}
else
{
AlphaTestParticleRenderer::getInstance()->setTextureUnits
(cur_mat->getTexture()->getOpenGLTextureName());
AlphaTestParticleRenderer::getInstance()->setUniforms(
m_particles_queue.at(p.second)[0]->getColorFrom(),
m_particles_queue.at(p.second)[0]->getColorTo(), flips);
}
glBindVertexArray(std::get<0>(m_gl_particles[p.second]));
glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4,
m_particles_generated.at(p.second).size());
}
} // drawAll
#endif

View File

@ -0,0 +1,101 @@
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2017 SuperTuxKart-Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#ifndef HEADER_CPU_PARTICLE_MANAGER_HPP
#define HEADER_CPU_PARTICLE_MANAGER_HPP
#ifndef SERVER_ONLY
#include "graphics/gl_headers.hpp"
#include "utils/no_copy.hpp"
#include "utils/singleton.hpp"
#include <dimension2d.h>
#include <vector3d.h>
#include <SColor.h>
#include <cassert>
#include <string>
#include <tuple>
#include <unordered_map>
#include <vector>
using namespace irr;
struct CPUParticle
{
core::vector3df m_position;
float m_lifetime;
float m_size;
// ------------------------------------------------------------------------
CPUParticle(const core::vector3df& position, float lifetime, float size)
: m_position(position), m_lifetime(lifetime), m_size(size) {}
};
class STKParticle;
class Material;
class CPUParticleManager : public Singleton<CPUParticleManager>, NoCopy
{
private:
std::unordered_map<std::string, std::vector<STKParticle*> >
m_particles_queue;
std::unordered_map<std::string, std::vector<CPUParticle> >
m_particles_generated;
std::unordered_map<std::string, std::tuple<GLuint/*VAO*/,
GLuint/*VBO*/, unsigned/*VBO*/> > m_gl_particles;
std::unordered_map<std::string, Material*> m_material_map;
public:
// ------------------------------------------------------------------------
CPUParticleManager() {}
// ------------------------------------------------------------------------
~CPUParticleManager();
// ------------------------------------------------------------------------
void addParticleNode(STKParticle* node);
// ------------------------------------------------------------------------
void generateAll();
// ------------------------------------------------------------------------
void uploadAll();
// ------------------------------------------------------------------------
void drawAll();
// ------------------------------------------------------------------------
void reset()
{
for (auto& p : m_particles_queue)
{
p.second.clear();
}
for (auto& p : m_particles_generated)
{
p.second.clear();
}
}
// ------------------------------------------------------------------------
void cleanMaterialMap()
{
m_material_map.clear();
}
};
#endif
#endif

View File

@ -20,8 +20,8 @@
#include "config/user_config.hpp"
#include "graphics/command_buffer.hpp"
#include "graphics/cpu_particle_manager.hpp"
#include "graphics/draw_tools.hpp"
#include "graphics/gpu_particles.hpp"
#include "graphics/lod_node.hpp"
#include "graphics/materials.hpp"
#include "graphics/render_info.hpp"
@ -30,6 +30,7 @@
#include "graphics/stk_animated_mesh.hpp"
#include "graphics/stk_billboard.hpp"
#include "graphics/stk_mesh.hpp"
#include "graphics/stk_particle.hpp"
#include "tracks/track.hpp"
#include "utils/profiler.hpp"
@ -64,6 +65,7 @@ void DrawCalls::clearLists()
m_immediate_draw_list.clear();
m_billboard_list.clear();
m_particles_list.clear();
CPUParticleManager::getInstance()->reset();
}
// ----------------------------------------------------------------------------
@ -562,10 +564,10 @@ void DrawCalls::parseSceneManager(core::list<scene::ISceneNode*> &List,
if (!(*I)->isVisible())
continue;
if (ParticleSystemProxy *node = dynamic_cast<ParticleSystemProxy *>(*I))
if (STKParticle *node = dynamic_cast<STKParticle *>(*I))
{
if (!isCulledPrecise(cam, *I))
m_particles_list.push_back(node);
if (!isCulledPrecise(cam, *I, irr_driver->getBoundingBoxesViz()))
CPUParticleManager::getInstance()->addParticleNode(node);
continue;
}
@ -604,7 +606,7 @@ DrawCalls::DrawCalls()
DrawCalls::~DrawCalls()
{
CPUParticleManager::kill();
#if !defined(USE_GLES2)
delete m_solid_cmd_buffer;
delete m_shadow_cmd_buffer;
@ -646,6 +648,9 @@ void DrawCalls::prepareDrawCalls( ShadowMatrices& shadow_matrices,
PROFILER_POP_CPU_MARKER();
irr_driver->setSkinningJoint(getSkinningOffset());
PROFILER_PUSH_CPU_MARKER("- cpu particle generation", 0x2F, 0x1F, 0x11);
CPUParticleManager::getInstance()->generateAll();
PROFILER_POP_CPU_MARKER();
// Add a 1 s timeout
if (!m_sync)
m_sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
@ -684,6 +689,10 @@ void DrawCalls::prepareDrawCalls( ShadowMatrices& shadow_matrices,
m_deferred_update[i]->updateGL();
PROFILER_POP_CPU_MARKER();
PROFILER_PUSH_CPU_MARKER("- cpu particle upload", 0x3F, 0x03, 0x61);
CPUParticleManager::getInstance()->uploadAll();
PROFILER_POP_CPU_MARKER();
if (!CVS->supportsIndirectInstancingRendering())
return;
@ -726,9 +735,7 @@ void DrawCalls::renderBillboardList() const
// ----------------------------------------------------------------------------
void DrawCalls::renderParticlesList() const
{
glActiveTexture(GL_TEXTURE0);
for(auto particles: m_particles_list)
particles->render();
CPUParticleManager::getInstance()->drawAll();
}
#if !defined(USE_GLES2)

View File

@ -1,684 +0,0 @@
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2014-2015 SuperTuxKart-Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#ifndef SERVER_ONLY
#include "graphics/gpu_particles.hpp"
#include "config/user_config.hpp"
#include "graphics/irr_driver.hpp"
#include "graphics/particle_emitter.hpp"
#include "graphics/shared_gpu_objects.hpp"
#include "graphics/texture_shader.hpp"
#include "guiengine/engine.hpp"
#include "io/file_manager.hpp"
#include <ICameraSceneNode.h>
#include <IParticleSystemSceneNode.h>
#include "../../lib/irrlicht/source/Irrlicht/os.h"
#define COMPONENTCOUNT 8
// ============================================================================
/** Transform feedback shader that simulates the particles on GPU.
*/
class PointEmitterShader : public Shader
< PointEmitterShader, core::matrix4, core::matrix4, int, int, float >
{
public:
PointEmitterShader()
{
const char *varyings[] = { "new_particle_position", "new_lifetime",
"new_particle_velocity", "new_size" };
loadTFBProgram("pointemitter.vert", varyings, 4);
assignUniforms("previous_frame_sourcematrix", "sourcematrix",
"dt", "level", "size_increase_factor");
} // PointEmitterShader
}; // PointEmitterShader
// ============================================================================
/** A Shader to render particles.
*/
class SimpleParticleRender : public TextureShader<SimpleParticleRender, 2,
video::SColorf, video::SColorf>
{
public:
SimpleParticleRender()
{
loadProgram(PARTICLES_RENDERING,
GL_VERTEX_SHADER, "particle.vert",
GL_FRAGMENT_SHADER, "particle.frag");
assignUniforms("color_from", "color_to");
assignSamplerNames(0, "tex", ST_TRILINEAR_ANISOTROPIC_FILTERED,
1, "dtex", ST_NEAREST_FILTERED);
} // SimpleParticleRender
}; // SimpleParticleRender
// ============================================================================
class FlipParticleRender : public TextureShader<FlipParticleRender, 2>
{
public:
FlipParticleRender()
{
loadProgram(PARTICLES_RENDERING,
GL_VERTEX_SHADER, "flipparticle.vert",
GL_FRAGMENT_SHADER, "particle.frag");
assignUniforms();
assignSamplerNames(0, "tex", ST_TRILINEAR_ANISOTROPIC_FILTERED,
1, "dtex", ST_NEAREST_FILTERED);
}
}; // FlipParticleShader
// ============================================================================
/** */
class HeightmapSimulationShader :
public TextureShader<HeightmapSimulationShader, 1,
core::matrix4, int, int,
float, float, float, float,
float>
{
public:
HeightmapSimulationShader()
{
const char *varyings[] = {"new_particle_position", "new_lifetime",
"new_particle_velocity", "new_size" };
loadTFBProgram("particlesimheightmap.vert", varyings, 4);
assignUniforms("sourcematrix", "dt", "level", "size_increase_factor",
"track_x", "track_x_len", "track_z", "track_z_len");
#if !defined(USE_GLES2)
assignSamplerNames(0, "heightmap", ST_TEXTURE_BUFFER);
#else
assignSamplerNames(0, "heightmap", ST_NEAREST_FILTERED);
#endif
} // HeightmapSimulationShader
}; // class HeightmapSimulationShader
// ============================================================================
scene::IParticleSystemSceneNode *ParticleSystemProxy::addParticleNode(
bool withDefaultEmitter, bool randomize_initial_y, ISceneNode* parent, s32 id,
const core::vector3df& position,
const core::vector3df& rotation,
const core::vector3df& scale)
{
if (!parent)
parent = irr_driver->getSceneManager()->getRootSceneNode();
IParticleSystemSceneNode* node = new ParticleSystemProxy(withDefaultEmitter,
parent, irr_driver->getSceneManager(), id, position, rotation, scale, randomize_initial_y);
node->drop();
return node;
}
ParticleSystemProxy::ParticleSystemProxy(bool createDefaultEmitter,
ISceneNode* parent, scene::ISceneManager* mgr, s32 id,
const core::vector3df& position,
const core::vector3df& rotation,
const core::vector3df& scale,
bool randomize_initial_y) : CParticleSystemSceneNode(createDefaultEmitter, parent, mgr, id, position, rotation, scale), m_alpha_additive(false), m_first_execution(true)
{
if (randomize_initial_y)
m_randomize_initial_y = randomize_initial_y;
m_randomize_initial_y = randomize_initial_y;
size_increase_factor = 0.;
ParticleParams = NULL;
InitialValues = NULL;
m_vertex_id_values = NULL;
m_color_from[0] = m_color_from[1] = m_color_from[2] = 1.0;
m_color_to[0] = m_color_to[1] = m_color_to[2] = 1.0;
// We set these later but avoid coverity report them
heighmapbuffer = 0;
heightmaptexture = 0;
has_height_map = false;
flip = false;
track_x = 0;
track_z = 0;
track_x_len = 0;
track_z_len = 0;
m_texture_name = 0;
}
ParticleSystemProxy::~ParticleSystemProxy()
{
delete[] m_vertex_id_values;
if (InitialValues)
free(InitialValues);
if (ParticleParams)
free(ParticleParams);
if (!m_first_execution)
cleanGL();
if (heighmapbuffer)
glDeleteBuffers(1, &heighmapbuffer);
if (heightmaptexture)
glDeleteTextures(1, &heightmaptexture);
}
void ParticleSystemProxy::setFlip()
{
flip = true;
}
void ParticleSystemProxy::setHeightmap(const std::vector<std::vector<float> > &hm,
float f1, float f2, float f3, float f4)
{
track_x = f1, track_z = f2, track_x_len = f3, track_z_len = f4;
unsigned width = (unsigned)hm.size();
unsigned height = (unsigned)hm[0].size();
float *hm_array = new float[width * height];
for (unsigned i = 0; i < width; i++)
{
for (unsigned j = 0; j < height; j++)
{
hm_array[i * height + j] = hm[i][j];
}
}
has_height_map = true;
#if !defined(USE_GLES2)
glGenBuffers(1, &heighmapbuffer);
glBindBuffer(GL_TEXTURE_BUFFER, heighmapbuffer);
glBufferData(GL_TEXTURE_BUFFER, width * height * sizeof(float), hm_array, GL_STREAM_COPY);
glGenTextures(1, &heightmaptexture);
glBindTexture(GL_TEXTURE_BUFFER, heightmaptexture);
glTexBuffer(GL_TEXTURE_BUFFER, GL_R32F, heighmapbuffer);
glBindBuffer(GL_TEXTURE_BUFFER, 0);
glBindTexture(GL_TEXTURE_BUFFER, 0);
#else
glGenTextures(1, &heightmaptexture);
glBindTexture(GL_TEXTURE_2D, heightmaptexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_R32F, width, height, 0, GL_RED, GL_FLOAT, hm_array);
glBindTexture(GL_TEXTURE_2D, 0);
#endif
delete[] hm_array;
}
static
void generateLifetimeSizeDirection(scene::IParticleEmitter *emitter, float &lifetime, float &size, float &dirX, float &dirY, float &dirZ)
{
float sizeMin = emitter->getMinStartSize().Height;
float sizeMax = emitter->getMaxStartSize().Height;
float lifetime_range = float(emitter->getMaxLifeTime() - emitter->getMinLifeTime());
lifetime = os::Randomizer::frand() * lifetime_range;
lifetime += emitter->getMinLifeTime();
size = os::Randomizer::frand();
size *= (sizeMax - sizeMin);
size += sizeMin;
core::vector3df particledir = emitter->getDirection();
particledir.rotateXYBy(os::Randomizer::frand() * emitter->getMaxAngleDegrees());
particledir.rotateYZBy(os::Randomizer::frand() * emitter->getMaxAngleDegrees());
particledir.rotateXZBy(os::Randomizer::frand() * emitter->getMaxAngleDegrees());
dirX = particledir.X;
dirY = particledir.Y;
dirZ = particledir.Z;
}
void ParticleSystemProxy::generateParticlesFromPointEmitter(scene::IParticlePointEmitter *emitter)
{
ParticleData* ParticleParamsTmp = (ParticleData *) realloc(ParticleParams, sizeof(ParticleData) * m_count);
ParticleData* InitialValuesTmp = (ParticleData *)realloc(InitialValues, sizeof(ParticleData)* m_count);
if (ParticleParamsTmp != NULL) // In case memory allocation succeeded
{
ParticleParams = ParticleParamsTmp;
}
else
{
Log::error("GPUParticles", "Not enough memory for %d from point particles.", m_count);
m_count = m_previous_count;
}
if (InitialValuesTmp != NULL)
{
InitialValues = InitialValuesTmp;
}
else
{
Log::fatal("GPUParticles", "Not enough memory for %d from point particles.", m_count);
m_count = m_previous_count;
}
for (unsigned i = 0; i < m_count; i++)
{
ParticleParams[i].PositionX = 0;
ParticleParams[i].PositionY = 0;
ParticleParams[i].PositionZ = 0;
// Initial lifetime is >1
InitialValues[i].Lifetime = 2.;
memcpy(&(InitialValues[i].PositionX), &(ParticleParams[i].PositionX), 3 * sizeof(float));
generateLifetimeSizeDirection(emitter, ParticleParams[i].Lifetime, ParticleParams[i].Size,
ParticleParams[i].DirectionX, ParticleParams[i].DirectionY, ParticleParams[i].DirectionZ);
memcpy(&(InitialValues[i].DirectionX), &(ParticleParams[i].DirectionX), 4 * sizeof(float));
}
}
void ParticleSystemProxy::generateParticlesFromBoxEmitter(scene::IParticleBoxEmitter *emitter)
{
ParticleData* ParticleParamsTmp = (ParticleData *) realloc(ParticleParams, sizeof(ParticleData) * m_count);
ParticleData* InitialValuesTmp = (ParticleData *)realloc(InitialValues, sizeof(ParticleData)* m_count);
if (ParticleParamsTmp != NULL) // In case memory allocation succeeded
{
ParticleParams = ParticleParamsTmp;
}
else
{
Log::error("GPUParticles", "Not enough memory for %d from box particles.", m_count);
m_count = m_previous_count;
}
if (InitialValuesTmp != NULL)
{
InitialValues = InitialValuesTmp;
}
else
{
Log::error("GPUParticles", "Not enough memory for %d from box particles.", m_count);
m_count = m_previous_count;
}
const core::vector3df& extent = emitter->getBox().getExtent();
for (unsigned i = 0; i < m_count; i++)
{
ParticleParams[i].PositionX = emitter->getBox().MinEdge.X + os::Randomizer::frand() * extent.X;
ParticleParams[i].PositionY = emitter->getBox().MinEdge.Y + os::Randomizer::frand() * extent.Y;
ParticleParams[i].PositionZ = emitter->getBox().MinEdge.Z + os::Randomizer::frand() * extent.Z;
// Initial lifetime is random
InitialValues[i].Lifetime = os::Randomizer::frand();
if (!m_randomize_initial_y)
InitialValues[i].Lifetime += 1.;
memcpy(&(InitialValues[i].PositionX), &(ParticleParams[i].PositionX), 3 * sizeof(float));
generateLifetimeSizeDirection(emitter, ParticleParams[i].Lifetime, ParticleParams[i].Size,
ParticleParams[i].DirectionX, ParticleParams[i].DirectionY, ParticleParams[i].DirectionZ);
memcpy(&(InitialValues[i].DirectionX), &(ParticleParams[i].DirectionX), 4 * sizeof(float));
if (m_randomize_initial_y)
InitialValues[i].PositionY = os::Randomizer::frand()*50.0f; // -100.0f;
}
}
void ParticleSystemProxy::generateParticlesFromSphereEmitter(scene::IParticleSphereEmitter *emitter)
{
ParticleData* ParticleParamsTmp = (ParticleData *) realloc(ParticleParams, sizeof(ParticleData) * m_count);
ParticleData* InitialValuesTmp = (ParticleData *)realloc(InitialValues, sizeof(ParticleData)* m_count);
if(ParticleParamsTmp != NULL) // In case memory allocation succeeded
ParticleParams = ParticleParamsTmp;
if(InitialValuesTmp != NULL)
InitialValues = InitialValuesTmp;
for (unsigned i = 0; i < m_count; i++) {
// Random distance from center
const f32 distance = os::Randomizer::frand() * emitter->getRadius();
// Random direction from center
vector3df pos = emitter->getCenter() + distance;
pos.rotateXYBy(os::Randomizer::frand() * 360.f, emitter->getCenter());
pos.rotateYZBy(os::Randomizer::frand() * 360.f, emitter->getCenter());
pos.rotateXZBy(os::Randomizer::frand() * 360.f, emitter->getCenter());
ParticleParams[i].PositionX = pos.X;
ParticleParams[i].PositionY = pos.Y;
ParticleParams[i].PositionZ = pos.Z;
// Initial lifetime is > 1
InitialValues[i].Lifetime = 2.;
memcpy(&(InitialValues[i].PositionX), &(ParticleParams[i].PositionX), 3 * sizeof(float));
generateLifetimeSizeDirection(emitter, ParticleParams[i].Lifetime, ParticleParams[i].Size,
ParticleParams[i].DirectionX, ParticleParams[i].DirectionY, ParticleParams[i].DirectionZ);
memcpy(&(InitialValues[i].DirectionX), &(ParticleParams[i].DirectionX), 4 * sizeof(float));
}
}
static bool isGPUParticleType(scene::E_PARTICLE_EMITTER_TYPE type)
{
switch (type)
{
case scene::EPET_POINT:
case scene::EPET_BOX:
case scene::EPET_SPHERE:
return true;
default:
return false;
}
}
void ParticleSystemProxy::setEmitter(scene::IParticleEmitter* emitter)
{
CParticleSystemSceneNode::setEmitter(emitter);
if (!emitter || !isGPUParticleType(emitter->getType()))
return;
if (!m_first_execution)
cleanGL();
has_height_map = false;
flip = false;
m_first_execution = true;
m_previous_count = m_count; // save to handle out of memory errors
m_count = emitter->getMaxParticlesPerSecond() * emitter->getMaxLifeTime() / 1000;
switch (emitter->getType())
{
case scene::EPET_POINT:
generateParticlesFromPointEmitter(emitter);
break;
case scene::EPET_BOX:
generateParticlesFromBoxEmitter(static_cast<scene::IParticleBoxEmitter *>(emitter));
break;
case scene::EPET_SPHERE:
generateParticlesFromSphereEmitter(static_cast<scene::IParticleSphereEmitter *>(emitter));
break;
default:
assert(0 && "Wrong particle type");
}
if (CVS->needsVertexIdWorkaround())
{
if (m_count != m_previous_count)
{
delete[] m_vertex_id_values;
m_vertex_id_values = NULL;
}
if (m_vertex_id_values == NULL && m_count > 0)
{
m_vertex_id_values = new int[m_count];
for (unsigned int i = 0; i < m_count; i++)
{
m_vertex_id_values[i] = i;
}
}
}
m_texture_name = getMaterial(0).getTexture(0)->getOpenGLTextureName();
}
void ParticleSystemProxy::cleanGL()
{
if (flip)
glDeleteBuffers(1, &quaternionsbuffer);
glDeleteBuffers(2, tfb_buffers);
glDeleteBuffers(1, &initial_values_buffer);
glDeleteVertexArrays(1, &current_rendering_vao);
glDeleteVertexArrays(1, &non_current_rendering_vao);
glDeleteVertexArrays(1, &current_simulation_vao);
glDeleteVertexArrays(1, &non_current_simulation_vao);
}
void ParticleSystemProxy::CommonRenderingVAO(GLuint PositionBuffer)
{
glBindBuffer(GL_ARRAY_BUFFER, SharedGPUObjects::getParticleQuadVBO());
glEnableVertexAttribArray(4);
glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);
glEnableVertexAttribArray(3);
glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (GLvoid *)(2 * sizeof(float)));
glBindBuffer(GL_ARRAY_BUFFER, PositionBuffer);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(ParticleData), 0);
glVertexAttribDivisorARB(0, 1);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 1, GL_FLOAT, GL_FALSE, sizeof(ParticleData), (GLvoid *)(3 * sizeof(float)));
glVertexAttribDivisorARB(1, 1);
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, sizeof(ParticleData), (GLvoid *)(7 * sizeof(float)));
glVertexAttribDivisorARB(2, 1);
}
void ParticleSystemProxy::AppendQuaternionRenderingVAO(GLuint QuaternionBuffer)
{
glBindBuffer(GL_ARRAY_BUFFER, QuaternionBuffer);
glEnableVertexAttribArray(5);
glVertexAttribPointer(5, 3, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);
glVertexAttribDivisorARB(5, 1);
glEnableVertexAttribArray(6);
glVertexAttribPointer(6, 1, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (GLvoid *)(3 * sizeof(float)));
glVertexAttribDivisorARB(6, 1);
}
void ParticleSystemProxy::CommonSimulationVAO(GLuint position_vbo, GLuint initialValues_vbo, GLuint vertex_id_buffer)
{
glBindBuffer(GL_ARRAY_BUFFER, position_vbo);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(ParticleSystemProxy::ParticleData), (GLvoid*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 1, GL_FLOAT, GL_FALSE, sizeof(ParticleSystemProxy::ParticleData), (GLvoid*)(3 * sizeof(float)));
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(ParticleSystemProxy::ParticleData), (GLvoid*)(4 * sizeof(float)));
glEnableVertexAttribArray(3);
glVertexAttribPointer(3, 1, GL_FLOAT, GL_FALSE, sizeof(ParticleSystemProxy::ParticleData), (GLvoid*)(7 * sizeof(float)));
glBindBuffer(GL_ARRAY_BUFFER, initialValues_vbo);
glEnableVertexAttribArray(4);
glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, sizeof(ParticleSystemProxy::ParticleData), (GLvoid*)0);
glEnableVertexAttribArray(5);
glVertexAttribPointer(5, 1, GL_FLOAT, GL_FALSE, sizeof(ParticleSystemProxy::ParticleData), (GLvoid*)(3 * sizeof(float)));
glEnableVertexAttribArray(6);
glVertexAttribPointer(6, 3, GL_FLOAT, GL_FALSE, sizeof(ParticleSystemProxy::ParticleData), (GLvoid*)(4 * sizeof(float)));
glEnableVertexAttribArray(7);
glVertexAttribPointer(7, 1, GL_FLOAT, GL_FALSE, sizeof(ParticleSystemProxy::ParticleData), (GLvoid*)(7 * sizeof(float)));
if (CVS->needsVertexIdWorkaround())
{
glBindBuffer(GL_ARRAY_BUFFER, vertex_id_buffer);
glEnableVertexAttribArray(8);
glVertexAttribIPointer(8, 1, GL_INT, sizeof(int), (GLvoid*)0);
}
}
void ParticleSystemProxy::simulate()
{
int timediff = int(GUIEngine::getLatestDt() * 1000.f);
int active_count = getEmitter()->getMaxLifeTime() * getEmitter()->getMaxParticlesPerSecond() / 1000;
core::matrix4 matrix = getAbsoluteTransformation();
glEnable(GL_RASTERIZER_DISCARD);
if (has_height_map)
{
HeightmapSimulationShader::getInstance()->use();
HeightmapSimulationShader::getInstance()->setTextureUnits(heightmaptexture);
HeightmapSimulationShader::getInstance()->setUniforms(matrix, timediff, active_count, size_increase_factor, track_x, track_x_len, track_z, track_z_len);
}
else
{
PointEmitterShader::getInstance()->use();
PointEmitterShader::getInstance()->setUniforms(m_previous_frame_matrix, matrix, timediff, active_count, size_increase_factor);
}
m_previous_frame_matrix = matrix;
glBindVertexArray(current_simulation_vao);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, tfb_buffers[1]);
glBeginTransformFeedback(GL_POINTS);
glDrawArrays(GL_POINTS, 0, m_count);
glEndTransformFeedback();
glBindVertexArray(0);
glActiveTexture(GL_TEXTURE0);
glDisable(GL_RASTERIZER_DISCARD);
#ifdef DEBUG_PARTICLES
// This code maps the data from the particles (initial data, current data
// and output of transform feedback shader) into memory. Useful for debugging.
if (std::string(getName()) == std::string("particles(C:/Users/jhenrich/supertuxkart/stk-assets/textures/skid-particle1.png)"))
{
glBindVertexArray(current_simulation_vao);
glBindBuffer(GL_ARRAY_BUFFER, initial_values_buffer);
ParticleData *p_initial = (ParticleData*)glMapBufferRange(GL_ARRAY_BUFFER, 0, m_count*sizeof(ParticleData), GL_MAP_READ_BIT);
glUnmapBuffer(GL_ARRAY_BUFFER);
glBindBuffer(GL_ARRAY_BUFFER, tfb_buffers[0]);
ParticleData *p_prev = (ParticleData*)glMapBufferRange(GL_ARRAY_BUFFER, 0, m_count*sizeof(ParticleData), GL_MAP_READ_BIT);
glUnmapBuffer(GL_ARRAY_BUFFER);
glBindBuffer(GL_ARRAY_BUFFER, tfb_buffers[1]);
ParticleData *p_new = (ParticleData*)glMapBufferRange(GL_ARRAY_BUFFER, 0, m_count*sizeof(ParticleData), GL_MAP_READ_BIT);
glUnmapBuffer(GL_ARRAY_BUFFER);
}
#endif
std::swap(tfb_buffers[0], tfb_buffers[1]);
std::swap(current_rendering_vao, non_current_rendering_vao);
std::swap(current_simulation_vao, non_current_simulation_vao);
}
void ParticleSystemProxy::drawFlip()
{
glBlendFunc(GL_ONE, GL_ONE);
FlipParticleRender::getInstance()->use();
FlipParticleRender::getInstance()->setTextureUnits(m_texture_name, irr_driver->getDepthStencilTexture());
FlipParticleRender::getInstance()->setUniforms();
glBindVertexArray(current_rendering_vao);
glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, m_count);
}
void ParticleSystemProxy::drawNotFlip()
{
if (m_alpha_additive)
glBlendFunc(GL_ONE, GL_ONE);
else
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
SimpleParticleRender::getInstance()->use();
SimpleParticleRender::getInstance()->setTextureUnits(m_texture_name, irr_driver->getDepthStencilTexture());
video::SColorf ColorFrom = video::SColorf(getColorFrom()[0], getColorFrom()[1], getColorFrom()[2]);
video::SColorf ColorTo = video::SColorf(getColorTo()[0], getColorTo()[1], getColorTo()[2]);
SimpleParticleRender::getInstance()->setUniforms(ColorFrom, ColorTo);
glBindVertexArray(current_rendering_vao);
glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, m_count);
}
void ParticleSystemProxy::draw()
{
if (flip)
drawFlip();
else
drawNotFlip();
}
void ParticleSystemProxy::generateVAOs()
{
glBindVertexArray(0);
glGenBuffers(1, &initial_values_buffer);
glBindBuffer(GL_ARRAY_BUFFER, initial_values_buffer);
glBufferData(GL_ARRAY_BUFFER, m_count * sizeof(ParticleData), ParticleParams, GL_STREAM_COPY);
glGenBuffers(2, tfb_buffers);
glBindBuffer(GL_ARRAY_BUFFER, tfb_buffers[0]);
glBufferData(GL_ARRAY_BUFFER, m_count * sizeof(ParticleData), InitialValues, GL_STREAM_COPY);
glBindBuffer(GL_ARRAY_BUFFER, tfb_buffers[1]);
glBufferData(GL_ARRAY_BUFFER, m_count * sizeof(ParticleData), 0, GL_STREAM_COPY);
if (CVS->needsVertexIdWorkaround())
{
glGenBuffers(1, &vertex_id_buffer);
glBindBuffer(GL_ARRAY_BUFFER, vertex_id_buffer);
glBufferData(GL_ARRAY_BUFFER, m_count * sizeof(int),
m_vertex_id_values, GL_STATIC_DRAW);
}
glBindBuffer(GL_ARRAY_BUFFER, 0);
glGenVertexArrays(1, &current_rendering_vao);
glGenVertexArrays(1, &non_current_rendering_vao);
glGenVertexArrays(1, &current_simulation_vao);
glGenVertexArrays(1, &non_current_simulation_vao);
// TODO: Don't execute CommonSimulationVAO twice because it unnecessarily
// initializes initial_values_buffer and vertex_id_buffer twice.
glBindVertexArray(current_simulation_vao);
CommonSimulationVAO(tfb_buffers[0], initial_values_buffer, vertex_id_buffer);
glBindVertexArray(non_current_simulation_vao);
CommonSimulationVAO(tfb_buffers[1], initial_values_buffer, vertex_id_buffer);
glBindVertexArray(0);
if (flip)
{
float *quaternions = new float[4 * m_count];
for (unsigned i = 0; i < m_count; i++)
{
core::vector3df rotationdir(0., 1., 0.);
quaternions[4 * i] = rotationdir.X;
quaternions[4 * i + 1] = rotationdir.Y;
quaternions[4 * i + 2] = rotationdir.Z;
quaternions[4 * i + 3] = 3.14f * 3.f * (2.f * os::Randomizer::frand() - 1.f); // 3 half rotation during lifetime at max
}
glGenBuffers(1, &quaternionsbuffer);
glBindBuffer(GL_ARRAY_BUFFER, quaternionsbuffer);
glBufferData(GL_ARRAY_BUFFER, 4 * m_count * sizeof(float), quaternions, GL_STREAM_COPY);
delete[] quaternions;
}
glBindVertexArray(current_rendering_vao);
CommonRenderingVAO(tfb_buffers[0]);
if (flip)
AppendQuaternionRenderingVAO(quaternionsbuffer);
glBindVertexArray(non_current_rendering_vao);
CommonRenderingVAO(tfb_buffers[1]);
if (flip)
AppendQuaternionRenderingVAO(quaternionsbuffer);
glBindVertexArray(0);
}
void ParticleSystemProxy::render() {
if (!getEmitter() || !isGPUParticleType(getEmitter()->getType()))
{
CParticleSystemSceneNode::render();
return;
}
if (m_first_execution)
{
generateVAOs();
m_previous_frame_matrix = getAbsoluteTransformation();
m_first_execution = false;
}
else
{
simulate();
draw();
}
}
#endif // SERVER_ONLY

View File

@ -1,119 +0,0 @@
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2014-2015 SuperTuxKart-Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#ifndef SERVER_ONLY
#ifndef HEADER_GPU_PARTICLES_HPP
#define HEADER_GPU_PARTICLES_HPP
#include "graphics/shader.hpp"
#include "../lib/irrlicht/source/Irrlicht/CParticleSystemSceneNode.h"
#include <ISceneManager.h>
#include <IParticleSystemSceneNode.h>
namespace irr { namespace video{ class ITexture; } }
using namespace irr;
class ParticleSystemProxy : public scene::CParticleSystemSceneNode
{
protected:
GLuint tfb_buffers[2], initial_values_buffer, heighmapbuffer,
heightmaptexture, quaternionsbuffer, vertex_id_buffer;
GLuint current_simulation_vao, non_current_simulation_vao;
GLuint current_rendering_vao, non_current_rendering_vao;
bool m_alpha_additive, has_height_map, flip;
float size_increase_factor, track_x, track_z, track_x_len, track_z_len;
float m_color_from[3];
float m_color_to[3];
bool m_first_execution;
bool m_randomize_initial_y;
GLuint m_texture_name;
/** Previous frame particles emitter source matrix */
core::matrix4 m_previous_frame_matrix;
/** Current count of particles. */
unsigned m_count;
/** Previous count - for error handling only. */
unsigned m_previous_count;
static void CommonRenderingVAO(GLuint PositionBuffer);
static void AppendQuaternionRenderingVAO(GLuint QuaternionBuffer);
static void CommonSimulationVAO(GLuint position_vbo,
GLuint initialValues_vbo,
GLuint vertex_id_buffer);
void generateVAOs();
void cleanGL();
void drawFlip();
void drawNotFlip();
virtual void simulate();
virtual void draw();
struct ParticleData
{
float PositionX;
float PositionY;
float PositionZ;
float Lifetime;
float DirectionX;
float DirectionY;
float DirectionZ;
float Size;
};
private:
ParticleData *ParticleParams, *InitialValues;
int* m_vertex_id_values;
void generateParticlesFromPointEmitter(scene::IParticlePointEmitter *);
void generateParticlesFromBoxEmitter(scene::IParticleBoxEmitter *);
void generateParticlesFromSphereEmitter(scene::IParticleSphereEmitter *);
public:
static IParticleSystemSceneNode *addParticleNode(
bool withDefaultEmitter = true, bool randomize_initial_y = false, ISceneNode* parent = 0, s32 id = -1,
const core::vector3df& position = core::vector3df(0, 0, 0),
const core::vector3df& rotation = core::vector3df(0, 0, 0),
const core::vector3df& scale = core::vector3df(1.0f, 1.0f, 1.0f));
ParticleSystemProxy(bool createDefaultEmitter,
ISceneNode* parent, scene::ISceneManager* mgr, s32 id,
const core::vector3df& position,
const core::vector3df& rotation,
const core::vector3df& scale,
bool randomize_initial_y);
~ParticleSystemProxy();
virtual void setEmitter(scene::IParticleEmitter* emitter);
virtual void render();
void setAlphaAdditive(bool val) { m_alpha_additive = val; }
void setIncreaseFactor(float val) { size_increase_factor = val; }
void setColorFrom(float r, float g, float b) { m_color_from[0] = r; m_color_from[1] = g; m_color_from[2] = b; }
void setColorTo(float r, float g, float b) { m_color_to[0] = r; m_color_to[1] = g; m_color_to[2] = b; }
const float* getColorFrom() const { return m_color_from; }
const float* getColorTo() const { return m_color_to; }
void setHeightmap(const std::vector<std::vector<float> >&, float, float, float, float);
void setFlip();
};
#endif // GPUPARTICLES_H
#endif // !SERVER_ONLY

View File

@ -21,11 +21,11 @@
#include "graphics/particle_emitter.hpp"
#include "graphics/central_settings.hpp"
#include "graphics/gpu_particles.hpp"
#include "graphics/irr_driver.hpp"
#include "graphics/material.hpp"
#include "graphics/material_manager.hpp"
#include "graphics/particle_kind.hpp"
#include "graphics/stk_particle.hpp"
#include "graphics/wind.hpp"
#include "io/file_manager.hpp"
#include "tracks/track.hpp"
@ -271,7 +271,9 @@ public:
{
const u32 maxdiff = particlearray[i].endTime - particlearray[i].startTime;
const u32 curdiff = now - particlearray[i].startTime;
const f32 timefraction = (f32)curdiff / maxdiff;
f32 timefraction = 0.0f;
if (maxdiff > 0)
timefraction = (f32)curdiff / maxdiff;
core::vector3df curr_color = m_color_from + (m_color_to - m_color_from)* timefraction;
particlearray[i].color = video::SColor(255, (int)curr_color.X, (int)curr_color.Y, (int)curr_color.Z);
}
@ -464,19 +466,9 @@ void ParticleEmitter::setParticleType(const ParticleKind* type)
else
{
if (m_is_glsl)
m_node = ParticleSystemProxy::addParticleNode(m_is_glsl, type->randomizeInitialY());
m_node = STKParticle::addParticleNode(m_is_glsl, type->randomizeInitialY());
else
m_node = irr_driver->addParticleNode();
if (m_is_glsl)
{
Material* material = type->getMaterial();
if (material != nullptr)
{
bool additive = (material->getShaderType() == Material::SHADERTYPE_ADDITIVE);
static_cast<ParticleSystemProxy *>(m_node)->setAlphaAdditive(additive);
}
}
}
if (m_parent != NULL)
@ -645,7 +637,7 @@ void ParticleEmitter::setParticleType(const ParticleKind* type)
{
if (m_is_glsl)
{
static_cast<ParticleSystemProxy *>(m_node)->setIncreaseFactor(type->getScaleAffectorFactorX());
static_cast<STKParticle*>(m_node)->setIncreaseFactor(type->getScaleAffectorFactorX());
}
else
{
@ -662,12 +654,12 @@ void ParticleEmitter::setParticleType(const ParticleKind* type)
if (m_is_glsl)
{
video::SColor color_from = type->getMinColor();
static_cast<ParticleSystemProxy *>(m_node)->setColorFrom(color_from.getRed() / 255.0f,
static_cast<STKParticle*>(m_node)->setColorFrom(color_from.getRed() / 255.0f,
color_from.getGreen() / 255.0f,
color_from.getBlue() / 255.0f);
video::SColor color_to = type->getMaxColor();
static_cast<ParticleSystemProxy *>(m_node)->setColorTo(color_to.getRed() / 255.0f,
static_cast<STKParticle*>(m_node)->setColorTo(color_to.getRed() / 255.0f,
color_to.getGreen() / 255.0f,
color_to.getBlue() / 255.0f);
}
@ -704,7 +696,7 @@ void ParticleEmitter::setParticleType(const ParticleKind* type)
if (flips)
{
if (m_is_glsl)
static_cast<ParticleSystemProxy *>(m_node)->setFlip();
static_cast<STKParticle*>(m_node)->setFlips();
}
}
} // setParticleType
@ -722,7 +714,8 @@ void ParticleEmitter::addHeightMapAffector(Track* t)
float track_z = aabb_min->getZ();
const float track_x_len = aabb_max->getX() - aabb_min->getX();
const float track_z_len = aabb_max->getZ() - aabb_min->getZ();
static_cast<ParticleSystemProxy *>(m_node)->setHeightmap(t->buildHeightMap(),
std::vector<std::vector<float> > array = t->buildHeightMap();
static_cast<STKParticle*>(m_node)->setHeightmap(array,
track_x, track_z, track_x_len, track_z_len);
}
else

View File

@ -261,7 +261,11 @@ Material* ParticleKind::getMaterial() const
if (material_manager->hasMaterial(m_material_file))
{
Material* material = material_manager->getMaterial(m_material_file);
if (material == NULL || material->getTexture(true/*srgb*/, true/*premul_alpha*/) == NULL)
if (material == NULL ||
material->getTexture(true/*srgb*/,
material->getShaderType() == Material::SHADERTYPE_ADDITIVE ||
material->getShaderType() == Material::SHADERTYPE_ALPHA_BLEND ?
true : false/*premul_alpha*/) == NULL)
{
throw std::runtime_error("[ParticleKind] Cannot locate file " + m_material_file);
}

View File

@ -512,13 +512,7 @@ void ShaderBasedRenderer::renderScene(scene::ICameraSceneNode * const camnode,
// ----------------------------------------------------------------------------
void ShaderBasedRenderer::renderParticles()
{
glDepthMask(GL_FALSE);
glDisable(GL_CULL_FACE);
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
m_draw_calls.renderParticlesList();
// m_scene_manager->drawAll(scene::ESNRP_TRANSPARENT_EFFECT);
} //renderParticles
// ----------------------------------------------------------------------------
@ -971,9 +965,10 @@ void ShaderBasedRenderer::preloadShaderFiles()
sfm->addShaderFile("billboard.vert", GL_VERTEX_SHADER);
sfm->addShaderFile("billboard.frag", GL_FRAGMENT_SHADER);
sfm->addShaderFile("pointemitter.vert", GL_VERTEX_SHADER);
sfm->addShaderFile("particle.vert", GL_VERTEX_SHADER);
sfm->addShaderFile("particle.frag", GL_FRAGMENT_SHADER);
sfm->addShaderFile("alphatest_particle.vert", GL_VERTEX_SHADER);
sfm->addShaderFile("alphatest_particle.frag", GL_FRAGMENT_SHADER);
sfm->addShaderFile("simple_particle.vert", GL_VERTEX_SHADER);
sfm->addShaderFile("simple_particle.frag", GL_FRAGMENT_SHADER);
if (CVS->supportsIndirectInstancingRendering())
{

View File

@ -97,7 +97,6 @@
#include "graphics/callbacks.hpp"
#include "graphics/central_settings.hpp"
#include "graphics/glwrap.hpp"
#include "graphics/gpu_particles.hpp"
#include "graphics/irr_driver.hpp"
#include "graphics/shared_gpu_objects.hpp"
#include "io/file_manager.hpp"

View File

@ -0,0 +1,490 @@
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2017 SuperTuxKart-Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#ifndef SERVER_ONLY
#include "graphics/stk_particle.hpp"
#include "graphics/cpu_particle_manager.hpp"
#include "graphics/irr_driver.hpp"
#include "guiengine/engine.hpp"
#include <cmath>
#include "../../lib/irrlicht/source/Irrlicht/os.h"
// ----------------------------------------------------------------------------
std::vector<Vec3> STKParticle::m_flips_data;
GLuint STKParticle::m_flips_buffer = 0;
// ----------------------------------------------------------------------------
scene::IParticleSystemSceneNode* STKParticle::addParticleNode(
bool withDefaultEmitter, bool randomize_initial_y, ISceneNode* parent,
s32 id, const core::vector3df& position, const core::vector3df& rotation,
const core::vector3df& scale)
{
if (!parent)
{
parent = irr_driver->getSceneManager()->getRootSceneNode();
}
IParticleSystemSceneNode* node = new STKParticle(withDefaultEmitter,
parent, irr_driver->getSceneManager(), id, position, rotation, scale,
randomize_initial_y);
node->drop();
return node;
} // addParticleNode
// ----------------------------------------------------------------------------
STKParticle::STKParticle(bool createDefaultEmitter,
ISceneNode* parent, scene::ISceneManager* mgr, s32 id,
const core::vector3df& position,
const core::vector3df& rotation,
const core::vector3df& scale,
bool randomize_initial_y)
: CParticleSystemSceneNode(createDefaultEmitter, parent, mgr, id,
position, rotation, scale)
{
m_hm = NULL;
m_color_to = video::SColorf(video::SColor(-1));
m_color_from = m_color_to;
m_size_increase_factor = 0.0f;
m_first_execution = true;
m_randomize_initial_y = randomize_initial_y;
m_flips = false;
m_max_count = 0;
} // STKParticle
// ----------------------------------------------------------------------------
static void generateLifetimeSizeDirection(scene::IParticleEmitter *emitter,
float& lifetime, float& size,
core::vector3df& direction)
{
float size_min = emitter->getMinStartSize().Height;
float size_max = emitter->getMaxStartSize().Height;
float lifetime_range =
float(emitter->getMaxLifeTime() - emitter->getMinLifeTime());
lifetime = os::Randomizer::frand() * lifetime_range;
lifetime += emitter->getMinLifeTime();
size = os::Randomizer::frand();
size *= (size_max - size_min);
size += size_min;
core::vector3df particledir = emitter->getDirection();
particledir.rotateXYBy(os::Randomizer::frand() *
emitter->getMaxAngleDegrees());
particledir.rotateYZBy(os::Randomizer::frand() *
emitter->getMaxAngleDegrees());
particledir.rotateXZBy(os::Randomizer::frand() *
emitter->getMaxAngleDegrees());
direction = particledir;
} // generateLifetimeSizeDirection
// ----------------------------------------------------------------------------
void STKParticle::generateParticlesFromPointEmitter
(scene::IParticlePointEmitter *emitter)
{
m_particles_generating.clear();
m_initial_particles.clear();
m_particles_generating.resize(m_max_count);
m_initial_particles.resize(m_max_count);
for (unsigned i = 0; i < m_max_count; i++)
{
// Initial lifetime is > 1
m_particles_generating[i].m_lifetime = 2.0f;
generateLifetimeSizeDirection(emitter,
m_initial_particles[i].m_lifetime,
m_particles_generating[i].m_size,
m_particles_generating[i].m_direction);
m_initial_particles[i].m_direction =
m_particles_generating[i].m_direction;
m_initial_particles[i].m_size = m_particles_generating[i].m_size;
}
} // generateParticlesFromPointEmitter
// ----------------------------------------------------------------------------
void STKParticle::generateParticlesFromBoxEmitter
(scene::IParticleBoxEmitter *emitter)
{
m_particles_generating.clear();
m_initial_particles.clear();
m_particles_generating.resize(m_max_count);
m_initial_particles.resize(m_max_count);
const core::vector3df& extent = emitter->getBox().getExtent();
for (unsigned i = 0; i < m_max_count; i++)
{
m_particles_generating[i].m_position.X =
emitter->getBox().MinEdge.X + os::Randomizer::frand() * extent.X;
m_particles_generating[i].m_position.Y =
emitter->getBox().MinEdge.Y + os::Randomizer::frand() * extent.Y;
m_particles_generating[i].m_position.Z =
emitter->getBox().MinEdge.Z + os::Randomizer::frand() * extent.Z;
// Initial lifetime is random
m_particles_generating[i].m_lifetime = os::Randomizer::frand();
if (!m_randomize_initial_y)
{
m_particles_generating[i].m_lifetime += 1.0f;
}
m_initial_particles[i].m_position =
m_particles_generating[i].m_position;
generateLifetimeSizeDirection(emitter,
m_initial_particles[i].m_lifetime,
m_particles_generating[i].m_size,
m_particles_generating[i].m_direction);
m_initial_particles[i].m_direction =
m_particles_generating[i].m_direction;
m_initial_particles[i].m_size = m_particles_generating[i].m_size;
if (m_randomize_initial_y)
{
m_initial_particles[i].m_position.Y =
os::Randomizer::frand() * 50.0f; // -100.0f;
}
}
} // generateParticlesFromBoxEmitter
// ----------------------------------------------------------------------------
void STKParticle::generateParticlesFromSphereEmitter
(scene::IParticleSphereEmitter *emitter)
{
m_particles_generating.clear();
m_initial_particles.clear();
m_particles_generating.resize(m_max_count);
m_initial_particles.resize(m_max_count);
for (unsigned i = 0; i < m_max_count; i++)
{
// Random distance from center
const f32 distance = os::Randomizer::frand() * emitter->getRadius();
// Random direction from center
vector3df pos = emitter->getCenter() + distance;
pos.rotateXYBy(os::Randomizer::frand() * 360.f, emitter->getCenter());
pos.rotateYZBy(os::Randomizer::frand() * 360.f, emitter->getCenter());
pos.rotateXZBy(os::Randomizer::frand() * 360.f, emitter->getCenter());
m_particles_generating[i].m_position = pos;
// Initial lifetime is > 1
m_particles_generating[i].m_lifetime = 2.0f;
m_initial_particles[i].m_position =
m_particles_generating[i].m_position;
generateLifetimeSizeDirection(emitter,
m_initial_particles[i].m_lifetime,
m_particles_generating[i].m_size,
m_particles_generating[i].m_direction);
m_initial_particles[i].m_direction =
m_particles_generating[i].m_direction;
m_initial_particles[i].m_size = m_particles_generating[i].m_size;
}
} // generateParticlesFromSphereEmitter
// ----------------------------------------------------------------------------
static bool isSTKParticleType(scene::E_PARTICLE_EMITTER_TYPE type)
{
switch (type)
{
case scene::EPET_POINT:
case scene::EPET_BOX:
case scene::EPET_SPHERE:
return true;
default:
return false;
}
} // isSTKParticleType
// ----------------------------------------------------------------------------
void STKParticle::setEmitter(scene::IParticleEmitter* emitter)
{
CParticleSystemSceneNode::setEmitter(emitter);
if (!emitter || !isSTKParticleType(emitter->getType()))
{
CParticleSystemSceneNode::setEmitter(NULL);
return;
}
delete m_hm;
m_hm = NULL;
m_first_execution = true;
m_flips = false;
m_max_count = emitter->getMaxParticlesPerSecond() * emitter->getMaxLifeTime() / 1000;
switch (emitter->getType())
{
case scene::EPET_POINT:
generateParticlesFromPointEmitter(emitter);
break;
case scene::EPET_BOX:
generateParticlesFromBoxEmitter
(static_cast<scene::IParticleBoxEmitter*>(emitter));
break;
case scene::EPET_SPHERE:
generateParticlesFromSphereEmitter
(static_cast<scene::IParticleSphereEmitter*>(emitter));
break;
default:
assert(false && "Wrong particle type");
}
} // setEmitter
// ----------------------------------------------------------------------------
void STKParticle::generate(std::vector<CPUParticle>* out)
{
if (!getEmitter())
{
return;
}
Buffer->BoundingBox.reset(AbsoluteTransformation.getTranslation());
if (m_first_execution)
{
m_previous_frame_matrix = AbsoluteTransformation;
m_first_execution = false;
}
float dt = GUIEngine::getLatestDt() * 1000.f;
int active_count = getEmitter()->getMaxLifeTime() *
getEmitter()->getMaxParticlesPerSecond() / 1000;
if (m_hm != NULL)
{
stimulateHeightMap(dt, active_count, out);
}
else
{
stimulateNormal(dt, active_count, out);
}
m_previous_frame_matrix = AbsoluteTransformation;
core::matrix4 inv(AbsoluteTransformation, core::matrix4::EM4CONST_INVERSE);
inv.transformBoxEx(Buffer->BoundingBox);
} // generate
// ----------------------------------------------------------------------------
inline float glslFract(float val)
{
return val - (float)floor(val);
} // glslFract
// ----------------------------------------------------------------------------
inline float glslMix(float x, float y, float a)
{
return x * (1.0f - a) + y * a;
} // glslMix
// ----------------------------------------------------------------------------
void STKParticle::stimulateHeightMap(float dt, unsigned int active_count,
std::vector<CPUParticle>* out)
{
assert(m_hm != NULL);
const core::matrix4 cur_matrix = AbsoluteTransformation;
for (unsigned i = 0; i < m_max_count; i++)
{
core::vector3df new_particle_position;
core::vector3df new_particle_direction;
float new_size = 0.0f;
float new_lifetime = 0.0f;
const core::vector3df particle_position =
m_particles_generating[i].m_position;
const float lifetime = m_particles_generating[i].m_lifetime;
const core::vector3df particle_direction =
m_particles_generating[i].m_direction;
const core::vector3df particle_position_initial =
m_initial_particles[i].m_position;
const float lifetime_initial = m_initial_particles[i].m_lifetime;
const core::vector3df particle_direction_initial =
m_initial_particles[i].m_direction;
const float size_initial = m_initial_particles[i].m_size;
bool reset = false;
const int px = core::clamp((int)(256.0f *
(particle_position.X - m_hm->m_x) / m_hm->m_x_len), 0, 255);
const int py = core::clamp((int)(256.0f *
(particle_position.Z - m_hm->m_z) / m_hm->m_z_len), 0, 255);
const float h = particle_position.Y - m_hm->m_array[px][py];
reset = h < 0.0f;
core::vector3df initial_position, initial_new_position;
cur_matrix.transformVect(initial_position, particle_position_initial);
cur_matrix.transformVect(initial_new_position,
particle_position_initial + particle_direction_initial);
core::vector3df adjusted_initial_direction =
initial_new_position - initial_position;
float adjusted_lifetime = lifetime + (dt / lifetime_initial);
reset = reset || ((adjusted_lifetime > 1.0f) && (i <= active_count));
reset = reset || (lifetime < 0.0f);
new_particle_position = !reset ?
(particle_position + particle_direction * dt) : initial_position;
new_lifetime = !reset ? adjusted_lifetime : 0.0f;
new_particle_direction = !reset ?
particle_direction : adjusted_initial_direction;
new_size = !reset ?
glslMix(size_initial, size_initial * m_size_increase_factor,
adjusted_lifetime) : 0.0f;
m_particles_generating[i].m_position = new_particle_position;
m_particles_generating[i].m_lifetime = new_lifetime;
m_particles_generating[i].m_direction = new_particle_direction;
m_particles_generating[i].m_size = new_size;
if (m_flips || new_size != 0.0f)
{
if (new_size != 0.0f)
{
out->emplace_back(new_particle_position, new_lifetime,
new_size);
}
Buffer->BoundingBox.addInternalPoint(new_particle_position);
}
}
} // stimulateHeightMap
// ----------------------------------------------------------------------------
void STKParticle::stimulateNormal(float dt, unsigned int active_count,
std::vector<CPUParticle>* out)
{
const core::matrix4 cur_matrix = AbsoluteTransformation;
core::vector3df previous_frame_position, current_frame_position,
previous_frame_direction, current_frame_direction;
for (unsigned i = 0; i < m_max_count; i++)
{
core::vector3df new_particle_position;
core::vector3df new_particle_direction;
float new_size = 0.0f;
float new_lifetime = 0.0f;
const core::vector3df particle_position =
m_particles_generating[i].m_position;
const float lifetime = m_particles_generating[i].m_lifetime;
const core::vector3df particle_direction =
m_particles_generating[i].m_direction;
const float size = m_particles_generating[i].m_size;
const core::vector3df particle_position_initial =
m_initial_particles[i].m_position;
const float lifetime_initial = m_initial_particles[i].m_lifetime;
const core::vector3df particle_direction_initial =
m_initial_particles[i].m_direction;
const float size_initial = m_initial_particles[i].m_size;
float updated_lifetime = lifetime + (dt / lifetime_initial);
if (updated_lifetime > 1.0f)
{
if (i < active_count)
{
float dt_from_last_frame =
glslFract(updated_lifetime) * lifetime_initial;
float coeff = dt_from_last_frame / dt;
m_previous_frame_matrix.transformVect(previous_frame_position,
particle_position_initial);
cur_matrix.transformVect(current_frame_position,
particle_position_initial);
core::vector3df updated_position = previous_frame_position
.getInterpolated(current_frame_position, coeff);
m_previous_frame_matrix.rotateVect(previous_frame_direction,
particle_direction_initial);
cur_matrix.rotateVect(current_frame_direction,
particle_direction_initial);
core::vector3df updated_direction = previous_frame_direction
.getInterpolated(current_frame_direction, coeff);
// + (current_frame_position - previous_frame_position) / dt;
// To be accurate, emitter speed should be added.
// But the simple formula
// ( (current_frame_position - previous_frame_position) / dt )
// with a constant speed between 2 frames creates visual
// artifacts when the framerate is low, and a more accurate
// formula would need more complex computations.
new_particle_position = updated_position + dt_from_last_frame * updated_direction;
new_particle_direction = updated_direction;
new_lifetime = glslFract(updated_lifetime);
new_size = glslMix(size_initial,
size_initial * m_size_increase_factor,
glslFract(updated_lifetime));
}
else
{
new_lifetime = glslFract(updated_lifetime);
new_size = 0.0f;
}
}
else
{
new_particle_position = particle_position +
particle_direction * dt;
new_particle_direction = particle_direction;
new_lifetime = updated_lifetime;
new_size = (size == 0.0f) ? 0.0f :
glslMix(size_initial, size_initial * m_size_increase_factor,
updated_lifetime);
}
m_particles_generating[i].m_position = new_particle_position;
m_particles_generating[i].m_lifetime = new_lifetime;
m_particles_generating[i].m_direction = new_particle_direction;
m_particles_generating[i].m_size = new_size;
if (m_flips || new_size != 0.0f)
{
if (new_size != 0.0f)
{
out->emplace_back(new_particle_position, new_lifetime,
new_size);
}
Buffer->BoundingBox.addInternalPoint(new_particle_position);
}
}
} // stimulateNormal
// ----------------------------------------------------------------------------
void STKParticle::updateFlips(unsigned maximum_particle_count)
{
bool updated = false;
while (maximum_particle_count > m_flips_data.size())
{
if (m_flips_buffer == 0)
{
glGenBuffers(1, &m_flips_buffer);
}
updated = true;
// 3 half rotation during lifetime at max
m_flips_data.emplace_back(0.0f, 1.0f, 0.0f,
3.14f * 3.0f * (2.0f * os::Randomizer::frand() - 1.0f));
}
if (updated)
{
glBindBuffer(GL_ARRAY_BUFFER, m_flips_buffer);
glBufferData(GL_ARRAY_BUFFER, m_flips_data.size() * 16,
m_flips_data.data(), GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
} // updateFlips
#endif // SERVER_ONLY

View File

@ -0,0 +1,163 @@
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2017 SuperTuxKart-Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#ifndef SERVER_ONLY
#ifndef HEADER_STK_PARTICLE_HPP
#define HEADER_STK_PARTICLE_HPP
#include "graphics/gl_headers.hpp"
#include "utils/vec3.hpp"
#include "../lib/irrlicht/source/Irrlicht/CParticleSystemSceneNode.h"
#include <vector>
using namespace irr;
struct CPUParticle;
class STKParticle : public scene::CParticleSystemSceneNode
{
private:
// ------------------------------------------------------------------------
struct HeightMapData
{
const std::vector<std::vector<float> > m_array;
const float m_x;
const float m_z;
const float m_x_len;
const float m_z_len;
// --------------------------------------------------------------------
HeightMapData(std::vector<std::vector<float> >& array,
float track_x, float track_z, float track_x_len,
float track_z_len)
: m_array(std::move(array)), m_x(track_x), m_z(track_z),
m_x_len(track_x_len), m_z_len(track_z_len) {}
};
// ------------------------------------------------------------------------
struct ParticleData
{
core::vector3df m_position;
float m_lifetime;
core::vector3df m_direction;
float m_size;
};
// ------------------------------------------------------------------------
HeightMapData* m_hm;
std::vector<ParticleData> m_particles_generating, m_initial_particles;
video::SColorf m_color_from, m_color_to;
float m_size_increase_factor;
bool m_first_execution, m_randomize_initial_y, m_flips;
/** Previous frame particles emitter source matrix */
core::matrix4 m_previous_frame_matrix;
/** Maximum count of particles. */
unsigned m_max_count;
static std::vector<Vec3> m_flips_data;
static GLuint m_flips_buffer;
// ------------------------------------------------------------------------
void generateParticlesFromPointEmitter(scene::IParticlePointEmitter*);
// ------------------------------------------------------------------------
void generateParticlesFromBoxEmitter(scene::IParticleBoxEmitter*);
// ------------------------------------------------------------------------
void generateParticlesFromSphereEmitter(scene::IParticleSphereEmitter*);
// ------------------------------------------------------------------------
void stimulateHeightMap(float, unsigned int, std::vector<CPUParticle>*);
// ------------------------------------------------------------------------
void stimulateNormal(float, unsigned int, std::vector<CPUParticle>*);
public:
// ------------------------------------------------------------------------
static IParticleSystemSceneNode *addParticleNode(
bool withDefaultEmitter = true, bool randomize_initial_y = false,
ISceneNode* parent = 0, s32 id = -1,
const core::vector3df& position = core::vector3df(0, 0, 0),
const core::vector3df& rotation = core::vector3df(0, 0, 0),
const core::vector3df& scale = core::vector3df(1.0f, 1.0f, 1.0f));
// ------------------------------------------------------------------------
STKParticle(bool createDefaultEmitter,
ISceneNode* parent, scene::ISceneManager* mgr, s32 id,
const core::vector3df& position,
const core::vector3df& rotation,
const core::vector3df& scale,
bool randomize_initial_y);
// ------------------------------------------------------------------------
~STKParticle()
{
delete m_hm;
}
// ------------------------------------------------------------------------
void setColorFrom(float r, float g, float b)
{
m_color_from.r = r;
m_color_from.g = g;
m_color_from.b = b;
}
// ------------------------------------------------------------------------
void setColorTo(float r, float g, float b)
{
m_color_to.r = r;
m_color_to.g = g;
m_color_to.b = b;
}
// ------------------------------------------------------------------------
const video::SColorf getColorFrom() const { return m_color_from; }
// ------------------------------------------------------------------------
const video::SColorf getColorTo() const { return m_color_to; }
// ------------------------------------------------------------------------
virtual void setEmitter(scene::IParticleEmitter* emitter);
// ------------------------------------------------------------------------
virtual void render() {}
// ------------------------------------------------------------------------
void setIncreaseFactor(float val) { m_size_increase_factor = val; }
// ------------------------------------------------------------------------
void setHeightmap(std::vector<std::vector<float> >& array, float track_x,
float track_z, float track_x_len, float track_z_len)
{
m_hm = new HeightMapData(array, track_x, track_z, track_x_len,
track_z_len);
}
// ------------------------------------------------------------------------
void generate(std::vector<CPUParticle>* out);
// ------------------------------------------------------------------------
void setFlips() { m_flips = true; }
// ------------------------------------------------------------------------
bool getFlips() const { return m_flips; }
// ------------------------------------------------------------------------
unsigned getMaxCount() const { return m_max_count; }
// ------------------------------------------------------------------------
static void updateFlips(unsigned maximum_particle_count);
// ------------------------------------------------------------------------
static void destroyFlipsBuffer()
{
glDeleteBuffers(1, &m_flips_buffer);
m_flips_buffer = 0;
}
// ------------------------------------------------------------------------
static GLuint getFlipsBuffer() { return m_flips_buffer; }
};
#endif
#endif // !SERVER_ONLY

View File

@ -29,6 +29,7 @@
#include "graphics/camera_end.hpp"
#include "graphics/CBatchingMesh.hpp"
#include "graphics/central_settings.hpp"
#include "graphics/cpu_particle_manager.hpp"
#include "graphics/irr_driver.hpp"
#include "graphics/lod_node.hpp"
#include "graphics/material.hpp"
@ -294,6 +295,7 @@ void Track::cleanup()
ItemManager::destroy();
#ifndef SERVER_ONLY
VAOManager::kill();
CPUParticleManager::getInstance()->cleanMaterialMap();
ParticleKindManager::get()->cleanUpTrackSpecificGfx();
#endif