17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-23 04:11:11 +02:00

Simulate and light the smoke on the GPU and make rain and snow of particles

This commit is contained in:
2026-10-20 16:44:27 +02:00
parent 6a109e984c
commit 867497b658
22 changed files with 1637 additions and 13 deletions

View File

@@ -0,0 +1,89 @@
// storage of the particles of the gpu (opengl46_gpu_particles, gl::particle_* in gl/ubo.h) for the compute passes
#ifndef PARTICLE_COMMON_GLSL
#define PARTICLE_COMMON_GLSL
// a particle of smoke; it's dead (or its place was never used) when its opacity is 0 or less
struct smoke_particle_s
{
vec4 position; // xyz: metres from the origin of the particles (world axes), w: rotation, radians
vec4 velocity; // xyz: m/s, w: size of the billboard, metres
vec4 state; // x: opacity, y: age, seconds
};
// a source of smoke as the last update of the simulation left it (smoke_source::gpu_state)
struct smoke_source_s
{
uvec4 pool; // x: first place of the source in the storage, y: number of its places, z: place of the first particle born in the update, w: particles born
vec4 location; // xyz: emitter at the update, from the origin
vec4 previous; // xyz: emitter at the update before, from the origin
vec4 launch; // xy: inclination, radians, zw: speed, m/s
vec4 initial; // xy: size, zw: opacity
vec4 sizechange; // x: per second, y: min, z: max
vec4 opacitychange; // x: per second, y: min, z: max
vec4 motion; // x: rise, y: damping (per second, of the vertical velocity), z: least vertical velocity
vec4 color; // rgb: colour of the smoke, lit by the lights of the pass
vec4 oldcolor; // rgb: colour lit as the cpu does it, without the lights (gfx.smoke.lighting off)
};
// what a draw of a particle reads, as attributes of an instance
struct particle_draw_s
{
vec4 place; // xyz: metres from the camera (world axes), w: size (smoke) or width (rain, snow)
vec4 shape; // x: rotation (smoke); xyz: from the head of the streak to its tail (rain, snow)
vec4 color; // rgb: lit colour, a: opacity
};
// the indirect draw command of the particles; the compute passes count the instances
layout(std430, binding = PARTICLE_COMMAND_SSBO_BINDING) buffer particle_command_ssbo
{
uint particle_draw_vertices;
uint particle_draw_instances;
uint particle_draw_first;
uint particle_draw_base;
};
layout(std430, binding = PARTICLE_OUTPUT_SSBO_BINDING) writeonly buffer particle_output_ssbo
{
particle_draw_s particle_draws[];
};
// pcg hash
uint particle_hash(uint value)
{
uint state = value * 747796405U + 2891336453U;
uint word = ((state >> ((state >> 28U) + 4U)) ^ state) * 277803737U;
return (word >> 22U) ^ word;
}
// next of a sequence of random numbers 0-1
float particle_random(inout uint state)
{
state = particle_hash(state);
return float(state >> 8U) * (1.0 / 16777216.0);
}
// whether a sphere (centre from the camera along the world axes) is within the planes of the view
bool particle_visible(vec4 planes[5], vec3 centre, float radius)
{
for (int i = 0; i < 5; i++)
if (dot(planes[i].xyz, centre) + planes[i].w < -radius)
return false;
return true;
}
// adds a particle to the draw; Capacity: the places of the storage of the draws
void particle_emit(vec3 place, float size, vec3 shape, vec3 color, float opacity, uint capacity)
{
uint index = atomicAdd(particle_draw_instances, 1U);
if (index >= capacity)
{
atomicAdd(particle_draw_instances, 0xFFFFFFFFU);
return;
}
particle_draws[index].place = vec4(place, size);
particle_draws[index].shape = vec4(shape, 0.0);
particle_draws[index].color = vec4(color, opacity);
}
#endif

146
shaders/particle_light.glsl Normal file
View File

