OpenGL ES _ 着色器_语法

来源:互联网 时间:2017-01-22


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



学习是一件开心的额事情
学习那些内容
程序从什么地方执行
声明变量
构造函数
聚合类型
如何访问向量和矩阵中的元素
结构
数组
类型限定符
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

mat2x2,mat2x3,mat4x4,


mat3x2,mat3x3,mat3x4,


mat4x2,mat4x3,mat4x4





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 用来限定着色器的输入,可能是顶点着色器或者片段着色器,片段着色器可以近一步进行限定










centroid
打开多采样,强制一个片段输入变量采样位于图元像素覆盖区域


smooth
以透视校正的方式插值片段输入变量


flat
不对片段输入差值


noperspective
线性差值片段变量

in关键字限定符说明

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 提供了一组简单操作符,便于创建更重算数操作来计算各种值。废话不多少,直接上表












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
,
-
操作序列

GLSL的操作符以及它们的优先级

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


流控制语句








break
终止循环块的执行,并接着执行循环块后的代码


continue
终止当前那次循环,然后继续执行下一次循环


return
从当前自程序返回,可以同时返回一个值


discard
丢弃当前的片段并且终止着色器执行。discard只能用在片段着色器

语句描述

函数

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


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

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


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


总结

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




相关阅读:
Top