Shader is a program that runs on the GPU to process graphical data: vertices, fragments, or computational tasks. Unlike regular CPU code, a shader runs in parallel on hundreds of cores, processing each element independently. According to Khronos Group (2025), shaders are used in 100% of modern 3D applications — from mobile games to UI effects in system applications. Developers write shaders in specialized languages: GLSL, HLSL, Metal Shading Language, or SPIR-V.
Key Takeaways
Shader — is a small program written in a specialized language and executed on the GPU. Each shader processes one data element — a vertex, fragment (pixel), or work item — independently from others. GPU parallelism allows running thousands of shader instances simultaneously.
The term was introduced by Pixar in the 1980s to describe programs that control the appearance of surfaces in RenderMan. Real-time shaders appeared with the NVIDIA GeForce 3 (2001) — the first graphics card with a programmable pipeline. Before shaders, graphics were configured through fixed parameters: color, texture, blending mode.
According to Unity Technologies (2025), the average mobile game uses 50–200 shaders. Each shader has variants for different platforms, quality levels, and lighting configurations. The number of variants can reach 10,000 per project.
Shader architecture consists of input data (attributes, uniform variables, textures), program code, and output data (color, position). Uniform variables are set from the CPU before shader execution and remain constant for all elements of the frame. Attributes are per-element data (vertex coordinates, normal, UV coordinates).
SIMT (Single Instruction, Multiple Threads) — the execution model for shaders on the GPU. One instruction is loaded once and executed by hundreds of threads on different data. For example, a Fragment Shader for 1000 pixels launches 1000 threads with the same code, but each thread receives its own UV coordinates and interpolated attributes.
An important consequence of SIMT: branching in shaders is expensive. If inside an if block half the threads go one way and the other half goes another, the GPU executes both branches sequentially for all threads. Performance drops to 50%. Avoid dynamic branching in shaders or minimize it through pre-computation on the CPU.
Modern GPUs support several types of shaders, each for its own stage of the graphics or compute pipeline. Let’s review the main types: vertex, fragment, compute, and modern mesh shaders.
| Shader Type | Pipeline Stage | Processes | Language |
|---|---|---|---|
| Vertex Shader | Vertex | Each vertex | GLSL, HLSL |
| Fragment Shader | Fragment | Each pixel | GLSL, HLSL |
| Compute Shader | Compute | Arbitrary data | GLSL, HLSL |
| Geometry Shader | Geometry | Primitives | GLSL, HLSL |
| Tessellation Shader | Tessellation | Patches | GLSL, HLSL |
| Mesh Shader | Mesh pipeline | Task + Mesh | HLSL, MSL |
Vertex Shader processes each geometry vertex: transforms coordinates, computes lighting, passes data to the fragment shader. It is a mandatory stage of the graphics pipeline — without it, vertices won’t reach the screen. The Vertex Shader runs for every vertex of every frame.
Fragment Shader (or Pixel Shader) — the most resource-intensive stage. It computes the final color of each fragment using textures, lighting, and material. The Fragment Shader runs for every pixel covered by geometry, accounting for all overlaid textures and effects. Optimizing this shader yields the greatest performance gain.
Compute Shader — a universal shader for arbitrary GPU computations. Unlike graphics shaders, it is not tied to geometry or pixels. The Compute Shader works with arbitrary data buffers through work groups. It is used for physics, particle simulation, post-effects, and neural network inference.
Mesh Shader — the newest type of shader, introduced with NVIDIA Turing (2018) and Metal 3. The Mesh Shader replaces vertex, geometry, and tessellation shaders with a single mesh pipeline. It works in tandem with Task Shader: the Task Shader decides which mesh groups to render, and the Mesh Shader generates geometry on the fly.
Shaders are written in specialized languages that compile to GPU machine code. The language choice depends on the platform and API. Let’s review the main languages: GLSL, HLSL, Metal Shading Language, and the intermediate format SPIR-V.
GLSL — the shader language for OpenGL, OpenGL ES, and WebGL. Its syntax is based on C with the addition of vector types (vec2, vec4, mat4) and built-in functions (texture, normalize, reflect). GLSL ES is the version for mobile devices with limited precision (lowp, mediump, highp). According to Khronos (2025), GLSL remains the most widespread shader language thanks to cross-platform support.
#version 300 es
precision mediump float;
in vec2 v_texCoord;
uniform sampler2D u_texture;
out vec4 fragColor;
void main() {
fragColor = texture(u_texture, v_texCoord);
}
A simple fragment shader reads color from a texture by UV coordinates. precision mediump instructs the GPU to use half precision for floats — speeding up execution on mobile devices by 25–40%.
HLSL — Microsoft’s shader language for DirectX. Its syntax is closer to C++ with support for classes, structures, and templates. HLSL is used in Windows applications and through Shader Model 6.7+ supports Ray Tracing, Mesh Shaders, and Sampler Feedback. For mobile development, HLSL is not used directly but compiles to SPIR-V for Vulkan.
MSL (Metal Shading Language) — Apple’s shader language based on C++14. Unlike GLSL and HLSL, MSL is compiled together with the application, eliminating JIT compilation on the device. MSL supports pointers, templates, and the C++ standard library. It is used on all Apple devices: iPhone, iPad, Mac, Apple TV.
#include <metal_stdlib>
using namespace metal;
struct VertexOut {
float4 position [[position]];
float2 texCoord;
};
fragment float4
myFragment(VertexOut in [[stage_in]],
texture2d<float> tex [[texture(0)]]) {
constexpr sampler s = sampler(filter::linear);
return tex.sample(s, in.texCoord);
}
MSL uses [[position]], [[stage_in]], and [[texture(N)]] attributes for resource binding. Apple’s compiler generates optimized code for the current GPU, accounting for the number of registers and cache.
SPIR-V — an intermediate binary format for shaders, the standard for Vulkan. Shaders are written in GLSL or HLSL, compiled into SPIR-V, and loaded into Vulkan applications. SPIR-V is not tied to a specific language — there are compilers from Rust, Python, OpenCL C to SPIR-V.
Shaders on mobile platforms have limitations compared to desktop: fewer registers, limited precision, and lack of support for certain instructions. Let’s examine the specifics of shaders on Android (Vulkan/OpenGL ES) and iOS (Metal).
Android uses GLSL ES for OpenGL and SPIR-V for Vulkan. OpenGL ES 3.2 supports shaders with mediump by default. Vulkan requires explicit compilation of GLSL to SPIR-V via glslangValidator. On Android, shaders are loaded from string resources or from compiled SPIR-V files.
Qualcomm Adreno GPU optimizes shaders at the driver level. Recommendations: use mediump for color and UV, highp only for positions; avoid texture lookups in vertex shaders; group computations into vectors (vec4 instead of 4x float). According to Qualcomm (2025), these optimizations yield 30–50% improvement.
iOS exclusively uses Metal Shading Language. Shaders are compiled into machine code along with the application via Xcode. Metal provides Shader Debugger and GPU Capture for shader profiling. Apple GPU (TBDR) has specific optimizations: early fragment test, memoryless render targets, and programmable blending.
According to Apple WWDC 2024, for iOS shaders it is critical to use half instead of float, limit register pressure (max 64 registers), and avoid dependent texture lookups. iOS shaders should be compact — optimal size is 50–100 ALU instructions.
Game engines Unity and Unreal Engine provide visual shader editors (Shader Graph, Material Editor) and abstract languages (ShaderLab, USF). The developer writes shaders in a high-level language, and the engine compiles for the target platform. Unity uses HLSL as an intermediate language, Unreal uses USF (Unreal Shader Format).
Shader optimization — a key stage in graphics development for mobile devices. A poorly written shader can drop FPS from 60 to 20. Let’s review the main rules and optimization techniques.
Use the minimum sufficient precision: lowp for color and UV, mediump for normals and lighting, highp only for positions and matrices. According to ARM (2025), mediump operations are 2 times faster than highp on Mali GPU. In GLSL ES, this is specified through precision qualifiers.
Texture lookups — the most expensive operation in a shader (10–30 cycles vs 1–2 for an ALU instruction). Reduce the number of lookups: combine textures into atlases, store data in uniform arrays instead of textures, cache neighboring pixel results through derivative instructions.
Dynamic branching (if with a uniform variable) reduces SIMT pipeline performance. Replace branching with mathematical functions: mix(), step(), smoothstep(), and clamp(). These functions execute in 1–2 instructions, while branching can cost 8–16 instructions due to thread divergence.
// Bad: dynamic branching in shader
if (u_enableLight) {
color *= computeLighting(normal);
}
// Good: mathematical branch replacement
color *= mix(1.0, computeLighting(normal),
float(u_enableLight));
mix() performs linear interpolation between two values. When u_enableLight=0, it returns 1.0 — the color stays unchanged. When u_enableLight=1, it returns the lighting result. No branching — all threads execute the same code.
Let’s explore practical shader examples for mobile development — from simple shading to procedural texture generation. Each example demonstrates a specific technique.
Phong model — a classic lighting model with diffuse and specular components. The Vertex Shader computes lighting for each vertex, the Fragment Shader only interpolates the result (Gouraud shading). Suitable for mobile devices due to low cost.
#version 300 es
layout(location = 0) in vec4 a_position;
layout(location = 1) in vec3 a_normal;
uniform mat4 u_mvp;
uniform mat4 u_modelView;
out vec3 v_color;
void main() {
vec3 normal = normalize(mat3(u_modelView) * a_normal);
vec3 lightDir = normalize(vec3(0.0, 1.0, 1.0));
float diff = max(dot(normal, lightDir), 0.0);
v_color = vec3(0.8, 0.2, 0.2) * (0.3 + diff * 0.7);
gl_Position = u_mvp * a_position;
}
The shader computes diffuse lighting: the dot product of the normal and light direction. The result is blended with ambient (0.3) and diffuse (0.7) components. It executes 10–15 ALU instructions per vertex — acceptable for 100,000 polygons at 60 FPS on a mobile GPU.
Procedural textures are generated in the Fragment Shader without loading from a file. This saves video memory and simplifies animation. Example — a checkerboard with adjustable cell size. Just a few ALU instructions — minimal GPU load.
#version 300 es
precision mediump float;
in vec2 v_uv;
uniform float u_cells;
out vec4 fragColor;
void main() {
vec2 cell = floor(v_uv * u_cells);
float isWhite = mod(cell.x + cell.y, 2.0);
fragColor = mix(vec4(0.1, 0.1, 0.1, 1.0),
vec4(0.9, 0.9, 0.9, 1.0),
isWhite);
}
8 ALU instructions — an ideal shader for mobile devices. u_cells sets the number of cells per axis. floor and mod are cheap operations, executed in 1 cycle on all mobile GPUs.
Frequently Asked Questions
Shader runs on the GPU in SIMT mode — one instruction for hundreds of threads. A regular C++ program runs on the CPU in MIMD mode. A shader has no access to the file system, I/O, or dynamic memory. It receives data through buffers and textures and returns a position or color.
GLSL — for OpenGL ES and WebGL, cross-platform. Metal Shading Language — for iOS. HLSL — the foundation for Unity and Unreal Engine. Start with GLSL — understanding the basics transfers to any shader language.
Main reasons: high precision (highp instead of mediump), excessive texture lookups, dynamic branching, and exceeding the register limit. Use RenderDoc or Xcode GPU Frame Debugger for shader analysis.
Compute Shader — a shader for arbitrary GPU computations: particle physics, image processing, cloth simulation. Unlike the Fragment Shader, it is not tied to pixels and can write to arbitrary buffers.
Yes, through visual editors: Shader Graph in Unity, Material Editor in Unreal Engine, or Metal Shader Converter. They generate shader code based on a node graph. Deep optimization will require understanding shader languages.
Summary
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також