@@ -0,0 +1,146 @@
// light of the particles of the gpu (opengl46_gpu_particles: smoke, rain, snow) at a point of the view space of the pass, as a
// particle takes it as a whole, at its middle: the ambient light, the sun (the moon at night) held back by the shadow map and
// by the clouds, and the lamps and headlights of the light cluster the point is in (the first lights of lights[] without the
// clusters). each light is scattered towards the camera by the phase of the particle, Forward: 0 evenly, towards 1 mostly
// onwards. the scale is the one of the shading of the surfaces (light_common.glsl): a white particle takes as much light as
// a white face turned half way to it. for the compute passes; needs <common> and <weather_common.glsl>
#ifndef PARTICLE_LIGHT_GLSL
#define PARTICLE_LIGHT_GLSL
#if SHADOWMAP_ENABLED
uniform sampler2DArrayShadow shadowmap;
#endif
uniform sampler2D headlightmap;
layout(binding = HEADLIGHT_SHADOW_TEX) uniform sampler2DArrayShadow headlight_shadowmap;
// AMBIENT_SCALE and SUN_DIFFUSE_SCALE of light_common.glsl
const float PARTICLE_AMBIENT_SCALE = 0.65;
const float PARTICLE_SUN_SCALE = 1.5;
// Henyey-Greenstein, 1 for every angle when G is 0
float particle_phase(float cosangle, float g)
{
float g2 = g * g;
return (1.0 - g2) / pow(max(1.0 + g2 - 2.0 * g * cosangle, 1e-4), 1.5);
}
// share of the sunlight the shadow map holds back at a point of the view space, one comparison (as fog_inject.comp)
float particle_sun_shadow(vec3 position)
{
#if SHADOWMAP_ENABLED
float span_distance = dot(position, position);
uint cascade;
for (cascade = 0U; cascade < MAX_CASCADES; cascade++)
if (span_distance <= cascade_end[cascade])
break;
for (; cascade < MAX_CASCADES; cascade++)
{
vec4 lightpos = lightview[cascade] * vec4(position, 1.0);
vec3 coords = lightpos.xyz / lightpos.w;
if (all(greaterThanEqual(coords.xy, vec2(0.0))) && all(lessThanEqual(coords.xy, vec2(1.0))))
{
if (coords.z < 0.0)
return 0.0;
return texture(shadowmap, vec4(coords.xy, float(cascade), coords.z + 0.0001 * float(cascade + 1U)));
}
// outside the map: lit, unless the cascades are fitted to the depth of the view (gfx.shadow.sdsm), whose maps cover only
// the area of the surfaces seen; then the first further cascade whose map holds the point
if (shadow_fit.x <= 0.0)
return 0.0;
}
return 0.0;
#else
return 0.0;
#endif
}
// light of a lamp or of the beam of headlights reaching a point of the view space (as fog_inject.comp; the beams with their
// shadow maps, the lamps without)
vec3 particle_lamp(light_s light, vec3 position)
{
if (light.type == LIGHT_HEADLIGHTS)
{
vec4 projected = light.headlight_projection * vec4(position, 1.0);
vec3 coords = projected.xyz / projected.w;
if (coords.z < 0.0 || coords.z > 1.0 || any(lessThan(coords.xy, vec2(0.0))) || any(greaterThan(coords.xy, vec2(1.0))))
return vec3(0.0);
float span_distance = length(light.pos - position);
float atten = 1.0 / (1.0 + light.linear * span_distance + light.quadratic * span_distance * span_distance);
atten *= mix(1.0, 0.0, clamp((coords.z - 0.998) * 500.0, 0.0, 1.0));
vec3 beam = textureLod(headlightmap, coords.xy, 1.0).rgb * light.headlight_weights.rgb;
float intensity = max(max(beam.r, beam.g), beam.b);
if ((light.headlight_weights.w > 0.5) && (intensity > 0.0))
{
uint map = uint(light.headlight_weights.w - 0.5);
vec4 shadowpos = headlight_shadow[map] * vec4(position, 1.0);
if (shadowpos.w > 0.0)
{
vec2 shadowcoords = shadowpos.xy / shadowpos.w;
if (all(greaterThanEqual(shadowcoords, vec2(0.0))) && all(lessThanEqual(shadowcoords, vec2(1.0))))
{
float texel = headlight_shadow_params[map].x * shadowpos.w;
float reference = shadowpos.z / max(shadowpos.w - texel * 1.5, 1e-3);
intensity *= 1.0 - texture(headlight_shadowmap, vec4(shadowcoords, float(map), reference));
}
}
}
return light.color * (atten * intensity * light.intensity);
}
vec3 tolight = light.pos - position;
float distance2 = dot(tolight, tolight);
float edge = clamp(1.0 - distance2 * distance2 * (light.quadratic * light.quadratic), 0.0, 1.0);
float atten = edge * edge / max(distance2, 0.01);
if (light.type == LIGHT_SPOT)
atten *= smoothstep(light.out_cutoff, light.in_cutoff, dot(-tolight * inversesqrt(max(distance2, 1e-8)), normalize(light.dir)));
return light.color * (atten * light.intensity);
}
// the light cluster of a point of the view space in front of the camera, as fragment_cluster() of light_common.glsl finds it for
// the fragment drawn there
uint particle_cluster(vec3 position)
{
vec4 clip = cluster_projection * vec4(position, 1.0);
vec2 screen = clip.xy / clip.w * 0.5 + 0.5;
ivec2 tile = clamp(ivec2(screen * vec2(CLUSTER_GRID_X, CLUSTER_GRID_Y)), ivec2(0), ivec2(CLUSTER_GRID_X - 1, CLUSTER_GRID_Y - 1));
int slice = clamp(int(floor(cluster_slice_bias + log(max(clip.w, 1e-4)) * cluster_slice_scale)), 0, CLUSTER_GRID_Z - 1);
return uint(tile.x + CLUSTER_GRID_X * (tile.y + CLUSTER_GRID_Y * slice));
}
// Position: of the particle in the view space; World: its offset from the camera along the world axes
vec3 particle_light(vec3 position, vec3 world, float forward)
{
vec3 tocamera = -normalize(position);
vec3 light = ambient * PARTICLE_AMBIENT_SCALE;
if (lights_count > 0U)
{
float sunlit = cloud_shadow(world) * (1.0 - particle_sun_shadow(position));
if (sunlit > 0.0)
light += lights[0].color * (0.5 * PARTICLE_SUN_SCALE * lights[0].intensity * sunlit * particle_phase(dot(normalize(lights[0].dir), tocamera), forward));
}
vec3 lamps = vec3(0.0);
if (lights_clustered != 0U)
{
if ((cluster_valid != 0U) && (position.z < 0.0))
{
cluster_range_s range = cluster_ranges[particle_cluster(position)];
for (uint i = 0U; i < range.count; i++)
{
light_s lamp = lights[cluster_light_indices[range.offset + i]];
vec3 fromlamp = position - lamp.pos;
lamps += particle_lamp(lamp, position) * particle_phase(dot(fromlamp, tocamera) * inversesqrt(max(dot(fromlamp, fromlamp), 1e-8)), forward);
}
}
}
else
{
for (uint i = 1U; i < lights_count; i++)
{
vec3 fromlamp = position - lights[i].pos;
lamps += particle_lamp(lights[i], position) * particle_phase(dot(fromlamp, tocamera) * inversesqrt(max(dot(fromlamp, fromlamp), 1e-8)), forward);
}
}
return light + lamps * 0.5;
}
#endif

