Prismal
Real-time glassmorphism rendering library for Android for Android XML Components
High-performance OpenGL ES 2.0 library delivering physically accurate glass refraction, blur, and chromatic aberration effects for Android UI components.
Overview
Prismal is an Android library that provides real-time glassmorphism effects through custom OpenGL ES 2.0 shaders. It captures content behind UI elements and applies optical distortions including refraction, blur, and chromatic aberration to simulate realistic glass materials.
Key Features
- Real-time Glass Rendering - GPU-accelerated shader-based effects with live background capture
- Physically Based Refraction - Accurate light bending using Index of Refraction (IOR), Fresnel effects, and double refraction
- Chromatic Aberration - Realistic RGB color separation at glass edges
- Interactive Components - Touch-responsive animations and dynamic distortions
- Pre-built Components - Ready-to-use buttons, switches, sliders, and containers
- Performance Optimized - Efficient rendering with minimal overdraw and smart texture caching
- Fully Customizable - Extensive XML attributes and runtime APIs
Installation
Gradle
Add the JitPack repository to your root build.gradle:
allprojects {
repositories {
maven { url 'https://jitpack.io' }
}
}
Add the dependency:
dependencies {
implementation 'com.github.styropyr0:prismal:1.0.0'
}
Maven
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependency>
<groupId>com.github.yourusername</groupId>
<artifactId>prismal</artifactId>
<version>1.0.0</version>
</dependency>
Requirements
- Min SDK: 25 (Android 7, Nougat)
- Target SDK: 36
- OpenGL ES: 2.0+
- Kotlin: 2.0.21
Quick Start
Basic Usage
Add Prismal components to your layout:
<com.matrix.prismal.PrismalFrameLayout
android:layout_width="300dp"
android:layout_height="200dp"
app:ior="1.5"
app:blurRadius="3"
app:cornerRadius="20dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Glass Container"
android:textColor="@android:color/white"
android:layout_gravity="center" />
</com.matrix.prismal.PrismalFrameLayout>
Programmatic Configuration
val glassLayout = findViewById<PrismalFrameLayout>(R.id.glassContainer)
glassLayout.apply {
setIOR(1.5f)
setCornerRadius(25f)
setThickness(15f)
setBlurRadius(4f)
setBrightness(1.2f)
setChromaticAberration(2f)
}
Components
PrismalFrameLayout
Base container that renders glass effects. Acts as a standard FrameLayout with an OpenGL-rendered glass surface beneath its children.
Note that, all of the other Prismal views are subclasses of PrismalFrameLayout. PrismalFrameLayout handles most of the works within, such that it would be easier for users to make their own subclasses. All you need to do is inherit from PrismalFrameLayout, set up renderer (PrismalGlassRenderer), and implement methods for changing properties.
XML Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
app:glassWidth |
float | view width | Glass surface width |
app:glassHeight |
float | view height | Glass surface height |
app:ior |
float | 1.5 | Index of Refraction (1.0-2.0) |
app:glassThickness |
dimension | 15dp | Glass thickness affecting distortion |
app:normalStrength |
float | 1.2 | Surface normal influence |
app:displacementScale |
float | 1.0 | Displacement mapping intensity |
app:heightTransitionWidth |
float | 8.0 | Height field transition width |
app:minSmoothing |
float | 1.0 | SDF smoothing threshold |
app:blurRadius |
float | 2.5 | Background blur radius |
app:highlightWidth |
float | 4.0 | Edge highlight width |
app:chromaticAberration |
float | 2.0 | RGB color split intensity |
app:brightness |
float | 1.15 | Overall brightness multiplier |
app:cornerRadius |
dimension | 10dp | Corner rounding |
app:showNormals |
boolean | false | Debug: visualize surface normals |
API Methods
setRefractionInset(value: Float)
setGlassSize(width: Float, height: Float)
setCornerRadius(radius: Float)
setIOR(value: Float)
setThickness(value: Float)
setNormalStrength(value: Float)
setDisplacementScale(value: Float)
setHeightBlurFactor(value: Float)
setMinSmoothing(value: Float)
setBlurRadius(value: Float)
setHighlightWidth(value: Float)
setChromaticAberration(value: Float)
setBrightness(value: Float)
setShowNormals(show: Boolean)
setShadowProperties(color: Int, softness: Float)
setEdgeRefractionFalloff(value: Float)
updateBackground()
PrismalButton
Pressable glass button with scale animations and interactive refraction effects.
XML Example
<com.matrix.prismal.PrismalButton
android:id="@+id/glassButton"
android:layout_width="200dp"
android:layout_height="60dp"
app:ior="1.85"
app:normalStrength="8"
app:blurRadius="1"
app:cornerRadius="32dp">
<TextView
android:text="Press Me"
android:textColor="#FFFFFF"
android:layout_gravity="center" />
</com.matrix.prismal.PrismalButton>
Attributes
app:ior(float, default: 1.85)app:normalStrength(float, default: 8)app:displacementScale(float, default: 10)app:blurRadius(float, default: 1)app:chromaticAberration(float, default: 8)app:cornerRadius(dimension, default: 32dp)app:brightness(float, default: 1.0)app:highlightWidth(float, default: 4)app:showNormals(boolean, default: false)
API
setIOR(value: Float)
setNormalStrength(value: Float)
setDisplacementScale(value: Float)
setBlurRadius(value: Float)
setChromaticAberration(value: Float)
setCornerRadius(value: Float)
setBrightness(value: Float)
setHighlightWidth(value: Float)
setShowNormals(enabled: Boolean)
setOnClickListener(l: OnClickListener?)
PrismalIconButton
Circular glass button optimized for icons with automatic corner radius calculation.
XML Example
<com.matrix.prismal.PrismalIconButton
android:id="@+id/iconButton"
android:layout_width="56dp"
android:layout_height="56dp"
app:iconSrc="@drawable/ic_heart"
app:iconPadding="12dp"
app:ior="1.85"
app:blurRadius="1.5"
app:pressScale="0.88"
app:animDuration="180" />
Attributes
app:iconSrc(reference) - Icon drawableapp:iconPadding(dimension, default: 8dp)app:pressScale(float, default: 0.88)app:animDuration(integer, default: 180ms)- All optical parameters from
PrismalButton
API
setIcon(resId: Int)
setIOR(value: Float)
setBlurRadius(value: Float)
setChromaticAberration(value: Float)
setDisplacementScale(value: Float)
setOnClickListener(l: OnClickListener?)
PrismalSwitch
Animated toggle switch with glass thumb and color-changing track.
XML Example
<com.matrix.prismal.PrismalSwitch
android:id="@+id/glassSwitch"
android:layout_width="120dp"
android:layout_height="60dp"
app:isOn="false"
app:animDuration="250"
app:thumbWidth="60dp"
app:trackHeight="22dp"
app:onColor="#00B624"
app:offColor="#555555"
app:thumbIOR="1.85"
app:thumbBlurRadius="1"
app:thumbCornerRadius="50dp"
app:thumbShadowAlpha="70"
app:thumbShadowSoftness="0.2" />
Attributes
app:isOn(boolean, default: false)app:animDuration(integer, default: 250ms)app:thumbWidth(dimension, default: auto)app:trackHeight(dimension, default: 22dp)app:onColor(color, default: #00B624)app:offColor(color, default: #555555)app:thumbIOR(float, default: 1.85)app:thumbNormalStrength(float, default: 8)app:thumbDisplacementScale(float, default: 10)app:thumbBlurRadius(float, default: 1)app:thumbChromaticAberration(float, default: 8)app:thumbCornerRadius(dimension, default: 50dp)app:thumbBrightness(float, default: 1.175)app:thumbShadowSoftness(float, default: 0.2)app:thumbShadowAlpha(integer, default: 70)
API
setOn(on: Boolean, animated: Boolean = false)
isOn(): Boolean
setThumbIOR(value: Float)
setThumbNormalStrength(value: Float)
setThumbBlurRadius(value: Float)
setThumbChromaticAberration(value: Float)
setThumbCornerRadius(value: Float)
setThumbBrightness(value: Float)
setThumbShadow(color: Int, radius: Float)
setOnToggleChangedListener(listener: (Boolean) -> Unit)
PrismalSlider
Horizontal slider with draggable glass thumb on colored track.
XML Example
<com.matrix.prismal.PrismalSlider
android:id="@+id/glassSlider"
android:layout_width="match_parent"
android:layout_height="80dp"
app:maxValue="200"
app:thumbWidth="60dp"
app:thumbCornerRadius="50"
app:thumbIOR="1.85"
app:thumbBlurRadius="1"
app:thumbBrightness="1.175"
app:thumbShadowAlpha="80"
app:thumbShadowSoftness="0.2" />
Attributes
app:maxValue(float, default: 100)app:thumbWidth(dimension, default: 60dp)- All thumb optical parameters matching
PrismalSwitch
API
setValue(value: Float)
getValue(): Float
setMaxValue(value: Float)
setOnValueChangedListener(listener: (Float) -> Unit)
setThumbWidthDp(dpValue: Float)
setThumbIOR(value: Float)
setThumbBlurRadius(value: Float)
setThumbChromaticAberration(value: Float)
setThumbCornerRadius(value: Float)
setThumbBrightness(value: Float)
setThumbShadow(color: Int, radius: Float)
getThumb(): PrismalFrameLayout
Shader Architecture
Prismal uses custom GLSL ES 2.0 shaders to achieve realistic glass effects through a multi-stage rendering pipeline.
Rendering Pipeline
- Background Capture - Captures view hierarchy as OpenGL texture
- SDF Shape Generation - Creates smooth rounded rectangle using signed distance fields
- Height Field Calculation - Generates depth map from SDF with sigmoid transition
- Normal Computation - Calculates surface normals via gradient sampling
- Refraction - Double refraction (air→glass→air) using Snell's law
- Chromatic Aberration - Separates RGB channels along refraction direction
- Blur Application - Shape-aware 9-tap blur respecting boundaries
- Shadow & Highlights - Adds depth with inner shadow and Fresnel highlights
- Final Composition - Combines all layers with opacity
Core Techniques
Signed Distance Fields (SDF)
Polynomial-smoothed SDFs create ultra-smooth glass edges:
float smin_polynomial(float a, float b, float k) {
float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);
return mix(b, a, h) - k * h * (1.0 - h);
}
float sdRoundedBoxSmooth(vec2 p, vec2 b, float r, float k_smooth) {
vec2 q = abs(p) - b + r;
float termA = smax_polynomial(q.x, q.y, k_smooth);
float termB = smin_polynomial(termA, 0.0, k_smooth * 0.5);
vec2 q_clamped = vec2(
smax_polynomial(q.x, 0.0, k_smooth),
smax_polynomial(q.y, 0.0, k_smooth)
);
return termB + length(q_clamped) - r;
}
Height Field Generation
Converts distance field to glass thickness using sigmoid:
float getHeightFromSDF(vec2 p, vec2 b, float r, float k, float transition) {
float dist = sdRoundedBoxSmooth(p, b, r, k);
float normalized_dist = dist / transition;
float height = 1.0 - (1.0 / (1.0 + exp(-normalized_dist * 6.0)));
return clamp(height, 0.0, 1.0);
}
Fresnel Effect
Schlick's approximation for angle-dependent reflectivity:
float fresnel(vec3 normal, vec3 viewDir, float ior) {
float cosTheta = abs(dot(normal, viewDir));
float r0 = pow((1.0 - ior) / (1.0 + ior), 2.0);
return r0 + (1.0 - r0) * pow(1.0 - cosTheta, 5.0);
}
Double Refraction
Simulates light path through glass:
vec3 refractedIn = refract(-viewDir, surfaceNormal3D, 1.0 / u_ior);
vec3 refractedOut = refract(refractedIn, -surfaceNormal3D, u_ior);
vec2 refractionOffset = refractedOut.xy * u_glassThickness * strength;
Chromatic Aberration
Wavelength-dependent refraction:
float chromaIntensity = u_chromaticAberration * 0.002 * depthFalloff;
vec2 refractionDir = normalize(baseOffset);
vec2 offsetR = baseOffset - refractionDir * chromaIntensity;
vec2 offsetG = baseOffset;
vec2 offsetB = baseOffset + refractionDir * chromaIntensity;
vec3 refractedColor = vec3(cR.r, cG.g, cB.b);
Shape-Aware Blur
9-tap blur respecting glass boundaries:
vec3 blur9(sampler2D tex, vec2 uv, vec2 offset, ...) {
vec3 accum = vec3(0.0);
float weightSum = 0.0;
for(int y = -1; y <= 1; ++y) {
for(int x = -1; x <= 1; ++x) {
vec2 sampleUV = uv + offset + sampleOffset;
float sampleOpacity = getShapeOpacity(sampleShapeCoord, ...);
if(sampleOpacity > 0.001) {
accum += texture2D(tex, sampleUV).rgb * sampleOpacity;
weightSum += sampleOpacity;
}
}
}
return weightSum > 0.001 ? accum / weightSum : vec3(0.0);
}
Shader Parameters (If you're a nerd or wish to contribute)
| Uniform | Type | Range | Description |
|---|---|---|---|
u_ior |
float | 1.0-2.0 | Index of Refraction |
u_glassThickness |
float | 1-100 | Glass depth |
u_normalStrength |
float | 0-20 | Surface bumpiness |
u_displacementScale |
float | 0.1-10 | Refraction displacement |
u_cornerRadius |
float | 0-∞ | Corner radius in pixels |
u_sminSmoothing |
float | 0-10 | SDF smoothing |
u_heightTransitionWidth |
float | 1-50 | Height field transition |
u_blurRadius |
float | 0-20 | Background blur |
u_chromaticAberration |
float | 0-20 | RGB color split |
u_brightness |
float | 0.5-2.0 | Brightness multiplier |
u_refractionInset |
float | 0-100 | Edge fade distance |
u_edgeRefractionFalloff |
float | 1-10 | Edge decay sharpness |
u_shadowColor |
vec4 | RGBA | Inner shadow color |
u_shadowSoftness |
float | 0-1 | Shadow blur extent |
u_showNormals |
int | 0/1 | Debug mode |
Performance
Per-frame operations:
- 27 texture samples per fragment (9 × 3 RGB)
- 4 height field evaluations for gradients
- 2 refraction calculations
- Early fragment discard for optimization
Typical performance:
- 60 FPS on mid-range devices (Snapdragon 600+)
- ~2-3ms frame time for 300×200dp surface
- Scales efficiently with hierarchy complexity
Shader Setup
Directory Structure
app/
└── src/
└── main/
└── res/
└── raw/
├── background_vert.vert
└── background_frag.frag
└── fragment_shader.frag
└── vertex_shader.frag
Vertex Shader
It takes vertex positions (a_position) of a quad representing the glass surface, scales them by u_glassSize, and offsets them around the mouse position (u_mousePos). It then converts the result into clip-space coordinates for rendering (gl_Position). The shader also outputs normalized texture and shape coordinates for use in the fragment shader.
Fragment Shader
The complete fragment shader is provided in fragment_shader.glsl. It includes:
- Smooth min/max polynomial functions
- SDF rounded box calculations
- Height field generation
- Fresnel computation
- Surface gradient calculation
- Shape opacity functions
- Blur implementation with shape awareness
- Main rendering pipeline
Loading Shaders
object ShaderUtils {
fun loadFromAssets(context: Context, filename: String): String {
return context.assets.open(filename).bufferedReader().use { it.readText() }
}
}
class PrismalGlassRenderer(private val context: Context) : GLSurfaceView.Renderer {
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
val vertexSource = ShaderUtils.loadFromAssets(context, "shaders/vertex_shader.glsl")
val fragmentSource = ShaderUtils.loadFromAssets(context, "shaders/fragment_shader.glsl")
// Compile and link shaders...
}
}
Integration Guide
Basic Integration
- Add dependency to
build.gradle - Create shader files in
assets/shaders/ - Add Prismal components to layouts
- Configure optical parameters
Custom Glass Materials
object GlassMaterials {
fun applyWindowGlass(layout: PrismalFrameLayout) {
layout.apply {
setIOR(1.52f)
setThickness(20f)
setBlurRadius(1f)
setChromaticAberration(1.5f)
}
}
fun applyFrostedGlass(layout: PrismalFrameLayout) {
layout.apply {
setIOR(1.5f)
setThickness(15f)
setBlurRadius(8f)
setChromaticAberration(0.5f)
}
}
}
Background Updates
// Manual update
glassLayout.updateBackground()
// After layout changes
glassLayout.viewTreeObserver.addOnGlobalLayoutListener {
glassLayout.updateBackground()
}
// Throttled updates for performance
private var lastUpdate = 0L
private val updateInterval = 50L
fun updateIfNeeded() {
val now = System.currentTimeMillis()
if (now - lastUpdate > updateInterval) {
glassLayout.updateBackground()
lastUpdate = now
}
}
Performance Optimization
Texture Capture
Minimize capture frequency:
// Good - manual control
glassLayout.updateBackground()
// Bad - unnecessary updates
glassLayout.setOnClickListener {
glassLayout.updateBackground() // Content unchanged
}
Render Mode
// Continuous - animations
glSurfaceView.renderMode = GLSurfaceView.RENDERMODE_CONTINUOUSLY
// On-demand - static content
glSurfaceView.renderMode = GLSurfaceView.RENDERMODE_WHEN_DIRTY
glSurfaceView.requestRender()
Device-Specific Tuning
data class GlassSettings(
val ior: Float,
val blur: Float,
val chromatic: Float
) {
companion object {
val HIGH_QUALITY = GlassSettings(1.75f, 4f, 8f)
val BALANCED = GlassSettings(1.6f, 2f, 4f)
val LOW_QUALITY = GlassSettings(1.5f, 1f, 0f)
}
}
Design Guidelines
Recommended Parameter Ranges
| Effect | Subtle | Moderate | Dramatic |
|---|---|---|---|
| IOR | 1.3-1.4 | 1.5-1.6 | 1.7-2.0 |
| Blur Radius | 0.5-1.5 | 2.0-4.0 | 5.0-10.0 |
| Normal Strength | 0.5-2.0 | 3.0-8.0 | 10.0-20.0 |
| Chromatic Aberration | 0.5-1.5 | 2.0-5.0 | 6.0-15.0 |
| Brightness | 1.0-1.1 | 1.15-1.3 | 1.4-1.8 |
Best Practices
- Keep glass surfaces under 400×400dp for optimal performance
- Use higher brightness (1.2-1.5) for content containers
- Reduce blur for text readability
- Match corner radius to app design language
- Consider device capabilities for parameter selection
Troubleshooting
Common Issues
Glass effect not visible
- Verify shaders in
assets/shaders/ - Ensure background content exists
- Call
updateBackground()after layout - Increase
normalStrengthordisplacementScale
Distortion too strong
- Reduce
normalStrengthto 1.0-3.0 - Lower
displacementScaleto 0.5-1.0 - Decrease
glassThicknessto 10-15
Performance problems
- Reduce
blurRadiusto 1-2 - Decrease surface dimensions
- Use
RENDERMODE_WHEN_DIRTYfor static content - Disable chromatic aberration on low-end devices
Sharp/pixelated edges
- Increase
minSmoothingto 2-5 - Ensure appropriate
cornerRadius - Verify EGL config includes anti-aliasing
Debug Mode
glassLayout.setShowNormals(true)
Displays surface normals as RGB colors for debugging surface calculations.
Sample Implementation
class MainActivity : AppCompatActivity() {
private lateinit var glassContainer: PrismalFrameLayout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
glassContainer = findViewById(R.id.glassContainer)
glassContainer.apply {
setIOR(1.5f)
setBlurRadius(3f)
setCornerRadius(20f)
setBrightness(1.2f)
}
}
override fun onResume() {
super.onResume()
glassContainer.updateBackground()
}
}
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.matrix.prismal.PrismalFrameLayout
android:id="@+id/glassContainer"
android:layout_width="300dp"
android:layout_height="200dp"
android:layout_centerInParent="true"
app:ior="1.5"
app:blurRadius="3"
app:cornerRadius="20dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Glass Container"
android:textColor="@android:color/white"
android:layout_gravity="center" />
</com.matrix.prismal.PrismalFrameLayout>
</RelativeLayout>
Contributing
Contributions are welcome. Please follow these guidelines:
- Fork the repository
- Create a feature branch
- Follow Kotlin coding conventions
- Add tests for new features
- Update documentation
- Submit pull request with clear description
Support
- Issues: GitHub Issues
- Documentation: Library Documentation
- Changelog: CHANGELOG.md
