OpenGL ES _ 着色器_语法

OpenGL ES _ 入门_01
OpenGL ES _ 入门_02
OpenGL ES _ 入门_03
OpenGL ES _ 入门_04
OpenGL ES _ 入门_05
OpenGL ES _ 入门练习_01
OpenGL ES _ 入门练习_02
OpenGL ES _ 入门练习_03
OpenGL ES _ 入门练习_04
OpenGL ES _ 入门练习_05
OpenGL ES _ 入门练习_06
OpenGL ES _ 着色器 _ 介绍
OpenGL ES _ 着色器 _ 程序
OpenGL ES _ 着色器 _ 语法
OpenGL ES_着色器_纹理图像
OpenGL ES_着色器_预处理
OpenGL ES_着色器_顶点着色器详解
OpenGL ES_着色器_片断着色器详解
OpenGL ES_着色器_实战01
OpenGL ES_着色器_实战02
OpenGL ES_着色器_实战03

OpenGL ES _ 着色器_语法
学习是一件开心的额事情

学习那些内容

  • 程序从什么地方执行
  • 声明变量
  • 构造函数
  • 聚合类型
  • 如何访问向量和矩阵中的元素
  • 结构
  • 数组
  • 类型限定符
  • uniform 块
  • 语句
  • 函数

你不知道我在说什么,请从这里开始,以上就是我们今天要讲的内容,(OpenGL Shading Language)加油!


内容详细讲解

  • 程序的起点
    着色器程序就像C 程序一样,是从main() 函数开始的,每个GLSL 着色器函数都是从下面结构开始执行的

    void main(){
    //code
    }
    

注释也是使用// 或者“/”和"/"

  • 变量
    首先要说一点,GLES 是一种强类型的语言,强类型形语言有个特点,每个变量必须进行声明,Swift 也是强类型语言,那为什么不用声明变量呢,因为它可以进行类型推断。GLES 有自己的变量类型,变量命名与c语言一样,可以使用字母,_ 和数字,但变量名的第一个字符不能是数字。

|类型 |描述 |
| ---------------|
|Float | 浮点类型 |
|int |有符号整型 |
|uint | 无符号整型|
|bool |布尔型 |
变量的作用域,和c语言一样,举个例子

  for(int i=0,i<10;++i){
  // loop body
  }

i 的作用域仅限于循环体内

变量的初始化
整型变量可以使用八进制,十进制,十六进制表示
浮点数必须包含一个小数点,并且可以向c语言中一样后面加个F或者f,也可以使用科学计数法表示
布尔值为true或者face

int i,num =1500;
float time = 1.23f;
bool  isRead = false;

不同类型的值不能进行隐式转换,比如int i = 10.3 编译器会报错的,那如何处理,我们需要借助构造函数 比如 :

 float f = 10.1;
 int t = int(f);
  • 聚合类型
    上面已经把基本类型讲过了,GLSL 基本类型可以进行组合使用,这样做的好处是能够和OpenGL 的数据相匹配,简化计算方法,GLSL 支持每种类基本型的二维,三维,四维的矢量运算,以及浮点类型的22,33,4*4 的浮点矩阵.

|基本类型|二维向量|三维向量|四维向量|矩阵类型|
|-----|
|float|vec2|vec3|vec4|mat2,mat3,mat4<p>mat2x2,mat2x3,mat4x4,</p><p>mat3x2,mat3x3,mat3x4,</p><p>mat4x2,mat4x3,mat4x4</p>|
|int|ivec2|ivec3|ivec4|...|
|uint|uvec2|uvec3|uvec4|...|
|bool|bvec2|bvec3|bvec4|...|
怎么初始化

 vec3 g = vec3(0.0,-9.8,3.0)

类型转换

 ivec3 ig = ivec3(g)

使用向量构造函数,将向量进行截短

vec4 color;
vec3 RGB = vec3(color);

使用构造函数,将向量进行拉长

vec3 RGB;
vec4 RGBA = vec4(RGB,0.5);

矩阵的构建
初始化为对角矩阵

mat3 m = mat3(1.0)

初始化为完整矩阵

