Add dispersion effect

This commit is contained in:
Kyant0 committed 2025-09-12 10:52:37 +08:00
1 parent 52378a1d54
commit 6504aabeef
10 files changed
+158 -38

No files matched your search

+5 -1
View File
@@ -50,16 +50,20 @@ Box(
providerState,
GlassStyle(
shape = RoundedCornerShape(16f.dp),
// you can customize more properties
innerRefraction = InnerRefraction(
height = RefractionHeight(8f.dp),
amount = RefractionAmount((-16f).dp),
depthEffect = 0f // or `1f` to have more 3D effect
),
dispersion = Dispersion.None, // or `Dispersion.Automatic` to enable dispersion effect
material = GlassMaterial(
blurRadius = 2f.dp,
brush = SolidColor(Color.White),
alpha = 0.3f
)
),
highlight = GlassHighlight.Default,
shadow = GlassShadow.Default // or `null` to disable shadow
)
)
)
@@ -206,39 +206,30 @@ fun Preview() {
float2 normal = gradSdRoundedRectangle(centeredCoord, halfSize, cornerRadius);
float2 tangent = normalToTangent(normal);
half4 dispersedColor = half4(0.0);
half4 weight = half4(0.0);
float dispersionFraction = 1.0 - -sd / dispersionHeight;
float dispersionWidth = dispersionHeight * 2.0 * pow(circleMap(dispersionFraction), 2.0);
float dispersionWidth = dispersionHeight * circleMap(dispersionFraction);
if (dispersionWidth < 2.0) {
half4 color = image.eval(coord);
return color;
}
float maxI = min(dispersionWidth, 100.0);
for (float i = 0.0; i < 100.0; i++) {
half4 dispersedColor = half4(0.0);
half4 weight = half4(0.0);
float maxI = min(dispersionWidth, 20.0);
for (float i = 0.0; i < 20.0; i++) {
float t = i / maxI;
if (t > 1.0) break;
half4 color = image.eval(coord + tangent * float2(t - 0.5) * dispersionWidth);
if (t >= 0.0 && t < 0.5) {
dispersedColor.b += color.b;
weight.b += 1.0;
}
if (t > 0.25 && t < 0.75) {
dispersedColor.g += color.g;
weight.g += 1.0;
}
if (t > 0.5 && t <= 1.0) {
dispersedColor.r += color.r;
weight.r += 1.0;
}
half rMask = step(0.5, t);
half gMask = step(0.25, t) * step(t, 0.75);
half bMask = step(t, 0.5);
half4 mask = half4(rMask, gMask, bMask, 0.0);
dispersedColor += color * mask;
weight += mask;
}
dispersedColor /= weight;
half4 color = image.eval(coord);
dispersedColor.a = color.a;
half4 blendedColor = dispersedColor;
return blendedColor;
dispersedColor.a = image.eval(coord).a;
return dispersedColor;
} else {
half4 color = image.eval(coord);
return color;
@@ -251,7 +242,7 @@ fun Preview() {
setFloatUniform("size", size.width, size.height)
setFloatUniform("cornerRadius", cornerRadius)
setFloatUniform("dispersionHeight", state.dispersionHeight.value.toPx())
setFloatUniform("dispersionHeight", state.refractionHeight.value.toPx())
},
"image"
).asComposeRenderEffect()
+1 -7
View File
@@ -1,7 +1 @@
!/build
/build/*
!/build/outputs/
/build/outputs/*
!/build/outputs/aar/
/build/outputs/aar/*
!/build/outputs/aar/liquid-glass-release.aar
/build
@@ -2,6 +2,7 @@ package com.kyant.liquidglass
import androidx.compose.foundation.shape.CornerBasedShape
import androidx.compose.runtime.Immutable
import com.kyant.liquidglass.dispersion.Dispersion
import com.kyant.liquidglass.highlight.GlassHighlight
import com.kyant.liquidglass.material.GlassMaterial
import com.kyant.liquidglass.refraction.InnerRefraction
@@ -14,6 +15,7 @@ import com.kyant.liquidglass.shadow.GlassShadow
data class GlassStyle(
val shape: CornerBasedShape,
val innerRefraction: InnerRefraction = InnerRefraction.Default,
val dispersion: Dispersion = Dispersion.None,
val material: GlassMaterial = GlassMaterial.Default,
val highlight: GlassHighlight = GlassHighlight.Default,
val shadow: GlassShadow? = GlassShadow.Default
@@ -28,6 +28,7 @@ import androidx.compose.ui.node.ObserverModifierNode
import androidx.compose.ui.node.observeReads
import androidx.compose.ui.node.requireGraphicsContext
import androidx.compose.ui.platform.InspectorInfo
import com.kyant.liquidglass.dispersion.Dispersion
import com.kyant.liquidglass.highlight.GlassHighlightElement
import com.kyant.liquidglass.highlight.SimpleGlassHighlightElement
import com.kyant.liquidglass.material.GlassBrushElement
@@ -193,6 +194,7 @@ private class SimpleLiquidGlassNode(
private var graphicsLayer: GraphicsLayer? = null
private val innerRefractionShader = RuntimeShader(GlassShaders.refractionShaderString)
private var dispersionShader: RuntimeShader? = null
private val drawNode = delegate(CacheDrawModifierNode {
val colorFilter = style.material.colorFilter
@@ -242,14 +244,62 @@ private class SimpleLiquidGlassNode(
"image"
)
val renderEffect =
if (blurRenderEffect != null) {
val dispersion = style.dispersion
val dispersionHeight: Float
val dispersionAmount: Float
when (dispersion) {
Dispersion.None -> {
dispersionHeight = 0f
dispersionAmount = 0f
}
Dispersion.Automatic -> {
dispersionHeight = innerRefractionHeight
dispersionAmount = -innerRefractionAmount
}
is Dispersion.Fractional -> {
dispersionHeight = innerRefractionHeight * dispersion.fraction
dispersionAmount = -innerRefractionAmount * dispersion.fraction
}
}
val dispersionEffect =
if (dispersionHeight == 0f || dispersionAmount == 0f) {
null
} else {
val shader =
dispersionShader
?: RuntimeShader(GlassShaders.dispersionShaderString).also { dispersionShader = it }
RenderEffect.createRuntimeShaderEffect(
shader.apply {
setFloatUniform("size", size.width, size.height)
setFloatUniform("cornerRadius", cornerRadiusPx)
setFloatUniform("dispersionHeight", dispersionHeight)
setFloatUniform("dispersionAmount", dispersionAmount)
},
"image"
)
}
val refractionWithDispersionRenderEffect =
if (dispersionEffect != null) {
RenderEffect.createChainEffect(
innerRefractionRenderEffect,
blurRenderEffect
dispersionEffect,
innerRefractionRenderEffect
)
} else {
innerRefractionRenderEffect
}
val renderEffect =
if (blurRenderEffect != null) {
RenderEffect.createChainEffect(
refractionWithDispersionRenderEffect,
blurRenderEffect
)
} else {
refractionWithDispersionRenderEffect
}.asComposeRenderEffect()
graphicsLayer?.renderEffect = renderEffect
@@ -0,0 +1,19 @@
package com.kyant.liquidglass.dispersion
import androidx.annotation.FloatRange
import androidx.compose.runtime.Immutable
@Immutable
sealed interface Dispersion {
@Immutable
data object None : Dispersion
@Immutable
data object Automatic : Dispersion
@Immutable
data class Fractional(
@param:FloatRange(from = 0.0, to = 1.0) val fraction: Float
) : Dispersion
}
@@ -23,7 +23,7 @@ sealed interface RefractionAmount : RefractionValue {
value class Fixed(val value: Dp) : RefractionAmount {
init {
requirePrecondition(value.value <= 0.0f) {
requirePrecondition(value.value <= 0f) {
"Refraction amount can't be positive!"
}
}
@@ -23,7 +23,7 @@ sealed interface RefractionHeight : RefractionValue {
value class Fixed(val value: Dp) : RefractionHeight {
init {
requirePrecondition(value.value >= 0.0f) {
requirePrecondition(value.value >= 0f) {
"Refraction height can't be negative!"
}
}
@@ -67,6 +67,66 @@ half4 main(float2 coord) {
return image.eval(refractedCoord);
}"""
@Language("AGSL")
val dispersionShaderString = """
uniform shader image;
uniform float2 size;
uniform float cornerRadius;
uniform float dispersionHeight;
uniform float dispersionAmount;
$sdRectangleShaderUtils
float circleMap(float x) {
return 1.0 - sqrt(1.0 - x * x);
}
float2 normalToTangent(float2 normal) {
return float2(normal.y, -normal.x);
}
half4 main(float2 coord) {
float2 halfSize = size * 0.5;
float2 centeredCoord = coord - halfSize;
float sd = sdRoundedRectangle(centeredCoord, halfSize, cornerRadius);
if (-sd >= dispersionHeight) {
return image.eval(coord);
}
sd = min(sd, 0.0);
float maxGradRadius = max(min(halfSize.x, halfSize.y), cornerRadius);
float gradRadius = min(cornerRadius * 1.5, maxGradRadius);
float2 normal = gradSdRoundedRectangle(centeredCoord, halfSize, gradRadius);
float2 tangent = normalToTangent(normal);
float dispersionDistance = circleMap(1.0 - -sd / dispersionHeight) * dispersionAmount;
if (dispersionDistance < 2.0) {
half4 color = image.eval(coord);
return color;
}
half4 dispersedColor = half4(0.0);
half4 weight = half4(0.0);
float maxI = min(dispersionDistance, 20.0);
for (float i = 0.0; i < 20.0; i++) {
float t = i / maxI;
if (t > 1.0) break;
half4 color = image.eval(coord + tangent * float2(t - 0.5) * dispersionDistance);
half rMask = step(0.5, t);
half gMask = step(0.25, t) * step(t, 0.75);
half bMask = step(t, 0.5);
half4 mask = half4(rMask, gMask, bMask, 0.0);
dispersedColor += color * mask;
weight += mask;
}
dispersedColor /= weight;
dispersedColor.a = image.eval(coord).a;
return dispersedColor;
}"""
@Language("AGSL")
val highlightShaderString = """
uniform shader image;