- Definition: A shader is a small program that runs on the GPU to determine the final color of each pixel or the position of vertices. Unlike CPU code, shaders execute in parallel on thousands of cores.
- Shader types: Vertex shaders process each vertex (position, normal, UV), while fragment (pixel) shaders compute per-fragment color. Geometry, tessellation, and compute shaders handle advanced tasks.
- Execution model: Shaders are called once per vertex or per fragment. They have no persistent state between invocations except through uniform variables set by the application.
- Inputs: Vertex attributes (positions, normals, texture coordinates) come from buffers; uniforms provide global data like transformation matrices; samplers access textures.
- Outputs: Vertex shaders output transformed positions and interpolated data; fragment shaders output a color (and optionally depth or stencil).
- Programmable pipeline: Modern GPUs replace fixed-function stages with programmable shaders, giving developers control over lighting, effects, and transformations.
- Languages: GLSL (OpenGL), HLSL (DirectX), and SPIR-V (Vulkan). They share similar syntax but have platform-specific functions.
- Compilation: Shaders are compiled at runtime or offline into GPU bytecode. Errors are reported as logs that must be parsed for debugging.
- Data flow between stages: Vertex shaders output varyings (interpolated data) to fragment shaders. The rasterizer interpolates these values across fragments. Varyings define how data like normals and UVs change smoothly across triangles, enabling per-pixel effects without per-vertex computation.
- Uniforms and push constants: Uniforms are constant across a draw call and set from the CPU. They include transformation matrices, lighting parameters, and time. Push constants in Vulkan offer faster updates for small data. Both avoid per-vertex storage but require careful management of update frequency to avoid CPU GPU synchronization stalls.
- Shader precision and data types: GLSL and HLSL support float, half (mediump), and fixed-point precision. Using half precision for color and normal calculations can improve performance on mobile GPUs. However, precision must be chosen carefully to avoid artifacts in complex lighting calculations or when interpolating depth values.
- Conditional branching in shaders: Dynamic branching with if statements can cause performance divergence when threads in a warp take different paths. Modern GPUs handle this with masked execution, but heavy branching in fragment shaders often reduces throughput. Use step or mix functions for simple conditional selection to keep execution uniform.
- Shader debugging techniques: Debugging shaders is challenging due to parallel execution. Common methods include outputting debug colors to the screen, using frame capture tools like RenderDoc or NSight, and writing intermediate values to buffer objects. Compilation error logs must be parsed for line numbers and token information.
- Specialization constants: Vulkan and Metal allow specialization constants to precompile shader variants without full recompilation. These constants can enable or disable features such as shadow mapping or fog at pipeline creation time, resulting in smaller and faster shaders because unused code paths are eliminated.
- Shader optimization principles: Minimize texture fetches, use vectorized operations, reduce dependent texture reads, and avoid dynamic loops with variable iteration counts. Profile shaders using GPU timers or tools like GPU PerfStudio. Always balance visual quality with performance by reducing precision or simplifying calculations where possible.
Principle: Separation of Concerns in the Pipeline
Definition: Each shader stage fulfills a distinct role: vertex transforms geometry, fragment colors pixels. This modularity allows efficient specialization and reuse.
Application: Keep vertex shaders focused on transformations (model, view, projection) and fragment shaders on lighting and texture. Avoid mixing responsibilities: do not compute lighting in the vertex shader unless performance requires it.
- Shaders are GPU programs that run in parallel per vertex or per fragment.
- Main types: vertex and fragment shaders; also geometry, tessellation, and compute.
- Inputs include attributes, uniforms, and samplers; outputs are positions or colors.
- Modern graphics pipelines are fully programmable using GLSL, HLSL, or SPIR-V.