View File

@@ -0,0 +1,24 @@
// soft particles (opengl46_gpu_particles): a particle fades where it nears what is behind it, instead of being cut by it.
// the resolved depth of the scene drawn so far (gl::SCENE_DEPTH_TEX), reversed with the far plane at infinity: the distance
// along the axis of the view is the near plane over the depth, and the sky, which writes no depth, stays at 0
#ifndef PARTICLE_SOFT_GLSL
#define PARTICLE_SOFT_GLSL
layout(binding = SCENE_DEPTH_TEX) uniform sampler2D particle_scene_depth;
// share of the particle left at the fragment, 0-1: Range (metres) is how far in front of the scene it fades out over, 0 for none.
// Fragdistance: of the fragment along the axis of the view
float particle_soft_fade(float range, float fragdistance)
{
if (range <= 0.0)
return 1.0;
float depth = texelFetch(particle_scene_depth, ivec2(gl_FragCoord.xy), 0).r;
if (depth <= 0.0)
return 1.0;
float scene = projection[3][2] / depth;
float share = clamp((scene - fragdistance) / range, 0.0, 1.0);
return share * share * (3.0 - 2.0 * share);
}
#endif

View File

@@ -0,0 +1,75 @@
// rain and snow of the gpu (opengl46_gpu_particles), in every colour pass which draws them: a thread per drop or flake. the
// particles fill three boxes around the camera, a small dense one and two larger ones, each repeated along the world axes:
// a particle keeps its place in the box, moved by how far the wind took them all and how far it fell, so nothing is kept
// from frame to frame. what the camera sees is lit (particle_light.glsl) and drawn as a streak from where it is to where
// it was a moment ago, seen from the moving camera (particles_precipitation.vert)
#include <common>
#include <weather_common.glsl>
#include <particle_light.glsl>
#include <particle_common.glsl>
layout(local_size_x = PARTICLE_GROUP_SIZE) in;
// x, y: first particles of the second and of the third box, z: particles, w: 1 for snow, 0 for rain
layout(location = 0) uniform uvec4 counts;
// xyz: how far the wind took the particles less where the camera is, in each box (metres, 0 to the size of the box); w: how
// far they fell in it, 0 to 16 sizes of the box
layout(location = 1) uniform vec4 offsets[3];
// xyz: size of each box, metres; w unused
layout(location = 4) uniform vec4 sizes;
// xyz: velocity of the wind less the one of the camera, m/s; w: speed of the fall
layout(location = 5) uniform vec4 velocity;
// x: seconds (for the flutter of the flakes), y: how long a streak lasts, seconds, z: opacity, w: places for the draws
layout(location = 6) uniform vec4 timing;
// the world axes around the camera to the view space of the pass
layout(location = 7) uniform mat4 view;
// y: how much a particle scatters the light onwards, z: brightness
layout(location = 11) uniform vec4 options;
layout(location = 12) uniform vec4 planes[5];
void main()
{
uint index = gl_GlobalInvocationID.x;
if (index >= counts.z)
return;
uint box = (index < counts.x) ? 0U : (index < counts.y) ? 1U : 2U;
float size = sizes[box];
bool snow = counts.w != 0U;
uint state = particle_hash(index * 3U + 0x9E3779B9U);
vec3 home = vec3(particle_random(state), particle_random(state), particle_random(state)) * size;
// the speed of the fall is (16 + k) / 16 of the common one, so a whole number of sizes of the box (16 of them) of the common
// fall is a whole number of them for every particle, and the fall can be kept within 16 sizes
float pace = (16.0 + floor(particle_random(state) * 7.0) - 3.0) / 16.0;
float width = snow ? mix(0.004, 0.012, particle_random(state)) : mix(0.0012, 0.0025, particle_random(state));
vec3 move = vec3(velocity.xyz) + vec3(0.0, -velocity.w * pace, 0.0);
vec3 place = home + offsets[box].xyz - vec3(0.0, offsets[box].w * pace, 0.0);
if (snow)
{
// the flakes flutter about their way
float amplitude = mix(0.1, 0.35, particle_random(state));
vec2 frequency = mix(vec2(1.5), vec2(5.0), vec2(particle_random(state), particle_random(state)));
vec2 phase = vec2(particle_random(state), particle_random(state)) * 6.2831853;
vec2 angle = frequency * timing.x + phase;
place.xz += amplitude * sin(angle);
move.xz += amplitude * frequency * cos(angle);
}
// within the box around the camera
vec3 world = mod(place, size) - 0.5 * size;
float opacity = timing.z;
// fading at the walls of the box, where the particles wrap around, and right at the camera
vec3 walls = abs(world) / (0.5 * size);
opacity *= 1.0 - smoothstep(0.7, 1.0, max(max(walls.x, walls.y), walls.z));
opacity *= smoothstep(0.15, 0.6, length(world));
if (opacity <= 0.002)
return;
vec3 tail = -move * timing.y;
float streak = length(tail);
if (streak > 3.0)
tail *= 3.0 / streak;
if (false == particle_visible(planes, world + 0.5 * tail, 0.5 * length(tail) + width))
return;
vec3 color = particle_light((view * vec4(world, 1.0)).xyz, world, options.y);
particle_emit(world, width, tail, color * options.z, opacity, uint(timing.w));
}

