这篇文章总结了一些GLSL语言的基础知识

GLSL总体上的写法和C语言相似

变量

声明变量的格式是这样的:

格式是变量类型 变量名=值;

比如我声明一个foo

float foo=1.;

这样我就给float浮点类型的数字变量foo赋值了一个1.0

在写的时候是可以忽视0的,1.0可以写成1.,0.5可以写成.5

浮点型变量必须要有小数点,不能省略!

在写GLSL的时候一定要记得:

语句结尾一定要加一个分号;。这是新手最容易会犯的错误。

浮点型float可以说是GLSL里最常用的类型。

除此之外,还有比较常用的是int整型,代表了整数,它无需加小数点

int foo=1;

还有一种bool布尔型,代表逻辑值,即真(true)或假(false),通常用于条件判断和控制流程。

    bool foolBool = true;

    bool bar = fragCoord.x < iResolution.x * .25;

    if (bar) {
        fragColor = vec4(1., 0., 0., 1.);
    } else {
        fragColor = vec4(0., 1., 0., 1.);
    }

上面提到的变量都是一维变量(标量),如果想要声明更高维度的变量的话,就要用到vec类型(也就是向量类型)。

vec2 a=vec2(1.,0.);
vec3 b=vec3(1.,.5,0.);
vec4 c=vec4(1.,.5,0.,1.);

vec类型支持 3 种维度:二维vec2、三维vec3和四维vec4,四个维度分别为xyzw(也可以写为r、g、b、a)。如果想对多维度变量进行取值或赋值操作,就要用到.符号,并且四个维度可以任意组合(称为Swizzling)。

vec2 d=b.xy;// d的值为vec2(1.,.5)
d.y=2.;// d的值为vec2(1.,2.)
vec3 e=c.yxy;// e的值为vec3(.5,1.,.5),c.yxy可以理解为vec3(c.y,c.x,c.y)
e.zx=vec2(1.);// e的值为vec3(1.,1.,1.)

除了vec类型有更高的维度支持外,mat类型(也就是矩阵类型)亦是如此。mat2类型代表了一个大小是2x2的矩阵,mat3类型则代表了一个3x3的矩阵、mat4类型是4x4的矩阵。

  • 矩阵是一个二维数组,它的每一行代表一个向量。
  • 矩阵的每一列代表一个向量的分量。
  • 矩阵的行数和列数决定了矩阵的维度。
  • 矩阵的维度是一个二维向量,它的x分量代表行数,y分量代表列数。
    mat2 f = mat2(1., 0., 0., 1.);
    mat3 g = mat3(1., 0., 0., 0., 1., 0., 0., 0., 1.);
    mat4 h = mat4(1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1.);

而我们要调用它也是很简单的:

    // 渲染矩阵
    fragColor = vec4(f[0].x, f[0].y, f[1].x, f[1].y);
  • 矩阵如何调用的?
  • 矩阵的调用方式是矩阵名行号。
  • 行号和列号都是从0开始的。
  • 矩阵的行号和列号都是整数,所以可以用整数变量来调用矩阵的元素。
  • f[0]就是矩阵f的第一行,f[1]就是矩阵f的第二行。
  • f[0].x就是矩阵f的第一行的第一列,f[0].y就是矩阵f的第一行的第二列。

如果想把多个变量捆绑到一个变量上,可以使用结构体struct,它的使用方式如下所示

    struct Foo {
        vec2 a;
        vec3 b;
        vec4 c;
    };

    Foo i;
    i.a = a;
    i.b = b;
    i.c = c;

    fragColor = vec4(i.a, i.b.x, i.c.y);

顺便提一嘴,vec4的变量是可以合起来的,比如上面的代码示例,vec4四维变量可以用vec2(x,y)+ z + w来组合起来,很灵活

在创建变量时,我们也可以先声明变量,然后再对它进行赋值操作,并且赋值是从右到左进行的,因此,我们可以同时给多个变量赋予同一个值。

    float one, two, three;
    one = two = three = 1.;

运算符

算数运算符

执行基本的四则运算

   float computA = 1.;
   float computB = 2.;

   float computC = computA + computB; // 3.
   float computD = computA - computB; // -1.
   float computE = computA * computB; // 2.
   float computF = computA / computB; // 0.5

   float g = mod(computA/computB) // 取余,值为1.

赋值运算符

用于将值赋给变量,赋值时也可同时进行算数运算。
(和一般编程语言差不多,比如a+=k这样的)

   float varA, varB, varC, varD;
   varA = varB = varC = varD = 3.;
   float varE = 2.;

   varA += varE; // varA = varA + varE; // 5.
   varB -= varE; // varB = varB - varE; // 1.
   varC *= varE; // varC = varC * varE; // 6.
   varD /= varE; // varD = varD / varE; // 1.5

除此之外,还有比较运算符>,<,==等)、逻辑运算符(&&,||,!等),这些运算符在一般的编程语言里也是有的,不多说了

