着色器 (Shader) 的基本概念和 GLSL 语法 笔记

一、着色器的基本概念

着色器是用一种类似 C 的语言(GLSL)编写的、在 GPU 上运行的小程序。它们不是你写的传统"应用代码",而是专门为处理图形渲染管线中的特定阶段而设计的。

核心思想 :传统的固定渲染管线是"黑盒",你只能配置参数。而可编程渲染管线允许你编写这些小程序,从而完全控制顶点如何变换、像素如何着色的过程,实现无限可能的效果。

在 OpenGL ES 2.0/3.0 中,最重要的两个可编程着色器是:

  1. 顶点着色器

    • 执行频率每个顶点执行一次
    • 核心任务
      • 坐标变换:将顶点从模型空间,经过模型、视图、投影矩阵变换,最终到屏幕的裁剪空间。
      • 传递数据:计算并输出后续阶段(如片段着色器)所需的数据,如颜色、纹理坐标、法线等。
  2. 片段着色器

    • 执行频率每个片段(可粗略理解为像素候选)执行一次
    • 核心任务
      • 决定最终颜色:计算该片段的最终颜色值。这包括纹理采样、光照计算、颜色混合等所有决定像素外观的操作。

渲染流程简化视图

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 值都是相同的。
    • 例如:变换矩阵、光源位置、颜色、时间。
    glsl 复制代码
    uniform 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(高精度)。
    glsl 复制代码
    precision 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. 内置变量与函数

  • 顶点着色器输出

    glsl 复制代码
    vec4 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 的关键语法变化

  1. in/out 取代 attribute/varying:声明更清晰,统一了顶点和片段着色器的输入输出语法。
  2. 自定义片段输出 :不再使用 gl_FragColor,而是自己声明 out vec4 outColor;
  3. 纹理函数统一texture2D, textureCube 等函数统一为 texture 函数。
  4. 更严格的语法:例如,不再支持数组和循环的不确定索引(除非使用特殊限定符)。

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端,务必 在编译链接着色器后,使用 glGetShaderInfoLogglGetProgramInfoLog 获取错误信息。这是调试着色器问题的唯一可靠途径
  • 从简单开始:先让一个最简单的着色器(如直接输出颜色)工作,再逐步增加复杂度(矩阵、纹理、光照)。
  • 注意精度 :在移动设备上,不当的精度选择可能导致性能下降或渲染瑕疵。通常 mediump 是安全且高效的起点。
  • 避免条件分支 :GPU 擅长并行处理相同指令流,复杂的 if-else 或循环会显著降低性能。尽量使用步进函数、混合等方式替代。
相关推荐
莫比乌斯环9 小时前
【Android技能点】一张图理清 开机、App启动流程
android
我命由我123459 小时前
Android Jetpack Compose - Compose 重组、AlertDialog、LazyColumn、Column 与 Row
android·java·java-ee·kotlin·android studio·android jetpack·android-studio
愤怒的代码10 小时前
在 Android 中执行 View.invalidate() 方法后经历了什么
android·java·kotlin
PoppyBu11 小时前
Ubuntu20.04版本上安装最新版本的scrcpy工具
android·ubuntu
执念、坚持11 小时前
Property Service源码分析
android
用户416596736935512 小时前
在 ViewPager2 + Fragment 架构中玩转 Jetpack Compose
android
GoldenPlayer12 小时前
Gradle脚本执行
android
用户745890020795412 小时前
Android进程模型基础
android
we1less12 小时前
[audio] Audio debug
android
Jomurphys12 小时前
AndroidStudio - TOML
android