View File

@@ -0,0 +1,46 @@
// rain and snow of the gpu (opengl46_gpu_particles, particles_precipitation.vert): a streak rounded at its ends, fainter towards
// its tail, in the colour the compute pass lit it with, faded in front of the scene, fogged
in vec4 f_pos;
in vec4 f_color;
in vec4 f_streak;
in float f_soft;
in vec4 f_clip_pos;
in vec4 f_clip_future_pos;
#include <common>
#include <apply_fog.glsl>
#include <tonemapping.glsl>
#include <particle_soft.glsl>
layout(location = 0) out vec4 out_color;
#if MOTIONBLUR_ENABLED
layout(location = 1) out vec4 out_motion;
#endif
void main()
{
// distance from the axis of the streak, in its radii
float along = clamp(f_streak.y, 0.0, f_streak.w);
float offaxis = length(vec2(f_streak.x, f_streak.y - along)) / max(f_streak.z, 1e-6);
float alpha = f_color.a * (1.0 - smoothstep(0.35, 1.0, offaxis));
alpha *= mix(1.0, 0.4, along / max(f_streak.w, 1e-6));
alpha *= particle_soft_fade(f_soft, -f_pos.z);
if (alpha <= 0.0)
discard;
vec4 color = vec4(apply_fog(f_color.rgb), alpha);
#if POSTFX_ENABLED
out_color = color;
#else
out_color = tonemap(color);
#endif
#if MOTIONBLUR_ENABLED
{
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;
out_motion = vec4(a - b, 0.0f, alpha * alpha_mult);
}
#endif
}

