Fragment Shader — 是图形管线中可编程的阶段,用于确定图像每个片段(像素)的最终颜色。根据Khronos Group,2025的数据,片段着色器接收来自顶点着色器的插值数据,并执行光照、纹理和颜色效果的计算。Fragment Shader为每个像素调用,这使其成为图形管线中最消耗资源的阶段。
要点
Fragment Shader(片段或像素着色器)— 是图形管线中可编程的阶段,计算图像每个片段的最终颜色。片段是一个潜在的像素,在通过深度和模板测试后,成为屏幕上可见的像素。
Fragment Shader在光栅化之后执行,此时三维图元(三角形)已被转换为片段集合。着色器在输入时接收插值属性 — 纹理坐标、法线、颜色,这些在顶点着色器中为三角形的顶点计算,并为其内部的每个像素进行线性插值。
在可编程着色器出现之前(DirectX 8 / OpenGL 2.0之前),像素颜色的计算由固定管线以有限的操作集执行。可编程Fragment Shader于2001年出现,从根本上扩展了图形能力:开发人员获得了对每个像素颜色的完全控制,这使得实现照片级逼真的光照、复杂材质和后处理效果成为可能。
Fragment Shader由GPU为光栅化器生成的每个片段启动。每次着色器调用处理一个片段,读取其输入数据,执行用户代码,并写入输出值 — 片段的颜色(RGBA格式)。
Fragment Shader的输入数据包括来自顶点着色器的插值属性(纹理坐标、法线、颜色)、uniform变量(光源、矩阵)和纹理采样器。输出数据 — 颜色向量(OpenGL ES 2.0中的gl_FragColor或ES 3.0中的用户变量),写入颜色缓冲区。
#version 300 es
precision mediump float;
in vec2 vTexCoord;
uniform sampler2D uTexture;
out vec4 fragColor;
void main() {
fragColor = texture(uTexture, vTexCoord);
}
Fragment Shader负责图像的视觉质量。用户在屏幕上看到的所有效果 — 颜色、阴影、反射、透明度 — 都在图形管线的这个阶段计算。
最常见的操作 — 根据插值的UV坐标从纹理中获取颜色。Sampler2D和texture()函数允许从纹理图集中获取像素颜色。过滤(双线性、三线性、各向异性)通过采样器参数控制,并影响不同视角下的图像清晰度。
冯氏光照模型(Phong shading)在Fragment Shader中为每个像素计算,比顶点光照(Gouraud shading)提供更平滑的高光。Phong lighting包括三个分量:环境光(ambient)、漫反射光(diffuse)和镜面反射光(specular)。
// 带有冯氏光照的片段着色器
in vec3 vNormal;
in vec3 vPosition;
uniform vec3 uLightDir;
uniform vec3 uLightColor;
void main() {
vec3 norm = normalize(vNormal);
float diff = max(dot(norm, uLightDir), 0.0);
fragColor = vec4(uLightColor * diff, 1.0);
}
Fragment Shader允许应用后处理:通过LUT表进行颜色校正、模糊(blur)、发光效果(bloom)以及HDR渲染的色调映射(tonemapping)。所有这些效果在像素级别工作,并实时执行。
Fragment Shader的性能 — 是现代移动游戏中限制FPS的主要因素。由于着色器为每个像素调用,即使代码复杂度的微小增加也可能导致帧率的显著下降。
对于移动GPU(Qualcomm Adreno、ARM Mali),建议:使用mediump precision处理float变量,合并纹理采样,应用early-z测试以在着色器执行前丢弃不可见的片段。
| 方法 | 描述 | 收益 |
|---|---|---|
| Mediump precision | 16位精度替代32位 | 最高2倍速度 |
| Early-Z | 着色器前的深度测试 | 减少调用 |
| 纹理图集 | 单一纹理替代多个 | 更少切换 |
两种着色器都由开发人员编程,但它们在管线的不同阶段工作并解决不同的任务。Fragment Shader由于调用次数多而显著更消耗资源。
| 参数 | Fragment Shader | Vertex Shader |
|---|---|---|
| 调用次数 | 数百万(屏幕分辨率) | 数千(顶点数量) |
| 输入数据 | 插值属性 | 顶点属性 |
| 主要功能 | 计算片段颜色 | 几何变换 |
| 纹理访问 | 完全(多个采样器) | 受限 |
| 性能影响 | 高(取决于分辨率) | 中等(取决于几何形状) |
Fragment Shader可以简单(纹理采样)或复杂(多层光照)。让我们来看两个实际示例:基础纹理映射和使用时间的渐变效果。
#version 300 es
precision mediump float;
in vec2 vTexCoord;
in vec3 vColor;
uniform sampler2D uDiffuseMap;
out vec4 fragColor;
void main() {
vec4 texColor = texture(uDiffuseMap, vTexCoord);
fragColor = texColor * vec4(vColor, 1.0);
}
#include <metal_stdlib>
using namespace metal;
struct FragmentIn {
float2 texCoord;
float3 normal;
};
fragment float4
fragmentMain(FragmentIn in [[stage_in]],
texture2d<float> tex [[texture(0)]]) {
constexpr sampler s(filter = linear);
float4 color = tex.sample(s, in.texCoord);
return color;
}
常见问题
Fragment Shader — 是GPU上的一个小程序,决定屏幕上每个像素的颜色。如果Vertex Shader确定对象的形状,那么Fragment Shader确定该对象的外观 — 纹理、光照、颜色。
Fragment Shader为屏幕上的每个像素调用。在分辨率为2532x1170的设备(iPhone)上,每帧几乎300万次调用。在60 FPS时,着色器每秒执行1.8亿次,这需要巨大的计算能力。
在Fragment Shader中实现:纹理映射、光照(Phong、PBR)、阴影、反射、折射、bloom、景深、颜色校正、LUT滤镜和程序化纹理生成。
使用mediump precision处理float,减少纹理采样次数,应用early-z剔除,合并着色器操作,避免着色器内部的条件分支。
它们是同一个概念。Fragment Shader术语用于OpenGL / Vulkan,而Pixel Shader用于Direct3D(Microsoft)的术语。功能上相同:两者都计算每个片段/像素的颜色。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。