Files
MapLibreParticleLab/shaders/particle_gpu_validation.vertex.glsl
tei f49ffa68a1
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
fix(step097): complete A2 clock and GPU field correctness
2026-09-26 15:17:43 +09:00

108 lines
4.3 KiB
GLSL

layout (location = 0) in vec2 a_pos;
out vec2 v_uv;
out vec2 v_world;
out vec2 v_particle_velocity;
out float v_particle_valid;
layout (std140) uniform ParticleGpuValidationUBO {
highp vec4 u_pass;
highp vec4 u_marker;
highp vec4 u_mask;
highp vec4 u_visual;
highp vec4 u_field_bounds;
highp vec4 u_respawn_bounds;
highp vec4 u_field_scale;
highp vec4 u_advection;
highp vec4 u_particle_color;
highp vec4 u_trail_color;
highp vec4 u_weather_stops[8];
highp vec4 u_weather_colors[8];
highp vec4 u_weather_ramp;
highp mat4 u_matrix;
};
uniform sampler2D u_state_texture;
uniform sampler2D u_previous;
uniform highp sampler2D u_field_texture;
uniform highp sampler2D u_next_field_texture;
vec2 fieldUv(vec2 world) {
return vec2((world.x - u_field_bounds.x) / (u_field_bounds.z - u_field_bounds.x),
(world.y - u_field_bounds.y) / (u_field_bounds.w - u_field_bounds.y));
}
bool validFieldUv(vec2 uv) {
return all(greaterThanEqual(uv, vec2(0.0))) && all(lessThanEqual(uv, vec2(1.0)));
}
bool resolveAdvection(vec4 field0, vec4 field1, out vec2 advection) {
vec2 mixed = mix(((floor(field0.rg * 255.0 + 0.5) - 128.0) / 127.0) * u_field_scale.x,
((floor(field1.rg * 255.0 + 0.5) - 128.0) / 127.0) * u_field_scale.y,
u_marker.x);
if (any(isnan(mixed)) || any(greaterThan(abs(mixed), vec2(3.4e38)))) return false;
if (u_advection.x > 0.5) {
float directionLength = length(mixed);
if (isnan(directionLength) || directionLength > 3.4e38 || directionLength <= u_advection.z) return false;
advection = mixed / directionLength * u_advection.y;
} else {
// VELOCITY remains the established component-wise m/s path.
advection = mixed;
}
return true;
}
void main() {
v_uv = a_pos;
v_world = a_pos;
v_particle_velocity = vec2(0.0);
v_particle_valid = 1.0;
if (u_pass.x > 4.5) {
gl_Position = u_matrix * vec4(a_pos * 8192.0, 0.0, 1.0);
} else if (u_pass.x < 1.5) {
gl_Position = vec4(a_pos * 2.0 - 1.0, 0.0, 1.0);
} else if (u_pass.x > 3.5) {
// a_pos.x packs State texel X plus one endpoint. The formal particle
// path draws the StateRead -> StateWrite displacement in current map
// clip space; it does not synthesize a fixed world-length streak.
float endpoint = mod(a_pos.x, 2.0) * 2.0 - 1.0;
vec2 stateIndex = vec2(floor(a_pos.x * 0.5), a_pos.y);
vec2 stateUV = (stateIndex + vec2(0.5)) / u_mask.xy;
vec4 previousState = texture(u_previous, stateUV);
vec4 currentState = texture(u_state_texture, stateUV);
vec2 currentPosition = currentState.rg;
vec2 uv = fieldUv(currentPosition);
vec4 field0 = texture(u_field_texture, uv);
vec4 field1 = texture(u_next_field_texture, uv);
vec2 advection;
bool valid = validFieldUv(uv) && field0.a >= 0.5 && field1.a >= 0.5 &&
resolveAdvection(field0, field1, advection);
vec2 mercatorVelocity = vec2(advection.x, -advection.y);
float speed = length(mercatorVelocity);
vec2 segmentPosition = endpoint < 0.0 ? previousState.rg : currentPosition;
v_uv = stateUV;
v_world = currentPosition;
v_particle_velocity = mercatorVelocity;
// A zero alpha is written by State on a respawn frame. This suppresses
// oldPosition -> newRandomPosition cross-screen segments.
v_particle_valid = valid && speed > 0.00001 && currentState.a > 0.5 &&
distance(previousState.rg, currentPosition) > 0.000000001
? 1.0
: 0.0;
if (v_particle_valid < 0.5) {
gl_Position = vec4(-2.0, -2.0, 0.0, 1.0);
} else {
gl_Position = u_matrix * vec4(segmentPosition * 8192.0, 0.0, 1.0);
}
} else {
if (u_mask.z > 0.5) {
// Trail targets are viewport-sized screen-space history buffers.
gl_Position = vec4(a_pos * 2.0 - 1.0, 0.0, 1.0);
} else {
// VALIDATION ONLY: a_pos is canonical z0 world geometry; u_matrix
// is PaintParameters::matrixForTile() for the selected world copy.
gl_Position = u_matrix * vec4(a_pos * 8192.0, 0.0, 1.0);
}
}
}