一、着色器的基本概念
着色器是用一种类似 C 的语言(GLSL)编写的、在 GPU 上运行的小程序。它们不是你写的传统"应用代码",而是专门为处理图形渲染管线中的特定阶段而设计的。
核心思想 :传统的固定渲染管线是"黑盒",你只能配置参数。而可编程渲染管线允许你编写这些小程序,从而完全控制顶点如何变换、像素如何着色的过程,实现无限可能的效果。
在 OpenGL ES 2.0/3.0 中,最重要的两个可编程着色器是:
-
顶点着色器:
- 执行频率 :每个顶点执行一次。
- 核心任务 :
- 坐标变换:将顶点从模型空间,经过模型、视图、投影矩阵变换,最终到屏幕的裁剪空间。
- 传递数据:计算并输出后续阶段(如片段着色器)所需的数据,如颜色、纹理坐标、法线等。
-
片段着色器:
- 执行频率 :每个片段(可粗略理解为像素候选)执行一次。
- 核心任务 :
- 决定最终颜色:计算该片段的最终颜色值。这包括纹理采样、光照计算、颜色混合等所有决定像素外观的操作。
渲染流程简化视图:
rust
顶点数据 -> [顶点着色器](处理每个顶点)-> 图元装配/光栅化 -> [片段着色器](处理每个片段)-> 测试与混合 -> 屏幕像素
二、GLSL 语法核心要点
GLSL(OpenGL Shading Language)是为图形计算量身定制的语言,语法类似C,但有显著的不同。
1. 版本声明
必须是第一行(注释除外)。OpenGL ES 的 GLSL 有特殊版本号。
glsl
// OpenGL ES 2.0 对应 GLSL ES 1.00
#version 100 es
// OpenGL ES 3.0 对应 GLSL ES 3.00
#version 300 es
2. 变量类型与限定符
这是 GLSL 最关键的特性之一,用于定义数据的来源、去向和性质。
-
attribute(仅用于顶点着色器,ES 2.0) /in(ES 3.0):- 定义逐顶点的输入数据。每个顶点可以有不同的值。
- 例如:顶点位置、顶点颜色、顶点法线、纹理坐标。
glsl// ES 2.0 attribute vec4 aPosition; attribute vec2 aTexCoord; // ES 3.0 in vec4 aPosition; -
uniform:- 定义全局 、恒定 的输入变量。在一次绘制调用中,所有顶点和片段访问到的
uniform值都是相同的。 - 例如:变换矩阵、光源位置、颜色、时间。
glsluniform mat4 uMVPMatrix; // 模型-视图-投影矩阵 uniform float uTime; - 定义全局 、恒定 的输入变量。在一次绘制调用中,所有顶点和片段访问到的
-
varying(ES 2.0) /out(顶点着色器) &in(片段着色器,ES 3.0):- 用于从顶点着色器向片段着色器传递数据。
- 顶点着色器为每个顶点计算出该值,在光栅化过程中,这些值会被插值 ,然后片段着色器收到的是插值后的、对应于当前片段的值。
- 例如:将顶点颜色、纹理坐标、从顶点着色器计算出的光照强度传递给片段着色器。
glsl// ES 2.0 // 顶点着色器中 varying vec2 vTexCoord; // 片段着色器中 varying vec2 vTexCoord; // ES 3.0 // 顶点着色器中 out vec2 vTexCoord; // 片段着色器中 in vec2 vTexCoord; -
精度限定符:
- GLSL ES 特有,用于指定变量的计算精度,平衡速度和资源。片段着色器中必须声明浮点数精度。
lowp(低精度)、mediump(中精度)、highp(高精度)。
glslprecision mediump float; // 在片段着色器开头声明默认浮点数精度 uniform highp sampler2D uTexture; // 纹理采样器通常需要高精度 varying lowp vec4 vColor; // 颜色数据可能用低精度就够了
3. 基本数据类型
float,int,bool: 标量。vec2,vec3,vec4: 包含2、3、4个浮点数的向量。常用于表示位置、颜色(RGBA)、纹理坐标。mat2,mat3,mat4: 2x2, 3x3, 4x4 浮点数矩阵。sampler2D,samplerCube: 纹理采样器,是用于从纹理中读取数据的特殊类型。
4. 内置变量与函数
-
顶点着色器输出 :
glslvec4 gl_Position; // 必须赋值!表示顶点在裁剪空间中的最终位置。 float gl_PointSize; // 可选,绘制点精灵时的大小。 -
片段着色器输出 :
glsl// ES 2.0 vec4 gl_FragColor; // 必须赋值!表示该片段的最终颜色。 // ES 3.0 out vec4 oFragColor; // 自定义输出变量,可以多个(用于多渲染目标)。 -
内置函数 : GLSL 提供了丰富的数学和图形函数。
- 数学:
sin,cos,pow,sqrt,abs - 几何:
dot(点积),cross(叉积),normalize(归一化),length(长度) - 纹理:
texture2D(ES 2.0) /texture(ES 3.0)(纹理采样)
- 数学:
三、一个完整的简单例子 (OpenGL ES 2.0)
顶点着色器 (vertex_shader.glsl):
glsl
#version 100 es // ES 2.0
// 输入:顶点的属性
attribute vec4 aPosition;
attribute vec4 aColor;
// 输出:传递给片段着色器的变量(会被插值)
varying vec4 vColor;
// 全局常量:变换矩阵
uniform mat4 uMVPMatrix;
void main() {
// 核心任务:计算裁剪空间坐标
gl_Position = uMVPMatrix * aPosition;
// 将顶点颜色传递给片段着色器
vColor = aColor;
}
片段着色器 (fragment_shader.glsl):
glsl
#version 100 es
// ES 2.0 片段着色器必须声明默认浮点精度
precision mediump float;
// 输入:从顶点着色器传来(经过插值)
varying vec4 vColor;
void main() {
// 核心任务:决定最终颜色
// 这里简单地使用插值后的颜色
gl_FragColor = vColor;
}
四、OpenGL ES 3.0 的关键语法变化
in/out取代attribute/varying:声明更清晰,统一了顶点和片段着色器的输入输出语法。- 自定义片段输出 :不再使用
gl_FragColor,而是自己声明out vec4 outColor;。 - 纹理函数统一 :
texture2D,textureCube等函数统一为texture函数。 - 更严格的语法:例如,不再支持数组和循环的不确定索引(除非使用特殊限定符)。
ES 3.0 等价示例片段:
glsl
// 顶点着色器开头
#version 300 es
in vec4 aPosition;
out vec4 vColor;
// 片段着色器开头
#version 300 es
precision mediump float;
in vec4 vColor;
out vec4 outColor; // 自定义输出
五、调试与最佳实践
- 错误检查 :在C++/Java端,务必 在编译链接着色器后,使用
glGetShaderInfoLog和glGetProgramInfoLog获取错误信息。这是调试着色器问题的唯一可靠途径。 - 从简单开始:先让一个最简单的着色器(如直接输出颜色)工作,再逐步增加复杂度(矩阵、纹理、光照)。
- 注意精度 :在移动设备上,不当的精度选择可能导致性能下降或渲染瑕疵。通常
mediump是安全且高效的起点。 - 避免条件分支 :GPU 擅长并行处理相同指令流,复杂的
if-else或循环会显著降低性能。尽量使用步进函数、混合等方式替代。