唯一要注意的一点是:运算一定要保证维度的匹配,比如你不能将一个vec2的变量与一个vec3的变量相加,要加的话你得选取vec3变量的其中 2 个维度,转成vec2变量才能与另一个vec2变量相加。

    vec3 color = vec3(1., 0., 0.);
    vec2 uv = fragCoord / iResolution.xy;

    vec2 res = color.xy + uv;

当然,也有一种特殊的情况:当一个向量和一个标量进行运算时,GLSL会将标量广播(broadcast)到向量的每一个分量上。

当一个向量与一个标量进行数学运算时,这个标量会被自动扩展(或广播)到向量的每一个分量上。这意味着,标量的值将被应用于向量的每一个分量,执行相应的运算。

例如,考虑一个二维向量 vec2 和一个标量值。如果你进行减法运算,GLSL会将标量值分别减到向量的两个分量上:

vec2 a=vec2(1.);
a-=.5;// a的值为vec2(.5)

函数

当你想要复用一个代码片段时,只需要定义一个函数就行,比如说:

float add(float a,float b){
    return a+b;
}

float c=add(1.,2.);// c的值为3.

函数要声明在main之外,并且从上到下调用,所以在main函数之前

一个函数包括返回类型(无返回类型为void)、函数名、带类型的参数和返回值(可选)。调用函数时也要严格遵循定义好的类型。

当然,GLSL本身也内置了相当多的函数,在以后我们会逐渐地碰到它们。

控制流

和常规的编程语言一样,GLSL有一些基本的控制流结构。

条件语句if,可以根据条件来执行不同的代码块。

if(condition){
    // ...
}else if(anotherCondition){
    // ...
}else{
    // ...
}

这里要注意一点:Shader是针对整个屏幕的像素进行处理的,因此if的所有分支只要满足一定的条件,都会被执行,也就是说,某一个分支对于其中一个点无效,那对应除了这个点之外的其他点可能就有效了。

循环语句for,可以用来重复执行一段代码。

for(int i=0;i<8;i++){
    // repeat ...
}

此外,还有while、do-while、switch这些常规的编程语言都支持的语句,这里就不再赘述了

变量限定符

变量限定符是用来描述变量的存储和使用方式的关键词。

它的书写格式是变量限定符 变量类型 变量名;,语句结尾要加分号,例如:

uniform vec3 uColor;

以上代码声明了一个uniform类型的名为uColor的 3 维向量。

GLSL中,常用的变量限定符有以下几种:uniformconstvaryingattribute等。

目前我们先要了解uniformconst,其他的我们以后会碰到。

uniform

uniform变量是一种全局的变量,一旦定义后会同时存在于顶点着色器与片元着色器中,并且它在每一个顶点和片元上的值都是相同的,是一个“统一”的值。

在我们目前的Shader创作环境中,有一些内置的uniform变量可供我们直接使用,无需显式地去声明它们。

要注意的一点是:以后在three.js环境下,这些变量就不是内置的了,需要手动地去注入和声明。

iTime

iTime变量表示Shader从开始到现在执行所经过的时间。

如果要创作动画效果,这个变量可以说是必备的。

iResolution

iResolution变量表示Shader所在画布的大小,默认是占满整个屏幕。

有时我们可能会根据屏幕的比例对Shader的坐标进行适当的调整。

iMouse

iMouse变量表示用户鼠标当前所在的位置。

如果要创作可交互的效果,这个变量也是必备的。

const

const定义的量是一种常量,它是无法被改变的一个值。
比如我声明一个圆周率

const float PI=3.14159265359;

宏(macros)是一种预处理指令,用于在编译时进行文本的替换,常用于定义常量、函数、条件编译等。

宏定义的格式是#define 宏的名称 宏的值,语句结尾没有分号

#define PI 3.14159265359

以上代码定义了一个名为PI的宏,Shader编译时会将所有的PI替换为3.14159265359这个值,可以类比为JSString.replace这个函数。

宏也可以带有参数,如下所示:

#define add(a,b) a+b

以上代码定义了一个名为add的宏,接受 2 个参数ab,对它们应用相加的运算,并且无需指定明确的类型,调用这个宏时只要参数的类型相匹配,就能正确执行宏定义的运算。

宏也可以条件编译,例如:

#define IS_IN_SHADERTOY 1

#if IS_IN_SHADERTOY==1
#define iChannel0Cube iChannel0
#endif

这个宏的#if部分会判断IS_IN_SHADERTOY这个宏的值是否为 1,是的话就会将下面的一行宏代码#define iChannel0Cube iChannel0包含到Shader代码中去,不是的话就忽略掉这行宏代码

需要注意的是,宏定义是一种文本替换机制,并没有类型检查和作用域限制,因此要根据实际的情况来使用它。

最后修改:2024 年 08 月 14 日
收款不要了,给孩子补充点点赞数吧