Shader:什么是着色器,着色器类型及工作原理

作者: IT Sectr 发布日期: 2026-06-11 阅读时间: 8 分钟

Shader(着色器)——是一种在GPU上运行的程序,用于处理图形数据:顶点、片段或计算任务。与CPU上的普通代码不同,着色器在数百个内核上并行工作,独立处理每个元素。根据Khronos Group(2025)的数据,100%的现代3D应用程序都使用着色器——从移动游戏到系统应用程序中的UI效果。开发人员使用专门的着色语言编写着色器:GLSL、HLSL、Metal Shading Language或SPIR-V。

要点

  • Shader——GPU程序,在数百个内核上并行执行,处理场景的每个元素。
  • 着色器类型:顶点着色器、片段着色器、计算着色器、几何着色器、曲面细分着色器和网格着色器。
  • 语言:GLSL(OpenGL)、HLSL(DirectX)、MSL(Metal)、SPIR-V(Vulkan)——全部编译为GPU机器码。
  • 移动开发中的着色器用于效果、后处理、光照和计算。
  • 着色器优化:mediump精度、最小化分支和为移动GPU优化的ATF(自适应纹理获取)。

什么是Shader?

Shader——是用专用语言编写并在GPU上执行的小程序。每个着色器独立处理一个数据元素——顶点、片段(像素)或工作元素——彼此互不影响。GPU的并行性允许同时运行数千个着色器实例。

该术语由Pixar在20世纪80年代引入,用于描述控制RenderMan中表面外观的程序。实时着色器随NVIDIA GeForce 3(2001)——第一张具有可编程管线的显卡——出现。在着色器出现之前,图形通过固定参数配置:颜色、纹理、混合模式。

根据Unity Technologies(2025)的数据,平均每个移动游戏使用50–200个着色器。每个着色器针对不同平台、质量级别和光照配置都有变体(variants)。变体数量每个项目可达10,000个。

着色器架构分为输入数据(属性、uniform变量、纹理)、程序代码输出数据(颜色、位置)。Uniform变量在着色器启动前由CPU设置,并在帧的所有元素中保持不变。属性是每个元素的数据(顶点坐标、法线、UV坐标)。

SIMT执行模型

SIMT(单指令多线程)——着色器在GPU上的执行模型。一条指令加载一次,但由数百个线程在不同数据上执行。例如,为1000个像素的Fragment Shader启动1000个具有相同代码的线程,但每个线程接收自己的UV坐标和内插属性。

SIMT的重要后果:着色器中的分支代价高昂。如果在if块内一半线程走一条分支,另一半走另一条分支,则GPU会顺序执行所有线程的两个分支。性能下降到50%。避免着色器中的动态分支,或通过在CPU上预先计算来最小化它们。

着色器类型

现代GPU支持多种着色器类型,每种类型针对图形或计算管线的不同阶段。让我们来看看主要类型:顶点着色器、片段着色器、计算着色器和现代网格着色器。

着色器类型管线阶段处理语言
Vertex Shader顶点每个顶点GLSL, HLSL
Fragment Shader片段每个像素GLSL, HLSL
Compute Shader计算任意数据GLSL, HLSL
Geometry Shader几何图元GLSL, HLSL
Tessellation Shader曲面细分补丁GLSL, HLSL
Mesh Shader网格管线Task + MeshHLSL, MSL

Vertex Shader

Vertex Shader处理几何体的每个顶点:转换坐标、计算光照、将数据传递到片段着色器。这是图形管线的强制阶段——没有它,顶点将无法显示在屏幕上。Vertex Shader在每帧的每个顶点上调用。

Fragment Shader

Fragment Shader(或Pixel Shader)——资源最密集的阶段。它使用纹理、光照和材质计算每个片段的最终颜色。Fragment Shader为几何体覆盖的每个像素执行,考虑所有应用的纹理和效果。优化此着色器可带来最大的性能提升。

Compute Shader

Compute Shader——用于在GPU上进行任意计算的通用着色器。与图形着色器不同,它不绑定到几何体或像素。Compute Shader通过工作组使用任意数据缓冲区。用于物理、粒子模拟、后效果和神经网络推理。

Mesh Shader

