Kyant
06/17/2024, 11:28 AMRebecca Franks
06/17/2024, 11:44 AMHalil Ozercan
06/17/2024, 11:45 AMKonstantin Tskhovrebov
06/17/2024, 11:46 AMRebecca Franks
06/17/2024, 11:50 AMSergey Y.
06/17/2024, 11:52 AMAlex Styl
06/17/2024, 12:57 PMrob42
06/17/2024, 12:59 PMKyant
06/17/2024, 1:09 PM@Language(value = "AGSL")
val shader = """
uniform shader image;
uniform float2 origin;
uniform float elapsedTime;
uniform float amplitude;
uniform float frequency;
uniform float decay;
uniform float speed;
uniform float radius;
uniform float tintRatio;
float2 lerp(float2 a, float2 b, float t) {
return a + t * (b - a);
}
// Note: we use easing to prevent deformation in the center
// Function to calculate the cubic Bezier curve value at t
vec2 cubicBezier(vec2 P0, vec2 P1, vec2 P2, vec2 P3, float t) {
float u = 1.0 - t;
float tt = t * t;
float uu = u * u;
float uuu = uu * u;
float ttt = tt * t;
vec2 p = uuu * P0; // (1 - t)^3 * P0
p += 3.0 * uu * t * P1; // 3 * (1 - t)^2 * t * P1
p += 3.0 * u * tt * P2; // 3 * (1 - t) * t^2 * P2
p += ttt * P3; // t^3 * P3
return p;
}
// Function to find t for a given x using the Newton-Raphson method
float findTForX(float x, vec2 P0, vec2 P1, vec2 P2, vec2 P3) {
float t = x; // Initial guess
for (int i = 0; i < 5; i++) { // Iterate a few times
vec2 bezierPoint = cubicBezier(P0, P1, P2, P3, t);
float x_t = bezierPoint.x;
float dx_dt = -3.0 * (1.0 - t) * (1.0 - t) * P0.x + 3.0 * (1.0 - 2.0 * t) * (1.0 - t) * P1.x + 3.0 * t * (2.0 - 3.0 * t) * P2.x + 3.0 * t * t * P3.x;
t -= (x_t - x) / dx_dt;
t = clamp(t, 0.0, 1.0); // Keep t within [0, 1]
}
return t;
}
// Function to get the y value for a given x using the cubic Bezier curve
float getYForX(float x, vec2 P1, vec2 P2) {
vec2 P0 = vec2(0.0, 0.0); // Start point
vec2 P3 = vec2(1.0, 1.0); // End point
float t = findTForX(x, P0, P1, P2, P3);
vec2 bezierPoint = cubicBezier(P0, P1, P2, P3, t);
return bezierPoint.y;
}
float transformEaseEmphasizedAccelerate(float x) {
float2 P1 = float2(0.05, 0.7);
float2 P2 = float2(0.1, 1.0);
return getYForX(x, P1, P2);
}
half4 main(float2 coord) {
float distance = length(coord - origin);
float delay = distance / speed;
float adjustedTime = max(0.0, elapsedTime - delay);
float rippleAmount = amplitude * sin(frequency * adjustedTime) * exp(-decay * adjustedTime);
float2 n = normalize(coord - origin);
float2 newPosition = coord + rippleAmount * n;
newPosition = lerp(coord, newPosition, transformEaseEmphasizedAccelerate(distance / radius));
half4 color = image.eval(newPosition);
if (amplitude != 0.0) {
color.rgb += tintRatio * (rippleAmount / amplitude) * color.a;
}
return color;
}
"""
Kyant
06/17/2024, 1:10 PM@RequiresApi(33)
class RippleShader(
val origin: Offset,
val radius: Float,
val amplitude: Float,
val frequency: Float,
val decay: Float,
val relativeSpeed: Float,
val tintRatio: Float,
val startTimeMillis: Int,
val fadeInDurationMillis: Int,
val fadeOutDurationMillis: Int
) {
internal var canceled = false
private val elapsedTime = Animatable(startTimeMillis / 1000f, visibilityThreshold = 1f)
private val runtimeShader = RuntimeShader(shader).apply {
setFloatUniform("origin", origin.x, origin.y)
setFloatUniform("elapsedTime", 0f)
setFloatUniform("amplitude", amplitude)
setFloatUniform("frequency", frequency)
setFloatUniform("decay", decay)
setFloatUniform("speed", radius * relativeSpeed)
setFloatUniform("radius", radius)
setFloatUniform("tintRatio", tintRatio)
}
var renderEffect by mutableStateOf(
RenderEffect
.createRuntimeShaderEffect(runtimeShader, "image")
.asComposeRenderEffect()
)
private set
suspend fun fadeIn() {
elapsedTime.animateTo(
(startTimeMillis + fadeInDurationMillis) / 1000f,
tween(
startTimeMillis + fadeInDurationMillis - (elapsedTime.value * 1000).fastRoundToInt(),
0,
LinearEasing
)
) {
runtimeShader.setFloatUniform("elapsedTime", value)
renderEffect = RenderEffect
.createRuntimeShaderEffect(runtimeShader, "image")
.asComposeRenderEffect()
}
}
suspend fun fadeOut() {
elapsedTime.animateTo(
(startTimeMillis + fadeInDurationMillis + fadeOutDurationMillis) / 1000f,
tween(fadeOutDurationMillis, 0, LinearEasing)
) {
runtimeShader.setFloatUniform("elapsedTime", value)
renderEffect = RenderEffect
.createRuntimeShaderEffect(runtimeShader, "image")
.asComposeRenderEffect()
}
}
Kyant
06/17/2024, 1:12 PMModifier
.graphicsLayer {
renderEffect = shader?.renderEffect
clip = true
}
.onSizeChanged { size ->
shader = RippleShader(
origin = Offset(size.width / 2f, size.height / 1.25f),
radius = sqrt(size.width.toFloat() * size.width + size.height * size.height) / 2f,
amplitude = with(density) { (-16).dp.toPx() },
frequency = 10f,
decay = 10f,
relativeSpeed = 6f,
tintRatio = 0.3f,
startTimeMillis = 0,
fadeInDurationMillis = 0,
fadeOutDurationMillis = 500
)
}
And using shader.fadeIn / fadeOutKyant
06/17/2024, 1:15 PMSergey Y.
06/17/2024, 1:16 PMKyant
06/17/2024, 1:17 PMKyant
06/17/2024, 1:32 PMKyant
06/17/2024, 1:32 PMKyant
06/17/2024, 1:33 PMSergey Y.
06/17/2024, 1:43 PMSergey Y.
06/17/2024, 1:53 PMDmitrii
06/20/2024, 12:57 PM