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:
89
shaders/particle_common.glsl
Normal file
89
shaders/particle_common.glsl
Normal file
@@ -0,0 +1,89 @@
|
||||
// storage of the particles of the gpu (opengl46_gpu_particles, gl::particle_* in gl/ubo.h) for the compute passes
|
||||
|
||||
#ifndef PARTICLE_COMMON_GLSL
|
||||
#define PARTICLE_COMMON_GLSL
|
||||
|
||||
// a particle of smoke; it's dead (or its place was never used) when its opacity is 0 or less
|
||||
struct smoke_particle_s
|
||||
{
|
||||
vec4 position; // xyz: metres from the origin of the particles (world axes), w: rotation, radians
|
||||
vec4 velocity; // xyz: m/s, w: size of the billboard, metres
|
||||
vec4 state; // x: opacity, y: age, seconds
|
||||
};
|
||||
|
||||
// a source of smoke as the last update of the simulation left it (smoke_source::gpu_state)
|
||||
struct smoke_source_s
|
||||
{
|
||||
uvec4 pool; // x: first place of the source in the storage, y: number of its places, z: place of the first particle born in the update, w: particles born
|
||||
vec4 location; // xyz: emitter at the update, from the origin
|
||||
vec4 previous; // xyz: emitter at the update before, from the origin
|
||||
vec4 launch; // xy: inclination, radians, zw: speed, m/s
|
||||
vec4 initial; // xy: size, zw: opacity
|
||||
vec4 sizechange; // x: per second, y: min, z: max
|
||||
vec4 opacitychange; // x: per second, y: min, z: max
|
||||
vec4 motion; // x: rise, y: damping (per second, of the vertical velocity), z: least vertical velocity
|
||||
vec4 color; // rgb: colour of the smoke, lit by the lights of the pass
|
||||
vec4 oldcolor; // rgb: colour lit as the cpu does it, without the lights (gfx.smoke.lighting off)
|
||||
};
|
||||
|
||||
// what a draw of a particle reads, as attributes of an instance
|
||||
struct particle_draw_s
|
||||
{
|
||||
vec4 place; // xyz: metres from the camera (world axes), w: size (smoke) or width (rain, snow)
|
||||
vec4 shape; // x: rotation (smoke); xyz: from the head of the streak to its tail (rain, snow)
|
||||
vec4 color; // rgb: lit colour, a: opacity
|
||||
};
|
||||
|
||||
// the indirect draw command of the particles; the compute passes count the instances
|
||||
layout(std430, binding = PARTICLE_COMMAND_SSBO_BINDING) buffer particle_command_ssbo
|
||||
{
|
||||
uint particle_draw_vertices;
|
||||
uint particle_draw_instances;
|
||||
uint particle_draw_first;
|
||||
uint particle_draw_base;
|
||||
};
|
||||
|
||||
layout(std430, binding = PARTICLE_OUTPUT_SSBO_BINDING) writeonly buffer particle_output_ssbo
|
||||
{
|
||||
particle_draw_s particle_draws[];
|
||||
};
|
||||
|
||||
// pcg hash
|
||||
uint particle_hash(uint value)
|
||||
{
|
||||
uint state = value * 747796405U + 2891336453U;
|
||||
uint word = ((state >> ((state >> 28U) + 4U)) ^ state) * 277803737U;
|
||||
return (word >> 22U) ^ word;
|
||||
}
|
||||
|
||||
// next of a sequence of random numbers 0-1
|
||||
float particle_random(inout uint state)
|
||||
{
|
||||
state = particle_hash(state);
|
||||
return float(state >> 8U) * (1.0 / 16777216.0);
|
||||
}
|
||||
|
||||
// whether a sphere (centre from the camera along the world axes) is within the planes of the view
|
||||
bool particle_visible(vec4 planes[5], vec3 centre, float radius)
|
||||
{
|
||||
for (int i = 0; i < 5; i++)
|
||||
if (dot(planes[i].xyz, centre) + planes[i].w < -radius)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// adds a particle to the draw; Capacity: the places of the storage of the draws
|
||||
void particle_emit(vec3 place, float size, vec3 shape, vec3 color, float opacity, uint capacity)
|
||||
{
|
||||
uint index = atomicAdd(particle_draw_instances, 1U);
|
||||
if (index >= capacity)
|
||||
{
|
||||
atomicAdd(particle_draw_instances, 0xFFFFFFFFU);
|
||||
return;
|
||||
}
|
||||
particle_draws[index].place = vec4(place, size);
|
||||
particle_draws[index].shape = vec4(shape, 0.0);
|
||||
particle_draws[index].color = vec4(color, opacity);
|
||||
}
|
||||
|
||||
#endif
|
||||
146
shaders/particle_light.glsl
Normal file
146
shaders/particle_light.glsl
Normal file
@@ -0,0 +1,146 @@
|
||||
// light of the particles of the gpu (opengl46_gpu_particles: smoke, rain, snow) at a point of the view space of the pass, as a
|
||||
// particle takes it as a whole, at its middle: the ambient light, the sun (the moon at night) held back by the shadow map and
|
||||
// by the clouds, and the lamps and headlights of the light cluster the point is in (the first lights of lights[] without the
|
||||
// clusters). each light is scattered towards the camera by the phase of the particle, Forward: 0 evenly, towards 1 mostly
|
||||
// onwards. the scale is the one of the shading of the surfaces (light_common.glsl): a white particle takes as much light as
|
||||
// a white face turned half way to it. for the compute passes; needs <common> and <weather_common.glsl>
|
||||
|
||||
#ifndef PARTICLE_LIGHT_GLSL
|
||||
#define PARTICLE_LIGHT_GLSL
|
||||
|
||||
#if SHADOWMAP_ENABLED
|
||||
uniform sampler2DArrayShadow shadowmap;
|
||||
#endif
|
||||
uniform sampler2D headlightmap;
|
||||
layout(binding = HEADLIGHT_SHADOW_TEX) uniform sampler2DArrayShadow headlight_shadowmap;
|
||||
|
||||
// AMBIENT_SCALE and SUN_DIFFUSE_SCALE of light_common.glsl
|
||||
const float PARTICLE_AMBIENT_SCALE = 0.65;
|
||||
const float PARTICLE_SUN_SCALE = 1.5;
|
||||
|
||||
// Henyey-Greenstein, 1 for every angle when G is 0
|
||||
float particle_phase(float cosangle, float g)
|
||||
{
|
||||
float g2 = g * g;
|
||||
return (1.0 - g2) / pow(max(1.0 + g2 - 2.0 * g * cosangle, 1e-4), 1.5);
|
||||
}
|
||||
|
||||
// share of the sunlight the shadow map holds back at a point of the view space, one comparison (as fog_inject.comp)
|
||||
float particle_sun_shadow(vec3 position)
|
||||
{
|
||||
#if SHADOWMAP_ENABLED
|
||||
float span_distance = dot(position, position);
|
||||
uint cascade;
|
||||
for (cascade = 0U; cascade < MAX_CASCADES; cascade++)
|
||||
if (span_distance <= cascade_end[cascade])
|
||||
break;
|
||||
for (; cascade < MAX_CASCADES; cascade++)
|
||||
{
|
||||
vec4 lightpos = lightview[cascade] * vec4(position, 1.0);
|
||||
vec3 coords = lightpos.xyz / lightpos.w;
|
||||
if (all(greaterThanEqual(coords.xy, vec2(0.0))) && all(lessThanEqual(coords.xy, vec2(1.0))))
|
||||
{
|
||||
if (coords.z < 0.0)
|
||||
return 0.0;
|
||||
return texture(shadowmap, vec4(coords.xy, float(cascade), coords.z + 0.0001 * float(cascade + 1U)));
|
||||
}
|
||||
// outside the map: lit, unless the cascades are fitted to the depth of the view (gfx.shadow.sdsm), whose maps cover only
|
||||
// the area of the surfaces seen; then the first further cascade whose map holds the point
|
||||
if (shadow_fit.x <= 0.0)
|
||||
return 0.0;
|
||||
}
|
||||
return 0.0;
|
||||
#else
|
||||
return 0.0;
|
||||
#endif
|
||||
}
|
||||
|
||||
// light of a lamp or of the beam of headlights reaching a point of the view space (as fog_inject.comp; the beams with their
|
||||
// shadow maps, the lamps without)
|
||||
vec3 particle_lamp(light_s light, vec3 position)
|
||||
{
|
||||
if (light.type == LIGHT_HEADLIGHTS)
|
||||
{
|
||||
vec4 projected = light.headlight_projection * vec4(position, 1.0);
|
||||
vec3 coords = projected.xyz / projected.w;
|
||||
if (coords.z < 0.0 || coords.z > 1.0 || any(lessThan(coords.xy, vec2(0.0))) || any(greaterThan(coords.xy, vec2(1.0))))
|
||||
return vec3(0.0);
|
||||
float span_distance = length(light.pos - position);
|
||||
float atten = 1.0 / (1.0 + light.linear * span_distance + light.quadratic * span_distance * span_distance);
|
||||
atten *= mix(1.0, 0.0, clamp((coords.z - 0.998) * 500.0, 0.0, 1.0));
|
||||
vec3 beam = textureLod(headlightmap, coords.xy, 1.0).rgb * light.headlight_weights.rgb;
|
||||
float intensity = max(max(beam.r, beam.g), beam.b);
|
||||
if ((light.headlight_weights.w > 0.5) && (intensity > 0.0))
|
||||
{
|
||||
uint map = uint(light.headlight_weights.w - 0.5);
|
||||
vec4 shadowpos = headlight_shadow[map] * vec4(position, 1.0);
|
||||
if (shadowpos.w > 0.0)
|
||||
{
|
||||
vec2 shadowcoords = shadowpos.xy / shadowpos.w;
|
||||
if (all(greaterThanEqual(shadowcoords, vec2(0.0))) && all(lessThanEqual(shadowcoords, vec2(1.0))))
|
||||
{
|
||||
float texel = headlight_shadow_params[map].x * shadowpos.w;
|
||||
float reference = shadowpos.z / max(shadowpos.w - texel * 1.5, 1e-3);
|
||||
intensity *= 1.0 - texture(headlight_shadowmap, vec4(shadowcoords, float(map), reference));
|
||||
}
|
||||
}
|
||||
}
|
||||
return light.color * (atten * intensity * light.intensity);
|
||||
}
|
||||
vec3 tolight = light.pos - position;
|
||||
float distance2 = dot(tolight, tolight);
|
||||
float edge = clamp(1.0 - distance2 * distance2 * (light.quadratic * light.quadratic), 0.0, 1.0);
|
||||
float atten = edge * edge / max(distance2, 0.01);
|
||||
if (light.type == LIGHT_SPOT)
|
||||
atten *= smoothstep(light.out_cutoff, light.in_cutoff, dot(-tolight * inversesqrt(max(distance2, 1e-8)), normalize(light.dir)));
|
||||
return light.color * (atten * light.intensity);
|
||||
}
|
||||
|
||||
// the light cluster of a point of the view space in front of the camera, as fragment_cluster() of light_common.glsl finds it for
|
||||
// the fragment drawn there
|
||||
uint particle_cluster(vec3 position)
|
||||
{
|
||||
vec4 clip = cluster_projection * vec4(position, 1.0);
|
||||
vec2 screen = clip.xy / clip.w * 0.5 + 0.5;
|
||||
ivec2 tile = clamp(ivec2(screen * vec2(CLUSTER_GRID_X, CLUSTER_GRID_Y)), ivec2(0), ivec2(CLUSTER_GRID_X - 1, CLUSTER_GRID_Y - 1));
|
||||
int slice = clamp(int(floor(cluster_slice_bias + log(max(clip.w, 1e-4)) * cluster_slice_scale)), 0, CLUSTER_GRID_Z - 1);
|
||||
return uint(tile.x + CLUSTER_GRID_X * (tile.y + CLUSTER_GRID_Y * slice));
|
||||
}
|
||||
|
||||
// Position: of the particle in the view space; World: its offset from the camera along the world axes
|
||||
vec3 particle_light(vec3 position, vec3 world, float forward)
|
||||
{
|
||||
vec3 tocamera = -normalize(position);
|
||||
vec3 light = ambient * PARTICLE_AMBIENT_SCALE;
|
||||
if (lights_count > 0U)
|
||||
{
|
||||
float sunlit = cloud_shadow(world) * (1.0 - particle_sun_shadow(position));
|
||||
if (sunlit > 0.0)
|
||||
light += lights[0].color * (0.5 * PARTICLE_SUN_SCALE * lights[0].intensity * sunlit * particle_phase(dot(normalize(lights[0].dir), tocamera), forward));
|
||||
}
|
||||
vec3 lamps = vec3(0.0);
|
||||
if (lights_clustered != 0U)
|
||||
{
|
||||
if ((cluster_valid != 0U) && (position.z < 0.0))
|
||||
{
|
||||
cluster_range_s range = cluster_ranges[particle_cluster(position)];
|
||||
for (uint i = 0U; i < range.count; i++)
|
||||
{
|
||||
light_s lamp = lights[cluster_light_indices[range.offset + i]];
|
||||
vec3 fromlamp = position - lamp.pos;
|
||||
lamps += particle_lamp(lamp, position) * particle_phase(dot(fromlamp, tocamera) * inversesqrt(max(dot(fromlamp, fromlamp), 1e-8)), forward);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (uint i = 1U; i < lights_count; i++)
|
||||
{
|
||||
vec3 fromlamp = position - lights[i].pos;
|
||||
lamps += particle_lamp(lights[i], position) * particle_phase(dot(fromlamp, tocamera) * inversesqrt(max(dot(fromlamp, fromlamp), 1e-8)), forward);
|
||||
}
|
||||
}
|
||||
return light + lamps * 0.5;
|
||||
}
|
||||
|
||||
#endif
|
||||
24
shaders/particle_soft.glsl
Normal file
24
shaders/particle_soft.glsl
Normal file
@@ -0,0 +1,24 @@
|
||||
// soft particles (opengl46_gpu_particles): a particle fades where it nears what is behind it, instead of being cut by it.
|
||||
// the resolved depth of the scene drawn so far (gl::SCENE_DEPTH_TEX), reversed with the far plane at infinity: the distance
|
||||
// along the axis of the view is the near plane over the depth, and the sky, which writes no depth, stays at 0
|
||||
|
||||
#ifndef PARTICLE_SOFT_GLSL
|
||||
#define PARTICLE_SOFT_GLSL
|
||||
|
||||
layout(binding = SCENE_DEPTH_TEX) uniform sampler2D particle_scene_depth;
|
||||
|
||||
// share of the particle left at the fragment, 0-1: Range (metres) is how far in front of the scene it fades out over, 0 for none.
|
||||
// Fragdistance: of the fragment along the axis of the view
|
||||
float particle_soft_fade(float range, float fragdistance)
|
||||
{
|
||||
if (range <= 0.0)
|
||||
return 1.0;
|
||||
float depth = texelFetch(particle_scene_depth, ivec2(gl_FragCoord.xy), 0).r;
|
||||
if (depth <= 0.0)
|
||||
return 1.0;
|
||||
float scene = projection[3][2] / depth;
|
||||
float share = clamp((scene - fragdistance) / range, 0.0, 1.0);
|
||||
return share * share * (3.0 - 2.0 * share);
|
||||
}
|
||||
|
||||
#endif
|
||||
75
shaders/particles_precipitation.comp
Normal file
75
shaders/particles_precipitation.comp
Normal file
@@ -0,0 +1,75 @@
|
||||
// rain and snow of the gpu (opengl46_gpu_particles), in every colour pass which draws them: a thread per drop or flake. the
|
||||
// particles fill three boxes around the camera, a small dense one and two larger ones, each repeated along the world axes:
|
||||
// a particle keeps its place in the box, moved by how far the wind took them all and how far it fell, so nothing is kept
|
||||
// from frame to frame. what the camera sees is lit (particle_light.glsl) and drawn as a streak from where it is to where
|
||||
// it was a moment ago, seen from the moving camera (particles_precipitation.vert)
|
||||
|
||||
#include <common>
|
||||
#include <weather_common.glsl>
|
||||
#include <particle_light.glsl>
|
||||
#include <particle_common.glsl>
|
||||
|
||||
layout(local_size_x = PARTICLE_GROUP_SIZE) in;
|
||||
|
||||
// x, y: first particles of the second and of the third box, z: particles, w: 1 for snow, 0 for rain
|
||||
layout(location = 0) uniform uvec4 counts;
|
||||
// xyz: how far the wind took the particles less where the camera is, in each box (metres, 0 to the size of the box); w: how
|
||||
// far they fell in it, 0 to 16 sizes of the box
|
||||
layout(location = 1) uniform vec4 offsets[3];
|
||||
// xyz: size of each box, metres; w unused
|
||||
layout(location = 4) uniform vec4 sizes;
|
||||
// xyz: velocity of the wind less the one of the camera, m/s; w: speed of the fall
|
||||
layout(location = 5) uniform vec4 velocity;
|
||||
// x: seconds (for the flutter of the flakes), y: how long a streak lasts, seconds, z: opacity, w: places for the draws
|
||||
layout(location = 6) uniform vec4 timing;
|
||||
// the world axes around the camera to the view space of the pass
|
||||
layout(location = 7) uniform mat4 view;
|
||||
// y: how much a particle scatters the light onwards, z: brightness
|
||||
layout(location = 11) uniform vec4 options;
|
||||
layout(location = 12) uniform vec4 planes[5];
|
||||
|
||||
void main()
|
||||
{
|
||||
uint index = gl_GlobalInvocationID.x;
|
||||
if (index >= counts.z)
|
||||
return;
|
||||
uint box = (index < counts.x) ? 0U : (index < counts.y) ? 1U : 2U;
|
||||
float size = sizes[box];
|
||||
bool snow = counts.w != 0U;
|
||||
|
||||
uint state = particle_hash(index * 3U + 0x9E3779B9U);
|
||||
vec3 home = vec3(particle_random(state), particle_random(state), particle_random(state)) * size;
|
||||
// the speed of the fall is (16 + k) / 16 of the common one, so a whole number of sizes of the box (16 of them) of the common
|
||||
// fall is a whole number of them for every particle, and the fall can be kept within 16 sizes
|
||||
float pace = (16.0 + floor(particle_random(state) * 7.0) - 3.0) / 16.0;
|
||||
float width = snow ? mix(0.004, 0.012, particle_random(state)) : mix(0.0012, 0.0025, particle_random(state));
|
||||
vec3 move = vec3(velocity.xyz) + vec3(0.0, -velocity.w * pace, 0.0);
|
||||
vec3 place = home + offsets[box].xyz - vec3(0.0, offsets[box].w * pace, 0.0);
|
||||
if (snow)
|
||||
{
|
||||
// the flakes flutter about their way
|
||||
float amplitude = mix(0.1, 0.35, particle_random(state));
|
||||
vec2 frequency = mix(vec2(1.5), vec2(5.0), vec2(particle_random(state), particle_random(state)));
|
||||
vec2 phase = vec2(particle_random(state), particle_random(state)) * 6.2831853;
|
||||
vec2 angle = frequency * timing.x + phase;
|
||||
place.xz += amplitude * sin(angle);
|
||||
move.xz += amplitude * frequency * cos(angle);
|
||||
}
|
||||
// within the box around the camera
|
||||
vec3 world = mod(place, size) - 0.5 * size;
|
||||
float opacity = timing.z;
|
||||
// fading at the walls of the box, where the particles wrap around, and right at the camera
|
||||
vec3 walls = abs(world) / (0.5 * size);
|
||||
opacity *= 1.0 - smoothstep(0.7, 1.0, max(max(walls.x, walls.y), walls.z));
|
||||
opacity *= smoothstep(0.15, 0.6, length(world));
|
||||
if (opacity <= 0.002)
|
||||
return;
|
||||
vec3 tail = -move * timing.y;
|
||||
float streak = length(tail);
|
||||
if (streak > 3.0)
|
||||
tail *= 3.0 / streak;
|
||||
if (false == particle_visible(planes, world + 0.5 * tail, 0.5 * length(tail) + width))
|
||||
return;
|
||||
vec3 color = particle_light((view * vec4(world, 1.0)).xyz, world, options.y);
|
||||
particle_emit(world, width, tail, color * options.z, opacity, uint(timing.w));
|
||||
}
|
||||
46
shaders/particles_precipitation.frag
Normal file
46
shaders/particles_precipitation.frag
Normal file
@@ -0,0 +1,46 @@
|
||||
// rain and snow of the gpu (opengl46_gpu_particles, particles_precipitation.vert): a streak rounded at its ends, fainter towards
|
||||
// its tail, in the colour the compute pass lit it with, faded in front of the scene, fogged
|
||||
|
||||
in vec4 f_pos;
|
||||
in vec4 f_color;
|
||||
in vec4 f_streak;
|
||||
in float f_soft;
|
||||
|
||||
in vec4 f_clip_pos;
|
||||
in vec4 f_clip_future_pos;
|
||||
|
||||
#include <common>
|
||||
#include <apply_fog.glsl>
|
||||
#include <tonemapping.glsl>
|
||||
#include <particle_soft.glsl>
|
||||
|
||||
layout(location = 0) out vec4 out_color;
|
||||
#if MOTIONBLUR_ENABLED
|
||||
layout(location = 1) out vec4 out_motion;
|
||||
#endif
|
||||
|
||||
void main()
|
||||
{
|
||||
// distance from the axis of the streak, in its radii
|
||||
float along = clamp(f_streak.y, 0.0, f_streak.w);
|
||||
float offaxis = length(vec2(f_streak.x, f_streak.y - along)) / max(f_streak.z, 1e-6);
|
||||
float alpha = f_color.a * (1.0 - smoothstep(0.35, 1.0, offaxis));
|
||||
alpha *= mix(1.0, 0.4, along / max(f_streak.w, 1e-6));
|
||||
alpha *= particle_soft_fade(f_soft, -f_pos.z);
|
||||
if (alpha <= 0.0)
|
||||
discard;
|
||||
vec4 color = vec4(apply_fog(f_color.rgb), alpha);
|
||||
#if POSTFX_ENABLED
|
||||
out_color = color;
|
||||
#else
|
||||
out_color = tonemap(color);
|
||||
#endif
|
||||
#if MOTIONBLUR_ENABLED
|
||||
{
|
||||
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;
|
||||
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;
|
||||
|
||||
out_motion = vec4(a - b, 0.0f, alpha * alpha_mult);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
58
shaders/particles_precipitation.vert
Normal file
58
shaders/particles_precipitation.vert
Normal file
@@ -0,0 +1,58 @@
|
||||
// rain and snow of the gpu (opengl46_gpu_particles): a streak per drop or flake the compute pass added to the draw, from where
|
||||
// it is to where it was a moment ago as the moving camera saw it, turned to face the camera, rounded at its ends. a particle
|
||||
// thinner than a pixel and a half is drawn that wide and as much fainter, so the distant ones don't flicker
|
||||
|
||||
layout(location = 0) in vec4 v_place; // xyz: the head, metres from the camera (world axes), w: width
|
||||
layout(location = 1) in vec4 v_shape; // xyz: from the head to the tail
|
||||
layout(location = 2) in vec4 v_color; // rgb: lit colour, a: opacity
|
||||
|
||||
out vec4 f_pos;
|
||||
out vec4 f_color;
|
||||
// x: across the streak, y: along it from the head, metres; z: its radius, w: its length
|
||||
out vec4 f_streak;
|
||||
out float f_soft;
|
||||
|
||||
out vec4 f_clip_pos;
|
||||
out vec4 f_clip_future_pos;
|
||||
|
||||
#include <common>
|
||||
|
||||
// x: metres it fades out over in front of the scene, 0: none
|
||||
layout(location = 0) uniform vec4 particle_soft;
|
||||
// x: width of a pixel one metre in front of the camera, metres
|
||||
layout(location = 1) uniform vec4 particle_pixel;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec3 head = (modelview * vec4(v_place.xyz, 1.0)).xyz;
|
||||
vec3 axis = mat3(modelview) * v_shape.xyz;
|
||||
float span = length(axis);
|
||||
vec3 direction = (span > 1e-5) ? axis / span : vec3(0.0, -1.0, 0.0);
|
||||
vec3 middle = head + 0.5 * axis;
|
||||
|
||||
float width = v_place.w;
|
||||
float alpha = v_color.a;
|
||||
float least = 1.5 * particle_pixel.x * max(-middle.z, 1e-3);
|
||||
if (width < least)
|
||||
{
|
||||
alpha *= width / least;
|
||||
width = least;
|
||||
}
|
||||
float radius = 0.5 * width;
|
||||
vec3 side = cross(direction, normalize(-middle));
|
||||
float sidelength = length(side);
|
||||
side = (sidelength > 1e-4) ? side / sidelength : vec3(1.0, 0.0, 0.0);
|
||||
|
||||
// a triangle strip of four corners: across, along
|
||||
vec2 corner = vec2(float(gl_VertexID & 1), float(gl_VertexID >> 1));
|
||||
float across = (corner.x * 2.0 - 1.0) * radius;
|
||||
float along = mix(-radius, span + radius, corner.y);
|
||||
f_pos = vec4(head + direction * along + side * across, 1.0);
|
||||
f_clip_pos = projection * f_pos;
|
||||
f_clip_future_pos = (projection * future) * f_pos;
|
||||
|
||||
gl_Position = f_clip_pos;
|
||||
f_streak = vec4(across, along, radius, span);
|
||||
f_color = vec4(v_color.rgb, alpha);
|
||||
f_soft = particle_soft.x;
|
||||
}
|
||||
42
shaders/particles_smoke.frag
Normal file
42
shaders/particles_smoke.frag
Normal file
@@ -0,0 +1,42 @@
|
||||
// the smoke of the gpu (opengl46_gpu_particles, particles_smoke.vert): the texture of the smoke in the colour the compute pass
|
||||
// lit the particle with, faded in front of the scene, fogged
|
||||
|
||||
in vec4 f_color;
|
||||
in vec2 f_coord;
|
||||
in vec4 f_pos;
|
||||
in float f_soft;
|
||||
|
||||
in vec4 f_clip_pos;
|
||||
in vec4 f_clip_future_pos;
|
||||
|
||||
#texture (tex1, 0, sRGB_A)
|
||||
uniform sampler2D tex1;
|
||||
|
||||
#include <common>
|
||||
#include <apply_fog.glsl>
|
||||
#include <tonemapping.glsl>
|
||||
#include <particle_soft.glsl>
|
||||
|
||||
layout(location = 0) out vec4 out_color;
|
||||
#if MOTIONBLUR_ENABLED
|
||||
layout(location = 1) out vec4 out_motion;
|
||||
#endif
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 tex_color = texture(tex1, f_coord) * f_color;
|
||||
tex_color.a *= particle_soft_fade(f_soft, -f_pos.z);
|
||||
#if POSTFX_ENABLED
|
||||
out_color = vec4(apply_fog(tex_color.rgb), tex_color.a);
|
||||
#else
|
||||
out_color = tonemap(vec4(apply_fog(tex_color.rgb), tex_color.a));
|
||||
#endif
|
||||
#if MOTIONBLUR_ENABLED
|
||||
{
|
||||
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;
|
||||
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;
|
||||
|
||||
out_motion = vec4(a - b, 0.0f, tex_color.a * alpha_mult);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
37
shaders/particles_smoke.vert
Normal file
37
shaders/particles_smoke.vert
Normal file
@@ -0,0 +1,37 @@
|
||||
// the smoke of the gpu (opengl46_gpu_particles): a billboard per particle the compute pass added to the draw, turned about the
|
||||
// axis of the view by the rotation of the particle (as opengl46_particles builds them on the cpu)
|
||||
|
||||
layout(location = 0) in vec4 v_place; // xyz: metres from the camera (world axes), w: size
|
||||
layout(location = 1) in vec4 v_shape; // x: rotation, radians
|
||||
layout(location = 2) in vec4 v_color; // rgb: lit colour, a: opacity
|
||||
|
||||
out vec4 f_pos;
|
||||
out vec4 f_color;
|
||||
out vec2 f_coord;
|
||||
// metres in front of the scene the particle fades out over, see particle_soft.glsl
|
||||
out float f_soft;
|
||||
|
||||
out vec4 f_clip_pos;
|
||||
out vec4 f_clip_future_pos;
|
||||
|
||||
#include <common>
|
||||
|
||||
// x: share of the size of the particle it fades out over in front of the scene (gfx.particles.soft), 0: none
|
||||
layout(location = 0) uniform vec4 particle_soft;
|
||||
|
||||
void main()
|
||||
{
|
||||
// a triangle strip of four corners
|
||||
vec2 corner = vec2(float(gl_VertexID & 1), float(gl_VertexID >> 1)) - 0.5;
|
||||
float c = cos(v_shape.x);
|
||||
float s = sin(v_shape.x);
|
||||
vec2 offset = vec2(c * corner.x - s * corner.y, s * corner.x + c * corner.y) * v_place.w;
|
||||
f_pos = modelview * vec4(v_place.xyz, 1.0) + vec4(offset, 0.0, 0.0);
|
||||
f_clip_pos = projection * f_pos;
|
||||
f_clip_future_pos = (projection * future) * f_pos;
|
||||
|
||||
gl_Position = f_clip_pos;
|
||||
f_coord = corner + 0.5;
|
||||
f_color = v_color;
|
||||
f_soft = particle_soft.x * 0.5 * v_place.w;
|
||||
}
|
||||
52
shaders/particles_smoke_prepare.comp
Normal file
52
shaders/particles_smoke_prepare.comp
Normal file
@@ -0,0 +1,52 @@
|
||||
// the smoke of the gpu (opengl46_gpu_particles), in every colour pass which draws it: a thread per place of the storage. the
|
||||
// living particles the view sees are lit (particle_light.glsl) and added to the draw (particles_smoke.vert)
|
||||
|
||||
#include <common>
|
||||
#include <weather_common.glsl>
|
||||
#include <particle_light.glsl>
|
||||
#include <particle_common.glsl>
|
||||
|
||||
layout(local_size_x = PARTICLE_GROUP_SIZE) in;
|
||||
|
||||
layout(std430, binding = PARTICLE_SSBO_BINDING) readonly buffer particle_ssbo
|
||||
{
|
||||
smoke_particle_s particles[];
|
||||
};
|
||||
layout(std430, binding = PARTICLE_SOURCE_SSBO_BINDING) readonly buffer particle_source_ssbo
|
||||
{
|
||||
smoke_source_s sources[];
|
||||
};
|
||||
layout(std430, binding = PARTICLE_OWNER_SSBO_BINDING) readonly buffer particle_owner_ssbo
|
||||
{
|
||||
uint owners[];
|
||||
};
|
||||
|
||||
layout(location = 0) uniform uint places;
|
||||
// xyz: the origin of the particles less the camera, metres along the world axes
|
||||
layout(location = 1) uniform vec3 origin;
|
||||
// the world axes around the camera to the view space of the pass
|
||||
layout(location = 2) uniform mat4 view;
|
||||
// x: 1 when the smoke is lit by the lights of the pass, y: how much it scatters the light onwards, z: places for the draws
|
||||
layout(location = 3) uniform vec4 options;
|
||||
// planes of the view (world axes around the camera): a point is inside when dot(xyz, point) + w >= 0
|
||||
layout(location = 4) uniform vec4 planes[5];
|
||||
|
||||
void main()
|
||||
{
|
||||
uint place = gl_GlobalInvocationID.x;
|
||||
if (place >= places)
|
||||
return;
|
||||
smoke_particle_s particle = particles[place];
|
||||
if (particle.state.x <= 0.0)
|
||||
return;
|
||||
vec3 world = particle.position.xyz + origin;
|
||||
float size = particle.velocity.w;
|
||||
// the corners of the billboard turned anyhow
|
||||
if (false == particle_visible(planes, world, size * 0.7072))
|
||||
return;
|
||||
smoke_source_s source = sources[owners[place]];
|
||||
vec3 color = source.oldcolor.rgb;
|
||||
if (options.x > 0.5)
|
||||
color = source.color.rgb * particle_light((view * vec4(world, 1.0)).xyz, world, options.y);
|
||||
particle_emit(world, size, vec3(particle.position.w, 0.0, 0.0), color, clamp(particle.state.x, 0.0, 1.0), uint(options.z));
|
||||
}
|
||||
84
shaders/particles_smoke_simulate.comp
Normal file
84
shaders/particles_smoke_simulate.comp
Normal file
@@ -0,0 +1,84 @@
|
||||
// the smoke of the gpu (opengl46_gpu_particles), once per update of the simulation: a thread per place of the storage. the
|
||||
// particles the sources had born in the update take the places after the ones of the last update (each source keeps its
|
||||
// places as a ring, the oldest give way), spread along the way the emitter went since the update before; the living ones
|
||||
// change as smoke_source::update() changes them on the cpu
|
||||
|
||||
#include <common>
|
||||
#include <particle_common.glsl>
|
||||
|
||||
layout(local_size_x = PARTICLE_GROUP_SIZE) in;
|
||||
|
||||
layout(std430, binding = PARTICLE_SSBO_BINDING) buffer particle_ssbo
|
||||
{
|
||||
smoke_particle_s particles[];
|
||||
};
|
||||
layout(std430, binding = PARTICLE_SOURCE_SSBO_BINDING) readonly buffer particle_source_ssbo
|
||||
{
|
||||
smoke_source_s sources[];
|
||||
};
|
||||
// the source of every place
|
||||
layout(std430, binding = PARTICLE_OWNER_SSBO_BINDING) readonly buffer particle_owner_ssbo
|
||||
{
|
||||
uint owners[];
|
||||
};
|
||||
|
||||
layout(location = 0) uniform uint places;
|
||||
// x: seconds of the update, yzw: added to the positions, as the origin moved
|
||||
layout(location = 1) uniform vec4 step;
|
||||
// xyz: the wind, m/s; w unused
|
||||
layout(location = 2) uniform vec4 wind;
|
||||
// x: number of the update, for the random numbers
|
||||
layout(location = 3) uniform uint seed;
|
||||
|
||||
// smoke_source::update( Particle ); false when the particle is dead
|
||||
bool advance(inout smoke_particle_s particle, smoke_source_s source, float seconds)
|
||||
{
|
||||
particle.state.x = clamp(particle.state.x + source.opacitychange.x * seconds, source.opacitychange.y, source.opacitychange.z);
|
||||
if (particle.state.x <= 0.0)
|
||||
return false;
|
||||
particle.velocity.w = clamp(particle.velocity.w + source.sizechange.x * seconds, source.sizechange.y, source.sizechange.z);
|
||||
// crude smoke dispersion
|
||||
particle.velocity.y += source.motion.x * particle.velocity.y * seconds;
|
||||
particle.velocity.y -= source.motion.y * particle.velocity.y * seconds;
|
||||
particle.velocity.y = max(source.motion.z, particle.velocity.y);
|
||||
particle.position.xyz += particle.velocity.xyz * seconds;
|
||||
particle.position.xyz += 0.1 * particle.state.y * wind.xyz * seconds;
|
||||
particle.state.y += seconds;
|
||||
return true;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
uint place = gl_GlobalInvocationID.x;
|
||||
if (place >= places)
|
||||
return;
|
||||
smoke_source_s source = sources[owners[place]];
|
||||
smoke_particle_s particle = particles[place];
|
||||
particle.position.xyz += step.yzw;
|
||||
|
||||
uint size = source.pool.y;
|
||||
uint order = (place - source.pool.x + size - source.pool.z) % size;
|
||||
if (order < source.pool.w)
|
||||
{
|
||||
// born in the update: smoke_source::particle_emitter::initialize()
|
||||
uint state = particle_hash(place ^ particle_hash(seed));
|
||||
float polar = mix(source.launch.x, source.launch.y, particle_random(state));
|
||||
float azimuth = radians(mix(-180.0, 180.0, particle_random(state)));
|
||||
vec3 direction = vec3(-sin(polar) * sin(azimuth), cos(polar), sin(polar) * cos(azimuth));
|
||||
particle.velocity.xyz = direction * mix(source.launch.z, source.launch.w, particle_random(state));
|
||||
particle.position.w = radians(mix(0.0, 360.0, particle_random(state)));
|
||||
particle.velocity.w = mix(source.initial.x, source.initial.y, particle_random(state));
|
||||
particle.state = vec4(mix(source.initial.z, source.initial.w, particle_random(state)), 0.0, 0.0, 0.0);
|
||||
// the births spread over the update: where the emitter was then, and the time since
|
||||
float share = (float(order) + 0.5) / float(source.pool.w);
|
||||
particle.position.xyz = mix(source.previous.xyz, source.location.xyz, share);
|
||||
if (false == advance(particle, source, step.x * (1.0 - share)))
|
||||
particle.state.x = 0.0;
|
||||
}
|
||||
else if (particle.state.x > 0.0)
|
||||
{
|
||||
if (false == advance(particle, source, step.x))
|
||||
particle.state.x = 0.0;
|
||||
}
|
||||
particles[place] = particle;
|
||||
}
|
||||
Reference in New Issue
Block a user