View File

@@ -0,0 +1,58 @@
// rain and snow of the gpu (opengl46_gpu_particles): a streak per drop or flake the compute pass added to the draw, from where
// it is to where it was a moment ago as the moving camera saw it, turned to face the camera, rounded at its ends. a particle
// thinner than a pixel and a half is drawn that wide and as much fainter, so the distant ones don't flicker
layout(location = 0) in vec4 v_place; // xyz: the head, metres from the camera (world axes), w: width
layout(location = 1) in vec4 v_shape; // xyz: from the head to the tail
layout(location = 2) in vec4 v_color; // rgb: lit colour, a: opacity
out vec4 f_pos;
out vec4 f_color;
// x: across the streak, y: along it from the head, metres; z: its radius, w: its length
out vec4 f_streak;
out float f_soft;
out vec4 f_clip_pos;
out vec4 f_clip_future_pos;
#include <common>
// x: metres it fades out over in front of the scene, 0: none
layout(location = 0) uniform vec4 particle_soft;
// x: width of a pixel one metre in front of the camera, metres
layout(location = 1) uniform vec4 particle_pixel;
void main()
{
vec3 head = (modelview * vec4(v_place.xyz, 1.0)).xyz;
vec3 axis = mat3(modelview) * v_shape.xyz;
float span = length(axis);
vec3 direction = (span > 1e-5) ? axis / span : vec3(0.0, -1.0, 0.0);
vec3 middle = head + 0.5 * axis;
float width = v_place.w;
float alpha = v_color.a;
float least = 1.5 * particle_pixel.x * max(-middle.z, 1e-3);
if (width < least)
{
alpha *= width / least;
width = least;
}
float radius = 0.5 * width;
vec3 side = cross(direction, normalize(-middle));
float sidelength = length(side);
side = (sidelength > 1e-4) ? side / sidelength : vec3(1.0, 0.0, 0.0);
// a triangle strip of four corners: across, along
vec2 corner = vec2(float(gl_VertexID & 1), float(gl_VertexID >> 1));
float across = (corner.x * 2.0 - 1.0) * radius;
float along = mix(-radius, span + radius, corner.y);
f_pos = vec4(head + direction * along + side * across, 1.0);
f_clip_pos = projection * f_pos;
f_clip_future_pos = (projection * future) * f_pos;
gl_Position = f_clip_pos;
f_streak = vec4(across, along, radius, span);
f_color = vec4(v_color.rgb, alpha);
f_soft = particle_soft.x;
}

View File

