blob: f1bddcbd2cc0cbe5cc4c6231d72d60b48ecc5214 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This algorithm is an adaptation of the RRect blur technique
// (`rrect_blur.frag`), tailored specifically for the RSuperellipse shape. The
// core distinction lies in how RSuperellipse curves are slightly drawn inward
// compared to RRect's.
//
// We begin by mapping the fragment's position to polar coordinates within the
// octant, with the angle `theta` ranging from 0 to pi/4. From this angle, we
// calculate a `baseRetraction`. This `baseRetraction` is crucial because it
// ensures the shape precisely matches an RSuperellipse when the blur `sigma` is
// very small.
//
// As we move further away from the edge (indicated by `d`, the radial
// distance), this retraction is progressively scaled down by `retractionDepth`.
// This scaling diminishes the influence of the retraction when the blur is
// significant, reflecting the idea that for larger blur values, subtle
// geometric differences like this retraction become visually insignificant.
//
// Essentially, the `baseRetraction` represents the exact distance between the
// RRect and RSuperellipse curves at a given `theta`.
//
// We split the angular range at `splitRadian`. This is a critical point because
// the RRect's geometry, and thus its formula, changes significantly: one side
// is a straight edge, the other a rounded corner. Our retraction calculation
// must adapt to these distinct behaviors.
//
// When `theta` is less than `splitRadian`, the RRect's edge is a straight line,
// and the RSuperellipse follows a well-defined superellipse formula. This
// allows us to directly compute the distance between the two curves.
//
// However, for `theta` greater than `splitRadian`, the exact mathematical
// expressions for both curves become too complex to evaluate efficiently. In
// these cases, we approximate the distance using a heuristic polynomial fit.
// This approximation is based on the observed behavior of the difference curve:
// it smoothly rises, then falls, eventually reaching zero with a zero slope.
//
// To see a visual representation of how this algorithm affects the shadow,
// refer to the `RoundSuperellipseShadowComparison` playground.
//
// (Note that the `theta` used throughout this file is distinct from the `theta`
// variable used in `RoundSuperellipseParam`.)
precision highp float;
#include <impeller/gaussian.glsl>
#include <impeller/math.glsl>
#include <impeller/rrect.glsl>
#include <impeller/types.glsl>
uniform FragInfo {
vec4 color;
vec4 center_adjust;
vec3 r1_exponent_exponentInv;
vec3 sInv_minEdge_scale;
vec4 halfAxes_retractionDepth;
// Information to compute retraction for two octants respectively.
// [splitRadian, splitGap, n, nInvNeg]
vec4 infoTop;
vec4 infoRight;
// Polynomial coeffs
vec4 polyTop;
vec4 polyRight;
}
frag_info;
in vec2 v_position;
out vec4 frag_color;
const float kPiOverFour = 3.1415926 / 4.0;
void main() {
vec2 center = frag_info.center_adjust.xy;
vec2 adjust = frag_info.center_adjust.zw;
vec2 centered = abs(v_position - center);
float d =
computeRRectDistance(centered, adjust, frag_info.r1_exponent_exponentInv);
/**** Start of RSuperellipse math ****/
vec2 halfAxes = frag_info.halfAxes_retractionDepth.xy;
float retractionDepth = frag_info.halfAxes_retractionDepth[2];
float octantOffset = halfAxes.y - halfAxes.x;
bool useTop = (centered.y - octantOffset) > centered.x;
vec4 angularInfo = useTop ? frag_info.infoTop : frag_info.infoRight;
float theta = atan(useTop ? centered.x / (centered.y - octantOffset)
: centered.y / (centered.x + octantOffset));
float splitRadian = angularInfo[0];
float splitGap = angularInfo[1];
float n = angularInfo[2];
float nInvNeg = angularInfo[3];
float baseRetraction;
if (theta < splitRadian) {
float a = useTop ? halfAxes.x : halfAxes.y;
baseRetraction = (1.0 - POW(1.0 + POW(tan(theta), n), nInvNeg)) * a;
} else {
float t = (theta - splitRadian) / (kPiOverFour - splitRadian);
float tt = t * t;
float ttt = tt * t;
float retProg = dot(vec4(ttt, tt, t, 1.0),
useTop ? frag_info.polyTop : frag_info.polyRight);
// Squaring `retProg` improves results empirically by boosting values > 1
// and dampening values < 1.
baseRetraction = retProg * retProg * splitGap;
}
float depthProg = smoothstep(-retractionDepth, 0.0, -abs(d));
d += baseRetraction * depthProg;
/**** End of RSuperellipse math ****/
float z = computeRRectFade(d, frag_info.sInv_minEdge_scale);
frag_color = frag_info.color * float16_t(z);
}