The shader are small programs running at the GPU level, widely used in computer graphics and video games. They take pixel coordinates as input and return a color as output. Shaders are designed to work on multiple pixels at the same time thanks to the parallelization. There are different “dialects” for managing shaders: one of the best known and most used is GLSL (OpenGL Shading Language), widely supported by web browsers.
What is GLSL and why is it useful for working with shaders
GLSL is a language used primarily for writing graphics shaders; is a specific dialect of the C programming language, designed to operate on the GPU. Thanks to GLSL they can perform graphics operations such as creating visual effects, manipulating textures, defining lighting, applying materials and many other additional operations. The wide range of data types, functions and structures available to the developer allow sophisticated shaders to be written to create complex visual effects and detailed.
The basic operation of shaders
The pixel coordinates in shaders they are normalized between 0 and 1, with (0, 0) in the bottom left corner and (1, 1) in the top right corner. These coordinates are commonly referred to as “st” or “uv”. Shaders can intervene on these coordinates to generate various graphic effects.
A classic example of a shader, and a very simple one at that, is a gradient where the red component increases from left to right and the green component increases from bottom to top. The gradient can be created in GLSL like this:
precision highp float;
uniform vec2 resolution;
void main() {
// Normalized pixel coordinate (0 to 1)
vec2 st = gl_FragCoord.xy / resolution.xy;// Red from left to right, green from bottom to top
gl_FragColor = vec4(st.x, st.y, 0.0, 1.0); // RGBA
}
To try the GLSL code in the browserwe suggest using the tool called GLSL Sandbox – just click the button New shader and paste the code seen above. The gradient effect generated using the shader is displayed as the background of the code.
In detail, precision highp float
specifies the floating point precision used within the shader. The line uniform vec2 resolution
represents the resolution of the rendering targeti.e. the size of the window or the canvas where the shader is executed and its graphical result displayed. This value is provided externally at shader execution and can be used to normalize pixel coordinates.
The variable gl_FragCoord.xy
contains the (x, y) coordinates of the current pixel. Dividing the coordinates by resolution.xy
and normalize the coordinates of the pixel in the range from 0 to 1 along the x and y axis. With st.x
is represented horizontal position normalized of the pixel (from left to right), while st.y
represents the upright position normalized of the pixel (from bottom to top).
The last line assigns the colore al pixel current: gl_FragColor
is a predefined variable in GLSL that represents the color of the current pixel. With vec4()
a 4-component vector (RGBA) is created where st.x
represents red, st.y
green, `0.0` blue, and `1.0` opacity. In the case of this shader, the red varies from left to right and the green from bottom to top, thus creating a gradient.
Use shaders to create complex shapes
Shaders can also be used to create complex shapes by combining distances from the points of the shapes you wish to obtain. These distances can be calculated using functions such as distance()
which establishes the distance between a point and the center of a shape. Additionally, functions such as step()
to place some transitions clear, thus creating rather complex shapes.
The function step()
is a function that allows you to make a clear transition between two values, producing an output based on a threshold comparison. In shader programming, it is useful for creating sharp transition effects or defining areas where a value exceeds a certain threshold.
How the step() and distance() functions behave
In the context of the shader examined above, the function step()
is exploited to obtain a clear edge for a shape, such as a circle. Considering the distance of each pixel from the center of the circle, the distance is calculated using the function distance()
which measures the distance between two points in the plane.
The result of distance()
is passed as input to the function step()
. This function uses two parameters: a threshold value (threshold) and a value to compare (value). If the value is greater than the threshold step()
returns 1, otherwise 0. This means that a net change from 0 to 1 is created when the distance exceeds threshold value. In the shader under consideration, if the distance d
exceeds 0.25, the value of s
it becomes 1, otherwise it is 0.
Added anti-aliasing, motion and interactivity
This approach is used to generate a clear edge or a defined outline for the shape, but it can cause aliasing or “sawjob” effects around the edges of the shape due to the abrupt transition between 0 and 1. To mitigate this problem, you can resort to another function, smoothstep()
which allows for a smoother transition between values, helping to achieve softer edges and fewer visual artifacts.
Furthermore, you can use the trigonometric functions come sin()
e cos()
to control the movement of shapes in a shader and create visually interesting effects.
It can also be added interactivity to shaders allowing the user to control parameters such as the position of shapes. For example, you can use mouse coordinates as input into your shader and adjust parameters in real time.
The mechanism described may appear demanding but the functions available allow you to model with precision and make truly impressive creatives. Try visiting Shadertoy to realize what you can create, in the end, with just a few lines of code.
To learn more, we suggest reading the article “A Journey Into Shaders” by Antoine Mayerowitz which guides you to discover further characteristics of shaders.
To invoke a shader from a web page, you need to use WebGL, a technology that allows you to use high-performance 3D and 2D graphics within the browser. Three.js is one JavaScript library for creating 3D graphics using WebGL like backend for rendering purposes. It includes functionality for writing and using GLSL shaders within its rendering frameworks.
Opening image credit: iStock.com/Ross Tomei