OpenGL ES _ 着色器_语法

作者: 酷走天涯 | 来源:发表于2016-08-12 22:35 被阅读2334次

    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<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|值赋值到函数中,并从函数中赋值出来|

    总结

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

    相关文章

      网友评论

        本文标题:OpenGL ES _ 着色器_语法

        本文链接:https://www.haomeiwen.com/subject/fvlesttx.html