mat3 m = mat3(1.0,2.0,3.0,
                  4.0,5.0,6.0,
                  7.0,8.0,9.0,)

还可以这样初始化

 vec3 v1 = vec3(1.0,2.0,3.0)
 vec3 v2 = vec3(1.0,2.0,3.0)
 vec3 v3 = vec3(1.0,2.0,3.0)
 mat3 m = mat3(v1,v2,v3)

你以为结束了吗,还可以这样初始化

vec2 col1 = vec2(1.0,2.0)
vec2 col2 = vec2(1.0,2.0)
vec2 col3 = vec2(1.0,2.0)
mat3 m = mat3(col1,1.0
              col2,2.0,
              col3,3.0)

接下来,讲一下如何访问向量和矩阵中的元素,大学中学过的,可能大家有些遗忘,那就带大家回顾一下.
访问向量

//可以通过名称访问向量
float red = color.r;
float v_y = velocity.y;
// 可以通过下标访问
float red = color[0];
float v_y = velocity[1];
//向量的另外一种访问方式,叫做搅拌式成分访问
vec3 lum = color.rrr;
///  移动向量的成分
vec4 color = color.abgr;
/// 唯一的限制是,一组向量只能使用一组成分,下面这样是错误的
vec4 color = color.rgza;
/// 如果访问超过范围也会报错
vec2 pos;
float z = pos.z;

访问矩阵

mat4 m = mat4(3.0);
vec4 zvec = mat4[2];
float yScale = m[1][1];

|成分访问名称|描述|
|---|
|(x,y,z,w)|位置相关|
|(r,g,b,a)|颜色相关|
|(s,t,p,q)|纹理坐标相关|
结构体
为甚要用结构体,结构体能将不同类型的数据从逻辑上结合在一起,结构体可以方便的把一组相关的数据传递给函数

struct Sun{
 float r;
 vec3 position;
 vec3 velour;
}

数组
GLSL 还支持数组类型,和c语言一样,很简单,写个例子大家看一下

 // 声明
float off[3];
float[3] coffe;
int indices[];
//  初始化
float coif[3] = float[3](1.0,1.0,1.0);
// GLES 数组提供了一个隐士的方法length() 获取数组长度
int length = coif.length()

类型限定符

顶点着色器的输入变量用关键字attribute 来限定
片段着色器的输入变量用关键字varying 来限定

注意在GLSL 1.4 中attribute 和varying都被删除,使用通用的 in,out 表示输入和输出
请看表

|类型限定符|描述|
|---|
|const|把变量标记为只读的编译器常量|
|in|指定变量量为着色器阶段的一个输入|
|out|指定变量为着色器的阶段的一个输出|
|uniform|指定这个值应从应用程序传给着色器,并在一个特定的图元中保持常量|
重点讲解一下关键字in的使用
in 用来限定着色器的输入,可能是顶点着色器或者片段着色器,片段着色器可以近一步进行限定

|in关键字限定符|说明|
|---|
|centroid|打开多采样,强制一个片段输入变量采样位于图元像素覆盖区域|
|smooth|以透视校正的方式插值片段输入变量|
|flat|不对片段输入差值|
|noperspective|线性差值片段变量|

out 类型限定符
用来限定着色器阶段的输出,顶点着色器可以使用centroid关键字限定输出,该关键字在片段着色器中也必须使用centroid 来限定一个输入(也就是说片段着色器中必须有一个和顶点着色器相同声明的变量)

uniform 类型限定符
uniform 限定了表示一个变量的值将有应用程序在着色器执行之前指定,并且在图元处理过程中不会发生变化,uniform 变量是有顶点着色器和片段着色器共享的,他们必须声明为全局变量
怎么使用呢?思考这样一个问题:创建一个着色器给图元使用这个指定的颜色着色.可以这样声明

uniform vec4 BaseColor;

思考: 在着色器内部可以通过名字来引用它,但是在程序中,我们应该如何设置它的值呢?
答:当GLSL 编译器连接到着色器程序中后,他会创建一个表格,其中包含了所有uniform 变量。为了在应用程序中设置BaseColor 的值,需要获取BaseColor 在表中的连接。这个是通过下面的函数获取的.
Glint glGetUniformLocation(GLuint program,const char *name)
参数 1:program 程序的标识
参数2 :name 着色器变量值得名称 如:"BaseColor" ,对于变量是数组的情况,可以直接指定数组名(array),也可以指定第一个元素的索引(array[0])