Mesh Shader——最新类型的着色器,出现在NVIDIA Turing(2018)和Metal 3中。Mesh Shader用一个网格管线取代了顶点、几何和曲面细分着色器。与Task Shader配合使用:Task Shader决定渲染哪些网格组,Mesh Shader即时生成几何体。

编写着色器的语言

着色器使用专门的语言编写,这些语言编译为GPU机器码。语言的选择取决于平台和API。让我们来看看主要语言:GLSL、HLSL、Metal Shading Language和中间格式SPIR-V。

GLSL(OpenGL着色语言)

GLSL——用于OpenGL、OpenGL ES和WebGL的着色语言。语法基于C,增加了向量类型(vec2、vec4、mat4)和内置函数(texture、normalize、reflect)。GLSL ES——用于移动设备的版本,精度有限(lowp、mediump、highp)。根据Khronos(2025)的数据,GLSL凭借其跨平台能力仍然是最广泛使用的着色语言。

glsl
#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);
}

一个简单的片段着色器根据UV坐标从纹理读取颜色。precision mediump指示GPU对float使用半精度——在移动设备上加速执行25–40%。

HLSL(高级着色语言)

HLSL——微软为DirectX开发的着色语言。语法更接近C++,支持类、结构和模板。HLSL用于Windows应用程序,并通过Shader Model 6.7+支持光线追踪、网格着色器和采样器反馈。对于移动开发,HLSL不直接使用,但可以编译为SPIR-V用于Vulkan。

Metal Shading Language

MSL(Metal着色语言)——苹果的着色语言,基于C++14。与GLSL和HLSL不同,MSL与应用程序一起编译,从而消除了设备上的JIT编译。MSL支持指针、模板和C++标准库。用于所有苹果设备:iPhone、iPad、Mac、Apple TV。

cpp
#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使用[[position]]、[[stage_in]]和[[texture(N)]]属性来绑定资源。苹果编译器为当前GPU生成优化代码,考虑寄存器和缓存数量。

SPIR-V

SPIR-V——着色器的中间二进制格式,Vulkan的标准。着色器用GLSL或HLSL编写,编译为SPIR-V并加载到Vulkan应用程序中。SPIR-V不绑定到特定语言——存在从Rust、Python、OpenCL C到SPIR-V的编译器。

移动开发中的着色器

移动平台上的着色器与桌面相比有限制:更少的寄存器、有限的精度和缺乏对某些指令的支持。让我们看看Android(Vulkan/OpenGL ES)和iOS(Metal)上着色器的特点。

Android上的着色器

Android使用GLSL ES用于OpenGL,使用SPIR-V用于Vulkan。OpenGL ES 3.2支持默认精度为mediump的着色器。Vulkan需要通过glslangValidator将GLSL显式编译为SPIR-V。在Android上,着色器从文本资源或编译的SPIR-V文件加载。

Qualcomm Adreno GPU在驱动级别优化着色器。建议:颜色和UV使用mediump,位置仅使用highp;避免在顶点着色器中进行纹理采样;将计算分组为向量(vec4而不是4x float)。根据Qualcomm(2025)的数据,这些优化可提升30–50%。

iOS上的着色器

iOS专门使用Metal Shading Language。着色器通过Xcode与应用程序一起编译为机器码。Metal提供Shader DebuggerGPU Capture用于分析着色器。苹果GPU(TBDR)具有特定的优化:早期片段测试、无内存渲染目标和可编程混合。

根据Apple WWDC 2024的数据,对于iOS着色器,关键是要使用half而不是float,限制寄存器压力(最多64个寄存器)并避免依赖纹理采样。iOS着色器应保持紧凑——最佳大小为50–100条ALU指令。

Unity和Unreal Engine中的着色器

Unity和Unreal Engine游戏引擎提供可视化着色器编辑器(Shader Graph、Material Editor)和抽象语言(ShaderLab、USF)。开发人员用高级语言编写着色器,引擎为目标平台编译。Unity使用HLSL作为中间语言,Unreal使用USF(Unreal着色器格式)。

着色器优化

着色器优化——移动设备图形开发的关键阶段。编写不当的着色器可能将FPS从60降低到20。让我们来看看主要的优化规则和技术。

计算精度

