上面两节,主要讲解了 GLSL 中变量和函数的定义语法和使用语法,现在我们可以在 shader 中自定义一些我们所需要用到的变量和函数。但是在 shader 中, 还存在着一些内置的变量和函数。这些变量主要是用于将 Shader 计算得到的值传给 GPU,完成 Shader 在 Pipeline 中的功能。由于我们使用 shader 的目的就是为了把所需要的值传给 GPU,所以这些变量对我们非常重要。
Shader 中内置的函数也非常重要,就好比 C 语言中的 printf 一样,把我们想要使用到的功能用一个函数来包装起来,这样我们就可以更加方便的处理我们传入的参数,以得到我们想要得到的结果。那么这一节,我们将详细的讲解 GLSL 中的内置变量和函数。
由于在 OpenGL ES 的 pipeline 中,Shader 最主要的功能就是计算出绘制的顶点位置信息和颜色信息,那么这些信息需要由 Shader 传给 GPU,所以就需要在 Shader 中定义特定的变量,用这些变量来保存这些特定的信息值,所以,在 Shader 中,包含有很多内置的变量。
在 shader 中,内置变量主要用于 VS 的输出,PS 的输入和输出。然而针对 VS 的输出和 PS 的输入,内置变量与开发者自定义的 varying 变量不同。varying 变量本质上是在 VS 和 PS 中各定义一个名字类型一样的变量,进行一一对应。而内置变量则是在 VS 和 PS 中都存在。
我们可以把内置变量再区分成,VS 中的内置变量以及 PS 中的内置变量。
gl_position
变量 gl_position 只存在于 VS 中,属于 VS 中的内置变量。用法是在 VS 中,需要将顶点坐标写入这个变量。所有的 VS,都应该存在一个运算,就是把值写入这个变量中。当然这个写入的过程可以存在于 VS 的任何地方,写入之后这个变量也是可以被读取的。这个值非常重要,在 VS 结束之后,在 OpenGL ES 的 pipeline 中,在 GPU 光栅化的时候,这个值会被用于组元装配,也就是把若干个点按照一定的规则组成三角形等形状。根据这些顶点坐标,进行裁剪(裁剪:判断该像素点是否在我们的视口范围内)、剔除(判断该点是属于物体的内表面还是外表面,根据规则可以不对物体的外表面或者内表面进行绘制,相应的顶点也就会被剔除掉)等操作。如果 gl_Position 没有被赋值,或者在赋值之前就进行了读取,编译器会发现并提示。如果 gl_Position 没有被赋值,那么该变量为 undefine。
gl_PointSize
变量 gl_PointSize,也是只存在于 VS 中。属于 VS 的内置变量,用于说明点的尺寸,这个尺寸的单位是像素,比如把这个变量设置为 4,那么就代表一个 2*2 的像素方块是一个点。在做光珊化的时候,生成的新点也是 2*2 的像素方块。举个更容易想象的例子就是,打开画图工具,选择画直线,然后可以选择线的粗细, 比如选择细的线,画出的可能就是用 1*1 的像素方块构成的点组成的线,选择粗的线,画出的可能就是用 2*2 的像素方块构成的点组成的线。这个变量也非常重要,但是区别于 gl_Position,gl_PointSize 如果不被赋值也不会出现画不出来东西的情况。因为如果不赋值的话,虽然该变量也是 undefine,但是在光珊化的时候, 会按照点的大小为 1*1 的像素方块来处理。所以在大部分情况下,gl_PointSize 是不需要被重新赋值的。
虽然这两个变量是内置变量,不需要我们去定义它们,但是如果了解这两个变量的定义方式,对我们更好的使用这两个变量有很大的帮助。所以,借用我们上两节学到的知识,gl_Position 本质上就是一个 vec4 变量,精度修饰符为 highp, gl_PointSize 是一个 float 值,精度修饰符为 mediump。
VS 中只有上述两个内置变量。总结一下这两个变量。如果没有对他们进行赋值,那么它们的值将为 undefine。当对它们进行赋值之后,可以对它们进行读取,读取的是刚刚赋值进去的值。如果在赋值之前就进行读取,那么读取到的就 是 undefine。如果对它们进行多次赋值,那么它们最终的值就是最后一次赋予的值。它们在 VS 中都属于全局变量。
下面说一下 PS 中的内置变量。
gl_FragColor gl_FragData
PS 的输出参数,将会参与到 OpenGL ES 的固定管线中,而 PS 的输出参数主要就是内置变量 gl_FragColor 和 gl_FragData。除非是在 PS 中调用到了 discard 这 个关键词,否则该点经过 PS 计算出来的 gl_FragColor 或者 gl_FragData 将会被传给 OpenGL ES。
虽然有 gl_FragColor 和 gl_FragData 两种输出参数,但是并没有什么要求指定必须用哪一个。
和 VS 中的内置变量有一点一样,PS 中的这些变量也可以被赋值两次,取最后一次被赋予的值传给 OpenGL ES 进行固定管线的计算。这些变量被赋值之后, 可以对这些变量进行读取,如果在赋值之前就进行读取,就会得到 undefine。
对 gl_FragColor 进行赋值,顾名思义就是写入该像素点的颜色,在 OpenGL ES 的固定管线中,如果在 PS 之后对像素颜色进行读取,那么获取到的就是 PS 中 gl_FragColor 的值。如果在 PS 中没有对 gl_FragColor 进行赋值,那么像素颜色就是 undefine。
由于 gl_FragColor 和 gl_FragData 的作用不完全相同,且在 shader 中存在大量的算法,比如 shadowmap 或者 shadow volumn,这两个算法都是用于计算阴影的,那么它们需要使用同一个 VS,对应不同的 PS 进行多次绘制,其中会有一次绘制是为了屏幕的高度图,而不需要向颜色值中进行赋值。
在 PS 中,可以给 gl_FragColor 和 gl_FragData 中的任何一个值赋值,但是不能给它们两个同时赋值。如果在 shader 中执行了 discard,那么该像素就会被舍弃,gl_FragColor 和 gl_FragData 的值就会被舍弃。
gl_FragCoord
gl_FragCoord 相当于 PS 的输入参数,是只读的。gl_FragCoord 是个 vec4,四个分量分别对应 x、y、z 和 1/w。其中,x 和 y 是当前片元的窗口相对坐标,不过它们不是整数,小数部分恒为 0.5。x - 0.5 和 y - 0.5 分别位于[0, windowWidth - 1]和[0, windowHeight - 1]内。windowWidth 和 windowHeight 都以像素为单位,即 glViewPort 指定的宽高。gl_FragCoord.z 是固定管线计算出的当前片元的深度。它已经考虑了多边形偏移,并经过了投影变换。它位于[0.0, 1.0]之间。如果用gl_FragColor = vec4(vec3(gl_FragCoord.z), 1.0)将其可视化,多半会看到一片白。这是由于变换的非线性,大多数点的深度都非常接近于 1。用 gl_FragColor = vec4(vec3(pow(gl_FragColor.z, exp)), 1.0)并将 exp 取为合适的值,就能看到从黑到白的深度变化了。距离观察者近的颜色深,接近 0.0;距离观察者远的颜色浅, 接近 1.0;gl_FragCoord.z / gl_FragCoord.w 可以得到当前片元和 camera 之间的距离。
gl_FrontFacing
gl_FrontFacing 也相当于 PS 的输入参数,也是只读的,当当前像素属于几何体的正面的时候,gll_FrontFacing 为 true。在 OpenGL ES 的 API 中我们可以设置哪些顶点属于几何体的正面,哪些属于背面。这个参数的其中一个用法是:从两种光照中选择一个,来模仿物体的两面光照。
gl_PointCoord
最后一个内置变量 gl_PointCoord 也相当于 PS 的输入参数,也是只读的,是一个两维坐标,指出该点组元的当前像素点的位置,这个两维坐标已经被归一化了,如果当前组元不是点,那么 gl_PointCoord 为 undefine。
下面介绍一下 PS 中内置变量的定义原型。
gl_FragColor 是一个 vec4,精度修饰符是 mediump。
gl_FragData 是一个数组,一共包含 gl_MaxDrawBuffers 个值。这里说到了drawbuffer 的概念,我们在讲 EGL 的时候,也说到了,OpenGL ES 需要向绘制 buffer 上绘制东西,这里说的 drawbuffer,就是可以绘制的 buffer,也称为 RT(render target)。具体的解释等我们说到 OpenGL ES 的时候再进行解释说明。每个 gl_FragData 都是一个 vec4,精度修饰符也是 mediump。
gl_FragCoord 是个 vec4,精度修饰符是 mediump。
gl_FrontFacing 是一个 bool 值,bool 值是没有精度修饰符的。
最后一个 gl_PointCoord 是 vec2,精度修饰符也是 mediump。
上一节我们介绍过存储修饰符的功能就是用于说明它们和 VS、PS 之间的关系。虽然这些变量都没有存储修饰符,但是每个变量都已经详细说明了它们的作用,谁是哪个 Shader 的输入或者输出。比如:gl_FragCoord 就是 PS 的输入。
它们也都是全局变量。
说完了 GLSL 中的内置变量,下面来说一下内置常量。
在 GLSL 中有一些被事先定义好的内置常量。
首先,介绍一下它们究竟是定义在哪里,这里需要回顾一下比 OpenGL ES 更底层的东西了。OpenGL ES 是 API,也就是一些函数,那么这些函数肯定是有函数体的,这些函数体的定义,就是在 GPU 的 driver 中,可以认为 GPU 的 driver 提供了 OpenGL ES 的库,当然还定义了很多别的东西,比如刚才说的 GLSL 的内置变量,以及现在要说的内置常量,还有一会要说的内置函数等。我们上一节说过,在 shader 中支持的 attribute、uniform 等都是有数量限制的,那么这个数量可以通过 OpenGL ES 的 API 得到,然而这些数量也都是有限制的,不能说我这个硬件只支持 4 个 attibute,这样是不行的,因为 Khronos 对支持的 attribute 的最少数量也是有限制的,比如最少要支持 8 个 attribute,那么这个限制其实是可以从 GLSL 的这些内置常量中得到。在 GPU driver 中,已经把能支持多少数量的 attibute 等,通过内置常量的形式进行了保存。开发者通过这些内置常量,就能得知这个设备至少支持多少个 attibute 之类的。
不管是 VS 还是 PS,都可以访问这些内置常量。下面,来一一解释一下这些内置常量。
gl_MaxVertexAttribs
这个比较容易理解,因为我们知道存储修饰符为 attribute 的变量,只能存在于 VS 中,那么这个意思就是 VS 中至少要支持不能超过 gl_MaxVertexAttribs 这么多的 attribute,gl_MaxVertexAttribs 的定义是 8。刚才这句话比较绕口,解释一下,就是说我们知道在 Shader 中 attribute 是非常有用的,但是由于受到硬件的限制,一个 shader 中 attribute 的数量不能特别多,不能超过硬件的限制,那么这里就是一个 shader 中不能超过 8 个 attribute,那么这个 8 又是 Khronos 定义给硬件厂商看的,意思就是硬件厂商在生产的时候可以生成超过 8 个位置给 attribute,比如可以说生成 16 个,生成 24 个,都没有问题,但是不能少于 8 个, 如果少于 8 个,Khronos 是不会对这个硬件进行认可的。所以我们在开发的时候, 如果 attribute 的数量少于 8 个,就放心的使用,没有问题,因为所有的硬件,无 论什么硬件,8 个都是可以支持的,但是如果某个开发者想要支持超过 8 个attribute,比如要在一个 shader 中使用 16 个 attribute,那么需要用 OpenGL ES 的 API 先查询一下,看当前设备是否支持 16 个 attribute。这个 API 等我们说 OpenGL ES API 的时候再进行说明。
gl_MaxVertexUniformVectors gl_MaxFragmentUniformVectors
上一节学到了 uniform 是可以存在于 VS 和 PS 中的,一般说,在 VS 中定义的 uniform,只能在 VS 中使用,在 PS 中定义的 uniform,只能在 PS 中使用。但假如在 VS 和 PS 中定义了一个变量名、修饰符都一样的 uniform,那么它们就是 一个 uniform。那么对应于这两个内置常量,第一个常量 gl_MaxVertexUniformVectors 定义了 VS 中至少需要支持不超过 gl_MaxVertexUniformVectors 个的 uniform , 第二个常量 gl_MaxFragmentUniformVectors 定义了 PS 中至少需要支持不超 过 gl_MaxFragmentUniformVectors 个的 uniform。而且 uniform 我们知道是支持多种数量类型的,比如 float,比如 vec3,比如 mat4。上一节也说了,一个 mat4 的 uniform 占用了 4 个 vec4 的位置。在这里 gl_MaxVertexUniformVectors 为 128, gl_MaxFragmentUniformVectors 为 16。也就是说在 VS 中,支持 128 个 vec4,但 是如果使用了 129 个 float 的 uniform,那么就出错了,支持 128 除以 4 等于 32 个 mat4 的 uniform,但是如果使用了 33 个 mat4 的 uniform 也就出错了。
gl_MaxVaryingVectors
varying 变量同时存在于 VS 和 PS 中才算有意义,所以这里也就不区分 VS 还 是 PS 了。那么这里也就是在一对 shader 中,最多只能存在 gl_MaxVaryingVectors 个 varying 变量,计量标准也是 vec4。
补充一句,刚才我们说了针对 attribute、uniform 和 varying 变量的限制,但是我们之前也说了假如定义了却没有使用的 attribute、uniform、varying,以及其他一些非 static use 的话,那么这些变量的定义是无效的,也就是不会被计入这种计算限制的数量中。也就是说我们就是在 VS 中定义了 10000 个 uniform,但是我们没有使用,shader 也就把它们优化掉,就相当于没有定义,在使用这个 shader 的时候也就不会出错。
我们知道在 OpenGL ES 中纹理非常重要,但是 texture 的使用也是受到限制的。纹理的使用比较复杂,并非我们想象中,直接显示使用几张 texture。纹理其实首先要被放到 texture unit 里面,然后把 texture unit 传入 shader。虽然说 OpenGL ES API 的时候我们还会详细介绍 texture unit,但是这里,我们还是先简单的解释一下。texture 在 OpenGL ES 中分为两种,一种是 2D 的,一种是 CubeMap 的,texture unit 类似于一个容器,一个 texture unit 中,每种 texture 可以各放一张,也就是 texture unit 中最多可以包含两张不同类型的 texture,如果再往里面放的话,就会把之前的 texture 覆盖掉。然后把 texture unit 往 shader 中传送的时候,在一套 shader 中,只能使用其中的一张 texture。
gl_MaxVertexTextureImageUnits
指的是 VS 中最多的 texture unit 的数量,这个内置常量被定义为了 0,可能是因为 Khronos 认为 VS 中不使用 texture 也没关系,也确实 texture 基本是被使用在 PS 中的。
gl_MaxCombinedTextureImageUnits
一般 texture 都是被作为 uniform 传入,这个也可以理解,因为 texture 基本是在 PS 中使用的,如果通过 attribute 传入,然后再通过 varying 传给 PS,一个是比较复杂,另外一个 Varying 是需要被光珊化的,所以 Varying 越少,对性能越好。那么 texture 基本都是 uniform,而作为 uniform,就存在刚才我们也提到的那个机制,就是 VS 和 PS 中的两个完全一样的 uniform 被认为是一个 uniform, 这里我们称之为 combine,也就是 VS 和 PS 中共同定义的 texture unit 的数量,最多是 8。
gl_MaxTextureImageUnits
指的是 shader 中 texture unit 的数量。
gl_MaxDrawBuffers
指的是最多可支持的绘制 buffer,绘制 buffer 的概念我们就不再重复了。一般情况下 OpenGL ES2.0 中,只使用一个 draw buffer 的,在这里 khronos 也把这个常量定义为了 1。而在 OpenGL ES 3.0 中开启了一个新的概念 MRT,就是同时有多个绘制 buffer,这个等我们以后开 OpenGL ES 3.0 的课程的时候再进行说明。
gl_DepthRange
最后还有一个特殊的内置变量,一个内置的 uniform 。刚才我们说的内置常量和变量,都是独立于 attribute、varying、uniform 之外的,而这个变量,就是一个 uniform。使用起来好比一个我们正常定义的 uniform,可以通过 OpenGL ES API 得到它的 location,然后传值进入。这个 uniform 就是 gl_DepthRange,类型是一个 struct gl_DepthRangeParameters,该 struct 有三个成员变量,分别是 near、far、 diff。
// Depth range in window coordinates
struct gl_DepthRangeParameters
{
highp float near; // n
highp float far; // f
highp float diff; // f – n
};
}uniform gl_DepthRangeParameters gl_DepthRange;
这三个成员变量都是 float 的,精度修饰符都是 high,但是由于默认 PS 是不支持 high 的,所以在这些设备中,这三个成员变量就相当于精度修饰符为 mediump。这个变量是用于存放在三维空间中的视野空间中的深度范围。具体的等我们在 OpenGL ES 中说到的时候再进行解释说明。在这里我们只需要知道这个内置 uniform 占用了一个 uniform 的名额。就好比刚才我们知道 VS 中只能有 128 个 uniform,那么除去这一个,我们开发者只能再定义 127 个 uniform 了。
GLSL 中是没有内置 attribute 和 varying 的。
GLSL 中定义了若干系列非常好用的内置函数,这些内置函数有一部分是在 VS 和 PS 中都可以被使用的,但是也有一部分是只能使用在某种 shader 中的。
内置函数大体上可以分为三种类型:
第一种函数提供了一种方便的方式,用于处理对硬件的操作,比如对纹理的处理。在 shader 中,开发者没有办法使用任何一种方式去替代这类函数,因为这类函数中涉及到了对硬件的操作。
第二种函数其实只提供了一些简单的平常的运算,比如 clamp 等,这些运算开发者可以很轻松的实现。但是由于这些运算很常用,所以提供一个统一的库就省去了开发者自己写的时间。而且也可以 GPU 内部的硬件做一定的优化,比如指数运算,通过汇编来实现句,如果交给普通的硬件处理就很复杂,而使用优化后对应的硬件去实现,就会很简单。
第三种函数是使用 GPU 硬件去实现一些函数,这样会对这些运算进行提速,比如三角运算。
大部分函数都类似于 C 语言中的同名函数,只是这些函数的输入支持 vector,比如 vec4 或者 mat4,而不仅仅是标量输入。
开发者被鼓励使用内置函数。开发者可以重载这些内置函数,但是不能对这些函数进行重写。
在这些内置函数的声明中没有注明精度修饰符,但是针对 texture 运算,返回类型的精度和 sample 类型的精度一致。其他精度修饰符的规则我们在上个课时讲精度修饰符的时候也进行过详细的说明。返回值的精度修饰符与输入参数的最高精度修饰符一致。
下面,我们来具体的说一下内置函数。
Angle and Trigonometry Functions
三角函数。比如角度和弧度的变换,弧度也就是角度除以 180 再乘以派。再比如常用的正弦,余弦,正切,反正切,反正弦,反余弦。假如 x 的正弦是 y, 那么 y 的反正弦为 x,这些函数就不进行具体介绍了。
Exponential Functions
指数预算。比如 pow 函数,传入 x 和 y 得到 x 的 y 次方。比如 exp 函数,传入x得到e的x次方。比如log函数,传入x得到log x,假如得到的结果是y, 也就是 e 的 y 次方是 x。比如 exp2 函数,也就是返回 2 的 x 次方。log2 函数,传入 x 返回 log2 x,假如得到的结果是 y,也就是 2 的 y 次方是 x。sqrt 函数,开根号函数,传入 x 得到根号 x。inversesqrt 函数,开根号然后求倒数,传入 x 得到 根号 x 分之 1。
Common Functions
普通的运算函数。比如 abs 取绝对值的函数。sign 取符号的函数,比如传入的 x 大于 0,则得到 1,等于 0 得到 0,小于 0,得到-1。floor 函数,也就是得到最接近且小于或者等于 x 的值。比如 4.6 则得到 4,-4.6 则得到-5。ceil 函数,于 floor 函数相反,得到最接近且大于或者等于 x 的值。比如 4.4 得到 5,- 4.4 得到-4。fract 函数,得到 x-floor(x),也就是 4.6 的话会得到 4.6-4=0.6。
mod 函数,传入 x 和 y,得到 x - y * floor(x/y)。min 和 max 函数,传入 x 和 y,得 到 x 和 y 中的最小值或者最大值。clamp 函数,传入 x 和 min 和 max 三个参数, 如果 x 在 min 和 max 之间,返回 x,如果 x 比 min 小,得到 min,如果 x 比 max 大,得到 max,但是如果 min 大于 max,得到 undefine。mix 函数,传入三个参 数 x,y 和 a,得到 x*(1-a) + y*a。step 函数,传入参数 x 和 y,如果第一个参数小于第二个参数则返回 0,否则返回 1。smoothstep 函数,传入三个参数 a,b, x,如果 x 小于等于 a,得到 0,如果 x 大于等于 b,得到 1,如果 a<x<b,则按照一定规则进行插值,如果 a 大于或者等于 b,则返回 undefine。
Geometric Functions
几何运算。比如 length 函数,求长度,假如传入一个 vec3 的 a,那么得到的就是 a.x 的平方+a.y 的平方+a.z 的平方得到的结果开根号。distance 函数,求距离,假如传入两个 vec3 a 和 b,得到就是 a-b 得到的 vec3 变量的 length。dot 函数,求两个向量的点积,假如传入两个 vec3 a 和 b,得到就是 a.x*b.x+a.y*b.y +a.z*b.z。cross 函数,求两个向量的叉积。normalize 函数,归一化函数,比如传入一个向量 vec3 x,那么把 x 的三个分量分别除以 a 的长度,得到的 vec3 就是结果。faceforward,传入三个参数,a b c,假如 b 和 c 的点积小于 0,返回 a, 否则返回-a。reflect 函数,得到反射向量。refract 函数,得到折射向量,这两个函数属于光照方面的函数,算法比较复杂,在这里我就不具体说了。
Matrix Functions
内置的矩阵运算只有一个,matrixCompMult,传入两个矩阵,计算乘法,得到他们的乘积。
Vector Relational Functions
内置的 Vec 运算有很多,可能是因为 shader 中绝大多数都是 vec 操作。比如比较函数中的小于函数,小于等于函数,大于函数,大于等于函数,等于函数和不等于函数。any 函数,传入一个 bvec,这里的 bvec 可以是 bvec2、bvec3、bvec4. 那么假如其中一个成员为 true,any 函数为 true。all 函数,只有所有的成员都是 true,all 函数才是 true。not 函数,返回一个于传入参数完全相反的 bvec。
Texture Lookup Functions
最后一种内置函数,是 texture 相关的内置函数。也就是在 VS 和 PS 中访问texture 的函数。最常规的函数就是 texture2D,传入一个 texture 的 sample,和一个纹理坐标,这样可以得到纹理的像素点对应到图片上。这个函数还可以再传一个 bias,bias 是用于计算 lod 的。因为 texture 可以通过 mipmap 分为很多层,从小到大,小的图片精度低但是使用起来效率快,大的图片精度高,但是使用起来耗能。bias 就是 lod 的 bias,决定取哪层 mipmap 的。假如有 bias,且开启了纹理的mipmap功能(使用glGeneratemipmap生成该纹理的mipmap数据,或者使用glTexImage2D定义了该纹理所有层mipmap信息,同时该纹理的minfilter设置为带mipmap的参数;如果没有使用glGenerateMipmap,而是使用glTexImage2D生成数据,但是又没有生成所有mipmap层次的数据,假如minfiler设置为带mipmap的参数,则显示黑色。),那么就先计算应该用哪层 mipmap,然后再根据bias进行偏移,比如当前应该使用第1层mipmap,而bias为1,那么则使用第2层mipmap数据,如果bias为-1,则使用第0层mipmap数据。如果没有 bias,且 texture 没有 mipmap,则 texture 直接使用,如果有 mipmap,那么在 PS 中则使用应该使用的那层 mipmap,在 VS 中则使用 base texture。 texture2DProj 函数,传入的纹理坐标区别于刚才的 vec2,而使用 vec3 和 vec4。 与刚才纹理坐标的区别是,这里虽然传入 vec3 或者 vec4,但是也是当作 vec2 使 用的,只是前两个成员会除以 vec3 或者 vec4 的最后一个成员,这里也可以传入 bias。texture2DProjLod 函数,这种函数只能在 VS 中使用。这些是 2D 的 texture, CubeMap 的 texture 也类似的有几种函数,texturecube 和 texturecubelod,就不一一介绍了。
通过以上这四节的学习,对 GLSL 的语法进行了详细的串讲。
首先学习了 GLSL 中的预处理和注释。
然后,学习了 GLSL 中变量的基本知识,包括变量的类型,其中有标量类型、向量类型还有复杂类型等,讲了如何定义变量、初始化变量,对变量进行操作,以及从哪里获取变量的范围。
再然后,讲解了变量的修饰符,包括 4 种,存储修饰符确定作用域和作用, 参数修饰符确定变量是函数的传入还是传出参数,精度修饰符确定精度, variance 修饰符确定变量是否是恒定的。
最后,讲解了 GLSL 中的内置变量、常量和函数,这些非常重要且有用,对它们进行充分理解之后,GLSL 最重要的语法和目的就基本上全部掌握了。
本节教程就到此结束,希望大家继续阅读我之后的教程。
谢谢大家,再见!
原创技术文章,撰写不易,转载请注明出处:电子设备中的画家|王烁 于 2017 年 7 月 11 日发表,原文链接(http://geekfaner.com/shineengine/blog7_OpenGLESv2_6.html)