问:现在我们已经获取到了这个变量的值了,那怎么使用设置它的值呢?
答:可以使用下面的函数去设置它的值:
void glUniform*()
void glUniformMatrix*()

上面不是两个函数,是两类函数如 glUniform1f()

time = glGetUniformLocation(program,"time ");// 获取
glUniform(timeLoc,timeValue);// 设定值

uniform 块

问:为什么要引用uniform 块,它能解决什么问题?
答:大家有没有想过,当着色器程序复杂的时候,我们如何管理不同着色器程序和uniform 变量之间的关系,在连接着色器的时候,调用glLink的时候,产生uniform 位置,索引可能会发生变化,即便uniform变量的值是相同的,统一缓冲区对象提供了一种方法,既优化uniform变量的访问,又可以使用跨着着色器共享uniform值.
先看一段代码

uniform Matrices {
mat4 ModelView
mat4 ProjectView
mat4 color
}

这个就是uniform 块的声明,这个uniform 变量集合可以使用glMapBuffer() 这样的程序进行访问.
除了采样器,所有的类型,都允许放在一个uniform 块中,注意 ,uniform 块必须声明为全局域.

uniform 块布局

|布局限定符|说明|
|---|
|shared|指定uniform块在多个程序之间共享|
|packed|布局uniform块以使其使用的内存最小化,然而,这通常不允许块程序共享|
|std140|为uniform块使用OpenGL 规范描述默认布局|
|row_major|使的uniform快中的矩阵按照行主序的方式存储|
|column_major|指定矩阵应该按照主序的方式存储|
怎么使用,看下面代码

layout(shared,row_major) uniform{...} // 指定单一的uniform 块
layout(packed,column_major) uniform;// 括号中的多个限定选项必须用逗号隔开,要影响到所有后续uniform块的布局,这样指定所有uniform块都讲使用该布局,知道全局布局修改,或者自己包含一个布局,覆盖对全局的声明指定。

问: 怎么对uniform块进行访问呢?
第一步.获取uniform块索引

    GLuint gLGetUniformBlockIndex(GLuint program,const char* uniformBlockName)

返回和program相关的uniformBlockName 所指定的具名uniform 的索引,如果uniformBlockName 不是一个有效的uniform块,则返回GL_INVALID_INDEX.
第二步. 初始化一个缓冲区
使用glBindBuffer() 把缓冲区对象绑定到一个GL_UNIFORM_BUFFER 目标
第三步 . 确定着色器这个uniform块需要多大的空间
使用glGetActiveUniformBlockiv()来请求GL_UNIFORM_BLOCK_DATA_SIZE ,它返回了编译器生成的块的大小。
第四步。绑定

void glBindBufferRange(GLenum target,GLunit index,GLuint buffer,GLintptr offset,GLsizeiptr size);
void glBindBufferBase(GLenum target,GLuint index,GLuint buffer)

上面两个函数的作用,是讲缓冲区对象buffer 和 index 相关的uniform块关联起来,
参数1: target 可以是GL_UNIFORM_BUFFER 或者GL_TRANSFORM_FEEDBACK_BUFFER(用于变换反馈)
参数2:index 是和uniform相关的索引
参数3: buffer 缓冲区标识
参数4: offset 起始索引
参数5: size 大小

使用glBindBufferBase() 等同于使用offset等于0和size等于缓冲区对象的大小来调用glBindBufferRange()

调用这些函数有可能出现哪些bug:
size 小与0
offset+size 大于缓冲区大小
offset 或者size不是4的倍数
index 小与0
如果一个uniform和缓冲区对象建立的关系,可以使用影响缓冲区值得任何命令来初始化或者修改该块中的值。

思考: 如果多个着色器要共享一个uniform块,如何实现?
可以把一个指定名称的uniform块绑定到一个缓冲区对象,它避免了为每个程序分配一个不同的块索引。如何实现这种方式呢?在使用glLinkProgram() 之前,调用 glUniformBlockBinding()

