☰
GLSL int 整数类型完全指南:声明、类型转换与着色器实战(The Book of Shaders 术语解读)
2026/10/16 5:35:50 网站建设 项目流程

【免费下载链接】thebookofshaders

Step-by-step guide through the abstract and complex universe of Fragment Shaders.

项目地址:https://gitcode.com/gh_mirrors/th/thebookofshaders
点击查看免费下载

在 GLSL(OpenGL Shading Language)的世界里,一切图形计算都建立在基础数据类型之上,而int(整数)是其中最核心的标量类型之一。本文以 The Book of Shaders 术语表中 int 词条为主线,系统讲解 GLSL 整数的声明方式、int()类型转换语义、整型向量ivec2/ivec3/ivec4的初始化规则,并结合仓库内shapes.frag、iching-01.frag、2d-voronoi.frag等真实着色器源码,演示int在循环、多边形绘制与网格采样中的典型用法。读完本文,你将能在自己的片段着色器中正确使用整数变量、避开混合类型运算的编译陷阱,并理解 GLSL 强类型系统带来的性能收益。

int 是什么:GLSL 三大标量类型之一

在 术语表 中,int被归入TYPES(类型)分类,官方英文词条给出的定义简洁明确:"Integer data type",即整数数据类型;乌克兰语版本(即本文关联文档 glossary/int/README-ua.md)进一步说明:int用于表示取整数值的变量。

GLSL 一共提供了三种基础标量类型,这在附录四 · GLSL 类型详解中有系统介绍:

  • bool:逻辑类型,仅可取true或false;
  • int:整数类型;
  • float:单精度浮点类型。

与 JavaScript 不同,GLSL 是强类型语言:你不能使用var关键字,必须显式声明每一个变量的类型。附录中的对照代码清晰地展示了这一点:

// 逻辑值 JS: var b = true; GLSL: bool b = true; // 整数值 JS: var i = 1; GLSL: int i = 1; // 浮点值 JS: var f = 3.14159; GLSL: float f = 3.14159;

这种"显式类型"约束看似繁琐,实则让编译器可以在编译期确定每个变量的类型与内存布局,从而生成更快、更适合 GPU 并行执行的代码——这是着色器性能优于 JS 逐像素运算的重要原因之一。

声明与初始化:从词条示例出发

术语词条给出的int声明示例是理解整数的第一手材料:

int aInt = 42; int bInt = int(aBool); int cInt = int(aFloat);

三行代码分别演示了三种整数变量的来源:

  1. 直接初始化:int aInt = 42;将一个整数字面量赋给变量;
  2. 从逻辑值转换:int bInt = int(aBool);利用与类型同名的构造函数完成转换;
  3. 从浮点值转换:int cInt = int(aFloat);将浮点数截断为整数。

同时,词条强调了一个容易被忽视的事实:int不仅是一个类型关键字,还是一个与类型同名(intrinsic)的转换函数,用于把数据强制转换到整数类型。这也呼应了附录中"类型的名字同时就是它自己的构造函数"的说法。

int() 构造函数与类型转换的精确语义

关于int作为构造函数的用法,附录四给出了更丰富的细节。下面四个声明在 GLSL 中完全等价、全部合法:

int i = 1; // 直接字面量初始化 int i = int(1); // 整数字面量经构造函数初始化 int i = int(1.9995); // 浮点数转换 int i = int(true); // 布尔值转换

理解转换语义至关重要,其中两条规则在实际写 shader 时几乎必然用到:

  • float→int的转换等价于Math.floor():int(1.9995)会丢弃小数部分得到1,而不是四舍五入。也就是说,转换结果是向零方向截断;
  • float/int→bool的转换规则:只要变量值非零,转换为bool即为true。

此外,GLSL 不允许不同标量类型之间直接进行算术运算。附录用一个生动的比喻说明:把int和float直接相乘,就像试图"把猫和长颈鹿杂交",编译器会直接报错。例如:

int i = 2; float f = 3.14159; // 错误:试图混合 int 与 float 运算 float r = i * f;

正确做法是使用显式类型转换,让编译器"相信"i是float,而变量的实际类型并未改变:

// 将整数变量 i 显式转换为 float 后再参与乘法 float r = float(i) * f;

这一规则在仓库的真实着色器中就有教科书级的应用。看 07/shapes.frag 中绘制多边形(以三角形为例)的代码:

// Number of sides of your shape int N = 3; // Angle from the current pixel float a = atan(st.x,st.y)+PI; // Angular spacing between vertices of shape float r = TWO_PI/float(N);

这里TWO_PI是#define的浮点常量,N是int,因此作者必须写float(N)完成显式转换,才能与TWO_PI相除。这正是 GLSL 强类型规则的实战印证——凡涉及 int 与 float 混合运算,必须先做转换。

整型向量:ivec2 / ivec3 / ivec4

除标量int外,GLSL 还提供了三个整型向量类型,对应术语表中 ivec2、ivec3、ivec4 三个词条:

  • ivec2:2 分量整型向量;
  • ivec3:3 分量整型向量;
  • ivec4:4 分量整型向量。

