OpenGL ES _ 着色器_语法

2022-05-13 16:27:07 浏览数 (1)

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)加油!


内容详细讲解

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

  • 变量 首先要说一点,GLES 是一种强类型的语言,强类型形语言有个特点,每个变量必须进行声明,Swift 也是强类型语言,那为什么不用声明变量呢,因为它可以进行类型推断。GLES 有自己的变量类型,变量命名与c语言一样,可以使用字母,_ 和数字,但变量名的第一个字符不能是数字。
代码语言:javascript复制
|类型       |描述         |
 | ---------------|
 |Float    | 浮点类型   |
 |int        |有符号整型 |
 |uint      | 无符号整型|
 |bool     |布尔型       |
 

变量的作用域,和c语言一样,举个例子

代码语言:javascript复制
  for(int i=0,i<10;  i){
  // loop body
  }

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

变量的初始化

整型变量可以使用八进制,十进制,十六进制表示

浮点数必须包含一个小数点,并且可以向c语言中一样后面加个F或者f,也可以使用科学计数法表示

布尔值为true或者face

代码语言:javascript复制
int i,num =1500;
float time = 1.23f;
bool  isRead = false;

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

代码语言:javascript复制
 float f = 10.1;
 int t = int(f);
  • 聚合类型 上面已经把基本类型讲过了,GLSL 基本类型可以进行组合使用,这样做的好处是能够和OpenGL 的数据相匹配,简化计算方法,GLSL 支持每种类基本型的二维,三维,四维的矢量运算,以及浮点类型的2_2,3_3,4*4 的浮点矩阵.
代码语言:javascript复制
|基本类型|二维向量|三维向量|四维向量|矩阵类型|
 |-----|
 |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|...|

怎么初始化

代码语言:javascript复制
 vec3 g = vec3(0.0,-9.8,3.0)

类型转换

代码语言:javascript复制
 ivec3 ig = ivec3(g)

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

代码语言:javascript复制
vec4 color;
vec3 RGB = vec3(color);

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

代码语言:javascript复制
vec3 RGB;
vec4 RGBA = vec4(RGB,0.5);

矩阵的构建

初始化为对角矩阵

代码语言:javascript复制
mat3 m = mat3(1.0)

初始化为完整矩阵

代码语言:javascript复制
mat3 m = mat3(1.0,2.0,3.0,
                  4.0,5.0,6.0,
                  7.0,8.0,9.0,)

还可以这样初始化

代码语言:javascript复制
 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)

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

代码语言:javascript复制
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)

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

访问向量

代码语言:javascript复制
//可以通过名称访问向量
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;

访问矩阵

代码语言:javascript复制
mat4 m = mat4(3.0);
vec4 zvec = mat4[2];
float yScale = m[1][1];
代码语言:javascript复制
|成分访问名称|描述|
 |---|
 |(x,y,z,w)|位置相关|
 |(r,g,b,a)|颜色相关|
 |(s,t,p,q)|纹理坐标相关|
 

结构体

为甚要用结构体,结构体能将不同类型的数据从逻辑上结合在一起,结构体可以方便的把一组相关的数据传递给函数

代码语言:javascript复制
struct Sun{
 float r;
 vec3 position;
 vec3 velour;
}

数组

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

代码语言:javascript复制
 // 声明
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 表示输入和输出

请看表

代码语言:javascript复制
|类型限定符|描述|
 |---|
 |const|把变量标记为只读的编译器常量|
 |in|指定变量量为着色器阶段的一个输入|
 |out|指定变量为着色器的阶段的一个输出|
 |uniform|指定这个值应从应用程序传给着色器,并在一个特定的图元中保持常量|
 

重点讲解一下关键字in的使用

in 用来限定着色器的输入,可能是顶点着色器或者片段着色器,片段着色器可以近一步进行限定

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

out 类型限定符

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

uniform 类型限定符

uniform 限定了表示一个变量的值将有应用程序在着色器执行之前指定,并且在图元处理过程中不会发生变化,uniform 变量是有顶点着色器和片段着色器共享的,他们必须声明为全局变量

怎么使用呢?思考这样一个问题:创建一个着色器给图元使用这个指定的颜色着色.可以这样声明

代码语言:javascript复制
uniform vec4 BaseColor;

思考: 在着色器内部可以通过名字来引用它,但是在程序中,我们应该如何设置它的值呢?

答:当GLSL 编译器连接到着色器程序中后,他会创建一个表格,其中包含了所有uniform 变量。为了在应用程序中设置BaseColor 的值,需要获取BaseColor 在表中的连接。这个是通过下面的函数获取的.

Glint glGetUniformLocation(GLuint program,const char *name)

参数 1:program 程序的标识

参数2 :name 着色器变量值得名称 如:"BaseColor" ,对于变量是数组的情况,可以直接指定数组名(array),也可以指定第一个元素的索引(array0)

问:现在我们已经获取到了这个变量的值了,那怎么使用设置它的值呢?

答:可以使用下面的函数去设置它的值:

代码语言:javascript复制
 void glUniform*()
 void glUniformMatrix*()

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

代码语言:javascript复制
time = glGetUniformLocation(program,"time ");// 获取
glUniform(timeLoc,timeValue);// 设定值

uniform 块

问:为什么要引用uniform 块,它能解决什么问题?

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

先看一段代码

代码语言:javascript复制
uniform Matrices {
mat4 ModelView
mat4 ProjectView
mat4 color
}

这个就是uniform 块的声明,这个uniform 变量集合可以使用glMapBuffer() 这样的程序进行访问.

除了采样器,所有的类型,都允许放在一个uniform 块中,注意 ,uniform 块必须声明为全局域.

uniform 块布局

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

怎么使用,看下面代码

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

问: 怎么对uniform块进行访问呢?

第一步.获取uniform块索引

代码语言:javascript复制
    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 ,它返回了编译器生成的块的大小。

第四步。绑定

代码语言:javascript复制
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()

代码语言:javascript复制
Glint gUniformBlockBinding(GLuint program,GLuint uniformBlockIndex,GLuint uniformBlockBinding)

参数1: program 程序标识

参数2:uniformBlockIndex 程序块的索引

参数3:共享缓冲区的标识

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

第一步:获取一个特定的uniform块的标识

代码语言:javascript复制
void glGetUniformIndices(GLuint program,GLsizei uniformCount,const char **uniformName,GLuint *uniformIndices)

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

注意点

GLSL 并不能保证不同的着色器使用相同的计算产生相同的效果,这是因为,指令顺序累积的差别,编译后的指定顺序可能会差生微小的差别。

问题来了: 如果想要在每道着色器渲染时计算的位置完全相同,不然其出现这种微小的错误,怎么办呢?

答 :送你一个关键字 invariant ,强制不变型

代码语言:javascript复制
invariant gl_position;
invariant centroid varying vec3 Color;

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

小技巧:

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

语句

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

代码语言:javascript复制
|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语言一样,在这里就不过多说明.

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

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

代码语言:javascript复制
float HornerEvalPolynormial(float coiff[10],float x);

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

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

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

0 人点赞