This content originally appeared on DEV Community and was authored by Denis
Creating dynamic visual effects for mobile applications requires developers not only to have a creative approach but also to meet performance requirements. One of the most efficient techniques for implementing smooth transitions and transformations of objects is the use of shaders, which allow complex parallel computations to be performed on the GPU. This not only ensures smooth animations but can also reduce the load on the CPU by offloading resource-intensive tasks to the graphics processor in certain scenarios, which is especially important for mobile devices with limited resources.
This article will explore an example of implementing smooth morphing animation of geometric shapes using SDF (Signed Distance Functions) and GLSL for graphical rendering.
Basics of SDF
Before diving into the implementation of smooth morphing between geometric shapes, it's important to first understand the principles of working with SDF. SDF (Signed Distance Function) is a mathematical model that defines the distance from a point to the nearest surface of an object. Each shape, whether it is a circle, square, or polygon, can be described by its unique SDF, which allows for operations like intersections, unions, and differences of shapes. In the context of morphing between two shapes, this allows for the creation of a continuous transition, integrating additional effects like deformation or smoothing.
Shapes Using SDF
To illustrate the workings of SDF, let's consider a few examples of geometric shapes that can be used:
1. Circle (sdCircle)
The sdCircle
function calculates the distance from a point to a circle with radius r
. The distance is calculated as the difference between the length of the vector from the point to the center of the circle and the radius.
•
length(p)
— is the length of the vector p
, which represents the point's coordinates relative to the circle's center.•
r
— is the radius of the circle.If the point is on the circle, the distance will be zero. If the point is inside the circle, the result will be negative, and if outside, the result will be positive.
2. Square (sdSquare)
The
sdSquare
function calculates the distance from a point to the nearest edge of a square with side length 2 * r,
where the square's center is at the origin.•
abs(p)
— function that converts coordinates to their absolute values, symmetrically mapping them to the first quadrant of the coordinate system.•
max(p.x, p.y)
— returns the greatest of the x
or y
coordinates, which corresponds to the farthest point from the center within the square.•
r
— is half the side length of the square.3. Diamond (sdDiamond)
The
sdDiamond
function calculates the distance to a diamond-shaped figure with size r
and an aspect ratio aspect
, which controls the stretching of the diamond along one axis.•
abs(p)
— function that converts coordinates to their absolute values, symmetrically mapping them to the first quadrant of the coordinate system.•
p.x *= aspect
— stretches the x-coordinate based on the aspect parameter, changing the shape of the diamond.• The expression
p.x + p.y - r
calculates the boundary of the diamond.4. Regular Polygon (sdRegularPolygon)
The
sdRegularPolygon
function computes the distance to a regular polygon with n
sides and a radius defining the distance from the center to the vertices.•
float an = 3.141593 / float(n)
calculates the angular value for each side of the polygon.•
vec2 acs = vec2(cos(an), sin(an))
is the directional vector for one side of the polygon.• The expression
atan(p.x, p.y)
calculates the angle of the point relative to the X-axis, and mod(atan(p.x, p.y), 2.0 * an) - an
returns the angle relative to the nearest side of the polygon.• The transformation
p = length(p) * vec2(cos(bn), abs(sin(bn)))
normalizes the point in the new coordinate system aligned with the polygon's side.In addition to the previously discussed functions for various geometric shapes, it is worth noting that a square can also be considered a regular polygon with four sides
(n = 4)
. In this case, we use the sdRegularPolygon function but need to scale the coordinates to match the square.5. Star (sdStar)
The
sdStar
function calculates the distance from a point to a 5-ray star. Unlike other shapes such as circles, squares, and diamonds, the star has a more complex structure due to the multiple rays that need to be precisely computed.•
const vec2 k1 = vec2(0.809016994375, -0.587785252292)
defines the first directional vector k1
corresponding to a 36° angle (one of the star's rays).•
const vec2 k2 = vec2(-k1.x, k1.y)
defines the second directional vector k2
, the mirror reflection of k1
along the Y
-axis.•
p.x = abs(p.x)
ensures that the x
-coordinate is positive.•
p -= 2.0 * max(dot(k1, p), 0.0) * k1
adjusts the point relative to the first directional ray. If the point lies beyond the ray, it is reflected relative to the ray. The same transformation applies for k2
.•
p.y -= r
adjusts the y-coordinate by subtracting the star's radius r
to account for the star's size.•
vec2 ba = rf * vec2(-k1.y, k1.x) - vec2(0, 1)
defines the vector used for further reflections based on the radius and scaling factor rf
.• The final calculation computes the distance from the point to the star, considering all transformations and reflections, and uses the sign to determine the point's position relative to the star.
The magic numbers in vectors
k1
and k2
define the key angles of the star, ensuring the rays are oriented correctly. These values are tied to the 36° and 72° angles used to form the star properly.
Morphing Shapes
Based on the principles of working with Signed Distance Functions (SDF), can proceed with morphing between shapes. During the morphing process, the shader calculates two distance values (for the first and second shapes) for each point on the screen (pixel), and then performs interpolation using the morphFactor
parameter. Depending on the value of this factor, obtain an intermediate form that is a mixture of the two shapes.
Here’s an example of a function with transformation:
Where:
•
getShapeDistance
is a function that returns the distance to the selected shape (circle, square, etc.), depending on the passed shape indices.•
p
represents the normalized fragment coordinates in space, where the center of the screen is (0, 0)
and the coordinate ranges for both axes (X
and Y
) are from -1 to 1.•
d1
and d2
are the SDF values for two shapes.•
morphFactor
is the morphing factor. When morphFactor
is 0, the first shape is displayed, and when it is 1, the second shape is displayed. Gradually changing this factor creates a smooth transition between the two shapes.
Color Effects in Shaders
An important aspect of morphing is working with color. Color influences the perception of objects and enhances visual effects by creating smooth transitions between states. It’s important to note that color can be used to enhance depth and lighting perception during the morphing animation. To achieve this, various visual effects are applied to integrate color into the shape transformation process and create a smooth and natural transition between different states.
Here’s how color effects can be integrated into the morphing process, enhancing visual perception:
In this fragment of code:
• The variable
col
of type vec3
stores the color.• Inside the
if (isColorMode)
block, it checks if the color mode is active. If enabled, it selects either the external or internal color based on the value of d
(the distance to the object's surface). If the point is outside the object (d > 0.0)
, the external color (externalColor)
is used; otherwise, the internal color (internalColor)
is used.• The color intensity is adjusted using the formula
col *= 1.05 - exp(-6.0 * abs(d));
creating a fading effect. The color becomes brighter as it approaches the surface and fades as the distance increases.• A dynamic color hue shift is applied with
col *= 0.8 + 0.2 * cos(110.0 * d);
creating a pulsating or lighting change effect.• The
mix(col, vec3(1.0), 1.0 - smoothstep(0.0, 0.01, abs(d)));
function smoothly transitions the color towards white near the surface, particularly when d is small.• If the color mode is off, the standard white
vec3(1.0)
is used for points outside the shape and black vec3(0.0)
for points inside.
Integrating the Shader on Android with OpenGL ES
After explaining the principles of shader construction, the next step for integrating it into Android is setting up the rendering of geometric shapes with morphing and color effects. An important step in this process is creating the OpenGL environment, loading shaders, and passing parameters to these shaders. All of this can be achieved using the GLSurfaceView
component for rendering, which provides access to OpenGL ES on Android devices. As an example, we will create a class MorphGLSurfaceView
, inherited from GLSurfaceView
, where the renderer is initialized using the setRenderer
method. Additionally, a method updateShaderValue
will be created to update shader values, allowing parameters such as the morphing slider value, selected shapes, color mode, and object colors to be passed:
To prepare the rendering process, we define a
MorphRenderer
class, which will initialize the shaders, pass values, and handle updates for displaying the morphing effect. The shaders are loaded using the loadShaderFromRawResource
function within the onSurfaceCreated
method, sourced from raw resources and compiled via the loadShader
function. Additionally, the class will include an array vertices
that contains the vertex coordinates defining the geometry of the object. These coordinates are stored in an array, with each element corresponding to a single vertex. Each vertex is represented by a triplet of numbers, where each triplet specifies spatial coordinates (X, Y, Z
). The shaders will later use these coordinates for transforming and visualizing the object.When the surface changes (in the
onSurfaceChanged
method), it is necessary to set the viewport for OpenGL:Then, in the
onDrawFrame
method, rendering takes place. First, the screen is cleared using GLES20.glClear
. After that, the shader program is activated. Next, all the required parameters, such as resolution, the slider value for morphing, shape selection, and color parameters, are passed via uniforms:To update values during runtime, methods such as
updateSliderValue
, updateSelectedShape
, updateColorMode
, and updateColors
are used.Next, using the
AndroidView
component from Compose, shaders can be dynamically updated as parameters change. This enables direct interaction with rendering from the UI layer of the application, creating smooth and fast transitions between morphing states and color effects:As a result of integrating the shader with OpenGL ES on Android, a system is created where the morphing of geometric shapes and the dynamic color changes of objects occur in real time using the GPU.
The full code is available via the link on GitHub.
This content originally appeared on DEV Community and was authored by Denis
Denis | Sciencx (2025-01-10T21:15:01+00:00) Morphing Geometric Shapes with SDF in GLSL Fragment Shaders and Visualization in Jetpack Compose. Retrieved from https://www.scien.cx/2025/01/10/morphing-geometric-shapes-with-sdf-in-glsl-fragment-shaders-and-visualization-in-jetpack-compose/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.