@@ -0,0 +1,42 @@
// the smoke of the gpu (opengl46_gpu_particles, particles_smoke.vert): the texture of the smoke in the colour the compute pass
// lit the particle with, faded in front of the scene, fogged
in vec4 f_color;
in vec2 f_coord;
in vec4 f_pos;
in float f_soft;
in vec4 f_clip_pos;
in vec4 f_clip_future_pos;
#texture (tex1, 0, sRGB_A)
uniform sampler2D tex1;
#include <common>
#include <apply_fog.glsl>
#include <tonemapping.glsl>
#include <particle_soft.glsl>
layout(location = 0) out vec4 out_color;
#if MOTIONBLUR_ENABLED
layout(location = 1) out vec4 out_motion;
#endif
void main()
{
vec4 tex_color = texture(tex1, f_coord) * f_color;
tex_color.a *= particle_soft_fade(f_soft, -f_pos.z);
#if POSTFX_ENABLED
out_color = vec4(apply_fog(tex_color.rgb), tex_color.a);
#else
out_color = tonemap(vec4(apply_fog(tex_color.rgb), tex_color.a));
#endif
#if MOTIONBLUR_ENABLED
{
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;
out_motion = vec4(a - b, 0.0f, tex_color.a * alpha_mult);
}
#endif
}

View File

@@ -0,0 +1,37 @@
// the smoke of the gpu (opengl46_gpu_particles): a billboard per particle the compute pass added to the draw, turned about the
// axis of the view by the rotation of the particle (as opengl46_particles builds them on the cpu)
layout(location = 0) in vec4 v_place; // xyz: metres from the camera (world axes), w: size
layout(location = 1) in vec4 v_shape; // x: rotation, radians
layout(location = 2) in vec4 v_color; // rgb: lit colour, a: opacity
out vec4 f_pos;
out vec4 f_color;
out vec2 f_coord;
// metres in front of the scene the particle fades out over, see particle_soft.glsl
out float f_soft;
out vec4 f_clip_pos;
out vec4 f_clip_future_pos;
#include <common>
// x: share of the size of the particle it fades out over in front of the scene (gfx.particles.soft), 0: none
layout(location = 0) uniform vec4 particle_soft;
void main()
{
// a triangle strip of four corners
vec2 corner = vec2(float(gl_VertexID & 1), float(gl_VertexID >> 1)) - 0.5;
float c = cos(v_shape.x);
float s = sin(v_shape.x);
vec2 offset = vec2(c * corner.x - s * corner.y, s * corner.x + c * corner.y) * v_place.w;
f_pos = modelview * vec4(v_place.xyz, 1.0) + vec4(offset, 0.0, 0.0);
f_clip_pos = projection * f_pos;
f_clip_future_pos = (projection * future) * f_pos;
gl_Position = f_clip_pos;
f_coord = corner + 0.5;
f_color = v_color;
f_soft = particle_soft.x * 0.5 * v_place.w;
}

View File

@@ -0,0 +1,52 @@
// the smoke of the gpu (opengl46_gpu_particles), in every colour pass which draws it: a thread per place of the storage. the
// living particles the view sees are lit (particle_light.glsl) and added to the draw (particles_smoke.vert)
#include <common>
#include <weather_common.glsl>
#include <particle_light.glsl>
#include <particle_common.glsl>
layout(local_size_x = PARTICLE_GROUP_SIZE) in;
layout(std430, binding = PARTICLE_SSBO_BINDING) readonly buffer particle_ssbo
{
smoke_particle_s particles[];
};
layout(std430, binding = PARTICLE_SOURCE_SSBO_BINDING) readonly buffer particle_source_ssbo
{
smoke_source_s sources[];
};
layout(std430, binding = PARTICLE_OWNER_SSBO_BINDING) readonly buffer particle_owner_ssbo
{
uint owners[];
};
layout(location = 0) uniform uint places;
// xyz: the origin of the particles less the camera, metres along the world axes
layout(location = 1) uniform vec3 origin;
// the world axes around the camera to the view space of the pass
layout(location = 2) uniform mat4 view;
// x: 1 when the smoke is lit by the lights of the pass, y: how much it scatters the light onwards, z: places for the draws
layout(location = 3) uniform vec4 options;
// planes of the view (world axes around the camera): a point is inside when dot(xyz, point) + w >= 0
layout(location = 4) uniform vec4 planes[5];
void main()
{
uint place = gl_GlobalInvocationID.x;
if (place >= places)
return;
smoke_particle_s particle = particles[place];
if (particle.state.x <= 0.0)
return;
vec3 world = particle.position.xyz + origin;
float size = particle.velocity.w;
// the corners of the billboard turned anyhow
if (false == particle_visible(planes, world, size * 0.7072))
return;
smoke_source_s source = sources[owners[place]];
vec3 color = source.oldcolor.rgb;
if (options.x > 0.5)
color = source.color.rgb * particle_light((view * vec4(world, 1.0)).xyz, world, options.y);
particle_emit(world, size, vec3(particle.position.w, 0.0, 0.0), color, clamp(particle.state.x, 0.0, 1.0), uint(options.z));
}

