Update the shader layer.
This commit is contained in:
parent
223ae9f02b
commit
a0be0274f1
8 changed files with 399 additions and 83 deletions
|
|
@ -0,0 +1,148 @@
|
|||
package com.pixelized.rplexicon.ui.agsl
|
||||
|
||||
import android.graphics.RenderEffect
|
||||
import android.graphics.RuntimeShader
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.asComposeRenderEffect
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.rplexicon.R
|
||||
import org.intellij.lang.annotations.Language
|
||||
|
||||
|
||||
@Language("AGSL")
|
||||
val CHROMATIC_ABERRATION: String = """
|
||||
uniform shader composable;
|
||||
uniform float displacement;
|
||||
uniform float2 size;
|
||||
|
||||
half4 main(float2 coord) {
|
||||
half4 texture = composable.eval(coord);
|
||||
|
||||
float2 distance = (coord - size * 0.5) * 2.0 / size;
|
||||
distance = distance * abs(distance);
|
||||
|
||||
half4 red = composable.eval(float2(
|
||||
coord.x - displacement * distance.x,
|
||||
coord.y - displacement * distance.y
|
||||
));
|
||||
half4 blue = composable.eval(float2(
|
||||
coord.x + displacement * distance.x,
|
||||
coord.y + displacement * distance.y
|
||||
));
|
||||
|
||||
texture.r = red.r;
|
||||
texture.b = blue.b;
|
||||
texture.a = max(texture.a, (red.a + blue.a) / 3.0);
|
||||
return texture;
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
|
||||
@Preview
|
||||
@Composable
|
||||
private fun ChromaticAberrationD20() {
|
||||
val shader = remember { RuntimeShader(CHROMATIC_ABERRATION) }
|
||||
val displacement = remember { mutableFloatStateOf(25f) }
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.graphicsLayer {
|
||||
shader.setFloatUniform("displacement", displacement.floatValue)
|
||||
shader.setFloatUniform("size", size.width, size.height)
|
||||
|
||||
renderEffect = RenderEffect
|
||||
.createRuntimeShaderEffect(shader, "composable")
|
||||
.asComposeRenderEffect()
|
||||
},
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(all = 16.dp),
|
||||
painter = painterResource(id = R.drawable.ic_d20_24),
|
||||
contentScale = ContentScale.FillWidth,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
||||
Slider(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
value = displacement.floatValue,
|
||||
valueRange = remember { 0f..100f },
|
||||
onValueChange = { displacement.floatValue = it },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
|
||||
@Preview
|
||||
@Composable
|
||||
private fun ChromaticAberrationImage() {
|
||||
val shader = remember { RuntimeShader(CHROMATIC_ABERRATION) }
|
||||
val displacement = remember { mutableFloatStateOf(25f) }
|
||||
|
||||
Column(
|
||||
modifier = Modifier.padding(all = 16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.graphicsLayer {
|
||||
shader.setFloatUniform("displacement", displacement.floatValue)
|
||||
shader.setFloatUniform("size", size.width, size.height)
|
||||
clip = true
|
||||
renderEffect = RenderEffect
|
||||
.createRuntimeShaderEffect(shader, "composable")
|
||||
.asComposeRenderEffect()
|
||||
},
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
painter = painterResource(id = R.drawable.im_naderius_background),
|
||||
contentScale = ContentScale.FillWidth,
|
||||
contentDescription = null,
|
||||
)
|
||||
Image(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
painter = painterResource(id = R.drawable.im_naderius_foreground),
|
||||
contentScale = ContentScale.FillWidth,
|
||||
contentDescription = null,
|
||||
)
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.offset(y = (-16).dp),
|
||||
painter = painterResource(id = R.drawable.im_naderius_dice),
|
||||
contentScale = ContentScale.FillWidth,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
||||
Slider(
|
||||
value = displacement.floatValue,
|
||||
valueRange = remember { 0f..100f },
|
||||
onValueChange = { displacement.floatValue = it },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
package com.pixelized.rplexicon.ui.agsl
|
||||
|
||||
import android.graphics.RuntimeShader
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawWithCache
|
||||
import androidx.compose.ui.graphics.BlendMode
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.CompositingStrategy
|
||||
import androidx.compose.ui.graphics.ShaderBrush
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.pixelized.rplexicon.R
|
||||
import org.intellij.lang.annotations.Language
|
||||
|
||||
@Language("AGSL")
|
||||
val DANCING_COLOR = """
|
||||
uniform float2 size;
|
||||
uniform float time;
|
||||
uniform float transition;
|
||||
layout(color) uniform half4 tint;
|
||||
|
||||
half4 main(float2 coord) {
|
||||
// Normalized pixel coordinates (from 0 to 1)
|
||||
half2 uv = coord.xy / size;
|
||||
// Time varying pixel color
|
||||
half2 wave = 0.5 + 0.5 * cos(time + uv.xy * 2.0 + half2(0, 3.14));
|
||||
half4 color = half4(wave, 1.0, 1.0);
|
||||
// Output to screen
|
||||
return mix(tint, color, transition);
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
fun Modifier.dancingColor(
|
||||
transition: State<Float>,
|
||||
time: State<Float>,
|
||||
default: Color,
|
||||
): Modifier = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
this
|
||||
.graphicsLayer {
|
||||
compositingStrategy = CompositingStrategy.Offscreen
|
||||
}
|
||||
.drawWithCache {
|
||||
val shader = RuntimeShader(DANCING_COLOR)
|
||||
val brush = ShaderBrush(shader)
|
||||
val color = android.graphics.Color.valueOf(default.red, default.green, default.blue)
|
||||
shader.setColorUniform("tint", color)
|
||||
shader.setFloatUniform("size", size.width, size.height)
|
||||
shader.setFloatUniform("transition", transition.value)
|
||||
|
||||
onDrawWithContent {
|
||||
drawContent()
|
||||
shader.setFloatUniform("time", time.value)
|
||||
drawRect(brush = brush, blendMode = BlendMode.SrcAtop)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
|
||||
@Composable
|
||||
@Preview
|
||||
private fun DancingColor() {
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.dancingColor(
|
||||
transition = remember { mutableFloatStateOf(1.0f) },
|
||||
time = rememberTimeState(),
|
||||
default = Color.White,
|
||||
),
|
||||
painter = painterResource(id = R.drawable.ic_d20_24),
|
||||
contentScale = ContentScale.FillWidth,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
package com.pixelized.rplexicon.ui.agsl
|
||||
|
||||
import android.graphics.RenderEffect
|
||||
import android.graphics.RuntimeShader
|
||||
import android.graphics.Shader
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.asComposeRenderEffect
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.pixelized.rplexicon.R
|
||||
import org.intellij.lang.annotations.Language
|
||||
|
||||
// https://medium.com/androiddevelopers/agsl-made-in-the-shade-r-7d06d14fe02a
|
||||
|
||||
@Language("AGSL")
|
||||
val FROSTED_GLASS_SHADER = """
|
||||
uniform shader inputShader;
|
||||
uniform float height;
|
||||
uniform float width;
|
||||
|
||||
vec4 main(vec2 coords) {
|
||||
vec4 currValue = inputShader.eval(coords);
|
||||
float top = height - 100;
|
||||
if (coords.y < top) {
|
||||
return currValue;
|
||||
} else {
|
||||
// Avoid blurring edges
|
||||
if (coords.x > 1 && coords.y > 1 &&
|
||||
coords.x < (width - 1) &&
|
||||
coords.y < (height - 1)) {
|
||||
// simple box blur - average 5x5 grid around pixel
|
||||
vec4 boxSum =
|
||||
inputShader.eval(coords + vec2(-2, -2)) +
|
||||
// ...
|
||||
currValue +
|
||||
// ...
|
||||
inputShader.eval(coords + vec2(2, 2));
|
||||
currValue = boxSum / 25;
|
||||
}
|
||||
|
||||
const vec4 white = vec4(1);
|
||||
// top-left corner of label area
|
||||
vec2 lefttop = vec2(0.0, top);
|
||||
float lightenFactor = min(1.0, .6 *
|
||||
length(coords - lefttop) /
|
||||
(0.85 * length(vec2(width, 100))));
|
||||
// White in upper-left, blended increasingly
|
||||
// toward lower-right
|
||||
return mix(currValue, white, 1 - lightenFactor);
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
|
||||
@Composable
|
||||
@Preview
|
||||
private fun FrostedGlass() {
|
||||
val shader = remember { RuntimeShader(FROSTED_GLASS_SHADER) }
|
||||
|
||||
Image(
|
||||
modifier = Modifier.graphicsLayer {
|
||||
shader.setFloatUniform("width", size.width)
|
||||
shader.setFloatUniform("height", size.height)
|
||||
clip = true
|
||||
val glass = RenderEffect.createRuntimeShaderEffect(shader, "inputShader")
|
||||
val blur = RenderEffect.createBlurEffect(30f, 30f, Shader.TileMode.CLAMP)
|
||||
renderEffect = RenderEffect.createChainEffect(blur, glass).asComposeRenderEffect()
|
||||
},
|
||||
painter = painterResource(id = R.drawable.im_brulkhai),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package com.pixelized.rplexicon.ui.agsl
|
||||
|
||||
import android.graphics.RenderEffect
|
||||
import android.graphics.RuntimeShader
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.asComposeRenderEffect
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.pixelized.rplexicon.R
|
||||
import org.intellij.lang.annotations.Language
|
||||
|
||||
@Language("AGSL")
|
||||
val RANDOM_NOISE = """
|
||||
uniform shader composable;
|
||||
uniform float2 size;
|
||||
|
||||
float random(float2 uv) {
|
||||
return fract(sin(dot(uv.xy, float2(12.9898, 78.233))) * 43758.5453123);
|
||||
}
|
||||
|
||||
half4 main(float2 coord) {
|
||||
float2 uv = coord / size;
|
||||
float noise = random(uv);
|
||||
return half4(uv.x * noise, 0.0, uv.y * noise, 1.0);
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
|
||||
@Composable
|
||||
@Preview
|
||||
private fun RandomNoise() {
|
||||
val shader = remember { RuntimeShader(RANDOM_NOISE) }
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.graphicsLayer {
|
||||
shader.setFloatUniform("size", size.width, size.height)
|
||||
clip = true
|
||||
val noise = RenderEffect
|
||||
.createRuntimeShaderEffect(shader, "composable")
|
||||
renderEffect = noise.asComposeRenderEffect()
|
||||
},
|
||||
) {
|
||||
Image(painter = painterResource(id = R.drawable.im_brulkhai), contentDescription = null)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.pixelized.rplexicon.ui.agsl
|
||||
|
||||
import androidx.compose.animation.core.withInfiniteAnimationFrameMillis
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.produceState
|
||||
|
||||
@Composable
|
||||
@Stable
|
||||
fun rememberTimeState(speed: Float = 1f): State<Float> {
|
||||
return produceState(0f) {
|
||||
while (true) {
|
||||
withInfiniteAnimationFrameMillis {
|
||||
value = it / 1000f * speed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -119,6 +119,7 @@ private fun LandingContent(
|
|||
.aspectRatio(1f)
|
||||
.clip(RectangleShape),
|
||||
) {
|
||||
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import androidx.compose.animation.fadeIn
|
|||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.shrinkOut
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
|
|
@ -21,7 +22,6 @@ import androidx.compose.foundation.layout.Column
|
|||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
|
|
@ -35,6 +35,7 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
|
@ -44,10 +45,10 @@ import androidx.compose.ui.unit.Dp
|
|||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.rplexicon.R
|
||||
import com.pixelized.rplexicon.ui.agsl.dancingColor
|
||||
import com.pixelized.rplexicon.ui.agsl.rememberTimeState
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.criticalShader
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier.rememberITimeState
|
||||
|
||||
|
||||
@Stable
|
||||
|
|
@ -120,25 +121,26 @@ private fun Dice(
|
|||
dice: State<RollDiceUio?>,
|
||||
) {
|
||||
dice.value?.let {
|
||||
val time = rememberTimeState()
|
||||
val animatedRotation = animateFloatAsState(
|
||||
targetValue = it.rotation * 360,
|
||||
animationSpec = it.animationSpec,
|
||||
label = "AnimatedRotation"
|
||||
)
|
||||
val isCritical = animateFloatAsState(
|
||||
val transition = animateFloatAsState(
|
||||
targetValue = if (it.isCriticalSuccess) 1.0f else 0f,
|
||||
label = "animatedCritical",
|
||||
animationSpec = tween(1500),
|
||||
)
|
||||
Icon(
|
||||
Image(
|
||||
modifier = modifier
|
||||
.rotate(degrees = animatedRotation.value % 360)
|
||||
.criticalShader(
|
||||
iDefault = MaterialTheme.colorScheme.primary,
|
||||
isCritical = isCritical.value,
|
||||
iTime = rememberITimeState().value,
|
||||
.dancingColor(
|
||||
time = time,
|
||||
transition = transition,
|
||||
default = MaterialTheme.colorScheme.primary,
|
||||
),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.primary),
|
||||
painter = painterResource(id = it.icon),
|
||||
contentDescription = null,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,73 +0,0 @@
|
|||
package com.pixelized.rplexicon.utilitary.extentions.modifier
|
||||
|
||||
import android.graphics.Color.valueOf
|
||||
import android.graphics.RuntimeShader
|
||||
import android.os.Build
|
||||
import androidx.compose.animation.core.withInfiniteAnimationFrameMillis
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawWithCache
|
||||
import androidx.compose.ui.graphics.BlendMode
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.CompositingStrategy
|
||||
import androidx.compose.ui.graphics.ShaderBrush
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import org.intellij.lang.annotations.Language
|
||||
|
||||
fun Modifier.criticalShader(
|
||||
isCritical: Float,
|
||||
iTime: Float,
|
||||
iDefault: Color,
|
||||
): Modifier = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
this
|
||||
.graphicsLayer {
|
||||
compositingStrategy = CompositingStrategy.Offscreen
|
||||
}
|
||||
.drawWithCache {
|
||||
val shader = RuntimeShader(CRITICAL_SHADER)
|
||||
val shaderBrush = ShaderBrush(shader)
|
||||
shader.setFloatUniform("iResolution", size.width, size.height)
|
||||
shader.setFloatUniform("isCritical", isCritical)
|
||||
shader.setColorUniform("iDefault", valueOf(iDefault.red, iDefault.green, iDefault.blue))
|
||||
|
||||
onDrawWithContent {
|
||||
drawContent()
|
||||
shader.setFloatUniform("iTime", iTime)
|
||||
drawRect(brush = shaderBrush, blendMode = BlendMode.SrcAtop)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Stable
|
||||
fun rememberITimeState(speed: Float = 1f): State<Float> {
|
||||
return produceState(0f) {
|
||||
while (true) {
|
||||
withInfiniteAnimationFrameMillis {
|
||||
value = it / 1000f * speed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Language("AGSL")
|
||||
val CRITICAL_SHADER = """
|
||||
uniform float2 iResolution;
|
||||
uniform float iTime;
|
||||
uniform float isCritical;
|
||||
layout(color) uniform half4 iDefault;
|
||||
|
||||
half4 main(float2 fragCoord) {
|
||||
// Normalized pixel coordinates (from 0 to 1)
|
||||
float2 uv = fragCoord.xy / iResolution;
|
||||
// Time varying pixel color
|
||||
float2 color = 0.5 + 0.5 * cos(iTime + uv.xy * 2.0 + float2(0, 3.14));
|
||||
// Output to screen
|
||||
return mix(iDefault, float4(color, 1.0, 1.0), isCritical);
|
||||
}
|
||||
""".trimIndent()
|
||||
Loading…
Add table
Add a link
Reference in a new issue