使用最小足够的精度:lowp用于颜色和UV,mediump用于法线和光照,highp仅用于位置和矩阵。根据ARM(2025)的数据,在Mali GPU上,mediump运算比highp快2倍。在GLSL ES中,这通过精度限定符设置。

最小化纹理采样

纹理采样——着色器中最昂贵的操作(10–30个周期,而ALU指令只需1–2个周期)。减少采样次数:将纹理合并到图集中,使用uniform数组存储代替纹理,通过导数指令缓存相邻像素的结果。

避免分支

动态分支(带有uniform变量的if)会降低SIMT管线的性能。用数学函数替换分支:mix()step()smoothstep()clamp()。这些函数在1–2条指令中执行,而分支可能因线程分歧而花费8–16条指令。

glsl
// 差:着色器中的动态分支
if (u_enableLight) {
    color *= computeLighting(normal);
}

// 好:分支的数学替换
color *= mix(1.0, computeLighting(normal),
              float(u_enableLight));

mix()在两个值之间执行线性插值。当u_enableLight=0时,返回1.0——颜色不变。当u_enableLight=1时,返回光照结果。没有分支——所有线程执行相同代码。

着色器示例

让我们看看移动开发的实用着色器示例——从简单着色到程序化纹理生成。每个示例演示一种特定技术。

冯氏着色(顶点着色器)

冯氏模型——具有漫反射和镜面反射分量的经典光照模型。Vertex Shader为每个顶点计算光照,Fragment Shader仅插值结果(高洛德着色)。由于成本低,适合移动设备。

glsl
#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;
}

着色器计算漫反射光照:法线和光照方向的点积。结果与环境光(0.3)和漫反射(0.7)分量混合。每个顶点执行10–15条ALU指令——在移动GPU上以60 FPS处理100,000个多边形是可以接受的。

程序化纹理(片段着色器)

程序化纹理在Fragment Shader中生成,无需从文件加载。节省视频内存并简化动画。示例——具有可变单元格大小的棋盘。仅需几条ALU指令——对GPU负载最小。

glsl
#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指令——移动设备的理想着色器。u_cells设置每个轴上的单元格数量。floormod——廉价操作,在所有移动GPU上1个周期执行。

常见问题

着色器与普通C++程序有何不同?

Shader在GPU上以SIMT模式运行——一条指令用于数百个线程。普通C++程序在CPU上以MIMD模式运行。着色器无法访问文件系统、输入/输出和动态内存。它通过缓冲区和纹理接收数据,并返回位置或颜色。

移动开发应该学习哪种着色语言?

GLSL——用于OpenGL ES和WebGL,跨平台。Metal Shading Language——用于iOS。HLSL——Unity和Unreal Engine的基础。从GLSL开始——理解基础可以迁移到任何着色语言。

为什么着色器在移动设备上运行缓慢?

主要原因:高精度(使用highp而非mediump)、过度纹理采样、动态分支和超过寄存器限制。使用RenderDocXcode GPU Frame Debugger分析着色器。

什么是计算着色器,它有什么用途?

Compute Shader——用于GPU上任意计算的着色器:粒子物理、图像处理、布料模拟。与Fragment Shader不同,它不绑定到像素,可以写入任意缓冲区。

可以在不了解编程语言的情况下编写着色器吗?

可以,通过可视化编辑器:Unity中的Shader Graph,Unreal Engine中的Material Editor或Metal Shader Converter。它们基于节点图生成着色器代码。深度优化需要对着色器语言的理解。

总结

  • Shader——GPU的并行程序,批量处理顶点、片段或任意数据。
  • 着色器类型:Vertex、Fragment、Compute、Geometry、Tessellation和Mesh——每种负责管线的不同阶段。
  • 语言:GLSL(OpenGL)、HLSL(DirectX)、MSL(Metal)、SPIR-V(Vulkan)——编译为GPU机器码。
  • 移动特性:mediump精度、最小纹理采样、无动态分支。
  • 优化:用mix()代替if、half精度、ATF、纹理合并到图集和CPU预先计算。
  • 应用:冯氏光照、程序化纹理、后效果、粒子物理和UI效果。
  • 工具:RenderDoc、Xcode GPU Debugger、Qualcomm Adreno Profiler和Mali Offline Compiler用于着色器分析。

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读