// VERTEX_SHADER uniform vec4 u_points; _NOPERSPECTIVE_ _OUT_ vec4 info; void main() { gl_Position = u_projection * (u_modelview * vec4(aPosition, 0.0, 1.0)); vec2 mv0 = u_modelview[0].xy; vec2 mv1 = u_modelview[1].xy; vec2 offset = aPosition - u_points.xy; vec2 coord = vec2(dot(mv0, offset), dot(mv1, offset)); // Original equation: // VS | maxDist = length(end - start); // VS | gradient = end - start; // VS | gradientLength = length(gradient); // FS | pos = frag_coord - start // FS | proj = (dot(gradient, pos) / (gradientLength * gradientLength)) * gradient // FS | offset = length(proj) / maxDist // Simplified formula derivation: // 1. Notice that maxDist = gradientLength: // offset = length(proj) / gradientLength // 2. Let gnorm = gradient / gradientLength, then: // proj = (dot(gnorm * gradientLength, pos) / (gradientLength * gradientLength)) * (gnorm * gradientLength) = // = dot(gnorm, pos) * gnorm // 3. Since gnorm is unit length then: // length(proj) = length(dot(gnorm, pos) * gnorm) = dot(gnorm, pos) // 4. We can avoid the FS division by passing a scaled pos from the VS: // offset = dot(gnorm, pos) / gradientLength = dot(gnorm, pos / gradientLength) // 5. 1.0 / length(gradient) is inversesqrt(dot(gradient, gradient)) in GLSL vec2 gradient = vec2(dot(mv0, u_points.zw), dot(mv1, u_points.zw)); float rcp_gradient_length = inversesqrt(dot(gradient, gradient)); info = rcp_gradient_length * vec4(coord, gradient); } // FRAGMENT_SHADER: #ifdef GSK_LEGACY uniform int u_num_color_stops; #else uniform highp int u_num_color_stops; // Why? Because it works like this. #endif uniform float u_color_stops[6 * 5]; uniform bool u_repeat; _NOPERSPECTIVE_ _IN_ vec4 info; float get_offset(int index) { return u_color_stops[5 * index]; } vec4 get_color(int index) { int base = 5 * index + 1; return vec4(u_color_stops[base], u_color_stops[base + 1], u_color_stops[base + 2], u_color_stops[base + 3]); } void main() { float offset = dot(info.xy, info.zw); if (u_repeat) { offset = fract(offset); } if (offset < get_offset(0)) { gskSetOutputColor(gsk_scaled_premultiply(get_color(0), u_alpha)); return; } int n = u_num_color_stops - 1; for (int i = 0; i < n; i++) { float curr_offset = get_offset(i); float next_offset = get_offset(i + 1); if (offset >= curr_offset && offset < next_offset) { float f = (offset - curr_offset) / (next_offset - curr_offset); vec4 curr_color = gsk_premultiply(get_color(i)); vec4 next_color = gsk_premultiply(get_color(i + 1)); vec4 color = mix(curr_color, next_color, f); gskSetOutputColor(color * u_alpha); return; } } gskSetOutputColor(gsk_scaled_premultiply(get_color(n), u_alpha)); }