它们与浮点向量vec2/vec3/vec4、逻辑向量bvec2/bvec3/bvec4一一对应,用于以"维度"为单位组织整数数据(例如像素坐标、网格索引、循环计数器组等)。整型向量的初始化方式比标量更灵活,词条总结了四种模式:

  1. 为每个分量分别提供标量值:

    ivec2 aIvec2 = ivec2(1, 1); ivec3 aIvec3 = ivec3(1, 1, 1); ivec4 aIvec4 = ivec4(1, 1, 1, 1);
  2. 提供一个标量,广播到所有分量:

    ivec2 bIvec2 = ivec2(1); // (1, 1) ivec3 bIvec3 = ivec3(1); // (1, 1, 1) ivec4 bIvec4 = ivec4(1); // (1, 1, 1, 1)
  3. 从更高维度向量截取分量:

    ivec2 cIvec2 = ivec2(aIvec3); // 取 aIvec3 的前两分量 ivec2 dIvec2 = ivec2(aIvec3.x, aIvec3.y); ivec3 cIvec3 = ivec3(aIvec4); // 取 aIvec4 的前三分量
  4. 向量与标量的混合组合(构造函数参数总数不得少于目标向量的分量数):

    ivec3 eIvec3 = ivec3(aIvec2, aInt); // 向量 + 标量 ivec3 fIvec3 = ivec3(aIvec2.x, aIvec2.y, aInt); ivec4 cIvec4 = ivec4(aIvec2, aInteger, aIvec3); // 混合组合

借助分量选择符(.x/.y/.z/.w)与构造函数的组合,可以极其灵活地组装出任意结构,这也是 GLSL 向量体系的强大之处。

仓库实战:int 在循环与采样中的典型用法

int最频繁出现的场景是for循环的计数器。由于 GPU 着色器的执行模型,GLSL 中的循环上限必须能在编译期确定,因此循环变量几乎总是int且边界为常量。仓库内多个示例体现了这一模式:

1. 六爻(IChing)解码循环—— 09/iching-01.frag 用固定次数的整数循环把卦象数值拆解为 6 位二进制:

float b[6]; float remain = floor(mod(N,64.)); for(int i = 0; i < 6; i++){ b[i] = 0.0; b[i] = step(1.0,mod(remain,2.)); remain = ceil(remain/2.); }

2. Voronoi 邻域扫描—— 12/2d-voronoi.frag 用两层嵌套的整数循环遍历 3×3 邻域格子,int i和int j的取值区间[-1, 1]直接由整数字面量确定:

for (int j= -1; j <= 1; j++) { for (int i= -1; i <= 1; i++) { vec2 g = vec2(float(i),float(j)); vec2 o = random2( n + g ); ... } }

注意这里再次出现float(i)/float(j):循环计数器是int,而要参与vec2浮点运算,必须显式转换为float。同样的模式还出现在 12/metaballs.frag(vec2 neighbor = vec2(float(i),float(j));)、12/cellnoise-02.frag、12/tissue.frag 等示例中。

3. 多边形边数控制—— 前文提到的 07/shapes.frag 用int N = 3;声明边数,再通过float r = TWO_PI/float(N);计算每个顶点的角度间隔,最终借助cos(floor(.5+a/r)*r-a)*length(st)生成多边形距离场。只需把N改为其他整数值(如 4、5、6),形状就会相应变为正方形、五边形、六边形,这是用int参数化几何形态的经典技巧。

从上述案例可以总结出两条实用经验:

  • 循环边界用整型常量:for(int i = 0; i < 6; i++)中6是编译期常量,确保循环可被 GPU 完全展开;
  • 涉及浮点运算前先转换:float(i)、float(N)是仓库中出现频率最高的类型转换写法,可避免"猫和长颈鼠杂交"式的编译错误。

与 bool、float、void 的关系及更多阅读

int并非孤立存在,它与其它类型在转换语义上紧密关联:

  • int↔bool:int(true)得1,int(false)得0;反向转换时非零整数为true,零为false(参见 bool 词条);
  • int↔float:float(aInt)得到浮点值;int(aFloat)向零截断(参见 float 词条);
  • void:表示"无返回值"的占位类型,不可用于变量声明(参见 void 词条)。

如果希望进一步深入 GLSL 的类型体系,建议按以下路径继续阅读当前仓库:

  1. 术语表总览:按主题与字母序浏览全部类型、限定符与内建函数;
  2. 与int直接相关的词条:bool、float、ivec2、ivec3、ivec4;
  3. 附录四 · 类型、转换与向量:完整讲解标量类型、类型转换、构造函数与向量/矩阵体系;
  4. 实战源码:07/shapes.frag(int 控制多边形)、09/iching-01.frag(int 循环解码)、12/2d-voronoi.frag(int 邻域扫描)。

小结

int是 GLSL 三大标量类型之一,承担着循环计数、几何参数化与索引计算等不可替代的职责。理解它的关键在于三件事:显式声明(强类型、无var)、构造函数语义(float → int截断、非零转bool为真)、以及混合类型运算必须先转换。结合 The Book of Shaders 仓库中shapes.frag、iching-01.frag、2d-voronoi.frag等示例,你可以在实际着色器编写中熟练运用int与ivec2/3/4,写出既高效又符合 GLSL 类型规则的代码。

【免费下载链接】thebookofshaders

Step-by-step guide through the abstract and complex universe of Fragment Shaders.

项目地址:https://gitcode.com/gh_mirrors/th/thebookofshaders
点击查看免费下载
上一篇:Domain Hunter vs 传统工具:为什么这款Burp插件能提升80%的域名收集效率?
下一篇:Gittyup与命令行对比:为什么图形化Git客户端更适合团队协作

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询