diff --git a/src/render/program/raster_particle_program.ts b/src/render/program/raster_particle_program.ts index 2e1e0680edc..b665af32851 100644 --- a/src/render/program/raster_particle_program.ts +++ b/src/render/program/raster_particle_program.ts @@ -10,7 +10,7 @@ import { import type Context from '../../gl/context'; import type {UniformValues} from '../uniform_binding'; -export const RASTER_PARTICLE_POS_OFFSET: number = 0.15; +export const RASTER_PARTICLE_POS_OFFSET: number = 0.05; export const RASTER_PARTICLE_POS_SCALE: number = 1.0 + 2.0 * RASTER_PARTICLE_POS_OFFSET; export type RasterParticleUniformsType = { diff --git a/src/shaders/_prelude.fragment.glsl b/src/shaders/_prelude.fragment.glsl index 7af592683fe..7405cd8bf28 100644 --- a/src/shaders/_prelude.fragment.glsl +++ b/src/shaders/_prelude.fragment.glsl @@ -54,18 +54,18 @@ in float v_cutoff_opacity; #endif // This function should be used in cases where mipmap usage is expected and -// the sampling coordinates are not continous. The lod_parameter should be -// a continous function derived from the sampling coordinates. +// the sampling coordinates are not continous. The lod_parameter should be +// a continous function derived from the sampling coordinates. vec4 textureLodCustom(sampler2D image, vec2 pos, vec2 lod_coord) { vec2 size = vec2(textureSize(image, 0)); vec2 dx = dFdx(lod_coord.xy * size); vec2 dy = dFdy(lod_coord.xy * size); float delta_max_sqr = max(dot(dx, dx), dot(dy, dy)); - float lod = 0.5 * log2(delta_max_sqr); + float lod = 0.5 * log2(delta_max_sqr); // Note: textureLod doesn't support anisotropic filtering - // We could use textureGrad instead which supports it, but it's discouraged + // We could use textureGrad instead which supports it, but it's discouraged // in the ARM Developer docs: - // "Do not use textureGrad() unless absolutely necessary. + // "Do not use textureGrad() unless absolutely necessary. // It is much slower that texture() and textureLod()..." // https://developer.arm.com/documentation/101897/0301/Buffers-and-textures/Texture-sampling-performance return textureLod(image, pos, lod); @@ -80,4 +80,4 @@ vec4 applyLUT(highp sampler3D lut, vec4 col) { vec3 applyLUT(highp sampler3D lut, vec3 col) { return applyLUT(lut, vec4(col, 1.0)).rgb; -} \ No newline at end of file +} diff --git a/src/shaders/line.fragment.glsl b/src/shaders/line.fragment.glsl index 0e7f721ac47..833d1ed7d0d 100644 --- a/src/shaders/line.fragment.glsl +++ b/src/shaders/line.fragment.glsl @@ -107,7 +107,7 @@ void main() { float alpha2 = clamp(min(dist - (v_width2.t - edgeBlur), v_width2.s - dist) / edgeBlur, 0.0, 1.0); if (alpha2 < 1.) { float smoothAlpha = smoothstep(0.6, 1.0, alpha2); - if (border_color.a == 0.0) { + if (border_color.a == 0.0) { float Y = (out_color.a > 0.01) ? luminance(out_color.rgb / out_color.a) : 1.; // out_color is premultiplied float adjustment = (Y > 0.) ? 0.5 / Y : 0.45; if (out_color.a > 0.25 && Y < 0.25) { diff --git a/src/shaders/raster_particle_draw.vertex.glsl b/src/shaders/raster_particle_draw.vertex.glsl index b6798c63c48..56e9eacae52 100644 --- a/src/shaders/raster_particle_draw.vertex.glsl +++ b/src/shaders/raster_particle_draw.vertex.glsl @@ -21,7 +21,7 @@ void main() { gl_Position = AWAY; v_particle_speed = 0.0; } else { - gl_Position = vec4(2.0 * pos - vec2(1.0), 0.0, 1.0); + gl_Position = vec4(2.0 * pos - 1.0, 0, 1); v_particle_speed = length(velocity); } gl_PointSize = 1.0; diff --git a/src/shaders/raster_particle_update.fragment.glsl b/src/shaders/raster_particle_update.fragment.glsl index 65895543fda..240d64fcce5 100644 --- a/src/shaders/raster_particle_update.fragment.glsl +++ b/src/shaders/raster_particle_update.fragment.glsl @@ -8,6 +8,10 @@ uniform highp float u_rand_seed; in highp vec2 v_tex_coord; +vec2 linearstep(vec2 edge0, vec2 edge1, vec2 x) { + return clamp((x - edge0) / (edge1 - edge0), vec2(0), vec2(1)); +} + // pseudo-random generator const highp vec3 rand_constants = vec3(12.9898, 78.233, 4375.85453); highp float rand(const highp vec2 co) { @@ -25,14 +29,29 @@ void main() { highp vec2 seed = (pos + v_tex_coord) * u_rand_seed; highp vec2 random_pos = vec2(rand(seed + 1.3), rand(seed + 2.1)); - highp float speed = velocity == INVALID_VELOCITY ? 0.0 : length(velocity); - highp float reset_rate_bump = speed * u_reset_rate; - highp vec2 particle_pos_min = -u_particle_pos_offset; - highp vec2 particle_pos_max = vec2(1.0) + u_particle_pos_offset; - // drop rate 0: (min pos) < x < (max pos), else drop rate 1 - highp vec2 pos_drop_rate = vec2(1.0) - step(particle_pos_min, pos) + step(particle_pos_max, pos); - highp float drop_rate = max(u_reset_rate + reset_rate_bump, length(pos_drop_rate)); - highp float drop = step(1.0 - drop_rate, rand(seed)); + + // An ad hoc mask that's 1 inside the tile and ramps to zero outside the + // boundary. The constant power of 4 is tuned to cause particles to traverse + // roughly the width of the boundary before dropping. + highp vec2 persist_rate = pow( + linearstep(vec2(-u_particle_pos_offset), vec2(0), pos) * + linearstep(vec2(1.0 + u_particle_pos_offset), vec2(1), pos), + vec2(4) + ); + + // Raise the persist rate to the inverse power of the number of steps + // taken to traverse the boundary. This yields a per-frame persist + // rate which gives the overall chance of dropping by the time it + // traverses the entire boundary buffer. + highp vec2 per_frame_persist = pow(persist_rate, abs(dp) / u_particle_pos_offset); + + // Combine drop probability wrt x-boundary and y-boundary into a single drop rate + highp float drop_rate = 1.0 - per_frame_persist.x * per_frame_persist.y; + + // Apply a hard drop cutoff outside the boundary of what we encode + drop_rate = any(greaterThanEqual(abs(pos - 0.5), vec2(0.5 + u_particle_pos_offset))) ? 1.0 : drop_rate; + + highp float drop = step(1.0 - drop_rate - u_reset_rate, rand(seed)); highp vec2 next_pos = mix(pos, random_pos, drop); glFragColor = pack_pos_to_rgba(next_pos); diff --git a/test/integration/render-tests/raster-particle/decoding/expected.png b/test/integration/render-tests/raster-particle/decoding/expected.png index b802323c07e..a8da303048f 100644 Binary files a/test/integration/render-tests/raster-particle/decoding/expected.png and b/test/integration/render-tests/raster-particle/decoding/expected.png differ diff --git a/test/integration/render-tests/raster-particle/decoding/style.json b/test/integration/render-tests/raster-particle/decoding/style.json index fe373acd3a0..3a7e61f3f64 100644 --- a/test/integration/render-tests/raster-particle/decoding/style.json +++ b/test/integration/render-tests/raster-particle/decoding/style.json @@ -4,7 +4,7 @@ "test": { "width": 256, "height": 256, - "allowed": 0.005, + "allowed": 0.006, "operations": [ ["wait", 100], ["wait", 100], diff --git a/test/integration/render-tests/raster-particle/default/expected.png b/test/integration/render-tests/raster-particle/default/expected.png index 52b5f45312c..7ad9ef914dc 100644 Binary files a/test/integration/render-tests/raster-particle/default/expected.png and b/test/integration/render-tests/raster-particle/default/expected.png differ diff --git a/test/integration/render-tests/raster-particle/raster-particle-array-band/expected.png b/test/integration/render-tests/raster-particle/raster-particle-array-band/expected.png index aa0977748b3..8e4d97f165d 100644 Binary files a/test/integration/render-tests/raster-particle/raster-particle-array-band/expected.png and b/test/integration/render-tests/raster-particle/raster-particle-array-band/expected.png differ