View File

@@ -0,0 +1,84 @@
// the smoke of the gpu (opengl46_gpu_particles), once per update of the simulation: a thread per place of the storage. the
// particles the sources had born in the update take the places after the ones of the last update (each source keeps its
// places as a ring, the oldest give way), spread along the way the emitter went since the update before; the living ones
// change as smoke_source::update() changes them on the cpu
#include <common>
#include <particle_common.glsl>
layout(local_size_x = PARTICLE_GROUP_SIZE) in;
layout(std430, binding = PARTICLE_SSBO_BINDING) buffer particle_ssbo
{
smoke_particle_s particles[];
};
layout(std430, binding = PARTICLE_SOURCE_SSBO_BINDING) readonly buffer particle_source_ssbo
{
smoke_source_s sources[];
};
// the source of every place
layout(std430, binding = PARTICLE_OWNER_SSBO_BINDING) readonly buffer particle_owner_ssbo
{
uint owners[];
};
layout(location = 0) uniform uint places;
// x: seconds of the update, yzw: added to the positions, as the origin moved
layout(location = 1) uniform vec4 step;
// xyz: the wind, m/s; w unused
layout(location = 2) uniform vec4 wind;
// x: number of the update, for the random numbers
layout(location = 3) uniform uint seed;
// smoke_source::update( Particle ); false when the particle is dead
bool advance(inout smoke_particle_s particle, smoke_source_s source, float seconds)
{
particle.state.x = clamp(particle.state.x + source.opacitychange.x * seconds, source.opacitychange.y, source.opacitychange.z);
if (particle.state.x <= 0.0)
return false;
particle.velocity.w = clamp(particle.velocity.w + source.sizechange.x * seconds, source.sizechange.y, source.sizechange.z);
// crude smoke dispersion
particle.velocity.y += source.motion.x * particle.velocity.y * seconds;
particle.velocity.y -= source.motion.y * particle.velocity.y * seconds;
particle.velocity.y = max(source.motion.z, particle.velocity.y);
particle.position.xyz += particle.velocity.xyz * seconds;
particle.position.xyz += 0.1 * particle.state.y * wind.xyz * seconds;
particle.state.y += seconds;
return true;
}
void main()
{
uint place = gl_GlobalInvocationID.x;
if (place >= places)
return;
smoke_source_s source = sources[owners[place]];
smoke_particle_s particle = particles[place];
particle.position.xyz += step.yzw;
uint size = source.pool.y;
uint order = (place - source.pool.x + size - source.pool.z) % size;
if (order < source.pool.w)
{
// born in the update: smoke_source::particle_emitter::initialize()
uint state = particle_hash(place ^ particle_hash(seed));
float polar = mix(source.launch.x, source.launch.y, particle_random(state));
float azimuth = radians(mix(-180.0, 180.0, particle_random(state)));
vec3 direction = vec3(-sin(polar) * sin(azimuth), cos(polar), sin(polar) * cos(azimuth));
particle.velocity.xyz = direction * mix(source.launch.z, source.launch.w, particle_random(state));
particle.position.w = radians(mix(0.0, 360.0, particle_random(state)));
particle.velocity.w = mix(source.initial.x, source.initial.y, particle_random(state));
particle.state = vec4(mix(source.initial.z, source.initial.w, particle_random(state)), 0.0, 0.0, 0.0);
// the births spread over the update: where the emitter was then, and the time since
float share = (float(order) + 0.5) / float(source.pool.w);
particle.position.xyz = mix(source.previous.xyz, source.location.xyz, share);
if (false == advance(particle, source, step.x * (1.0 - share)))
particle.state.x = 0.0;
}
else if (particle.state.x > 0.0)
{
if (false == advance(particle, source, step.x))
particle.state.x = 0.0;
}
particles[place] = particle;
}