从零开始的Shadertoy生活_02

aaaaa Lv2

02 glsl 语言:内置参数,函数与常量

在上一章中,我们已经充分了解了glsl语言与C语言的异同。这一章将接着上一章的内容,继续补充完善glsl语言的内置函数常量。在介绍完常量过后,我们将进一步了解内置参数

内置函数

glsl的内置函数有很多,在此无法一一列举,仅对最常用、最通用的函数进行介绍。(若想全面、深刻了解glsl语言,请参考 OpenGL相关标准

数学计算函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 三角相关
radians(degrees) // 角度转弧度
degrees(radians) // 弧度转角度
sin(x), cos(x), tan(x) // 三角函数
asin(x), acos(x), atan(x) // 反三角函数
sinh(x), cosh(x), tanh(x) // 双曲函数

// 指对幂
pow(x, y) // 指数 x^y
exp(x) // e指数 e^x
exp2(x) // 2指数 2^x
log(x) // ln(x)
log2(x) // log2(x)
sqrt(x) // 平方根
inversesqrt(x) // 1/sqrt(x)

// 整数相关
abs(x) // 绝对值
sign(x) // 符号函数(-1, 0, 1)
floor(x) // 向下取整
ceil(x) // 向上取整
round(x) // 四舍五入
trunc(x) // 截断到整数
fract(x) // 小数部分(x - floor(x))
mod(x, y) // 浮点数取模 (正整数为x % y)
min(x, y), max(x, y) // 最小/最大值
clamp(x, min, max) // x在min以下时返回min,在max以上时返回max,其余直接返回x
mix(x, y, a) // 线性插值(x*(1-a) + y*a)
step(edge, x) // x >= edge ? 1 : 0
smoothstep(edge0, edge1, x) // x在edge0及以下时返回0,在edge1及以上时返回1,中间平滑差值(3次Hermite差值)

注意事项:

  • 以上所有运算均可应用于 vec ,效果等同于对每一项分别操作。
  • 三角函数、反三角函数默认使用弧度制
  • pow 函数对于负数的非整数次方返回值不定(这是Undefined Behavior)。
  • mod 函数不能用于整数,整数请用 a % b 计算。
  • mod(x, y) = x - y * floor(x / y) ,即取模结果与y同号
  • min max clamp 会返回更宽的类型,即 min(float, int) -> float max(vec3, float) -> vec3
1
2
3
4
5
6
7
8
9
10
11
12
// 向量相关
length(x) // 向量模长
distance(p0, p1) // 两点距离
dot(x, y) // 点积
cross(x, y) // 叉积
normalize(x) // 归一化向量,返回对应单位向量
reflect(I, N) // 反射向量(I 为入射,N 为法线)
refract(I, N, eta) // 折射向量(eta 为折射率)
faceforward(N, I, Nref) // 确保法线 N 最终朝向与参考方向 Nref 相反的一侧
any(x) // 任意分量为真则返回 true
all(x) // 所有分量为真则返回 true
lessThan(x, y), greaterThan(x, y) // 分量逐项比较

注意事项:

  • 叉乘 cross 仅适用于 vec3
  • reflectrefractIN 参数都需要归一化
  • reflect(I, N) = I − 2 * (N ⋅ I) * N
  • 折射 refract 符合斯涅耳定律,全反射时返回反射角方向
1
2
3
4
5
// 矩阵相关
transpose(M) // 转置矩阵
determinant(M) // 行列式(仅适用于方阵)
inverse(M) // 逆矩阵(仅适用于方阵)
outerProduct(c, r) // 外积(生成矩阵)

注意事项:

  • 仅部分高版本(GLSL 3.30+(OpenGL 3.3+))支持非方阵(如 mat2x3 ),但是计算消耗大于方阵,因此尽可能少使用非方阵。

以上数学函数将贯通整个 Shader 学习过程,请熟记于心(记不住也没关系,多写自然熟练)。

texture 相关纹理函数

由于纹理比较复杂,此处暂且按下不表,后面会单独开一个章节讨论纹理和缓冲(Buffer)。

常量

学过C语言的同学都知道,常量就是 const ,声明必须同时定义,一旦定义就不能修改。如果真的这么简单,那根本没必要单开一节来讨论常量

glsl中大致符合以上标准的“常量”共有3种:const #defineuniform , 我们一个个看。

const:真正的“常量”

和C语言中的一样,const常量是真正的“常量”,在作用域内保持值不可变。如果定义在主函数或是其他某个函数内部,那么const常量将在每次调用这个函数的时候被定义,生命周期持续到函数结束;如果定义在全局(即写在所有函数外面),const常量将仅在编译开始后定义一次,每帧不再重新定义

1
2
3
4
5
6
7
8
9
10
11
12
// 全局 const(全局只定义一次)
const vec3 c_sky = vec3(0.53, 0.81, 0.92);

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// 归一化像素
vec2 uv = (fragCoord * 2.0 - iResolution.xy) / min(iResolution.x, iResolution.y);

// 局部 const(每次调用 mainImage 函数时重新定义)
const vec3 ORANGE = vec3(1.0, 0.4, 0.0);
fragColor = vec4(mix(c_sky, ORANGE, uv.y * 0.7), 1.0);
}

