Add dispersion effect
This commit is contained in:
1 parent
52378a1d54
commit
6504aabeef
10 files changed
+158
-38
No files matched your search
@@ -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 +1 @@
|
||||
!/build
|
||||
/build/*
|
||||
!/build/outputs/
|
||||
/build/outputs/*
|
||||
!/build/outputs/aar/
|
||||
/build/outputs/aar/*
|
||||
!/build/outputs/aar/liquid-glass-release.aar
|
||||
/build
|
||||
Binary file not shown.
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user