Shader (shader) — to program wykonywany na GPU do przetwarzania danych graficznych: wierzchołków, fragmentów lub zadań obliczeniowych. W przeciwieństwie do zwykłego kodu na CPU, shader działa równolegle na setkach rdzeni, przetwarzając każdy element niezależnie. Według danych Khronos Group (2025), shadery są używane w 100% nowoczesnych aplikacji 3D — od gier mobilnych po efekty UI w aplikacjach systemowych. Deweloperzy piszą shadery w wyspecjalizowanych językach: GLSL, HLSL, Metal Shading Language lub SPIR-V.
Najważniejsze
Shader — to mały program napisany w wyspecjalizowanym języku i wykonywany na GPU. Każdy shader przetwarza jeden element danych — wierzchołek, fragment (piksel) lub element roboczy — niezależnie od innych. Równoległość GPU pozwala uruchamiać tysiące instancji shadera jednocześnie.
Termin został wprowadzony przez Pixar w latach 80. XX wieku do opisania programów sterujących wyglądem powierzchni w RenderMan. W czasie rzeczywistym shadery pojawiły się z NVIDIA GeForce 3 (2001) — pierwszą kartą graficzną z programowalnym potokiem. Przed shaderami grafikę konfigurowano za pomocą stałych parametrów: kolor, tekstura, tryb mieszania.
Według danych Unity Technologies (2025), przeciętna gra mobilna używa 50–200 shaderów. Każdy shader ma warianty (variants) dla różnych platform, poziomów jakości i konfiguracji oświetlenia. Liczba wariantów może osiągnąć 10 000 na projekt.
Architektura shadera dzieli się na dane wejściowe (atrybuty, zmienne uniform, tekstury), kod programu i dane wyjściowe (kolor, pozycja). Zmienne uniform są ustawiane z CPU przed uruchomieniem shadera i są niezmienne dla wszystkich elementów klatki. Atrybuty to dane na element (współrzędne wierzchołka, normalna, współrzędne UV).
SIMT (Single Instruction, Multiple Threads) — model wykonania shaderów na GPU. Jedna instrukcja jest ładowana raz, a wykonywana przez setki wątków na różnych danych. Na przykład Fragment Shader dla 1000 pikseli uruchamia 1000 wątków z tym samym kodem, ale każdy wątek otrzymuje własne współrzędne UV i interpolowane atrybuty.
Ważna konsekwencja SIMT: rozgałęzienia w shaderach są kosztowne. Jeśli wewnątrz bloku if połowa wątków idzie jedną ścieżką, a druga połowa drugą, GPU wykonuje obie ścieżki sekwencyjnie dla wszystkich wątków. Wydajność spada do 50%. Unikaj dynamicznych rozgałęzień w shaderach lub minimalizuj je poprzez wstępne obliczenia na CPU.
Nowoczesne GPU obsługują kilka typów shaderów, każdy dla swojego etapu potoku graficznego lub obliczeniowego. Omówmy główne rodzaje: wierzchołkowe, fragmentowe, compute i nowoczesne mesh-shadery.
| Typ shadera | Etap potoku | Przetwarza | Język |
|---|---|---|---|
| Vertex Shader | Wierzchołkowy | Każdy wierzchołek | GLSL, HLSL |
| Fragment Shader | Fragmentowy | Każdy piksel | GLSL, HLSL |
| Compute Shader | Obliczeniowy | Dowolne dane | GLSL, HLSL |
| Geometry Shader | Geometryczny | Prymitywy | GLSL, HLSL |
| Tessellation Shader | Mozaikowanie | Płatki | GLSL, HLSL |
| Mesh Shader | Potok Mesh | Task + Mesh | HLSL, MSL |
Vertex Shader przetwarza każdy wierzchołek geometrii: transformuje współrzędne, oblicza oświetlenie, przekazuje dane do fragment shadera. To obowiązkowy etap potoku graficznego — bez niego wierzchołki nie trafią na ekran. Vertex Shader jest wywoływany dla każdego wierzchołka każdej klatki.
Fragment Shader (lub Pixel Shader) — najbardziej zasobożerny etap. Oblicza końcowy kolor każdego fragmentu, używając tekstur, oświetlenia i materiału. Fragment Shader jest wykonywany dla każdego piksela pokrytego geometrią, z uwzględnieniem wszystkich nałożonych tekstur i efektów. Optymalizacja tego shadera daje największy wzrost wydajności.
Compute Shader — uniwersalny shader do dowolnych obliczeń na GPU. W przeciwieństwie do shaderów graficznych, nie jest związany z geometrią ani pikselami. Compute Shader pracuje z dowolnymi buforami danych poprzez grupy robocze. Używany do fizyki, symulacji cząstek, post-efektów i inferencji sieci neuronowych.
Mesh Shader — najnowszy typ shaderów, który pojawił się w NVIDIA Turing (2018) i Metal 3. Mesh Shader zastępuje shadery wierzchołkowy, geometryczny i mozaikowania jednym potokiem mesh. Działa w parze z Task Shader: Task Shader decyduje, które grupy mesh renderować, Mesh Shader generuje geometrię w locie.
Shadery są pisane w wyspecjalizowanych językach, które są kompilowane do kodu maszynowego GPU. Wybór języka zależy od platformy i API. Omówmy główne języki: GLSL, HLSL, Metal Shading Language i format pośredni SPIR-V.
GLSL — język shaderów dla OpenGL, OpenGL ES i WebGL. Składnia oparta na C z dodatkiem typów wektorowych (vec2, vec4, mat4) i wbudowanych funkcji (texture, normalize, reflect). GLSL ES — wersja dla urządzeń mobilnych z ograniczoną precyzją (lowp, mediump, highp). Według danych Khronos (2025), GLSL pozostaje najpopularniejszym językiem shaderów dzięki cross-platformowości.
#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);
}
Prosty fragment shader odczytuje kolor z tekstury po współrzędnych UV. precision mediump nakazuje GPU używać połowicznej precyzji dla float — przyspiesza wykonanie na urządzeniach mobilnych o 25–40%.
HLSL — język shaderów od Microsoft dla DirectX. Składnia bliższa C++ z obsługą klas, struktur i szablonów. HLSL jest używany w aplikacjach Windows i przez Shader Model 6.7+ obsługuje Ray Tracing, Mesh Shaders i Sampler Feedback. Dla programowania mobilnego HLSL nie jest używany bezpośrednio, ale jest kompilowany do SPIR-V dla Vulkan.
MSL (Metal Shading Language) — język shaderów Apple, oparty na C++14. W przeciwieństwie do GLSL i HLSL, MSL jest kompilowany razem z aplikacją, co eliminuje kompilację JIT na urządzeniu. MSL obsługuje wskaźniki, szablony i standardową bibliotekę C++. Używany na wszystkich urządzeniach Apple: 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 używa atrybutów [[position]], [[stage_in]] i [[texture(N)]] do wiązania zasobów. Kompilator Apple generuje zoptymalizowany kod dla bieżącego GPU, uwzględniając liczbę rejestrów i pamięć podręczną.
SPIR-V — pośredni binarny format shaderów, standard dla Vulkan. Shadery są pisane w GLSL lub HLSL, kompilowane do SPIR-V i ładowane do aplikacji Vulkan. SPIR-V nie jest związany z konkretnym językiem — istnieją kompilatory z Rust, Python, OpenCL C do SPIR-V.
Shadery na platformach mobilnych mają ograniczenia w porównaniu do desktopowych: mniej rejestrów, ograniczona precyzja i brak obsługi niektórych instrukcji. Omówmy specyfikę shaderów na Android (Vulkan/OpenGL ES) i iOS (Metal).
Android używa GLSL ES dla OpenGL i SPIR-V dla Vulkan. OpenGL ES 3.2 obsługuje shadery z domyślną precyzją mediump. Vulkan wymaga jawnej kompilacji GLSL do SPIR-V przez glslangValidator. Na Android shadery są ładowane z zasobów tekstowych lub skompilowanych plików SPIR-V.
Qualcomm Adreno GPU optymalizuje shadery na poziomie sterownika. Zalecenia: używaj mediump dla koloru i UV, highp tylko dla pozycji; unikaj próbkowania tekstur w shaderach wierzchołkowych; grupuj obliczenia w wektory (vec4 zamiast 4x float). Według danych Qualcomm (2025), te optymalizacje dają wzrost 30–50%.
iOS używa wyłącznie Metal Shading Language. Shadery są kompilowane do kodu maszynowego razem z aplikacją przez Xcode. Metal dostarcza Shader Debugger i GPU Capture do profilowania shaderów. Apple GPU (TBDR) ma specyficzne optymalizacje: early fragment test, memoryless render targets i programowalne mieszanie.
Według danych Apple WWDC 2024, dla shaderów iOS krytyczne jest używanie half zamiast float, ograniczanie presji rejestrowej (max 64 rejestry) i unikanie zależnych próbkowań tekstur. Shadery iOS powinny być kompaktowe — optymalny rozmiar 50–100 instrukcji ALU.
Silniki gier Unity i Unreal Engine dostarczają wizualne edytory shaderów (Shader Graph, Material Editor) i języki abstrakcyjne (ShaderLab, USF). Deweloper pisze shader w języku wysokiego poziomu, a silnik kompiluje go dla docelowej platformy. Unity używa HLSL jako języka pośredniego, Unreal — USF (Unreal Shader Format).
Optymalizacja shaderów — kluczowy etap tworzenia grafiki dla urządzeń mobilnych. Źle napisany shader może obniżyć FPS z 60 do 20. Omówmy główne zasady i techniki optymalizacji.
Używaj minimalnej wystarczającej precyzji: lowp dla koloru i UV, mediump dla normalnych i oświetlenia, highp tylko dla pozycji i macierzy. Według danych ARM (2025), operacje mediump są 2 razy szybsze niż highp na Mali GPU. W GLSL ES określa się to za pomocą kwalifikatorów precision.
Próbkowanie tekstur — najdroższa operacja w shaderze (10–30 cykli vs 1–2 dla instrukcji ALU). Zmniejszaj liczbę próbkowań: łącz tekstury w atlasy, używaj przechowywania danych w tablicach uniform zamiast tekstur, buforuj wyniki sąsiednich pikseli przez instrukcje pochodnych.
Dynamiczne rozgałęzienia (if ze zmienną uniform) obniżają wydajność potoku SIMT. Zastępuj rozgałęzienia funkcjami matematycznymi: mix(), step(), smoothstep() i clamp(). Te funkcje wykonują się w 1–2 instrukcje, podczas gdy rozgałęzienie może kosztować 8–16 instrukcji z powodu dywergencji wątków.
// Źle: dynamiczne rozgałęzienie w shaderze
if (u_enableLight) {
color *= computeLighting(normal);
}
// Dobrze: matematyczne zastąpienie rozgałęzienia
color *= mix(1.0, computeLighting(normal),
float(u_enableLight));
mix() wykonuje liniową interpolację między dwiema wartościami. Gdy u_enableLight=0, zwracane jest 1.0 — kolor się nie zmienia. Gdy u_enableLight=1, zwracany jest wynik oświetlenia. Brak rozgałęzienia — wszystkie wątki wykonują ten sam kod.
Omówmy praktyczne przykłady shaderów dla programowania mobilnego — od prostego cieniowania po proceduralne generowanie tekstur. Każdy przykład demonstruje konkretną technikę.
Model Phonga — klasyczny model oświetlenia z komponentami dyfuzyjnym i spekularnym. Vertex Shader oblicza oświetlenie dla każdego wierzchołka, Fragment Shader tylko interpoluje wynik (Gouraud shading). Odpowiedni dla urządzeń mobilnych ze względu na niski koszt.
#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;
}
Shader oblicza oświetlenie diffuse: iloczyn skalarny normalnej i kierunku światła. Wynik jest mieszany z komponentami ambient (0.3) i diffuse (0.7). Wykonuje 10–15 instrukcji ALU na wierzchołek — akceptowalne dla 100 000 wielokątów przy 60 FPS na mobilnym GPU.
Tekstury proceduralne są generowane w Fragment Shader bez ładowania z pliku. Oszczędza pamięć wideo i upraszcza animację. Przykład — szachownica ze zmiennym rozmiarem komórek. Zaledwie kilka instrukcji ALU — minimalne obciążenie GPU.
#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 instrukcji ALU — idealny shader dla urządzeń mobilnych. u_cells określa liczbę komórek na oś. floor i mod — tanie operacje, wykonywane w 1 cyklu na wszystkich mobilnych GPU.
Często zadawane pytania
Shader jest wykonywany na GPU w trybie SIMT — jedna instrukcja dla setek wątków. Zwykły program w C++ — na CPU w trybie MIMD. Shader nie ma dostępu do systemu plików, wejścia/wyjścia i pamięci dynamicznej. Otrzymuje dane przez bufory i tekstury i zwraca pozycję lub kolor.
GLSL — dla OpenGL ES i WebGL, cross-platformowy. Metal Shading Language — dla iOS. HLSL — podstawa dla Unity i Unreal Engine. Zacznij od GLSL — zrozumienie podstaw przenosi się na każdy język shaderów.
Główne przyczyny: wysoka precyzja (highp zamiast mediump), nadmierne próbkowanie tekstur, dynamiczne rozgałęzienia i przekroczenie limitu rejestrów. Używaj RenderDoc lub Xcode GPU Frame Debugger do analizy shaderów.
Compute Shader — shader do dowolnych obliczeń na GPU: fizyka cząstek, przetwarzanie obrazów, symulacja tkanin. W przeciwieństwie do Fragment Shader, nie jest związany z pikselami i może zapisywać do dowolnych buforów.
Tak, poprzez edytory wizualne: Shader Graph w Unity, Material Editor w Unreal Engine lub Metal Shader Converter. Generują one kod shadera na podstawie schematu węzłowego. Do głębokiej optymalizacji potrzebne będzie zrozumienie języka shaderów.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również