Update shader: bleed

This commit is contained in:
Kyant0 committed 2025-06-21 12:30:58 +08:00
1 parent e38de3b375
commit 508848bca5
2 files changed
+129 -78

No files matched your search

+2 -2
View File
@@ -18,8 +18,8 @@ You mustn't use the shaders in the project for commercial purposes, they are onl
| Outer refraction amount | ❌ | ❌ |
| Eccentric factor | ✔️ | ✔️ |
| Dispersion height | 🚧 | ❌ |
| Bleed amount | 🚧 | ❌ |
| Bleed blur radius | ❌ | ❌ |
| Bleed amount | ✔️ | ❌ |
| Bleed blur radius | ✔️ | ❌ |
| Bleed opacity | ✔️ | 🚧 |
## Comparisons
@@ -1,5 +1,6 @@
package com.kyant.glass.playground
import android.graphics.BlendMode
import android.graphics.RenderEffect
import android.graphics.RuntimeShader
import android.graphics.Shader
@@ -42,6 +43,7 @@ import androidx.compose.ui.unit.roundToIntSize
import androidx.compose.ui.util.fastCoerceAtMost
import com.kyant.expressa.prelude.*
import com.kyant.glass.R
import org.intellij.lang.annotations.Language
@Composable
fun Preview(state: PreviewState) {
@@ -74,28 +76,75 @@ fun Preview(state: PreviewState) {
contentScale = ContentScale.Crop
)
val colorShaderUtils = """
@Language("AGSL")
val colorShaderUtils = """// This file belongs to Kyant. You must not use it without permission.
const half3 rgbToY = half3(0.2126, 0.7152, 0.0722);
half3 linearizedRgb(half3 rgb) {
return mix(rgb / 12.92, pow((rgb + 0.055) / 1.055, half3(2.4)), step(half3(0.04045), rgb));
}
half3 delinearizedRgb(half3 rgb) {
return mix(rgb * 12.92, 1.055 * pow(rgb, half3(1.0 / 2.4)) - 0.055, step(half3(0.0031308), rgb));
}
float luma(half4 color) {
return dot(linearizedRgb(color.rgb), rgbToY);
return dot(toLinearSrgb(color.rgb), rgbToY);
}
half4 saturateColor(half4 color, float amount) {
half3 linearRgb = linearizedRgb(color.rgb);
float y = dot(linearRgb, rgbToY);
half3 gray = half3(y, y, y);
half3 adjustedLinear = mix(gray, linearRgb, amount);
half3 finalColor = delinearizedRgb(adjustedLinear);
return half4(finalColor, color.a);
half3 linearSrgb = toLinearSrgb(color.rgb);
float y = dot(linearSrgb, rgbToY);
half3 gray = half3(y);
half3 adjustedLinearSrgb = mix(gray, linearSrgb, amount);
half3 adjustedSrgb = fromLinearSrgb(adjustedLinearSrgb);
return half4(adjustedSrgb, color.a);
}
""".trimIndent()
@Language("AGSL")
val refractionShaderUtils = """// This file belongs to Kyant. You must not use it without permission.
float circleMap(float x) {
return 1.0 - sqrt(1.0 - x * x);
}
float sdRectangle(float2 coord, float2 halfSize) {
float2 d = abs(coord) - halfSize;
float outside = length(max(d, 0.0));
float inside = min(max(d.x, d.y), 0.0);
return outside + inside;
}
float sdRoundedRectangle(float2 coord, float2 halfSize, float cornerRadius) {
float2 innerHalfSize = halfSize - float2(cornerRadius);
return sdRectangle(coord, innerHalfSize) - cornerRadius;
}
float2 gradSdRoundedRectangle(float2 coord, float2 halfSize, float cornerRadius) {
float2 innerHalfSize = halfSize - float2(cornerRadius);
float2 cornerCoord = abs(coord) - innerHalfSize;
if (cornerCoord.x >= 0.0 && cornerCoord.y >= 0.0) {
return sign(coord) * normalize(cornerCoord);
} else {
return sign(coord) * ((-cornerCoord.x < -cornerCoord.y) ? float2(1.0, 0.0) : float2(0.0, 1.0));
}
}
half4 refractionColor(float2 coord, float2 size, float cornerRadius, float eccentricFactor, float height, float amount) {
float2 halfSize = size * 0.5;
float2 centeredCoord = coord - halfSize;
float sd = sdRoundedRectangle(centeredCoord, halfSize, cornerRadius);
if (sd < 0.0 && -sd < height) {
float maxGradRadius = max(min(halfSize.x, halfSize.y), cornerRadius);
float gradRadius = min(cornerRadius * 1.5, maxGradRadius);
float2 normal = gradSdRoundedRectangle(centeredCoord, halfSize, gradRadius);
float refractedDistance = circleMap(1.0 - -sd / height) * amount;
float2 refractedDirection = normalize(normal + eccentricFactor * normalize(centeredCoord));
float2 refractedCoord = coord + refractedDistance * refractedDirection;
if (refractedCoord.x < 0.0 || refractedCoord.x >= size.x ||
refractedCoord.y < 0.0 || refractedCoord.y >= size.y) {
return half4(0.0, 0.0, 0.0, 1.0);
}
return image.eval(refractedCoord);
} else {
return image.eval(coord);
}
}
""".trimIndent()
@@ -235,7 +284,7 @@ fun Preview(state: PreviewState) {
).asComposeRenderEffect()
}
.graphicsLayer { // refraction + bleed effect
renderEffect = RenderEffect.createRuntimeShaderEffect(
val refractionRenderEffect = RenderEffect.createRuntimeShaderEffect(
RuntimeShader(
"""// This file belongs to Kyant. You must not use it without permission.
uniform shader image;
@@ -247,72 +296,19 @@ fun Preview(state: PreviewState) {
uniform float refractionAmount;
uniform float eccentricFactor;
uniform float bleedAmount;
uniform float bleedBlurRadius;
uniform float bleedOpacity;
$colorShaderUtils
float circleMap(float x) {
return 1.0 - sqrt(1.0 - x * x);
}
float sdRectangle(float2 coord, float2 halfSize) {
float2 d = abs(coord) - halfSize;
float outside = length(max(d, 0.0));
float inside = min(max(d.x, d.y), 0.0);
return outside + inside;
}
float sdRoundedRectangle(float2 coord, float2 halfSize, float cornerRadius) {
float2 innerHalfSize = halfSize - float2(cornerRadius);
return sdRectangle(coord, innerHalfSize) - cornerRadius;
}
float2 gradSdRoundedRectangle(float2 coord, float2 halfSize, float cornerRadius) {
float2 innerHalfSize = halfSize - float2(cornerRadius);
float2 cornerCoord = abs(coord) - innerHalfSize;
if (cornerCoord.x >= 0.0 && cornerCoord.y >= 0.0) {
return sign(coord) * normalize(cornerCoord);
} else {
return sign(coord) * ((-cornerCoord.x < -cornerCoord.y) ? float2(1.0, 0.0) : float2(0.0, 1.0));
}
}
half4 refraction(float2 coord, float height, float amount) {
float2 halfSize = size * 0.5;
float2 centeredCoord = coord - halfSize;
float sd = sdRoundedRectangle(centeredCoord, halfSize, cornerRadius);
if (sd < 0.0 && -sd < height) {
float maxGradRadius = max(min(halfSize.x, halfSize.y), cornerRadius);
float gradRadius = min(cornerRadius * 1.5, maxGradRadius);
float2 normal = gradSdRoundedRectangle(centeredCoord, halfSize, gradRadius);
float refractedDistance = circleMap(1.0 - -sd / height) * amount;
float2 refractedDirection = normalize(normal + eccentricFactor * normalize(centeredCoord));
float2 refractedCoord = coord + refractedDistance * refractedDirection;
if (refractedCoord.x < 0.0 || refractedCoord.x >= size.x ||
refractedCoord.y < 0.0 || refractedCoord.y >= size.y) {
return half4(0.0, 0.0, 0.0, 1.0);
}
return image.eval(refractedCoord);
} else {
return image.eval(coord);
}
}
$refractionShaderUtils
half4 main(float2 coord) {
half4 refractedColor = refraction(coord, refractionHeight, refractionAmount);
half4 refractedColor = refractionColor(coord, size, cornerRadius, eccentricFactor, refractionHeight, refractionAmount);
if (bleedOpacity <= 0.0) {
return refractedColor;
} else {
refractedColor *= 1.0 - bleedOpacity * luma(refractedColor);
return refractedColor;
}
half4 bleedColor = refraction(coord, cornerRadius * 3.5, bleedAmount);
float bleedFraction = 0.5 * luma(refractedColor);
return mix(refractedColor, bleedColor, bleedOpacity * bleedFraction);
}"""
).apply {
setFloatUniform("size", size.width, size.height)
@@ -322,12 +318,67 @@ fun Preview(state: PreviewState) {
setFloatUniform("refractionAmount", state.refractionAmount.value.toPx())
setFloatUniform("eccentricFactor", state.eccentricFactor.value)
setFloatUniform("bleedOpacity", state.bleedOpacity.value)
},
"image"
)
val bleedRenderEffect = RenderEffect.createRuntimeShaderEffect(
RuntimeShader(
"""// This file belongs to Kyant. You must not use it without permission.
uniform shader image;
uniform float2 size;
uniform float cornerRadius;
uniform float eccentricFactor;
uniform float bleedAmount;
uniform float bleedBlurRadius;
uniform float bleedOpacity;
$refractionShaderUtils
half4 main(float2 coord) {
half4 bleedColor = refractionColor(coord, size, cornerRadius, eccentricFactor, cornerRadius * 3.5, bleedAmount);
return bleedColor;
}"""
).apply {
setFloatUniform("size", size.width, size.height)
setFloatUniform("cornerRadius", state.cornerRadius.value.toPx())
setFloatUniform("eccentricFactor", state.eccentricFactor.value)
setFloatUniform("bleedAmount", state.bleedAmount.value.toPx())
setFloatUniform("bleedBlurRadius", state.bleedBlurRadius.value.toPx())
setFloatUniform("bleedOpacity", state.bleedOpacity.value)
},
"image"
).asComposeRenderEffect()
)
renderEffect =
if (state.bleedOpacity.value > 0f) {
val bleedBlurRadiusPx = state.bleedBlurRadius.value.toPx()
val blurredBleedRenderEffect =
if (bleedBlurRadiusPx > 0f) {
RenderEffect.createBlurEffect(
bleedBlurRadiusPx,
bleedBlurRadiusPx,
bleedRenderEffect,
Shader.TileMode.CLAMP
)
} else {
bleedRenderEffect
}
RenderEffect.createBlendModeEffect(
blurredBleedRenderEffect,
refractionRenderEffect,
BlendMode.SRC_OVER
).asComposeRenderEffect()
} else {
refractionRenderEffect.asComposeRenderEffect()
}
}
.graphicsLayer { // blur
val blurRadiusPx = state.blurRadius.value.toPx()