// 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 #include #include #include 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); }