Glint gUniformBlockBinding(GLuint program,GLuint uniformBlockIndex,GLuint uniformBlockBinding)

参数1: program 程序标识
参数2:uniformBlockIndex 程序块的索引
参数3:共享缓冲区的标识

思考:uniform 变量在一个uniform块中的布局,是由指定的布局限定符来控制的,而这是在编译和连接uniform块的时候进行的,如果使用默认的布局指定,需要确定uniform块中的每个变量的offset和数据存储size。为了做到这一点,我们将下面两个函数:
第一步:获取一个特定的uniform块的标识

void glGetUniformIndices(GLuint program,GLsizei uniformCount,const char **uniformName,GLuint *uniformIndices)

第二步. 调用glGetActiveUniformsiv()获取这个特定索引的offset和size

注意点
GLSL 并不能保证不同的着色器使用相同的计算产生相同的效果,这是因为,指令顺序累积的差别,编译后的指定顺序可能会差生微小的差别。
问题来了: 如果想要在每道着色器渲染时计算的位置完全相同,不然其出现这种微小的错误,怎么办呢?
答 :送你一个关键字 invariant ,强制不变型

invariant gl_position;
invariant centroid varying vec3 Color;

caring这个关键字,之前讲过,用于把顶点着色器的数据传给片段着色器,不变性变量,必须在顶点和片段着色器中都声明为invariant 。注意,可以在着色器中使用变量之前的任何使用对他应用的invariant关键字,并可以用他修改以前的变量。

小技巧:
在调试的时候,使用 #program STDGL inveriant(all) 就可以对所有varing 变量 加上不变形限制。可能性能会受点影响.因为保证不变性通常会进制GLSL 编译器所执行的那些优化。

语句

着色器真正工作是通过对值进行计算以及做出决策来完成的。CLSL 提供了一组简单操作符,便于创建更重算数操作来计算各种值。废话不多少,直接上表

|GLSL的操作符以及它们的优先级||||
|---|
|1|()|-|对操作进行聚组|
|2|[]|数组|数组下标|
|3|f()|函数|函数调用和构造器|
|4|.|结构|结构字段或方法访问|
|5|++ --|int、float、vec、mat*|后缀的自增或自减|
|6|++ --|int、float、vec
、mat*|前缀的自增或自减|
|7|+ - !|int、float、vec、mat*|正、负、求反|
|8|
/|int、float、vec、mat*|乘除操作|
|9|+ -|int、float、vec
、mat*|加减操作|
|10|<> <= >=|int、float、vec、mat*|关系操作|
|11|== !=|int、float、vec
、mat*|相等测试做操|
|12|&&|bool|逻辑与操作|
|13|^^|bool|逻辑异或操作|
|14|II|bool|逻辑或操作|
|15|a?b:c|bool、int、float、vec*、mat*、int、float、vec*、mat*|条件操作|
|16|=|int、float、vec*,mat*|赋值|
|17|+= -= *=/=|int、float、vec*,mat*|算数赋值|
|18|,|-|操作序列|

逻辑操作\循环结构 和 c语言一样,在这里就不过多说明.

  • 流控制语句

|语句|描述|
|---|
|break| 终止循环块的执行,并接着执行循环块后的代码|
|continue|终止当前那次循环,然后继续执行下一次循环|
|return|从当前自程序返回,可以同时返回一个值|
|discard|丢弃当前的片段并且终止着色器执行。discard只能用在片段着色器|

函数

函数允许使用一个函数调用代替一段经常出现的代码

float HornerEvalPolynormial(float coiff[10],float x);

函数和C 语言几乎一样,唯一的不同就是变量访问的限定符,接下来你可能会问有哪些限定符不一样,请看下面的这张表

|访问限定符|描述|
|in|值赋值到函数中|
|const in|只读的值|
|out|从函数中复制出来的值(在传递给函数前未初始化)|
|inout|值赋值到函数中,并从函数中赋值出来|

总结

着色器基本的语法,已经说得查不多了。接下来,我们要开始进阶了,请大家持续关注!