#define:“常”而非“量”

和C语言中的一样,#define 是预处理器指令,在编译之前就直接做文本替换,不消耗空间,几乎不影响运行效率。所以这不是一种“量”,只是一种宏定义的别名。相比于const,#define 不会进行类型检查,也就是不会被编辑器的红色波形曲线发现,但有可能造成 Compile Error

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#define PI 3.1415926538
#define TWO_PI 6.28318530718 // 2 * PI

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
// 归一化像素坐标到 [0, 1] 并居中
vec2 uv = (fragCoord * 2.0 - iResolution.xy) / min(iResolution.x, iResolution.y);

// 计算极坐标(半径和角度)
float radius = length(uv);
float angle = atan(uv.y, uv.x);

// 使用 PI 控制圆环的周期性(每 PI/2 弧度变换一次颜色)
float colorMask = sin(angle * 4.0 + radius * 10.0); // 4.0 = 2*PI/(PI/2)

// 生成彩虹色
vec3 rainbow = 0.5 + 0.5 * cos(angle + vec3(0.0, TWO_PI/3.0, TWO_PI*2.0/3.0));

// 圆环效果(内半径 0.3,外半径 0.4)
float ring = smoothstep(0.3, 0.31, radius) - smoothstep(0.4, 0.41, radius);

// 混合颜色和圆环
fragColor = vec4(rainbow * ring * colorMask, 1.0);
}

uniform:“量”而不“常”

uniform是glsl有别于C的一大特性:它用于传输从宿主程序(如 CPU)向着色器传递的运行时不可变的全局数据。换句话说,uniform常量是CPU向GPU传递数据的单向管道。uniform常量将保持在运行时不变,也就是说在同一帧内每个像素点运行主函数时保持不变且不可修改;但是在不同帧运行之前,CPU可以修改传入的uniform的值。因此,uniform常量不是真正的“常”量。一些常见的uniform常量将在下一节:内置参数中给出。

注:由于自定义uniform常量将涉及到OpenGL API在CPU端修改配置(主要是作者不会),本系列暂不考虑,仅讨论Shadertoy内制的uniform参数。

内置参数

新建一个Shadertoy在线着色器,点开代码区顶部的“着色器输入”,你会发现这些内置参数:

1
2
3
4
5
6
7
8
9
10
uniform vec3      iResolution;           // viewport resolution (in pixels)
uniform float iTime; // shader playback time (in seconds)
uniform float iTimeDelta; // render time (in seconds)
uniform float iFrameRate; // shader frame rate
uniform int iFrame; // shader playback frame
uniform float iChannelTime[4]; // channel playback time (in seconds)
uniform vec3 iChannelResolution[4]; // channel resolution (in pixels)
uniform vec4 iMouse; // mouse pixel coords. xy: current (if MLB down), zw: click
uniform samplerXX iChannel0..3; // input channel. XX = 2D/Cube
uniform vec4 iDate; // (year, month, day, time in seconds)

根据我们对 uniform 常量的了解,我们知道,这是每一帧CPU将要传递给GPU的数据。

  • 标题: 从零开始的Shadertoy生活_02
  • 作者: aaaaa
  • 创建于 : 2025-06-30 15:00:00
  • 更新于 : 2025-06-30 12:10:08
  • 链接: https://redefine.ohevan.com/2025/06/30/从零开始的Shadertoy生活_02/
  • 版权声明: 版权所有 © aaaaa,禁止转载。