Shading the Canvas: A Beginner’s Guide to Vertex and Fragment Shaders
Hello everyone! Today, we’re exploring Vertex and Fragment Shaders in OpenGL ES 3. These dynamic components are vital for visual transformations. We’ll simplify their roles, introduce OpenGL Shading Language (GLSL), and demonstrate practical implementation in Android development. 🚀✨
What is Shader?
A Shader is a user-defined program designed to run on some stage of a graphics processor. Shaders provide the code for certain programmable stages of the rendering pipeline. They can also be used in a slightly more limited form for general, on-GPU computation.
In simple terms, a shader is a program that compiles, and the GPU executes its instructions. In OpenGL ES, we encounter two crucial shaders: Vertex and Fragment shaders. The Vertex shader shapes object geometry, processing vertex data, while the Fragment shader defines pixel colors, adding shading and texture details. Together, these shaders form the core of the graphics rendering pipeline, essential for creating compelling visual experiences in OpenGL ES.
What is Vertex Shader?
The Vertex Shader is the programmable Shader stage in the rendering pipeline that handles the processing of individual vertices. Vertex shaders are fed Vertex Attribute data, as specified from a vertex array object by a drawing command. A vertex shader receives a single vertex from the vertex stream and generates a single vertex to the output vertex stream. There must be a 1:1 mapping from input vertices to output vertices.
What is Fragment Shader?
A Fragment Shader is the Shader stage that will process a Fragment generated by the Rasterization into a set of colors and a single depth value.
Think of the Vertex Shader as positioning and shaping a shape, while the Fragment Shader handles its color or texture. It’s that simple: Vertex sets the stage, and Fragment adds the color!
For a more in-depth understanding, I’ve quoted the main definitions from the Khronos Wiki. Take a moment to explore them for a deeper dive into the intricacies of Vertex and Fragment Shaders.
Unveiling GLSL: The Language of Shaders
OpenGL Shading Language (GLSL) is a high-level shading language with a syntax based on the C programming language. It was created by the OpenGL ARB (OpenGL Architecture Review Board) to give developers more direct control of the graphics pipeline without having to use ARB assembly language or hardware-specific languages.
GLSL is like a special language for talking to your computer’s graphics card. In GLSL, you have the flexibility to perform various mathematical calculations not only on scalar numbers but also on vectors and matrices. This high-level shading language supports user-defined functions and provides built-in functions like max
, min
, pow
, sin
, dot
, normalize
, clamp
, and more.
GLSL boasts a versatile array of types for graphics programming. It starts with scalars like float
, int
, and bool
, then extends to vectors (vec2
, vec3
, vec4
), boolean vectors (bvec2
, bvec3
, bvec4
), and integer vectors (ivec2
, ivec3
, ivec4
). For matrices, there are mat2
, mat3
, and mat4
. Texture sampling is facilitated through sampler1D
, sampler2D
, sampler3D
, and samplerCube
.
Exploring Sample Codes in OpenGL ES 3
#version 300 es
void main() {
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
}
In this basic example, the Vertex Shader sets the position of the vertex to the center of the screen.
Quick Note: In OpenGL, we operate in a Right-Handed Coordinate System (RHS), where the position of the center is located at (x = 0, y = 0, z = 0). For a deeper understanding and further details, feel free to explore this article.
#version 300 es
precision mediump float;
out vec4 FragColor;
void main() {
FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
Here, the Fragment Shader colors the fragment red (vec4(red, green, blue, alpha)
).
Understanding Shader Variable Qualifiers in GLSL: uniform, in, and out
In GLSL, uniform
, in
, and out
are qualifiers used to define the roles and behavior of variables in shaders.
uniform
: This qualifier is used for variables that do not change during the execution of a shader. They are constants that remain consistent across all processed vertices or fragments. Typically, uniform
variables are used for values that are the same for all vertices or fragments, such as transformation matrices, lighting parameters, or texture samplers.
in
: This qualifier is used for input variables in a shader. In the Vertex Shader, in
variables receive data from the application (e.g., vertex attributes). In the Fragment Shader, in
variables typically receive interpolated values from the Vertex Shader.
out
: This qualifier is used for output variables in a shader. In the Vertex Shader, out
variables pass data to the next stage of the pipeline, usually the Fragment Shader. In the Fragment Shader, out
variables are used to specify the final output color or other values.
Check out the following Vertex and Fragment Shaders:
#version 300 es
in vec3 position;
out vec4 vertexColor;
void main() {
if (position.x < -0.5) {
vertexColor = vec4(1.0, 0.0, 0.0, 1.0);
} else if (position.x >= -0.5 && position.x <= 0.5) {
vertexColor = vec4(0.0, 1.0, 0.0, 1.0);
} else {
vertexColor = vec4(0.0, 0.0, 1.0, 1.0);
}
gl_Position = vec4(position, 1.0);
}
#version 300 es
precision mediump float;
in vec4 vertexColor;
out vec4 FragColor;
void main() {
FragColor = vertexColor;
}
The Vertex Shader takes a 3D position (x, y, z) as input and assigns a color (vertexColor
) based on the X-coordinate of the position. If the X-coordinate is less than -0.5 (Left area), the color is set to red; if it's between -0.5 and 0.5 (Center area), the color is green; otherwise (Right area), it's blue. The resulting position and color are then passed to the Fragment Shader for further processing in rendering.
The Fragment Shader simply passes the color (vertexColor
) received as input to the output variable FragColor
. In other words, it takes the color calculated in the Vertex Shader and directly assigns it to the final color of the fragment, resulting in a straightforward color rendering without additional modifications.
Let’s start implementation in Android!